492 lines
15 KiB
Go
492 lines
15 KiB
Go
package extension_playground
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"runtime"
|
|
"seanime/internal/api/anilist"
|
|
"seanime/internal/api/metadata"
|
|
"seanime/internal/extension"
|
|
hibikemanga "seanime/internal/extension/hibike/manga"
|
|
hibikeonlinestream "seanime/internal/extension/hibike/onlinestream"
|
|
hibiketorrent "seanime/internal/extension/hibike/torrent"
|
|
"seanime/internal/extension_repo"
|
|
goja_runtime "seanime/internal/goja/goja_runtime"
|
|
"seanime/internal/manga"
|
|
"seanime/internal/onlinestream"
|
|
"seanime/internal/platforms/platform"
|
|
"seanime/internal/util"
|
|
"seanime/internal/util/result"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/goccy/go-json"
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
type (
|
|
PlaygroundRepository struct {
|
|
logger *zerolog.Logger
|
|
platform platform.Platform
|
|
baseAnimeCache *result.Cache[int, *anilist.BaseAnime]
|
|
baseMangaCache *result.Cache[int, *anilist.BaseManga]
|
|
metadataProvider metadata.Provider
|
|
gojaRuntimeManager *goja_runtime.Manager
|
|
}
|
|
|
|
RunPlaygroundCodeResponse struct {
|
|
Logs string `json:"logs"`
|
|
Value string `json:"value"`
|
|
}
|
|
|
|
RunPlaygroundCodeParams struct {
|
|
Type extension.Type `json:"type"`
|
|
Language extension.Language `json:"language"`
|
|
Code string `json:"code"`
|
|
Inputs map[string]interface{} `json:"inputs"`
|
|
Function string `json:"function"`
|
|
}
|
|
)
|
|
|
|
func NewPlaygroundRepository(logger *zerolog.Logger, platform platform.Platform, metadataProvider metadata.Provider) *PlaygroundRepository {
|
|
return &PlaygroundRepository{
|
|
logger: logger,
|
|
platform: platform,
|
|
metadataProvider: metadataProvider,
|
|
baseAnimeCache: result.NewCache[int, *anilist.BaseAnime](),
|
|
baseMangaCache: result.NewCache[int, *anilist.BaseManga](),
|
|
gojaRuntimeManager: goja_runtime.NewManager(logger),
|
|
}
|
|
}
|
|
|
|
func (r *PlaygroundRepository) RunPlaygroundCode(params *RunPlaygroundCodeParams) (resp *RunPlaygroundCodeResponse, err error) {
|
|
defer util.HandlePanicInModuleWithError("extension_playground/RunPlaygroundCode", &err)
|
|
|
|
if params == nil {
|
|
return nil, fmt.Errorf("no parameters provided")
|
|
}
|
|
|
|
ext := &extension.Extension{
|
|
ID: "playground-extension",
|
|
Name: "Playground",
|
|
Version: "0.0.0",
|
|
ManifestURI: "",
|
|
Language: params.Language,
|
|
Type: params.Type,
|
|
Description: "",
|
|
Author: "",
|
|
Icon: "",
|
|
Website: "",
|
|
Payload: params.Code,
|
|
}
|
|
|
|
r.logger.Debug().Msgf("playground: Inputs: %s", strings.ReplaceAll(spew.Sprint(params.Inputs), "\n", ""))
|
|
|
|
switch params.Type {
|
|
case extension.TypeMangaProvider:
|
|
return r.runPlaygroundCodeMangaProvider(ext, params)
|
|
case extension.TypeOnlinestreamProvider:
|
|
return r.runPlaygroundCodeOnlinestreamProvider(ext, params)
|
|
case extension.TypeAnimeTorrentProvider:
|
|
return r.runPlaygroundCodeAnimeTorrentProvider(ext, params)
|
|
default:
|
|
}
|
|
|
|
runtime.GC()
|
|
|
|
return nil, fmt.Errorf("invalid extension type")
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type PlaygroundDebugLogger struct {
|
|
logger *zerolog.Logger
|
|
buff *bytes.Buffer
|
|
}
|
|
|
|
func (r *PlaygroundRepository) newPlaygroundDebugLogger() *PlaygroundDebugLogger {
|
|
buff := bytes.NewBuffer(nil)
|
|
consoleWritier := zerolog.ConsoleWriter{
|
|
Out: buff,
|
|
TimeFormat: time.DateTime,
|
|
FormatMessage: util.ZerologFormatMessageSimple,
|
|
FormatLevel: util.ZerologFormatLevelSimple,
|
|
NoColor: true, // Needed to prevent color codes from being written to the file
|
|
}
|
|
|
|
logger := zerolog.New(consoleWritier).With().Timestamp().Logger()
|
|
|
|
return &PlaygroundDebugLogger{
|
|
logger: &logger,
|
|
buff: buff,
|
|
}
|
|
}
|
|
|
|
func newPlaygroundResponse(playgroundLogger *PlaygroundDebugLogger, value interface{}) *RunPlaygroundCodeResponse {
|
|
v := ""
|
|
|
|
switch value.(type) {
|
|
case error:
|
|
v = fmt.Sprintf("ERROR: %+v", value)
|
|
case string:
|
|
v = value.(string)
|
|
default:
|
|
// Pretty print the value to json
|
|
prettyJSON, err := json.MarshalIndent(value, "", " ")
|
|
if err != nil {
|
|
v = fmt.Sprintf("ERROR: Failed to marshal value to JSON: %+v", err)
|
|
} else {
|
|
v = string(prettyJSON)
|
|
}
|
|
}
|
|
|
|
logs := playgroundLogger.buff.String()
|
|
|
|
return &RunPlaygroundCodeResponse{
|
|
Logs: logs,
|
|
Value: v,
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
func (r *PlaygroundRepository) getAnime(mediaId int) (anime *anilist.BaseAnime, am *metadata.AnimeMetadata, err error) {
|
|
var ok bool
|
|
anime, ok = r.baseAnimeCache.Get(mediaId)
|
|
if !ok {
|
|
anime, err = r.platform.GetAnime(context.Background(), mediaId)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
r.baseAnimeCache.SetT(mediaId, anime, 24*time.Hour)
|
|
}
|
|
|
|
am, _ = r.metadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, mediaId)
|
|
return anime, am, nil
|
|
}
|
|
|
|
func (r *PlaygroundRepository) getManga(mediaId int) (manga *anilist.BaseManga, err error) {
|
|
var ok bool
|
|
manga, ok = r.baseMangaCache.Get(mediaId)
|
|
if !ok {
|
|
manga, err = r.platform.GetManga(context.Background(), mediaId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r.baseMangaCache.SetT(mediaId, manga, 24*time.Hour)
|
|
}
|
|
return
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
func (r *PlaygroundRepository) runPlaygroundCodeAnimeTorrentProvider(ext *extension.Extension, params *RunPlaygroundCodeParams) (resp *RunPlaygroundCodeResponse, err error) {
|
|
|
|
playgroundLogger := r.newPlaygroundDebugLogger()
|
|
|
|
// Inputs
|
|
// - mediaId int
|
|
// - options struct
|
|
|
|
mediaId, ok := params.Inputs["mediaId"].(float64)
|
|
if !ok || mediaId <= 0 {
|
|
return nil, fmt.Errorf("invalid mediaId")
|
|
}
|
|
|
|
// Fetch the anime
|
|
anime, animeMetadata, err := r.getAnime(int(mediaId))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
queryMedia := hibiketorrent.Media{
|
|
ID: anime.GetID(),
|
|
IDMal: anime.GetIDMal(),
|
|
Status: string(*anime.GetStatus()),
|
|
Format: string(*anime.GetFormat()),
|
|
EnglishTitle: anime.GetTitle().GetEnglish(),
|
|
RomajiTitle: anime.GetRomajiTitleSafe(),
|
|
EpisodeCount: anime.GetTotalEpisodeCount(),
|
|
AbsoluteSeasonOffset: 0,
|
|
Synonyms: anime.GetSynonymsContainingSeason(),
|
|
IsAdult: *anime.GetIsAdult(),
|
|
StartDate: &hibiketorrent.FuzzyDate{
|
|
Year: *anime.GetStartDate().GetYear(),
|
|
Month: anime.GetStartDate().GetMonth(),
|
|
Day: anime.GetStartDate().GetDay(),
|
|
},
|
|
}
|
|
|
|
switch params.Language {
|
|
case extension.LanguageGo:
|
|
//...
|
|
case extension.LanguageJavascript, extension.LanguageTypescript:
|
|
_, provider, err := extension_repo.NewGojaAnimeTorrentProvider(ext, params.Language, playgroundLogger.logger, r.gojaRuntimeManager)
|
|
if err != nil {
|
|
return newPlaygroundResponse(playgroundLogger, err), nil
|
|
}
|
|
defer r.gojaRuntimeManager.DeletePluginPool(ext.ID)
|
|
|
|
// Run the code
|
|
switch params.Function {
|
|
case "search":
|
|
res, err := provider.Search(hibiketorrent.AnimeSearchOptions{
|
|
Media: queryMedia,
|
|
Query: params.Inputs["query"].(string),
|
|
})
|
|
if err != nil {
|
|
return newPlaygroundResponse(playgroundLogger, err), nil
|
|
}
|
|
return newPlaygroundResponse(playgroundLogger, res), nil
|
|
case "smartSearch":
|
|
type p struct {
|
|
Query string `json:"query"`
|
|
Batch bool `json:"batch"`
|
|
EpisodeNumber int `json:"episodeNumber"`
|
|
Resolution string `json:"resolution"`
|
|
BestReleases bool `json:"bestReleases"`
|
|
}
|
|
m, _ := json.Marshal(params.Inputs["options"])
|
|
var options p
|
|
_ = json.Unmarshal(m, &options)
|
|
|
|
anidbAID := 0
|
|
anidbEID := 0
|
|
|
|
// Get the AniDB Anime ID and Episode ID
|
|
if animeMetadata != nil {
|
|
// Override absolute offset value of queryMedia
|
|
queryMedia.AbsoluteSeasonOffset = animeMetadata.GetOffset()
|
|
|
|
if animeMetadata.GetMappings() != nil {
|
|
|
|
anidbAID = animeMetadata.GetMappings().AnidbId
|
|
// Find Animap Episode based on inputted episode number
|
|
anizipEpisode, found := animeMetadata.FindEpisode(strconv.Itoa(options.EpisodeNumber))
|
|
if found {
|
|
anidbEID = anizipEpisode.AnidbEid
|
|
}
|
|
}
|
|
}
|
|
|
|
res, err := provider.SmartSearch(hibiketorrent.AnimeSmartSearchOptions{
|
|
Media: queryMedia,
|
|
Query: options.Query,
|
|
Batch: options.Batch,
|
|
EpisodeNumber: options.EpisodeNumber,
|
|
Resolution: options.Resolution,
|
|
BestReleases: options.BestReleases,
|
|
AnidbAID: anidbAID,
|
|
AnidbEID: anidbEID,
|
|
})
|
|
if err != nil {
|
|
return newPlaygroundResponse(playgroundLogger, err), nil
|
|
}
|
|
return newPlaygroundResponse(playgroundLogger, res), nil
|
|
case "getTorrentInfoHash":
|
|
var torrent hibiketorrent.AnimeTorrent
|
|
_ = json.Unmarshal([]byte(params.Inputs["torrent"].(string)), &torrent)
|
|
|
|
res, err := provider.GetTorrentInfoHash(&torrent)
|
|
if err != nil {
|
|
return newPlaygroundResponse(playgroundLogger, err), nil
|
|
}
|
|
return newPlaygroundResponse(playgroundLogger, res), nil
|
|
case "getTorrentMagnetLink":
|
|
var torrent hibiketorrent.AnimeTorrent
|
|
_ = json.Unmarshal([]byte(params.Inputs["torrent"].(string)), &torrent)
|
|
|
|
res, err := provider.GetTorrentMagnetLink(&torrent)
|
|
if err != nil {
|
|
return newPlaygroundResponse(playgroundLogger, err), nil
|
|
}
|
|
return newPlaygroundResponse(playgroundLogger, res), nil
|
|
case "getLatest":
|
|
res, err := provider.GetLatest()
|
|
if err != nil {
|
|
return newPlaygroundResponse(playgroundLogger, err), nil
|
|
}
|
|
return newPlaygroundResponse(playgroundLogger, res), nil
|
|
case "getSettings":
|
|
res := provider.GetSettings()
|
|
return newPlaygroundResponse(playgroundLogger, res), nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("unknown call")
|
|
}
|
|
|
|
func (r *PlaygroundRepository) runPlaygroundCodeMangaProvider(ext *extension.Extension, params *RunPlaygroundCodeParams) (resp *RunPlaygroundCodeResponse, err error) {
|
|
|
|
playgroundLogger := r.newPlaygroundDebugLogger()
|
|
|
|
mediaId, ok := params.Inputs["mediaId"].(float64)
|
|
if !ok || mediaId <= 0 {
|
|
return nil, fmt.Errorf("invalid mediaId")
|
|
}
|
|
|
|
media, err := r.getManga(int(mediaId))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
titles := media.GetAllTitles()
|
|
|
|
switch params.Language {
|
|
case extension.LanguageGo:
|
|
//...
|
|
case extension.LanguageJavascript, extension.LanguageTypescript:
|
|
_, provider, err := extension_repo.NewGojaMangaProvider(ext, params.Language, playgroundLogger.logger, r.gojaRuntimeManager)
|
|
if err != nil {
|
|
return newPlaygroundResponse(playgroundLogger, err), nil
|
|
}
|
|
defer r.gojaRuntimeManager.DeletePluginPool(ext.ID)
|
|
|
|
// Run the code
|
|
switch params.Function {
|
|
case "search":
|
|
// Search
|
|
y := 0
|
|
if media.GetStartDate().GetYear() != nil {
|
|
y = *media.GetStartDate().GetYear()
|
|
}
|
|
|
|
ret := make([]*hibikemanga.SearchResult, 0)
|
|
for _, title := range titles {
|
|
res, err := provider.Search(hibikemanga.SearchOptions{
|
|
Query: *title,
|
|
Year: y,
|
|
})
|
|
if err != nil {
|
|
playgroundLogger.logger.Error().Err(err).Msgf("playground: Search failed for title \"%s\"", *title)
|
|
}
|
|
manga.HydrateSearchResultSearchRating(res, title)
|
|
ret = append(ret, res...)
|
|
}
|
|
|
|
var selected *hibikemanga.SearchResult
|
|
if len(ret) > 0 {
|
|
selected = manga.GetBestSearchResult(ret)
|
|
}
|
|
|
|
return newPlaygroundResponse(playgroundLogger, selected), nil
|
|
|
|
case "findChapters":
|
|
res, err := provider.FindChapters(params.Inputs["id"].(string))
|
|
if err != nil {
|
|
return newPlaygroundResponse(playgroundLogger, err), nil
|
|
}
|
|
return newPlaygroundResponse(playgroundLogger, res), nil
|
|
|
|
case "findChapterPages":
|
|
res, err := provider.FindChapterPages(params.Inputs["id"].(string))
|
|
if err != nil {
|
|
return newPlaygroundResponse(playgroundLogger, err), nil
|
|
}
|
|
return newPlaygroundResponse(playgroundLogger, res), nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("unknown call")
|
|
}
|
|
|
|
func (r *PlaygroundRepository) runPlaygroundCodeOnlinestreamProvider(ext *extension.Extension, params *RunPlaygroundCodeParams) (resp *RunPlaygroundCodeResponse, err error) {
|
|
|
|
playgroundLogger := r.newPlaygroundDebugLogger()
|
|
|
|
mediaId, ok := params.Inputs["mediaId"].(float64)
|
|
if !ok || mediaId <= 0 {
|
|
return nil, fmt.Errorf("invalid mediaId")
|
|
}
|
|
|
|
// Fetch the anime
|
|
anime, _, err := r.getAnime(int(mediaId))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
titles := anime.GetAllTitles()
|
|
|
|
queryMedia := hibikeonlinestream.Media{
|
|
ID: anime.GetID(),
|
|
IDMal: anime.GetIDMal(),
|
|
Status: string(*anime.GetStatus()),
|
|
Format: string(*anime.GetFormat()),
|
|
EnglishTitle: anime.GetTitle().GetEnglish(),
|
|
RomajiTitle: anime.GetRomajiTitleSafe(),
|
|
EpisodeCount: anime.GetTotalEpisodeCount(),
|
|
Synonyms: anime.GetSynonymsContainingSeason(),
|
|
IsAdult: *anime.GetIsAdult(),
|
|
StartDate: &hibikeonlinestream.FuzzyDate{
|
|
Year: *anime.GetStartDate().GetYear(),
|
|
Month: anime.GetStartDate().GetMonth(),
|
|
Day: anime.GetStartDate().GetDay(),
|
|
},
|
|
}
|
|
|
|
switch params.Language {
|
|
case extension.LanguageGo:
|
|
//...
|
|
case extension.LanguageJavascript, extension.LanguageTypescript:
|
|
_, provider, err := extension_repo.NewGojaOnlinestreamProvider(ext, params.Language, playgroundLogger.logger, r.gojaRuntimeManager)
|
|
if err != nil {
|
|
return newPlaygroundResponse(playgroundLogger, err), nil
|
|
}
|
|
defer r.gojaRuntimeManager.DeletePluginPool(ext.ID)
|
|
|
|
// Run the code
|
|
switch params.Function {
|
|
case "search":
|
|
// Search - params: dub: boolean
|
|
ret := make([]*hibikeonlinestream.SearchResult, 0)
|
|
for _, title := range titles {
|
|
res, err := provider.Search(hibikeonlinestream.SearchOptions{
|
|
Media: queryMedia,
|
|
Query: *title,
|
|
Dub: params.Inputs["dub"].(bool),
|
|
Year: anime.GetStartYearSafe(),
|
|
})
|
|
if err != nil {
|
|
playgroundLogger.logger.Error().Err(err).Msgf("playground: Search failed for title \"%s\"", *title)
|
|
}
|
|
ret = append(ret, res...)
|
|
}
|
|
|
|
if len(ret) == 0 {
|
|
return newPlaygroundResponse(playgroundLogger, onlinestream.ErrNoAnimeFound), nil
|
|
}
|
|
|
|
bestRes, found := onlinestream.GetBestSearchResult(ret, titles)
|
|
if !found {
|
|
return newPlaygroundResponse(playgroundLogger, onlinestream.ErrNoAnimeFound), nil
|
|
}
|
|
|
|
return newPlaygroundResponse(playgroundLogger, bestRes), nil
|
|
|
|
case "findEpisodes":
|
|
// FindEpisodes - params: id: string
|
|
res, err := provider.FindEpisodes(params.Inputs["id"].(string))
|
|
if err != nil {
|
|
return newPlaygroundResponse(playgroundLogger, err), nil
|
|
}
|
|
return newPlaygroundResponse(playgroundLogger, res), nil
|
|
|
|
case "findEpisodeServer":
|
|
// FindEpisodeServer - params: episode: EpisodeDetails, server: string
|
|
var episode hibikeonlinestream.EpisodeDetails
|
|
_ = json.Unmarshal([]byte(params.Inputs["episode"].(string)), &episode)
|
|
|
|
res, err := provider.FindEpisodeServer(&episode, params.Inputs["server"].(string))
|
|
if err != nil {
|
|
return newPlaygroundResponse(playgroundLogger, err), nil
|
|
}
|
|
return newPlaygroundResponse(playgroundLogger, res), nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("unknown call")
|
|
}
|