Files
MeBox/internal/service/subscription_candidate_collect.go
T
2026-06-26 13:50:53 +08:00

156 lines
4.2 KiB
Go

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 !subscriptionSearchResultMatchesQuery(sub, item) {
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)
episodes := episodeNumbersFromRefs(episodeRefsFromTitle(matchText), season)
score := subscriptionCandidateScore(sub, item)
stats.Prepared++
candidates = append(candidates, siteSearchCandidate{
Item: item,
Download: download,
GUID: guid,
Season: season,
Episode: episode,
Episodes: episodes,
Pack: isSeriesPackTitle(item.Title) || len(episodes) > 1,
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)
episodes := episodeNumbersFromRefs(episodeRefsFromTitle(title), season)
candidates = append(candidates, siteSearchCandidate{
Item: searchItem,
Download: download,
GUID: guid,
Season: season,
Episode: episode,
Episodes: episodes,
Pack: isSeriesPackTitle(title) || len(episodes) > 1,
Score: subscriptionCandidateScore(sub, searchItem),
})
}
return selectPreparedSubscriptionCandidates(candidates, sub, local)
}
func episodeNumbersFromRefs(refs []episodeRef, fallbackSeason int) []int {
if len(refs) == 0 {
return nil
}
if fallbackSeason <= 0 {
fallbackSeason = refs[0].Season
}
out := make([]int, 0, len(refs))
seen := map[int]struct{}{}
for _, ref := range refs {
season := ref.Season
if season <= 0 {
season = 1
}
if fallbackSeason > 0 && season != fallbackSeason {
continue
}
if ref.Episode <= 0 {
continue
}
if _, ok := seen[ref.Episode]; ok {
continue
}
seen[ref.Episode] = struct{}{}
out = append(out, ref.Episode)
}
return out
}