diff --git a/fastanime/cli/commands/download.py b/fastanime/cli/commands/download.py index 801dc3d..667b202 100644 --- a/fastanime/cli/commands/download.py +++ b/fastanime/cli/commands/download.py @@ -13,7 +13,6 @@ if TYPE_CHECKING: from typing_extensions import Unpack - from ...libs.players.base import BasePlayer from ...libs.providers.anime.base import BaseAnimeProvider from ...libs.providers.anime.types import Anime from ...libs.selectors.base import BaseSelector @@ -116,7 +115,6 @@ def download(config: AppConfig, **options: "Unpack[Options]"): from ...libs.selectors.selector import create_selector provider = create_provider(config.general.provider) - player = create_player(config) selector = create_selector(config) anime_titles = options["anime_title"] @@ -149,7 +147,7 @@ def download(config: AppConfig, **options: "Unpack[Options]"): # ---- fetch selected anime ---- with Progress() as progress: progress.add_task("Fetching Anime...", total=None) - anime = provider.get(AnimeParams(id=anime_result.id)) + anime = provider.get(AnimeParams(id=anime_result.id, query=anime_title)) if not anime: raise FastAnimeError(f"Failed to fetch anime {anime_result.title}") @@ -184,7 +182,13 @@ def download(config: AppConfig, **options: "Unpack[Options]"): for episode in episodes_range: download_anime( - config, options, provider, selector, player, anime, episode + config, + options, + provider, + selector, + anime, + episode, + anime_title, ) else: episode = selector.choose( @@ -193,7 +197,9 @@ def download(config: AppConfig, **options: "Unpack[Options]"): ) if not episode: raise FastAnimeError("No episode selected") - download_anime(config, options, provider, selector, player, anime, episode) + download_anime( + config, options, provider, selector, anime, episode, anime_title + ) def download_anime( @@ -201,15 +207,14 @@ def download_anime( download_options: "Options", provider: "BaseAnimeProvider", selector: "BaseSelector", - player: "BasePlayer", anime: "Anime", episode: str, + anime_title: str, ): from rich import print from rich.progress import Progress from ...core.downloader import DownloadParams, create_downloader - from ...libs.players.params import PlayerParams from ...libs.providers.anime.params import EpisodeStreamsParams downloader = create_downloader(config.downloads) @@ -219,6 +224,7 @@ def download_anime( streams = provider.episode_streams( EpisodeStreamsParams( anime_id=anime.id, + query=anime_title, episode=episode, translation_type=config.stream.translation_type, ) diff --git a/fastanime/cli/commands/search.py b/fastanime/cli/commands/search.py index d1bd596..bf257d2 100644 --- a/fastanime/cli/commands/search.py +++ b/fastanime/cli/commands/search.py @@ -46,7 +46,6 @@ def search(config: AppConfig, **options: "Unpack[Options]"): from rich.progress import Progress from ...core.exceptions import FastAnimeError - from ...libs.players.player import create_player from ...libs.providers.anime.params import ( AnimeParams, SearchParams, @@ -55,7 +54,6 @@ def search(config: AppConfig, **options: "Unpack[Options]"): from ...libs.selectors.selector import create_selector provider = create_provider(config.general.provider) - player = create_player(config) selector = create_selector(config) anime_titles = options["anime_title"] @@ -88,7 +86,7 @@ def search(config: AppConfig, **options: "Unpack[Options]"): # ---- fetch selected anime ---- with Progress() as progress: progress.add_task("Fetching Anime...", total=None) - anime = provider.get(AnimeParams(id=anime_result.id)) + anime = provider.get(AnimeParams(id=anime_result.id, query=anime_title)) if not anime: raise FastAnimeError(f"Failed to fetch anime {anime_result.title}") @@ -122,7 +120,7 @@ def search(config: AppConfig, **options: "Unpack[Options]"): episodes_range = iter(episodes_range) for episode in episodes_range: - stream_anime(config, provider, selector, player, anime, episode) + stream_anime(config, provider, selector, anime, episode, anime_title) else: episode = selector.choose( "Select Episode", @@ -130,28 +128,32 @@ def search(config: AppConfig, **options: "Unpack[Options]"): ) if not episode: raise FastAnimeError("No episode selected") - stream_anime(config, provider, selector, player, anime, episode) + stream_anime(config, provider, selector, anime, episode, anime_title) def stream_anime( config: AppConfig, provider: "BaseAnimeProvider", selector: "BaseSelector", - player: "BasePlayer", anime: "Anime", episode: str, + anime_title: str, ): from rich import print from rich.progress import Progress from ...libs.players.params import PlayerParams + from ...libs.players.player import create_player from ...libs.providers.anime.params import EpisodeStreamsParams + player = create_player(config) + with Progress() as progress: progress.add_task("Fetching Episode Streams...", total=None) streams = provider.episode_streams( EpisodeStreamsParams( anime_id=anime.id, + query=anime_title, episode=episode, translation_type=config.stream.translation_type, ) diff --git a/fastanime/cli/interactive/menus/provider_search.py b/fastanime/cli/interactive/menus/provider_search.py index 1db5c91..26886ae 100644 --- a/fastanime/cli/interactive/menus/provider_search.py +++ b/fastanime/cli/interactive/menus/provider_search.py @@ -80,7 +80,9 @@ def provider_search(ctx: Context, state: State) -> State | ControlFlow: ) from ....libs.providers.anime.params import AnimeParams - full_provider_anime = provider.get(AnimeParams(id=selected_provider_anime.id)) + full_provider_anime = provider.get( + AnimeParams(id=selected_provider_anime.id, query=anilist_title.lower()) + ) if not full_provider_anime: feedback.warning( diff --git a/fastanime/cli/interactive/menus/servers.py b/fastanime/cli/interactive/menus/servers.py index 3cf70f0..63bd6a4 100644 --- a/fastanime/cli/interactive/menus/servers.py +++ b/fastanime/cli/interactive/menus/servers.py @@ -25,6 +25,11 @@ def servers(ctx: Context, state: State) -> State | ControlFlow: then launches the media player and transitions to post-playback controls. """ provider_anime = state.provider.anime + if not state.media_api.anime: + return ControlFlow.BACK + anime_title = ( + state.media_api.anime.title.romaji or state.media_api.anime.title.romaji + ) episode_number = state.provider.episode_number config = ctx.config provider = ctx.provider @@ -47,6 +52,7 @@ def servers(ctx: Context, state: State) -> State | ControlFlow: server_iterator = provider.episode_streams( EpisodeStreamsParams( anime_id=provider_anime.id, + query=anime_title, episode=episode_number, translation_type=config.stream.translation_type, ) diff --git a/fastanime/libs/providers/anime/animepahe/constants.py b/fastanime/libs/providers/anime/animepahe/constants.py index 0be1dee..af8cf49 100644 --- a/fastanime/libs/providers/anime/animepahe/constants.py +++ b/fastanime/libs/providers/anime/animepahe/constants.py @@ -6,9 +6,9 @@ ANIMEPAHE_ENDPOINT = f"{ANIMEPAHE_BASE}/api" SERVERS_AVAILABLE = ["kwik"] REQUEST_HEADERS = { - "Cookie": "__ddgid_=VvX0ebHrH2DsFZo4; __ddgmark_=3savRpSVFhvZcn5x; __ddg2_=buBJ3c4pNBYKFZNp; __ddg1_=rbVADKr9URtt55zoIGFa; SERVERID=janna; XSRF-TOKEN=eyJpdiI6IjV5bFNtd0phUHgvWGJxc25wL0VJSUE9PSIsInZhbHVlIjoicEJTZktlR2hxR2JZTWhnL0JzazlvZU5TQTR2bjBWZ2dDb0RwUXVUUWNSclhQWUhLRStYSmJmWmUxWkpiYkFRYU12RjFWejlSWHorME1wZG5qQ1U0TnFlNnBFR2laQjN1MjdyNjc5TjVPdXdJb2o5VkU1bEduRW9pRHNDTHh6Sy8iLCJtYWMiOiI0OTc0ZmNjY2UwMGJkOWY2MWNkM2NlMjk2ZGMyZGJmMWE0NTdjZTdkNGI2Y2IwNTIzZmFiZWU5ZTE2OTk0YmU4IiwidGFnIjoiIn0%3D; laravel_session=eyJpdiI6ImxvdlpqREFnTjdaeFJubUlXQWlJVWc9PSIsInZhbHVlIjoiQnE4R3VHdjZ4M1NDdEVWM1ZqMUxtNnVERnJCcmtCUHZKNzRPR2RFbzNFcStTL29xdnVTbWhsNVRBUXEybVZWNU1UYVlTazFqYlN5UjJva1k4czNGaXBTbkJJK01oTUd3VHRYVHBoc3dGUWxHYnFlS2NJVVNFbTFqMVBWdFpuVUgiLCJtYWMiOiI1NDdjZTVkYmNhNjUwZTMxZmRlZmVmMmRlMGNiYjAwYjlmYjFjY2U0MDc1YTQzZThiMTIxMjJlYTg1NTA4YjBmIiwidGFnIjoiIn0%3D; latest=5592 ", + "Cookie": "__ddgid_=VvX0ebHrH2DsFZo4; __ddgmark_=3savRpSVFhvZcn5x; __ddg2_=buBJ3c4pNBYKFZNp; __ddg1_=rbVADKr9URtt55zoIGFa; SERVERID=janna; XSRF-TOKEN=eyJpdiI6IjV5bFNtd0phUHgvWGJxc25wL0VJSUE9PSIsInZhbHVlIjoicEJTZktlR2hxR2JZTWhnL0JzazlvZU5TQTR2bjBWZ2dDb0RwUXVUUWNSclhQWUhLRStYSmJmWmUxWkpiYkFRYU12RjFWejlSWHorME1wZG5qQ1U0TnFlNnBFR2laQjN1MjdyNjc5TjVPdXdJb2o5VkU1bEduRW9pRHNDTHh6Sy8iLCJtYWMiOiI0OTc0ZmNjY2UwMGJkOWY2MWNkM2NlMjk2ZGMyZGJmMWE0NTdjZTdkNGI2Y2IwNTIzZmFiZWU5ZTE2OTk0YmU4IiwidGFnIjoiIn0%3D; laravel_session=eyJpdiI6ImxvdlpqREFnTjdaeFJubUlXQWlJVWc9PSIsInZhbHVlIjoiQnE4R3VHdjZ4M1NDdEVWM1ZqMUxtNnVERnJCcmtCUHZKNzRPR2RFbzNFcStTL29xdnVTbWhsNVRBUXEybVZWNU1UYVlTazFqYlN5UjJva1k4czNGaXBTbkJJK01oTUd3VHRYVHBoc3dGUWxHYnFlS2NJVVNFbTFqMVBWdFpuVUgiLCJtYWMiOiI1NDdjZTVkYmNhNjUwZTMxZmRlZmVmMmRlMGNiYjAwYjlmYjFjY2U0MDc1YTQzZThiMTIxMjJlYTg1NTA4YjBmIiwidGFnIjoiIn0%3D; latest=5592", "Host": ANIMEPAHE, - "Accept": "application , text/javascript, */*; q=0.01", + "Accept": "application, text/javascript, */*; q=0.01", "Accept-Encoding": "Utf-8", "Referer": ANIMEPAHE_BASE, "DNT": "1", diff --git a/fastanime/libs/providers/anime/animepahe/parser.py b/fastanime/libs/providers/anime/animepahe/parser.py index 66b44a8..3e918af 100644 --- a/fastanime/libs/providers/anime/animepahe/parser.py +++ b/fastanime/libs/providers/anime/animepahe/parser.py @@ -1,7 +1,25 @@ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any -from ..types import Anime, AnimeEpisodes, AnimeEpisodeInfo, PageInfo, SearchResult, SearchResults, Server, EpisodeStream, Subtitle -from .types import AnimePaheAnimePage, AnimePaheSearchResult, AnimePaheSearchPage, AnimePaheServer, AnimePaheEpisodeInfo, AnimePaheAnime, AnimePaheStreamLink +from ..types import ( + Anime, + AnimeEpisodeInfo, + AnimeEpisodes, + EpisodeStream, + PageInfo, + SearchResult, + SearchResults, + Server, + Subtitle, +) +from .types import ( + AnimePaheAnime, + AnimePaheAnimePage, + AnimePaheEpisodeInfo, + AnimePaheSearchPage, + AnimePaheSearchResult, + AnimePaheServer, + AnimePaheStreamLink, +) def map_to_search_results(data: AnimePaheSearchPage) -> SearchResults: @@ -21,6 +39,7 @@ def map_to_search_results(data: AnimePaheSearchPage) -> SearchResults: status=result["status"], season=result["season"], poster=result["poster"], + year=str(result["year"]), ) ) @@ -34,47 +53,45 @@ def map_to_search_results(data: AnimePaheSearchPage) -> SearchResults: ) -def map_to_anime_result(data: AnimePaheAnime) -> Anime: +def map_to_anime_result( + search_result: SearchResult, anime: AnimePaheAnimePage +) -> Anime: episodes_info = [] - for ep_info in data["episodesInfo"]: + episodes = [] + for ep_info in anime["data"]: + episodes.append(str(ep_info["episode"])) episodes_info.append( AnimeEpisodeInfo( - id=ep_info["id"], + id=str(ep_info["id"]), + session_id=ep_info["session"], episode=str(ep_info["episode"]), title=ep_info["title"], - poster=ep_info["poster"], - duration=ep_info["duration"], + poster=ep_info["snapshot"], + duration=str(ep_info["duration"]), ) ) return Anime( - id=data["id"], - title=data["title"], + id=search_result.id, + title=search_result.title, episodes=AnimeEpisodes( - sub=data["availableEpisodesDetail"]["sub"], - dub=data["availableEpisodesDetail"]["dub"], - raw=data["availableEpisodesDetail"]["raw"], + sub=episodes, + dub=episodes, ), - year=str(data["year"]), - poster=data["poster"], + year=str(search_result.year), + poster=search_result.poster, episodes_info=episodes_info, ) -def map_to_server(data: AnimePaheServer) -> Server: - links = [] - for link in data["links"]: - links.append( - EpisodeStream( - link=link["link"], - quality=link["quality"], - translation_type=link["translation_type"], - ) +def map_to_server( + episode: AnimeEpisodeInfo, translation_type: Any, quality: Any, stream_link: Any +) -> Server: + links = [ + EpisodeStream( + link=stream_link, + quality=quality, + translation_type=translation_type, ) - return Server( - name=data["server"], - links=links, - episode_title=data["episode_title"], - subtitles=data["subtitles"], - headers=data["headers"], - ) + ] + return Server(name="kwik", links=links, episode_title=episode.title) diff --git a/fastanime/libs/providers/anime/animepahe/provider.py b/fastanime/libs/providers/anime/animepahe/provider.py index 7796398..6c39848 100644 --- a/fastanime/libs/providers/anime/animepahe/provider.py +++ b/fastanime/libs/providers/anime/animepahe/provider.py @@ -1,7 +1,8 @@ import logging import random import time -from typing import TYPE_CHECKING +from functools import lru_cache +from typing import TYPE_CHECKING, Iterator, Optional, Union from yt_dlp.utils import ( extract_attributes, @@ -11,7 +12,7 @@ from yt_dlp.utils import ( from ..base import BaseAnimeProvider from ..params import AnimeParams, EpisodeStreamsParams, SearchParams -from ..types import Anime, SearchResults, Server +from ..types import Anime, AnimeEpisodeInfo, SearchResult, SearchResults, Server from ..utils.debug import debug_provider from .constants import ( ANIMEPAHE_BASE, @@ -21,7 +22,7 @@ from .constants import ( SERVER_HEADERS, ) from .extractors import process_animepahe_embed_page -from .parser import map_to_anime_result, map_to_server, map_to_search_results +from .parser import map_to_anime_result, map_to_search_results, map_to_server from .types import AnimePaheAnimePage, AnimePaheSearchPage, AnimePaheSearchResult logger = logging.getLogger(__name__) @@ -32,62 +33,53 @@ class AnimePahe(BaseAnimeProvider): @debug_provider def search(self, params: SearchParams) -> SearchResults | None: - response = self.client.get( - ANIMEPAHE_ENDPOINT, params={"m": "search", "q": params.query} - ) + return self._search(params) + + @lru_cache() + def _search(self, params: SearchParams) -> SearchResults | None: + url_params = {"m": "search", "q": params.query} + response = self.client.get(ANIMEPAHE_ENDPOINT, params=url_params) response.raise_for_status() data: AnimePaheSearchPage = response.json() + if not data.get("data"): + return return map_to_search_results(data) @debug_provider def get(self, params: AnimeParams) -> Anime | None: + return self._get_anime(params) + + @lru_cache() + def _get_anime(self, params: AnimeParams) -> Anime | None: page = 1 standardized_episode_number = 0 - anime_result: AnimePaheSearchResult = self.search(SearchParams(query=params.id)).results[0] - data: AnimePaheAnimePage = {} # pyright:ignore - def _pages_loader( - self, - data, - session_id, - params, - page, - standardized_episode_number, - ): - response = self.client.get(ANIMEPAHE_ENDPOINT, params=params) - response.raise_for_status() - if not data: - data.update(response.json()) - elif ep_data := response.json().get("data"): - data["data"].extend(ep_data) - if response.json()["next_page_url"]: - # TODO: Refine this - time.sleep( - random.choice( - [ - 0.25, - 0.1, - 0.5, - 0.75, - 1, - ] - ) - ) - page += 1 - self._pages_loader( - data, - session_id, - params={ - "m": "release", - "page": page, - "id": session_id, - "sort": "episode_asc", - }, + search_result = self._get_search_result(params) + if not search_result: + logger.error(f"No search result found for ID {params.id}") + return None + + anime: Optional[AnimePaheAnimePage] = None + + has_next_page = True + while has_next_page: + logger.debug(f"Loading page: {page}") + _anime_page = self._anime_page_loader( + m="release", + id=params.id, + sort="episode_asc", page=page, - standardized_episode_number=standardized_episode_number, ) - else: - for episode in data.get("data", []): + + has_next_page = True if _anime_page["next_page_url"] else False + page += 1 + if not anime: + anime = _anime_page + else: + anime["data"].extend(_anime_page["data"]) + + if anime: + for episode in anime.get("data", []): if episode["episode"] % 1 == 0: standardized_episode_number += 1 episode.update({"episode": standardized_episode_number}) @@ -95,103 +87,52 @@ class AnimePahe(BaseAnimeProvider): standardized_episode_number += episode["episode"] % 1 episode.update({"episode": standardized_episode_number}) standardized_episode_number = int(standardized_episode_number) - return data - @debug_provider - def get(self, params: AnimeParams) -> Anime | None: - page = 1 - standardized_episode_number = 0 - search_results = self.search(SearchParams(query=params.id)) + return map_to_anime_result(search_result, anime) + + @lru_cache() + def _get_search_result(self, params: AnimeParams) -> Optional[SearchResult]: + search_results = self._search(SearchParams(query=params.query)) if not search_results or not search_results.results: - logger.error(f"[ANIMEPAHE-ERROR]: No search results found for ID {params.id}") + logger.error(f"No search results found for ID {params.id}") return None - anime_result: AnimePaheSearchResult = search_results.results[0] + for search_result in search_results.results: + if search_result.id == params.id: + return search_result - data: AnimePaheAnimePage = {} # pyright:ignore - - data = self._pages_loader( - data, - params.id, - params={ - "m": "release", - "id": params.id, - "sort": "episode_asc", - "page": page, - }, - page=page, - standardized_episode_number=standardized_episode_number, - ) - - if not data: - return None - - # Construct AnimePaheAnime TypedDict for mapping - anime_pahe_anime_data = { - "id": params.id, - "title": anime_result.title, - "year": anime_result.year, - "season": anime_result.season, - "poster": anime_result.poster, - "score": anime_result.score, - "availableEpisodesDetail": { - "sub": list(map(str, [episode["episode"] for episode in data["data"]])), - "dub": list(map(str, [episode["episode"] for episode in data["data"]])), - "raw": list(map(str, [episode["episode"] for episode in data["data"]])), - }, - "episodesInfo": [ - { - "title": episode["title"], - "episode": episode["episode"], - "id": episode["session"], - "translation_type": episode["audio"], - "duration": episode["duration"], - "poster": episode["snapshot"], - } - for episode in data["data"] - ], + @lru_cache() + def _anime_page_loader(self, m, id, sort, page) -> AnimePaheAnimePage: + url_params = { + "m": m, + "id": id, + "sort": sort, + "page": page, } - return map_to_anime_result(anime_pahe_anime_data) + response = self.client.get(ANIMEPAHE_ENDPOINT, params=url_params) + response.raise_for_status() + return response.json() @debug_provider - def episode_streams(self, params: EpisodeStreamsParams) -> "Iterator[Server] | None": - anime_info = self.get(AnimeParams(id=params.anime_id)) - if not anime_info: - logger.error( - f"[ANIMEPAHE-ERROR]: Anime with ID {params.anime_id} not found" - ) - return - - episode = next( - ( - ep - for ep in anime_info.episodes_info - if float(ep.episode) == float(params.episode) - ), - None, - ) - + def episode_streams(self, params: EpisodeStreamsParams) -> Iterator[Server] | None: + episode = self._get_episode_info(params) if not episode: logger.error( - f"[ANIMEPAHE-ERROR]: Episode {params.episode} doesn't exist for anime {anime_info.title}" + f"Episode {params.episode} doesn't exist for anime {params.anime_id}" ) return - url = f"{ANIMEPAHE_BASE}/play/{params.anime_id}/{episode.id}" - response = self.client.get(url) + url = f"{ANIMEPAHE_BASE}/play/{params.anime_id}/{episode.session_id}" + response = self.client.get(url, follow_redirects=True) response.raise_for_status() c = get_element_by_id("resolutionMenu", response.text) resolutionMenuItems = get_elements_html_by_class("dropdown-item", c) res_dicts = [extract_attributes(item) for item in resolutionMenuItems] + quality = None + translation_type = None + stream_link = None - streams = { - "server": "kwik", - "links": [], - "episode_title": f"{episode.title or anime_info.title}; Episode {episode.episode}", - "subtitles": [], - "headers": {}, - } - + # TODO: better document the scraping process for res_dict in res_dicts: embed_url = res_dict["data-src"] data_audio = "dub" if res_dict["data-audio"] == "eng" else "sub" @@ -200,40 +141,54 @@ class AnimePahe(BaseAnimeProvider): continue if not embed_url: - logger.warning( - "[ANIMEPAHE-WARN]: embed url not found please report to the developers" - ) + logger.warning("embed url not found please report to the developers") continue embed_response = self.client.get( - embed_url, headers={"User-Agent": self.client.headers["User-Agent"], **SERVER_HEADERS} + embed_url, + headers={ + "User-Agent": self.client.headers["User-Agent"], + **SERVER_HEADERS, + }, ) embed_response.raise_for_status() embed_page = embed_response.text decoded_js = process_animepahe_embed_page(embed_page) if not decoded_js: - logger.error("[ANIMEPAHE-ERROR]: failed to decode embed page") + logger.error("failed to decode embed page") continue juicy_stream = JUICY_STREAM_REGEX.search(decoded_js) if not juicy_stream: - logger.error("[ANIMEPAHE-ERROR]: failed to find juicy stream") + logger.error("failed to find juicy stream") continue juicy_stream = juicy_stream.group(1) + quality = res_dict["data-resolution"] + translation_type = data_audio + stream_link = juicy_stream - streams["links"].append( - { - "quality": res_dict["data-resolution"], - "translation_type": data_audio, - "link": juicy_stream, - } - ) - if streams["links"]: - yield map_to_server(streams) + if translation_type and quality and stream_link: + yield map_to_server(episode, translation_type, quality, stream_link) + + @lru_cache() + def _get_episode_info( + self, params: EpisodeStreamsParams + ) -> Optional[AnimeEpisodeInfo]: + anime_info = self._get_anime( + AnimeParams(id=params.anime_id, query=params.query) + ) + if not anime_info: + logger.error(f"No anime info for {params.anime_id}") + return + if not anime_info.episodes_info: + logger.error(f"No episodes info for {params.anime_id}") + return + for episode in anime_info.episodes_info: + if episode.episode == params.episode: + return episode if __name__ == "__main__": - from httpx import Client from ..utils.debug import test_anime_provider - test_anime_provider(AnimePahe, Client()) + test_anime_provider(AnimePahe) diff --git a/fastanime/libs/providers/anime/params.py b/fastanime/libs/providers/anime/params.py index fe7a03d..d59ec49 100644 --- a/fastanime/libs/providers/anime/params.py +++ b/fastanime/libs/providers/anime/params.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from typing import Literal -@dataclass +@dataclass(frozen=True) class SearchParams: """Parameters for searching anime.""" @@ -24,10 +24,11 @@ class SearchParams: country_of_origin: str | None = None -@dataclass +@dataclass(frozen=True) class EpisodeStreamsParams: """Parameters for fetching episode streams.""" + query: str anime_id: str episode: str translation_type: Literal["sub", "dub"] = "sub" @@ -36,8 +37,10 @@ class EpisodeStreamsParams: subtitles: bool = True -@dataclass +@dataclass(frozen=True) class AnimeParams: """Parameters for fetching anime details.""" id: str + # HACK: for the sake of providers which require previous data + query: str diff --git a/fastanime/libs/providers/anime/types.py b/fastanime/libs/providers/anime/types.py index ffda0bc..635a861 100644 --- a/fastanime/libs/providers/anime/types.py +++ b/fastanime/libs/providers/anime/types.py @@ -1,4 +1,4 @@ -from typing import Literal +from typing import Literal, Optional from pydantic import BaseModel @@ -25,20 +25,23 @@ class SearchResult(BaseAnimeProviderModel): episodes: AnimeEpisodes other_titles: list[str] = [] media_type: str | None = None - score: int | None = None + score: float | None = None status: str | None = None season: str | None = None poster: str | None = None + year: str | None = None class SearchResults(BaseAnimeProviderModel): page_info: PageInfo results: list[SearchResult] + model_config = {"frozen": True} class AnimeEpisodeInfo(BaseAnimeProviderModel): id: str episode: str + session_id: Optional[str] = None title: str | None = None poster: str | None = None duration: str | None = None diff --git a/fastanime/libs/providers/anime/utils/debug.py b/fastanime/libs/providers/anime/utils/debug.py index 7e2938b..5e42263 100644 --- a/fastanime/libs/providers/anime/utils/debug.py +++ b/fastanime/libs/providers/anime/utils/debug.py @@ -46,7 +46,7 @@ def test_anime_provider(AnimeProvider: Type[BaseAnimeProvider]): result = search_results.results[ int(input(f"Select result (1-{len(search_results.results)}): ")) - 1 ] - anime = anime_provider.get(AnimeParams(id=result.id)) + anime = anime_provider.get(AnimeParams(id=result.id, query=query)) if not anime: return @@ -56,6 +56,7 @@ def test_anime_provider(AnimeProvider: Type[BaseAnimeProvider]): episode_number = input("What episode do you wish to watch: ") episode_streams = anime_provider.episode_streams( EpisodeStreamsParams( + query=query, anime_id=anime.id, episode=episode_number, translation_type=translation_type, # type:ignore