mirror of
https://github.com/Benexl/FastAnime.git
synced 2026-02-04 11:07:48 -08:00
feat: mass refactor
This commit is contained in:
@@ -12,15 +12,15 @@ from ...core.constants import (
|
||||
ROFI_THEME_PREVIEW,
|
||||
)
|
||||
from ...libs.anilist.constants import SORTS_AVAILABLE
|
||||
from ...libs.anime_provider import PROVIDERS_AVAILABLE, SERVERS_AVAILABLE
|
||||
from ...libs.providers.anime import PROVIDERS_AVAILABLE, SERVERS_AVAILABLE
|
||||
from ..constants import APP_ASCII_ART, USER_VIDEOS_DIR
|
||||
|
||||
|
||||
class External(BaseModel):
|
||||
class OtherConfig(BaseModel):
|
||||
pass
|
||||
|
||||
|
||||
class FzfConfig(External):
|
||||
class FzfConfig(OtherConfig):
|
||||
"""Configuration specific to the FZF selector."""
|
||||
|
||||
opts: str = Field(
|
||||
@@ -48,7 +48,7 @@ class FzfConfig(External):
|
||||
)
|
||||
|
||||
|
||||
class RofiConfig(External):
|
||||
class RofiConfig(OtherConfig):
|
||||
"""Configuration specific to the Rofi selector."""
|
||||
|
||||
theme_main: Path = Field(
|
||||
@@ -69,7 +69,7 @@ class RofiConfig(External):
|
||||
)
|
||||
|
||||
|
||||
class MpvConfig(External):
|
||||
class MpvConfig(OtherConfig):
|
||||
"""Configuration specific to the MPV player integration."""
|
||||
|
||||
args: str = Field(
|
||||
@@ -92,7 +92,7 @@ class MpvConfig(External):
|
||||
)
|
||||
|
||||
|
||||
class AnilistConfig(External):
|
||||
class AnilistConfig(OtherConfig):
|
||||
"""Configuration for interacting with the AniList API."""
|
||||
|
||||
per_page: int = Field(
|
||||
@@ -182,10 +182,10 @@ class GeneralConfig(BaseModel):
|
||||
|
||||
@field_validator("provider")
|
||||
@classmethod
|
||||
def validate_server(cls, v: str) -> str:
|
||||
if v.lower() != "top" and v not in PROVIDERS_AVAILABLE:
|
||||
def validate_provider(cls, v: str) -> str:
|
||||
if v not in PROVIDERS_AVAILABLE:
|
||||
raise ValueError(
|
||||
f"'{v}' is not a valid server. Must be 'top' or one of: {PROVIDERS_AVAILABLE}"
|
||||
f"'{v}' is not a valid provider. Must be one of: {PROVIDERS_AVAILABLE}"
|
||||
)
|
||||
return v
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal, get_origin, get_args
|
||||
from typing import Any, Literal, get_args, get_origin
|
||||
|
||||
import click
|
||||
from pydantic import BaseModel
|
||||
from pydantic.fields import FieldInfo
|
||||
from pydantic_core import PydanticUndefined
|
||||
|
||||
from .config.model import External
|
||||
from .config.model import OtherConfig
|
||||
|
||||
# Mapping from Python/Pydantic types to Click types
|
||||
TYPE_MAP = {
|
||||
@@ -50,7 +50,7 @@ def options_from_model(model: type[BaseModel], parent_name: str = "") -> Callabl
|
||||
decorators = []
|
||||
|
||||
# Check if this model inherits from ExternalTool
|
||||
is_external_tool = issubclass(model, External)
|
||||
is_external_tool = issubclass(model, OtherConfig)
|
||||
model_name = model.__name__.lower().replace("config", "")
|
||||
|
||||
# Introspect the model's fields
|
||||
|
||||
0
fastanime/cli/selectors/inquirer/__init__.py
Normal file
0
fastanime/cli/selectors/inquirer/__init__.py
Normal file
0
fastanime/cli/selectors/inquirer/selector.py
Normal file
0
fastanime/cli/selectors/inquirer/selector.py
Normal file
0
fastanime/cli/selectors/selector.py
Normal file
0
fastanime/cli/selectors/selector.py
Normal file
0
fastanime/libs/players/__init__.py
Normal file
0
fastanime/libs/players/__init__.py
Normal file
0
fastanime/libs/players/base.py
Normal file
0
fastanime/libs/players/base.py
Normal file
0
fastanime/libs/players/mpv/__init__.py
Normal file
0
fastanime/libs/players/mpv/__init__.py
Normal file
0
fastanime/libs/players/syncplay/__init__.py
Normal file
0
fastanime/libs/players/syncplay/__init__.py
Normal file
0
fastanime/libs/players/vlc/__init__.py
Normal file
0
fastanime/libs/players/vlc/__init__.py
Normal file
0
fastanime/libs/players/vlc/player.py
Normal file
0
fastanime/libs/players/vlc/player.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from fastanime.libs.anime_provider.allanime.parser import (
|
||||
map_to_anime_result,
|
||||
map_to_search_results,
|
||||
)
|
||||
|
||||
from ....core.utils.graphql import execute_graphql_query
|
||||
from .....core.utils.graphql import execute_graphql_query
|
||||
from ..base import AnimeProvider
|
||||
from ..utils.decorators import debug_provider
|
||||
from .constants import (
|
||||
@@ -18,6 +13,10 @@ from .constants import (
|
||||
SEARCH_GQL,
|
||||
)
|
||||
from .extractors import extract_server
|
||||
from .parser import (
|
||||
map_to_anime_result,
|
||||
map_to_search_results,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .types import AllAnimeEpisode
|
||||
@@ -25,7 +24,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AllAnime(AnimeProvider):
|
||||
DEFAULT_HEADERS = {"Referer": API_GRAPHQL_REFERER}
|
||||
HEADERS = {"Referer": API_GRAPHQL_REFERER}
|
||||
|
||||
@debug_provider
|
||||
def search_for_anime(self, params):
|
||||
@@ -1,8 +1,9 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
from typing import TYPE_CHECKING, ClassVar, Dict
|
||||
|
||||
from httpx import AsyncClient, Client
|
||||
from httpx import Client
|
||||
|
||||
from .params import AnimeParams, EpisodeStreamsParams, SearchParams
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
@@ -10,61 +11,29 @@ if TYPE_CHECKING:
|
||||
from .types import Anime, SearchResults, Server
|
||||
|
||||
|
||||
@dataclass
|
||||
class SearchParams:
|
||||
"""Parameters for searching anime."""
|
||||
|
||||
query: str
|
||||
|
||||
# pagination and sorting
|
||||
current_page: int = 1
|
||||
page_limit: int = 20
|
||||
sort_by: str = "relevance"
|
||||
order: Literal["asc", "desc"] = "desc"
|
||||
|
||||
# filters
|
||||
translation_type: Literal["sub", "dub"] = "sub"
|
||||
genre: str | None = None
|
||||
year: int | None = None
|
||||
status: str | None = None
|
||||
allow_nsfw: bool = True
|
||||
allow_unknown: bool = True
|
||||
country_of_origin: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class EpisodeStreamsParams:
|
||||
"""Parameters for fetching episode streams."""
|
||||
|
||||
anime_id: str
|
||||
episode: str
|
||||
translation_type: Literal["sub", "dub"] = "sub"
|
||||
server: str | None = None
|
||||
quality: Literal["1080", "720", "480", "360"] = "720"
|
||||
subtitles: bool = True
|
||||
|
||||
|
||||
@dataclass
|
||||
class AnimeParams:
|
||||
"""Parameters for fetching anime details."""
|
||||
|
||||
anime_id: str
|
||||
|
||||
|
||||
class AnimeProvider(ABC):
|
||||
HEADERS: ClassVar[Dict[str, str]]
|
||||
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
super().__init_subclass__(**kwargs)
|
||||
if not hasattr(cls, "HEADERS"):
|
||||
raise TypeError(
|
||||
f"Subclasses of AnimeProvider must define a 'HEADERS' class attribute."
|
||||
)
|
||||
|
||||
def __init__(self, client: Client) -> None:
|
||||
self.client = client
|
||||
|
||||
@abstractmethod
|
||||
def search_for_anime(self, params: SearchParams) -> "SearchResults | None":
|
||||
def search(self, params: SearchParams) -> "SearchResults | None":
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_anime(self, params: AnimeParams) -> "Anime | None":
|
||||
def get(self, params: AnimeParams) -> "Anime | None":
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_episode_streams(
|
||||
def episode_streams(
|
||||
self, params: EpisodeStreamsParams
|
||||
) -> "Iterator[Server] | None":
|
||||
pass
|
||||
|
||||
43
fastanime/libs/providers/anime/params.py
Normal file
43
fastanime/libs/providers/anime/params.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
|
||||
|
||||
@dataclass
|
||||
class SearchParams:
|
||||
"""Parameters for searching anime."""
|
||||
|
||||
query: str
|
||||
|
||||
# pagination and sorting
|
||||
current_page: int = 1
|
||||
page_limit: int = 20
|
||||
sort_by: str = "relevance"
|
||||
order: Literal["asc", "desc"] = "desc"
|
||||
|
||||
# filters
|
||||
translation_type: Literal["sub", "dub"] = "sub"
|
||||
genre: str | None = None
|
||||
year: int | None = None
|
||||
status: str | None = None
|
||||
allow_nsfw: bool = True
|
||||
allow_unknown: bool = True
|
||||
country_of_origin: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class EpisodeStreamsParams:
|
||||
"""Parameters for fetching episode streams."""
|
||||
|
||||
anime_id: str
|
||||
episode: str
|
||||
translation_type: Literal["sub", "dub"] = "sub"
|
||||
server: str | None = None
|
||||
quality: Literal["1080", "720", "480", "360"] = "720"
|
||||
subtitles: bool = True
|
||||
|
||||
|
||||
@dataclass
|
||||
class AnimeParams:
|
||||
"""Parameters for fetching anime details."""
|
||||
|
||||
anime_id: str
|
||||
@@ -1,141 +1,86 @@
|
||||
"""An abstraction over all providers offering added features with a simple and well typed api"""
|
||||
|
||||
import importlib
|
||||
import logging
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from yt_dlp.utils.networking import random_user_agent
|
||||
|
||||
from .allanime.constants import SERVERS_AVAILABLE as ALLANIME_SERVERS
|
||||
from .animepahe.constants import SERVERS_AVAILABLE as ANIMEPAHE_SERVERS
|
||||
from .base import AnimeProvider as Base
|
||||
from .hianime.constants import SERVERS_AVAILABLE as HIANIME_SERVERS
|
||||
from httpx import Client, AsyncClient
|
||||
from yt_dlp.utils.networking import random_user_agent
|
||||
from .params import AnimeParams, EpisodeStreamsParams, SearchParams
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
from httpx import AsyncClient, Client
|
||||
|
||||
from .types import Anime, SearchResults, Server
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
PROVIDERS_AVAILABLE = {
|
||||
"allanime": "api.AllAnime",
|
||||
"animepahe": "api.AnimePahe",
|
||||
"hianime": "api.HiAnime",
|
||||
"nyaa": "api.Nyaa",
|
||||
"yugen": "api.Yugen",
|
||||
"allanime": "provider.AllAnime",
|
||||
"animepahe": "provider.AnimePahe",
|
||||
"hianime": "provider.HiAnime",
|
||||
"nyaa": "provider.Nyaa",
|
||||
"yugen": "provider.Yugen",
|
||||
}
|
||||
SERVERS_AVAILABLE = ["top", *ALLANIME_SERVERS, *ANIMEPAHE_SERVERS, *HIANIME_SERVERS]
|
||||
SERVERS_AVAILABLE = ["TOP", *ALLANIME_SERVERS, *ANIMEPAHE_SERVERS, *HIANIME_SERVERS]
|
||||
|
||||
|
||||
class AnimeProvider:
|
||||
"""An abstraction over all anime providers"""
|
||||
|
||||
PROVIDERS = list(PROVIDERS_AVAILABLE.keys())
|
||||
provider = PROVIDERS[0]
|
||||
current_provider_name = PROVIDERS[0]
|
||||
current_provider: Base
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
provider,
|
||||
cache_requests=os.environ.get("FASTANIME_CACHE_REQUESTS", "false"),
|
||||
use_persistent_provider_store=os.environ.get(
|
||||
"FASTANIME_USE_PERSISTENT_PROVIDER_STORE", "false"
|
||||
),
|
||||
provider: str,
|
||||
cache_requests=False,
|
||||
use_persistent_provider_store=False,
|
||||
dynamic=False,
|
||||
retries=0,
|
||||
) -> None:
|
||||
self.provider = provider
|
||||
self.current_provider_name = provider
|
||||
self.dynamic = dynamic
|
||||
self.retries = retries
|
||||
self.cache_requests = cache_requests
|
||||
self.use_persistent_provider_store = use_persistent_provider_store
|
||||
self.lazyload_provider(self.provider)
|
||||
self.lazyload(self.current_provider_name)
|
||||
|
||||
def setup_httpx_client(self) -> Client:
|
||||
def search(self, params: SearchParams) -> "SearchResults | None":
|
||||
results = self.current_provider.search(params)
|
||||
|
||||
return results
|
||||
|
||||
def get(self, params: AnimeParams) -> "Anime | None":
|
||||
results = self.current_provider.get(params)
|
||||
|
||||
return results
|
||||
|
||||
def episode_streams(
|
||||
self, params: EpisodeStreamsParams
|
||||
) -> "Iterator[Server] | None":
|
||||
results = self.current_provider.episode_streams(params)
|
||||
return results
|
||||
|
||||
def setup_httpx_client(self, headers) -> "Client":
|
||||
"""Sets up a httpx client with a random user agent"""
|
||||
client = Client(headers={"User-Agent": random_user_agent()})
|
||||
client = Client(headers={"User-Agent": random_user_agent(), **headers})
|
||||
return client
|
||||
|
||||
def setup_httpx_async_client(self) -> AsyncClient:
|
||||
def setup_httpx_async_client(self) -> "AsyncClient":
|
||||
"""Sets up a httpx client with a random user agent"""
|
||||
client = AsyncClient(headers={"User-Agent": random_user_agent()})
|
||||
return client
|
||||
|
||||
def lazyload_provider(self, provider):
|
||||
"""updates the current provider being used"""
|
||||
try:
|
||||
self.anime_provider.session.kill_connection_to_db()
|
||||
except Exception:
|
||||
pass
|
||||
def lazyload(self, provider):
|
||||
_, anime_provider_cls_name = PROVIDERS_AVAILABLE[provider].split(".", 1)
|
||||
package = f"fastanime.libs.anime_provider.{provider}"
|
||||
package = f"fastanime.libs.providers.anime.{provider}"
|
||||
provider_api = importlib.import_module(".api", package)
|
||||
anime_provider = getattr(provider_api, anime_provider_cls_name)
|
||||
self.anime_provider = anime_provider(
|
||||
self.cache_requests, self.use_persistent_provider_store
|
||||
)
|
||||
|
||||
def search_for_anime(
|
||||
self, search_keywords, translation_type, **kwargs
|
||||
) -> "SearchResults | None":
|
||||
"""core abstraction over all providers search functionality
|
||||
|
||||
Args:
|
||||
user_query ([TODO:parameter]): [TODO:description]
|
||||
translation_type ([TODO:parameter]): [TODO:description]
|
||||
nsfw ([TODO:parameter]): [TODO:description]
|
||||
unknown ([TODO:parameter]): [TODO:description]
|
||||
anilist_obj: [TODO:description]
|
||||
|
||||
Returns:
|
||||
[TODO:return]
|
||||
"""
|
||||
anime_provider = self.anime_provider
|
||||
results = anime_provider.search_for_anime(
|
||||
search_keywords, translation_type, **kwargs
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
def get_anime(
|
||||
self,
|
||||
anime_id: str,
|
||||
**kwargs,
|
||||
) -> "Anime | None":
|
||||
"""core abstraction over getting info of an anime from all providers
|
||||
|
||||
Args:
|
||||
anime_id: [TODO:description]
|
||||
anilist_obj: [TODO:description]
|
||||
|
||||
Returns:
|
||||
[TODO:return]
|
||||
"""
|
||||
anime_provider = self.anime_provider
|
||||
results = anime_provider.get_anime(anime_id, **kwargs)
|
||||
|
||||
return results
|
||||
|
||||
def get_episode_streams(
|
||||
self,
|
||||
anime_id,
|
||||
episode: str,
|
||||
translation_type: str,
|
||||
**kwargs,
|
||||
) -> "Iterator[Server] | None":
|
||||
"""core abstractions for getting juicy streams from all providers
|
||||
|
||||
Args:
|
||||
anime ([TODO:parameter]): [TODO:description]
|
||||
episode: [TODO:description]
|
||||
translation_type: [TODO:description]
|
||||
anilist_obj: [TODO:description]
|
||||
|
||||
Returns:
|
||||
[TODO:return]
|
||||
"""
|
||||
anime_provider = self.anime_provider
|
||||
results = anime_provider.get_episode_streams(
|
||||
anime_id, episode, translation_type, **kwargs
|
||||
)
|
||||
return results
|
||||
client = self.setup_httpx_client(anime_provider.HEADERS)
|
||||
self.current_provider = anime_provider(client)
|
||||
|
||||
Reference in New Issue
Block a user