diff --git a/internal/controllers/facr_controller.go b/internal/controllers/facr_controller.go index 742ca61..3c0d092 100644 --- a/internal/controllers/facr_controller.go +++ b/internal/controllers/facr_controller.go @@ -224,102 +224,53 @@ func (fc *FACRController) SearchClubs(c *gin.Context) { }) return } + + // Use external FACR API proxy instead of scraping www.fotbal.cz directly vals := neturl.Values{} vals.Set("q", q) - searchURL := "https://www.fotbal.cz/club/hledej?" + vals.Encode() + searchURL := "https://facr.tdvorak.dev/club/search?" + vals.Encode() - req, err := http.NewRequest("GET", searchURL, nil) + httpClient := &http.Client{Timeout: 15 * time.Second} + resp, err := httpClient.Get(searchURL) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Error creating request: %v", err)}) - return - } - req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36") - req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8") - req.Header.Set("Accept-Language", "cs-CZ,cs;q=0.9,en;q=0.8") - req.Header.Set("Referer", "https://www.fotbal.cz/club/hledej") - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Error fetching search page: %v", err)}) + c.JSON(http.StatusBadGateway, gin.H{"error": fmt.Sprintf("Error fetching from FACR API: %v", err)}) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - // Try once more with quoted query if short tokens present - resp.Body.Close() - searchURL2 := searchURL - tokens := strings.Fields(q) - for _, t := range tokens { - if len([]rune(t)) <= 2 { - vals2 := neturl.Values{} - vals2.Set("q", "\""+q+"\"") - searchURL2 = "https://www.fotbal.cz/club/hledej?" + vals2.Encode() - break - } - } - req2, _ := http.NewRequest("GET", searchURL2, nil) - req2.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36") - req2.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") - req2.Header.Set("Accept-Language", "en-US,en;q=0.9") - resp2, err2 := client.Do(req2) - if err2 != nil { - c.JSON(http.StatusBadGateway, gin.H{"error": fmt.Sprintf("Error fetching (retry): %v", err2)}) - return - } - defer resp2.Body.Close() - if resp2.StatusCode != http.StatusOK { - c.JSON(http.StatusOK, gin.H{"query": q, "count": 0, "results": []SearchResult{}}) - return - } - resp = resp2 - } - - doc, err := goquery.NewDocumentFromReader(resp.Body) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Error parsing HTML: %v", err)}) + body, _ := io.ReadAll(io.LimitReader(resp.Body, 2048)) + c.Data(resp.StatusCode, "application/json", body) return } - var results []SearchResult - doc.Find("li.ListItemSplit").Each(func(_ int, li *goquery.Selection) { - a := li.Find("a.Link--inverted").First() - href, _ := a.Attr("href") - if href == "" { - return + + // Read and parse the external API response + body, err := io.ReadAll(resp.Body) + if err != nil { + c.JSON(http.StatusBadGateway, gin.H{"error": fmt.Sprintf("Error reading response: %v", err)}) + return + } + + // Parse the JSON to apply local processing (logo handling, filtering, deduplication) + var apiResp struct { + Query string `json:"query"` + Count int `json:"count"` + Results []SearchResult `json:"results"` + } + if err := json.Unmarshal(body, &apiResp); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Error parsing response: %v", err)}) + return + } + + results := apiResp.Results + + // Process logos through local rembg service + for i := range results { + if logoURL := strings.TrimSpace(results[i].LogoURL); logoURL != "" { + if p, err := services.ProcessFACRLogo(logoURL); err == nil && strings.TrimSpace(p) != "" { + results[i].LogoURL = p + } } - name := strings.TrimSpace(a.Find("span.H7").First().Text()) - if name == "" { - name = strings.TrimSpace(a.Text()) - } - img := a.Find("img").First() - logoURL, _ := img.Attr("src") - // Best-effort: Process FACR logos to transparent PNG. Non-facr URLs are returned unchanged. - if p, err := services.ProcessFACRLogo(logoURL); err == nil && strings.TrimSpace(p) != "" { - logoURL = p - } - category := strings.TrimSpace(li.Find(".ClubCategories .BadgeCategory").First().Text()) - address := strings.TrimSpace(li.Find(".ClubAddress p").First().Text()) - clubType := "football" - if strings.Contains(strings.ToLower(href), "/futsal/") { - clubType = "futsal" - } - parts := strings.Split(strings.TrimRight(href, "/"), "/") - clubID := "" - if len(parts) > 0 { - clubID = parts[len(parts)-1] - } - if !strings.HasPrefix(href, "http://") && !strings.HasPrefix(href, "https://") { - href = "https://www.fotbal.cz" + href - } - results = append(results, SearchResult{ - Name: name, - ClubID: clubID, - ClubType: clubType, - URL: href, - LogoURL: logoURL, - Category: category, - Address: address, - }) - }) + } // If setup is completed and a sport is configured, filter results to that sport only if fc.DB != nil { @@ -338,6 +289,7 @@ func (fc *FACRController) SearchClubs(c *gin.Context) { } } + // Deduplicate results if len(results) > 1 { unique := make([]SearchResult, 0, len(results)) seenByID := map[string]int{} @@ -379,7 +331,6 @@ func (fc *FACRController) SearchClubs(c *gin.Context) { results = unique } - // respond and close the function c.JSON(http.StatusOK, gin.H{ "query": q, "count": len(results),