node build fixed
This commit is contained in:
116
seanime-2.9.10/internal/extension/bank.go
Normal file
116
seanime-2.9.10/internal/extension/bank.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package extension
|
||||
|
||||
import (
|
||||
"seanime/internal/util/result"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type UnifiedBank struct {
|
||||
extensions *result.Map[string, BaseExtension]
|
||||
extensionAddedCh chan struct{}
|
||||
extensionRemovedCh chan struct{}
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewUnifiedBank() *UnifiedBank {
|
||||
return &UnifiedBank{
|
||||
extensions: result.NewResultMap[string, BaseExtension](),
|
||||
extensionAddedCh: make(chan struct{}, 100),
|
||||
extensionRemovedCh: make(chan struct{}, 100),
|
||||
mu: sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (b *UnifiedBank) Lock() {
|
||||
b.mu.Lock()
|
||||
}
|
||||
|
||||
func (b *UnifiedBank) Unlock() {
|
||||
b.mu.Unlock()
|
||||
}
|
||||
|
||||
func (b *UnifiedBank) Reset() {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
b.extensions = result.NewResultMap[string, BaseExtension]()
|
||||
}
|
||||
|
||||
func (b *UnifiedBank) RemoveExternalExtensions() {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
b.extensions.Range(func(id string, ext BaseExtension) bool {
|
||||
if ext.GetManifestURI() != "builtin" {
|
||||
b.extensions.Delete(id)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (b *UnifiedBank) Set(id string, ext BaseExtension) {
|
||||
// Add the extension to the map
|
||||
b.extensions.Set(id, ext)
|
||||
|
||||
// Notify listeners that an extension has been added
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
b.extensionAddedCh <- struct{}{}
|
||||
}
|
||||
|
||||
func (b *UnifiedBank) Get(id string) (BaseExtension, bool) {
|
||||
//b.mu.RLock()
|
||||
//defer b.mu.RUnlock()
|
||||
return b.extensions.Get(id)
|
||||
}
|
||||
|
||||
func (b *UnifiedBank) Delete(id string) {
|
||||
// Delete the extension from the map
|
||||
b.extensions.Delete(id)
|
||||
|
||||
// Notify listeners that an extension has been removed
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
b.extensionRemovedCh <- struct{}{}
|
||||
}
|
||||
|
||||
func (b *UnifiedBank) GetExtensionMap() *result.Map[string, BaseExtension] {
|
||||
b.mu.RLock()
|
||||
defer b.mu.RUnlock()
|
||||
return b.extensions
|
||||
}
|
||||
|
||||
func (b *UnifiedBank) Range(f func(id string, ext BaseExtension) bool) {
|
||||
// No need to lock
|
||||
b.extensions.Range(f)
|
||||
}
|
||||
|
||||
func (b *UnifiedBank) OnExtensionAdded() <-chan struct{} {
|
||||
return b.extensionAddedCh
|
||||
}
|
||||
|
||||
func (b *UnifiedBank) OnExtensionRemoved() <-chan struct{} {
|
||||
return b.extensionRemovedCh
|
||||
}
|
||||
|
||||
func GetExtension[T BaseExtension](bank *UnifiedBank, id string) (ret T, ok bool) {
|
||||
// No need to lock
|
||||
ext, ok := bank.extensions.Get(id)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
ret, ok = ext.(T)
|
||||
return
|
||||
}
|
||||
|
||||
func RangeExtensions[T BaseExtension](bank *UnifiedBank, f func(id string, ext T) bool) {
|
||||
// No need to lock
|
||||
bank.extensions.Range(func(id string, ext BaseExtension) bool {
|
||||
if typedExt, ok := ext.(T); ok {
|
||||
return f(id, typedExt)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
226
seanime-2.9.10/internal/extension/extension.go
Normal file
226
seanime-2.9.10/internal/extension/extension.go
Normal file
@@ -0,0 +1,226 @@
|
||||
package extension
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Consumer interface {
|
||||
InitExtensionBank(bank *UnifiedBank)
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type Type string
|
||||
|
||||
type Language string
|
||||
|
||||
type PluginPermissionScope string
|
||||
|
||||
const (
|
||||
TypeAnimeTorrentProvider Type = "anime-torrent-provider"
|
||||
TypeMangaProvider Type = "manga-provider"
|
||||
TypeOnlinestreamProvider Type = "onlinestream-provider"
|
||||
TypePlugin Type = "plugin"
|
||||
)
|
||||
|
||||
const (
|
||||
LanguageJavascript Language = "javascript"
|
||||
LanguageTypescript Language = "typescript"
|
||||
LanguageGo Language = "go"
|
||||
)
|
||||
|
||||
type Extension struct {
|
||||
// ID is the unique identifier of the extension
|
||||
// It must be unique across all extensions
|
||||
// It must start with a letter and contain only alphanumeric characters
|
||||
ID string `json:"id"` // e.g. "extension-example"
|
||||
Name string `json:"name"` // e.g. "Extension"
|
||||
Version string `json:"version"` // e.g. "1.0.0"
|
||||
SemverConstraint string `json:"semverConstraint,omitempty"`
|
||||
// The URI to the extension manifest file.
|
||||
// This is "builtin" if the extension is built-in and "" if the extension is local.
|
||||
ManifestURI string `json:"manifestURI"` // e.g. "http://cdn.something.app/extensions/extension-example/manifest.json"
|
||||
// The programming language of the extension
|
||||
// It is used to determine how to interpret the extension
|
||||
Language Language `json:"language"` // e.g. "go"
|
||||
// Type is the area of the application the extension is targeting
|
||||
Type Type `json:"type"` // e.g. "anime-torrent-provider"
|
||||
Description string `json:"description"` // e.g. "This extension provides torrents"
|
||||
Author string `json:"author"` // e.g. "Seanime"
|
||||
// Icon is the URL to the extension icon
|
||||
Icon string `json:"icon"`
|
||||
// Website is the URL to the extension website
|
||||
Website string `json:"website"`
|
||||
// ISO 639-1 language code.
|
||||
// Set this to "multi" if the extension supports multiple languages.
|
||||
// Defaults to "en".
|
||||
Lang string `json:"lang"`
|
||||
// List of permissions asked by the extension.
|
||||
// The user must grant these permissions before the extension can be loaded.
|
||||
Permissions []string `json:"permissions,omitempty"` // NOT IMPLEMENTED
|
||||
UserConfig *UserConfig `json:"userConfig,omitempty"`
|
||||
// Payload is the content of the extension.
|
||||
Payload string `json:"payload"`
|
||||
// PayloadURI is the URI to the extension payload.
|
||||
// It can be used as an alternative to the Payload field to load the payload from a remote source.
|
||||
// If the extension is in debug mode, this can be a file path to the local payload.
|
||||
PayloadURI string `json:"payloadURI,omitempty"`
|
||||
// Plugin is the manifest of the extension if it is a plugin.
|
||||
Plugin *PluginManifest `json:"plugin,omitempty"`
|
||||
|
||||
// IsDevelopment is true if the extension is in development mode.
|
||||
// If true, the extension code will be loaded from PayloadURI and allow you to edit the code from an editor and reload the extension without restarting the application.
|
||||
IsDevelopment bool `json:"isDevelopment,omitempty"`
|
||||
|
||||
SavedUserConfig *SavedUserConfig `json:"-"` // Contains the saved user config for the extension
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// BaseExtension is the base interface for all extensions
|
||||
// An extension is a JS file that is loaded by HTTP request
|
||||
type BaseExtension interface {
|
||||
GetID() string
|
||||
GetName() string
|
||||
GetVersion() string
|
||||
GetManifestURI() string
|
||||
GetLanguage() Language
|
||||
GetType() Type
|
||||
GetDescription() string
|
||||
GetAuthor() string
|
||||
GetPayload() string
|
||||
GetPayloadURI() string
|
||||
GetLang() string
|
||||
GetIcon() string
|
||||
GetWebsite() string
|
||||
GetPermissions() []string
|
||||
GetUserConfig() *UserConfig
|
||||
GetSavedUserConfig() *SavedUserConfig
|
||||
GetIsDevelopment() bool
|
||||
}
|
||||
|
||||
type Configurable interface {
|
||||
SetSavedUserConfig(config SavedUserConfig)
|
||||
}
|
||||
|
||||
func ToExtensionData(ext BaseExtension) *Extension {
|
||||
return &Extension{
|
||||
ID: ext.GetID(),
|
||||
Name: ext.GetName(),
|
||||
Version: ext.GetVersion(),
|
||||
ManifestURI: ext.GetManifestURI(),
|
||||
Language: ext.GetLanguage(),
|
||||
Lang: GetExtensionLang(ext.GetLang()),
|
||||
Type: ext.GetType(),
|
||||
Description: ext.GetDescription(),
|
||||
Author: ext.GetAuthor(),
|
||||
Permissions: ext.GetPermissions(),
|
||||
UserConfig: ext.GetUserConfig(),
|
||||
Icon: ext.GetIcon(),
|
||||
Website: ext.GetWebsite(),
|
||||
Payload: ext.GetPayload(),
|
||||
PayloadURI: ext.GetPayloadURI(),
|
||||
IsDevelopment: ext.GetIsDevelopment(),
|
||||
SavedUserConfig: ext.GetSavedUserConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
func GetExtensionLang(lang string) string {
|
||||
if lang == "" {
|
||||
return "en"
|
||||
}
|
||||
if lang == "all" {
|
||||
return "multi"
|
||||
}
|
||||
return lang
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type InvalidExtensionErrorCode string
|
||||
|
||||
const (
|
||||
// InvalidExtensionManifestError is returned when the extension manifest is invalid
|
||||
InvalidExtensionManifestError InvalidExtensionErrorCode = "invalid_manifest"
|
||||
// InvalidExtensionPayloadError is returned when the extension code is invalid / obsolete
|
||||
InvalidExtensionPayloadError InvalidExtensionErrorCode = "invalid_payload"
|
||||
InvalidExtensionUserConfigError InvalidExtensionErrorCode = "user_config_error"
|
||||
// InvalidExtensionAuthorizationError is returned when some authorization scopes have not been granted
|
||||
InvalidExtensionAuthorizationError InvalidExtensionErrorCode = "invalid_authorization"
|
||||
// InvalidExtensionPluginPermissionsNotGranted is returned when the plugin permissions have not been granted
|
||||
InvalidExtensionPluginPermissionsNotGranted InvalidExtensionErrorCode = "plugin_permissions_not_granted"
|
||||
// InvalidExtensionSemverConstraintError is returned when the semver constraint is invalid
|
||||
InvalidExtensionSemverConstraintError InvalidExtensionErrorCode = "invalid_semver_constraint"
|
||||
)
|
||||
|
||||
type InvalidExtension struct {
|
||||
// Auto-generated ID
|
||||
ID string `json:"id"`
|
||||
Path string `json:"path"`
|
||||
Extension Extension `json:"extension"`
|
||||
Reason string `json:"reason"`
|
||||
Code InvalidExtensionErrorCode `json:"code"`
|
||||
PluginPermissionDescription string `json:"pluginPermissionDescription,omitempty"`
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type UserConfig struct {
|
||||
// The version of the extension configuration.
|
||||
// This is used to determine if the configuration has changed.
|
||||
Version int `json:"version"`
|
||||
// Whether the extension requires user configuration.
|
||||
RequiresConfig bool `json:"requiresConfig"`
|
||||
// This will be used to generate the user configuration form, and the values will be passed to the extension.
|
||||
Fields []ConfigField `json:"fields"`
|
||||
}
|
||||
|
||||
type Preferences struct {
|
||||
// This will be used to generate the preference form, and the values will be passed to the extension.
|
||||
Fields []ConfigField `json:"fields"`
|
||||
}
|
||||
|
||||
type SavedUserConfig struct {
|
||||
// The version of the extension configuration.
|
||||
Version int `json:"version"`
|
||||
// The values of the user configuration fields.
|
||||
Values map[string]string `json:"values"`
|
||||
}
|
||||
|
||||
const (
|
||||
ConfigFieldTypeText ConfigFieldType = "text"
|
||||
ConfigFieldTypeSwitch ConfigFieldType = "switch"
|
||||
ConfigFieldTypeSelect ConfigFieldType = "select"
|
||||
)
|
||||
|
||||
type (
|
||||
|
||||
// ConfigField represents a field in an extension's configuration.
|
||||
// The fields are defined in the manifest file.
|
||||
ConfigField struct {
|
||||
Type ConfigFieldType `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
Options []ConfigFieldSelectOption `json:"options,omitempty"`
|
||||
Default string `json:"default,omitempty"`
|
||||
}
|
||||
|
||||
ConfigFieldType string
|
||||
|
||||
ConfigFieldSelectOption struct {
|
||||
Value string `json:"value"`
|
||||
Label string `json:"label"`
|
||||
}
|
||||
|
||||
ConfigFieldValueValidator func(value string) error
|
||||
)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func (p *PluginPermissionScope) String() string {
|
||||
return string(*p)
|
||||
}
|
||||
|
||||
func (p *PluginPermissionScope) Is(str string) bool {
|
||||
return strings.EqualFold(string(*p), str)
|
||||
}
|
||||
97
seanime-2.9.10/internal/extension/hibike/manga/types.go
Normal file
97
seanime-2.9.10/internal/extension/hibike/manga/types.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package hibikemanga
|
||||
|
||||
type (
|
||||
Provider interface {
|
||||
// Search returns the search results for the given query.
|
||||
Search(opts SearchOptions) ([]*SearchResult, error)
|
||||
// FindChapters returns the chapter details for the given manga ID.
|
||||
FindChapters(id string) ([]*ChapterDetails, error)
|
||||
// FindChapterPages returns the chapter pages for the given chapter ID.
|
||||
FindChapterPages(id string) ([]*ChapterPage, error)
|
||||
// GetSettings returns the provider settings.
|
||||
GetSettings() Settings
|
||||
}
|
||||
|
||||
Settings struct {
|
||||
SupportsMultiScanlator bool `json:"supportsMultiScanlator"`
|
||||
SupportsMultiLanguage bool `json:"supportsMultiLanguage"`
|
||||
}
|
||||
|
||||
SearchOptions struct {
|
||||
Query string `json:"query"`
|
||||
// Year is the year the manga was released.
|
||||
// It will be 0 if the year is not available.
|
||||
Year int `json:"year"`
|
||||
}
|
||||
|
||||
SearchResult struct {
|
||||
// "ID" of the extension.
|
||||
Provider string `json:"provider"`
|
||||
// ID of the manga, used to fetch the chapter details.
|
||||
// It can be a combination of keys separated by a delimiter. (Delimiters should not be slashes).
|
||||
ID string `json:"id"`
|
||||
// The title of the manga.
|
||||
Title string `json:"title"`
|
||||
// Synonyms are alternative titles for the manga.
|
||||
Synonyms []string `json:"synonyms,omitempty"`
|
||||
// Year the manga was released.
|
||||
Year int `json:"year,omitempty"`
|
||||
// URL of the manga cover image.
|
||||
Image string `json:"image,omitempty"`
|
||||
// Indicates how well the chapter title matches the search query.
|
||||
// It is a number from 0 to 1.
|
||||
// Leave it empty if the comparison should be done by Seanime.
|
||||
SearchRating float64 `json:"searchRating,omitempty"`
|
||||
}
|
||||
|
||||
ChapterDetails struct {
|
||||
// "ID" of the extension.
|
||||
// This should be the same as the extension ID and follow the same format.
|
||||
Provider string `json:"provider"`
|
||||
// ID of the chapter, used to fetch the chapter pages.
|
||||
// It can be a combination of keys separated by a delimiter. (Delimiters should not be slashes).
|
||||
// If the same ID has multiple languages, the language key should be included. (e.g., "one-piece-001$chapter-1$en").
|
||||
// If the same ID has multiple scanlators, the group key should be included. (e.g., "one-piece-001$chapter-1$group-1").
|
||||
ID string `json:"id"`
|
||||
// The chapter page URL.
|
||||
URL string `json:"url"`
|
||||
// The chapter title.
|
||||
// It should be in this format: "Chapter X.Y - {title}" where X is the chapter number and Y is the subchapter number.
|
||||
Title string `json:"title"`
|
||||
// e.g., "1", "1.5", "2", "3"
|
||||
Chapter string `json:"chapter"`
|
||||
// From 0 to n
|
||||
Index uint `json:"index"`
|
||||
// The scanlator that translated the chapter.
|
||||
// Leave it empty if your extension does not support multiple scanlators.
|
||||
Scanlator string `json:"scanlator,omitempty"`
|
||||
// The language of the chapter.
|
||||
// Leave it empty if your extension does not support multiple languages.
|
||||
Language string `json:"language,omitempty"`
|
||||
// The rating of the chapter. It is a number from 0 to 100.
|
||||
// Leave it empty if the rating is not available.
|
||||
Rating int `json:"rating,omitempty"`
|
||||
// UpdatedAt is the date when the chapter was last updated.
|
||||
// It should be in the format "YYYY-MM-DD".
|
||||
// Leave it empty if the date is not available.
|
||||
UpdatedAt string `json:"updatedAt,omitempty"`
|
||||
|
||||
// LocalIsPDF is true if the chapter is a single, readable PDF file.
|
||||
LocalIsPDF bool `json:"localIsPDF,omitempty"`
|
||||
}
|
||||
|
||||
ChapterPage struct {
|
||||
// ID of the provider.
|
||||
// This should be the same as the extension ID and follow the same format.
|
||||
Provider string `json:"provider"`
|
||||
// URL of the chapter page.
|
||||
URL string `json:"url"`
|
||||
// Index of the page in the chapter.
|
||||
// From 0 to n.
|
||||
Index int `json:"index"`
|
||||
// Request headers for the page if proxying is required.
|
||||
Headers map[string]string `json:"headers"`
|
||||
|
||||
Buf []byte `json:"-"`
|
||||
}
|
||||
)
|
||||
146
seanime-2.9.10/internal/extension/hibike/onlinestream/types.go
Normal file
146
seanime-2.9.10/internal/extension/hibike/onlinestream/types.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package hibikeonlinestream
|
||||
|
||||
type (
|
||||
Provider interface {
|
||||
Search(opts SearchOptions) ([]*SearchResult, error)
|
||||
// FindEpisodes returns the episodes for the given anime ID.
|
||||
FindEpisodes(id string) ([]*EpisodeDetails, error)
|
||||
// FindEpisodeServer returns the episode server for the given episode.
|
||||
// The "server" argument can be "default"
|
||||
FindEpisodeServer(episode *EpisodeDetails, server string) (*EpisodeServer, error)
|
||||
// GetSettings returns the provider settings.
|
||||
GetSettings() Settings
|
||||
}
|
||||
|
||||
SearchOptions struct {
|
||||
// The media object provided by Seanime.
|
||||
Media Media `json:"media"`
|
||||
// The search query.
|
||||
Query string `json:"query"`
|
||||
// Whether to search for subbed or dubbed anime.
|
||||
Dub bool `json:"dub"`
|
||||
// The year the anime was released.
|
||||
// Will be 0 if the year is not available.
|
||||
Year int `json:"year"`
|
||||
}
|
||||
|
||||
Media struct {
|
||||
// AniList ID of the media.
|
||||
ID int `json:"id"`
|
||||
// MyAnimeList ID of the media.
|
||||
IDMal *int `json:"idMal,omitempty"`
|
||||
// e.g. "FINISHED", "RELEASING", "NOT_YET_RELEASED", "CANCELLED", "HIATUS"
|
||||
// This will be set to "NOT_YET_RELEASED" if the status is unknown.
|
||||
Status string `json:"status,omitempty"`
|
||||
// e.g. "TV", "TV_SHORT", "MOVIE", "SPECIAL", "OVA", "ONA", "MUSIC"
|
||||
// This will be set to "TV" if the format is unknown.
|
||||
Format string `json:"format,omitempty"`
|
||||
// e.g. "Attack on Titan"
|
||||
// This will be undefined if the english title is unknown.
|
||||
EnglishTitle *string `json:"englishTitle,omitempty"`
|
||||
// e.g. "Shingeki no Kyojin"
|
||||
RomajiTitle string `json:"romajiTitle,omitempty"`
|
||||
// TotalEpisodes is total number of episodes of the media.
|
||||
// This will be -1 if the total number of episodes is unknown / not applicable.
|
||||
EpisodeCount int `json:"episodeCount,omitempty"`
|
||||
// All alternative titles of the media.
|
||||
Synonyms []string `json:"synonyms"`
|
||||
// Whether the media is NSFW.
|
||||
IsAdult bool `json:"isAdult"`
|
||||
// Start date of the media.
|
||||
// This will be undefined if it has no start date.
|
||||
StartDate *FuzzyDate `json:"startDate,omitempty"`
|
||||
}
|
||||
|
||||
FuzzyDate struct {
|
||||
Year int `json:"year"`
|
||||
Month *int `json:"month"`
|
||||
Day *int `json:"day"`
|
||||
}
|
||||
|
||||
Settings struct {
|
||||
EpisodeServers []string `json:"episodeServers"`
|
||||
SupportsDub bool `json:"supportsDub"`
|
||||
}
|
||||
|
||||
SearchResult struct {
|
||||
// ID is the anime slug.
|
||||
// It is used to fetch the episode details.
|
||||
ID string `json:"id"`
|
||||
// Title is the anime title.
|
||||
Title string `json:"title"`
|
||||
// URL is the anime page URL.
|
||||
URL string `json:"url"`
|
||||
SubOrDub SubOrDub `json:"subOrDub"`
|
||||
}
|
||||
|
||||
// EpisodeDetails contains the episode information from a provider.
|
||||
// It is obtained by scraping the list of episodes.
|
||||
EpisodeDetails struct {
|
||||
// "ID" of the extension.
|
||||
Provider string `json:"provider"`
|
||||
// ID is the episode slug.
|
||||
// e.g. "the-apothecary-diaries-18578".
|
||||
ID string `json:"id"`
|
||||
// Episode number.
|
||||
// From 0 to n.
|
||||
Number int `json:"number"`
|
||||
// Episode page URL.
|
||||
URL string `json:"url"`
|
||||
// Episode title.
|
||||
// Leave it empty if the episode title is not available.
|
||||
Title string `json:"title,omitempty"`
|
||||
}
|
||||
|
||||
// EpisodeServer contains the server, headers and video sources for an episode.
|
||||
EpisodeServer struct {
|
||||
// "ID" of the extension.
|
||||
Provider string `json:"provider"`
|
||||
// Episode server name.
|
||||
// e.g. "vidcloud".
|
||||
Server string `json:"server"`
|
||||
// HTTP headers for the video request.
|
||||
Headers map[string]string `json:"headers"`
|
||||
// Video sources for the episode.
|
||||
VideoSources []*VideoSource `json:"videoSources"`
|
||||
}
|
||||
|
||||
SubOrDub string
|
||||
|
||||
VideoSourceType string
|
||||
|
||||
VideoSource struct {
|
||||
// URL of the video source.
|
||||
URL string `json:"url"`
|
||||
// Type of the video source.
|
||||
Type VideoSourceType `json:"type"`
|
||||
// Quality of the video source.
|
||||
// e.g. "default", "auto", "1080p".
|
||||
Quality string `json:"quality"`
|
||||
// Subtitles for the video source.
|
||||
Subtitles []*VideoSubtitle `json:"subtitles"`
|
||||
}
|
||||
|
||||
VideoSubtitle struct {
|
||||
ID string `json:"id"`
|
||||
URL string `json:"url"`
|
||||
// e.g. "en", "fr"
|
||||
Language string `json:"language"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
}
|
||||
|
||||
VideoExtractor interface {
|
||||
Extract(uri string) ([]*VideoSource, error)
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
Sub SubOrDub = "sub"
|
||||
Dub SubOrDub = "dub"
|
||||
SubAndDub SubOrDub = "both"
|
||||
)
|
||||
|
||||
const (
|
||||
VideoSourceMP4 VideoSourceType = "mp4"
|
||||
VideoSourceM3U8 VideoSourceType = "m3u8"
|
||||
)
|
||||
170
seanime-2.9.10/internal/extension/hibike/torrent/types.go
Normal file
170
seanime-2.9.10/internal/extension/hibike/torrent/types.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package hibiketorrent
|
||||
|
||||
// Resolutions represent resolution filters available to the user.
|
||||
var Resolutions = []string{"1080", "720", "540", "480"}
|
||||
|
||||
const (
|
||||
// AnimeProviderTypeMain providers can be used as default providers.
|
||||
AnimeProviderTypeMain AnimeProviderType = "main"
|
||||
// AnimeProviderTypeSpecial providers cannot be set as default provider.
|
||||
// Providers that return only specific content (e.g. adult content).
|
||||
// These providers should not return anything from "GetLatest".
|
||||
AnimeProviderTypeSpecial AnimeProviderType = "special"
|
||||
)
|
||||
|
||||
const (
|
||||
AnimeProviderSmartSearchFilterBatch AnimeProviderSmartSearchFilter = "batch"
|
||||
AnimeProviderSmartSearchFilterEpisodeNumber AnimeProviderSmartSearchFilter = "episodeNumber"
|
||||
AnimeProviderSmartSearchFilterResolution AnimeProviderSmartSearchFilter = "resolution"
|
||||
AnimeProviderSmartSearchFilterQuery AnimeProviderSmartSearchFilter = "query"
|
||||
AnimeProviderSmartSearchFilterBestReleases AnimeProviderSmartSearchFilter = "bestReleases"
|
||||
)
|
||||
|
||||
type (
|
||||
AnimeProviderType string
|
||||
|
||||
AnimeProviderSmartSearchFilter string
|
||||
|
||||
AnimeProviderSettings struct {
|
||||
CanSmartSearch bool `json:"canSmartSearch"`
|
||||
SmartSearchFilters []AnimeProviderSmartSearchFilter `json:"smartSearchFilters"`
|
||||
SupportsAdult bool `json:"supportsAdult"`
|
||||
Type AnimeProviderType `json:"type"`
|
||||
}
|
||||
|
||||
AnimeProvider interface {
|
||||
// Search for torrents.
|
||||
Search(opts AnimeSearchOptions) ([]*AnimeTorrent, error)
|
||||
// SmartSearch for torrents.
|
||||
SmartSearch(opts AnimeSmartSearchOptions) ([]*AnimeTorrent, error)
|
||||
// GetTorrentInfoHash returns the info hash of the torrent.
|
||||
// This should just return the info hash without scraping the torrent page if already available.
|
||||
GetTorrentInfoHash(torrent *AnimeTorrent) (string, error)
|
||||
// GetTorrentMagnetLink returns the magnet link of the torrent.
|
||||
// This should just return the magnet link without scraping the torrent page if already available.
|
||||
GetTorrentMagnetLink(torrent *AnimeTorrent) (string, error)
|
||||
// GetLatest returns the latest torrents.
|
||||
GetLatest() ([]*AnimeTorrent, error)
|
||||
// GetSettings returns the provider settings.
|
||||
GetSettings() AnimeProviderSettings
|
||||
}
|
||||
|
||||
Media struct {
|
||||
// AniList ID of the media.
|
||||
ID int `json:"id"`
|
||||
// MyAnimeList ID of the media.
|
||||
IDMal *int `json:"idMal,omitempty"`
|
||||
// e.g. "FINISHED", "RELEASING", "NOT_YET_RELEASED", "CANCELLED", "HIATUS"
|
||||
// This will be set to "NOT_YET_RELEASED" if the status is unknown.
|
||||
Status string `json:"status,omitempty"`
|
||||
// e.g. "TV", "TV_SHORT", "MOVIE", "SPECIAL", "OVA", "ONA", "MUSIC"
|
||||
// This will be set to "TV" if the format is unknown.
|
||||
Format string `json:"format,omitempty"`
|
||||
// e.g. "Attack on Titan"
|
||||
// This will be undefined if the english title is unknown.
|
||||
EnglishTitle *string `json:"englishTitle,omitempty"`
|
||||
// e.g. "Shingeki no Kyojin"
|
||||
RomajiTitle string `json:"romajiTitle,omitempty"`
|
||||
// TotalEpisodes is total number of episodes of the media.
|
||||
// This will be -1 if the total number of episodes is unknown / not applicable.
|
||||
EpisodeCount int `json:"episodeCount,omitempty"`
|
||||
// Absolute offset of the media's season.
|
||||
// This will be 0 if the media is not seasonal or the offset is unknown.
|
||||
AbsoluteSeasonOffset int `json:"absoluteSeasonOffset,omitempty"`
|
||||
// All alternative titles of the media.
|
||||
Synonyms []string `json:"synonyms"`
|
||||
// Whether the media is NSFW.
|
||||
IsAdult bool `json:"isAdult"`
|
||||
// Start date of the media.
|
||||
// This will be undefined if it has no start date.
|
||||
StartDate *FuzzyDate `json:"startDate,omitempty"`
|
||||
}
|
||||
|
||||
FuzzyDate struct {
|
||||
Year int `json:"year"`
|
||||
Month *int `json:"month"`
|
||||
Day *int `json:"day"`
|
||||
}
|
||||
|
||||
// AnimeSearchOptions represents the options to search for torrents without filters.
|
||||
AnimeSearchOptions struct {
|
||||
// The media object provided by Seanime.
|
||||
Media Media `json:"media"`
|
||||
// The user search query.
|
||||
Query string `json:"query"`
|
||||
}
|
||||
|
||||
AnimeSmartSearchOptions struct {
|
||||
// The media object provided by Seanime.
|
||||
Media Media `json:"media"`
|
||||
// The user search query.
|
||||
// This will be empty if your extension does not support custom queries.
|
||||
Query string `json:"query"`
|
||||
// Indicates whether the user wants to search for batch torrents.
|
||||
// This will be false if your extension does not support batch torrents.
|
||||
Batch bool `json:"batch"`
|
||||
// The episode number the user wants to search for.
|
||||
// This will be 0 if your extension does not support episode number filtering.
|
||||
EpisodeNumber int `json:"episodeNumber"`
|
||||
// The resolution the user wants to search for.
|
||||
// This will be empty if your extension does not support resolution filtering.
|
||||
// e.g. "1080", "720"
|
||||
Resolution string `json:"resolution"`
|
||||
// AniDB Anime ID of the media.
|
||||
AnidbAID int `json:"anidbAID"`
|
||||
// AniDB Episode ID of the media.
|
||||
AnidbEID int `json:"anidbEID"`
|
||||
// Indicates whether the user wants to search for the best releases.
|
||||
// This will be false if your extension does not support filtering by best releases.
|
||||
BestReleases bool `json:"bestReleases"`
|
||||
}
|
||||
|
||||
AnimeTorrent struct {
|
||||
// "ID" of the extension.
|
||||
Provider string `json:"provider,omitempty"`
|
||||
// Title of the torrent.
|
||||
Name string `json:"name"`
|
||||
// Date of the torrent.
|
||||
// The date should have RFC3339 format. e.g. "2006-01-02T15:04:05Z07:00"
|
||||
Date string `json:"date"`
|
||||
// Size of the torrent in bytes.
|
||||
Size int64 `json:"size"`
|
||||
// Formatted size of the torrent. e.g. "1.2 GB"
|
||||
// Leave this empty if you want Seanime to format the size.
|
||||
FormattedSize string `json:"formattedSize"`
|
||||
// Number of seeders.
|
||||
Seeders int `json:"seeders"`
|
||||
// Number of leechers.
|
||||
Leechers int `json:"leechers"`
|
||||
// Number of downloads.
|
||||
DownloadCount int `json:"downloadCount"`
|
||||
// Link to the torrent page.
|
||||
Link string `json:"link"`
|
||||
// Download URL of the torrent.
|
||||
// Leave this empty if you cannot provide a direct download URL.
|
||||
DownloadUrl string `json:"downloadUrl"`
|
||||
// Magnet link of the torrent.
|
||||
// Leave this empty if you cannot provide a magnet link without scraping.
|
||||
MagnetLink string `json:"magnetLink,omitempty"`
|
||||
// InfoHash of the torrent.
|
||||
// Leave empty if it should be scraped later.
|
||||
InfoHash string `json:"infoHash,omitempty"`
|
||||
// Resolution of the video.
|
||||
// e.g. "1080p", "720p"
|
||||
Resolution string `json:"resolution,omitempty"`
|
||||
// Set this to true if you can confirm that the torrent is a batch.
|
||||
// Else, Seanime will parse the torrent name to determine if it's a batch.
|
||||
IsBatch bool `json:"isBatch,omitempty"`
|
||||
// Episode number of the torrent.
|
||||
// Return -1 if unknown / unable to determine and Seanime will parse the torrent name.
|
||||
EpisodeNumber int `json:"episodeNumber,omitempty"`
|
||||
// Release group of the torrent.
|
||||
// Leave this empty if you want Seanime to parse the release group from the name.
|
||||
ReleaseGroup string `json:"releaseGroup,omitempty"`
|
||||
// Set this to true if you can confirm that the torrent is the best release.
|
||||
IsBestRelease bool `json:"isBestRelease"`
|
||||
// Set this to true if you can confirm that the torrent matches the anime the user is searching for.
|
||||
// e.g. If the torrent was found using the AniDB anime or episode ID
|
||||
Confirmed bool `json:"confirmed"`
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
package hibikextension
|
||||
|
||||
type (
|
||||
SelectOption struct {
|
||||
Value string `json:"value"`
|
||||
Label string `json:"label"`
|
||||
}
|
||||
)
|
||||
98
seanime-2.9.10/internal/extension/manga_provider.go
Normal file
98
seanime-2.9.10/internal/extension/manga_provider.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package extension
|
||||
|
||||
import (
|
||||
hibikemanga "seanime/internal/extension/hibike/manga"
|
||||
)
|
||||
|
||||
type MangaProviderExtension interface {
|
||||
BaseExtension
|
||||
GetProvider() hibikemanga.Provider
|
||||
}
|
||||
|
||||
type MangaProviderExtensionImpl struct {
|
||||
ext *Extension
|
||||
provider hibikemanga.Provider
|
||||
}
|
||||
|
||||
func NewMangaProviderExtension(ext *Extension, provider hibikemanga.Provider) MangaProviderExtension {
|
||||
return &MangaProviderExtensionImpl{
|
||||
ext: ext,
|
||||
provider: provider,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MangaProviderExtensionImpl) GetProvider() hibikemanga.Provider {
|
||||
return m.provider
|
||||
}
|
||||
|
||||
func (m *MangaProviderExtensionImpl) GetExtension() *Extension {
|
||||
return m.ext
|
||||
}
|
||||
|
||||
func (m *MangaProviderExtensionImpl) GetType() Type {
|
||||
return m.ext.Type
|
||||
}
|
||||
|
||||
func (m *MangaProviderExtensionImpl) GetID() string {
|
||||
return m.ext.ID
|
||||
}
|
||||
|
||||
func (m *MangaProviderExtensionImpl) GetName() string {
|
||||
return m.ext.Name
|
||||
}
|
||||
|
||||
func (m *MangaProviderExtensionImpl) GetVersion() string {
|
||||
return m.ext.Version
|
||||
}
|
||||
|
||||
func (m *MangaProviderExtensionImpl) GetManifestURI() string {
|
||||
return m.ext.ManifestURI
|
||||
}
|
||||
|
||||
func (m *MangaProviderExtensionImpl) GetLanguage() Language {
|
||||
return m.ext.Language
|
||||
}
|
||||
|
||||
func (m *MangaProviderExtensionImpl) GetLang() string {
|
||||
return GetExtensionLang(m.ext.Lang)
|
||||
}
|
||||
|
||||
func (m *MangaProviderExtensionImpl) GetDescription() string {
|
||||
return m.ext.Description
|
||||
}
|
||||
|
||||
func (m *MangaProviderExtensionImpl) GetAuthor() string {
|
||||
return m.ext.Author
|
||||
}
|
||||
|
||||
func (m *MangaProviderExtensionImpl) GetPayload() string {
|
||||
return m.ext.Payload
|
||||
}
|
||||
|
||||
func (m *MangaProviderExtensionImpl) GetWebsite() string {
|
||||
return m.ext.Website
|
||||
}
|
||||
|
||||
func (m *MangaProviderExtensionImpl) GetIcon() string {
|
||||
return m.ext.Icon
|
||||
}
|
||||
|
||||
func (m *MangaProviderExtensionImpl) GetPermissions() []string {
|
||||
return m.ext.Permissions
|
||||
}
|
||||
|
||||
func (m *MangaProviderExtensionImpl) GetUserConfig() *UserConfig {
|
||||
return m.ext.UserConfig
|
||||
}
|
||||
|
||||
func (m *MangaProviderExtensionImpl) GetSavedUserConfig() *SavedUserConfig {
|
||||
return m.ext.SavedUserConfig
|
||||
}
|
||||
|
||||
func (m *MangaProviderExtensionImpl) GetPayloadURI() string {
|
||||
return m.ext.PayloadURI
|
||||
}
|
||||
|
||||
func (m *MangaProviderExtensionImpl) GetIsDevelopment() bool {
|
||||
return m.ext.IsDevelopment
|
||||
}
|
||||
98
seanime-2.9.10/internal/extension/onlinestream_provider.go
Normal file
98
seanime-2.9.10/internal/extension/onlinestream_provider.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package extension
|
||||
|
||||
import (
|
||||
hibikeonlinestream "seanime/internal/extension/hibike/onlinestream"
|
||||
)
|
||||
|
||||
type OnlinestreamProviderExtension interface {
|
||||
BaseExtension
|
||||
GetProvider() hibikeonlinestream.Provider
|
||||
}
|
||||
|
||||
type OnlinestreamProviderExtensionImpl struct {
|
||||
ext *Extension
|
||||
provider hibikeonlinestream.Provider
|
||||
}
|
||||
|
||||
func NewOnlinestreamProviderExtension(ext *Extension, provider hibikeonlinestream.Provider) OnlinestreamProviderExtension {
|
||||
return &OnlinestreamProviderExtensionImpl{
|
||||
ext: ext,
|
||||
provider: provider,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *OnlinestreamProviderExtensionImpl) GetProvider() hibikeonlinestream.Provider {
|
||||
return m.provider
|
||||
}
|
||||
|
||||
func (m *OnlinestreamProviderExtensionImpl) GetExtension() *Extension {
|
||||
return m.ext
|
||||
}
|
||||
|
||||
func (m *OnlinestreamProviderExtensionImpl) GetType() Type {
|
||||
return m.ext.Type
|
||||
}
|
||||
|
||||
func (m *OnlinestreamProviderExtensionImpl) GetID() string {
|
||||
return m.ext.ID
|
||||
}
|
||||
|
||||
func (m *OnlinestreamProviderExtensionImpl) GetName() string {
|
||||
return m.ext.Name
|
||||
}
|
||||
|
||||
func (m *OnlinestreamProviderExtensionImpl) GetVersion() string {
|
||||
return m.ext.Version
|
||||
}
|
||||
|
||||
func (m *OnlinestreamProviderExtensionImpl) GetManifestURI() string {
|
||||
return m.ext.ManifestURI
|
||||
}
|
||||
|
||||
func (m *OnlinestreamProviderExtensionImpl) GetLanguage() Language {
|
||||
return m.ext.Language
|
||||
}
|
||||
|
||||
func (m *OnlinestreamProviderExtensionImpl) GetLang() string {
|
||||
return GetExtensionLang(m.ext.Lang)
|
||||
}
|
||||
|
||||
func (m *OnlinestreamProviderExtensionImpl) GetDescription() string {
|
||||
return m.ext.Description
|
||||
}
|
||||
|
||||
func (m *OnlinestreamProviderExtensionImpl) GetAuthor() string {
|
||||
return m.ext.Author
|
||||
}
|
||||
|
||||
func (m *OnlinestreamProviderExtensionImpl) GetPayload() string {
|
||||
return m.ext.Payload
|
||||
}
|
||||
|
||||
func (m *OnlinestreamProviderExtensionImpl) GetWebsite() string {
|
||||
return m.ext.Website
|
||||
}
|
||||
|
||||
func (m *OnlinestreamProviderExtensionImpl) GetIcon() string {
|
||||
return m.ext.Icon
|
||||
}
|
||||
|
||||
func (m *OnlinestreamProviderExtensionImpl) GetPermissions() []string {
|
||||
return m.ext.Permissions
|
||||
}
|
||||
|
||||
func (m *OnlinestreamProviderExtensionImpl) GetUserConfig() *UserConfig {
|
||||
return m.ext.UserConfig
|
||||
}
|
||||
|
||||
func (m *OnlinestreamProviderExtensionImpl) GetSavedUserConfig() *SavedUserConfig {
|
||||
return m.ext.SavedUserConfig
|
||||
}
|
||||
|
||||
func (m *OnlinestreamProviderExtensionImpl) GetPayloadURI() string {
|
||||
return m.ext.PayloadURI
|
||||
}
|
||||
|
||||
func (m *OnlinestreamProviderExtensionImpl) GetIsDevelopment() bool {
|
||||
return m.ext.IsDevelopment
|
||||
}
|
||||
399
seanime-2.9.10/internal/extension/plugin.go
Normal file
399
seanime-2.9.10/internal/extension/plugin.go
Normal file
@@ -0,0 +1,399 @@
|
||||
package extension
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
PluginManifestVersion = "1"
|
||||
)
|
||||
|
||||
var (
|
||||
PluginPermissionStorage PluginPermissionScope = "storage" // Allows the plugin to store its own data
|
||||
PluginPermissionDatabase PluginPermissionScope = "database" // Allows the plugin to read non-auth data from the database and write to it
|
||||
PluginPermissionPlayback PluginPermissionScope = "playback" // Allows the plugin to use the playback manager
|
||||
PluginPermissionAnilist PluginPermissionScope = "anilist" // Allows the plugin to use the Anilist client
|
||||
PluginPermissionAnilistToken PluginPermissionScope = "anilist-token" // Allows the plugin to see and use the Anilist token
|
||||
PluginPermissionSystem PluginPermissionScope = "system" // Allows the plugin to use the OS/Filesystem/Filepath functions. SystemPermissions must be granted additionally.
|
||||
PluginPermissionCron PluginPermissionScope = "cron" // Allows the plugin to use the cron manager
|
||||
PluginPermissionNotification PluginPermissionScope = "notification" // Allows the plugin to use the notification manager
|
||||
PluginPermissionDiscord PluginPermissionScope = "discord" // Allows the plugin to use the discord rpc
|
||||
PluginPermissionTorrentClient PluginPermissionScope = "torrent-client" // Allows the plugin to use the torrent client
|
||||
)
|
||||
|
||||
type PluginManifest struct {
|
||||
Version string `json:"version"`
|
||||
// Permissions is a list of permissions that the plugin is asking for.
|
||||
// The user must acknowledge these permissions before the plugin can be loaded.
|
||||
Permissions PluginPermissions `json:"permissions,omitempty"`
|
||||
}
|
||||
|
||||
type PluginPermissions struct {
|
||||
Scopes []PluginPermissionScope `json:"scopes,omitempty"`
|
||||
Allow PluginAllowlist `json:"allow,omitempty"`
|
||||
}
|
||||
|
||||
// PluginAllowlist is a list of system permissions that the plugin is asking for.
|
||||
//
|
||||
// The user must acknowledge these permissions before the plugin can be loaded.
|
||||
type PluginAllowlist struct {
|
||||
// ReadPaths is a list of paths that the plugin is allowed to read from.
|
||||
ReadPaths []string `json:"readPaths,omitempty"`
|
||||
// WritePaths is a list of paths that the plugin is allowed to write to.
|
||||
WritePaths []string `json:"writePaths,omitempty"`
|
||||
// CommandScopes defines the commands that the plugin is allowed to execute.
|
||||
// Each command scope has a unique identifier and configuration.
|
||||
CommandScopes []CommandScope `json:"commandScopes,omitempty"`
|
||||
}
|
||||
|
||||
// CommandScope defines a specific command or set of commands that can be executed
|
||||
// with specific arguments and validation rules.
|
||||
type CommandScope struct {
|
||||
// Description explains why this command scope is needed
|
||||
Description string `json:"description,omitempty"`
|
||||
// Command is the executable program
|
||||
Command string `json:"command"`
|
||||
// Args defines the allowed arguments for this command
|
||||
// If nil or empty, no arguments are allowed
|
||||
// If contains "$ARGS", any arguments are allowed at that position
|
||||
Args []CommandArg `json:"args,omitempty"`
|
||||
}
|
||||
|
||||
// CommandArg represents an argument for a command
|
||||
type CommandArg struct {
|
||||
// Value is the fixed argument value
|
||||
// If empty, Validator must be set
|
||||
Value string `json:"value,omitempty"`
|
||||
// Validator is a Perl compatible regex pattern to validate dynamic argument values
|
||||
// Special values:
|
||||
// - "$ARGS" allows any arguments at this position
|
||||
// - "$PATH" allows any valid file path
|
||||
Validator string `json:"validator,omitempty"`
|
||||
}
|
||||
|
||||
// ReadAllowCommands returns a human-readable representation of the commands
|
||||
// that the plugin is allowed to execute.
|
||||
func (p *PluginAllowlist) ReadAllowCommands() []string {
|
||||
if p == nil {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
result := make([]string, 0)
|
||||
|
||||
// Add commands from CommandScopes
|
||||
if len(p.CommandScopes) > 0 {
|
||||
for _, scope := range p.CommandScopes {
|
||||
cmd := scope.Command
|
||||
|
||||
// Build argument string
|
||||
args := ""
|
||||
for i, arg := range scope.Args {
|
||||
if i > 0 {
|
||||
args += " "
|
||||
}
|
||||
|
||||
if arg.Value != "" {
|
||||
args += arg.Value
|
||||
} else if arg.Validator == "$ARGS" {
|
||||
args += "[any arguments]"
|
||||
} else if arg.Validator == "$PATH" {
|
||||
args += "[any path]"
|
||||
} else if arg.Validator != "" {
|
||||
args += "[matching: " + arg.Validator + "]"
|
||||
}
|
||||
}
|
||||
|
||||
if args != "" {
|
||||
cmd += " " + args
|
||||
}
|
||||
|
||||
// Add description if available
|
||||
if scope.Description != "" {
|
||||
cmd += " - " + scope.Description
|
||||
}
|
||||
|
||||
result = append(result, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PluginPermissions) GetHash() string {
|
||||
if p == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(p.Scopes) == 0 &&
|
||||
len(p.Allow.ReadPaths) == 0 &&
|
||||
len(p.Allow.WritePaths) == 0 &&
|
||||
len(p.Allow.CommandScopes) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
h := sha256.New()
|
||||
|
||||
// Hash scopes
|
||||
for _, scope := range p.Scopes {
|
||||
h.Write([]byte(scope))
|
||||
}
|
||||
|
||||
// Hash allowlist read paths
|
||||
for _, path := range p.Allow.ReadPaths {
|
||||
h.Write([]byte("read:" + path))
|
||||
}
|
||||
|
||||
// Hash allowlist write paths
|
||||
for _, path := range p.Allow.WritePaths {
|
||||
h.Write([]byte("write:" + path))
|
||||
}
|
||||
|
||||
// Hash command scopes
|
||||
for _, cmd := range p.Allow.CommandScopes {
|
||||
h.Write([]byte("cmd:" + cmd.Command + ":" + cmd.Description))
|
||||
for _, arg := range cmd.Args {
|
||||
h.Write([]byte("arg:" + arg.Value + ":" + arg.Validator))
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
func (p *PluginPermissions) GetDescription() string {
|
||||
if p == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Check if any permissions exist
|
||||
if len(p.Scopes) == 0 &&
|
||||
len(p.Allow.ReadPaths) == 0 &&
|
||||
len(p.Allow.WritePaths) == 0 &&
|
||||
len(p.Allow.CommandScopes) == 0 {
|
||||
return "No permissions requested."
|
||||
}
|
||||
|
||||
var desc strings.Builder
|
||||
|
||||
// Add scopes section if any exist
|
||||
if len(p.Scopes) > 0 {
|
||||
desc.WriteString("Application:\n")
|
||||
for _, scope := range p.Scopes {
|
||||
desc.WriteString("• ")
|
||||
switch scope {
|
||||
case PluginPermissionStorage:
|
||||
desc.WriteString("Storage: Store plugin data locally\n")
|
||||
case PluginPermissionDatabase:
|
||||
desc.WriteString("Database: Read and write non-auth data\n")
|
||||
case PluginPermissionPlayback:
|
||||
desc.WriteString("Playback: Control media playback and media players\n")
|
||||
case PluginPermissionAnilist:
|
||||
desc.WriteString("AniList: View and edit your AniList lists\n")
|
||||
case PluginPermissionAnilistToken:
|
||||
desc.WriteString("AniList Token: View and use your AniList token\n")
|
||||
case PluginPermissionSystem:
|
||||
desc.WriteString("System: Access OS functions (accessing files, running commands, etc.)\n")
|
||||
case PluginPermissionCron:
|
||||
desc.WriteString("Cron: Schedule automated tasks\n")
|
||||
case PluginPermissionNotification:
|
||||
desc.WriteString("Notification: Send system notifications\n")
|
||||
case PluginPermissionDiscord:
|
||||
desc.WriteString("Discord: Set Discord Rich Presence\n")
|
||||
case PluginPermissionTorrentClient:
|
||||
desc.WriteString("Torrent Client: Control torrent clients\n")
|
||||
default:
|
||||
desc.WriteString(string(scope) + "\n")
|
||||
}
|
||||
}
|
||||
desc.WriteString("\n")
|
||||
}
|
||||
|
||||
// Add file permissions if any exist
|
||||
hasFilePaths := len(p.Allow.ReadPaths) > 0 || len(p.Allow.WritePaths) > 0
|
||||
if hasFilePaths {
|
||||
desc.WriteString("File System:\n")
|
||||
|
||||
if len(p.Allow.ReadPaths) > 0 {
|
||||
desc.WriteString("• Read from:\n")
|
||||
for _, path := range p.Allow.ReadPaths {
|
||||
desc.WriteString("\t - " + explainPath(path) + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
if len(p.Allow.WritePaths) > 0 {
|
||||
desc.WriteString("• Write to:\n")
|
||||
for _, path := range p.Allow.WritePaths {
|
||||
desc.WriteString("\t - " + explainPath(path) + "\n")
|
||||
}
|
||||
}
|
||||
desc.WriteString("\n")
|
||||
}
|
||||
|
||||
// Add command permissions if any exist
|
||||
if len(p.Allow.CommandScopes) > 0 {
|
||||
desc.WriteString("Commands:\n")
|
||||
for _, cmd := range p.Allow.CommandScopes {
|
||||
cmdDesc := "• " + cmd.Command
|
||||
|
||||
// Format arguments
|
||||
if len(cmd.Args) > 0 {
|
||||
argsDesc := ""
|
||||
for _, arg := range cmd.Args {
|
||||
if arg.Value != "" {
|
||||
argsDesc += " " + arg.Value
|
||||
} else if arg.Validator == "$ARGS" {
|
||||
argsDesc += " [any arguments]"
|
||||
} else if arg.Validator == "$PATH" {
|
||||
argsDesc += " [any file path]"
|
||||
} else if arg.Validator != "" {
|
||||
argsDesc += " [pattern: " + arg.Validator + "]"
|
||||
}
|
||||
}
|
||||
cmdDesc += argsDesc
|
||||
}
|
||||
|
||||
// Add command description if available
|
||||
if cmd.Description != "" {
|
||||
cmdDesc += "\n\t Purpose: " + cmd.Description
|
||||
}
|
||||
|
||||
desc.WriteString(cmdDesc + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimSpace(desc.String())
|
||||
}
|
||||
|
||||
// explainPath adds human-readable descriptions to paths containing environment variables
|
||||
func explainPath(path string) string {
|
||||
environmentVars := map[string]string{
|
||||
"$SEANIME_ANIME_LIBRARY": "Your anime library directories",
|
||||
"$HOME": "Your system's Home directory",
|
||||
"$CACHE": "Your system's Cache directory",
|
||||
"$TEMP": "Your system's Temporary directory",
|
||||
"$CONFIG": "Your system's Config directory",
|
||||
"$DOWNLOAD": "Your system's Downloads directory",
|
||||
"$DESKTOP": "Your system's Desktop directory",
|
||||
"$DOCUMENT": "Your system's Documents directory",
|
||||
}
|
||||
|
||||
result := path
|
||||
|
||||
// Check if we need to add an explanation
|
||||
needsExplanation := false
|
||||
explanation := ""
|
||||
|
||||
for envVar, description := range environmentVars {
|
||||
if strings.Contains(path, envVar) {
|
||||
if explanation != "" {
|
||||
explanation += ", "
|
||||
}
|
||||
explanation += fmt.Sprintf("%s = %s", envVar, description)
|
||||
needsExplanation = true
|
||||
}
|
||||
}
|
||||
|
||||
if needsExplanation {
|
||||
result += " (" + explanation + ")"
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type PluginExtension interface {
|
||||
BaseExtension
|
||||
GetPermissionHash() string
|
||||
}
|
||||
|
||||
type PluginExtensionImpl struct {
|
||||
ext *Extension
|
||||
}
|
||||
|
||||
func NewPluginExtension(ext *Extension) PluginExtension {
|
||||
return &PluginExtensionImpl{
|
||||
ext: ext,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *PluginExtensionImpl) GetPermissionHash() string {
|
||||
if m.ext.Plugin == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return m.ext.Plugin.Permissions.GetHash()
|
||||
}
|
||||
|
||||
func (m *PluginExtensionImpl) GetExtension() *Extension {
|
||||
return m.ext
|
||||
}
|
||||
|
||||
func (m *PluginExtensionImpl) GetType() Type {
|
||||
return m.ext.Type
|
||||
}
|
||||
|
||||
func (m *PluginExtensionImpl) GetID() string {
|
||||
return m.ext.ID
|
||||
}
|
||||
|
||||
func (m *PluginExtensionImpl) GetName() string {
|
||||
return m.ext.Name
|
||||
}
|
||||
|
||||
func (m *PluginExtensionImpl) GetVersion() string {
|
||||
return m.ext.Version
|
||||
}
|
||||
|
||||
func (m *PluginExtensionImpl) GetManifestURI() string {
|
||||
return m.ext.ManifestURI
|
||||
}
|
||||
|
||||
func (m *PluginExtensionImpl) GetLanguage() Language {
|
||||
return m.ext.Language
|
||||
}
|
||||
|
||||
func (m *PluginExtensionImpl) GetLang() string {
|
||||
return GetExtensionLang(m.ext.Lang)
|
||||
}
|
||||
|
||||
func (m *PluginExtensionImpl) GetDescription() string {
|
||||
return m.ext.Description
|
||||
}
|
||||
|
||||
func (m *PluginExtensionImpl) GetAuthor() string {
|
||||
return m.ext.Author
|
||||
}
|
||||
|
||||
func (m *PluginExtensionImpl) GetPayload() string {
|
||||
return m.ext.Payload
|
||||
}
|
||||
|
||||
func (m *PluginExtensionImpl) GetWebsite() string {
|
||||
return m.ext.Website
|
||||
}
|
||||
|
||||
func (m *PluginExtensionImpl) GetIcon() string {
|
||||
return m.ext.Icon
|
||||
}
|
||||
|
||||
func (m *PluginExtensionImpl) GetPermissions() []string {
|
||||
return m.ext.Permissions
|
||||
}
|
||||
|
||||
func (m *PluginExtensionImpl) GetUserConfig() *UserConfig {
|
||||
return m.ext.UserConfig
|
||||
}
|
||||
|
||||
func (m *PluginExtensionImpl) GetSavedUserConfig() *SavedUserConfig {
|
||||
return m.ext.SavedUserConfig
|
||||
}
|
||||
|
||||
func (m *PluginExtensionImpl) GetPayloadURI() string {
|
||||
return m.ext.PayloadURI
|
||||
}
|
||||
|
||||
func (m *PluginExtensionImpl) GetIsDevelopment() bool {
|
||||
return m.ext.IsDevelopment
|
||||
}
|
||||
98
seanime-2.9.10/internal/extension/torrent_provider.go
Normal file
98
seanime-2.9.10/internal/extension/torrent_provider.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package extension
|
||||
|
||||
import (
|
||||
hibiketorrent "seanime/internal/extension/hibike/torrent"
|
||||
)
|
||||
|
||||
type AnimeTorrentProviderExtension interface {
|
||||
BaseExtension
|
||||
GetProvider() hibiketorrent.AnimeProvider
|
||||
}
|
||||
|
||||
type AnimeTorrentProviderExtensionImpl struct {
|
||||
ext *Extension
|
||||
provider hibiketorrent.AnimeProvider
|
||||
}
|
||||
|
||||
func NewAnimeTorrentProviderExtension(ext *Extension, provider hibiketorrent.AnimeProvider) AnimeTorrentProviderExtension {
|
||||
return &AnimeTorrentProviderExtensionImpl{
|
||||
ext: ext,
|
||||
provider: provider,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *AnimeTorrentProviderExtensionImpl) GetProvider() hibiketorrent.AnimeProvider {
|
||||
return m.provider
|
||||
}
|
||||
|
||||
func (m *AnimeTorrentProviderExtensionImpl) GetExtension() *Extension {
|
||||
return m.ext
|
||||
}
|
||||
|
||||
func (m *AnimeTorrentProviderExtensionImpl) GetType() Type {
|
||||
return m.ext.Type
|
||||
}
|
||||
|
||||
func (m *AnimeTorrentProviderExtensionImpl) GetID() string {
|
||||
return m.ext.ID
|
||||
}
|
||||
|
||||
func (m *AnimeTorrentProviderExtensionImpl) GetName() string {
|
||||
return m.ext.Name
|
||||
}
|
||||
|
||||
func (m *AnimeTorrentProviderExtensionImpl) GetVersion() string {
|
||||
return m.ext.Version
|
||||
}
|
||||
|
||||
func (m *AnimeTorrentProviderExtensionImpl) GetManifestURI() string {
|
||||
return m.ext.ManifestURI
|
||||
}
|
||||
|
||||
func (m *AnimeTorrentProviderExtensionImpl) GetLanguage() Language {
|
||||
return m.ext.Language
|
||||
}
|
||||
|
||||
func (m *AnimeTorrentProviderExtensionImpl) GetLang() string {
|
||||
return GetExtensionLang(m.ext.Lang)
|
||||
}
|
||||
|
||||
func (m *AnimeTorrentProviderExtensionImpl) GetDescription() string {
|
||||
return m.ext.Description
|
||||
}
|
||||
|
||||
func (m *AnimeTorrentProviderExtensionImpl) GetAuthor() string {
|
||||
return m.ext.Author
|
||||
}
|
||||
|
||||
func (m *AnimeTorrentProviderExtensionImpl) GetPayload() string {
|
||||
return m.ext.Payload
|
||||
}
|
||||
|
||||
func (m *AnimeTorrentProviderExtensionImpl) GetWebsite() string {
|
||||
return m.ext.Website
|
||||
}
|
||||
|
||||
func (m *AnimeTorrentProviderExtensionImpl) GetIcon() string {
|
||||
return m.ext.Icon
|
||||
}
|
||||
|
||||
func (m *AnimeTorrentProviderExtensionImpl) GetPermissions() []string {
|
||||
return m.ext.Permissions
|
||||
}
|
||||
|
||||
func (m *AnimeTorrentProviderExtensionImpl) GetUserConfig() *UserConfig {
|
||||
return m.ext.UserConfig
|
||||
}
|
||||
|
||||
func (m *AnimeTorrentProviderExtensionImpl) GetSavedUserConfig() *SavedUserConfig {
|
||||
return m.ext.SavedUserConfig
|
||||
}
|
||||
|
||||
func (m *AnimeTorrentProviderExtensionImpl) GetPayloadURI() string {
|
||||
return m.ext.PayloadURI
|
||||
}
|
||||
|
||||
func (m *AnimeTorrentProviderExtensionImpl) GetIsDevelopment() bool {
|
||||
return m.ext.IsDevelopment
|
||||
}
|
||||
Reference in New Issue
Block a user