package search import ( "net/url" "strings" ) func resultHost(raw string) string { u, err := url.Parse(raw) if err != nil { return "" } h := strings.ToLower(u.Hostname()) return strings.TrimSuffix(h, ".") } func hostMatch(host, d string) bool { return host == d || strings.HasSuffix(host, "."+d) } func filterDomains(hits []Hit, f Filter) []Hit { if len(f.IncludeDomains) == 0 && len(f.ExcludeDomains) == 0 { return hits } mode := f.IncludeDomainsMode if mode == "" { mode = "restrict" } restrict := mode != "prefer" && len(f.IncludeDomains) > 0 out := make([]Hit, 0, len(hits)) for _, h := range hits { host := resultHost(h.URL) if host == "" { if restrict { continue } } else { drop := false for _, d := range f.ExcludeDomains { if hostMatch(host, d) { drop = true break } } if drop { continue } if restrict { ok := false for _, d := range f.IncludeDomains { if hostMatch(host, d) { ok = true break } } if !ok { continue } } } out = append(out, h) } return out } func filterMinScore(hits []Hit, min float64) []Hit { if min <= 0 { return hits } out := make([]Hit, 0, len(hits)) for _, h := range hits { if h.Score >= min { out = append(out, h) } } return out } func dedupeStrings(in []string) []string { seen := map[string]bool{} out := make([]string, 0, len(in)) for _, s := range in { if s == "" || seen[s] { continue } seen[s] = true out = append(out, s) } return out } func searxQueryAndNotes(q string, f Filter) (string, []string) { var notes []string if f.IncludeDomainsMode == "prefer" && len(f.IncludeDomains) > 0 { notes = append(notes, "searxng: include_domains_mode=prefer not applied") } else if n := len(f.IncludeDomains); n == 1 { q = "site:" + f.IncludeDomains[0] + " " + q } else if n > 1 { max := n if max > searxSiteCap { max = searxSiteCap notes = append(notes, "searxng: extra include_domains dropped") } var b strings.Builder for i := 0; i < max; i++ { if i > 0 { b.WriteString(" OR ") } b.WriteString("site:") b.WriteString(f.IncludeDomains[i]) } b.WriteByte(' ') b.WriteString(q) q = b.String() } if f.Topic == "finance" { notes = append(notes, "searxng: topic ignored") } if f.StartDate != "" || f.EndDate != "" { notes = append(notes, "searxng: start_date/end_date not applied") } if f.MinScore > 0 { notes = append(notes, "searxng: min_score not applied") } return q, notes } func braveNotes(f Filter) []string { var notes []string if f.TimeRange != "" { notes = append(notes, "brave_llm: time_range not applied") } if f.Topic != "" && f.Topic != "general" { notes = append(notes, "brave_llm: topic not applied") } if f.ExactMatch { notes = append(notes, "brave_llm: exact_match not applied") } if f.IncludeDomainsMode == "prefer" && len(f.IncludeDomains) > 0 { notes = append(notes, "brave_llm: include_domains_mode=prefer not applied") } if f.MinScore > 0 { notes = append(notes, "brave_llm: min_score not applied") } return notes } func FilterKinds(f Filter) string { var k []string if len(f.IncludeDomains) > 0 || len(f.ExcludeDomains) > 0 { k = append(k, "domains") } if f.TimeRange != "" || f.StartDate != "" || f.EndDate != "" { k = append(k, "time") } if f.Topic != "" && f.Topic != "general" { k = append(k, "topic") } if f.ExactMatch { k = append(k, "exact") } if f.MinScore > 0 { k = append(k, "score") } return strings.Join(k, ",") }