node build fixed

This commit is contained in:
ra_ma
2025-09-20 14:08:38 +01:00
parent c6ebbe069d
commit 3d298fa434
1516 changed files with 535727 additions and 2 deletions

View File

@@ -0,0 +1,350 @@
package anime
import (
"errors"
"fmt"
"seanime/internal/api/anilist"
"seanime/internal/api/metadata"
"seanime/internal/hook"
"strconv"
"github.com/samber/lo"
"github.com/sourcegraph/conc/pool"
)
type (
// EntryDownloadInfo is instantiated by the Entry
EntryDownloadInfo struct {
EpisodesToDownload []*EntryDownloadEpisode `json:"episodesToDownload"`
CanBatch bool `json:"canBatch"`
BatchAll bool `json:"batchAll"`
HasInaccurateSchedule bool `json:"hasInaccurateSchedule"`
Rewatch bool `json:"rewatch"`
AbsoluteOffset int `json:"absoluteOffset"`
}
EntryDownloadEpisode struct {
EpisodeNumber int `json:"episodeNumber"`
AniDBEpisode string `json:"aniDBEpisode"`
Episode *Episode `json:"episode"`
}
)
type (
NewEntryDownloadInfoOptions struct {
// Media's local files
LocalFiles []*LocalFile
AnimeMetadata *metadata.AnimeMetadata
Media *anilist.BaseAnime
Progress *int
Status *anilist.MediaListStatus
MetadataProvider metadata.Provider
}
)
// NewEntryDownloadInfo returns a list of episodes to download or episodes for the torrent/debrid streaming views
// based on the options provided.
func NewEntryDownloadInfo(opts *NewEntryDownloadInfoOptions) (*EntryDownloadInfo, error) {
reqEvent := &AnimeEntryDownloadInfoRequestedEvent{
LocalFiles: opts.LocalFiles,
AnimeMetadata: opts.AnimeMetadata,
Media: opts.Media,
Progress: opts.Progress,
Status: opts.Status,
EntryDownloadInfo: &EntryDownloadInfo{},
}
err := hook.GlobalHookManager.OnAnimeEntryDownloadInfoRequested().Trigger(reqEvent)
if err != nil {
return nil, err
}
if reqEvent.DefaultPrevented {
return reqEvent.EntryDownloadInfo, nil
}
opts.LocalFiles = reqEvent.LocalFiles
opts.AnimeMetadata = reqEvent.AnimeMetadata
opts.Media = reqEvent.Media
opts.Progress = reqEvent.Progress
opts.Status = reqEvent.Status
if *opts.Media.Status == anilist.MediaStatusNotYetReleased {
return &EntryDownloadInfo{}, nil
}
if opts.AnimeMetadata == nil {
return nil, errors.New("could not get anime metadata")
}
currentEpisodeCount := opts.Media.GetCurrentEpisodeCount()
if currentEpisodeCount == -1 && opts.AnimeMetadata != nil {
currentEpisodeCount = opts.AnimeMetadata.GetCurrentEpisodeCount()
}
if currentEpisodeCount == -1 {
return nil, errors.New("could not get current media episode count")
}
// +---------------------+
// | Discrepancy |
// +---------------------+
// Whether AniList includes episode 0 as part of main episodes, but AniDB does not, however AniDB has "S1"
discrepancy := FindDiscrepancy(opts.Media, opts.AnimeMetadata)
// AniList is the source of truth for episode numbers
epSlice := newEpisodeSlice(currentEpisodeCount)
// Handle discrepancies
if discrepancy != DiscrepancyNone {
// If AniList includes episode 0 as part of main episodes, but AniDB does not, however AniDB has "S1"
if discrepancy == DiscrepancyAniListCountsEpisodeZero {
// Add "S1" to the beginning of the episode slice
epSlice.trimEnd(1)
epSlice.prepend(0, "S1")
}
// If AniList includes specials, but AniDB does not
if discrepancy == DiscrepancyAniListCountsSpecials {
diff := currentEpisodeCount - opts.AnimeMetadata.GetMainEpisodeCount()
epSlice.trimEnd(diff)
for i := 0; i < diff; i++ {
epSlice.add(currentEpisodeCount-i, "S"+strconv.Itoa(i+1))
}
}
// If AniDB has more episodes than AniList
if discrepancy == DiscrepancyAniDBHasMore {
// Do nothing
}
}
// Filter out episodes not aired
if opts.Media.NextAiringEpisode != nil {
epSlice.filter(func(item *episodeSliceItem, index int) bool {
// e.g. if the next airing episode is 13, then filter out episodes 14 and above
return index+1 < opts.Media.NextAiringEpisode.Episode
})
}
// Get progress, if the media isn't in the user's list, progress is 0
// If the media is completed, set progress is 0
progress := 0
if opts.Progress != nil {
progress = *opts.Progress
}
if opts.Status != nil {
if *opts.Status == anilist.MediaListStatusCompleted {
progress = 0
}
}
hasInaccurateSchedule := false
if opts.Media.NextAiringEpisode == nil && *opts.Media.Status == anilist.MediaStatusReleasing {
hasInaccurateSchedule = true
}
// Filter out episodes already watched (index+1 is the progress number)
toDownloadSlice := epSlice.filterNew(func(item *episodeSliceItem, index int) bool {
return index+1 > progress
})
// This slice contains episode numbers that are not downloaded
// The source of truth is AniDB, but we will handle discrepancies
lfsEpSlice := newEpisodeSlice(0)
if opts.LocalFiles != nil {
// Get all episode numbers of main local files
for _, lf := range opts.LocalFiles {
if lf.Metadata.Type == LocalFileTypeMain {
lfsEpSlice.add(lf.Metadata.Episode, lf.Metadata.AniDBEpisode)
}
}
}
// Filter out downloaded episodes
toDownloadSlice.filter(func(item *episodeSliceItem, index int) bool {
isDownloaded := false
for _, lf := range opts.LocalFiles {
if lf.Metadata.Type != LocalFileTypeMain {
continue
}
// If the file episode number matches that of the episode slice item
if lf.Metadata.Episode == item.episodeNumber {
isDownloaded = true
}
// If the slice episode number is 0 and the file is a main S1
if discrepancy == DiscrepancyAniListCountsEpisodeZero && item.episodeNumber == 0 && lf.Metadata.AniDBEpisode == "S1" {
isDownloaded = true
}
}
return !isDownloaded
})
// +---------------------+
// | EntryEpisode |
// +---------------------+
// Generate `episodesToDownload` based on `toDownloadSlice`
// DEVNOTE: The EntryEpisode generated has inaccurate progress numbers since not local files are passed in
progressOffset := 0
if discrepancy == DiscrepancyAniListCountsEpisodeZero {
progressOffset = 1
}
p := pool.NewWithResults[*EntryDownloadEpisode]()
for _, ep := range toDownloadSlice.getSlice() {
p.Go(func() *EntryDownloadEpisode {
str := new(EntryDownloadEpisode)
str.EpisodeNumber = ep.episodeNumber
str.AniDBEpisode = ep.aniDBEpisode
// Create a new episode with a placeholder local file
// We pass that placeholder local file so that all episodes are hydrated as main episodes for consistency
str.Episode = NewEpisode(&NewEpisodeOptions{
LocalFile: &LocalFile{
ParsedData: &LocalFileParsedData{},
ParsedFolderData: []*LocalFileParsedData{},
Metadata: &LocalFileMetadata{
Episode: ep.episodeNumber,
Type: LocalFileTypeMain,
AniDBEpisode: ep.aniDBEpisode,
},
},
OptionalAniDBEpisode: str.AniDBEpisode,
AnimeMetadata: opts.AnimeMetadata,
Media: opts.Media,
ProgressOffset: progressOffset,
IsDownloaded: false,
MetadataProvider: opts.MetadataProvider,
})
str.Episode.AniDBEpisode = ep.aniDBEpisode
// Reset the local file to nil, since it's a placeholder
str.Episode.LocalFile = nil
return str
})
}
episodesToDownload := p.Wait()
//--------------
canBatch := false
if *opts.Media.GetStatus() == anilist.MediaStatusFinished && opts.Media.GetTotalEpisodeCount() > 0 {
canBatch = true
}
batchAll := false
if canBatch && lfsEpSlice.len() == 0 && progress == 0 {
batchAll = true
}
rewatch := false
if opts.Status != nil && *opts.Status == anilist.MediaListStatusCompleted {
rewatch = true
}
downloadInfo := &EntryDownloadInfo{
EpisodesToDownload: episodesToDownload,
CanBatch: canBatch,
BatchAll: batchAll,
Rewatch: rewatch,
HasInaccurateSchedule: hasInaccurateSchedule,
AbsoluteOffset: opts.AnimeMetadata.GetOffset(),
}
event := &AnimeEntryDownloadInfoEvent{
EntryDownloadInfo: downloadInfo,
}
err = hook.GlobalHookManager.OnAnimeEntryDownloadInfo().Trigger(event)
if err != nil {
return nil, err
}
return event.EntryDownloadInfo, nil
}
type episodeSliceItem struct {
episodeNumber int
aniDBEpisode string
}
type episodeSlice []*episodeSliceItem
func newEpisodeSlice(episodeCount int) *episodeSlice {
s := make([]*episodeSliceItem, 0)
for i := 0; i < episodeCount; i++ {
s = append(s, &episodeSliceItem{episodeNumber: i + 1, aniDBEpisode: strconv.Itoa(i + 1)})
}
ret := &episodeSlice{}
ret.set(s)
return ret
}
func (s *episodeSlice) set(eps []*episodeSliceItem) {
*s = eps
}
func (s *episodeSlice) add(episodeNumber int, aniDBEpisode string) {
*s = append(*s, &episodeSliceItem{episodeNumber: episodeNumber, aniDBEpisode: aniDBEpisode})
}
func (s *episodeSlice) prepend(episodeNumber int, aniDBEpisode string) {
*s = append([]*episodeSliceItem{{episodeNumber: episodeNumber, aniDBEpisode: aniDBEpisode}}, *s...)
}
func (s *episodeSlice) trimEnd(n int) {
*s = (*s)[:len(*s)-n]
}
func (s *episodeSlice) trimStart(n int) {
*s = (*s)[n:]
}
func (s *episodeSlice) len() int {
return len(*s)
}
func (s *episodeSlice) get(index int) *episodeSliceItem {
return (*s)[index]
}
func (s *episodeSlice) getEpisodeNumber(episodeNumber int) *episodeSliceItem {
for _, item := range *s {
if item.episodeNumber == episodeNumber {
return item
}
}
return nil
}
func (s *episodeSlice) filter(filter func(*episodeSliceItem, int) bool) {
*s = lo.Filter(*s, filter)
}
func (s *episodeSlice) filterNew(filter func(*episodeSliceItem, int) bool) *episodeSlice {
s2 := make(episodeSlice, 0)
for i, item := range *s {
if filter(item, i) {
s2 = append(s2, item)
}
}
return &s2
}
func (s *episodeSlice) copy() *episodeSlice {
s2 := make(episodeSlice, len(*s), cap(*s))
for i, item := range *s {
s2[i] = item
}
return &s2
}
func (s *episodeSlice) getSlice() []*episodeSliceItem {
return *s
}
func (s *episodeSlice) print() {
for i, item := range *s {
fmt.Printf("(%d) %d -> %s\n", i, item.episodeNumber, item.aniDBEpisode)
}
}