Initial commit: DeepRes Go backend — bot + worker + embed service
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SearXNGClient struct {
|
||||
baseURL string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
type SearXNGResult struct {
|
||||
Title string `json:"title"`
|
||||
URL string `json:"url"`
|
||||
Content string `json:"content"`
|
||||
Engine string `json:"engine"`
|
||||
}
|
||||
|
||||
type SearXNGResponse struct {
|
||||
Results []SearXNGResult `json:"results"`
|
||||
}
|
||||
|
||||
func NewSearXNGClient(baseURL string) *SearXNGClient {
|
||||
return &SearXNGClient{
|
||||
baseURL: baseURL,
|
||||
client: &http.Client{Timeout: 15 * time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SearXNGClient) Search(ctx context.Context, query string, limit int) ([]SearXNGResult, error) {
|
||||
u, err := url.Parse(c.baseURL + "/search")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse url: %w", err)
|
||||
}
|
||||
q := u.Query()
|
||||
q.Set("q", query)
|
||||
q.Set("format", "json")
|
||||
q.Set("language", "ru-RU,en-US")
|
||||
q.Set("categories", "general,news")
|
||||
if limit > 0 {
|
||||
q.Set("pageno", "1")
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create request: %w", err)
|
||||
}
|
||||
req.Header.Set("User-Agent", "DeepRes/1.0")
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("search request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("searxng returned %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read response: %w", err)
|
||||
}
|
||||
|
||||
var sr SearXNGResponse
|
||||
if err := json.Unmarshal(body, &sr); err != nil {
|
||||
return nil, fmt.Errorf("parse response: %w", err)
|
||||
}
|
||||
|
||||
if len(sr.Results) > limit {
|
||||
sr.Results = sr.Results[:limit]
|
||||
}
|
||||
return sr.Results, nil
|
||||
}
|
||||
|
||||
// SearchWithVariations запускает несколько поисковых запросов с разными формулировками
|
||||
func (c *SearXNGClient) SearchWithVariations(ctx context.Context, baseQuery string, variations []string, limit int) ([]SearXNGResult, error) {
|
||||
seen := make(map[string]bool)
|
||||
var allResults []SearXNGResult
|
||||
|
||||
queries := append([]string{baseQuery}, variations...)
|
||||
for _, q := range queries {
|
||||
results, err := c.Search(ctx, q, limit)
|
||||
if err != nil {
|
||||
// Логируем но продолжаем — один упавший запрос не ломает весь ресерч
|
||||
continue
|
||||
}
|
||||
for _, r := range results {
|
||||
if !seen[r.URL] {
|
||||
seen[r.URL] = true
|
||||
allResults = append(allResults, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
return allResults, nil
|
||||
}
|
||||
Reference in New Issue
Block a user