node build fixed
This commit is contained in:
377
seanime-2.9.10/internal/library/anime/entry.go
Normal file
377
seanime-2.9.10/internal/library/anime/entry.go
Normal file
@@ -0,0 +1,377 @@
|
||||
package anime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"seanime/internal/api/anilist"
|
||||
"seanime/internal/api/metadata"
|
||||
"seanime/internal/hook"
|
||||
"seanime/internal/platforms/anilist_platform"
|
||||
"seanime/internal/platforms/platform"
|
||||
"sort"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/sourcegraph/conc/pool"
|
||||
)
|
||||
|
||||
type (
|
||||
// Entry is a container for all data related to a media.
|
||||
// It is the primary data structure used by the frontend.
|
||||
Entry struct {
|
||||
MediaId int `json:"mediaId"`
|
||||
Media *anilist.BaseAnime `json:"media"`
|
||||
EntryListData *EntryListData `json:"listData"`
|
||||
EntryLibraryData *EntryLibraryData `json:"libraryData"`
|
||||
EntryDownloadInfo *EntryDownloadInfo `json:"downloadInfo,omitempty"`
|
||||
Episodes []*Episode `json:"episodes"`
|
||||
NextEpisode *Episode `json:"nextEpisode"`
|
||||
LocalFiles []*LocalFile `json:"localFiles"`
|
||||
AnidbId int `json:"anidbId"`
|
||||
CurrentEpisodeCount int `json:"currentEpisodeCount"`
|
||||
|
||||
IsNakamaEntry bool `json:"_isNakamaEntry"`
|
||||
NakamaLibraryData *NakamaEntryLibraryData `json:"nakamaLibraryData,omitempty"`
|
||||
}
|
||||
|
||||
// EntryListData holds the details of the AniList entry.
|
||||
EntryListData struct {
|
||||
Progress int `json:"progress,omitempty"`
|
||||
Score float64 `json:"score,omitempty"`
|
||||
Status *anilist.MediaListStatus `json:"status,omitempty"`
|
||||
Repeat int `json:"repeat,omitempty"`
|
||||
StartedAt string `json:"startedAt,omitempty"`
|
||||
CompletedAt string `json:"completedAt,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
// NewEntryOptions is a constructor for Entry.
|
||||
NewEntryOptions struct {
|
||||
MediaId int
|
||||
LocalFiles []*LocalFile // All local files
|
||||
AnimeCollection *anilist.AnimeCollection
|
||||
Platform platform.Platform
|
||||
MetadataProvider metadata.Provider
|
||||
IsSimulated bool // If the account is simulated
|
||||
}
|
||||
)
|
||||
|
||||
// NewEntry creates a new Entry based on the media id and a list of local files.
|
||||
// A Entry is a container for all data related to a media.
|
||||
// It is the primary data structure used by the frontend.
|
||||
//
|
||||
// It has the following properties:
|
||||
// - EntryListData: Details of the AniList entry (if any)
|
||||
// - EntryLibraryData: Details of the local files (if any)
|
||||
// - EntryDownloadInfo: Details of the download status
|
||||
// - Episodes: List of episodes (if any)
|
||||
// - NextEpisode: Next episode to watch (if any)
|
||||
// - LocalFiles: List of local files (if any)
|
||||
// - AnidbId: AniDB id
|
||||
// - CurrentEpisodeCount: Current episode count
|
||||
func NewEntry(ctx context.Context, opts *NewEntryOptions) (*Entry, error) {
|
||||
// Create new Entry
|
||||
entry := new(Entry)
|
||||
entry.MediaId = opts.MediaId
|
||||
|
||||
reqEvent := new(AnimeEntryRequestedEvent)
|
||||
reqEvent.MediaId = opts.MediaId
|
||||
reqEvent.LocalFiles = opts.LocalFiles
|
||||
reqEvent.AnimeCollection = opts.AnimeCollection
|
||||
reqEvent.Entry = entry
|
||||
|
||||
err := hook.GlobalHookManager.OnAnimeEntryRequested().Trigger(reqEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.MediaId = reqEvent.MediaId // Override the media ID
|
||||
opts.LocalFiles = reqEvent.LocalFiles // Override the local files
|
||||
opts.AnimeCollection = reqEvent.AnimeCollection // Override the anime collection
|
||||
entry = reqEvent.Entry // Override the entry
|
||||
|
||||
// Default prevented, return the modified entry
|
||||
if reqEvent.DefaultPrevented {
|
||||
event := new(AnimeEntryEvent)
|
||||
event.Entry = reqEvent.Entry
|
||||
err = hook.GlobalHookManager.OnAnimeEntry().Trigger(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if event.Entry == nil {
|
||||
return nil, errors.New("no entry was returned")
|
||||
}
|
||||
return event.Entry, nil
|
||||
}
|
||||
|
||||
if opts.AnimeCollection == nil ||
|
||||
opts.Platform == nil {
|
||||
return nil, errors.New("missing arguments when creating media entry")
|
||||
}
|
||||
|
||||
// +---------------------+
|
||||
// | AniList entry |
|
||||
// +---------------------+
|
||||
|
||||
// Get the Anilist List entry
|
||||
anilistEntry, found := opts.AnimeCollection.GetListEntryFromAnimeId(opts.MediaId)
|
||||
|
||||
// Set the media
|
||||
// If the Anilist List entry does not exist, fetch the media from AniList
|
||||
if !found {
|
||||
// If the Anilist entry does not exist, instantiate one with zero values
|
||||
anilistEntry = &anilist.AnimeListEntry{}
|
||||
|
||||
// Fetch the media
|
||||
fetchedMedia, err := opts.Platform.GetAnime(ctx, opts.MediaId) // DEVNOTE: Maybe cache it?
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entry.Media = fetchedMedia
|
||||
} else {
|
||||
animeEvent := new(anilist_platform.GetAnimeEvent)
|
||||
animeEvent.Anime = anilistEntry.Media
|
||||
err := hook.GlobalHookManager.OnGetAnime().Trigger(animeEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entry.Media = animeEvent.Anime
|
||||
}
|
||||
|
||||
// If the account is simulated and the media was in the library, we will still fetch
|
||||
// the media from AniList to ensure we have the latest data
|
||||
if opts.IsSimulated && found {
|
||||
// Fetch the media
|
||||
fetchedMedia, err := opts.Platform.GetAnime(ctx, opts.MediaId) // DEVNOTE: Maybe cache it?
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entry.Media = fetchedMedia
|
||||
}
|
||||
|
||||
entry.CurrentEpisodeCount = entry.Media.GetCurrentEpisodeCount()
|
||||
|
||||
// +---------------------+
|
||||
// | Local files |
|
||||
// +---------------------+
|
||||
|
||||
// Get the entry's local files
|
||||
lfs := GetLocalFilesFromMediaId(opts.LocalFiles, opts.MediaId)
|
||||
entry.LocalFiles = lfs // Returns empty slice if no local files are found
|
||||
|
||||
libraryData, _ := NewEntryLibraryData(&NewEntryLibraryDataOptions{
|
||||
EntryLocalFiles: lfs,
|
||||
MediaId: entry.Media.ID,
|
||||
CurrentProgress: anilistEntry.GetProgressSafe(),
|
||||
})
|
||||
entry.EntryLibraryData = libraryData
|
||||
|
||||
// +---------------------+
|
||||
// | Animap |
|
||||
// +---------------------+
|
||||
|
||||
// Fetch AniDB data and cache it for 30 minutes
|
||||
animeMetadata, err := opts.MetadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, opts.MediaId)
|
||||
if err != nil {
|
||||
|
||||
// +---------------- Start
|
||||
// +---------------------+
|
||||
// | Without Animap |
|
||||
// +---------------------+
|
||||
|
||||
// If Animap data is not found, we will still create the Entry without it
|
||||
simpleAnimeEntry, err := NewSimpleEntry(ctx, &NewSimpleAnimeEntryOptions{
|
||||
MediaId: opts.MediaId,
|
||||
LocalFiles: opts.LocalFiles,
|
||||
AnimeCollection: opts.AnimeCollection,
|
||||
Platform: opts.Platform,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
event := &AnimeEntryEvent{
|
||||
Entry: &Entry{
|
||||
MediaId: simpleAnimeEntry.MediaId,
|
||||
Media: simpleAnimeEntry.Media,
|
||||
EntryListData: simpleAnimeEntry.EntryListData,
|
||||
EntryLibraryData: simpleAnimeEntry.EntryLibraryData,
|
||||
EntryDownloadInfo: nil,
|
||||
Episodes: simpleAnimeEntry.Episodes,
|
||||
NextEpisode: simpleAnimeEntry.NextEpisode,
|
||||
LocalFiles: simpleAnimeEntry.LocalFiles,
|
||||
AnidbId: 0,
|
||||
CurrentEpisodeCount: simpleAnimeEntry.CurrentEpisodeCount,
|
||||
},
|
||||
}
|
||||
err = hook.GlobalHookManager.OnAnimeEntry().Trigger(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return event.Entry, nil
|
||||
// +--------------- End
|
||||
|
||||
}
|
||||
|
||||
entry.AnidbId = animeMetadata.GetMappings().AnidbId
|
||||
|
||||
// Instantiate EntryListData
|
||||
// If the media exist in the user's anime list, add the details
|
||||
if found {
|
||||
entry.EntryListData = NewEntryListData(anilistEntry)
|
||||
}
|
||||
|
||||
// +---------------------+
|
||||
// | Episodes |
|
||||
// +---------------------+
|
||||
|
||||
// Create episode entities
|
||||
entry.hydrateEntryEpisodeData(anilistEntry, animeMetadata, opts.MetadataProvider)
|
||||
|
||||
event := &AnimeEntryEvent{
|
||||
Entry: entry,
|
||||
}
|
||||
err = hook.GlobalHookManager.OnAnimeEntry().Trigger(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return event.Entry, nil
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// hydrateEntryEpisodeData
|
||||
// AniZipData, Media and LocalFiles should be defined
|
||||
func (e *Entry) hydrateEntryEpisodeData(
|
||||
anilistEntry *anilist.AnimeListEntry,
|
||||
animeMetadata *metadata.AnimeMetadata,
|
||||
metadataProvider metadata.Provider,
|
||||
) {
|
||||
|
||||
if animeMetadata.Episodes == nil && len(animeMetadata.Episodes) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// +---------------------+
|
||||
// | Discrepancy |
|
||||
// +---------------------+
|
||||
|
||||
// We offset the progress number by 1 if there is a discrepancy
|
||||
progressOffset := 0
|
||||
if FindDiscrepancy(e.Media, animeMetadata) == DiscrepancyAniListCountsEpisodeZero {
|
||||
progressOffset = 1
|
||||
|
||||
_, ok := lo.Find(e.LocalFiles, func(lf *LocalFile) bool {
|
||||
return lf.Metadata.Episode == 0
|
||||
})
|
||||
// Remove the offset if episode 0 is not found
|
||||
if !ok {
|
||||
progressOffset = 0
|
||||
}
|
||||
}
|
||||
|
||||
// +---------------------+
|
||||
// | Episodes |
|
||||
// +---------------------+
|
||||
|
||||
p := pool.NewWithResults[*Episode]()
|
||||
for _, lf := range e.LocalFiles {
|
||||
p.Go(func() *Episode {
|
||||
return NewEpisode(&NewEpisodeOptions{
|
||||
LocalFile: lf,
|
||||
OptionalAniDBEpisode: "",
|
||||
AnimeMetadata: animeMetadata,
|
||||
Media: e.Media,
|
||||
ProgressOffset: progressOffset,
|
||||
IsDownloaded: true,
|
||||
MetadataProvider: metadataProvider,
|
||||
})
|
||||
})
|
||||
}
|
||||
episodes := p.Wait()
|
||||
// Sort by progress number
|
||||
sort.Slice(episodes, func(i, j int) bool {
|
||||
return episodes[i].EpisodeNumber < episodes[j].EpisodeNumber
|
||||
})
|
||||
e.Episodes = episodes
|
||||
|
||||
// +---------------------+
|
||||
// | Download Info |
|
||||
// +---------------------+
|
||||
|
||||
info, err := NewEntryDownloadInfo(&NewEntryDownloadInfoOptions{
|
||||
LocalFiles: e.LocalFiles,
|
||||
AnimeMetadata: animeMetadata,
|
||||
Progress: anilistEntry.Progress,
|
||||
Status: anilistEntry.Status,
|
||||
Media: e.Media,
|
||||
MetadataProvider: metadataProvider,
|
||||
})
|
||||
if err == nil {
|
||||
e.EntryDownloadInfo = info
|
||||
}
|
||||
|
||||
nextEp, found := e.FindNextEpisode()
|
||||
if found {
|
||||
e.NextEpisode = nextEp
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func NewEntryListData(anilistEntry *anilist.AnimeListEntry) *EntryListData {
|
||||
return &EntryListData{
|
||||
Progress: anilistEntry.GetProgressSafe(),
|
||||
Score: anilistEntry.GetScoreSafe(),
|
||||
Status: anilistEntry.Status,
|
||||
Repeat: anilistEntry.GetRepeatSafe(),
|
||||
StartedAt: anilist.FuzzyDateToString(anilistEntry.StartedAt),
|
||||
CompletedAt: anilist.FuzzyDateToString(anilistEntry.CompletedAt),
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
type Discrepancy int
|
||||
|
||||
const (
|
||||
DiscrepancyAniListCountsEpisodeZero Discrepancy = iota
|
||||
DiscrepancyAniListCountsSpecials
|
||||
DiscrepancyAniDBHasMore
|
||||
DiscrepancyNone
|
||||
)
|
||||
|
||||
// FindDiscrepancy returns the discrepancy between the AniList and AniDB episode counts.
|
||||
// It returns DiscrepancyAniListCountsEpisodeZero if AniList most likely has episode 0 as part of the main count.
|
||||
// It returns DiscrepancyAniListCountsSpecials if there is a discrepancy between the AniList and AniDB episode counts and specials are included in the AniList count.
|
||||
// It returns DiscrepancyAniDBHasMore if the AniDB episode count is greater than the AniList episode count.
|
||||
// It returns DiscrepancyNone if there is no discrepancy.
|
||||
func FindDiscrepancy(media *anilist.BaseAnime, animeMetadata *metadata.AnimeMetadata) Discrepancy {
|
||||
if media == nil || animeMetadata == nil || animeMetadata.Episodes == nil {
|
||||
return DiscrepancyNone
|
||||
}
|
||||
|
||||
_, aniDBHasS1 := animeMetadata.Episodes["S1"]
|
||||
_, aniDBHasS2 := animeMetadata.Episodes["S2"]
|
||||
|
||||
difference := media.GetCurrentEpisodeCount() - animeMetadata.GetMainEpisodeCount()
|
||||
|
||||
if difference == 0 {
|
||||
return DiscrepancyNone
|
||||
}
|
||||
|
||||
if difference < 0 {
|
||||
return DiscrepancyAniDBHasMore
|
||||
}
|
||||
|
||||
if difference == 1 && aniDBHasS1 {
|
||||
return DiscrepancyAniListCountsEpisodeZero
|
||||
}
|
||||
|
||||
if difference > 1 && aniDBHasS1 && aniDBHasS2 {
|
||||
return DiscrepancyAniListCountsSpecials
|
||||
}
|
||||
|
||||
return DiscrepancyNone
|
||||
}
|
||||
Reference in New Issue
Block a user