package service import ( "regexp" "strings" "github.com/ShukeBta/MediaStationGo/internal/model" ) func collectSiteSearchCandidates(results []SearchResult, sub *model.Subscription, seenSet map[string]struct{}, allowQueryMismatch bool, stats *siteSearchSelectionStats) []siteSearchCandidate { candidates := make([]siteSearchCandidate, 0, len(results)) for _, item := range results { matchText := subscriptionSearchResultText(item) if !subscriptionTitleMatchesQuery(sub, matchText) { if allowQueryMismatch { stats.RelaxedQueryMatch++ } else { stats.QueryMismatch++ stats.QueryMismatchExamples = appendLimitedStrings(stats.QueryMismatchExamples, matchText, 5) continue } } if !matchesSubscriptionRules(sub, matchText) { stats.RuleMismatch++ continue } download := strings.TrimSpace(item.DownloadURL) if download == "" { download = strings.TrimSpace(item.TorrentURL) } if download == "" { stats.MissingDownload++ continue } guid := stableSiteSearchGUID(item, download) if _, ok := seenSet[guid]; ok { stats.Seen++ continue } season, episode := ParseEpisode(matchText) score := subscriptionCandidateScore(sub, item) stats.Prepared++ candidates = append(candidates, siteSearchCandidate{ Item: item, Download: download, GUID: guid, Season: season, Episode: episode, Pack: isSeriesPackTitle(item.Title), Score: score, }) } return candidates } func appendLimitedStrings(values []string, value string, limit int) []string { value = strings.TrimSpace(value) if value == "" || limit <= 0 || len(values) >= limit { return values } return append(values, value) } func shouldRelaxSiteSearchQueryMatch(sub *model.Subscription, local LocalAvailability) bool { if sub == nil { return false } mediaType := normalizeMediaType(sub.MediaType, sub.Name+" "+sub.Filter, "") if !isSubscriptionSeriesType(mediaType) { return false } if local.LocalMediaCount == 0 && len(local.ExistingEpisodeKeys) == 0 { return false } return local.TotalEpisodes > 0 || len(local.MissingEpisodes) > 0 } func subscriptionSearchResultText(item SearchResult) string { return strings.TrimSpace(strings.Join([]string{item.Title, item.Subtitle}, " ")) } func selectRSSSubscriptionCandidates(items []rssItem, sub *model.Subscription, filter *regexp.Regexp, seenSet map[string]struct{}, local LocalAvailability) []siteSearchCandidate { if seenSet == nil { seenSet = map[string]struct{}{} } candidates := make([]siteSearchCandidate, 0, len(items)) for _, item := range items { title := strings.TrimSpace(item.Title) if title == "" { continue } if filter != nil && !filter.MatchString(title) { continue } if !matchesSubscriptionRules(sub, title) { continue } download := strings.TrimSpace(item.Enclosure.URL) if download == "" { download = strings.TrimSpace(item.Link) } if download == "" { continue } guid := stableRSSItemGUID(title, item.GUID, item.Link, item.Enclosure.URL) if _, ok := seenSet[guid]; ok { continue } searchItem := SearchResult{Title: title, DownloadURL: download} season, episode := ParseEpisode(title) candidates = append(candidates, siteSearchCandidate{ Item: searchItem, Download: download, GUID: guid, Season: season, Episode: episode, Pack: isSeriesPackTitle(title), Score: subscriptionCandidateScore(sub, searchItem), }) } return selectPreparedSubscriptionCandidates(candidates, sub, local) }