Source code for nyora.services.manga

"""Manga browse, search, reader, and metadata operations."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, cast

from nyora.models import (
    GlobalSearchGroup,
    Manga,
    MangaDetails,
    MangaPage,
    MangaPrefs,
    SearchPage,
)

if TYPE_CHECKING:
    from nyora.client import Nyora


[docs] class MangaService: """Browse, search, read, and configure manga via the helper. Attached to a client as ``client.manga``. """ def __init__(self, client: Nyora) -> None: """Bind the service to a helper client. Args: client: The owning :class:`nyora.client.Nyora` instance. """ self._client = client
[docs] def popular(self, source_id: str, page: int = 1) -> SearchPage: """Fetch a page of popular manga from a source. Args: source_id: Identifier of the source to query. page: One-based page number. Returns: A :class:`~nyora.models.SearchPage` of entries. """ return self._browse("/sources/popular", source_id, page=page)
[docs] def latest(self, source_id: str, page: int = 1) -> SearchPage: """Fetch a page of the latest updated manga from a source. Args: source_id: Identifier of the source to query. page: One-based page number. Returns: A :class:`~nyora.models.SearchPage` of entries. """ return self._browse("/sources/latest", source_id, page=page)
[docs] def search( self, source_id: str, query: str, page: int = 1, *, filters: list[dict[str, Any]] | None = None, ) -> SearchPage: """Search a source for manga matching a query. Args: source_id: Identifier of the source to query. query: Free-text search query. page: One-based page number. filters: Optional source-specific filter selections. Returns: A :class:`~nyora.models.SearchPage` of matching entries. """ params: dict[str, Any] = {"id": source_id, "q": query, "page": page} if filters: params["filters"] = filters return SearchPage.from_json(self._client.get("/sources/search", params=params))
[docs] def details( self, source_id: str, manga_url: str, *, manga_id: str | None = None, ) -> MangaDetails: """Fetch full metadata and chapters for one manga. Args: source_id: Identifier of the owning source. manga_url: The manga's URL. manga_id: Optional known manga id to disambiguate. Returns: A :class:`~nyora.models.MangaDetails`. """ params = {"id": source_id, "url": manga_url} if manga_id: params["mangaId"] = manga_id return MangaDetails.from_json(self._client.get("/manga/details", params=params))
[docs] def pages( self, source_id: str, chapter_url: str, *, branch: str | None = None, ) -> list[MangaPage]: """Resolve the readable image pages of a chapter. Args: source_id: Identifier of the owning source. chapter_url: The chapter's URL. branch: Optional scanlation branch/translation to select. Returns: An ordered list of :class:`~nyora.models.MangaPage` objects. """ params = {"id": source_id, "url": chapter_url} if branch: params["branch"] = branch data = self._client.get("/manga/pages", params=params) entries = data.get("pages", data if isinstance(data, list) else []) return [MangaPage.from_json(item) for item in entries]
[docs] def alternatives(self, title: str) -> list[dict[str, Any]]: """Find alternative editions/sources for a title. Args: title: The manga title to look up. Returns: Raw alternative-entry dicts from the helper. """ data = cast( dict[str, Any], self._client.get("/manga/alternatives", params={"title": title}), ) return cast(list[dict[str, Any]], data.get("entries", []))
[docs] def suggestions(self) -> list[Manga]: """Return personalized manga suggestions. Returns: Suggested :class:`~nyora.models.Manga` entries. """ data = cast(dict[str, Any], self._client.get("/suggestions")) return [Manga.from_json(item) for item in data.get("entries", [])]
[docs] def prefs(self, manga_id: str) -> MangaPrefs: """Fetch the stored reader preferences for a manga. Args: manga_id: Identifier of the manga. Returns: The manga's :class:`~nyora.models.MangaPrefs`. """ return MangaPrefs.from_json(self._client.get("/manga/prefs", params={"mangaId": manga_id}))
[docs] def save_prefs( self, manga_id: str, *, reader_mode: str = "", brightness: float = 0.0, contrast: float = 1.0, saturation: float = 1.0, hue: float = 0.0, palette: str = "", ) -> dict[str, Any]: """Save reader preferences for a manga. Args: manga_id: Identifier of the manga. reader_mode: Reader layout/mode. brightness: Brightness adjustment. contrast: Contrast multiplier. saturation: Saturation multiplier. hue: Hue rotation. palette: Named color palette. Returns: The raw helper response. """ return cast(dict[str, Any], self._client.post( "/manga/prefs/save", params={ "mangaId": manga_id, "readerMode": reader_mode, "brightness": brightness, "contrast": contrast, "saturation": saturation, "hue": hue, "palette": palette, }, ))
[docs] def clear_prefs(self, manga_id: str) -> dict[str, Any]: """Clear stored reader preferences for a manga. Args: manga_id: Identifier of the manga. Returns: The raw helper response. """ return cast( dict[str, Any], self._client.post("/manga/prefs/clear", params={"mangaId": manga_id}), )
def _browse(self, path: str, source_id: str, *, page: int) -> SearchPage: """Issue a paginated browse request and parse the page. Args: path: The browse endpoint path. source_id: Identifier of the source to query. page: One-based page number. Returns: A :class:`~nyora.models.SearchPage`. """ return SearchPage.from_json(self._client.get(path, params={"id": source_id, "page": page}))