feat: improve performance

This commit is contained in:
Benexl
2025-07-24 18:17:06 +03:00
parent 367520450d
commit 17636d766a
10 changed files with 143 additions and 30 deletions

View File

@@ -1,12 +1,6 @@
import click
from .....core.config.model import AppConfig
from .....core.constants import ANILIST_AUTH
from .....libs.api.factory import create_api_client
from .....libs.selectors.selector import create_selector
from ....services.auth import AuthService
from ....services.feedback import FeedbackService
@click.command(help="Login to your AniList account to enable progress tracking.")
@@ -15,6 +9,12 @@ from ....services.feedback import FeedbackService
@click.pass_obj
def auth(config: AppConfig, status: bool, logout: bool):
"""Handles user authentication and credential management."""
from .....core.constants import ANILIST_AUTH
from .....libs.api.factory import create_api_client
from .....libs.selectors.selector import create_selector
from ....services.auth import AuthService
from ....services.feedback import FeedbackService
auth_service = AuthService("anilist")
feedback = FeedbackService(config.general.icons)
selector = create_selector(config)

View File

@@ -9,14 +9,9 @@ import click
from ...core.config import AppConfig
from ...core.constants import APP_DIR, USER_CONFIG_PATH
from ...libs.api.base import BaseApiClient
from ...libs.api.factory import create_api_client
from ...libs.players import create_player
from ...libs.players.base import BasePlayer
from ...libs.providers.anime.base import BaseAnimeProvider
from ...libs.providers.anime.provider import create_provider
from ...libs.selectors import create_selector
from ...libs.selectors.base import BaseSelector
from ..config import ConfigLoader
from ..services.auth import AuthService
from ..services.feedback import FeedbackService
from ..services.registry import MediaRegistryService
@@ -66,6 +61,11 @@ class Session:
def _load_context(self, config: AppConfig):
"""Initializes all shared services based on the provided configuration."""
from ...libs.api.factory import create_api_client
from ...libs.players import create_player
from ...libs.providers.anime.provider import create_provider
from ...libs.selectors import create_selector
media_registry = MediaRegistryService(
media_api=config.general.media_api, config=config.media_registry
)
@@ -100,6 +100,8 @@ class Session:
logger.info("Application context reloaded.")
def _edit_config(self):
from ..config import ConfigLoader
click.edit(filename=str(USER_CONFIG_PATH))
logger.debug("Config changed; Reloading context")
loader = ConfigLoader()

View File

@@ -2,8 +2,6 @@ import re
from datetime import datetime
from typing import List, Optional
from yt_dlp.utils import clean_html as ytdlp_clean_html
from ...libs.api.types import AiringSchedule
COMMA_REGEX = re.compile(r"([0-9]{3})(?=\d)")
@@ -68,9 +66,71 @@ def format_date(dt: Optional[datetime], format_str: str = "%A, %d %B %Y") -> str
return dt.strftime(format_str)
def clean_html(raw_html: str) -> str:
"""A wrapper around yt-dlp's clean_html to handle None inputs."""
return ytdlp_clean_html(raw_html) if raw_html else ""
def _htmlentity_transform(entity_with_semicolon):
import contextlib
import html.entities
import html.parser
"""Transforms an HTML entity to a character."""
entity = entity_with_semicolon[:-1]
# Known non-numeric HTML entity
if entity in html.entities.name2codepoint:
return chr(html.entities.name2codepoint[entity])
# TODO: HTML5 allows entities without a semicolon.
# E.g. '&Eacuteric' should be decoded as 'Éric'.
if entity_with_semicolon in html.entities.html5:
return html.entities.html5[entity_with_semicolon]
mobj = re.match(r"#(x[0-9a-fA-F]+|[0-9]+)", entity)
if mobj is not None:
numstr = mobj.group(1)
if numstr.startswith("x"):
base = 16
numstr = f"0{numstr}"
else:
base = 10
# See https://github.com/ytdl-org/youtube-dl/issues/7518
with contextlib.suppress(ValueError):
return chr(int(numstr, base))
# Unknown entity in name, return its literal representation
return f"&{entity};"
def unescapeHTML(s: str):
if s is None:
return None
assert isinstance(s, str)
return re.sub(r"&([^&;]+;)", lambda m: _htmlentity_transform(m.group(1)), s)
def escapeHTML(text):
return (
text.replace("&", "&")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace('"', "&quot;")
.replace("'", "&#39;")
)
def clean_html(html: Optional[str]):
"""Clean an HTML snippet into a readable string"""
if html is None: # Convenience for sanitizing descriptions etc.
return html
html = re.sub(r"\s+", " ", html)
html = re.sub(r"(?u)\s?<\s?br\s?/?\s?>\s?", "\n", html)
html = re.sub(r"(?u)<\s?/\s?p\s?>\s?<\s?p[^>]*>", "\n", html)
# Strip html tags
html = re.sub("<.*?>", "", html)
# Replace html entities
html = unescapeHTML(html)
return html.strip()
def format_number_with_commas(number: Optional[int]) -> str:

