Files
MeBox/internal/service/subscription_site_search_helpers.go
T

198 lines
6.0 KiB
Go

package service
import (
"context"
"net/url"
"strings"
"go.uber.org/zap"
"github.com/ShukeBta/MediaStationGo/internal/model"
)
func subscriptionSiteSearchLogFields(sub *model.Subscription, keyword string) []zap.Field {
fields := []zap.Field{zap.String("keyword", keyword), zap.Strings("search_keywords", siteSearchKeywords(sub))}
if sub == nil {
return fields
}
fields = append(fields,
zap.String("subscription_id", sub.ID),
zap.String("subscription", sub.Name),
zap.String("filter", sub.Filter),
zap.String("media_type", sub.MediaType),
zap.String("media_category", sub.MediaCategory),
zap.String("search_mode", sub.SearchMode),
zap.String("imdb_id", sub.IMDBID),
zap.Bool("wash_enabled", sub.WashEnabled),
zap.String("wash_priority", sub.WashPriority),
zap.Int("total_episodes", sub.TotalEpisodes),
)
return fields
}
func appendSiteSearchSelectionLogFields(fields []zap.Field, stats siteSearchSelectionStats) []zap.Field {
return append(fields,
zap.Int("results_count", stats.Total),
zap.Int("query_mismatch_count", stats.QueryMismatch),
zap.Strings("query_mismatch_examples", stats.QueryMismatchExamples),
zap.Int("relaxed_query_match_count", stats.RelaxedQueryMatch),
zap.Int("rule_mismatch_count", stats.RuleMismatch),
zap.Int("missing_download_count", stats.MissingDownload),
zap.Int("seen_count", stats.Seen),
zap.Int("prepared_count", stats.Prepared),
zap.Int("selected_count", stats.Selected),
zap.Bool("local_already_satisfied", stats.LocalAlreadySatisfied),
zap.Bool("local_series_pack_present", stats.LocalSeriesPackPresent),
zap.Bool("series_complete", stats.SeriesComplete),
zap.Int("existing_episode_skipped_count", stats.ExistingEpisodeSkipped),
zap.Int("not_missing_episode_skipped_count", stats.NotMissingEpisodeSkipped),
zap.Int("no_episode_skipped_count", stats.NoEpisodeSkipped),
zap.Bool("pack_fallback_available", stats.PackFallbackAvailable),
zap.Bool("pack_fallback_used", stats.PackFallbackUsed),
)
}
func appendAvailabilityLogFields(fields []zap.Field, availability LocalAvailability) []zap.Field {
missingSample, missingMore := limitedEpisodeSample(availability.MissingEpisodes, 20)
return append(fields,
zap.Int("local_media_count", availability.LocalMediaCount),
zap.Bool("in_library", availability.InLibrary),
zap.Bool("has_series_pack", availability.HasSeriesPack),
zap.Int("downloaded_episodes", availability.DownloadedEpisodes),
zap.Int("availability_total_episodes", availability.TotalEpisodes),
zap.Int("missing_episode_count", len(availability.MissingEpisodes)),
zap.Ints("missing_episodes", missingSample),
zap.Int("missing_episodes_more", missingMore),
)
}
func limitedEpisodeSample(values []int, limit int) ([]int, int) {
if limit <= 0 || len(values) == 0 {
return nil, len(values)
}
if len(values) <= limit {
out := append([]int(nil), values...)
return out, 0
}
out := append([]int(nil), values[:limit]...)
return out, len(values) - limit
}
func (s *SubscriptionService) shouldSkipExistingTorrent(ctx context.Context, mediaType string, candidate siteSearchCandidate) bool {
if s == nil || s.downloads == nil {
return false
}
if isSubscriptionSeriesType(mediaType) && !candidate.Pack && candidate.Episode > 0 {
return false
}
return s.downloads.TorrentExistsByName(ctx, candidate.Item.Title)
}
func siteSearchKeywords(sub *model.Subscription) []string {
if sub == nil {
return nil
}
values := make([]string, 0, 8)
if strings.EqualFold(strings.TrimSpace(sub.SearchMode), "imdb") && strings.TrimSpace(sub.IMDBID) != "" {
values = append(values, strings.TrimSpace(sub.IMDBID))
}
if u, err := url.Parse(sub.FeedURL); err == nil {
if keyword := strings.TrimSpace(u.Query().Get("keyword")); keyword != "" {
values = append(values, keyword)
}
}
if strings.TrimSpace(sub.Filter) != "" {
values = append(values, sub.Filter)
}
if len(values) == 0 && strings.TrimSpace(sub.Name) != "" {
values = append(values, sub.Name)
}
values = append(values, subscriptionFeedAliases(sub)...)
values = append(values, subscriptionMetadataAliases(sub)...)
for _, value := range append([]string(nil), values...) {
if cleaned := cleanAvailabilityTitle(value); cleaned != "" {
values = append(values, cleaned)
}
}
return compactUniqueStrings(values...)
}
func siteSearchKeyword(sub *model.Subscription) string {
keywords := siteSearchKeywords(sub)
if len(keywords) == 0 {
return ""
}
return keywords[0]
}
func subscriptionFeedAliases(sub *model.Subscription) []string {
if sub == nil {
return nil
}
u, err := url.Parse(sub.FeedURL)
if err != nil {
return nil
}
q := u.Query()
values := make([]string, 0, len(q["alias"])+2)
values = append(values, q["alias"]...)
for _, raw := range q["aliases"] {
for _, part := range strings.FieldsFunc(raw, func(r rune) bool {
return r == '|' || r == '\n' || r == '\r' || r == '\t'
}) {
values = append(values, part)
}
}
return compactUniqueStrings(values...)
}
func subscriptionMetadataAliases(sub *model.Subscription) []string {
if sub == nil {
return nil
}
title := cleanAvailabilityTitle(firstNonEmpty(sub.Filter, sub.Name))
return buildSubscribeAliases(title, sub.OriginalName, sub.Year)
}
func compactUniqueStrings(values ...string) []string {
seen := map[string]struct{}{}
out := make([]string, 0, len(values))
for _, value := range values {
value = strings.TrimSpace(value)
if value == "" {
continue
}
key := normalizeAvailabilityComparable(value)
if key == "" {
continue
}
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
out = append(out, value)
}
return out
}
func dedupeSiteSearchResults(results []SearchResult) []SearchResult {
if len(results) < 2 {
return results
}
seen := make(map[string]struct{}, len(results))
out := make([]SearchResult, 0, len(results))
for _, item := range results {
download := strings.TrimSpace(item.DownloadURL)
if download == "" {
download = strings.TrimSpace(item.TorrentURL)
}
key := stableSiteSearchGUID(item, download)
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
out = append(out, item)
}
return out
}