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,288 @@
package onlinestream
import (
"context"
"errors"
"fmt"
"seanime/internal/api/anilist"
"seanime/internal/api/metadata"
"seanime/internal/database/db"
"seanime/internal/extension"
"seanime/internal/library/anime"
"seanime/internal/platforms/platform"
"seanime/internal/util/filecache"
"strconv"
"strings"
"time"
"github.com/rs/zerolog"
"github.com/samber/lo"
)
type (
Repository struct {
logger *zerolog.Logger
providerExtensionBank *extension.UnifiedBank
fileCacher *filecache.Cacher
metadataProvider metadata.Provider
platform platform.Platform
anilistBaseAnimeCache *anilist.BaseAnimeCache
db *db.Database
}
)
var (
ErrNoVideoSourceFound = errors.New("no video source found")
)
type (
Episode struct {
Number int `json:"number"`
Title string `json:"title,omitempty"`
Image string `json:"image,omitempty"`
Description string `json:"description,omitempty"`
IsFiller bool `json:"isFiller,omitempty"`
}
EpisodeSource struct {
Number int `json:"number"`
VideoSources []*VideoSource `json:"videoSources"`
Subtitles []*Subtitle `json:"subtitles,omitempty"`
}
VideoSource struct {
Server string `json:"server"`
Headers map[string]string `json:"headers,omitempty"`
URL string `json:"url"`
Quality string `json:"quality"`
}
EpisodeListResponse struct {
Episodes []*Episode `json:"episodes"`
Media *anilist.BaseAnime `json:"media"`
}
Subtitle struct {
URL string `json:"url"`
Language string `json:"language"`
}
)
type (
NewRepositoryOptions struct {
Logger *zerolog.Logger
FileCacher *filecache.Cacher
MetadataProvider metadata.Provider
Platform platform.Platform
Database *db.Database
}
)
func NewRepository(opts *NewRepositoryOptions) *Repository {
return &Repository{
logger: opts.Logger,
metadataProvider: opts.MetadataProvider,
fileCacher: opts.FileCacher,
providerExtensionBank: extension.NewUnifiedBank(),
anilistBaseAnimeCache: anilist.NewBaseAnimeCache(),
platform: opts.Platform,
db: opts.Database,
}
}
func (r *Repository) InitExtensionBank(bank *extension.UnifiedBank) {
r.providerExtensionBank = bank
r.logger.Debug().Msg("onlinestream: Initialized provider extension bank")
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// getFcEpisodeDataBucket returns a episode data bucket for the provider and mediaId.
// "Episode data" refers to the episodeData struct
//
// e.g., onlinestream_zoro_episode-data_123
func (r *Repository) getFcEpisodeDataBucket(provider string, mediaId int) filecache.Bucket {
return filecache.NewBucket("onlinestream_"+provider+"_episode-data_"+strconv.Itoa(mediaId), time.Hour*24*2)
}
// getFcEpisodeListBucket returns a episode data bucket for the provider and mediaId.
// "Episode list" refers to a slice of onlinestream_providers.EpisodeDetails
//
// e.g., onlinestream_zoro_episode-list_123
func (r *Repository) getFcEpisodeListBucket(provider string, mediaId int) filecache.Bucket {
return filecache.NewBucket("onlinestream_"+provider+"_episode-data_"+strconv.Itoa(mediaId), time.Hour*24*1)
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func (r *Repository) getMedia(ctx context.Context, mId int) (*anilist.BaseAnime, error) {
media, err := r.anilistBaseAnimeCache.GetOrSet(mId, func() (*anilist.BaseAnime, error) {
media, err := r.platform.GetAnime(ctx, mId)
if err != nil {
return nil, err
}
return media, nil
})
if err != nil {
return nil, err
}
return media, nil
}
func (r *Repository) GetMedia(ctx context.Context, mId int) (*anilist.BaseAnime, error) {
return r.getMedia(ctx, mId)
}
func (r *Repository) EmptyCache(mediaId int) error {
_ = r.fileCacher.RemoveAllBy(func(filename string) bool {
return strings.HasPrefix(filename, "onlinestream_") && strings.Contains(filename, strconv.Itoa(mediaId))
})
return nil
}
func (r *Repository) GetMediaEpisodes(provider string, media *anilist.BaseAnime, dubbed bool) ([]*Episode, error) {
episodes := make([]*Episode, 0)
if provider == "" {
return episodes, nil
}
// +---------------------+
// | Animap |
// +---------------------+
//animeMetadata, err := r.metadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, mId)
// //foundAnimeMetadata := err == nil && animeMetadata != nil
//aw := r.metadataProvider.GetAnimeMetadataWrapper(media, animeMetadata)
episodeCollection, err := anime.NewEpisodeCollection(anime.NewEpisodeCollectionOptions{
AnimeMetadata: nil,
Media: media,
MetadataProvider: r.metadataProvider,
Logger: r.logger,
})
foundEpisodeCollection := err == nil && episodeCollection != nil
// +---------------------+
// | Episode list |
// +---------------------+
// Fetch the episode list from the provider
// "from" and "to" are set to 0 in order not to fetch episode servers
ec, err := r.getEpisodeContainer(provider, media, 0, 0, dubbed, media.GetStartYearSafe())
if err != nil {
return nil, err
}
for _, episodeDetails := range ec.ProviderEpisodeList {
// If the title contains "[{", it means it's an episode part (e.g. "Episode 6 [{6.5}]", the episode number should be 6)
if strings.Contains(episodeDetails.Title, "[{") {
ep := strings.Split(episodeDetails.Title, "[{")[1]
ep = strings.Split(ep, "}]")[0]
episodes = append(episodes, &Episode{
Number: episodeDetails.Number,
Title: fmt.Sprintf("Episode %s", ep),
Image: media.GetBannerImageSafe(),
Description: "",
IsFiller: false,
})
} else {
if foundEpisodeCollection {
episode, found := episodeCollection.FindEpisodeByNumber(episodeDetails.Number)
if found {
episodes = append(episodes, &Episode{
Number: episodeDetails.Number,
Title: episode.EpisodeTitle,
Image: episode.EpisodeMetadata.Image,
Description: episode.EpisodeMetadata.Summary,
IsFiller: episode.EpisodeMetadata.IsFiller,
})
} else {
episodes = append(episodes, &Episode{
Number: episodeDetails.Number,
Title: episodeDetails.Title,
Image: media.GetCoverImageSafe(),
})
}
} else {
episodes = append(episodes, &Episode{
Number: episodeDetails.Number,
Title: episodeDetails.Title,
Image: media.GetCoverImageSafe(),
})
}
}
}
episodes = lo.Filter(episodes, func(item *Episode, index int) bool {
return item != nil
})
return episodes, nil
}
func (r *Repository) GetEpisodeSources(ctx context.Context, provider string, mId int, number int, dubbed bool, year int) (*EpisodeSource, error) {
// +---------------------+
// | Media |
// +---------------------+
media, err := r.getMedia(ctx, mId)
if err != nil {
return nil, err
}
// +---------------------+
// | Episode servers |
// +---------------------+
ec, err := r.getEpisodeContainer(provider, media, number, number, dubbed, year)
if err != nil {
return nil, err
}
var sources *EpisodeSource
for _, ep := range ec.Episodes {
if ep.Number == number {
s := &EpisodeSource{
Number: ep.Number,
VideoSources: make([]*VideoSource, 0),
}
for _, es := range ep.Servers {
for _, vs := range es.VideoSources {
s.VideoSources = append(s.VideoSources, &VideoSource{
Server: es.Server,
Headers: es.Headers,
URL: vs.URL,
Quality: vs.Quality,
})
// Add subtitles if available
// Subtitles are stored in each video source, but they are the same, so only add them once.
if len(vs.Subtitles) > 0 && s.Subtitles == nil {
s.Subtitles = make([]*Subtitle, 0, len(vs.Subtitles))
for _, sub := range vs.Subtitles {
s.Subtitles = append(s.Subtitles, &Subtitle{
URL: sub.URL,
Language: sub.Language,
})
}
}
}
}
sources = s
break
}
}
if sources == nil {
return nil, ErrNoVideoSourceFound
}
return sources, nil
}