View File

@@ -1,10 +1,57 @@
TIMEOUT = 10
import os
import random
import re
from urllib.parse import unquote, urlparse
import httpx
TIMEOUT = 10
def random_user_agent():
_USER_AGENT_TPL = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36"
_CHROME_VERSIONS = (
"90.0.4430.212",
"90.0.4430.24",
"90.0.4430.70",
"90.0.4430.72",
"90.0.4430.85",
"90.0.4430.93",
"91.0.4472.101",
"91.0.4472.106",
"91.0.4472.114",
"91.0.4472.124",
"91.0.4472.164",
"91.0.4472.19",
"91.0.4472.77",
"92.0.4515.107",
"92.0.4515.115",
"92.0.4515.131",
"92.0.4515.159",
"92.0.4515.43",
"93.0.4556.0",
"93.0.4577.15",
"93.0.4577.63",
"93.0.4577.82",
"94.0.4606.41",
"94.0.4606.54",
"94.0.4606.61",
"94.0.4606.71",
"94.0.4606.81",
"94.0.4606.85",
"95.0.4638.17",
"95.0.4638.50",
"95.0.4638.54",
"95.0.4638.69",
"95.0.4638.74",
"96.0.4664.18",
"96.0.4664.45",
"96.0.4664.55",
"96.0.4664.93",
"97.0.4692.20",
)
return _USER_AGENT_TPL % random.choice(_CHROME_VERSIONS)
def get_remote_filename(response: httpx.Response) -> str | None:
"""

View File

@@ -5,7 +5,8 @@ import logging
from typing import TYPE_CHECKING
from httpx import Client
from yt_dlp.utils.networking import random_user_agent
from ...core.utils.networking import random_user_agent
if TYPE_CHECKING:
from ...core.config import AppConfig

View File

@@ -11,7 +11,6 @@ from .constants import (
EPISODE_GQL,
SEARCH_GQL,
)
from .extractors import extract_server
from .parser import (
map_to_anime_result,
map_to_search_results,
@@ -57,6 +56,8 @@ class AllAnime(BaseAnimeProvider):
@debug_provider
def episode_streams(self, params):
from .extractors import extract_server
episode_response = execute_graphql_query_with_get_request(
API_GRAPHQL_ENDPOINT,
self.client,

View File

@@ -1,10 +1,9 @@
# from ..utils import int2base
import re
from yt_dlp.utils import encode_base_n, get_element_text_and_html_by_tag
def animepahe_key_creator(c: int, a: int):
from yt_dlp.utils import encode_base_n
if c < a:
val_a = ""
else:
@@ -38,6 +37,8 @@ ENCODE_JS_REGEX = re.compile(r"'(.*?);',(\d+),(\d+),'(.*)'\.split")
def process_animepahe_embed_page(embed_page: str):
from yt_dlp.utils import get_element_text_and_html_by_tag
encoded_js_string = ""
embed_page_content = embed_page
for _ in range(8):

View File

@@ -2,12 +2,6 @@ import logging
from functools import lru_cache
from typing import Iterator, Optional
from yt_dlp.utils import (
extract_attributes,
get_element_by_id,
get_elements_html_by_class,
)
from ..base import BaseAnimeProvider
from ..params import AnimeParams, EpisodeStreamsParams, SearchParams
from ..types import Anime, AnimeEpisodeInfo, SearchResult, SearchResults, Server
@@ -112,6 +106,13 @@ class AnimePahe(BaseAnimeProvider):
@debug_provider
def episode_streams(self, params: EpisodeStreamsParams) -> Iterator[Server] | None:
# TODO: replace with custom implementations using default html parser or lxml
from yt_dlp.utils import (
extract_attributes,
get_element_by_id,
get_elements_html_by_class,
)
episode = self._get_episode_info(params)
if not episode:
logger.error(

View File

@@ -2,7 +2,6 @@ import importlib
import logging
from httpx import Client
from yt_dlp.utils.networking import random_user_agent
from .base import BaseAnimeProvider
from .types import ProviderName
@@ -39,6 +38,7 @@ class AnimeProviderFactory:
ValueError: If the provider_name is not supported.
ImportError: If the provider module or class cannot be found.
"""
from ....core.utils.networking import random_user_agent
# Correctly determine module and class name from the map
import_path = PROVIDERS_AVAILABLE[provider_name.value.lower()]

View File

@@ -28,9 +28,9 @@ def test_anime_provider(AnimeProvider: Type[BaseAnimeProvider]):
import subprocess
from httpx import Client
from yt_dlp.utils.networking import random_user_agent
from .....core.constants import APP_ASCII_ART
from .....core.utils.networking import random_user_agent
from ..params import AnimeParams, EpisodeStreamsParams, SearchParams
anime_provider = AnimeProvider(