cleanup
This commit is contained in:
parent
cdc4c60618
commit
8fece91f75
16 changed files with 2275 additions and 2252 deletions
710
agent.go
Normal file → Executable file
710
agent.go
Normal file → Executable file
|
@ -1,355 +1,355 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BrowserVersion struct {
|
type BrowserVersion struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Global float64 `json:"global"`
|
Global float64 `json:"global"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type BrowserData struct {
|
type BrowserData struct {
|
||||||
Firefox []BrowserVersion `json:"firefox"`
|
Firefox []BrowserVersion `json:"firefox"`
|
||||||
Chromium []BrowserVersion `json:"chrome"`
|
Chromium []BrowserVersion `json:"chrome"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cache = struct {
|
cache = struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
data map[string]string
|
data map[string]string
|
||||||
}{
|
}{
|
||||||
data: make(map[string]string),
|
data: make(map[string]string),
|
||||||
}
|
}
|
||||||
browserCache = struct {
|
browserCache = struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
data BrowserData
|
data BrowserData
|
||||||
expires time.Time
|
expires time.Time
|
||||||
}{
|
}{
|
||||||
expires: time.Now(),
|
expires: time.Now(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func fetchLatestBrowserVersions() (BrowserData, error) {
|
func fetchLatestBrowserVersions() (BrowserData, error) {
|
||||||
url := "https://raw.githubusercontent.com/Fyrd/caniuse/master/fulldata-json/data-2.0.json"
|
url := "https://raw.githubusercontent.com/Fyrd/caniuse/master/fulldata-json/data-2.0.json"
|
||||||
|
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return BrowserData{}, err
|
return BrowserData{}, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return BrowserData{}, err
|
return BrowserData{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var rawData map[string]interface{}
|
var rawData map[string]interface{}
|
||||||
if err := json.Unmarshal(body, &rawData); err != nil {
|
if err := json.Unmarshal(body, &rawData); err != nil {
|
||||||
return BrowserData{}, err
|
return BrowserData{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
stats := rawData["agents"].(map[string]interface{})
|
stats := rawData["agents"].(map[string]interface{})
|
||||||
|
|
||||||
var data BrowserData
|
var data BrowserData
|
||||||
|
|
||||||
if firefoxData, ok := stats["firefox"].(map[string]interface{}); ok {
|
if firefoxData, ok := stats["firefox"].(map[string]interface{}); ok {
|
||||||
for version, usage := range firefoxData["usage_global"].(map[string]interface{}) {
|
for version, usage := range firefoxData["usage_global"].(map[string]interface{}) {
|
||||||
data.Firefox = append(data.Firefox, BrowserVersion{
|
data.Firefox = append(data.Firefox, BrowserVersion{
|
||||||
Version: version,
|
Version: version,
|
||||||
Global: usage.(float64),
|
Global: usage.(float64),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if chromeData, ok := stats["chrome"].(map[string]interface{}); ok {
|
if chromeData, ok := stats["chrome"].(map[string]interface{}); ok {
|
||||||
for version, usage := range chromeData["usage_global"].(map[string]interface{}) {
|
for version, usage := range chromeData["usage_global"].(map[string]interface{}) {
|
||||||
data.Chromium = append(data.Chromium, BrowserVersion{
|
data.Chromium = append(data.Chromium, BrowserVersion{
|
||||||
Version: version,
|
Version: version,
|
||||||
Global: usage.(float64),
|
Global: usage.(float64),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLatestBrowserVersions() (BrowserData, error) {
|
func getLatestBrowserVersions() (BrowserData, error) {
|
||||||
browserCache.RLock()
|
browserCache.RLock()
|
||||||
if time.Now().Before(browserCache.expires) {
|
if time.Now().Before(browserCache.expires) {
|
||||||
data := browserCache.data
|
data := browserCache.data
|
||||||
browserCache.RUnlock()
|
browserCache.RUnlock()
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
browserCache.RUnlock()
|
browserCache.RUnlock()
|
||||||
|
|
||||||
data, err := fetchLatestBrowserVersions()
|
data, err := fetchLatestBrowserVersions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return BrowserData{}, err
|
return BrowserData{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
browserCache.Lock()
|
browserCache.Lock()
|
||||||
browserCache.data = data
|
browserCache.data = data
|
||||||
browserCache.expires = time.Now().Add(24 * time.Hour)
|
browserCache.expires = time.Now().Add(24 * time.Hour)
|
||||||
browserCache.Unlock()
|
browserCache.Unlock()
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func randomUserAgent() (string, error) {
|
func randomUserAgent() (string, error) {
|
||||||
browsers, err := getLatestBrowserVersions()
|
browsers, err := getLatestBrowserVersions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
// Simulated browser usage statistics (in percentages)
|
// Simulated browser usage statistics (in percentages)
|
||||||
usageStats := map[string]float64{
|
usageStats := map[string]float64{
|
||||||
"Firefox": 30.0,
|
"Firefox": 30.0,
|
||||||
"Chromium": 70.0,
|
"Chromium": 70.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the probabilities for the versions
|
// Calculate the probabilities for the versions
|
||||||
probabilities := []float64{0.5, 0.25, 0.125, 0.0625, 0.03125, 0.015625, 0.0078125, 0.00390625}
|
probabilities := []float64{0.5, 0.25, 0.125, 0.0625, 0.03125, 0.015625, 0.0078125, 0.00390625}
|
||||||
|
|
||||||
// Select a browser based on usage statistics
|
// Select a browser based on usage statistics
|
||||||
browserType := ""
|
browserType := ""
|
||||||
randVal := rand.Float64() * 100
|
randVal := rand.Float64() * 100
|
||||||
cumulative := 0.0
|
cumulative := 0.0
|
||||||
for browser, usage := range usageStats {
|
for browser, usage := range usageStats {
|
||||||
cumulative += usage
|
cumulative += usage
|
||||||
if randVal < cumulative {
|
if randVal < cumulative {
|
||||||
browserType = browser
|
browserType = browser
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var versions []BrowserVersion
|
var versions []BrowserVersion
|
||||||
switch browserType {
|
switch browserType {
|
||||||
case "Firefox":
|
case "Firefox":
|
||||||
versions = browsers.Firefox
|
versions = browsers.Firefox
|
||||||
case "Chromium":
|
case "Chromium":
|
||||||
versions = browsers.Chromium
|
versions = browsers.Chromium
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(versions) == 0 {
|
if len(versions) == 0 {
|
||||||
return "", fmt.Errorf("no versions found for browser: %s", browserType)
|
return "", fmt.Errorf("no versions found for browser: %s", browserType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort versions by usage (descending order)
|
// Sort versions by usage (descending order)
|
||||||
sort.Slice(versions, func(i, j int) bool {
|
sort.Slice(versions, func(i, j int) bool {
|
||||||
return versions[i].Global > versions[j].Global
|
return versions[i].Global > versions[j].Global
|
||||||
})
|
})
|
||||||
|
|
||||||
// Select a version based on the probabilities
|
// Select a version based on the probabilities
|
||||||
version := ""
|
version := ""
|
||||||
randVal = rand.Float64()
|
randVal = rand.Float64()
|
||||||
cumulative = 0.0
|
cumulative = 0.0
|
||||||
for i, p := range probabilities {
|
for i, p := range probabilities {
|
||||||
cumulative += p
|
cumulative += p
|
||||||
if randVal < cumulative && i < len(versions) {
|
if randVal < cumulative && i < len(versions) {
|
||||||
version = versions[i].Version
|
version = versions[i].Version
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if version == "" {
|
if version == "" {
|
||||||
version = versions[len(versions)-1].Version
|
version = versions[len(versions)-1].Version
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the user agent string
|
// Generate the user agent string
|
||||||
userAgent := generateUserAgent(browserType, version)
|
userAgent := generateUserAgent(browserType, version)
|
||||||
return userAgent, nil
|
return userAgent, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateUserAgent(browser, version string) string {
|
func generateUserAgent(browser, version string) string {
|
||||||
oses := []struct {
|
oses := []struct {
|
||||||
os string
|
os string
|
||||||
probability float64
|
probability float64
|
||||||
}{
|
}{
|
||||||
{"Windows NT 10.0; Win64; x64", 44.0},
|
{"Windows NT 10.0; Win64; x64", 44.0},
|
||||||
{"Windows NT 11.0; Win64; x64", 44.0},
|
{"Windows NT 11.0; Win64; x64", 44.0},
|
||||||
{"X11; Linux x86_64", 1.0},
|
{"X11; Linux x86_64", 1.0},
|
||||||
{"X11; Ubuntu; Linux x86_64", 1.0},
|
{"X11; Ubuntu; Linux x86_64", 1.0},
|
||||||
{"Macintosh; Intel Mac OS X 10_15_7", 10.0},
|
{"Macintosh; Intel Mac OS X 10_15_7", 10.0},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select an OS based on probabilities
|
// Select an OS based on probabilities
|
||||||
randVal := rand.Float64() * 100
|
randVal := rand.Float64() * 100
|
||||||
cumulative := 0.0
|
cumulative := 0.0
|
||||||
selectedOS := ""
|
selectedOS := ""
|
||||||
for _, os := range oses {
|
for _, os := range oses {
|
||||||
cumulative += os.probability
|
cumulative += os.probability
|
||||||
if randVal < cumulative {
|
if randVal < cumulative {
|
||||||
selectedOS = os.os
|
selectedOS = os.os
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch browser {
|
switch browser {
|
||||||
case "Firefox":
|
case "Firefox":
|
||||||
return fmt.Sprintf("Mozilla/5.0 (%s; rv:%s) Gecko/20100101 Firefox/%s", selectedOS, version, version)
|
return fmt.Sprintf("Mozilla/5.0 (%s; rv:%s) Gecko/20100101 Firefox/%s", selectedOS, version, version)
|
||||||
case "Chromium":
|
case "Chromium":
|
||||||
return fmt.Sprintf("Mozilla/5.0 (%s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", selectedOS, version)
|
return fmt.Sprintf("Mozilla/5.0 (%s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", selectedOS, version)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateCachedUserAgents(newVersions BrowserData) {
|
func updateCachedUserAgents(newVersions BrowserData) {
|
||||||
cache.Lock()
|
cache.Lock()
|
||||||
defer cache.Unlock()
|
defer cache.Unlock()
|
||||||
for key, userAgent := range cache.data {
|
for key, userAgent := range cache.data {
|
||||||
randVal := rand.Float64()
|
randVal := rand.Float64()
|
||||||
if randVal < 0.5 {
|
if randVal < 0.5 {
|
||||||
updatedUserAgent := updateUserAgentVersion(userAgent, newVersions)
|
updatedUserAgent := updateUserAgentVersion(userAgent, newVersions)
|
||||||
cache.data[key] = updatedUserAgent
|
cache.data[key] = updatedUserAgent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUserAgentVersion(userAgent string, newVersions BrowserData) string {
|
func updateUserAgentVersion(userAgent string, newVersions BrowserData) string {
|
||||||
// Parse the current user agent to extract browser and version
|
// Parse the current user agent to extract browser and version
|
||||||
var browserType, version string
|
var browserType, version string
|
||||||
if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", &version); err == nil {
|
if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", &version); err == nil {
|
||||||
browserType = "Chromium"
|
browserType = "Chromium"
|
||||||
} else if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (Windows NT 11.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", &version); err == nil {
|
} else if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (Windows NT 11.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", &version); err == nil {
|
||||||
browserType = "Chromium"
|
browserType = "Chromium"
|
||||||
} else if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", &version); err == nil {
|
} else if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", &version); err == nil {
|
||||||
browserType = "Chromium"
|
browserType = "Chromium"
|
||||||
} else if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (X11; Ubuntu; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", &version); err == nil {
|
} else if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (X11; Ubuntu; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", &version); err == nil {
|
||||||
browserType = "Chromium"
|
browserType = "Chromium"
|
||||||
} else if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", &version); err == nil {
|
} else if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", &version); err == nil {
|
||||||
browserType = "Chromium"
|
browserType = "Chromium"
|
||||||
} else if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:%s) Gecko/20100101 Firefox/%s", &version, &version); err == nil {
|
} else if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:%s) Gecko/20100101 Firefox/%s", &version, &version); err == nil {
|
||||||
browserType = "Firefox"
|
browserType = "Firefox"
|
||||||
} else if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (Windows NT 11.0; Win64; x64; rv:%s) Gecko/20100101 Firefox/%s", &version, &version); err == nil {
|
} else if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (Windows NT 11.0; Win64; x64; rv:%s) Gecko/20100101 Firefox/%s", &version, &version); err == nil {
|
||||||
browserType = "Firefox"
|
browserType = "Firefox"
|
||||||
} else if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (X11; Linux x86_64; rv:%s) Gecko/20100101 Firefox/%s", &version, &version); err == nil {
|
} else if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (X11; Linux x86_64; rv:%s) Gecko/20100101 Firefox/%s", &version, &version); err == nil {
|
||||||
browserType = "Firefox"
|
browserType = "Firefox"
|
||||||
} else if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:%s) Gecko/20100101 Firefox/%s", &version, &version); err == nil {
|
} else if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:%s) Gecko/20100101 Firefox/%s", &version, &version); err == nil {
|
||||||
browserType = "Firefox"
|
browserType = "Firefox"
|
||||||
} else if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7; rv:%s) Gecko/20100101 Firefox/%s", &version, &version); err == nil {
|
} else if _, err := fmt.Sscanf(userAgent, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7; rv:%s) Gecko/20100101 Firefox/%s", &version, &version); err == nil {
|
||||||
browserType = "Firefox"
|
browserType = "Firefox"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the latest version for the browser type
|
// Get the latest version for the browser type
|
||||||
var latestVersion string
|
var latestVersion string
|
||||||
if browserType == "Firefox" {
|
if browserType == "Firefox" {
|
||||||
latestVersion = newVersions.Firefox[0].Version
|
latestVersion = newVersions.Firefox[0].Version
|
||||||
} else if browserType == "Chromium" {
|
} else if browserType == "Chromium" {
|
||||||
latestVersion = newVersions.Chromium[0].Version
|
latestVersion = newVersions.Chromium[0].Version
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the user agent string with the new version
|
// Update the user agent string with the new version
|
||||||
return generateUserAgent(browserType, latestVersion)
|
return generateUserAgent(browserType, latestVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func periodicUpdate() {
|
func periodicUpdate() {
|
||||||
for {
|
for {
|
||||||
// Sleep for a random interval between 1 and 2 days
|
// Sleep for a random interval between 1 and 2 days
|
||||||
time.Sleep(time.Duration(24+rand.Intn(24)) * time.Hour)
|
time.Sleep(time.Duration(24+rand.Intn(24)) * time.Hour)
|
||||||
|
|
||||||
// Fetch the latest browser versions
|
// Fetch the latest browser versions
|
||||||
newVersions, err := fetchLatestBrowserVersions()
|
newVersions, err := fetchLatestBrowserVersions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error fetching latest browser versions:", err)
|
printWarn("Error fetching latest browser versions: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the browser version cache
|
// Update the browser version cache
|
||||||
browserCache.Lock()
|
browserCache.Lock()
|
||||||
browserCache.data = newVersions
|
browserCache.data = newVersions
|
||||||
browserCache.expires = time.Now().Add(24 * time.Hour)
|
browserCache.expires = time.Now().Add(24 * time.Hour)
|
||||||
browserCache.Unlock()
|
browserCache.Unlock()
|
||||||
|
|
||||||
// Update the cached user agents
|
// Update the cached user agents
|
||||||
updateCachedUserAgents(newVersions)
|
updateCachedUserAgents(newVersions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserAgent(cacheKey string) (string, error) {
|
func GetUserAgent(cacheKey string) (string, error) {
|
||||||
cache.RLock()
|
cache.RLock()
|
||||||
userAgent, found := cache.data[cacheKey]
|
userAgent, found := cache.data[cacheKey]
|
||||||
cache.RUnlock()
|
cache.RUnlock()
|
||||||
|
|
||||||
if found {
|
if found {
|
||||||
return userAgent, nil
|
return userAgent, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
userAgent, err := randomUserAgent()
|
userAgent, err := randomUserAgent()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.Lock()
|
cache.Lock()
|
||||||
cache.data[cacheKey] = userAgent
|
cache.data[cacheKey] = userAgent
|
||||||
cache.Unlock()
|
cache.Unlock()
|
||||||
|
|
||||||
return userAgent, nil
|
return userAgent, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetNewUserAgent(cacheKey string) (string, error) {
|
func GetNewUserAgent(cacheKey string) (string, error) {
|
||||||
userAgent, err := randomUserAgent()
|
userAgent, err := randomUserAgent()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.Lock()
|
cache.Lock()
|
||||||
cache.data[cacheKey] = userAgent
|
cache.data[cacheKey] = userAgent
|
||||||
cache.Unlock()
|
cache.Unlock()
|
||||||
|
|
||||||
return userAgent, nil
|
return userAgent, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
go periodicUpdate()
|
go periodicUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// func main() {
|
// func main() {
|
||||||
// go periodicUpdate() // not needed here
|
// go periodicUpdate() // not needed here
|
||||||
|
|
||||||
// cacheKey := "image-search"
|
// cacheKey := "image-search"
|
||||||
// userAgent, err := GetUserAgent(cacheKey)
|
// userAgent, err := GetUserAgent(cacheKey)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// fmt.Println("Error:", err)
|
// fmt.Println("Error:", err)
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fmt.Println("Generated User Agent:", userAgent)
|
// fmt.Println("Generated User Agent:", userAgent)
|
||||||
|
|
||||||
// // Request a new user agent for the same key
|
// // Request a new user agent for the same key
|
||||||
// newUserAgent, err := GetNewUserAgent(cacheKey)
|
// newUserAgent, err := GetNewUserAgent(cacheKey)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// fmt.Println("Error:", err)
|
// fmt.Println("Error:", err)
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fmt.Println("New User Agent:", newUserAgent)
|
// fmt.Println("New User Agent:", newUserAgent)
|
||||||
|
|
||||||
// AcacheKey := "image-search"
|
// AcacheKey := "image-search"
|
||||||
// AuserAgent, err := GetUserAgent(AcacheKey)
|
// AuserAgent, err := GetUserAgent(AcacheKey)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// fmt.Println("Error:", err)
|
// fmt.Println("Error:", err)
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fmt.Println("Generated User Agent:", AuserAgent)
|
// fmt.Println("Generated User Agent:", AuserAgent)
|
||||||
|
|
||||||
// DcacheKey := "image-search"
|
// DcacheKey := "image-search"
|
||||||
// DuserAgent, err := GetUserAgent(DcacheKey)
|
// DuserAgent, err := GetUserAgent(DcacheKey)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// fmt.Println("Error:", err)
|
// fmt.Println("Error:", err)
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fmt.Println("Generated User Agent:", DuserAgent)
|
// fmt.Println("Generated User Agent:", DuserAgent)
|
||||||
|
|
||||||
// }
|
// }
|
||||||
|
|
99
common.go
Normal file → Executable file
99
common.go
Normal file → Executable file
|
@ -1,50 +1,49 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
"strings"
|
||||||
"strings"
|
)
|
||||||
)
|
|
||||||
|
var (
|
||||||
var (
|
funcs = template.FuncMap{
|
||||||
funcs = template.FuncMap{
|
"sub": func(a, b int) int {
|
||||||
"sub": func(a, b int) int {
|
return a - b
|
||||||
return a - b
|
},
|
||||||
},
|
"add": func(a, b int) int {
|
||||||
"add": func(a, b int) int {
|
return a + b
|
||||||
return a + b
|
},
|
||||||
},
|
}
|
||||||
}
|
)
|
||||||
)
|
|
||||||
|
func generateStrongRandomString(length int) string {
|
||||||
func generateStrongRandomString(length int) string {
|
bytes := make([]byte, length)
|
||||||
bytes := make([]byte, length)
|
_, err := rand.Read(bytes)
|
||||||
_, err := rand.Read(bytes)
|
if err != nil {
|
||||||
if err != nil {
|
printErr("Error generating random string: %v", err)
|
||||||
log.Fatalf("Error generating random string: %v", err)
|
}
|
||||||
}
|
return base64.URLEncoding.EncodeToString(bytes)[:length]
|
||||||
return base64.URLEncoding.EncodeToString(bytes)[:length]
|
}
|
||||||
}
|
|
||||||
|
// Checks if the URL already includes a protocol
|
||||||
// Checks if the URL already includes a protocol
|
func hasProtocol(url string) bool {
|
||||||
func hasProtocol(url string) bool {
|
return strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://")
|
||||||
return strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://")
|
}
|
||||||
}
|
|
||||||
|
// Checks if the domain is a local address
|
||||||
// Checks if the domain is a local address
|
func isLocalAddress(domain string) bool {
|
||||||
func isLocalAddress(domain string) bool {
|
return domain == "localhost" || strings.HasPrefix(domain, "127.") || strings.HasPrefix(domain, "192.168.") || strings.HasPrefix(domain, "10.")
|
||||||
return domain == "localhost" || strings.HasPrefix(domain, "127.") || strings.HasPrefix(domain, "192.168.") || strings.HasPrefix(domain, "10.")
|
}
|
||||||
}
|
|
||||||
|
// Ensures that HTTP or HTTPS is befor the adress if needed
|
||||||
// Ensures that HTTP or HTTPS is befor the adress if needed
|
func addProtocol(domain string) string {
|
||||||
func addProtocol(domain string) string {
|
if hasProtocol(domain) {
|
||||||
if hasProtocol(domain) {
|
return domain
|
||||||
return domain
|
}
|
||||||
}
|
if isLocalAddress(domain) {
|
||||||
if isLocalAddress(domain) {
|
return "http://" + domain
|
||||||
return "http://" + domain
|
}
|
||||||
}
|
return "https://" + domain
|
||||||
return "https://" + domain
|
}
|
||||||
}
|
|
||||||
|
|
515
files.go
Normal file → Executable file
515
files.go
Normal file → Executable file
|
@ -1,258 +1,257 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
"net/http"
|
||||||
"net/http"
|
"net/url"
|
||||||
"net/url"
|
"regexp"
|
||||||
"regexp"
|
"sort"
|
||||||
"sort"
|
"strconv"
|
||||||
"strconv"
|
"strings"
|
||||||
"strings"
|
"time"
|
||||||
"time"
|
)
|
||||||
)
|
|
||||||
|
type Settings struct {
|
||||||
type Settings struct {
|
UxLang string
|
||||||
UxLang string
|
Safe string
|
||||||
Safe string
|
}
|
||||||
}
|
|
||||||
|
type TorrentSite interface {
|
||||||
type TorrentSite interface {
|
Name() string
|
||||||
Name() string
|
Search(query string, category string) ([]TorrentResult, error)
|
||||||
Search(query string, category string) ([]TorrentResult, error)
|
}
|
||||||
}
|
|
||||||
|
var (
|
||||||
var (
|
torrentGalaxy TorrentSite
|
||||||
torrentGalaxy TorrentSite
|
nyaa TorrentSite
|
||||||
nyaa TorrentSite
|
thePirateBay TorrentSite
|
||||||
thePirateBay TorrentSite
|
rutor TorrentSite
|
||||||
rutor TorrentSite
|
)
|
||||||
)
|
|
||||||
|
var fileResultsChan = make(chan []TorrentResult)
|
||||||
var fileResultsChan = make(chan []TorrentResult)
|
|
||||||
|
func initializeTorrentSites() {
|
||||||
func initializeTorrentSites() {
|
torrentGalaxy = NewTorrentGalaxy()
|
||||||
torrentGalaxy = NewTorrentGalaxy()
|
// nyaa = NewNyaa()
|
||||||
// nyaa = NewNyaa()
|
thePirateBay = NewThePirateBay()
|
||||||
thePirateBay = NewThePirateBay()
|
// rutor = NewRutor()
|
||||||
// rutor = NewRutor()
|
}
|
||||||
}
|
|
||||||
|
func handleFileSearch(w http.ResponseWriter, settings UserSettings, query string, page int) {
|
||||||
func handleFileSearch(w http.ResponseWriter, settings UserSettings, query, safe, lang string, page int) {
|
startTime := time.Now()
|
||||||
startTime := time.Now()
|
|
||||||
|
cacheKey := CacheKey{Query: query, Page: page, Safe: settings.SafeSearch == "true", Lang: settings.Language, Type: "file"}
|
||||||
cacheKey := CacheKey{Query: query, Page: page, Safe: safe == "true", Lang: lang, Type: "file"}
|
combinedResults := getFileResultsFromCacheOrFetch(cacheKey, query, settings.SafeSearch, settings.Language, page)
|
||||||
combinedResults := getFileResultsFromCacheOrFetch(cacheKey, query, safe, lang, page)
|
|
||||||
|
sort.Slice(combinedResults, func(i, j int) bool { return combinedResults[i].Seeders > combinedResults[j].Seeders })
|
||||||
sort.Slice(combinedResults, func(i, j int) bool { return combinedResults[i].Seeders > combinedResults[j].Seeders })
|
|
||||||
|
elapsedTime := time.Since(startTime)
|
||||||
elapsedTime := time.Since(startTime)
|
funcMap := template.FuncMap{
|
||||||
funcMap := template.FuncMap{
|
"sub": func(a, b int) int { return a - b },
|
||||||
"sub": func(a, b int) int { return a - b },
|
"add": func(a, b int) int { return a + b },
|
||||||
"add": func(a, b int) int { return a + b },
|
}
|
||||||
}
|
tmpl, err := template.New("files.html").Funcs(funcMap).ParseFiles("templates/files.html")
|
||||||
tmpl, err := template.New("files.html").Funcs(funcMap).ParseFiles("templates/files.html")
|
if err != nil {
|
||||||
if err != nil {
|
printErr("Failed to load template: %v", err)
|
||||||
log.Printf("Failed to load template: %v", err)
|
http.Error(w, "Failed to load template", http.StatusInternalServerError)
|
||||||
http.Error(w, "Failed to load template", http.StatusInternalServerError)
|
return
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
data := struct {
|
||||||
data := struct {
|
Results []TorrentResult
|
||||||
Results []TorrentResult
|
Query string
|
||||||
Query string
|
Fetched string
|
||||||
Fetched string
|
Category string
|
||||||
Category string
|
Sort string
|
||||||
Sort string
|
HasPrevPage bool
|
||||||
HasPrevPage bool
|
HasNextPage bool
|
||||||
HasNextPage bool
|
Page int
|
||||||
Page int
|
Settings Settings
|
||||||
Settings Settings
|
Theme string
|
||||||
Theme string
|
}{
|
||||||
}{
|
Results: combinedResults,
|
||||||
Results: combinedResults,
|
Query: query,
|
||||||
Query: query,
|
Fetched: fmt.Sprintf("%.2f", elapsedTime.Seconds()),
|
||||||
Fetched: fmt.Sprintf("%.2f", elapsedTime.Seconds()),
|
Category: "all",
|
||||||
Category: "all",
|
Sort: "seed",
|
||||||
Sort: "seed",
|
HasPrevPage: page > 1,
|
||||||
HasPrevPage: page > 1,
|
HasNextPage: len(combinedResults) > 0,
|
||||||
HasNextPage: len(combinedResults) > 0,
|
Page: page,
|
||||||
Page: page,
|
Settings: Settings{UxLang: settings.Language, Safe: settings.SafeSearch}, // Now this is painful, are there two Settings variables??
|
||||||
Settings: Settings{UxLang: lang, Safe: safe}, // Now this is painful, are there two Settings variables??
|
Theme: settings.Theme,
|
||||||
Theme: settings.Theme,
|
}
|
||||||
}
|
|
||||||
|
// // Debugging: Print results before rendering template
|
||||||
// // Debugging: Print results before rendering template
|
// for _, result := range combinedResults {
|
||||||
// for _, result := range combinedResults {
|
// fmt.Printf("Title: %s, Magnet: %s\n", result.Title, result.Magnet)
|
||||||
// fmt.Printf("Title: %s, Magnet: %s\n", result.Title, result.Magnet)
|
// }
|
||||||
// }
|
|
||||||
|
if err := tmpl.Execute(w, data); err != nil {
|
||||||
if err := tmpl.Execute(w, data); err != nil {
|
printErr("Failed to render template: %v", err)
|
||||||
log.Printf("Failed to render template: %v", err)
|
http.Error(w, "Failed to render template", http.StatusInternalServerError)
|
||||||
http.Error(w, "Failed to render template", http.StatusInternalServerError)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
func getFileResultsFromCacheOrFetch(cacheKey CacheKey, query, safe, lang string, page int) []TorrentResult {
|
||||||
func getFileResultsFromCacheOrFetch(cacheKey CacheKey, query, safe, lang string, page int) []TorrentResult {
|
cacheChan := make(chan []SearchResult)
|
||||||
cacheChan := make(chan []SearchResult)
|
var combinedResults []TorrentResult
|
||||||
var combinedResults []TorrentResult
|
|
||||||
|
go func() {
|
||||||
go func() {
|
results, exists := resultsCache.Get(cacheKey)
|
||||||
results, exists := resultsCache.Get(cacheKey)
|
if exists {
|
||||||
if exists {
|
printInfo("Cache hit")
|
||||||
log.Println("Cache hit")
|
cacheChan <- results
|
||||||
cacheChan <- results
|
} else {
|
||||||
} else {
|
printInfo("Cache miss")
|
||||||
log.Println("Cache miss")
|
cacheChan <- nil
|
||||||
cacheChan <- nil
|
}
|
||||||
}
|
}()
|
||||||
}()
|
|
||||||
|
select {
|
||||||
select {
|
case results := <-cacheChan:
|
||||||
case results := <-cacheChan:
|
if results == nil {
|
||||||
if results == nil {
|
combinedResults = fetchFileResults(query, safe, lang, page)
|
||||||
combinedResults = fetchFileResults(query, safe, lang, page)
|
if len(combinedResults) > 0 {
|
||||||
if len(combinedResults) > 0 {
|
resultsCache.Set(cacheKey, convertToSearchResults(combinedResults))
|
||||||
resultsCache.Set(cacheKey, convertToSearchResults(combinedResults))
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
_, torrentResults, _ := convertToSpecificResults(results)
|
||||||
_, torrentResults, _ := convertToSpecificResults(results)
|
combinedResults = torrentResults
|
||||||
combinedResults = torrentResults
|
}
|
||||||
}
|
case <-time.After(2 * time.Second):
|
||||||
case <-time.After(2 * time.Second):
|
printInfo("Cache check timeout")
|
||||||
log.Println("Cache check timeout")
|
combinedResults = fetchFileResults(query, safe, lang, page)
|
||||||
combinedResults = fetchFileResults(query, safe, lang, page)
|
if len(combinedResults) > 0 {
|
||||||
if len(combinedResults) > 0 {
|
resultsCache.Set(cacheKey, convertToSearchResults(combinedResults))
|
||||||
resultsCache.Set(cacheKey, convertToSearchResults(combinedResults))
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return combinedResults
|
||||||
return combinedResults
|
}
|
||||||
}
|
|
||||||
|
func fetchFileResults(query, safe, lang string, page int) []TorrentResult {
|
||||||
func fetchFileResults(query, safe, lang string, page int) []TorrentResult {
|
sites := []TorrentSite{torrentGalaxy, nyaa, thePirateBay, rutor}
|
||||||
sites := []TorrentSite{torrentGalaxy, nyaa, thePirateBay, rutor}
|
results := []TorrentResult{}
|
||||||
results := []TorrentResult{}
|
|
||||||
|
for _, site := range sites {
|
||||||
for _, site := range sites {
|
if site == nil {
|
||||||
if site == nil {
|
continue
|
||||||
continue
|
}
|
||||||
}
|
res, err := site.Search(query, "all")
|
||||||
res, err := site.Search(query, "all")
|
if err != nil {
|
||||||
if err != nil {
|
continue
|
||||||
continue
|
}
|
||||||
}
|
for _, r := range res {
|
||||||
for _, r := range res {
|
r.Magnet = removeMagnetLink(r.Magnet) // Remove "magnet:", prehaps usless now?
|
||||||
r.Magnet = removeMagnetLink(r.Magnet) // Remove "magnet:", prehaps usless now?
|
results = append(results, r)
|
||||||
results = append(results, r)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if len(results) == 0 {
|
||||||
if len(results) == 0 {
|
printWarn("No file results found for query: %s, trying other nodes", query)
|
||||||
log.Printf("No file results found for query: %s, trying other nodes", query)
|
results = tryOtherNodesForFileSearch(query, safe, lang, page, []string{hostID})
|
||||||
results = tryOtherNodesForFileSearch(query, safe, lang, page, []string{hostID})
|
}
|
||||||
}
|
|
||||||
|
return results
|
||||||
return results
|
}
|
||||||
}
|
|
||||||
|
func removeMagnetLink(magnet string) string {
|
||||||
func removeMagnetLink(magnet string) string {
|
// Remove the magnet: prefix unconditionally
|
||||||
// Remove the magnet: prefix unconditionally
|
return strings.TrimPrefix(magnet, "magnet:")
|
||||||
return strings.TrimPrefix(magnet, "magnet:")
|
}
|
||||||
}
|
|
||||||
|
func parseInt(s string) int {
|
||||||
func parseInt(s string) int {
|
i, err := strconv.Atoi(s)
|
||||||
i, err := strconv.Atoi(s)
|
if err != nil {
|
||||||
if err != nil {
|
return 0
|
||||||
return 0
|
}
|
||||||
}
|
return i
|
||||||
return i
|
}
|
||||||
}
|
|
||||||
|
func parseSize(sizeStr string) int64 {
|
||||||
func parseSize(sizeStr string) int64 {
|
sizeStr = strings.TrimSpace(sizeStr)
|
||||||
sizeStr = strings.TrimSpace(sizeStr)
|
if sizeStr == "" {
|
||||||
if sizeStr == "" {
|
return 0
|
||||||
return 0
|
}
|
||||||
}
|
|
||||||
|
// Use regex to extract numeric value and unit separately
|
||||||
// Use regex to extract numeric value and unit separately
|
re := regexp.MustCompile(`(?i)([\d.]+)\s*([KMGT]?B)`)
|
||||||
re := regexp.MustCompile(`(?i)([\d.]+)\s*([KMGT]?B)`)
|
matches := re.FindStringSubmatch(sizeStr)
|
||||||
matches := re.FindStringSubmatch(sizeStr)
|
if len(matches) < 3 {
|
||||||
if len(matches) < 3 {
|
printWarn("Error parsing size: invalid format %s", sizeStr)
|
||||||
log.Printf("Error parsing size: invalid format %s", sizeStr)
|
return 0
|
||||||
return 0
|
}
|
||||||
}
|
|
||||||
|
sizeStr = matches[1]
|
||||||
sizeStr = matches[1]
|
unit := strings.ToUpper(matches[2])
|
||||||
unit := strings.ToUpper(matches[2])
|
|
||||||
|
var multiplier int64 = 1
|
||||||
var multiplier int64 = 1
|
switch unit {
|
||||||
switch unit {
|
case "KB":
|
||||||
case "KB":
|
multiplier = 1024
|
||||||
multiplier = 1024
|
case "MB":
|
||||||
case "MB":
|
multiplier = 1024 * 1024
|
||||||
multiplier = 1024 * 1024
|
case "GB":
|
||||||
case "GB":
|
multiplier = 1024 * 1024 * 1024
|
||||||
multiplier = 1024 * 1024 * 1024
|
case "TB":
|
||||||
case "TB":
|
multiplier = 1024 * 1024 * 1024 * 1024
|
||||||
multiplier = 1024 * 1024 * 1024 * 1024
|
default:
|
||||||
default:
|
printWarn("Unknown unit: %s", unit)
|
||||||
log.Printf("Unknown unit: %s", unit)
|
return 0
|
||||||
return 0
|
}
|
||||||
}
|
|
||||||
|
size, err := strconv.ParseFloat(sizeStr, 64)
|
||||||
size, err := strconv.ParseFloat(sizeStr, 64)
|
if err != nil {
|
||||||
if err != nil {
|
printWarn("Error parsing size: %v", err)
|
||||||
log.Printf("Error parsing size: %v", err)
|
return 0
|
||||||
return 0
|
}
|
||||||
}
|
return int64(size * float64(multiplier))
|
||||||
return int64(size * float64(multiplier))
|
}
|
||||||
}
|
|
||||||
|
// apparently this is needed so it can announce that magnet link is being used and people start seeding it, but I dont like the fact that I add trackers purposefully
|
||||||
// apparently this is needed so it can announce that magnet link is being used and people start seeding it, but I dont like the fact that I add trackers purposefully
|
func applyTrackers(magnetLink string) string {
|
||||||
func applyTrackers(magnetLink string) string {
|
if magnetLink == "" {
|
||||||
if magnetLink == "" {
|
return ""
|
||||||
return ""
|
}
|
||||||
}
|
trackers := []string{
|
||||||
trackers := []string{
|
"udp://tracker.openbittorrent.com:80/announce",
|
||||||
"udp://tracker.openbittorrent.com:80/announce",
|
"udp://tracker.opentrackr.org:1337/announce",
|
||||||
"udp://tracker.opentrackr.org:1337/announce",
|
"udp://tracker.coppersurfer.tk:6969/announce",
|
||||||
"udp://tracker.coppersurfer.tk:6969/announce",
|
"udp://tracker.leechers-paradise.org:6969/announce",
|
||||||
"udp://tracker.leechers-paradise.org:6969/announce",
|
}
|
||||||
}
|
for _, tracker := range trackers {
|
||||||
for _, tracker := range trackers {
|
magnetLink += "&tr=" + url.QueryEscape(tracker)
|
||||||
magnetLink += "&tr=" + url.QueryEscape(tracker)
|
}
|
||||||
}
|
return magnetLink
|
||||||
return magnetLink
|
}
|
||||||
}
|
|
||||||
|
func formatSize(size int64) string {
|
||||||
func formatSize(size int64) string {
|
if size >= 1024*1024*1024*1024 {
|
||||||
if size >= 1024*1024*1024*1024 {
|
return fmt.Sprintf("%.2f TB", float64(size)/(1024*1024*1024*1024))
|
||||||
return fmt.Sprintf("%.2f TB", float64(size)/(1024*1024*1024*1024))
|
} else if size >= 1024*1024*1024 {
|
||||||
} else if size >= 1024*1024*1024 {
|
return fmt.Sprintf("%.2f GB", float64(size)/(1024*1024*1024))
|
||||||
return fmt.Sprintf("%.2f GB", float64(size)/(1024*1024*1024))
|
} else if size >= 1024*1024 {
|
||||||
} else if size >= 1024*1024 {
|
return fmt.Sprintf("%.2f MB", float64(size)/(1024*1024))
|
||||||
return fmt.Sprintf("%.2f MB", float64(size)/(1024*1024))
|
} else if size >= 1024 {
|
||||||
} else if size >= 1024 {
|
return fmt.Sprintf("%.2f KB", float64(size)/1024)
|
||||||
return fmt.Sprintf("%.2f KB", float64(size)/1024)
|
}
|
||||||
}
|
return fmt.Sprintf("%d B", size)
|
||||||
return fmt.Sprintf("%d B", size)
|
}
|
||||||
}
|
|
||||||
|
func sanitizeFileName(name string) string {
|
||||||
func sanitizeFileName(name string) string {
|
// Replace spaces with dashes
|
||||||
// Replace spaces with dashes
|
sanitized := regexp.MustCompile(`\s+`).ReplaceAllString(name, "-")
|
||||||
sanitized := regexp.MustCompile(`\s+`).ReplaceAllString(name, "-")
|
// Remove any characters that are not alphanumeric, dashes, or parentheses
|
||||||
// Remove any characters that are not alphanumeric, dashes, or parentheses
|
sanitized = regexp.MustCompile(`[^a-zA-Z0-9\-\(\)]`).ReplaceAllString(sanitized, "")
|
||||||
sanitized = regexp.MustCompile(`[^a-zA-Z0-9\-\(\)]`).ReplaceAllString(sanitized, "")
|
return sanitized
|
||||||
return sanitized
|
}
|
||||||
}
|
|
||||||
|
func contains(slice []string, item string) bool {
|
||||||
func contains(slice []string, item string) bool {
|
for _, v := range slice {
|
||||||
for _, v := range slice {
|
if v == item {
|
||||||
if v == item {
|
return true
|
||||||
return true
|
}
|
||||||
}
|
}
|
||||||
}
|
return false
|
||||||
return false
|
}
|
||||||
}
|
|
||||||
|
|
284
forums.go
Normal file → Executable file
284
forums.go
Normal file → Executable file
|
@ -1,142 +1,142 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PerformRedditSearch(query string, safe string, page int) ([]ForumSearchResult, error) {
|
func PerformRedditSearch(query string, safe string, page int) ([]ForumSearchResult, error) {
|
||||||
const (
|
const (
|
||||||
pageSize = 25
|
pageSize = 25
|
||||||
baseURL = "https://www.reddit.com"
|
baseURL = "https://www.reddit.com"
|
||||||
maxRetries = 5
|
maxRetries = 5
|
||||||
initialBackoff = 2 * time.Second
|
initialBackoff = 2 * time.Second
|
||||||
)
|
)
|
||||||
var results []ForumSearchResult
|
var results []ForumSearchResult
|
||||||
|
|
||||||
searchURL := fmt.Sprintf("%s/search.json?q=%s&limit=%d&start=%d", baseURL, url.QueryEscape(query), pageSize, page*pageSize)
|
searchURL := fmt.Sprintf("%s/search.json?q=%s&limit=%d&start=%d", baseURL, url.QueryEscape(query), pageSize, page*pageSize)
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Retry logic with exponential backoff
|
// Retry logic with exponential backoff
|
||||||
for i := 0; i <= maxRetries; i++ {
|
for i := 0; i <= maxRetries; i++ {
|
||||||
resp, err = http.Get(searchURL)
|
resp, err = http.Get(searchURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("making request: %v", err)
|
return nil, fmt.Errorf("making request: %v", err)
|
||||||
}
|
}
|
||||||
if resp.StatusCode != http.StatusTooManyRequests {
|
if resp.StatusCode != http.StatusTooManyRequests {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for some time before retrying
|
// Wait for some time before retrying
|
||||||
backoff := time.Duration(math.Pow(2, float64(i))) * initialBackoff
|
backoff := time.Duration(math.Pow(2, float64(i))) * initialBackoff
|
||||||
time.Sleep(backoff)
|
time.Sleep(backoff)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("making request: %v", err)
|
return nil, fmt.Errorf("making request: %v", err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
var searchResults map[string]interface{}
|
var searchResults map[string]interface{}
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&searchResults); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&searchResults); err != nil {
|
||||||
return nil, fmt.Errorf("decoding response: %v", err)
|
return nil, fmt.Errorf("decoding response: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
data, ok := searchResults["data"].(map[string]interface{})
|
data, ok := searchResults["data"].(map[string]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("no data field in response")
|
return nil, fmt.Errorf("no data field in response")
|
||||||
}
|
}
|
||||||
|
|
||||||
posts, ok := data["children"].([]interface{})
|
posts, ok := data["children"].([]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("no children field in data")
|
return nil, fmt.Errorf("no children field in data")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, post := range posts {
|
for _, post := range posts {
|
||||||
postData := post.(map[string]interface{})["data"].(map[string]interface{})
|
postData := post.(map[string]interface{})["data"].(map[string]interface{})
|
||||||
|
|
||||||
if safe == "active" && postData["over_18"].(bool) {
|
if safe == "active" && postData["over_18"].(bool) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
header := postData["title"].(string)
|
header := postData["title"].(string)
|
||||||
description := postData["selftext"].(string)
|
description := postData["selftext"].(string)
|
||||||
if len(description) > 500 {
|
if len(description) > 500 {
|
||||||
description = description[:500] + "..."
|
description = description[:500] + "..."
|
||||||
}
|
}
|
||||||
publishedDate := time.Unix(int64(postData["created_utc"].(float64)), 0)
|
publishedDate := time.Unix(int64(postData["created_utc"].(float64)), 0)
|
||||||
permalink := postData["permalink"].(string)
|
permalink := postData["permalink"].(string)
|
||||||
resultURL := fmt.Sprintf("%s%s", baseURL, permalink)
|
resultURL := fmt.Sprintf("%s%s", baseURL, permalink)
|
||||||
|
|
||||||
result := ForumSearchResult{
|
result := ForumSearchResult{
|
||||||
URL: resultURL,
|
URL: resultURL,
|
||||||
Header: header,
|
Header: header,
|
||||||
Description: description,
|
Description: description,
|
||||||
PublishedDate: publishedDate,
|
PublishedDate: publishedDate,
|
||||||
}
|
}
|
||||||
|
|
||||||
thumbnail := postData["thumbnail"].(string)
|
thumbnail := postData["thumbnail"].(string)
|
||||||
if parsedURL, err := url.Parse(thumbnail); err == nil && parsedURL.Scheme != "" {
|
if parsedURL, err := url.Parse(thumbnail); err == nil && parsedURL.Scheme != "" {
|
||||||
result.ImgSrc = postData["url"].(string)
|
result.ImgSrc = postData["url"].(string)
|
||||||
result.ThumbnailSrc = thumbnail
|
result.ThumbnailSrc = thumbnail
|
||||||
}
|
}
|
||||||
|
|
||||||
results = append(results, result)
|
results = append(results, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleForumsSearch(w http.ResponseWriter, settings UserSettings, query, safe, lang string, page int) {
|
func handleForumsSearch(w http.ResponseWriter, settings UserSettings, query string, page int) {
|
||||||
results, err := PerformRedditSearch(query, safe, page)
|
results, err := PerformRedditSearch(query, settings.SafeSearch, page)
|
||||||
if err != nil || len(results) == 0 { // 0 == 0 to force search by other node
|
if err != nil || len(results) == 0 { // 0 == 0 to force search by other node
|
||||||
log.Printf("No results from primary search, trying other nodes")
|
log.Printf("No results from primary search, trying other nodes")
|
||||||
results = tryOtherNodesForForumSearch(query, safe, lang, page)
|
results = tryOtherNodesForForumSearch(query, settings.SafeSearch, settings.Language, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
data := struct {
|
data := struct {
|
||||||
Query string
|
Query string
|
||||||
Results []ForumSearchResult
|
Results []ForumSearchResult
|
||||||
LanguageOptions []LanguageOption
|
LanguageOptions []LanguageOption
|
||||||
CurrentLang string
|
CurrentLang string
|
||||||
Page int
|
Page int
|
||||||
HasPrevPage bool
|
HasPrevPage bool
|
||||||
HasNextPage bool
|
HasNextPage bool
|
||||||
Theme string
|
Theme string
|
||||||
}{
|
}{
|
||||||
Query: query,
|
Query: query,
|
||||||
Results: results,
|
Results: results,
|
||||||
LanguageOptions: languageOptions,
|
LanguageOptions: languageOptions,
|
||||||
CurrentLang: lang,
|
CurrentLang: settings.Language,
|
||||||
Page: page,
|
Page: page,
|
||||||
HasPrevPage: page > 1,
|
HasPrevPage: page > 1,
|
||||||
HasNextPage: len(results) == 25,
|
HasNextPage: len(results) == 25,
|
||||||
Theme: settings.Theme,
|
Theme: settings.Theme,
|
||||||
}
|
}
|
||||||
|
|
||||||
funcMap := template.FuncMap{
|
funcMap := template.FuncMap{
|
||||||
"sub": func(a, b int) int { return a - b },
|
"sub": func(a, b int) int { return a - b },
|
||||||
"add": func(a, b int) int { return a + b },
|
"add": func(a, b int) int { return a + b },
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl, err := template.New("forums.html").Funcs(funcMap).ParseFiles("templates/forums.html")
|
tmpl, err := template.New("forums.html").Funcs(funcMap).ParseFiles("templates/forums.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, fmt.Sprintf("Error loading template: %v", err), http.StatusInternalServerError)
|
http.Error(w, fmt.Sprintf("Error loading template: %v", err), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tmpl.Execute(w, data); err != nil {
|
if err := tmpl.Execute(w, data); err != nil {
|
||||||
http.Error(w, fmt.Sprintf("Error rendering template: %v", err), http.StatusInternalServerError)
|
http.Error(w, fmt.Sprintf("Error rendering template: %v", err), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
294
images.go
Normal file → Executable file
294
images.go
Normal file → Executable file
|
@ -1,147 +1,147 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var imageSearchEngines []SearchEngine
|
var imageSearchEngines []SearchEngine
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
imageSearchEngines = []SearchEngine{
|
imageSearchEngines = []SearchEngine{
|
||||||
{Name: "Qwant", Func: wrapImageSearchFunc(PerformQwantImageSearch), Weight: 1},
|
{Name: "Qwant", Func: wrapImageSearchFunc(PerformQwantImageSearch), Weight: 1},
|
||||||
{Name: "Bing", Func: wrapImageSearchFunc(PerformBingImageSearch), Weight: 2}, // Bing sometimes returns with low amount of images, this leads to danamica page loading not working
|
{Name: "Bing", Func: wrapImageSearchFunc(PerformBingImageSearch), Weight: 2}, // Bing sometimes returns with low amount of images, this leads to danamica page loading not working
|
||||||
{Name: "Imgur", Func: wrapImageSearchFunc(PerformImgurImageSearch), Weight: 3},
|
{Name: "Imgur", Func: wrapImageSearchFunc(PerformImgurImageSearch), Weight: 3},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleImageSearch(w http.ResponseWriter, settings UserSettings, query, safe, lang string, page int) {
|
func handleImageSearch(w http.ResponseWriter, settings UserSettings, query string, page int) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
cacheKey := CacheKey{Query: query, Page: page, Safe: safe == "true", Lang: lang, Type: "image"}
|
cacheKey := CacheKey{Query: query, Page: page, Safe: settings.SafeSearch == "true", Lang: settings.Language, Type: "image"}
|
||||||
combinedResults := getImageResultsFromCacheOrFetch(cacheKey, query, safe, lang, page)
|
combinedResults := getImageResultsFromCacheOrFetch(cacheKey, query, settings.SafeSearch, settings.Language, page)
|
||||||
|
|
||||||
elapsedTime := time.Since(startTime)
|
elapsedTime := time.Since(startTime)
|
||||||
tmpl, err := template.New("images.html").Funcs(funcs).ParseFiles("templates/images.html")
|
tmpl, err := template.New("images.html").Funcs(funcs).ParseFiles("templates/images.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error parsing template: %v", err)
|
log.Printf("Error parsing template: %v", err)
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := struct {
|
data := struct {
|
||||||
Results []ImageSearchResult
|
Results []ImageSearchResult
|
||||||
Query string
|
Query string
|
||||||
Page int
|
Page int
|
||||||
Fetched string
|
Fetched string
|
||||||
LanguageOptions []LanguageOption
|
LanguageOptions []LanguageOption
|
||||||
CurrentLang string
|
CurrentLang string
|
||||||
HasPrevPage bool
|
HasPrevPage bool
|
||||||
HasNextPage bool
|
HasNextPage bool
|
||||||
NoResults bool
|
NoResults bool
|
||||||
Theme string
|
Theme string
|
||||||
}{
|
}{
|
||||||
Results: combinedResults,
|
Results: combinedResults,
|
||||||
Query: query,
|
Query: query,
|
||||||
Page: page,
|
Page: page,
|
||||||
Fetched: fmt.Sprintf("%.2f seconds", elapsedTime.Seconds()),
|
Fetched: fmt.Sprintf("%.2f seconds", elapsedTime.Seconds()),
|
||||||
LanguageOptions: languageOptions,
|
LanguageOptions: languageOptions,
|
||||||
CurrentLang: lang,
|
CurrentLang: settings.Language,
|
||||||
HasPrevPage: page > 1,
|
HasPrevPage: page > 1,
|
||||||
HasNextPage: len(combinedResults) >= 50,
|
HasNextPage: len(combinedResults) >= 50,
|
||||||
NoResults: len(combinedResults) == 0,
|
NoResults: len(combinedResults) == 0,
|
||||||
Theme: settings.Theme,
|
Theme: settings.Theme,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tmpl.Execute(w, data)
|
err = tmpl.Execute(w, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error executing template: %v", err)
|
printErr("Error executing template: %v", err)
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getImageResultsFromCacheOrFetch(cacheKey CacheKey, query, safe, lang string, page int) []ImageSearchResult {
|
func getImageResultsFromCacheOrFetch(cacheKey CacheKey, query, safe, lang string, page int) []ImageSearchResult {
|
||||||
cacheChan := make(chan []SearchResult)
|
cacheChan := make(chan []SearchResult)
|
||||||
var combinedResults []ImageSearchResult
|
var combinedResults []ImageSearchResult
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
results, exists := resultsCache.Get(cacheKey)
|
results, exists := resultsCache.Get(cacheKey)
|
||||||
if exists {
|
if exists {
|
||||||
log.Println("Cache hit")
|
printInfo("Cache hit")
|
||||||
cacheChan <- results
|
cacheChan <- results
|
||||||
} else {
|
} else {
|
||||||
log.Println("Cache miss")
|
printInfo("Cache miss")
|
||||||
cacheChan <- nil
|
cacheChan <- nil
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case results := <-cacheChan:
|
case results := <-cacheChan:
|
||||||
if results == nil {
|
if results == nil {
|
||||||
combinedResults = fetchImageResults(query, safe, lang, page)
|
combinedResults = fetchImageResults(query, safe, lang, page)
|
||||||
if len(combinedResults) > 0 {
|
if len(combinedResults) > 0 {
|
||||||
resultsCache.Set(cacheKey, convertToSearchResults(combinedResults))
|
resultsCache.Set(cacheKey, convertToSearchResults(combinedResults))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_, _, imageResults := convertToSpecificResults(results)
|
_, _, imageResults := convertToSpecificResults(results)
|
||||||
combinedResults = imageResults
|
combinedResults = imageResults
|
||||||
}
|
}
|
||||||
case <-time.After(2 * time.Second):
|
case <-time.After(2 * time.Second):
|
||||||
log.Println("Cache check timeout")
|
printInfo("Cache check timeout")
|
||||||
combinedResults = fetchImageResults(query, safe, lang, page)
|
combinedResults = fetchImageResults(query, safe, lang, page)
|
||||||
if len(combinedResults) > 0 {
|
if len(combinedResults) > 0 {
|
||||||
resultsCache.Set(cacheKey, convertToSearchResults(combinedResults))
|
resultsCache.Set(cacheKey, convertToSearchResults(combinedResults))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return combinedResults
|
return combinedResults
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchImageResults(query, safe, lang string, page int) []ImageSearchResult {
|
func fetchImageResults(query, safe, lang string, page int) []ImageSearchResult {
|
||||||
var results []ImageSearchResult
|
var results []ImageSearchResult
|
||||||
|
|
||||||
for _, engine := range imageSearchEngines {
|
for _, engine := range imageSearchEngines {
|
||||||
log.Printf("Using image search engine: %s", engine.Name)
|
printInfo("Using image search engine: %s", engine.Name)
|
||||||
|
|
||||||
searchResults, duration, err := engine.Func(query, safe, lang, page)
|
searchResults, duration, err := engine.Func(query, safe, lang, page)
|
||||||
updateEngineMetrics(&engine, duration, err == nil)
|
updateEngineMetrics(&engine, duration, err == nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error performing image search with %s: %v", engine.Name, err)
|
printWarn("Error performing image search with %s: %v", engine.Name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, result := range searchResults {
|
for _, result := range searchResults {
|
||||||
results = append(results, result.(ImageSearchResult))
|
results = append(results, result.(ImageSearchResult))
|
||||||
}
|
}
|
||||||
|
|
||||||
// If results are found, break out of the loop
|
// If results are found, break out of the loop
|
||||||
if len(results) > 0 {
|
if len(results) > 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no results found after trying all engines
|
// If no results found after trying all engines
|
||||||
if len(results) == 0 {
|
if len(results) == 0 {
|
||||||
log.Printf("No image results found for query: %s, trying other nodes", query)
|
printWarn("No image results found for query: %s, trying other nodes", query)
|
||||||
results = tryOtherNodesForImageSearch(query, safe, lang, page, []string{hostID})
|
results = tryOtherNodesForImageSearch(query, safe, lang, page, []string{hostID})
|
||||||
}
|
}
|
||||||
|
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapImageSearchFunc(f func(string, string, string, int) ([]ImageSearchResult, time.Duration, error)) func(string, string, string, int) ([]SearchResult, time.Duration, error) {
|
func wrapImageSearchFunc(f func(string, string, string, int) ([]ImageSearchResult, time.Duration, error)) func(string, string, string, int) ([]SearchResult, time.Duration, error) {
|
||||||
return func(query, safe, lang string, page int) ([]SearchResult, time.Duration, error) {
|
return func(query, safe, lang string, page int) ([]SearchResult, time.Duration, error) {
|
||||||
imageResults, duration, err := f(query, safe, lang, page)
|
imageResults, duration, err := f(query, safe, lang, page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, duration, err
|
return nil, duration, err
|
||||||
}
|
}
|
||||||
searchResults := make([]SearchResult, len(imageResults))
|
searchResults := make([]SearchResult, len(imageResults))
|
||||||
for i, result := range imageResults {
|
for i, result := range imageResults {
|
||||||
searchResults[i] = result
|
searchResults[i] = result
|
||||||
}
|
}
|
||||||
return searchResults, duration, nil
|
return searchResults, duration, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
355
main.go
Normal file → Executable file
355
main.go
Normal file → Executable file
|
@ -1,178 +1,177 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LanguageOption represents a language option for search
|
// LanguageOption represents a language option for search
|
||||||
type LanguageOption struct {
|
type LanguageOption struct {
|
||||||
Code string
|
Code string
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
var settings UserSettings
|
var settings UserSettings
|
||||||
|
|
||||||
var languageOptions = []LanguageOption{
|
var languageOptions = []LanguageOption{
|
||||||
{Code: "", Name: "Any Language"},
|
{Code: "", Name: "Any Language"},
|
||||||
{Code: "lang_en", Name: "English"},
|
{Code: "lang_en", Name: "English"},
|
||||||
{Code: "lang_af", Name: "Afrikaans"},
|
{Code: "lang_af", Name: "Afrikaans"},
|
||||||
{Code: "lang_ar", Name: "العربية (Arabic)"},
|
{Code: "lang_ar", Name: "العربية (Arabic)"},
|
||||||
{Code: "lang_hy", Name: "Հայերեն (Armenian)"},
|
{Code: "lang_hy", Name: "Հայերեն (Armenian)"},
|
||||||
{Code: "lang_be", Name: "Беларуская (Belarusian)"},
|
{Code: "lang_be", Name: "Беларуская (Belarusian)"},
|
||||||
{Code: "lang_bg", Name: "български (Bulgarian)"},
|
{Code: "lang_bg", Name: "български (Bulgarian)"},
|
||||||
{Code: "lang_ca", Name: "Català (Catalan)"},
|
{Code: "lang_ca", Name: "Català (Catalan)"},
|
||||||
{Code: "lang_zh-CN", Name: "中文 (简体) (Chinese Simplified)"},
|
{Code: "lang_zh-CN", Name: "中文 (简体) (Chinese Simplified)"},
|
||||||
{Code: "lang_zh-TW", Name: "中文 (繁體) (Chinese Traditional)"},
|
{Code: "lang_zh-TW", Name: "中文 (繁體) (Chinese Traditional)"},
|
||||||
{Code: "lang_hr", Name: "Hrvatski (Croatian)"},
|
{Code: "lang_hr", Name: "Hrvatski (Croatian)"},
|
||||||
{Code: "lang_cs", Name: "Čeština (Czech)"},
|
{Code: "lang_cs", Name: "Čeština (Czech)"},
|
||||||
{Code: "lang_da", Name: "Dansk (Danish)"},
|
{Code: "lang_da", Name: "Dansk (Danish)"},
|
||||||
{Code: "lang_nl", Name: "Nederlands (Dutch)"},
|
{Code: "lang_nl", Name: "Nederlands (Dutch)"},
|
||||||
{Code: "lang_eo", Name: "Esperanto"},
|
{Code: "lang_eo", Name: "Esperanto"},
|
||||||
{Code: "lang_et", Name: "Eesti (Estonian)"},
|
{Code: "lang_et", Name: "Eesti (Estonian)"},
|
||||||
{Code: "lang_tl", Name: "Filipino (Tagalog)"},
|
{Code: "lang_tl", Name: "Filipino (Tagalog)"},
|
||||||
{Code: "lang_fi", Name: "Suomi (Finnish)"},
|
{Code: "lang_fi", Name: "Suomi (Finnish)"},
|
||||||
{Code: "lang_fr", Name: "Français (French)"},
|
{Code: "lang_fr", Name: "Français (French)"},
|
||||||
{Code: "lang_de", Name: "Deutsch (German)"},
|
{Code: "lang_de", Name: "Deutsch (German)"},
|
||||||
{Code: "lang_el", Name: "Ελληνικά (Greek)"},
|
{Code: "lang_el", Name: "Ελληνικά (Greek)"},
|
||||||
{Code: "lang_iw", Name: "עברית (Hebrew)"},
|
{Code: "lang_iw", Name: "עברית (Hebrew)"},
|
||||||
{Code: "lang_hi", Name: "हिन्दी (Hindi)"},
|
{Code: "lang_hi", Name: "हिन्दी (Hindi)"},
|
||||||
{Code: "lang_hu", Name: "magyar (Hungarian)"},
|
{Code: "lang_hu", Name: "magyar (Hungarian)"},
|
||||||
{Code: "lang_is", Name: "íslenska (Icelandic)"},
|
{Code: "lang_is", Name: "íslenska (Icelandic)"},
|
||||||
{Code: "lang_id", Name: "Bahasa Indonesia (Indonesian)"},
|
{Code: "lang_id", Name: "Bahasa Indonesia (Indonesian)"},
|
||||||
{Code: "lang_it", Name: "italiano (Italian)"},
|
{Code: "lang_it", Name: "italiano (Italian)"},
|
||||||
{Code: "lang_ja", Name: "日本語 (Japanese)"},
|
{Code: "lang_ja", Name: "日本語 (Japanese)"},
|
||||||
{Code: "lang_ko", Name: "한국어 (Korean)"},
|
{Code: "lang_ko", Name: "한국어 (Korean)"},
|
||||||
{Code: "lang_lv", Name: "latviešu (Latvian)"},
|
{Code: "lang_lv", Name: "latviešu (Latvian)"},
|
||||||
{Code: "lang_lt", Name: "lietuvių (Lithuanian)"},
|
{Code: "lang_lt", Name: "lietuvių (Lithuanian)"},
|
||||||
{Code: "lang_no", Name: "norsk (Norwegian)"},
|
{Code: "lang_no", Name: "norsk (Norwegian)"},
|
||||||
{Code: "lang_fa", Name: "فارسی (Persian)"},
|
{Code: "lang_fa", Name: "فارسی (Persian)"},
|
||||||
{Code: "lang_pl", Name: "polski (Polish)"},
|
{Code: "lang_pl", Name: "polski (Polish)"},
|
||||||
{Code: "lang_pt", Name: "português (Portuguese)"},
|
{Code: "lang_pt", Name: "português (Portuguese)"},
|
||||||
{Code: "lang_ro", Name: "română (Romanian)"},
|
{Code: "lang_ro", Name: "română (Romanian)"},
|
||||||
{Code: "lang_ru", Name: "русский (Russian)"},
|
{Code: "lang_ru", Name: "русский (Russian)"},
|
||||||
{Code: "lang_sr", Name: "српски (Serbian)"},
|
{Code: "lang_sr", Name: "српски (Serbian)"},
|
||||||
{Code: "lang_sk", Name: "slovenčina (Slovak)"},
|
{Code: "lang_sk", Name: "slovenčina (Slovak)"},
|
||||||
{Code: "lang_sl", Name: "slovenščina (Slovenian)"},
|
{Code: "lang_sl", Name: "slovenščina (Slovenian)"},
|
||||||
{Code: "lang_es", Name: "español (Spanish)"},
|
{Code: "lang_es", Name: "español (Spanish)"},
|
||||||
{Code: "lang_sw", Name: "Kiswahili (Swahili)"},
|
{Code: "lang_sw", Name: "Kiswahili (Swahili)"},
|
||||||
{Code: "lang_sv", Name: "svenska (Swedish)"},
|
{Code: "lang_sv", Name: "svenska (Swedish)"},
|
||||||
{Code: "lang_th", Name: "ไทย (Thai)"},
|
{Code: "lang_th", Name: "ไทย (Thai)"},
|
||||||
{Code: "lang_tr", Name: "Türkçe (Turkish)"},
|
{Code: "lang_tr", Name: "Türkçe (Turkish)"},
|
||||||
{Code: "lang_uk", Name: "українська (Ukrainian)"},
|
{Code: "lang_uk", Name: "українська (Ukrainian)"},
|
||||||
{Code: "lang_vi", Name: "Tiếng Việt (Vietnamese)"},
|
{Code: "lang_vi", Name: "Tiếng Việt (Vietnamese)"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSearch(w http.ResponseWriter, r *http.Request) {
|
func handleSearch(w http.ResponseWriter, r *http.Request) {
|
||||||
query, safe, lang, searchType, page := parseSearchParams(r)
|
query, safe, lang, searchType, page := parseSearchParams(r)
|
||||||
|
|
||||||
// Load user settings
|
// Load user settings
|
||||||
settings = loadUserSettings(r)
|
settings = loadUserSettings(r)
|
||||||
|
|
||||||
// Update the theme, safe search, and language based on query parameters or use existing settings
|
// Update the theme, safe search, and language based on query parameters or use existing settings
|
||||||
theme := r.URL.Query().Get("theme")
|
theme := r.URL.Query().Get("theme")
|
||||||
if theme != "" {
|
if theme != "" {
|
||||||
settings.Theme = theme
|
settings.Theme = theme
|
||||||
saveUserSettings(w, settings)
|
saveUserSettings(w, settings)
|
||||||
} else if settings.Theme == "" {
|
} else if settings.Theme == "" {
|
||||||
settings.Theme = "dark" // Default theme
|
settings.Theme = "dark" // Default theme
|
||||||
}
|
}
|
||||||
|
|
||||||
if safe != "" {
|
if safe != "" {
|
||||||
settings.SafeSearch = safe
|
settings.SafeSearch = safe
|
||||||
saveUserSettings(w, settings)
|
saveUserSettings(w, settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
if lang != "" {
|
if lang != "" {
|
||||||
settings.Language = lang
|
settings.Language = lang
|
||||||
saveUserSettings(w, settings)
|
saveUserSettings(w, settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the search page template if no query
|
// Render the search page template if no query
|
||||||
if query == "" {
|
if query == "" {
|
||||||
tmpl := template.Must(template.ParseFiles("templates/search.html"))
|
tmpl := template.Must(template.ParseFiles("templates/search.html"))
|
||||||
tmpl.Execute(w, settings)
|
tmpl.Execute(w, settings)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
settings := loadUserSettings(r)
|
settings := loadUserSettings(r)
|
||||||
|
|
||||||
// Handle search based on the type
|
// Handle search based on the type
|
||||||
switch searchType {
|
switch searchType {
|
||||||
case "image":
|
case "image":
|
||||||
handleImageSearch(w, settings, query, safe, lang, page)
|
handleImageSearch(w, settings, query, page)
|
||||||
case "video":
|
case "video":
|
||||||
handleVideoSearch(w, settings, query, safe, lang, page)
|
handleVideoSearch(w, settings, query, page)
|
||||||
case "map":
|
case "map":
|
||||||
handleMapSearch(w, settings, query, safe)
|
handleMapSearch(w, settings, query)
|
||||||
case "forum":
|
case "forum":
|
||||||
handleForumsSearch(w, settings, query, safe, lang, page)
|
handleForumsSearch(w, settings, query, page)
|
||||||
case "file":
|
case "file":
|
||||||
handleFileSearch(w, settings, query, safe, lang, page)
|
handleFileSearch(w, settings, query, page)
|
||||||
case "text":
|
case "text":
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
HandleTextSearch(w, settings, query, safe, lang, page)
|
HandleTextSearch(w, settings, query, page)
|
||||||
}
|
}
|
||||||
// This is immeasurably stupid it passes safe and language then it passes settings with safe and lang again
|
}
|
||||||
}
|
|
||||||
|
func parseSearchParams(r *http.Request) (query, safe, lang, searchType string, page int) {
|
||||||
func parseSearchParams(r *http.Request) (query, safe, lang, searchType string, page int) {
|
if r.Method == "GET" {
|
||||||
if r.Method == "GET" {
|
query = r.URL.Query().Get("q")
|
||||||
query = r.URL.Query().Get("q")
|
safe = r.URL.Query().Get("safe")
|
||||||
safe = r.URL.Query().Get("safe")
|
lang = r.URL.Query().Get("lang")
|
||||||
lang = r.URL.Query().Get("lang")
|
searchType = r.URL.Query().Get("t")
|
||||||
searchType = r.URL.Query().Get("t")
|
pageStr := r.URL.Query().Get("p")
|
||||||
pageStr := r.URL.Query().Get("p")
|
page = parsePageParameter(pageStr)
|
||||||
page = parsePageParameter(pageStr)
|
} else if r.Method == "POST" {
|
||||||
} else if r.Method == "POST" {
|
query = r.FormValue("q")
|
||||||
query = r.FormValue("q")
|
safe = r.FormValue("safe")
|
||||||
safe = r.FormValue("safe")
|
lang = r.FormValue("lang")
|
||||||
lang = r.FormValue("lang")
|
searchType = r.FormValue("t")
|
||||||
searchType = r.FormValue("t")
|
pageStr := r.FormValue("p")
|
||||||
pageStr := r.FormValue("p")
|
page = parsePageParameter(pageStr)
|
||||||
page = parsePageParameter(pageStr)
|
}
|
||||||
}
|
|
||||||
|
if searchType == "" {
|
||||||
if searchType == "" {
|
searchType = "text"
|
||||||
searchType = "text"
|
}
|
||||||
}
|
|
||||||
|
return
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
func parsePageParameter(pageStr string) int {
|
||||||
func parsePageParameter(pageStr string) int {
|
page, err := strconv.Atoi(pageStr)
|
||||||
page, err := strconv.Atoi(pageStr)
|
if err != nil || page < 1 {
|
||||||
if err != nil || page < 1 {
|
page = 1
|
||||||
page = 1
|
}
|
||||||
}
|
return page
|
||||||
return page
|
}
|
||||||
}
|
|
||||||
|
func runServer() {
|
||||||
func runServer() {
|
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
||||||
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
http.HandleFunc("/", handleSearch)
|
||||||
http.HandleFunc("/", handleSearch)
|
http.HandleFunc("/search", handleSearch)
|
||||||
http.HandleFunc("/search", handleSearch)
|
http.HandleFunc("/img_proxy", handleImageProxy)
|
||||||
http.HandleFunc("/img_proxy", handleImageProxy)
|
http.HandleFunc("/node", handleNodeRequest)
|
||||||
http.HandleFunc("/node", handleNodeRequest)
|
http.HandleFunc("/settings", func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.HandleFunc("/settings", func(w http.ResponseWriter, r *http.Request) {
|
http.ServeFile(w, r, "templates/settings.html")
|
||||||
http.ServeFile(w, r, "templates/settings.html")
|
})
|
||||||
})
|
http.HandleFunc("/opensearch.xml", func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.HandleFunc("/opensearch.xml", func(w http.ResponseWriter, r *http.Request) {
|
w.Header().Set("Content-Type", "application/opensearchdescription+xml")
|
||||||
w.Header().Set("Content-Type", "application/opensearchdescription+xml")
|
http.ServeFile(w, r, "static/opensearch.xml")
|
||||||
http.ServeFile(w, r, "static/opensearch.xml")
|
})
|
||||||
})
|
initializeTorrentSites()
|
||||||
initializeTorrentSites()
|
|
||||||
|
config := loadConfig()
|
||||||
config := loadConfig()
|
generateOpenSearchXML(config)
|
||||||
generateOpenSearchXML(config)
|
|
||||||
|
printMessage("Server is listening on http://localhost:%d", config.Port)
|
||||||
printMessage("Server is listening on http://localhost:%d", config.Port)
|
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", config.Port), nil))
|
||||||
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", config.Port), nil))
|
|
||||||
|
// Start automatic update checker
|
||||||
// Start automatic update checker
|
go checkForUpdates()
|
||||||
go checkForUpdates()
|
}
|
||||||
}
|
|
||||||
|
|
144
map.go
Normal file → Executable file
144
map.go
Normal file → Executable file
|
@ -1,72 +1,72 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NominatimResponse struct {
|
type NominatimResponse struct {
|
||||||
Lat string `json:"lat"`
|
Lat string `json:"lat"`
|
||||||
Lon string `json:"lon"`
|
Lon string `json:"lon"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func geocodeQuery(query string) (latitude, longitude string, found bool, err error) {
|
func geocodeQuery(query string) (latitude, longitude string, found bool, err error) {
|
||||||
// URL encode the query
|
// URL encode the query
|
||||||
query = url.QueryEscape(query)
|
query = url.QueryEscape(query)
|
||||||
|
|
||||||
// Construct the request URL
|
// Construct the request URL
|
||||||
urlString := fmt.Sprintf("https://nominatim.openstreetmap.org/search?format=json&q=%s", query)
|
urlString := fmt.Sprintf("https://nominatim.openstreetmap.org/search?format=json&q=%s", query)
|
||||||
|
|
||||||
// Make the HTTP GET request
|
// Make the HTTP GET request
|
||||||
resp, err := http.Get(urlString)
|
resp, err := http.Get(urlString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", false, err
|
return "", "", false, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// Read the response
|
// Read the response
|
||||||
var result []NominatimResponse
|
var result []NominatimResponse
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||||
return "", "", false, err
|
return "", "", false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there are any results
|
// Check if there are any results
|
||||||
if len(result) > 0 {
|
if len(result) > 0 {
|
||||||
latitude = result[0].Lat
|
latitude = result[0].Lat
|
||||||
longitude = result[0].Lon
|
longitude = result[0].Lon
|
||||||
return latitude, longitude, true, nil
|
return latitude, longitude, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", "", false, nil
|
return "", "", false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMapSearch(w http.ResponseWriter, settings UserSettings, query string, lang string) {
|
func handleMapSearch(w http.ResponseWriter, settings UserSettings, query string) {
|
||||||
// Geocode the query to get coordinates
|
// Geocode the query to get coordinates
|
||||||
latitude, longitude, found, err := geocodeQuery(query)
|
latitude, longitude, found, err := geocodeQuery(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printDebug("Error geocoding query: %s, error: %v", query, err)
|
printDebug("Error geocoding query: %s, error: %v", query, err)
|
||||||
http.Error(w, "Failed to find location", http.StatusInternalServerError)
|
http.Error(w, "Failed to find location", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use a template to serve the map page
|
// Use a template to serve the map page
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"Query": query,
|
"Query": query,
|
||||||
"Latitude": latitude,
|
"Latitude": latitude,
|
||||||
"Longitude": longitude,
|
"Longitude": longitude,
|
||||||
"Found": found,
|
"Found": found,
|
||||||
"Theme": settings.Theme,
|
"Theme": settings.Theme,
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl, err := template.ParseFiles("templates/map.html")
|
tmpl, err := template.ParseFiles("templates/map.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printErr("Error loading map template: %v", err)
|
printErr("Error loading map template: %v", err)
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl.Execute(w, data)
|
tmpl.Execute(w, data)
|
||||||
}
|
}
|
||||||
|
|
438
node-handle-search.go
Normal file → Executable file
438
node-handle-search.go
Normal file → Executable file
|
@ -1,219 +1,219 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func handleSearchTextMessage(msg Message) {
|
func handleSearchTextMessage(msg Message) {
|
||||||
var searchParams struct {
|
var searchParams struct {
|
||||||
Query string `json:"query"`
|
Query string `json:"query"`
|
||||||
Safe string `json:"safe"`
|
Safe string `json:"safe"`
|
||||||
Lang string `json:"lang"`
|
Lang string `json:"lang"`
|
||||||
Page int `json:"page"`
|
Page int `json:"page"`
|
||||||
ResponseAddr string `json:"responseAddr"`
|
ResponseAddr string `json:"responseAddr"`
|
||||||
}
|
}
|
||||||
err := json.Unmarshal([]byte(msg.Content), &searchParams)
|
err := json.Unmarshal([]byte(msg.Content), &searchParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error parsing search parameters: %v", err)
|
printWarn("Error parsing search parameters: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Received search-text request. ResponseAddr: %s", searchParams.ResponseAddr)
|
printDebug("Received search-text request. ResponseAddr: %s", searchParams.ResponseAddr)
|
||||||
|
|
||||||
results := fetchTextResults(searchParams.Query, searchParams.Safe, searchParams.Lang, searchParams.Page)
|
results := fetchTextResults(searchParams.Query, searchParams.Safe, searchParams.Lang, searchParams.Page)
|
||||||
resultsJSON, err := json.Marshal(results)
|
resultsJSON, err := json.Marshal(results)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error marshalling search results: %v", err)
|
printWarn("Error marshalling search results: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
responseMsg := Message{
|
responseMsg := Message{
|
||||||
ID: hostID,
|
ID: hostID,
|
||||||
Type: "text-results",
|
Type: "text-results",
|
||||||
Content: string(resultsJSON),
|
Content: string(resultsJSON),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log the address to be used for sending the response
|
// Log the address to be used for sending the response
|
||||||
log.Printf("Sending text search results to %s", searchParams.ResponseAddr)
|
printDebug("Sending text search results to %s", searchParams.ResponseAddr)
|
||||||
|
|
||||||
if searchParams.ResponseAddr == "" {
|
if searchParams.ResponseAddr == "" {
|
||||||
log.Printf("Error: Response address is empty")
|
printErr("Error: Response address is empty")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = sendMessage(searchParams.ResponseAddr, responseMsg)
|
err = sendMessage(searchParams.ResponseAddr, responseMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error sending text search results to %s: %v", searchParams.ResponseAddr, err)
|
printWarn("Error sending text search results to %s: %v", searchParams.ResponseAddr, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSearchImageMessage(msg Message) {
|
func handleSearchImageMessage(msg Message) {
|
||||||
var searchParams struct {
|
var searchParams struct {
|
||||||
Query string `json:"query"`
|
Query string `json:"query"`
|
||||||
Safe string `json:"safe"`
|
Safe string `json:"safe"`
|
||||||
Lang string `json:"lang"`
|
Lang string `json:"lang"`
|
||||||
Page int `json:"page"`
|
Page int `json:"page"`
|
||||||
ResponseAddr string `json:"responseAddr"`
|
ResponseAddr string `json:"responseAddr"`
|
||||||
}
|
}
|
||||||
err := json.Unmarshal([]byte(msg.Content), &searchParams)
|
err := json.Unmarshal([]byte(msg.Content), &searchParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error parsing search parameters: %v", err)
|
log.Printf("Error parsing search parameters: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Received search-image request. ResponseAddr: %s", searchParams.ResponseAddr)
|
log.Printf("Received search-image request. ResponseAddr: %s", searchParams.ResponseAddr)
|
||||||
|
|
||||||
results := fetchImageResults(searchParams.Query, searchParams.Safe, searchParams.Lang, searchParams.Page)
|
results := fetchImageResults(searchParams.Query, searchParams.Safe, searchParams.Lang, searchParams.Page)
|
||||||
resultsJSON, err := json.Marshal(results)
|
resultsJSON, err := json.Marshal(results)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error marshalling search results: %v", err)
|
log.Printf("Error marshalling search results: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
responseMsg := Message{
|
responseMsg := Message{
|
||||||
ID: hostID,
|
ID: hostID,
|
||||||
Type: "image-results",
|
Type: "image-results",
|
||||||
Content: string(resultsJSON),
|
Content: string(resultsJSON),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log the address to be used for sending the response
|
// Log the address to be used for sending the response
|
||||||
log.Printf("Sending image search results to %s", searchParams.ResponseAddr)
|
log.Printf("Sending image search results to %s", searchParams.ResponseAddr)
|
||||||
|
|
||||||
if searchParams.ResponseAddr == "" {
|
if searchParams.ResponseAddr == "" {
|
||||||
log.Printf("Error: Response address is empty")
|
log.Printf("Error: Response address is empty")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = sendMessage(searchParams.ResponseAddr, responseMsg)
|
err = sendMessage(searchParams.ResponseAddr, responseMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error sending image search results to %s: %v", searchParams.ResponseAddr, err)
|
log.Printf("Error sending image search results to %s: %v", searchParams.ResponseAddr, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSearchVideoMessage(msg Message) {
|
func handleSearchVideoMessage(msg Message) {
|
||||||
var searchParams struct {
|
var searchParams struct {
|
||||||
Query string `json:"query"`
|
Query string `json:"query"`
|
||||||
Safe string `json:"safe"`
|
Safe string `json:"safe"`
|
||||||
Lang string `json:"lang"`
|
Lang string `json:"lang"`
|
||||||
Page int `json:"page"`
|
Page int `json:"page"`
|
||||||
ResponseAddr string `json:"responseAddr"`
|
ResponseAddr string `json:"responseAddr"`
|
||||||
}
|
}
|
||||||
err := json.Unmarshal([]byte(msg.Content), &searchParams)
|
err := json.Unmarshal([]byte(msg.Content), &searchParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error parsing search parameters: %v", err)
|
log.Printf("Error parsing search parameters: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Received search-video request. ResponseAddr: %s", searchParams.ResponseAddr)
|
log.Printf("Received search-video request. ResponseAddr: %s", searchParams.ResponseAddr)
|
||||||
|
|
||||||
results := fetchVideoResults(searchParams.Query, searchParams.Safe, searchParams.Lang, searchParams.Page)
|
results := fetchVideoResults(searchParams.Query, searchParams.Safe, searchParams.Lang, searchParams.Page)
|
||||||
resultsJSON, err := json.Marshal(results)
|
resultsJSON, err := json.Marshal(results)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error marshalling search results: %v", err)
|
log.Printf("Error marshalling search results: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
responseMsg := Message{
|
responseMsg := Message{
|
||||||
ID: hostID,
|
ID: hostID,
|
||||||
Type: "video-results",
|
Type: "video-results",
|
||||||
Content: string(resultsJSON),
|
Content: string(resultsJSON),
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Sending video search results to %s", searchParams.ResponseAddr)
|
log.Printf("Sending video search results to %s", searchParams.ResponseAddr)
|
||||||
|
|
||||||
if searchParams.ResponseAddr == "" {
|
if searchParams.ResponseAddr == "" {
|
||||||
log.Printf("Error: Response address is empty")
|
log.Printf("Error: Response address is empty")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = sendMessage(searchParams.ResponseAddr, responseMsg)
|
err = sendMessage(searchParams.ResponseAddr, responseMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error sending video search results to %s: %v", searchParams.ResponseAddr, err)
|
log.Printf("Error sending video search results to %s: %v", searchParams.ResponseAddr, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSearchFileMessage(msg Message) {
|
func handleSearchFileMessage(msg Message) {
|
||||||
var searchParams struct {
|
var searchParams struct {
|
||||||
Query string `json:"query"`
|
Query string `json:"query"`
|
||||||
Safe string `json:"safe"`
|
Safe string `json:"safe"`
|
||||||
Lang string `json:"lang"`
|
Lang string `json:"lang"`
|
||||||
Page int `json:"page"`
|
Page int `json:"page"`
|
||||||
ResponseAddr string `json:"responseAddr"`
|
ResponseAddr string `json:"responseAddr"`
|
||||||
}
|
}
|
||||||
err := json.Unmarshal([]byte(msg.Content), &searchParams)
|
err := json.Unmarshal([]byte(msg.Content), &searchParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error parsing search parameters: %v", err)
|
log.Printf("Error parsing search parameters: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Received search-file request. ResponseAddr: %s", searchParams.ResponseAddr)
|
log.Printf("Received search-file request. ResponseAddr: %s", searchParams.ResponseAddr)
|
||||||
|
|
||||||
results := fetchFileResults(searchParams.Query, searchParams.Safe, searchParams.Lang, searchParams.Page)
|
results := fetchFileResults(searchParams.Query, searchParams.Safe, searchParams.Lang, searchParams.Page)
|
||||||
resultsJSON, err := json.Marshal(results)
|
resultsJSON, err := json.Marshal(results)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error marshalling search results: %v", err)
|
log.Printf("Error marshalling search results: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
responseMsg := Message{
|
responseMsg := Message{
|
||||||
ID: hostID,
|
ID: hostID,
|
||||||
Type: "file-results",
|
Type: "file-results",
|
||||||
Content: string(resultsJSON),
|
Content: string(resultsJSON),
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Sending file search results to %s", searchParams.ResponseAddr)
|
log.Printf("Sending file search results to %s", searchParams.ResponseAddr)
|
||||||
|
|
||||||
if searchParams.ResponseAddr == "" {
|
if searchParams.ResponseAddr == "" {
|
||||||
log.Printf("Error: Response address is empty")
|
log.Printf("Error: Response address is empty")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = sendMessage(searchParams.ResponseAddr, responseMsg)
|
err = sendMessage(searchParams.ResponseAddr, responseMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error sending file search results to %s: %v", searchParams.ResponseAddr, err)
|
log.Printf("Error sending file search results to %s: %v", searchParams.ResponseAddr, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSearchForumMessage(msg Message) {
|
func handleSearchForumMessage(msg Message) {
|
||||||
var searchParams struct {
|
var searchParams struct {
|
||||||
Query string `json:"query"`
|
Query string `json:"query"`
|
||||||
Safe string `json:"safe"`
|
Safe string `json:"safe"`
|
||||||
Lang string `json:"lang"`
|
Lang string `json:"lang"`
|
||||||
Page int `json:"page"`
|
Page int `json:"page"`
|
||||||
ResponseAddr string `json:"responseAddr"`
|
ResponseAddr string `json:"responseAddr"`
|
||||||
}
|
}
|
||||||
err := json.Unmarshal([]byte(msg.Content), &searchParams)
|
err := json.Unmarshal([]byte(msg.Content), &searchParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error parsing search parameters: %v", err)
|
log.Printf("Error parsing search parameters: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Received search-forum request. ResponseAddr: %s", searchParams.ResponseAddr)
|
log.Printf("Received search-forum request. ResponseAddr: %s", searchParams.ResponseAddr)
|
||||||
|
|
||||||
results := fetchForumResults(searchParams.Query, searchParams.Safe, searchParams.Lang, searchParams.Page)
|
results := fetchForumResults(searchParams.Query, searchParams.Safe, searchParams.Lang, searchParams.Page)
|
||||||
resultsJSON, err := json.Marshal(results)
|
resultsJSON, err := json.Marshal(results)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error marshalling search results: %v", err)
|
log.Printf("Error marshalling search results: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
responseMsg := Message{
|
responseMsg := Message{
|
||||||
ID: hostID,
|
ID: hostID,
|
||||||
Type: "forum-results",
|
Type: "forum-results",
|
||||||
Content: string(resultsJSON),
|
Content: string(resultsJSON),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log the address to be used for sending the response
|
// Log the address to be used for sending the response
|
||||||
log.Printf("Sending forum search results to %s", searchParams.ResponseAddr)
|
log.Printf("Sending forum search results to %s", searchParams.ResponseAddr)
|
||||||
|
|
||||||
if searchParams.ResponseAddr == "" {
|
if searchParams.ResponseAddr == "" {
|
||||||
log.Printf("Error: Response address is empty")
|
log.Printf("Error: Response address is empty")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = sendMessage(searchParams.ResponseAddr, responseMsg)
|
err = sendMessage(searchParams.ResponseAddr, responseMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error sending forum search results to %s: %v", searchParams.ResponseAddr, err)
|
log.Printf("Error sending forum search results to %s: %v", searchParams.ResponseAddr, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
165
node-request-files.go
Normal file → Executable file
165
node-request-files.go
Normal file → Executable file
|
@ -1,83 +1,82 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"time"
|
||||||
"time"
|
)
|
||||||
)
|
|
||||||
|
func tryOtherNodesForFileSearch(query, safe, lang string, page int, visitedNodes []string) []TorrentResult {
|
||||||
func tryOtherNodesForFileSearch(query, safe, lang string, page int, visitedNodes []string) []TorrentResult {
|
for _, nodeAddr := range peers {
|
||||||
for _, nodeAddr := range peers {
|
if contains(visitedNodes, nodeAddr) {
|
||||||
if contains(visitedNodes, nodeAddr) {
|
continue // Skip nodes already visited
|
||||||
continue // Skip nodes already visited
|
}
|
||||||
}
|
results, err := sendFileSearchRequestToNode(nodeAddr, query, safe, lang, page, visitedNodes)
|
||||||
results, err := sendFileSearchRequestToNode(nodeAddr, query, safe, lang, page, visitedNodes)
|
if err != nil {
|
||||||
if err != nil {
|
printWarn("Error contacting node %s: %v", nodeAddr, err)
|
||||||
log.Printf("Error contacting node %s: %v", nodeAddr, err)
|
continue
|
||||||
continue
|
}
|
||||||
}
|
if len(results) > 0 {
|
||||||
if len(results) > 0 {
|
return results
|
||||||
return results
|
}
|
||||||
}
|
}
|
||||||
}
|
return nil
|
||||||
return nil
|
}
|
||||||
}
|
|
||||||
|
func sendFileSearchRequestToNode(nodeAddr, query, safe, lang string, page int, visitedNodes []string) ([]TorrentResult, error) {
|
||||||
func sendFileSearchRequestToNode(nodeAddr, query, safe, lang string, page int, visitedNodes []string) ([]TorrentResult, error) {
|
visitedNodes = append(visitedNodes, nodeAddr)
|
||||||
visitedNodes = append(visitedNodes, nodeAddr)
|
searchParams := struct {
|
||||||
searchParams := struct {
|
Query string `json:"query"`
|
||||||
Query string `json:"query"`
|
Safe string `json:"safe"`
|
||||||
Safe string `json:"safe"`
|
Lang string `json:"lang"`
|
||||||
Lang string `json:"lang"`
|
Page int `json:"page"`
|
||||||
Page int `json:"page"`
|
ResponseAddr string `json:"responseAddr"`
|
||||||
ResponseAddr string `json:"responseAddr"`
|
VisitedNodes []string `json:"visitedNodes"`
|
||||||
VisitedNodes []string `json:"visitedNodes"`
|
}{
|
||||||
}{
|
Query: query,
|
||||||
Query: query,
|
Safe: safe,
|
||||||
Safe: safe,
|
Lang: lang,
|
||||||
Lang: lang,
|
Page: page,
|
||||||
Page: page,
|
ResponseAddr: fmt.Sprintf("http://localhost:%d/node", config.Port),
|
||||||
ResponseAddr: fmt.Sprintf("http://localhost:%d/node", config.Port),
|
VisitedNodes: visitedNodes,
|
||||||
VisitedNodes: visitedNodes,
|
}
|
||||||
}
|
|
||||||
|
msgBytes, err := json.Marshal(searchParams)
|
||||||
msgBytes, err := json.Marshal(searchParams)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("failed to marshal search parameters: %v", err)
|
||||||
return nil, fmt.Errorf("failed to marshal search parameters: %v", err)
|
}
|
||||||
}
|
|
||||||
|
msg := Message{
|
||||||
msg := Message{
|
ID: hostID,
|
||||||
ID: hostID,
|
Type: "search-file",
|
||||||
Type: "search-file",
|
Content: string(msgBytes),
|
||||||
Content: string(msgBytes),
|
}
|
||||||
}
|
|
||||||
|
err = sendMessage(nodeAddr, msg)
|
||||||
err = sendMessage(nodeAddr, msg)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("failed to send search request to node %s: %v", nodeAddr, err)
|
||||||
return nil, fmt.Errorf("failed to send search request to node %s: %v", nodeAddr, err)
|
}
|
||||||
}
|
|
||||||
|
// Wait for results
|
||||||
// Wait for results
|
select {
|
||||||
select {
|
case res := <-fileResultsChan:
|
||||||
case res := <-fileResultsChan:
|
return res, nil
|
||||||
return res, nil
|
case <-time.After(20 * time.Second):
|
||||||
case <-time.After(20 * time.Second):
|
return nil, fmt.Errorf("timeout waiting for results from node %s", nodeAddr)
|
||||||
return nil, fmt.Errorf("timeout waiting for results from node %s", nodeAddr)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
func handleFileResultsMessage(msg Message) {
|
||||||
func handleFileResultsMessage(msg Message) {
|
var results []TorrentResult
|
||||||
var results []TorrentResult
|
err := json.Unmarshal([]byte(msg.Content), &results)
|
||||||
err := json.Unmarshal([]byte(msg.Content), &results)
|
if err != nil {
|
||||||
if err != nil {
|
printWarn("Error unmarshalling file results: %v", err)
|
||||||
log.Printf("Error unmarshalling file results: %v", err)
|
return
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
printDebug("Received file results: %+v", results)
|
||||||
log.Printf("Received file results: %+v", results)
|
// Send results to fileResultsChan
|
||||||
// Send results to fileResultsChan
|
go func() {
|
||||||
go func() {
|
fileResultsChan <- results
|
||||||
fileResultsChan <- results
|
}()
|
||||||
}()
|
}
|
||||||
}
|
|
||||||
|
|
201
node-request-forums.go
Normal file → Executable file
201
node-request-forums.go
Normal file → Executable file
|
@ -1,101 +1,100 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"time"
|
||||||
"time"
|
)
|
||||||
)
|
|
||||||
|
var forumResultsChan = make(chan []ForumSearchResult)
|
||||||
var forumResultsChan = make(chan []ForumSearchResult)
|
|
||||||
|
func tryOtherNodesForForumSearch(query, safe, lang string, page int) []ForumSearchResult {
|
||||||
func tryOtherNodesForForumSearch(query, safe, lang string, page int) []ForumSearchResult {
|
for _, nodeAddr := range peers {
|
||||||
for _, nodeAddr := range peers {
|
results, err := sendForumSearchRequestToNode(nodeAddr, query, safe, lang, page, []string{})
|
||||||
results, err := sendForumSearchRequestToNode(nodeAddr, query, safe, lang, page, []string{})
|
if err != nil {
|
||||||
if err != nil {
|
printWarn("Error contacting node %s: %v", nodeAddr, err)
|
||||||
log.Printf("Error contacting node %s: %v", nodeAddr, err)
|
continue
|
||||||
continue
|
}
|
||||||
}
|
if len(results) > 0 {
|
||||||
if len(results) > 0 {
|
return results
|
||||||
return results
|
}
|
||||||
}
|
}
|
||||||
}
|
return nil
|
||||||
return nil
|
}
|
||||||
}
|
|
||||||
|
func sendForumSearchRequestToNode(nodeAddr, query, safe, lang string, page int, visitedNodes []string) ([]ForumSearchResult, error) {
|
||||||
func sendForumSearchRequestToNode(nodeAddr, query, safe, lang string, page int, visitedNodes []string) ([]ForumSearchResult, error) {
|
// Check if the current node has already been visited
|
||||||
// Check if the current node has already been visited
|
for _, node := range visitedNodes {
|
||||||
for _, node := range visitedNodes {
|
if node == hostID {
|
||||||
if node == hostID {
|
return nil, fmt.Errorf("loop detected: this node (%s) has already been visited", hostID)
|
||||||
return nil, fmt.Errorf("loop detected: this node (%s) has already been visited", hostID)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Add current node to the list of visited nodes
|
||||||
// Add current node to the list of visited nodes
|
visitedNodes = append(visitedNodes, hostID)
|
||||||
visitedNodes = append(visitedNodes, hostID)
|
|
||||||
|
searchParams := struct {
|
||||||
searchParams := struct {
|
Query string `json:"query"`
|
||||||
Query string `json:"query"`
|
Safe string `json:"safe"`
|
||||||
Safe string `json:"safe"`
|
Lang string `json:"lang"`
|
||||||
Lang string `json:"lang"`
|
Page int `json:"page"`
|
||||||
Page int `json:"page"`
|
ResponseAddr string `json:"responseAddr"`
|
||||||
ResponseAddr string `json:"responseAddr"`
|
VisitedNodes []string `json:"visitedNodes"`
|
||||||
VisitedNodes []string `json:"visitedNodes"`
|
}{
|
||||||
}{
|
Query: query,
|
||||||
Query: query,
|
Safe: safe,
|
||||||
Safe: safe,
|
Lang: lang,
|
||||||
Lang: lang,
|
Page: page,
|
||||||
Page: page,
|
ResponseAddr: fmt.Sprintf("http://localhost:%d/node", config.Port),
|
||||||
ResponseAddr: fmt.Sprintf("http://localhost:%d/node", config.Port),
|
VisitedNodes: visitedNodes,
|
||||||
VisitedNodes: visitedNodes,
|
}
|
||||||
}
|
|
||||||
|
msgBytes, err := json.Marshal(searchParams)
|
||||||
msgBytes, err := json.Marshal(searchParams)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("failed to marshal search parameters: %v", err)
|
||||||
return nil, fmt.Errorf("failed to marshal search parameters: %v", err)
|
}
|
||||||
}
|
|
||||||
|
msg := Message{
|
||||||
msg := Message{
|
ID: hostID,
|
||||||
ID: hostID,
|
Type: "search-forum",
|
||||||
Type: "search-forum",
|
Content: string(msgBytes),
|
||||||
Content: string(msgBytes),
|
}
|
||||||
}
|
|
||||||
|
err = sendMessage(nodeAddr, msg)
|
||||||
err = sendMessage(nodeAddr, msg)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("failed to send search request to node %s: %v", nodeAddr, err)
|
||||||
return nil, fmt.Errorf("failed to send search request to node %s: %v", nodeAddr, err)
|
}
|
||||||
}
|
|
||||||
|
// Wait for results
|
||||||
// Wait for results
|
select {
|
||||||
select {
|
case res := <-forumResultsChan:
|
||||||
case res := <-forumResultsChan:
|
return res, nil
|
||||||
return res, nil
|
case <-time.After(20 * time.Second):
|
||||||
case <-time.After(20 * time.Second):
|
return nil, fmt.Errorf("timeout waiting for results from node %s", nodeAddr)
|
||||||
return nil, fmt.Errorf("timeout waiting for results from node %s", nodeAddr)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
func handleForumResultsMessage(msg Message) {
|
||||||
func handleForumResultsMessage(msg Message) {
|
var results []ForumSearchResult
|
||||||
var results []ForumSearchResult
|
err := json.Unmarshal([]byte(msg.Content), &results)
|
||||||
err := json.Unmarshal([]byte(msg.Content), &results)
|
if err != nil {
|
||||||
if err != nil {
|
printWarn("Error unmarshalling forum results: %v", err)
|
||||||
log.Printf("Error unmarshalling forum results: %v", err)
|
return
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
printDebug("Received forum results: %+v", results)
|
||||||
log.Printf("Received forum results: %+v", results)
|
// Send results to forumResultsChan
|
||||||
// Send results to forumResultsChan
|
go func() {
|
||||||
go func() {
|
forumResultsChan <- results
|
||||||
forumResultsChan <- results
|
}()
|
||||||
}()
|
}
|
||||||
}
|
|
||||||
|
// Used only to answer requests
|
||||||
// Used only to answer requests
|
func fetchForumResults(query, safe, lang string, page int) []ForumSearchResult {
|
||||||
func fetchForumResults(query, safe, lang string, page int) []ForumSearchResult {
|
results, err := PerformRedditSearch(query, safe, page)
|
||||||
results, err := PerformRedditSearch(query, safe, page)
|
if err != nil {
|
||||||
if err != nil {
|
printWarn("Error fetching forum results: %v", err)
|
||||||
log.Printf("Error fetching forum results: %v", err)
|
return nil
|
||||||
return nil
|
}
|
||||||
}
|
return results
|
||||||
return results
|
}
|
||||||
}
|
|
||||||
|
|
169
node-request-images.go
Normal file → Executable file
169
node-request-images.go
Normal file → Executable file
|
@ -1,85 +1,84 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"time"
|
||||||
"time"
|
)
|
||||||
)
|
|
||||||
|
var imageResultsChan = make(chan []ImageSearchResult)
|
||||||
var imageResultsChan = make(chan []ImageSearchResult)
|
|
||||||
|
func handleImageResultsMessage(msg Message) {
|
||||||
func handleImageResultsMessage(msg Message) {
|
var results []ImageSearchResult
|
||||||
var results []ImageSearchResult
|
err := json.Unmarshal([]byte(msg.Content), &results)
|
||||||
err := json.Unmarshal([]byte(msg.Content), &results)
|
if err != nil {
|
||||||
if err != nil {
|
printWarn("Error unmarshalling image results: %v", err)
|
||||||
log.Printf("Error unmarshalling image results: %v", err)
|
return
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
printDebug("Received image results: %+v", results)
|
||||||
log.Printf("Received image results: %+v", results)
|
// Send results to imageResultsChan
|
||||||
// Send results to imageResultsChan
|
go func() {
|
||||||
go func() {
|
imageResultsChan <- results
|
||||||
imageResultsChan <- results
|
}()
|
||||||
}()
|
}
|
||||||
}
|
|
||||||
|
func sendImageSearchRequestToNode(nodeAddr, query, safe, lang string, page int, visitedNodes []string) ([]ImageSearchResult, error) {
|
||||||
func sendImageSearchRequestToNode(nodeAddr, query, safe, lang string, page int, visitedNodes []string) ([]ImageSearchResult, error) {
|
visitedNodes = append(visitedNodes, nodeAddr)
|
||||||
visitedNodes = append(visitedNodes, nodeAddr)
|
searchParams := struct {
|
||||||
searchParams := struct {
|
Query string `json:"query"`
|
||||||
Query string `json:"query"`
|
Safe string `json:"safe"`
|
||||||
Safe string `json:"safe"`
|
Lang string `json:"lang"`
|
||||||
Lang string `json:"lang"`
|
Page int `json:"page"`
|
||||||
Page int `json:"page"`
|
ResponseAddr string `json:"responseAddr"`
|
||||||
ResponseAddr string `json:"responseAddr"`
|
VisitedNodes []string `json:"visitedNodes"`
|
||||||
VisitedNodes []string `json:"visitedNodes"`
|
}{
|
||||||
}{
|
Query: query,
|
||||||
Query: query,
|
Safe: safe,
|
||||||
Safe: safe,
|
Lang: lang,
|
||||||
Lang: lang,
|
Page: page,
|
||||||
Page: page,
|
ResponseAddr: fmt.Sprintf("http://localhost:%d/node", config.Port),
|
||||||
ResponseAddr: fmt.Sprintf("http://localhost:%d/node", config.Port),
|
VisitedNodes: visitedNodes,
|
||||||
VisitedNodes: visitedNodes,
|
}
|
||||||
}
|
|
||||||
|
msgBytes, err := json.Marshal(searchParams)
|
||||||
msgBytes, err := json.Marshal(searchParams)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("failed to marshal search parameters: %v", err)
|
||||||
return nil, fmt.Errorf("failed to marshal search parameters: %v", err)
|
}
|
||||||
}
|
|
||||||
|
msg := Message{
|
||||||
msg := Message{
|
ID: hostID,
|
||||||
ID: hostID,
|
Type: "search-image",
|
||||||
Type: "search-image",
|
Content: string(msgBytes),
|
||||||
Content: string(msgBytes),
|
}
|
||||||
}
|
|
||||||
|
err = sendMessage(nodeAddr, msg)
|
||||||
err = sendMessage(nodeAddr, msg)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("failed to send search request to node %s: %v", nodeAddr, err)
|
||||||
return nil, fmt.Errorf("failed to send search request to node %s: %v", nodeAddr, err)
|
}
|
||||||
}
|
|
||||||
|
// Wait for results
|
||||||
// Wait for results
|
select {
|
||||||
select {
|
case res := <-imageResultsChan:
|
||||||
case res := <-imageResultsChan:
|
return res, nil
|
||||||
return res, nil
|
case <-time.After(30 * time.Second):
|
||||||
case <-time.After(30 * time.Second):
|
return nil, fmt.Errorf("timeout waiting for results from node %s", nodeAddr)
|
||||||
return nil, fmt.Errorf("timeout waiting for results from node %s", nodeAddr)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
func tryOtherNodesForImageSearch(query, safe, lang string, page int, visitedNodes []string) []ImageSearchResult {
|
||||||
func tryOtherNodesForImageSearch(query, safe, lang string, page int, visitedNodes []string) []ImageSearchResult {
|
for _, nodeAddr := range peers {
|
||||||
for _, nodeAddr := range peers {
|
if contains(visitedNodes, nodeAddr) {
|
||||||
if contains(visitedNodes, nodeAddr) {
|
continue // Skip nodes already visited
|
||||||
continue // Skip nodes already visited
|
}
|
||||||
}
|
results, err := sendImageSearchRequestToNode(nodeAddr, query, safe, lang, page, visitedNodes)
|
||||||
results, err := sendImageSearchRequestToNode(nodeAddr, query, safe, lang, page, visitedNodes)
|
if err != nil {
|
||||||
if err != nil {
|
printWarn("Error contacting node %s: %v", nodeAddr, err)
|
||||||
log.Printf("Error contacting node %s: %v", nodeAddr, err)
|
continue
|
||||||
continue
|
}
|
||||||
}
|
if len(results) > 0 {
|
||||||
if len(results) > 0 {
|
return results
|
||||||
return results
|
}
|
||||||
}
|
}
|
||||||
}
|
return nil
|
||||||
return nil
|
}
|
||||||
}
|
|
||||||
|
|
169
node-request-text.go
Normal file → Executable file
169
node-request-text.go
Normal file → Executable file
|
@ -1,85 +1,84 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"time"
|
||||||
"time"
|
)
|
||||||
)
|
|
||||||
|
var textResultsChan = make(chan []TextSearchResult)
|
||||||
var textResultsChan = make(chan []TextSearchResult)
|
|
||||||
|
func tryOtherNodesForTextSearch(query, safe, lang string, page int, visitedNodes []string) []TextSearchResult {
|
||||||
func tryOtherNodesForTextSearch(query, safe, lang string, page int, visitedNodes []string) []TextSearchResult {
|
for _, nodeAddr := range peers {
|
||||||
for _, nodeAddr := range peers {
|
if contains(visitedNodes, nodeAddr) {
|
||||||
if contains(visitedNodes, nodeAddr) {
|
continue // Skip nodes already visited
|
||||||
continue // Skip nodes already visited
|
}
|
||||||
}
|
results, err := sendTextSearchRequestToNode(nodeAddr, query, safe, lang, page, visitedNodes)
|
||||||
results, err := sendTextSearchRequestToNode(nodeAddr, query, safe, lang, page, visitedNodes)
|
if err != nil {
|
||||||
if err != nil {
|
printWarn("Error contacting node %s: %v", nodeAddr, err)
|
||||||
log.Printf("Error contacting node %s: %v", nodeAddr, err)
|
continue
|
||||||
continue
|
}
|
||||||
}
|
if len(results) > 0 {
|
||||||
if len(results) > 0 {
|
return results
|
||||||
return results
|
}
|
||||||
}
|
}
|
||||||
}
|
return nil
|
||||||
return nil
|
}
|
||||||
}
|
|
||||||
|
func sendTextSearchRequestToNode(nodeAddr, query, safe, lang string, page int, visitedNodes []string) ([]TextSearchResult, error) {
|
||||||
func sendTextSearchRequestToNode(nodeAddr, query, safe, lang string, page int, visitedNodes []string) ([]TextSearchResult, error) {
|
visitedNodes = append(visitedNodes, nodeAddr)
|
||||||
visitedNodes = append(visitedNodes, nodeAddr)
|
searchParams := struct {
|
||||||
searchParams := struct {
|
Query string `json:"query"`
|
||||||
Query string `json:"query"`
|
Safe string `json:"safe"`
|
||||||
Safe string `json:"safe"`
|
Lang string `json:"lang"`
|
||||||
Lang string `json:"lang"`
|
Page int `json:"page"`
|
||||||
Page int `json:"page"`
|
ResponseAddr string `json:"responseAddr"`
|
||||||
ResponseAddr string `json:"responseAddr"`
|
VisitedNodes []string `json:"visitedNodes"`
|
||||||
VisitedNodes []string `json:"visitedNodes"`
|
}{
|
||||||
}{
|
Query: query,
|
||||||
Query: query,
|
Safe: safe,
|
||||||
Safe: safe,
|
Lang: lang,
|
||||||
Lang: lang,
|
Page: page,
|
||||||
Page: page,
|
ResponseAddr: fmt.Sprintf("http://localhost:%d/node", config.Port),
|
||||||
ResponseAddr: fmt.Sprintf("http://localhost:%d/node", config.Port),
|
VisitedNodes: visitedNodes,
|
||||||
VisitedNodes: visitedNodes,
|
}
|
||||||
}
|
|
||||||
|
msgBytes, err := json.Marshal(searchParams)
|
||||||
msgBytes, err := json.Marshal(searchParams)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("failed to marshal search parameters: %v", err)
|
||||||
return nil, fmt.Errorf("failed to marshal search parameters: %v", err)
|
}
|
||||||
}
|
|
||||||
|
msg := Message{
|
||||||
msg := Message{
|
ID: hostID,
|
||||||
ID: hostID,
|
Type: "search-text",
|
||||||
Type: "search-text",
|
Content: string(msgBytes),
|
||||||
Content: string(msgBytes),
|
}
|
||||||
}
|
|
||||||
|
err = sendMessage(nodeAddr, msg)
|
||||||
err = sendMessage(nodeAddr, msg)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("failed to send search request to node %s: %v", nodeAddr, err)
|
||||||
return nil, fmt.Errorf("failed to send search request to node %s: %v", nodeAddr, err)
|
}
|
||||||
}
|
|
||||||
|
// Wait for results
|
||||||
// Wait for results
|
select {
|
||||||
select {
|
case res := <-textResultsChan:
|
||||||
case res := <-textResultsChan:
|
return res, nil
|
||||||
return res, nil
|
case <-time.After(20 * time.Second):
|
||||||
case <-time.After(20 * time.Second):
|
return nil, fmt.Errorf("timeout waiting for results from node %s", nodeAddr)
|
||||||
return nil, fmt.Errorf("timeout waiting for results from node %s", nodeAddr)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
func handleTextResultsMessage(msg Message) {
|
||||||
func handleTextResultsMessage(msg Message) {
|
var results []TextSearchResult
|
||||||
var results []TextSearchResult
|
err := json.Unmarshal([]byte(msg.Content), &results)
|
||||||
err := json.Unmarshal([]byte(msg.Content), &results)
|
if err != nil {
|
||||||
if err != nil {
|
printWarn("Error unmarshalling text results: %v", err)
|
||||||
log.Printf("Error unmarshalling text results: %v", err)
|
return
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
printDebug("Received text results: %+v", results)
|
||||||
log.Printf("Received text results: %+v", results)
|
// Send results to textResultsChan
|
||||||
// Send results to textResultsChan
|
go func() {
|
||||||
go func() {
|
textResultsChan <- results
|
||||||
textResultsChan <- results
|
}()
|
||||||
}()
|
}
|
||||||
}
|
|
||||||
|
|
165
node-request-video.go
Normal file → Executable file
165
node-request-video.go
Normal file → Executable file
|
@ -1,83 +1,82 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"time"
|
||||||
"time"
|
)
|
||||||
)
|
|
||||||
|
func tryOtherNodesForVideoSearch(query, safe, lang string, page int, visitedNodes []string) []VideoResult {
|
||||||
func tryOtherNodesForVideoSearch(query, safe, lang string, page int, visitedNodes []string) []VideoResult {
|
for _, nodeAddr := range peers {
|
||||||
for _, nodeAddr := range peers {
|
if contains(visitedNodes, nodeAddr) {
|
||||||
if contains(visitedNodes, nodeAddr) {
|
continue // Skip nodes already visited
|
||||||
continue // Skip nodes already visited
|
}
|
||||||
}
|
results, err := sendVideoSearchRequestToNode(nodeAddr, query, safe, lang, page, visitedNodes)
|
||||||
results, err := sendVideoSearchRequestToNode(nodeAddr, query, safe, lang, page, visitedNodes)
|
if err != nil {
|
||||||
if err != nil {
|
printWarn("Error contacting node %s: %v", nodeAddr, err)
|
||||||
log.Printf("Error contacting node %s: %v", nodeAddr, err)
|
continue
|
||||||
continue
|
}
|
||||||
}
|
if len(results) > 0 {
|
||||||
if len(results) > 0 {
|
return results
|
||||||
return results
|
}
|
||||||
}
|
}
|
||||||
}
|
return nil
|
||||||
return nil
|
}
|
||||||
}
|
|
||||||
|
func sendVideoSearchRequestToNode(nodeAddr, query, safe, lang string, page int, visitedNodes []string) ([]VideoResult, error) {
|
||||||
func sendVideoSearchRequestToNode(nodeAddr, query, safe, lang string, page int, visitedNodes []string) ([]VideoResult, error) {
|
visitedNodes = append(visitedNodes, nodeAddr)
|
||||||
visitedNodes = append(visitedNodes, nodeAddr)
|
searchParams := struct {
|
||||||
searchParams := struct {
|
Query string `json:"query"`
|
||||||
Query string `json:"query"`
|
Safe string `json:"safe"`
|
||||||
Safe string `json:"safe"`
|
Lang string `json:"lang"`
|
||||||
Lang string `json:"lang"`
|
Page int `json:"page"`
|
||||||
Page int `json:"page"`
|
ResponseAddr string `json:"responseAddr"`
|
||||||
ResponseAddr string `json:"responseAddr"`
|
VisitedNodes []string `json:"visitedNodes"`
|
||||||
VisitedNodes []string `json:"visitedNodes"`
|
}{
|
||||||
}{
|
Query: query,
|
||||||
Query: query,
|
Safe: safe,
|
||||||
Safe: safe,
|
Lang: lang,
|
||||||
Lang: lang,
|
Page: page,
|
||||||
Page: page,
|
ResponseAddr: fmt.Sprintf("http://localhost:%d/node", config.Port),
|
||||||
ResponseAddr: fmt.Sprintf("http://localhost:%d/node", config.Port),
|
VisitedNodes: visitedNodes,
|
||||||
VisitedNodes: visitedNodes,
|
}
|
||||||
}
|
|
||||||
|
msgBytes, err := json.Marshal(searchParams)
|
||||||
msgBytes, err := json.Marshal(searchParams)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("failed to marshal search parameters: %v", err)
|
||||||
return nil, fmt.Errorf("failed to marshal search parameters: %v", err)
|
}
|
||||||
}
|
|
||||||
|
msg := Message{
|
||||||
msg := Message{
|
ID: hostID,
|
||||||
ID: hostID,
|
Type: "search-video",
|
||||||
Type: "search-video",
|
Content: string(msgBytes),
|
||||||
Content: string(msgBytes),
|
}
|
||||||
}
|
|
||||||
|
err = sendMessage(nodeAddr, msg)
|
||||||
err = sendMessage(nodeAddr, msg)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("failed to send search request to node %s: %v", nodeAddr, err)
|
||||||
return nil, fmt.Errorf("failed to send search request to node %s: %v", nodeAddr, err)
|
}
|
||||||
}
|
|
||||||
|
// Wait for results
|
||||||
// Wait for results
|
select {
|
||||||
select {
|
case res := <-videoResultsChan:
|
||||||
case res := <-videoResultsChan:
|
return res, nil
|
||||||
return res, nil
|
case <-time.After(20 * time.Second):
|
||||||
case <-time.After(20 * time.Second):
|
return nil, fmt.Errorf("timeout waiting for results from node %s", nodeAddr)
|
||||||
return nil, fmt.Errorf("timeout waiting for results from node %s", nodeAddr)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
func handleVideoResultsMessage(msg Message) {
|
||||||
func handleVideoResultsMessage(msg Message) {
|
var results []VideoResult
|
||||||
var results []VideoResult
|
err := json.Unmarshal([]byte(msg.Content), &results)
|
||||||
err := json.Unmarshal([]byte(msg.Content), &results)
|
if err != nil {
|
||||||
if err != nil {
|
printWarn("Error unmarshalling video results: %v", err)
|
||||||
log.Printf("Error unmarshalling video results: %v", err)
|
return
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
printDebug("Received video results: %+v", results)
|
||||||
log.Printf("Received video results: %+v", results)
|
// Send results to videoResultsChan
|
||||||
// Send results to videoResultsChan
|
go func() {
|
||||||
go func() {
|
videoResultsChan <- results
|
||||||
videoResultsChan <- results
|
}()
|
||||||
}()
|
}
|
||||||
}
|
|
||||||
|
|
31
run.bat
Executable file
31
run.bat
Executable file
|
@ -0,0 +1,31 @@
|
||||||
|
@echo off
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
|
rem Directory where the Go files are located
|
||||||
|
set GO_DIR=C:\path\to\your\go\files
|
||||||
|
|
||||||
|
rem Explicitly list the main files in the required order
|
||||||
|
set FILES=main.go init.go search-engine.go text.go text-google.go text-librex.go text-brave.go text-duckduckgo.go common.go cache.go agent.go files.go files-thepiratebay.go files-torrentgalaxy.go forums.go get-searchxng.go imageproxy.go images.go images-imgur.go images-quant.go map.go node.go open-search.go video.go
|
||||||
|
|
||||||
|
rem Change to the directory with the Go files
|
||||||
|
pushd %GO_DIR%
|
||||||
|
|
||||||
|
rem Find all other .go files that were not explicitly listed
|
||||||
|
set OTHER_GO_FILES=
|
||||||
|
|
||||||
|
for %%f in (*.go) do (
|
||||||
|
set file=%%~nxf
|
||||||
|
set found=0
|
||||||
|
for %%i in (%FILES%) do (
|
||||||
|
if /i "%%i"=="!file!" set found=1
|
||||||
|
)
|
||||||
|
if !found!==0 (
|
||||||
|
set OTHER_GO_FILES=!OTHER_GO_FILES! "%%f"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
rem Run the Go program with the specified files first, followed by the remaining files
|
||||||
|
go run %FILES% %OTHER_GO_FILES%
|
||||||
|
|
||||||
|
rem Return to the original directory
|
||||||
|
popd
|
366
text.go
Normal file → Executable file
366
text.go
Normal file → Executable file
|
@ -1,183 +1,183 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var textSearchEngines []SearchEngine
|
var textSearchEngines []SearchEngine
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
textSearchEngines = []SearchEngine{
|
textSearchEngines = []SearchEngine{
|
||||||
{Name: "Google", Func: wrapTextSearchFunc(PerformGoogleTextSearch), Weight: 1},
|
{Name: "Google", Func: wrapTextSearchFunc(PerformGoogleTextSearch), Weight: 1},
|
||||||
{Name: "LibreX", Func: wrapTextSearchFunc(PerformLibreXTextSearch), Weight: 2},
|
{Name: "LibreX", Func: wrapTextSearchFunc(PerformLibreXTextSearch), Weight: 2},
|
||||||
{Name: "Brave", Func: wrapTextSearchFunc(PerformBraveTextSearch), Weight: 2},
|
{Name: "Brave", Func: wrapTextSearchFunc(PerformBraveTextSearch), Weight: 2},
|
||||||
{Name: "DuckDuckGo", Func: wrapTextSearchFunc(PerformDuckDuckGoTextSearch), Weight: 5},
|
{Name: "DuckDuckGo", Func: wrapTextSearchFunc(PerformDuckDuckGoTextSearch), Weight: 5},
|
||||||
// {Name: "SearXNG", Func: wrapTextSearchFunc(PerformSearXNGTextSearch), Weight: 2}, // Uncomment when implemented
|
// {Name: "SearXNG", Func: wrapTextSearchFunc(PerformSearXNGTextSearch), Weight: 2}, // Uncomment when implemented
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleTextSearch(w http.ResponseWriter, settings UserSettings, query, safe, lang string, page int) {
|
func HandleTextSearch(w http.ResponseWriter, settings UserSettings, query string, page int) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
cacheKey := CacheKey{Query: query, Page: page, Safe: safe == "true", Lang: lang, Type: "text"}
|
cacheKey := CacheKey{Query: query, Page: page, Safe: settings.SafeSearch == "true", Lang: settings.Language, Type: "text"}
|
||||||
combinedResults := getTextResultsFromCacheOrFetch(cacheKey, query, safe, lang, page)
|
combinedResults := getTextResultsFromCacheOrFetch(cacheKey, query, settings.SafeSearch, settings.Language, page)
|
||||||
|
|
||||||
hasPrevPage := page > 1 // dupe
|
hasPrevPage := page > 1 // dupe
|
||||||
|
|
||||||
//displayResults(w, combinedResults, query, lang, time.Since(startTime).Seconds(), page, hasPrevPage, hasNextPage)
|
//displayResults(w, combinedResults, query, lang, time.Since(startTime).Seconds(), page, hasPrevPage, hasNextPage)
|
||||||
|
|
||||||
// Prefetch next and previous pages
|
// Prefetch next and previous pages
|
||||||
go prefetchPage(query, safe, lang, page+1)
|
go prefetchPage(query, settings.SafeSearch, settings.Language, page+1)
|
||||||
if hasPrevPage {
|
if hasPrevPage {
|
||||||
go prefetchPage(query, safe, lang, page-1)
|
go prefetchPage(query, settings.SafeSearch, settings.Language, page-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
elapsedTime := time.Since(startTime)
|
elapsedTime := time.Since(startTime)
|
||||||
tmpl, err := template.New("text.html").Funcs(funcs).ParseFiles("templates/text.html")
|
tmpl, err := template.New("text.html").Funcs(funcs).ParseFiles("templates/text.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printErr("Error parsing template: %v", err)
|
printErr("Error parsing template: %v", err)
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := struct {
|
data := struct {
|
||||||
Results []TextSearchResult
|
Results []TextSearchResult
|
||||||
Query string
|
Query string
|
||||||
Page int
|
Page int
|
||||||
Fetched string
|
Fetched string
|
||||||
LanguageOptions []LanguageOption
|
LanguageOptions []LanguageOption
|
||||||
CurrentLang string
|
CurrentLang string
|
||||||
HasPrevPage bool
|
HasPrevPage bool
|
||||||
HasNextPage bool
|
HasNextPage bool
|
||||||
NoResults bool
|
NoResults bool
|
||||||
Theme string
|
Theme string
|
||||||
}{
|
}{
|
||||||
Results: combinedResults,
|
Results: combinedResults,
|
||||||
Query: query,
|
Query: query,
|
||||||
Page: page,
|
Page: page,
|
||||||
Fetched: fmt.Sprintf("%.2f seconds", elapsedTime.Seconds()),
|
Fetched: fmt.Sprintf("%.2f seconds", elapsedTime.Seconds()),
|
||||||
LanguageOptions: languageOptions,
|
LanguageOptions: languageOptions,
|
||||||
CurrentLang: lang,
|
CurrentLang: settings.Language,
|
||||||
HasPrevPage: page > 1,
|
HasPrevPage: page > 1,
|
||||||
HasNextPage: len(combinedResults) >= 50,
|
HasNextPage: len(combinedResults) >= 50,
|
||||||
NoResults: len(combinedResults) == 0,
|
NoResults: len(combinedResults) == 0,
|
||||||
Theme: settings.Theme,
|
Theme: settings.Theme,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tmpl.Execute(w, data)
|
err = tmpl.Execute(w, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printErr("Error executing template: %v", err)
|
printErr("Error executing template: %v", err)
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTextResultsFromCacheOrFetch(cacheKey CacheKey, query, safe, lang string, page int) []TextSearchResult {
|
func getTextResultsFromCacheOrFetch(cacheKey CacheKey, query, safe, lang string, page int) []TextSearchResult {
|
||||||
cacheChan := make(chan []SearchResult)
|
cacheChan := make(chan []SearchResult)
|
||||||
var combinedResults []TextSearchResult
|
var combinedResults []TextSearchResult
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
results, exists := resultsCache.Get(cacheKey)
|
results, exists := resultsCache.Get(cacheKey)
|
||||||
if exists {
|
if exists {
|
||||||
printInfo("Cache hit")
|
printInfo("Cache hit")
|
||||||
cacheChan <- results
|
cacheChan <- results
|
||||||
} else {
|
} else {
|
||||||
printInfo("Cache miss")
|
printInfo("Cache miss")
|
||||||
cacheChan <- nil
|
cacheChan <- nil
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case results := <-cacheChan:
|
case results := <-cacheChan:
|
||||||
if results == nil {
|
if results == nil {
|
||||||
combinedResults = fetchTextResults(query, safe, lang, page)
|
combinedResults = fetchTextResults(query, safe, lang, page)
|
||||||
if len(combinedResults) > 0 {
|
if len(combinedResults) > 0 {
|
||||||
resultsCache.Set(cacheKey, convertToSearchResults(combinedResults))
|
resultsCache.Set(cacheKey, convertToSearchResults(combinedResults))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
textResults, _, _ := convertToSpecificResults(results)
|
textResults, _, _ := convertToSpecificResults(results)
|
||||||
combinedResults = textResults
|
combinedResults = textResults
|
||||||
}
|
}
|
||||||
case <-time.After(2 * time.Second):
|
case <-time.After(2 * time.Second):
|
||||||
printInfo("Cache check timeout")
|
printInfo("Cache check timeout")
|
||||||
combinedResults = fetchTextResults(query, safe, lang, page)
|
combinedResults = fetchTextResults(query, safe, lang, page)
|
||||||
if len(combinedResults) > 0 {
|
if len(combinedResults) > 0 {
|
||||||
resultsCache.Set(cacheKey, convertToSearchResults(combinedResults))
|
resultsCache.Set(cacheKey, convertToSearchResults(combinedResults))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return combinedResults
|
return combinedResults
|
||||||
}
|
}
|
||||||
|
|
||||||
func prefetchPage(query, safe, lang string, page int) {
|
func prefetchPage(query, safe, lang string, page int) {
|
||||||
cacheKey := CacheKey{Query: query, Page: page, Safe: safe == "true", Lang: lang, Type: "text"}
|
cacheKey := CacheKey{Query: query, Page: page, Safe: safe == "true", Lang: lang, Type: "text"}
|
||||||
if _, exists := resultsCache.Get(cacheKey); !exists {
|
if _, exists := resultsCache.Get(cacheKey); !exists {
|
||||||
printInfo("Page %d not cached, caching now...", page)
|
printInfo("Page %d not cached, caching now...", page)
|
||||||
pageResults := fetchTextResults(query, safe, lang, page)
|
pageResults := fetchTextResults(query, safe, lang, page)
|
||||||
if len(pageResults) > 0 {
|
if len(pageResults) > 0 {
|
||||||
resultsCache.Set(cacheKey, convertToSearchResults(pageResults))
|
resultsCache.Set(cacheKey, convertToSearchResults(pageResults))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
printInfo("Page %d already cached", page)
|
printInfo("Page %d already cached", page)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchTextResults(query, safe, lang string, page int) []TextSearchResult {
|
func fetchTextResults(query, safe, lang string, page int) []TextSearchResult {
|
||||||
var results []TextSearchResult
|
var results []TextSearchResult
|
||||||
|
|
||||||
for _, engine := range textSearchEngines {
|
for _, engine := range textSearchEngines {
|
||||||
printInfo("Using search engine: %s", engine.Name)
|
printInfo("Using search engine: %s", engine.Name)
|
||||||
|
|
||||||
searchResults, duration, err := engine.Func(query, safe, lang, page)
|
searchResults, duration, err := engine.Func(query, safe, lang, page)
|
||||||
updateEngineMetrics(&engine, duration, err == nil)
|
updateEngineMetrics(&engine, duration, err == nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printWarn("Error performing search with %s: %v", engine.Name, err)
|
printWarn("Error performing search with %s: %v", engine.Name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
results = append(results, validateResults(searchResults)...)
|
results = append(results, validateResults(searchResults)...)
|
||||||
|
|
||||||
// If results are found, break out of the loop
|
// If results are found, break out of the loop
|
||||||
if len(results) > 0 {
|
if len(results) > 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no results found after trying all engines
|
// If no results found after trying all engines
|
||||||
if len(results) == 0 {
|
if len(results) == 0 {
|
||||||
printWarn("No text results found for query: %s, trying other nodes", query)
|
printWarn("No text results found for query: %s, trying other nodes", query)
|
||||||
results = tryOtherNodesForTextSearch(query, safe, lang, page, []string{hostID})
|
results = tryOtherNodesForTextSearch(query, safe, lang, page, []string{hostID})
|
||||||
}
|
}
|
||||||
|
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateResults(searchResults []SearchResult) []TextSearchResult {
|
func validateResults(searchResults []SearchResult) []TextSearchResult {
|
||||||
var validResults []TextSearchResult
|
var validResults []TextSearchResult
|
||||||
|
|
||||||
// Remove anything that is missing a URL or Header
|
// Remove anything that is missing a URL or Header
|
||||||
for _, result := range searchResults {
|
for _, result := range searchResults {
|
||||||
textResult := result.(TextSearchResult)
|
textResult := result.(TextSearchResult)
|
||||||
if textResult.URL != "" || textResult.Header != "" {
|
if textResult.URL != "" || textResult.Header != "" {
|
||||||
validResults = append(validResults, textResult)
|
validResults = append(validResults, textResult)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return validResults
|
return validResults
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapTextSearchFunc(f func(string, string, string, int) ([]TextSearchResult, time.Duration, error)) func(string, string, string, int) ([]SearchResult, time.Duration, error) {
|
func wrapTextSearchFunc(f func(string, string, string, int) ([]TextSearchResult, time.Duration, error)) func(string, string, string, int) ([]SearchResult, time.Duration, error) {
|
||||||
return func(query, safe, lang string, page int) ([]SearchResult, time.Duration, error) {
|
return func(query, safe, lang string, page int) ([]SearchResult, time.Duration, error) {
|
||||||
textResults, duration, err := f(query, safe, lang, page)
|
textResults, duration, err := f(query, safe, lang, page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, duration, err
|
return nil, duration, err
|
||||||
}
|
}
|
||||||
searchResults := make([]SearchResult, len(textResults))
|
searchResults := make([]SearchResult, len(textResults))
|
||||||
for i, result := range textResults {
|
for i, result := range textResults {
|
||||||
searchResults[i] = result
|
searchResults[i] = result
|
||||||
}
|
}
|
||||||
return searchResults, duration, nil
|
return searchResults, duration, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
422
video.go
Normal file → Executable file
422
video.go
Normal file → Executable file
|
@ -1,211 +1,211 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const retryDuration = 12 * time.Hour // Retry duration for unresponding piped instances
|
const retryDuration = 12 * time.Hour // Retry duration for unresponding piped instances
|
||||||
|
|
||||||
var (
|
var (
|
||||||
pipedInstances = []string{
|
pipedInstances = []string{
|
||||||
"api.piped.yt",
|
"api.piped.yt",
|
||||||
"pipedapi.moomoo.me",
|
"pipedapi.moomoo.me",
|
||||||
"pipedapi.darkness.services",
|
"pipedapi.darkness.services",
|
||||||
"pipedapi.kavin.rocks",
|
"pipedapi.kavin.rocks",
|
||||||
"piped-api.hostux.net",
|
"piped-api.hostux.net",
|
||||||
"pipedapi.syncpundit.io",
|
"pipedapi.syncpundit.io",
|
||||||
"piped-api.cfe.re",
|
"piped-api.cfe.re",
|
||||||
"pipedapi.in.projectsegfau.lt",
|
"pipedapi.in.projectsegfau.lt",
|
||||||
"piapi.ggtyler.dev",
|
"piapi.ggtyler.dev",
|
||||||
"piped-api.codespace.cz",
|
"piped-api.codespace.cz",
|
||||||
"pipedapi.coldforge.xyz",
|
"pipedapi.coldforge.xyz",
|
||||||
"pipedapi.osphost.fi",
|
"pipedapi.osphost.fi",
|
||||||
}
|
}
|
||||||
disabledInstances = make(map[string]bool)
|
disabledInstances = make(map[string]bool)
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
videoResultsChan = make(chan []VideoResult) // Channel to receive video results from other nodes
|
videoResultsChan = make(chan []VideoResult) // Channel to receive video results from other nodes
|
||||||
)
|
)
|
||||||
|
|
||||||
// VideoAPIResponse matches the structure of the JSON response from the Piped API
|
// VideoAPIResponse matches the structure of the JSON response from the Piped API
|
||||||
type VideoAPIResponse struct {
|
type VideoAPIResponse struct {
|
||||||
Items []struct {
|
Items []struct {
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
UploaderName string `json:"uploaderName"`
|
UploaderName string `json:"uploaderName"`
|
||||||
Views int `json:"views"`
|
Views int `json:"views"`
|
||||||
Thumbnail string `json:"thumbnail"`
|
Thumbnail string `json:"thumbnail"`
|
||||||
Duration int `json:"duration"`
|
Duration int `json:"duration"`
|
||||||
UploadedDate string `json:"uploadedDate"`
|
UploadedDate string `json:"uploadedDate"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
} `json:"items"`
|
} `json:"items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to format views similarly to the Python code
|
// Function to format views similarly to the Python code
|
||||||
func formatViews(views int) string {
|
func formatViews(views int) string {
|
||||||
switch {
|
switch {
|
||||||
case views >= 1_000_000_000:
|
case views >= 1_000_000_000:
|
||||||
return fmt.Sprintf("%.1fB views", float64(views)/1_000_000_000)
|
return fmt.Sprintf("%.1fB views", float64(views)/1_000_000_000)
|
||||||
case views >= 1_000_000:
|
case views >= 1_000_000:
|
||||||
return fmt.Sprintf("%.1fM views", float64(views)/1_000_000)
|
return fmt.Sprintf("%.1fM views", float64(views)/1_000_000)
|
||||||
case views >= 10_000:
|
case views >= 10_000:
|
||||||
return fmt.Sprintf("%.1fK views", float64(views)/1_000)
|
return fmt.Sprintf("%.1fK views", float64(views)/1_000)
|
||||||
case views == 1:
|
case views == 1:
|
||||||
return fmt.Sprintf("%d view", views)
|
return fmt.Sprintf("%d view", views)
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("%d views", views)
|
return fmt.Sprintf("%d views", views)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// formatDuration formats video duration as done in the Python code
|
// formatDuration formats video duration as done in the Python code
|
||||||
func formatDuration(seconds int) string {
|
func formatDuration(seconds int) string {
|
||||||
if 0 > seconds {
|
if 0 > seconds {
|
||||||
return "Live"
|
return "Live"
|
||||||
}
|
}
|
||||||
|
|
||||||
hours := seconds / 3600
|
hours := seconds / 3600
|
||||||
minutes := (seconds % 3600) / 60
|
minutes := (seconds % 3600) / 60
|
||||||
seconds = seconds % 60
|
seconds = seconds % 60
|
||||||
|
|
||||||
if hours > 0 {
|
if hours > 0 {
|
||||||
return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds)
|
return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%02d:%02d", minutes, seconds)
|
return fmt.Sprintf("%02d:%02d", minutes, seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
go checkDisabledInstancesPeriodically()
|
go checkDisabledInstancesPeriodically()
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkDisabledInstancesPeriodically() {
|
func checkDisabledInstancesPeriodically() {
|
||||||
checkAndReactivateInstances() // Initial immediate check
|
checkAndReactivateInstances() // Initial immediate check
|
||||||
ticker := time.NewTicker(retryDuration)
|
ticker := time.NewTicker(retryDuration)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
checkAndReactivateInstances()
|
checkAndReactivateInstances()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkAndReactivateInstances() {
|
func checkAndReactivateInstances() {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
|
||||||
for instance, isDisabled := range disabledInstances {
|
for instance, isDisabled := range disabledInstances {
|
||||||
if isDisabled {
|
if isDisabled {
|
||||||
// Check if the instance is available again
|
// Check if the instance is available again
|
||||||
if testInstanceAvailability(instance) {
|
if testInstanceAvailability(instance) {
|
||||||
printInfo("Instance %s is now available and reactivated.", instance)
|
printInfo("Instance %s is now available and reactivated.", instance)
|
||||||
delete(disabledInstances, instance)
|
delete(disabledInstances, instance)
|
||||||
} else {
|
} else {
|
||||||
printInfo("Instance %s is still not available.", instance)
|
printInfo("Instance %s is still not available.", instance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testInstanceAvailability(instance string) bool {
|
func testInstanceAvailability(instance string) bool {
|
||||||
resp, err := http.Get(fmt.Sprintf("https://%s/search?q=%s&filter=all", instance, url.QueryEscape("test")))
|
resp, err := http.Get(fmt.Sprintf("https://%s/search?q=%s&filter=all", instance, url.QueryEscape("test")))
|
||||||
if err != nil || resp.StatusCode != http.StatusOK {
|
if err != nil || resp.StatusCode != http.StatusOK {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeHTMLRequest(query, safe, lang string, page int) (*VideoAPIResponse, error) {
|
func makeHTMLRequest(query, safe, lang string, page int) (*VideoAPIResponse, error) {
|
||||||
var lastError error
|
var lastError error
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
|
||||||
for _, instance := range pipedInstances {
|
for _, instance := range pipedInstances {
|
||||||
if disabledInstances[instance] {
|
if disabledInstances[instance] {
|
||||||
continue // Skip this instance because it's still disabled
|
continue // Skip this instance because it's still disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
url := fmt.Sprintf("https://%s/search?q=%s&filter=all&safe=%s&lang=%s&page=%d", instance, url.QueryEscape(query), safe, lang, page)
|
url := fmt.Sprintf("https://%s/search?q=%s&filter=all&safe=%s&lang=%s&page=%d", instance, url.QueryEscape(query), safe, lang, page)
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil || resp.StatusCode != http.StatusOK {
|
if err != nil || resp.StatusCode != http.StatusOK {
|
||||||
printInfo("Disabling instance %s due to error or status code: %v", instance, err)
|
printInfo("Disabling instance %s due to error or status code: %v", instance, err)
|
||||||
disabledInstances[instance] = true
|
disabledInstances[instance] = true
|
||||||
lastError = fmt.Errorf("error making request to %s: %w", instance, err)
|
lastError = fmt.Errorf("error making request to %s: %w", instance, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
var apiResp VideoAPIResponse
|
var apiResp VideoAPIResponse
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
|
||||||
lastError = fmt.Errorf("error decoding response from %s: %w", instance, err)
|
lastError = fmt.Errorf("error decoding response from %s: %w", instance, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return &apiResp, nil
|
return &apiResp, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("all instances failed, last error: %v", lastError)
|
return nil, fmt.Errorf("all instances failed, last error: %v", lastError)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleVideoSearch adapted from the Python `videoResults`, handles video search requests
|
// handleVideoSearch adapted from the Python `videoResults`, handles video search requests
|
||||||
func handleVideoSearch(w http.ResponseWriter, settings UserSettings, query, safe, lang string, page int) {
|
func handleVideoSearch(w http.ResponseWriter, settings UserSettings, query string, page int) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
results := fetchVideoResults(query, settings.SafeSearch, settings.Language, page)
|
results := fetchVideoResults(query, settings.SafeSearch, settings.Language, page)
|
||||||
if len(results) == 0 {
|
if len(results) == 0 {
|
||||||
printWarn("No results from primary search, trying other nodes")
|
printWarn("No results from primary search, trying other nodes")
|
||||||
results = tryOtherNodesForVideoSearch(query, settings.SafeSearch, settings.Language, page, []string{hostID})
|
results = tryOtherNodesForVideoSearch(query, settings.SafeSearch, settings.Language, page, []string{hostID})
|
||||||
}
|
}
|
||||||
|
|
||||||
elapsed := time.Since(start)
|
elapsed := time.Since(start)
|
||||||
tmpl, err := template.New("videos.html").Funcs(funcs).ParseFiles("templates/videos.html")
|
tmpl, err := template.New("videos.html").Funcs(funcs).ParseFiles("templates/videos.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printErr("Error parsing template: %v", err)
|
printErr("Error parsing template: %v", err)
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tmpl.Execute(w, map[string]interface{}{
|
err = tmpl.Execute(w, map[string]interface{}{
|
||||||
"Results": results,
|
"Results": results,
|
||||||
"Query": query,
|
"Query": query,
|
||||||
"Fetched": fmt.Sprintf("%.2f seconds", elapsed.Seconds()),
|
"Fetched": fmt.Sprintf("%.2f seconds", elapsed.Seconds()),
|
||||||
"Page": page,
|
"Page": page,
|
||||||
"HasPrevPage": page > 1,
|
"HasPrevPage": page > 1,
|
||||||
"HasNextPage": len(results) > 0, // no
|
"HasNextPage": len(results) > 0, // no
|
||||||
"Theme": settings.Theme,
|
"Theme": settings.Theme,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printErr("Error executing template: %v", err)
|
printErr("Error executing template: %v", err)
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchVideoResults(query, safe, lang string, page int) []VideoResult {
|
func fetchVideoResults(query, safe, lang string, page int) []VideoResult {
|
||||||
apiResp, err := makeHTMLRequest(query, safe, lang, page)
|
apiResp, err := makeHTMLRequest(query, safe, lang, page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printWarn("Error fetching video results: %v", err)
|
printWarn("Error fetching video results: %v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var results []VideoResult
|
var results []VideoResult
|
||||||
for _, item := range apiResp.Items {
|
for _, item := range apiResp.Items {
|
||||||
if item.Type == "channel" || item.Type == "playlist" {
|
if item.Type == "channel" || item.Type == "playlist" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if item.UploadedDate == "" {
|
if item.UploadedDate == "" {
|
||||||
item.UploadedDate = "Now"
|
item.UploadedDate = "Now"
|
||||||
}
|
}
|
||||||
|
|
||||||
results = append(results, VideoResult{
|
results = append(results, VideoResult{
|
||||||
Href: fmt.Sprintf("https://youtube.com%s", item.URL),
|
Href: fmt.Sprintf("https://youtube.com%s", item.URL),
|
||||||
Title: item.Title,
|
Title: item.Title,
|
||||||
Date: item.UploadedDate,
|
Date: item.UploadedDate,
|
||||||
Views: formatViews(item.Views),
|
Views: formatViews(item.Views),
|
||||||
Creator: item.UploaderName,
|
Creator: item.UploaderName,
|
||||||
Publisher: "Piped",
|
Publisher: "Piped",
|
||||||
Image: fmt.Sprintf("/img_proxy?url=%s", url.QueryEscape(item.Thumbnail)),
|
Image: fmt.Sprintf("/img_proxy?url=%s", url.QueryEscape(item.Thumbnail)),
|
||||||
Duration: formatDuration(item.Duration),
|
Duration: formatDuration(item.Duration),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue