package main import ( "encoding/json" "fmt" "html/template" "log" "math" "net/http" "net/url" "time" ) func PerformRedditSearch(query string, safe string, page int) ([]ForumSearchResult, error) { const ( pageSize = 25 baseURL = "https://www.reddit.com" maxRetries = 5 initialBackoff = 2 * time.Second ) var results []ForumSearchResult searchURL := fmt.Sprintf("%s/search.json?q=%s&limit=%d&start=%d", baseURL, url.QueryEscape(query), pageSize, page*pageSize) var resp *http.Response var err error // Retry logic with exponential backoff for i := 0; i <= maxRetries; i++ { resp, err = http.Get(searchURL) if err != nil { return nil, fmt.Errorf("making request: %v", err) } if resp.StatusCode != http.StatusTooManyRequests { break } // Wait for some time before retrying backoff := time.Duration(math.Pow(2, float64(i))) * initialBackoff time.Sleep(backoff) } if err != nil { return nil, fmt.Errorf("making request: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) } var searchResults map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&searchResults); err != nil { return nil, fmt.Errorf("decoding response: %v", err) } data, ok := searchResults["data"].(map[string]interface{}) if !ok { return nil, fmt.Errorf("no data field in response") } posts, ok := data["children"].([]interface{}) if !ok { return nil, fmt.Errorf("no children field in data") } for _, post := range posts { postData := post.(map[string]interface{})["data"].(map[string]interface{}) if safe == "active" && postData["over_18"].(bool) { continue } header := postData["title"].(string) description := postData["selftext"].(string) if len(description) > 500 { description = description[:500] + "..." } publishedDate := time.Unix(int64(postData["created_utc"].(float64)), 0) permalink := postData["permalink"].(string) resultURL := fmt.Sprintf("%s%s", baseURL, permalink) result := ForumSearchResult{ URL: resultURL, Header: header, Description: description, PublishedDate: publishedDate, } thumbnail := postData["thumbnail"].(string) if parsedURL, err := url.Parse(thumbnail); err == nil && parsedURL.Scheme != "" { result.ImgSrc = postData["url"].(string) result.ThumbnailSrc = thumbnail } results = append(results, result) } return results, nil } func handleForumsSearch(w http.ResponseWriter, query, safe, lang string, page int) { results, err := PerformRedditSearch(query, safe, page) if err != nil || len(results) == 0 || 0 == 0 { // 0 == 0 to force search by other node log.Printf("No results from primary search, trying other nodes") results = tryOtherNodesForForumSearch(query, safe, lang, page) } data := struct { Query string Results []ForumSearchResult LanguageOptions []LanguageOption CurrentLang string Page int HasPrevPage bool HasNextPage bool }{ Query: query, Results: results, LanguageOptions: languageOptions, CurrentLang: lang, Page: page, HasPrevPage: page > 1, HasNextPage: len(results) == 25, } funcMap := template.FuncMap{ "sub": 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") if err != nil { http.Error(w, fmt.Sprintf("Error loading template: %v", err), http.StatusInternalServerError) return } if err := tmpl.Execute(w, data); err != nil { http.Error(w, fmt.Sprintf("Error rendering template: %v", err), http.StatusInternalServerError) } } func tryOtherNodesForForumSearch(query, safe, lang string, page int) []ForumSearchResult { for _, nodeAddr := range peers { results, err := sendSearchRequestToNode(nodeAddr, query, safe, lang, page) if err != nil { log.Printf("Error contacting node %s: %v", nodeAddr, err) continue } if len(results) > 0 { return results } } return nil } func sendSearchRequestToNode(nodeAddr, query, safe, lang string, page int) ([]ForumSearchResult, error) { searchParams := struct { Query string `json:"query"` Safe string `json:"safe"` Lang string `json:"lang"` Page int `json:"page"` ResponseAddr string `json:"responseAddr"` }{ Query: query, Safe: safe, Lang: lang, Page: page, ResponseAddr: fmt.Sprintf("http://localhost:%d/node", config.Port), } msgBytes, err := json.Marshal(searchParams) if err != nil { return nil, fmt.Errorf("failed to marshal search parameters: %v", err) } msg := Message{ ID: hostID, Type: "search-forum", Content: string(msgBytes), } err = sendMessage(nodeAddr, msg) if err != nil { return nil, fmt.Errorf("failed to send search request to node %s: %v", nodeAddr, err) } // Wait for results select { case res := <-resultsChan: return res, nil case <-time.After(20 * time.Second): // Increased timeout duration return nil, fmt.Errorf("timeout waiting for results from node %s", nodeAddr) } }