Files
MeBox/internal/service/local_availability.go
T
2026-10-11 23:44:11 +08:00

147 lines
4.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"context"
"fmt"
"regexp"
"strings"
"github.com/truewhile/MeBox/internal/model"
"github.com/truewhile/MeBox/internal/repository"
)
var availabilityNoiseRE = regexp.MustCompile(`(?i)(自动订阅|订阅|全集|合集|complete|batch|s\d{1,2}e\d{1,3}|season\s*\d+|s\d{1,2}|第\s*\d+\s*季|第\s*\d+\s*[集话話期]|\(\d{4}\)|\b\d{4}\b|2160p|1080p|720p|4k|uhd|bluray|blu-ray|web-?dl|hdtv|remux|x26[45]|h\.?26[45]|hevc|avc|hdr10?\+?|dovi|dv|atmos|aac|ddp?5\.1|truehd|flac)`)
type LocalAvailability struct {
DownloadedEpisodes int
TotalEpisodes int
LocalMediaCount int
MissingEpisodes []int
InLibrary bool
HasSeriesPack bool
ExistingEpisodeKeys map[string]struct{}
MissingEpisodeKeys map[string]struct{}
}
func EnrichExternalMediaAvailability(ctx context.Context, repo *repository.Container, items []ExternalMediaResult) {
for i := range items {
availability := LookupLocalAvailability(ctx, repo, items[i].Title, items[i].SubscribeKeyword, items[i].MediaType, items[i].TotalEpisodes)
items[i].DownloadedEpisodes = availability.DownloadedEpisodes
items[i].LocalMediaCount = availability.LocalMediaCount
items[i].MissingEpisodes = availability.MissingEpisodes
items[i].InLibrary = availability.InLibrary
if items[i].TotalEpisodes == 0 {
items[i].TotalEpisodes = availability.TotalEpisodes
}
}
}
func LookupLocalAvailability(ctx context.Context, repo *repository.Container, title, keyword, mediaType string, expectedTotal int) LocalAvailability {
out := LocalAvailability{
TotalEpisodes: expectedTotal,
ExistingEpisodeKeys: map[string]struct{}{},
MissingEpisodeKeys: map[string]struct{}{},
}
if repo == nil || repo.DB == nil {
return out
}
query := availabilityQuery(title, keyword)
if query == "" {
return out
}
like := "%" + query + "%"
// 方言相关的 ILIKE/LIKE:PostgreSQL 下用 LIKE 会因标题大小写与查询不一致而
// 漏判「本地已有该剧」,进而反复提示订阅缺失。
likeOp := repository.LikeOperator(repo.DB)
var rows []model.Media
if err := repo.DB.WithContext(ctx).
Where("title "+likeOp+" ? OR original_name "+likeOp+" ? OR path "+likeOp+" ?", like, like, like).
Order("season_num asc, episode_num asc, episode_fraction asc, created_at desc").
Limit(2000).
Find(&rows).Error; err != nil {
return out
}
out.LocalMediaCount = len(rows)
out.InLibrary = len(rows) > 0
if len(rows) == 0 {
return out
}
seriesLike := isSubscriptionSeriesType(mediaType)
for _, row := range rows {
if row.EpisodeNum <= 0 {
if seriesLike {
out.HasSeriesPack = true
}
continue
}
season := row.SeasonNum
if season <= 0 {
season = 1
}
key := episodeKey(season, row.EpisodeNum)
out.ExistingEpisodeKeys[key] = struct{}{}
}
if seriesLike || len(out.ExistingEpisodeKeys) > 0 {
out.DownloadedEpisodes = len(out.ExistingEpisodeKeys)
out.MissingEpisodes = missingEpisodes(out.ExistingEpisodeKeys, out.TotalEpisodes)
for _, episode := range out.MissingEpisodes {
out.MissingEpisodeKeys[episodeKey(1, episode)] = struct{}{}
}
return out
}
out.DownloadedEpisodes = 1
if out.TotalEpisodes == 0 {
out.TotalEpisodes = 1
}
return out
}
func isSubscriptionSeriesType(mediaType string) bool {
switch normalizeMediaType(mediaType, "", "") {
case "tv", "anime", "variety":
return true
default:
return false
}
}
func missingEpisodes(existing map[string]struct{}, total int) []int {
if total <= 0 {
return nil
}
missing := make([]int, 0)
for episode := 1; episode <= total; episode++ {
if _, ok := existing[episodeKey(1, episode)]; ok {
continue
}
missing = append(missing, episode)
}
return missing
}
func availabilityQuery(title, keyword string) string {
for _, candidate := range []string{keyword, title} {
cleaned := cleanAvailabilityTitle(candidate)
if cleaned != "" {
return cleaned
}
}
return ""
}
func cleanAvailabilityTitle(value string) string {
value = availabilityNoiseRE.ReplaceAllString(value, " ")
value = strings.TrimSpace(strings.Join(strings.Fields(value), " "))
value = strings.TrimSuffix(value, "-")
value = strings.TrimSpace(value)
return value
}
func episodeKey(season, episode int) string {
if season <= 0 {
season = 1
}
return fmt.Sprintf("%02dE%03d", season, episode)
}