213 lines
5.6 KiB
Go
213 lines
5.6 KiB
Go
package metadata
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"seanime/internal/api/anilist"
|
|
"seanime/internal/api/animap"
|
|
"seanime/internal/hook"
|
|
"seanime/internal/util/filecache"
|
|
"seanime/internal/util/result"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog"
|
|
"github.com/samber/mo"
|
|
"golang.org/x/sync/singleflight"
|
|
)
|
|
|
|
type (
|
|
ProviderImpl struct {
|
|
logger *zerolog.Logger
|
|
fileCacher *filecache.Cacher
|
|
animeMetadataCache *result.BoundedCache[string, *AnimeMetadata]
|
|
singleflight *singleflight.Group
|
|
}
|
|
|
|
NewProviderImplOptions struct {
|
|
Logger *zerolog.Logger
|
|
FileCacher *filecache.Cacher
|
|
}
|
|
)
|
|
|
|
func GetAnimeMetadataCacheKey(platform Platform, mId int) string {
|
|
return fmt.Sprintf("%s$%d", platform, mId)
|
|
}
|
|
|
|
// NewProvider creates a new metadata provider.
|
|
func NewProvider(options *NewProviderImplOptions) Provider {
|
|
return &ProviderImpl{
|
|
logger: options.Logger,
|
|
fileCacher: options.FileCacher,
|
|
animeMetadataCache: result.NewBoundedCache[string, *AnimeMetadata](100),
|
|
singleflight: &singleflight.Group{},
|
|
}
|
|
}
|
|
|
|
// GetCache returns the anime metadata cache.
|
|
func (p *ProviderImpl) GetCache() *result.BoundedCache[string, *AnimeMetadata] {
|
|
return p.animeMetadataCache
|
|
}
|
|
|
|
// GetAnimeMetadata fetches anime metadata from api.ani.zip.
|
|
func (p *ProviderImpl) GetAnimeMetadata(platform Platform, mId int) (ret *AnimeMetadata, err error) {
|
|
cacheKey := GetAnimeMetadataCacheKey(platform, mId)
|
|
if cached, ok := p.animeMetadataCache.Get(cacheKey); ok {
|
|
return cached, nil
|
|
}
|
|
|
|
res, err, _ := p.singleflight.Do(cacheKey, func() (interface{}, error) {
|
|
return p.fetchAnimeMetadata(platform, mId)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return res.(*AnimeMetadata), nil
|
|
}
|
|
|
|
func (p *ProviderImpl) fetchAnimeMetadata(platform Platform, mId int) (*AnimeMetadata, error) {
|
|
ret := &AnimeMetadata{
|
|
Titles: make(map[string]string),
|
|
Episodes: make(map[string]*EpisodeMetadata),
|
|
EpisodeCount: 0,
|
|
SpecialCount: 0,
|
|
Mappings: &AnimeMappings{},
|
|
}
|
|
|
|
// Invoke AnimeMetadataRequested hook
|
|
reqEvent := &AnimeMetadataRequestedEvent{
|
|
MediaId: mId,
|
|
AnimeMetadata: ret,
|
|
}
|
|
err := hook.GlobalHookManager.OnAnimeMetadataRequested().Trigger(reqEvent)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mId = reqEvent.MediaId
|
|
|
|
// Default prevented by hook, return the metadata
|
|
if reqEvent.DefaultPrevented {
|
|
// Override the metadata
|
|
ret = reqEvent.AnimeMetadata
|
|
|
|
// Trigger the event
|
|
event := &AnimeMetadataEvent{
|
|
MediaId: mId,
|
|
AnimeMetadata: ret,
|
|
}
|
|
err = hook.GlobalHookManager.OnAnimeMetadata().Trigger(event)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret = event.AnimeMetadata
|
|
mId = event.MediaId
|
|
|
|
if ret == nil {
|
|
return nil, errors.New("no metadata was returned")
|
|
}
|
|
p.animeMetadataCache.SetT(GetAnimeMetadataCacheKey(platform, mId), ret, 1*time.Hour)
|
|
return ret, nil
|
|
}
|
|
|
|
m, err := animap.FetchAnimapMedia(string(platform), mId)
|
|
if err != nil || m == nil {
|
|
//return p.AnizipFallback(platform, mId)
|
|
return nil, err
|
|
}
|
|
|
|
ret.Titles = m.Titles
|
|
ret.EpisodeCount = 0
|
|
ret.SpecialCount = 0
|
|
ret.Mappings.AnimeplanetId = m.Mappings.AnimePlanetID
|
|
ret.Mappings.KitsuId = m.Mappings.KitsuID
|
|
ret.Mappings.MalId = m.Mappings.MalID
|
|
ret.Mappings.Type = m.Mappings.Type
|
|
ret.Mappings.AnilistId = m.Mappings.AnilistID
|
|
ret.Mappings.AnisearchId = m.Mappings.AnisearchID
|
|
ret.Mappings.AnidbId = m.Mappings.AnidbID
|
|
ret.Mappings.NotifymoeId = m.Mappings.NotifyMoeID
|
|
ret.Mappings.LivechartId = m.Mappings.LivechartID
|
|
ret.Mappings.ThetvdbId = m.Mappings.TheTvdbID
|
|
ret.Mappings.ImdbId = ""
|
|
ret.Mappings.ThemoviedbId = m.Mappings.TheMovieDbID
|
|
|
|
for key, ep := range m.Episodes {
|
|
firstChar := key[0]
|
|
if firstChar == 'S' {
|
|
ret.SpecialCount++
|
|
} else {
|
|
if firstChar >= '0' && firstChar <= '9' {
|
|
ret.EpisodeCount++
|
|
}
|
|
}
|
|
em := &EpisodeMetadata{
|
|
AnidbId: ep.AnidbId,
|
|
TvdbId: ep.TvdbId,
|
|
Title: ep.AnidbTitle,
|
|
Image: ep.Image,
|
|
AirDate: ep.AirDate,
|
|
Length: ep.Runtime,
|
|
Summary: strings.ReplaceAll(ep.Overview, "`", "'"),
|
|
Overview: strings.ReplaceAll(ep.Overview, "`", "'"),
|
|
EpisodeNumber: ep.Number,
|
|
Episode: key,
|
|
SeasonNumber: ep.SeasonNumber,
|
|
AbsoluteEpisodeNumber: ep.AbsoluteNumber,
|
|
AnidbEid: ep.AnidbId,
|
|
HasImage: ep.Image != "",
|
|
}
|
|
if em.Length == 0 && ep.Runtime > 0 {
|
|
em.Length = ep.Runtime
|
|
}
|
|
if em.Summary == "" && ep.Overview != "" {
|
|
em.Summary = ep.Overview
|
|
}
|
|
if em.Overview == "" && ep.Overview != "" {
|
|
em.Overview = ep.Overview
|
|
}
|
|
if ep.TvdbTitle != "" && ep.AnidbTitle == "Episode "+ep.AnidbEpisode {
|
|
em.Title = ep.TvdbTitle
|
|
|
|
}
|
|
ret.Episodes[key] = em
|
|
}
|
|
|
|
// Event
|
|
event := &AnimeMetadataEvent{
|
|
MediaId: mId,
|
|
AnimeMetadata: ret,
|
|
}
|
|
err = hook.GlobalHookManager.OnAnimeMetadata().Trigger(event)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret = event.AnimeMetadata
|
|
mId = event.MediaId
|
|
|
|
p.animeMetadataCache.SetT(GetAnimeMetadataCacheKey(platform, mId), ret, 1*time.Hour)
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// GetAnimeMetadataWrapper creates a new anime wrapper.
|
|
//
|
|
// Example:
|
|
//
|
|
// metadataProvider.GetAnimeMetadataWrapper(media, metadata)
|
|
// metadataProvider.GetAnimeMetadataWrapper(media, nil)
|
|
func (p *ProviderImpl) GetAnimeMetadataWrapper(media *anilist.BaseAnime, metadata *AnimeMetadata) AnimeMetadataWrapper {
|
|
aw := &AnimeWrapperImpl{
|
|
metadata: mo.None[*AnimeMetadata](),
|
|
baseAnime: media,
|
|
fileCacher: p.fileCacher,
|
|
logger: p.logger,
|
|
}
|
|
|
|
if metadata != nil {
|
|
aw.metadata = mo.Some(metadata)
|
|
}
|
|
|
|
return aw
|
|
}
|