mirror of
https://github.com/Benexl/FastAnime.git
synced 2025-12-27 05:03:41 -08:00
feat: animepahe provider
This commit is contained in:
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user