diff --git a/internal/service/scraper.go b/internal/service/scraper.go index cdc0162..a13a2da 100644 --- a/internal/service/scraper.go +++ b/internal/service/scraper.go @@ -8,84 +8,10 @@ package service import ( "context" "strings" - "time" "go.uber.org/zap" - "github.com/ShukeBta/MediaStationGo/internal/config" "github.com/ShukeBta/MediaStationGo/internal/model" - "github.com/ShukeBta/MediaStationGo/internal/repository" -) - -// ScraperService coordinates metadata enrichment across providers. -type ScraperService struct { - cfg *config.Config - log *zap.Logger - repo *repository.Container - tmdb *TMDbProvider - bangumi *BangumiProvider - thetvdb *TheTVDBProvider - douban *DoubanProvider - fanart *FanartProvider - adult *AdultProvider - hub *Hub - notify *NotifyChannelService - cache *RuntimeCacheService - images *ImageProxy -} - -// NewScraperService is the constructor. -func NewScraperService( - cfg *config.Config, - log *zap.Logger, - repo *repository.Container, - tmdb *TMDbProvider, - bangumi *BangumiProvider, - thetvdb *TheTVDBProvider, - fanart *FanartProvider, - hub *Hub, - adult ...*AdultProvider, -) *ScraperService { - var adultProvider *AdultProvider - if len(adult) > 0 { - adultProvider = adult[0] - } - return &ScraperService{ - cfg: cfg, log: log, repo: repo, - tmdb: tmdb, bangumi: bangumi, thetvdb: thetvdb, fanart: fanart, adult: adultProvider, hub: hub, - } -} - -func (s *ScraperService) SetDouban(douban *DoubanProvider) { - s.douban = douban -} - -func (s *ScraperService) SetNotifyChannels(notify *NotifyChannelService) { - if s != nil { - s.notify = notify - } -} - -func (s *ScraperService) SetRuntimeCache(cache *RuntimeCacheService) *ScraperService { - if s != nil { - s.cache = cache - } - return s -} - -func (s *ScraperService) SetImageProxy(images *ImageProxy) *ScraperService { - if s != nil { - s.images = images - } - return s -} - -var tmdbDetailsTimeout = 8 * time.Second - -const ( - defaultScrapeDelayMinMS = 250 - defaultScrapeDelayMaxMS = 500 - maxScrapeDelayMS = 5 * 60 * 1000 ) // EnrichOne runs the provider chain for a single media row. @@ -262,54 +188,3 @@ func (s *ScraperService) applyProviderMatchWithOptions(ctx context.Context, m *m }) return nil } - -// AnyEnabled reports whether at least one provider can run. -func (s *ScraperService) AnyEnabled() bool { - if s.tmdb != nil && s.tmdb.Enabled() { - return true - } - if s.bangumi != nil && s.bangumi.Enabled() { - return true - } - if s.thetvdb != nil && s.thetvdb.Enabled() { - return true - } - if s.adult != nil && s.adult.Enabled() { - return true - } - if s.douban != nil && s.douban.Enabled() { - return true - } - return false -} - -// determineMediaType returns "tv" for TV shows and "movie" for movies. -// It uses the library type as the primary signal. -func (s *ScraperService) determineMediaType(lib *model.Library, match *Match) string { - return s.determineMediaTypeForMedia(lib, nil, match) -} - -func (s *ScraperService) determineMediaTypeForMedia(lib *model.Library, media *model.Media, match *Match) string { - if match != nil { - switch normalizeOrganizeMediaType(match.MediaType) { - case "tv", "anime", "variety": - return "tv" - case "movie", "adult": - return "movie" - } - } - if media != nil && mediaIsEpisodic(media, lib) { - return "tv" - } - if lib != nil { - switch lib.Type { - case "tv", "anime", "variety", "show", "shows": - return "tv" - } - } - // Fallback: if Bangumi ID is present, treat as TV/anime - if match != nil && match.BangumiID > 0 { - return "tv" - } - return "movie" -} diff --git a/internal/service/scraper_service.go b/internal/service/scraper_service.go new file mode 100644 index 0000000..d898a87 --- /dev/null +++ b/internal/service/scraper_service.go @@ -0,0 +1,133 @@ +package service + +import ( + "time" + + "go.uber.org/zap" + + "github.com/ShukeBta/MediaStationGo/internal/config" + "github.com/ShukeBta/MediaStationGo/internal/model" + "github.com/ShukeBta/MediaStationGo/internal/repository" +) + +// ScraperService coordinates metadata enrichment across providers. +type ScraperService struct { + cfg *config.Config + log *zap.Logger + repo *repository.Container + tmdb *TMDbProvider + bangumi *BangumiProvider + thetvdb *TheTVDBProvider + douban *DoubanProvider + fanart *FanartProvider + adult *AdultProvider + hub *Hub + notify *NotifyChannelService + cache *RuntimeCacheService + images *ImageProxy +} + +// NewScraperService is the constructor. +func NewScraperService( + cfg *config.Config, + log *zap.Logger, + repo *repository.Container, + tmdb *TMDbProvider, + bangumi *BangumiProvider, + thetvdb *TheTVDBProvider, + fanart *FanartProvider, + hub *Hub, + adult ...*AdultProvider, +) *ScraperService { + var adultProvider *AdultProvider + if len(adult) > 0 { + adultProvider = adult[0] + } + return &ScraperService{ + cfg: cfg, log: log, repo: repo, + tmdb: tmdb, bangumi: bangumi, thetvdb: thetvdb, fanart: fanart, adult: adultProvider, hub: hub, + } +} + +func (s *ScraperService) SetDouban(douban *DoubanProvider) { + s.douban = douban +} + +func (s *ScraperService) SetNotifyChannels(notify *NotifyChannelService) { + if s != nil { + s.notify = notify + } +} + +func (s *ScraperService) SetRuntimeCache(cache *RuntimeCacheService) *ScraperService { + if s != nil { + s.cache = cache + } + return s +} + +func (s *ScraperService) SetImageProxy(images *ImageProxy) *ScraperService { + if s != nil { + s.images = images + } + return s +} + +var tmdbDetailsTimeout = 8 * time.Second + +const ( + defaultScrapeDelayMinMS = 250 + defaultScrapeDelayMaxMS = 500 + maxScrapeDelayMS = 5 * 60 * 1000 +) + +// AnyEnabled reports whether at least one provider can run. +func (s *ScraperService) AnyEnabled() bool { + if s.tmdb != nil && s.tmdb.Enabled() { + return true + } + if s.bangumi != nil && s.bangumi.Enabled() { + return true + } + if s.thetvdb != nil && s.thetvdb.Enabled() { + return true + } + if s.adult != nil && s.adult.Enabled() { + return true + } + if s.douban != nil && s.douban.Enabled() { + return true + } + return false +} + +// determineMediaType returns "tv" for TV shows and "movie" for movies. +// It uses the library type as the primary signal. +func (s *ScraperService) determineMediaType(lib *model.Library, match *Match) string { + return s.determineMediaTypeForMedia(lib, nil, match) +} + +func (s *ScraperService) determineMediaTypeForMedia(lib *model.Library, media *model.Media, match *Match) string { + if match != nil { + switch normalizeOrganizeMediaType(match.MediaType) { + case "tv", "anime", "variety": + return "tv" + case "movie", "adult": + return "movie" + } + } + if media != nil && mediaIsEpisodic(media, lib) { + return "tv" + } + if lib != nil { + switch lib.Type { + case "tv", "anime", "variety", "show", "shows": + return "tv" + } + } + // Fallback: if Bangumi ID is present, treat as TV/anime. + if match != nil && match.BangumiID > 0 { + return "tv" + } + return "movie" +}