mirror of
https://github.com/Benexl/FastAnime.git
synced 2026-06-12 19:11:24 -07:00
fix: allanime provider
This commit is contained in:
@@ -9,6 +9,7 @@ dependencies = [
|
||||
"click>=8.1.7",
|
||||
"httpx>=0.28.1",
|
||||
"inquirerpy>=0.3.4",
|
||||
"pycryptodomex>=3.23.0",
|
||||
"pydantic>=2.11.7",
|
||||
"rich>=13.9.2",
|
||||
]
|
||||
|
||||
@@ -3816,6 +3816,7 @@ dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "httpx" },
|
||||
{ name = "inquirerpy" },
|
||||
{ name = "pycryptodomex" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "rich" },
|
||||
]
|
||||
@@ -3882,6 +3883,7 @@ requires-dist = [
|
||||
{ name = "mpv", marker = "extra == 'mpv'", specifier = ">=1.0.7" },
|
||||
{ name = "plyer", marker = "extra == 'notifications'", specifier = ">=2.1.0" },
|
||||
{ name = "plyer", marker = "extra == 'standard'", specifier = ">=2.1.0" },
|
||||
{ name = "pycryptodomex", specifier = ">=3.23.0" },
|
||||
{ name = "pycryptodomex", marker = "extra == 'download'", specifier = ">=3.23.0" },
|
||||
{ name = "pycryptodomex", marker = "extra == 'standard'", specifier = ">=3.23.0" },
|
||||
{ name = "pydantic", specifier = ">=2.11.7" },
|
||||
|
||||
@@ -19,6 +19,15 @@ API_GRAPHQL_HEADERS= {
|
||||
"Content-Type": "application/json",
|
||||
"Origin": f"{API_GRAPHQL_REFERER}",
|
||||
}
|
||||
API_EPISODE_HEADERS = {
|
||||
"Referer": "https://youtu-chan.com",
|
||||
"Origin": "https://youtu-chan.com",
|
||||
}
|
||||
|
||||
PERSISTED_QUERY_SHA256 = (
|
||||
"d405d0edd690624b66baba3068e0edc3ac90f1597d898a1ec8db4e5c43c00fec"
|
||||
)
|
||||
TOBEPARSED_DECRYPTION_SEED = "Xot36i3lK3:v1"
|
||||
|
||||
# search constants
|
||||
DEFAULT_COUNTRY_OF_ORIGIN = "all"
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
from json import dumps
|
||||
from typing import Any, Iterator
|
||||
|
||||
from .....core.utils.graphql import execute_graphql
|
||||
from ..base import BaseAnimeProvider
|
||||
from ..params import AnimeParams, EpisodeStreamsParams, SearchParams
|
||||
from ..types import Anime, SearchResults, Server
|
||||
from ..utils.debug import debug_provider
|
||||
from .constants import (
|
||||
ANIME_GQL,
|
||||
API_EPISODE_HEADERS,
|
||||
API_GRAPHQL_ENDPOINT,
|
||||
API_GRAPHQL_HEADERS,
|
||||
API_GRAPHQL_REFERER,
|
||||
EPISODE_GQL,
|
||||
PERSISTED_QUERY_SHA256,
|
||||
SEARCH_GQL,
|
||||
TOBEPARSED_DECRYPTION_SEED,
|
||||
)
|
||||
from .mappers import (
|
||||
map_to_anime_result,
|
||||
map_to_search_results,
|
||||
)
|
||||
from .types import AllAnimeEpisode
|
||||
from .utils import decode_tobeparsed
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .types import AllAnimeEpisode
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -26,7 +32,7 @@ class AllAnime(BaseAnimeProvider):
|
||||
HEADERS = {"Referer": API_GRAPHQL_REFERER}
|
||||
|
||||
@debug_provider
|
||||
def search(self, params):
|
||||
def search(self, params: SearchParams) -> SearchResults | None:
|
||||
response = execute_graphql(
|
||||
API_GRAPHQL_ENDPOINT,
|
||||
self.client,
|
||||
@@ -39,28 +45,94 @@ class AllAnime(BaseAnimeProvider):
|
||||
},
|
||||
"limit": params.page_limit,
|
||||
"page": params.current_page,
|
||||
"translationtype": params.translation_type,
|
||||
"countryorigin": params.country_of_origin,
|
||||
"translationType": params.translation_type,
|
||||
"countryOrigin": params.country_of_origin,
|
||||
},
|
||||
headers=API_GRAPHQL_HEADERS
|
||||
headers=API_GRAPHQL_HEADERS,
|
||||
)
|
||||
return map_to_search_results(response)
|
||||
|
||||
@debug_provider
|
||||
def get(self, params):
|
||||
def get(self, params: AnimeParams) -> Anime | None:
|
||||
response = execute_graphql(
|
||||
API_GRAPHQL_ENDPOINT,
|
||||
self.client,
|
||||
ANIME_GQL,
|
||||
variables={"showId": params.id},
|
||||
headers=API_GRAPHQL_HEADERS
|
||||
headers=API_GRAPHQL_HEADERS,
|
||||
)
|
||||
return map_to_anime_result(response)
|
||||
|
||||
@debug_provider
|
||||
def episode_streams(self, params):
|
||||
def episode_streams(self, params: EpisodeStreamsParams) -> Iterator[Server] | None:
|
||||
from .extractors import extract_server
|
||||
|
||||
episode = self._get_episode_payload(params)
|
||||
if not episode:
|
||||
logger.error(
|
||||
f"Could not fetch streams for episode {params.episode} ({params.translation_type})"
|
||||
)
|
||||
return
|
||||
|
||||
sources = episode.get("sourceUrls") or []
|
||||
if not sources:
|
||||
logger.error(
|
||||
f"No sources found for episode {params.episode} ({params.translation_type})"
|
||||
)
|
||||
return
|
||||
|
||||
for source in sources:
|
||||
if server := extract_server(self.client, params.episode, episode, source):
|
||||
yield server
|
||||
|
||||
def _extract_episode_from_payload(self, payload: dict[str, Any]) -> AllAnimeEpisode | None:
|
||||
data = payload.get("data")
|
||||
if not isinstance(data, dict):
|
||||
return None
|
||||
|
||||
episode = data.get("episode")
|
||||
if isinstance(episode, dict):
|
||||
return episode # type: ignore[return-value]
|
||||
|
||||
encoded_payload = data.get("tobeparsed")
|
||||
if not isinstance(encoded_payload, str):
|
||||
return None
|
||||
|
||||
parsed_payload = decode_tobeparsed(encoded_payload, TOBEPARSED_DECRYPTION_SEED)
|
||||
parsed_episode = parsed_payload.get("episode")
|
||||
if isinstance(parsed_episode, dict):
|
||||
return parsed_episode # type: ignore[return-value]
|
||||
return None
|
||||
|
||||
def _get_episode_payload(self, params: EpisodeStreamsParams) -> AllAnimeEpisode | None:
|
||||
persisted_query_response = self.client.get(
|
||||
API_GRAPHQL_ENDPOINT,
|
||||
params={
|
||||
"variables": dumps(
|
||||
{
|
||||
"showId": params.anime_id,
|
||||
"translationType": params.translation_type,
|
||||
"episodeString": params.episode,
|
||||
},
|
||||
separators=(",", ":"),
|
||||
),
|
||||
"extensions": dumps(
|
||||
{
|
||||
"persistedQuery": {
|
||||
"version": 1,
|
||||
"sha256Hash": PERSISTED_QUERY_SHA256,
|
||||
}
|
||||
},
|
||||
separators=(",", ":"),
|
||||
),
|
||||
},
|
||||
headers={**API_GRAPHQL_HEADERS, **API_EPISODE_HEADERS},
|
||||
)
|
||||
persisted_query_response.raise_for_status()
|
||||
|
||||
if episode := self._extract_episode_from_payload(persisted_query_response.json()):
|
||||
return episode
|
||||
|
||||
episode_response = execute_graphql(
|
||||
API_GRAPHQL_ENDPOINT,
|
||||
self.client,
|
||||
@@ -70,12 +142,9 @@ class AllAnime(BaseAnimeProvider):
|
||||
"translationType": params.translation_type,
|
||||
"episodeString": params.episode,
|
||||
},
|
||||
headers=API_GRAPHQL_HEADERS
|
||||
headers=API_GRAPHQL_HEADERS,
|
||||
)
|
||||
episode: AllAnimeEpisode = episode_response.json()["data"]["episode"]
|
||||
for source in episode["sourceUrls"]:
|
||||
if server := extract_server(self.client, params.episode, episode, source):
|
||||
yield server
|
||||
return self._extract_episode_from_payload(episode_response.json())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import functools
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from base64 import b64decode
|
||||
from itertools import cycle
|
||||
from typing import Any
|
||||
|
||||
from Cryptodome.Cipher import AES
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -73,6 +79,23 @@ def one_digit_symmetric_xor(password: int, target: str):
|
||||
return bytes(genexp()).decode("utf-8")
|
||||
|
||||
|
||||
def decode_tobeparsed(payload: str, key_seed: str) -> dict[str, Any]:
|
||||
base64_padding = (-len(payload)) % 4
|
||||
encrypted_payload = b64decode(payload + ("=" * base64_padding))
|
||||
iv = encrypted_payload[1:13]
|
||||
ciphertext = encrypted_payload[13:-16]
|
||||
decryption_key = hashlib.sha256(key_seed.encode("utf-8")).digest()
|
||||
|
||||
plain_text = AES.new(
|
||||
decryption_key,
|
||||
AES.MODE_CTR,
|
||||
nonce=iv,
|
||||
initial_value=2,
|
||||
).decrypt(ciphertext)
|
||||
|
||||
return json.loads(plain_text.decode("utf-8"))
|
||||
|
||||
|
||||
def decode_hex_string(hex_string):
|
||||
"""some of the sources encrypt the urls into hex codes this function decrypts the urls
|
||||
|
||||
|
||||
Reference in New Issue
Block a user