155 lines
4.5 KiB
Go
155 lines
4.5 KiB
Go
package anime
|
|
|
|
import (
|
|
"fmt"
|
|
"seanime/internal/api/anilist"
|
|
"seanime/internal/api/metadata"
|
|
"seanime/internal/hook"
|
|
"seanime/internal/util/limiter"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/samber/lo"
|
|
lop "github.com/samber/lo/parallel"
|
|
"github.com/sourcegraph/conc/pool"
|
|
)
|
|
|
|
type (
|
|
MissingEpisodes struct {
|
|
Episodes []*Episode `json:"episodes"`
|
|
SilencedEpisodes []*Episode `json:"silencedEpisodes"`
|
|
}
|
|
|
|
NewMissingEpisodesOptions struct {
|
|
AnimeCollection *anilist.AnimeCollection
|
|
LocalFiles []*LocalFile
|
|
SilencedMediaIds []int
|
|
MetadataProvider metadata.Provider
|
|
}
|
|
)
|
|
|
|
func NewMissingEpisodes(opts *NewMissingEpisodesOptions) *MissingEpisodes {
|
|
missing := new(MissingEpisodes)
|
|
|
|
reqEvent := new(MissingEpisodesRequestedEvent)
|
|
reqEvent.AnimeCollection = opts.AnimeCollection
|
|
reqEvent.LocalFiles = opts.LocalFiles
|
|
reqEvent.SilencedMediaIds = opts.SilencedMediaIds
|
|
reqEvent.MissingEpisodes = missing
|
|
err := hook.GlobalHookManager.OnMissingEpisodesRequested().Trigger(reqEvent)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
opts.AnimeCollection = reqEvent.AnimeCollection // Override the anime collection
|
|
opts.LocalFiles = reqEvent.LocalFiles // Override the local files
|
|
opts.SilencedMediaIds = reqEvent.SilencedMediaIds // Override the silenced media IDs
|
|
missing = reqEvent.MissingEpisodes
|
|
|
|
// Default prevented by hook, return the missing episodes
|
|
if reqEvent.DefaultPrevented {
|
|
event := new(MissingEpisodesEvent)
|
|
event.MissingEpisodes = missing
|
|
err = hook.GlobalHookManager.OnMissingEpisodes().Trigger(event)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return event.MissingEpisodes
|
|
}
|
|
|
|
groupedLfs := GroupLocalFilesByMediaID(opts.LocalFiles)
|
|
|
|
rateLimiter := limiter.NewLimiter(time.Second, 20)
|
|
p := pool.NewWithResults[[]*EntryDownloadEpisode]()
|
|
for mId, lfs := range groupedLfs {
|
|
p.Go(func() []*EntryDownloadEpisode {
|
|
entry, found := opts.AnimeCollection.GetListEntryFromAnimeId(mId)
|
|
if !found {
|
|
return nil
|
|
}
|
|
|
|
// Skip if the status is nil, dropped or completed
|
|
if entry.Status == nil || *entry.Status == anilist.MediaListStatusDropped || *entry.Status == anilist.MediaListStatusCompleted {
|
|
return nil
|
|
}
|
|
|
|
latestLf, found := FindLatestLocalFileFromGroup(lfs)
|
|
if !found {
|
|
return nil
|
|
}
|
|
//If the latest local file is the same or higher than the current episode count, skip
|
|
if entry.Media.GetCurrentEpisodeCount() == -1 || entry.Media.GetCurrentEpisodeCount() <= latestLf.GetEpisodeNumber() {
|
|
return nil
|
|
}
|
|
rateLimiter.Wait()
|
|
// Fetch anime metadata
|
|
animeMetadata, err := opts.MetadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, entry.Media.ID)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// Get download info
|
|
downloadInfo, err := NewEntryDownloadInfo(&NewEntryDownloadInfoOptions{
|
|
LocalFiles: lfs,
|
|
AnimeMetadata: animeMetadata,
|
|
Media: entry.Media,
|
|
Progress: entry.Progress,
|
|
Status: entry.Status,
|
|
MetadataProvider: opts.MetadataProvider,
|
|
})
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
episodes := downloadInfo.EpisodesToDownload
|
|
|
|
sort.Slice(episodes, func(i, j int) bool {
|
|
return episodes[i].Episode.GetEpisodeNumber() < episodes[j].Episode.GetEpisodeNumber()
|
|
})
|
|
|
|
// If there are more than 1 episode to download, modify the name of the first episode
|
|
if len(episodes) > 1 {
|
|
episodes = episodes[:1] // keep the first episode
|
|
if episodes[0].Episode != nil {
|
|
episodes[0].Episode.DisplayTitle = episodes[0].Episode.DisplayTitle + fmt.Sprintf(" & %d more", len(downloadInfo.EpisodesToDownload)-1)
|
|
}
|
|
}
|
|
return episodes
|
|
})
|
|
}
|
|
epsToDownload := p.Wait()
|
|
epsToDownload = lo.Filter(epsToDownload, func(item []*EntryDownloadEpisode, _ int) bool {
|
|
return item != nil
|
|
})
|
|
|
|
// Flatten
|
|
flattenedEpsToDownload := lo.Flatten(epsToDownload)
|
|
eps := lop.Map(flattenedEpsToDownload, func(item *EntryDownloadEpisode, _ int) *Episode {
|
|
return item.Episode
|
|
})
|
|
// Sort
|
|
sort.Slice(eps, func(i, j int) bool {
|
|
return eps[i].GetEpisodeNumber() < eps[j].GetEpisodeNumber()
|
|
})
|
|
sort.Slice(eps, func(i, j int) bool {
|
|
return eps[i].BaseAnime.ID < eps[j].BaseAnime.ID
|
|
})
|
|
|
|
missing.Episodes = lo.Filter(eps, func(item *Episode, _ int) bool {
|
|
return !lo.Contains(opts.SilencedMediaIds, item.BaseAnime.ID)
|
|
})
|
|
|
|
missing.SilencedEpisodes = lo.Filter(eps, func(item *Episode, _ int) bool {
|
|
return lo.Contains(opts.SilencedMediaIds, item.BaseAnime.ID)
|
|
})
|
|
|
|
// Event
|
|
event := new(MissingEpisodesEvent)
|
|
event.MissingEpisodes = missing
|
|
err = hook.GlobalHookManager.OnMissingEpisodes().Trigger(event)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
return event.MissingEpisodes
|
|
}
|