diff --git a/.gitignore b/.gitignore index e539d90..f2f710e 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,5 @@ venv/ # [date]full_name(id) report folders \[*\]*\(*\)/ *.egg-info -mloader_downloads/ \ No newline at end of file +mloader_downloads/ +build \ No newline at end of file diff --git a/README.md b/README.md index 4c77ce7..9efc7dc 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ # Mangaplus Downloader [![Latest Github release](https://img.shields.io/github/tag/hurlenko/mloader.svg)](https://github.com/hurlenko/mloader/releases/latest) -![Python](https://img.shields.io/badge/python-v3.6+-blue.svg) +![Python](https://img.shields.io/badge/python-v3.12+-blue.svg) ![License](https://img.shields.io/badge/license-GPLv3-blue.svg) ## **mloader** - download manga from mangaplus.shueisha.co.jp ## 🚩 Table of Contents -- [Installation](#-installation) -- [Usage](#-usage) -- [Command line interface](#%EF%B8%8F-command-line-interface) +- [Installation](#-installation) +- [Usage](#-usage) +- [Command line interface](#%EF%B8%8F-command-line-interface) ## 💾 Installation @@ -30,7 +30,7 @@ You can use `--title` and `--chapter` command line argument to download by title You can download individual chapters or full title (but only available chapters). -Chapters can be saved as `CBZ` archives (default) or separate images by passing the `--raw` parameter. +Chapters can be saved in different formats (check the `--help` output for the available formats). ## 🖥️ Command line interface @@ -45,20 +45,16 @@ Options: --version Show the version and exit. -o, --out Save directory (not a file) [default: mloader_downloads] - -r, --raw Save raw images [default: False] + -f, --format [raw|cbz|pdf] Output format [default: cbz] -q, --quality [super_high|high|low] Image quality [default: super_high] - -s, --split Split combined images [default: False] + -s, --split Split combined images -c, --chapter INTEGER Chapter id -t, --title INTEGER Title id -b, --begin INTEGER RANGE Minimal chapter to try to download [default: 0;x>=0] -e, --end INTEGER RANGE Maximal chapter to try to download [x>=1] -l, --last Download only the last chapter for title - [default: False] --chapter-title Include chapter titles in filenames - [default: False] - --chapter-subdir Save raw images in sub directory by chapter - [default: False] --help Show this message and exit. -``` \ No newline at end of file +``` diff --git a/mloader/.env b/mloader/.env new file mode 100644 index 0000000..1b6ab5d --- /dev/null +++ b/mloader/.env @@ -0,0 +1,4 @@ +APP_VER=97 +OS=ios +OS_VER=18.1 +SECRET=f40080bcb01a9a963912f46688d411a3 \ No newline at end of file diff --git a/mloader/__main__.py b/mloader/__main__.py index afcad18..4587941 100644 --- a/mloader/__main__.py +++ b/mloader/__main__.py @@ -1,231 +1,11 @@ -import logging -import re -import sys -from functools import partial -from typing import Optional, Set +# mloader/__main__.py -import click - -from mloader import __version__ as about -from mloader.exporter import RawExporter, CBZExporter -from mloader.loader import MangaLoader - -log = logging.getLogger() - - -def setup_logging(): - for logger in ("requests", "urllib3"): - logging.getLogger(logger).setLevel(logging.WARNING) - handlers = [logging.StreamHandler(sys.stdout)] - logging.basicConfig( - handlers=handlers, - format=( - "{asctime:^} | {levelname: ^8} | " - "{filename: ^14} {lineno: <4} | {message}" - ), - style="{", - datefmt="%d.%m.%Y %H:%M:%S", - level=logging.INFO, - ) - - -setup_logging() - - -def validate_urls(ctx: click.Context, param, value): - if not value: - return value - - res = {"viewer": set(), "titles": set()} - for url in value: - match = re.search(r"(\w+)/(\d+)", url) - if not match: - raise click.BadParameter(f"Invalid url: {url}") - try: - res[match.group(1)].add(int(match.group(2))) - except (ValueError, KeyError): - raise click.BadParameter(f"Invalid url: {url}") - - ctx.params.setdefault("titles", set()).update(res["titles"]) - ctx.params.setdefault("chapters", set()).update(res["viewer"]) - - -def validate_ids(ctx: click.Context, param, value): - if not value: - return value - - assert param.name in ("chapter", "title") - - ctx.params.setdefault(f"{param.name}s", set()).update(value) - - -EPILOG = f""" -Examples: - -{click.style('• download manga chapter 1 as CBZ archive', fg="green")} - - $ mloader https://mangaplus.shueisha.co.jp/viewer/1 - -{click.style('• download all chapters for manga title 2 and save ' -'to current directory', fg="green")} - - $ mloader https://mangaplus.shueisha.co.jp/titles/2 -o . - -{click.style('• download chapter 1 AND all available chapters from ' -'title 2 (can be two different manga) in low quality and save as ' -'separate images', fg="green")} - - $ mloader https://mangaplus.shueisha.co.jp/viewer/1 - https://mangaplus.shueisha.co.jp/titles/2 -r -q low -""" - - -@click.command( - help=about.__description__, - epilog=EPILOG, -) -@click.version_option( - about.__version__, - prog_name=about.__title__, - message="%(prog)s by Hurlenko, version %(version)s\n" - f"Check {about.__url__} for more info", -) -@click.option( - "--out", - "-o", - "out_dir", - type=click.Path(exists=False, writable=True), - metavar="", - default="mloader_downloads", - show_default=True, - help="Save directory (not a file)", - envvar="MLOADER_EXTRACT_OUT_DIR", -) -@click.option( - "--raw", - "-r", - is_flag=True, - default=False, - show_default=True, - help="Save raw images", - envvar="MLOADER_RAW", -) -@click.option( - "--quality", - "-q", - default="super_high", - type=click.Choice(["super_high", "high", "low"]), - show_default=True, - help="Image quality", - envvar="MLOADER_QUALITY", -) -@click.option( - "--split", - "-s", - is_flag=True, - default=False, - show_default=True, - help="Split combined images", - envvar="MLOADER_SPLIT", -) -@click.option( - "--chapter", - "-c", - type=click.INT, - multiple=True, - help="Chapter id", - expose_value=False, - callback=validate_ids, -) -@click.option( - "--title", - "-t", - type=click.INT, - multiple=True, - help="Title id", - expose_value=False, - callback=validate_ids, -) -@click.option( - "--begin", - "-b", - type=click.IntRange(min=0), - default=0, - show_default=True, - help="Minimal chapter to try to download", -) -@click.option( - "--end", - "-e", - type=click.IntRange(min=1), - help="Maximal chapter to try to download", -) -@click.option( - "--last", - "-l", - is_flag=True, - default=False, - show_default=True, - help="Download only the last chapter for title", -) -@click.option( - "--chapter-title", - is_flag=True, - default=False, - show_default=True, - help="Include chapter titles in filenames", -) -@click.option( - "--chapter-subdir", - is_flag=True, - default=False, - show_default=True, - help="Save raw images in sub directory by chapter", -) -@click.argument("urls", nargs=-1, callback=validate_urls, expose_value=False) -@click.pass_context -def main( - ctx: click.Context, - out_dir: str, - raw: bool, - quality: str, - split: bool, - begin: int, - end: int, - last: bool, - chapter_title: bool, - chapter_subdir: bool, - chapters: Optional[Set[int]] = None, - titles: Optional[Set[int]] = None, -): - click.echo(click.style(about.__doc__, fg="blue")) - if not any((chapters, titles)): - click.echo(ctx.get_help()) - return - end = end or float("inf") - log.info("Started export") - - exporter = RawExporter if raw else CBZExporter - exporter = partial( - exporter, - destination=out_dir, - add_chapter_title=chapter_title, - add_chapter_subdir=chapter_subdir, - ) - - loader = MangaLoader(exporter, quality, split) - try: - loader.download( - title_ids=titles, - chapter_ids=chapters, - min_chapter=begin, - max_chapter=end, - last_chapter=last, - ) - except Exception: - log.exception("Failed to download manga") - log.info("SUCCESS") +# Import the logging setup early so that it applies to all loggers. +from mloader.cli.config import setup_logging +setup_logging() # This ensures all logging settings are in place. +# Now import the main CLI command. +from mloader.cli.main import main if __name__ == "__main__": - main(prog_name=about.__title__) + main() \ No newline at end of file diff --git a/mloader/__version__.py b/mloader/__version__.py index 981c900..bc8c006 100644 --- a/mloader/__version__.py +++ b/mloader/__version__.py @@ -1,13 +1,14 @@ -""" +__intro__ = r""" _ _ _ __ ___ | | ___ __ _ __| | ___ _ __ | '_ ` _ \| |/ _ \ / _` |/ _` |/ _ \ '__| | | | | | | | (_) | (_| | (_| | __/ | |_| |_| |_|_|\___/ \__,_|\__,_|\___|_| + """ __title__ = "mloader" __description__ = "Command-line tool to download manga from mangaplus" __url__ = "https://github.com/hurlenko/mloader" -__version__ = "1.1.12" +__version__ = "1.2.0" __license__ = "GPLv3" diff --git a/mloader/cli/__init__.py b/mloader/cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mloader/cli/config.py b/mloader/cli/config.py new file mode 100644 index 0000000..91a69a0 --- /dev/null +++ b/mloader/cli/config.py @@ -0,0 +1,41 @@ +import logging +import sys + + +def setup_logging(): + """ + Configure logging for the application. + + Sets third-party loggers (e.g., 'requests', 'urllib3') to WARNING and configures the + root logger to output logs to stdout with a custom format. + """ + for logger_name in ("requests", "urllib3"): + logging.getLogger(logger_name).setLevel(logging.WARNING) + + stream_handler = logging.StreamHandler(sys.stdout) + logging.basicConfig( + handlers=[stream_handler], + format=( + "{asctime:^} | {levelname: ^8} | {filename: ^14} {lineno: <4} | {message}" + ), + style="{", + datefmt="%d.%m.%Y %H:%M:%S", + level=logging.INFO, + ) + + +# Immediately configure logging upon module import. +setup_logging() + + +def get_logger(name: str = None) -> logging.Logger: + """ + Retrieve a logger instance with the given name. + + Parameters: + name (str, optional): The name of the logger. Defaults to None for the root logger. + + Returns: + logging.Logger: The configured logger. + """ + return logging.getLogger(name) \ No newline at end of file diff --git a/mloader/cli/init.py b/mloader/cli/init.py new file mode 100644 index 0000000..dd58579 --- /dev/null +++ b/mloader/cli/init.py @@ -0,0 +1,3 @@ +from .main import main + +__all__ = ["main"] \ No newline at end of file diff --git a/mloader/cli/main.py b/mloader/cli/main.py new file mode 100644 index 0000000..5b753de --- /dev/null +++ b/mloader/cli/main.py @@ -0,0 +1,201 @@ +import logging +from functools import partial +from typing import Optional, Set + +import click + +from mloader import __version__ as about +from mloader.exporters.init import RawExporter, CBZExporter +from mloader.manga_loader.init import MangaLoader +from mloader.cli.validators import validate_urls, validate_ids + +# Get a logger for this module. +log = logging.getLogger(__name__) + +# Define an epilog message with examples. +EPILOG = f""" +Examples: + +{click.style('• download manga chapter 1 as CBZ archive', fg="green")} + + $ mloader https://mangaplus.shueisha.co.jp/viewer/1 + +{click.style('• download all chapters for manga title 2 and save to current directory', fg="green")} + + $ mloader https://mangaplus.shueisha.co.jp/titles/2 -o . + +{click.style('• download chapter 1 AND all available chapters from title 2 (can be two different manga) in low quality and save as separate images', fg="green")} + + $ mloader https://mangaplus.shueisha.co.jp/viewer/1 + https://mangaplus.shueisha.co.jp/titles/2 -r -q low +""" + + +@click.command( + help=about.__description__, + epilog=EPILOG, +) +@click.version_option( + about.__version__, + prog_name=about.__title__, + message="%(prog)s by Hurlenko, version %(version)s\nCheck {url} for more info".format(url=about.__url__), +) +@click.option( + "--out", "-o", + "out_dir", + type=click.Path(exists=False, writable=True), + metavar="", + default="mloader_downloads", + show_default=True, + help="Output directory for downloads", + envvar="MLOADER_EXTRACT_OUT_DIR", +) +@click.option( + "--raw", "-r", + is_flag=True, + default=False, + show_default=True, + help="Save raw images", + envvar="MLOADER_RAW", +) +@click.option( + "--quality", "-q", + default="super_high", + type=click.Choice(["super_high", "high", "low"]), + show_default=True, + help="Image quality", + envvar="MLOADER_QUALITY", +) +@click.option( + "--split", "-s", + is_flag=True, + default=False, + show_default=True, + help="Split combined images", + envvar="MLOADER_SPLIT", +) +@click.option( + "--chapter", "-c", + type=click.INT, + multiple=True, + help="Chapter id", + expose_value=False, + callback=validate_ids, +) +@click.option( + "--title", "-t", + type=click.INT, + multiple=True, + help="Title id", + expose_value=False, + callback=validate_ids, +) +@click.option( + "--begin", "-b", + type=click.IntRange(min=0), + default=0, + show_default=True, + help="Minimal chapter to download", +) +@click.option( + "--end", "-e", + type=click.IntRange(min=1), + help="Maximal chapter to download", +) +@click.option( + "--last", "-l", + is_flag=True, + default=False, + show_default=True, + help="Download only the last chapter for each title", +) +@click.option( + "--chapter-title", + is_flag=True, + default=False, + show_default=True, + help="Include chapter titles in filenames", +) +@click.option( + "--chapter-subdir", + is_flag=True, + default=False, + show_default=True, + help="Save raw images in subdirectories by chapter", +) +@click.argument("urls", nargs=-1, callback=validate_urls, expose_value=False) +@click.pass_context +def main( + ctx: click.Context, + out_dir: str, + raw: bool, + quality: str, + split: bool, + begin: int, + end: int, + last: bool, + chapter_title: bool, + chapter_subdir: bool, + chapters: Optional[Set[int]] = None, + titles: Optional[Set[int]] = None, +): + """ + Main entry point for the manga downloader CLI. + + This command validates inputs, initializes the appropriate exporter (raw or CBZ), sets up + the manga loader, and starts the download process. + + Parameters: + ctx (click.Context): Click context. + out_dir (str): Output directory for downloads. + raw (bool): Flag indicating whether to save raw images. + quality (str): Image quality setting. + split (bool): Flag indicating whether to split combined images. + begin (int): Minimal chapter number to download. + end (int): Maximal chapter number to download. + last (bool): Flag to download only the last chapter of each title. + chapter_title (bool): Flag to include chapter titles in filenames. + chapter_subdir (bool): Flag to save raw images in subdirectories by chapter. + chapters (Optional[Set[int]]): Set of chapter IDs. + titles (Optional[Set[int]]): Set of title IDs. + """ + # Display application description. + click.echo(click.style(about.__intro__, fg="blue")) + + # If neither chapter nor title IDs are provided, show help text. + if not any((chapters, titles)): + click.echo(ctx.get_help()) + return + + # Set maximum chapter to infinity if not provided. + end = end or float("inf") + log.info("Started export") + + # Choose exporter class based on the 'raw' flag. + exporter_class = RawExporter if raw else CBZExporter + + # Create a factory for the exporter with common parameters. + exporter_factory = partial( + exporter_class, + destination=out_dir, + add_chapter_title=chapter_title, + add_chapter_subdir=chapter_subdir, + ) + + # Initialize the manga loader with the exporter factory, quality, and split options. + loader = MangaLoader(exporter_factory, quality, split) + try: + loader.download( + title_ids=titles, + chapter_ids=chapters, + min_chapter=begin, + max_chapter=end, + last_chapter=last, + ) + except Exception: + log.exception("Failed to download manga") + log.info("SUCCESS") + + +if __name__ == "__main__": + main(prog_name=about.__title__) \ No newline at end of file diff --git a/mloader/cli/validators.py b/mloader/cli/validators.py new file mode 100644 index 0000000..58243eb --- /dev/null +++ b/mloader/cli/validators.py @@ -0,0 +1,61 @@ +import re +import click + +def validate_urls(ctx: click.Context, param, value): + """ + Validate URL arguments and extract title and viewer IDs. + + Each URL is expected to match the pattern / (for example, "viewer/123" or "titles/456"). + Extracted IDs are added to the context parameters 'titles' and 'chapters'. + + Parameters: + ctx (click.Context): The Click context. + param: The parameter definition. + value: The list of URL strings provided. + + Returns: + The original value if valid; otherwise, raises a click.BadParameter exception. + """ + if not value: + return value + + # Initialize result sets for viewer and title IDs. + results = {"viewer": set(), "titles": set()} + for url in value: + match = re.search(r"(\w+)/(\d+)", url) + if not match: + raise click.BadParameter(f"Invalid url: {url}") + try: + key = match.group(1) + id_value = int(match.group(2)) + results[key].add(id_value) + except (ValueError, KeyError): + raise click.BadParameter(f"Invalid url: {url}") + + ctx.params.setdefault("titles", set()).update(results["titles"]) + ctx.params.setdefault("chapters", set()).update(results["viewer"]) + return value + + +def validate_ids(ctx: click.Context, param, value): + """ + Validate chapter or title IDs and update context parameters. + + Ensures that IDs provided for 'chapter' or 'title' are valid integers and updates the + corresponding set in the context. + + Parameters: + ctx (click.Context): The Click context. + param: The parameter definition. + value: The list of IDs provided. + + Returns: + The original value if valid. + """ + if not value: + return value + + # Parameter name must be either 'chapter' or 'title'. + assert param.name in ("chapter", "title"), f"Unexpected parameter: {param.name}" + ctx.params.setdefault(f"{param.name}s", set()).update(value) + return value \ No newline at end of file diff --git a/mloader/config.py b/mloader/config.py new file mode 100644 index 0000000..cbc2b77 --- /dev/null +++ b/mloader/config.py @@ -0,0 +1,12 @@ +import os +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() + +AUTH_PARAMS = { + "app_ver": os.getenv("APP_VER", "97"), + "os": os.getenv("OS", "ios"), + "os_ver": os.getenv("OS_VER", "18.1"), + "secret": os.getenv("SECRET", "f40080bcb01a9a963912f46688d411a3"), +} \ No newline at end of file diff --git a/mloader/constants.py b/mloader/constants.py index 2ef3f9e..1d8dd60 100644 --- a/mloader/constants.py +++ b/mloader/constants.py @@ -2,25 +2,28 @@ class Language(Enum): - eng = 0 - spa = 1 - fre = 2 - ind = 3 - por = 4 - rus = 5 - tha = 6 - deu = 7 - vie = 9 + """Represents supported languages.""" + ENGLISH = 0 + SPANISH = 1 + FRENCH = 2 + INDONESIAN = 3 + PORTUGUESE = 4 + RUSSIAN = 5 + THAI = 6 + GERMAN = 7 + VIETNAMESE = 8 class ChapterType(Enum): - latest = 0 - sequence = 1 - nosequence = 2 + """Represents different chapter types.""" + LATEST = 0 + SEQUENCE = 1 + NO_SEQUENCE = 2 class PageType(Enum): - single = 0 - left = 1 - right = 2 - double = 3 + """Represents different page display types.""" + SINGLE = 0 + LEFT = 1 + RIGHT = 2 + DOUBLE = 3 \ No newline at end of file diff --git a/mloader/exporter.py b/mloader/exporter.py deleted file mode 100644 index bf119a0..0000000 --- a/mloader/exporter.py +++ /dev/null @@ -1,162 +0,0 @@ -import zipfile -from abc import ABCMeta, abstractmethod -from itertools import chain -from pathlib import Path -from typing import Union, Optional - -from mloader.constants import Language -from mloader.response_pb2 import Title, Chapter -from mloader.utils import ( - escape_path, - is_oneshot, - chapter_name_to_int, - is_windows, -) - - -class ExporterBase(metaclass=ABCMeta): - def __init__( - self, - destination: str, - title: Title, - chapter: Chapter, - next_chapter: Optional[Chapter] = None, - add_chapter_title: bool = False, - add_chapter_subdir: bool = False, - ): - self.destination = destination - - if is_windows(): - destination = Path(self.destination).resolve().as_posix() - self.destination = f"\\\\?\\{destination}" - - self.add_chapter_title = add_chapter_title - self.add_chapter_subdir = add_chapter_subdir - self.title_name = escape_path(title.name).title() - self.is_oneshot = is_oneshot(chapter.name, chapter.sub_title) - self.is_extra = self._is_extra(chapter.name) - - self._extra_info = [] - - if self.is_oneshot: - self._extra_info.append("[Oneshot]") - - if self.add_chapter_title: - self._extra_info.append(f"[{escape_path(chapter.sub_title)}]") - - self._chapter_prefix = self._format_chapter_prefix( - self.title_name, - chapter.name, - title.language, - next_chapter and next_chapter.name, - ) - self._chapter_suffix = self._format_chapter_suffix() - self.chapter_name = " ".join( - (self._chapter_prefix, self._chapter_suffix) - ) - - def _is_extra(self, chapter_name: str) -> bool: - return chapter_name.strip("#") == "ex" - - def _format_chapter_prefix( - self, - title_name: str, - chapter_name: str, - language: int, - next_chapter_name: Optional[str] = None, - ) -> str: - # https://github.com/Daiz/manga-naming-scheme - components = [title_name] - if Language(language) != Language.eng: - components.append(f"[{Language(language).name}]") - components.append("-") - suffix = "" - prefix = "" - if self.is_oneshot: - chapter_num = 0 - elif self.is_extra and next_chapter_name: - suffix = "x1" - chapter_num = chapter_name_to_int(next_chapter_name) - if chapter_num is not None: - chapter_num -= 1 - prefix = "c" if chapter_num < 1000 else "d" - else: - chapter_num = chapter_name_to_int(chapter_name) - if chapter_num is not None: - prefix = "c" if chapter_num < 1000 else "d" - - if chapter_num is None: - chapter_num = escape_path(chapter_name) - - components.append(f"{prefix}{chapter_num:0>3}{suffix}") - components.append("(web)") - return " ".join(components) - - def _format_chapter_suffix(self) -> str: - return " ".join(chain(self._extra_info, ["[Unknown]"])) - - def format_page_name(self, page: Union[int, range], ext=".jpg") -> str: - if isinstance(page, range): - page = f"p{page.start:0>3}-{page.stop:0>3}" - else: - page = f"p{page:0>3}" - - ext = ext.lstrip(".") - - return f"{self._chapter_prefix} - {page} {self._chapter_suffix}.{ext}" - - def close(self): - pass - - @abstractmethod - def add_image(self, image_data: bytes, index: Union[int, range]): - pass - - @abstractmethod - def skip_image(self, index: Union[int, range]) -> bool: - pass - - -class RawExporter(ExporterBase): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.path = Path(self.destination, self.title_name) - self.path.mkdir(parents=True, exist_ok=True) - if self.add_chapter_subdir: - self.path = self.path.joinpath(self.chapter_name) - self.path.mkdir(parents=True, exist_ok=True) - - def add_image(self, image_data: bytes, index: Union[int, range]): - filename = Path(self.format_page_name(index)) - self.path.joinpath(filename).write_bytes(image_data) - - def skip_image(self, index: Union[int, range]) -> bool: - filename = Path(self.format_page_name(index)) - return self.path.joinpath(filename).exists() - - -class CBZExporter(ExporterBase): - def __init__(self, compression=zipfile.ZIP_DEFLATED, *args, **kwargs): - super().__init__(*args, **kwargs) - self.path = Path(self.destination, self.title_name) - self.path.mkdir(parents=True, exist_ok=True) - self.path = self.path.joinpath(self.chapter_name).with_suffix(".cbz") - self.skip_all_images = self.path.exists() - if not self.skip_all_images: - self.archive = zipfile.ZipFile( - self.path, mode="w", compression=compression - ) - - def add_image(self, image_data: bytes, index: Union[int, range]): - if self.skip_all_images: - return - path = Path(self.chapter_name, self.format_page_name(index)) - self.archive.writestr(path.as_posix(), image_data) - - def skip_image(self, index: Union[int, range]) -> bool: - return self.skip_all_images - - def close(self): - if self.skip_all_images: - return - self.archive.close() diff --git a/mloader/exporters/__init__.py b/mloader/exporters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mloader/exporters/cbz_exporter.py b/mloader/exporters/cbz_exporter.py new file mode 100644 index 0000000..a0eda98 --- /dev/null +++ b/mloader/exporters/cbz_exporter.py @@ -0,0 +1,70 @@ +import zipfile +from io import BytesIO +from pathlib import Path +from typing import Union +from mloader.exporters.exporter_base import ExporterBase + + +class CBZExporter(ExporterBase): + """ + Export manga pages as a CBZ (Comic Book Zip) archive. + """ + format = "cbz" + + def __init__(self, compression=zipfile.ZIP_DEFLATED, *args, **kwargs): + """ + Initialize the CBZ exporter with ZIP compression and prepare the archive. + + Parameters: + compression: The ZIP compression mode (default is ZIP_DEFLATED). + """ + super().__init__(*args, **kwargs) + # Create base directory and determine the output CBZ file path. + base_path = Path(self.destination, self.title_name) + base_path.mkdir(parents=True, exist_ok=True) + self.path = base_path.joinpath(self.chapter_name).with_suffix(".cbz") + + # Check if the archive already exists. + self.skip_all_images = self.path.exists() + if not self.skip_all_images: + # Use an in-memory buffer to build the archive, then write to disk. + self.archive_buffer = BytesIO() + self.archive = zipfile.ZipFile( + self.archive_buffer, mode="w", compression=compression + ) + + def add_image(self, image_data: bytes, index: Union[int, range]): + """ + Add an image to the CBZ archive. + + Parameters: + image_data (bytes): The raw image data. + index (Union[int, range]): The page index or range. + """ + if self.skip_all_images: + return + # Create an internal path for the image inside the archive. + image_path = Path(self.chapter_name, self.format_page_name(index)) + self.archive.writestr(image_path.as_posix(), image_data) + + def skip_image(self, index: Union[int, range]) -> bool: + """ + Always skip image addition if the archive file already exists. + + Parameters: + index (Union[int, range]): The page index or range. + + Returns: + bool: True if the archive exists, False otherwise. + """ + return self.skip_all_images + + def close(self): + """ + Finalize the CBZ export by writing the archive to disk. + """ + if self.skip_all_images: + return + self.archive.close() + # Write the complete archive to the destination file. + self.path.write_bytes(self.archive_buffer.getvalue()) \ No newline at end of file diff --git a/mloader/exporters/exporter_base.py b/mloader/exporters/exporter_base.py new file mode 100644 index 0000000..96ab91b --- /dev/null +++ b/mloader/exporters/exporter_base.py @@ -0,0 +1,241 @@ +from abc import ABCMeta, abstractmethod +from itertools import chain +from pathlib import Path +from typing import Union, Optional + +from mloader.constants import Language +from mloader.response_pb2 import Title, Chapter +from mloader.utils import escape_path, is_oneshot, chapter_name_to_int, is_windows + + +def _is_extra(chapter_name: str) -> bool: + """ + Determine if the chapter is marked as extra. + + A chapter is considered extra if its name (after stripping '#' characters) + equals "ex". + + Parameters: + chapter_name (str): The raw chapter name. + + Returns: + bool: True if the chapter is extra, False otherwise. + """ + return chapter_name.strip("#").lower() == "ex" + + +class ExporterBase(metaclass=ABCMeta): + """ + Base class for exporting manga chapters to various formats. + + This abstract class defines common functionality used by concrete exporters, + including path normalization, chapter naming, and basic file operations. + Exporters should implement `add_image`, `skip_image`, and the `format` property. + """ + FORMAT_REGISTRY = {} + + def __init__( + self, + destination: str, + title: Title, + chapter: Chapter, + next_chapter: Optional[Chapter] = None, + add_chapter_title: bool = False, + add_chapter_subdir: bool = False, + ): + """ + Initialize the exporter with destination path, title/chapter metadata, and options. + + Parameters: + destination (str): The base directory to save the exported files. + title (Title): Title metadata from the manga. + chapter (Chapter): Current chapter metadata. + next_chapter (Optional[Chapter]): Next chapter metadata, if available. + add_chapter_title (bool): If True, include the chapter subtitle in the output. + add_chapter_subdir (bool): If True, create a separate subdirectory for the chapter. + """ + self.destination = destination + + # Adjust the destination path for Windows (using extended-length path prefix). + if is_windows(): + resolved_path = Path(self.destination).resolve().as_posix() + self.destination = f"\\\\?\\{resolved_path}" + + self.add_chapter_title = add_chapter_title + self.add_chapter_subdir = add_chapter_subdir + + # Clean up and format the manga title. + self.title_name = escape_path(title.name).title() + + # Determine if this is a oneshot and if it is marked as extra. + self.is_oneshot = is_oneshot(chapter.name, chapter.sub_title) + self.is_extra = _is_extra(chapter.name) + + # Build extra information strings for naming. + self._extra_info = [] + if self.is_oneshot: + self._extra_info.append("[Oneshot]") + if self.add_chapter_title: + # Use the escaped version of the chapter subtitle. + self._extra_info.append(f"[{escape_path(chapter.sub_title)}]") + + # Compute the chapter prefix and suffix for naming. + self._chapter_prefix = self._format_chapter_prefix( + self.title_name, + chapter.name, + title.language, + next_chapter.name if next_chapter else None, + ) + self._chapter_suffix = self._format_chapter_suffix() + self.chapter_name = f"{self._chapter_prefix} {self._chapter_suffix}" + + def _format_chapter_prefix( + self, + title_name: str, + chapter_name: str, + language: int, + next_chapter_name: Optional[str] = None, + ) -> str: + """ + Format the prefix for the chapter name based on a naming scheme. + + The prefix includes the title, language (if non-English), and a chapter number + formatted with a prefix letter and zero-padding. For oneshots, a chapter number of 0 + is used. For extra chapters, the chapter number is derived from the next chapter. + + Parameters: + title_name (str): Cleaned title name. + chapter_name (str): The current chapter name. + language (int): Language code for the title. + next_chapter_name (Optional[str]): The next chapter name, if available. + + Returns: + str: The formatted chapter prefix. + """ + # Start with the title name. + components = [title_name] + + # Append the language label if not English. + if Language(language) != Language.ENGLISH: + components.append(f"[{Language(language).name}]") + + # Append a separator. + components.append("-") + + # Determine chapter number and prefix letter. + prefix_letter = "" + chapter_number = None + suffix = "" + + if self.is_oneshot: + chapter_number = 0 + elif self.is_extra and next_chapter_name: + suffix = "x1" + chapter_number = chapter_name_to_int(next_chapter_name) + if chapter_number is not None: + chapter_number -= 1 + prefix_letter = "c" if chapter_number < 1000 else "d" + else: + chapter_number = chapter_name_to_int(chapter_name) + if chapter_number is not None: + prefix_letter = "c" if chapter_number < 1000 else "d" + + # Fallback: if chapter_number is None, use an escaped version of the chapter name. + if chapter_number is None: + chapter_number = escape_path(chapter_name) + else: + # Ensure the chapter number is zero-padded to three digits. + chapter_number = f"{chapter_number:0>3}" + + # Append the formatted chapter number. + components.append(f"{prefix_letter}{chapter_number}{suffix}") + + # Append a fixed source label. + components.append("(web)") + return " ".join(components) + + def _format_chapter_suffix(self) -> str: + """ + Format the chapter suffix using extra information and a default marker. + + Returns: + str: The formatted chapter suffix. + """ + # Always append "[Unknown]" to indicate an unspecified part. + return " ".join(chain(self._extra_info, ["[Unknown]"])) + + def format_page_name(self, page: Union[int, range], ext: str = ".jpg") -> str: + """ + Format the filename for an individual manga page. + + The page name is constructed from the chapter prefix, page index (or range), and + the chapter suffix, with the specified file extension. + + Parameters: + page (Union[int, range]): The page index or a range of page indices. + ext (str): File extension (default is ".jpg"). + + Returns: + str: The formatted page filename. + """ + if isinstance(page, range): + page_str = f"p{page.start:0>3}-{page.stop:0>3}" + else: + page_str = f"p{page:0>3}" + + # Remove any leading '.' from the extension. + ext = ext.lstrip(".") + return f"{self._chapter_prefix} - {page_str} {self._chapter_suffix}.{ext}" + + def close(self): + """ + Finalize the export process. + + Concrete exporters may override this method to perform cleanup tasks, such as + writing buffered data to disk. + """ + pass + + def __init_subclass__(cls, **kwargs) -> None: + """ + Automatically register subclasses by their format. + + This method updates the FORMAT_REGISTRY to associate each exporter format with its class. + """ + cls.FORMAT_REGISTRY[cls.format] = cls + return super().__init_subclass__(**kwargs) + + @abstractmethod + def add_image(self, image_data: bytes, index: Union[int, range]): + """ + Add an image to the export output. + + Parameters: + image_data (bytes): The raw image data. + index (Union[int, range]): The index or range for naming the image. + """ + pass + + @abstractmethod + def skip_image(self, index: Union[int, range]) -> bool: + """ + Determine whether an image should be skipped (e.g., already exists). + + Parameters: + index (Union[int, range]): The index or range of the image. + + Returns: + bool: True if the image should be skipped, False otherwise. + """ + pass + + @property + @abstractmethod + def format(self) -> str: + """ + The file format of the exporter (e.g., "raw", "cbz", "pdf"). + + Returns: + str: The exporter format identifier. + """ + pass \ No newline at end of file diff --git a/mloader/exporters/init.py b/mloader/exporters/init.py new file mode 100644 index 0000000..cfa572a --- /dev/null +++ b/mloader/exporters/init.py @@ -0,0 +1,11 @@ +from .exporter_base import ExporterBase +from .raw_exporter import RawExporter +from .cbz_exporter import CBZExporter +from .pdf_exporter import PDFExporter + +__all__ = [ + "ExporterBase", + "RawExporter", + "CBZExporter", + "PDFExporter", +] \ No newline at end of file diff --git a/mloader/exporters/pdf_exporter.py b/mloader/exporters/pdf_exporter.py new file mode 100644 index 0000000..915bf85 --- /dev/null +++ b/mloader/exporters/pdf_exporter.py @@ -0,0 +1,70 @@ +import io +from pathlib import Path +from typing import Union +from PIL import Image +from mloader.exporters.exporter_base import ExporterBase +from mloader.__version__ import __title__, __version__ + + +class PDFExporter(ExporterBase): + """ + Export manga pages as a PDF document. + """ + format = "pdf" + + def __init__(self, *args, **kwargs): + """ + Initialize the PDF exporter and set up output paths. + """ + super().__init__(*args, **kwargs) + # Create base directory for the title. + base_path = Path(self.destination, self.title_name) + base_path.mkdir(parents=True, exist_ok=True) + self.path = base_path.joinpath(self.chapter_name).with_suffix(".pdf") + self.skip_all_images = self.path.exists() + self.images = [] # List to hold PIL Image objects. + + def add_image(self, image_data: bytes, index: Union[int, range]): + """ + Add an image to the PDF by opening it with PIL and appending to a list. + + Parameters: + image_data (bytes): The raw image data. + index (Union[int, range]): The page index or range. + """ + if self.skip_all_images: + return + # Open the image from bytes and add it to the list. + self.images.append(Image.open(io.BytesIO(image_data))) + + def skip_image(self, index: Union[int, range]) -> bool: + """ + Skip adding an image if the PDF file already exists. + + Parameters: + index (Union[int, range]): The page index or range. + + Returns: + bool: True if the PDF exists, False otherwise. + """ + return self.skip_all_images + + def close(self): + """ + Finalize the PDF export by saving all collected images as a single PDF file. + """ + if self.skip_all_images or not self.images: + return + + app_info = f"{__title__} - {__version__}" + + self.images[0].save( + self.path, + "PDF", + resolution=100.0, + save_all=True, + append_images=self.images[1:], + title=self.chapter_name, + producer=app_info, + creator=app_info, + ) \ No newline at end of file diff --git a/mloader/exporters/raw_exporter.py b/mloader/exporters/raw_exporter.py new file mode 100644 index 0000000..dd0b1b6 --- /dev/null +++ b/mloader/exporters/raw_exporter.py @@ -0,0 +1,49 @@ +from pathlib import Path +from typing import Union +from mloader.exporters.exporter_base import ExporterBase + + +class RawExporter(ExporterBase): + """ + Export manga pages as raw image files to the filesystem. + """ + format = "raw" + + def __init__(self, *args, **kwargs): + """ + Initialize the raw exporter and create necessary directories. + """ + super().__init__(*args, **kwargs) + # Create base directory for the title. + self.path = Path(self.destination, self.title_name) + self.path.mkdir(parents=True, exist_ok=True) + + # Optionally create a subdirectory for the chapter. + if self.add_chapter_subdir: + self.path = self.path.joinpath(self.chapter_name) + self.path.mkdir(parents=True, exist_ok=True) + + def add_image(self, image_data: bytes, index: Union[int, range]): + """ + Write a single image to the filesystem. + + Parameters: + image_data (bytes): The raw image data. + index (Union[int, range]): The page index or range. + """ + filename = self.format_page_name(index) + file_path = self.path.joinpath(filename) + file_path.write_bytes(image_data) + + def skip_image(self, index: Union[int, range]) -> bool: + """ + Skip an image if the file already exists. + + Parameters: + index (Union[int, range]): The page index or range. + + Returns: + bool: True if the file exists and should be skipped. + """ + filename = self.format_page_name(index) + return self.path.joinpath(filename).exists() \ No newline at end of file diff --git a/mloader/loader.py b/mloader/loader.py deleted file mode 100644 index 25041d7..0000000 --- a/mloader/loader.py +++ /dev/null @@ -1,191 +0,0 @@ -import logging -from collections import namedtuple -from functools import lru_cache -from itertools import chain, count -from typing import Union, Dict, Set, Collection, Optional, Callable - -import click -from requests import Session - -from mloader.constants import PageType -from mloader.exporter import ExporterBase -from mloader.response_pb2 import ( - Response, - MangaViewer, - TitleDetailView, - Chapter, - Title, -) -from mloader.utils import chapter_name_to_int - -log = logging.getLogger() - -MangaList = Dict[int, Set[int]] # Title ID: Set[Chapter ID] - - -class MangaLoader: - def __init__( - self, - exporter: Callable[[Title, Chapter, Optional[Chapter]], ExporterBase], - quality: str = "super_high", - split: bool = False, - ): - self.exporter = exporter - self.quality = quality - self.split = split - self._api_url = "https://jumpg-webapi.tokyo-cdn.com" - self.session = Session() - self.session.headers.update( - { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; " - "rv:72.0) Gecko/20100101 Firefox/72.0" - } - ) - - def _decrypt_image(self, url: str, encryption_hex: str) -> bytearray: - resp = self.session.get(url) - data = bytearray(resp.content) - key = bytes.fromhex(encryption_hex) - a = len(key) - for s in range(len(data)): - data[s] ^= key[s % a] - return data - - @lru_cache(None) - def _load_pages(self, chapter_id: Union[str, int]) -> MangaViewer: - resp = self.session.get( - f"{self._api_url}/api/manga_viewer", - params={ - "chapter_id": chapter_id, - "split": "yes" if self.split else "no", - "img_quality": self.quality, - }, - ) - return Response.FromString(resp.content).success.manga_viewer - - @lru_cache(None) - def _get_title_details(self, title_id: Union[str, int]) -> TitleDetailView: - resp = self.session.get( - f"{self._api_url}/api/title_detailV3", params={"title_id": title_id} - ) - return Response.FromString(resp.content).success.title_detail_view - - def _normalize_ids( - self, - title_ids: Collection[int], - chapter_ids: Collection[int], - min_chapter: int, - max_chapter: int, - last_chapter: bool = False, - ) -> MangaList: - # mloader allows you to mix chapters and titles(collections of chapters) - # This method tries to merge them while trying to avoid unnecessary - # http requests - if not any((title_ids, chapter_ids)): - raise ValueError("Expected at least one title or chapter id") - title_ids = set(title_ids or []) - chapter_ids = set(chapter_ids or []) - mangas = {} - chapter_meta = namedtuple("ChapterMeta", "id name") - for cid in chapter_ids: - viewer = self._load_pages(cid) - title_id = viewer.title_id - # Fetching details for this chapter also downloads all other - # visible chapters for the same title. - if title_id in title_ids: - title_ids.remove(title_id) - mangas.setdefault(title_id, []).extend( - chapter_meta(c.chapter_id, c.name) for c in viewer.chapters - ) - else: - mangas.setdefault(title_id, []).append( - chapter_meta(viewer.chapter_id, viewer.chapter_name) - ) - - for tid in title_ids: - details = self._get_title_details(tid) - mangas[tid] = [ - chapter_meta(chapter.chapter_id, chapter.name) - # Skipping mid_chapter_list, since it contains unavailable chapters - for chapter in chain.from_iterable( - chain(group.first_chapter_list, group.last_chapter_list) - for group in details.chapter_list_group - ) - ] - - for tid in mangas: - if last_chapter: - chapters = mangas[tid][-1:] - else: - chapters = [ - c - for c in mangas[tid] - if min_chapter - <= (chapter_name_to_int(c.name) or 0) - <= max_chapter - ] - - mangas[tid] = set(c.id for c in chapters) - - return mangas - - def _download(self, manga_list: MangaList): - manga_num = len(manga_list) - for title_index, (title_id, chapters) in enumerate( - manga_list.items(), 1 - ): - title = self._get_title_details(title_id).title - - title_name = title.name - log.info(f"{title_index}/{manga_num}) Manga: {title_name}") - log.info(" Author: %s", title.author) - - chapter_num = len(chapters) - for chapter_index, chapter_id in enumerate(sorted(chapters), 1): - viewer = self._load_pages(chapter_id) - chapter = viewer.pages[-1].last_page.current_chapter - next_chapter = viewer.pages[-1].last_page.next_chapter - next_chapter = ( - next_chapter if next_chapter.chapter_id != 0 else None - ) - chapter_name = viewer.chapter_name - log.info( - f" {chapter_index}/{chapter_num}) " - f"Chapter {chapter_name}: {chapter.sub_title}" - ) - exporter = self.exporter( - title=title, chapter=chapter, next_chapter=next_chapter - ) - pages = [ - p.manga_page for p in viewer.pages if p.manga_page.image_url - ] - - with click.progressbar( - pages, label=chapter_name, show_pos=True - ) as pbar: - page_counter = count() - for page_index, page in zip(page_counter, pbar): - if PageType(page.type) == PageType.double: - page_index = range(page_index, next(page_counter)) - if not exporter.skip_image(page_index): - # Todo use asyncio + async requests 3 - image_blob = self._decrypt_image( - page.image_url, page.encryption_key - ) - exporter.add_image(image_blob, page_index) - - exporter.close() - - def download( - self, - *, - title_ids: Optional[Collection[int]] = None, - chapter_ids: Optional[Collection[int]] = None, - min_chapter: int, - max_chapter: int, - last_chapter: bool = False, - ): - manga_list = self._normalize_ids( - title_ids, chapter_ids, min_chapter, max_chapter, last_chapter - ) - self._download(manga_list) diff --git a/mloader/main.py b/mloader/main.py new file mode 100644 index 0000000..d31df78 --- /dev/null +++ b/mloader/main.py @@ -0,0 +1,9 @@ +# Ensure logging is set up early +from cli.config import setup_logging +setup_logging() + +# Import the main CLI command. +from cli.main import main + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/mloader/manga_loader/__init__.py b/mloader/manga_loader/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mloader/manga_loader/api.py b/mloader/manga_loader/api.py new file mode 100644 index 0000000..0827bd7 --- /dev/null +++ b/mloader/manga_loader/api.py @@ -0,0 +1,57 @@ +from functools import lru_cache +from typing import Union +from mloader.config import AUTH_PARAMS +from mloader.response_pb2 import Response + + +def _parse_manga_viewer_response(content: bytes) -> 'MangaViewer': + """Parse the API response to extract the MangaViewer object.""" + parsed = Response.FromString(content) + return parsed.success.manga_viewer + + +def _build_title_detail_params(title_id: Union[str, int]) -> dict: + """Assemble the query parameters for the title details API request.""" + return {**AUTH_PARAMS, "title_id": title_id} + + +def _parse_title_detail_response(content: bytes) -> 'TitleDetailView': + """Parse the API response to extract the TitleDetailView object.""" + parsed = Response.FromString(content) + return parsed.success.title_detail_view + + +class APILoaderMixin: + @lru_cache(None) + def _load_pages(self, chapter_id: Union[str, int]) -> 'MangaViewer': + """ + Retrieve and cache manga viewer data for a given chapter. + """ + url = self._build_manga_viewer_url() + params = self._build_manga_viewer_params(chapter_id) + response = self.session.get(url, params=params) + return _parse_manga_viewer_response(response.content) + + def _build_manga_viewer_url(self) -> str: + """Construct the full URL for the manga viewer API endpoint.""" + return f"{self._api_url}/api/manga_viewer" + + def _build_manga_viewer_params(self, chapter_id: Union[str, int]) -> dict: + """Assemble the query parameters for the manga viewer API request.""" + split_value = "yes" if self.split else "no" + return {**AUTH_PARAMS, "chapter_id": chapter_id, "split": split_value, "img_quality": self.quality} + + @lru_cache(None) + def _get_title_details(self, title_id: Union[str, int]) -> 'TitleDetailView': + """ + Retrieve and cache detailed information for a given manga title. + """ + url = self._build_title_detail_url() + params = _build_title_detail_params(title_id) + response = self.session.get(url, params=params) + return _parse_title_detail_response(response.content) + + def _build_title_detail_url(self) -> str: + """Construct the full URL for the title details API endpoint.""" + return f"{self._api_url}/api/title_detailV3" + diff --git a/mloader/manga_loader/decryption.py b/mloader/manga_loader/decryption.py new file mode 100644 index 0000000..d1110b9 --- /dev/null +++ b/mloader/manga_loader/decryption.py @@ -0,0 +1,33 @@ +def _convert_hex_to_bytes(hex_str: str) -> bytes: + """ + Convert a hexadecimal string to bytes. + """ + return bytes.fromhex(hex_str) + + +def _xor_decrypt(data: bytearray, key: bytes) -> bytearray: + """ + Decrypt data using XOR with a repeating key. + """ + key_length = len(key) + for index in range(len(data)): + data[index] ^= key[index % key_length] + return data + + +class DecryptionMixin: + def _decrypt_image(self, url: str, encryption_hex: str) -> bytearray: + """ + Retrieve and decrypt an image using XOR decryption with a repeating key. + """ + encrypted_data = self._fetch_encrypted_data(url) + encryption_key = _convert_hex_to_bytes(encryption_hex) + return _xor_decrypt(encrypted_data, encryption_key) + + def _fetch_encrypted_data(self, url: str) -> bytearray: + """ + Fetch encrypted image data from the provided URL. + """ + response = self.session.get(url) + return bytearray(response.content) + diff --git a/mloader/manga_loader/downloader.py b/mloader/manga_loader/downloader.py new file mode 100644 index 0000000..302d4ed --- /dev/null +++ b/mloader/manga_loader/downloader.py @@ -0,0 +1,117 @@ +from itertools import count +import click +import logging +from mloader.constants import PageType + +log = logging.getLogger(__name__) + + +class DownloadMixin: + def download( + self, + *, + title_ids=None, + chapter_ids=None, + min_chapter: int, + max_chapter: int, + last_chapter: bool = False, + ): + """ + Start the manga download process using the provided filtering parameters. + + Parameters: + title_ids (Optional[Collection[int]]): Collection of manga title IDs. + chapter_ids (Optional[Collection[int]]): Collection of specific chapter IDs. + min_chapter (int): Minimum chapter number to include. + max_chapter (int): Maximum chapter number to include. + last_chapter (bool): If True, only download the last chapter of each title. + """ + normalized_mapping = self._prepare_normalized_manga_list( + title_ids, chapter_ids, min_chapter, max_chapter, last_chapter + ) + self._download(normalized_mapping) + + def _download(self, manga_mapping): + """ + Download and export manga chapters based on the normalized mapping. + + Parameters: + manga_mapping: Mapping of title IDs to sets of chapter IDs. + """ + total_titles = len(manga_mapping) + for title_index, (title_id, chapter_ids) in enumerate(manga_mapping.items(), 1): + self._process_title(title_index, total_titles, title_id, chapter_ids) + + def _process_title(self, title_index, total_titles, title_id, chapter_ids): + """ + Process all chapters for a single manga title. + + Parameters: + title_index (int): Index of the current manga title. + total_titles (int): Total number of manga titles. + title_id: Unique identifier for the manga title. + chapter_ids: Collection of chapter IDs for the title. + """ + title_detail = self._get_title_details(title_id).title # Provided by APILoaderMixin. + log.info(f"{title_index}/{total_titles}) Manga: {title_detail.name}") + log.info(" Author: %s", title_detail.author) + total_chapters = len(chapter_ids) + for chapter_index, chapter_id in enumerate(sorted(chapter_ids), 1): + self._process_chapter(title_detail, chapter_index, total_chapters, chapter_id) + + def _process_chapter(self, title_detail, chapter_index, total_chapters, chapter_id): + """ + Process a single chapter: load pages, log details, and download images. + + Parameters: + title_detail: Object containing the manga title details. + chapter_index (int): The chapter's index for logging. + total_chapters (int): Total number of chapters for the title. + chapter_id: Unique identifier for the chapter. + """ + viewer = self._load_pages(chapter_id) # Provided by APILoaderMixin. + last_page = viewer.pages[-1].last_page + current_chapter = last_page.current_chapter + next_chapter = last_page.next_chapter if last_page.next_chapter.chapter_id != 0 else None + chapter_name = viewer.chapter_name + log.info( + f" {chapter_index}/{total_chapters}) Chapter {chapter_name}: {current_chapter.sub_title}" + ) + + exporter = self.exporter(title=title_detail, chapter=current_chapter, next_chapter=next_chapter) + pages = [page.manga_page for page in viewer.pages if page.manga_page.image_url] + self._process_chapter_pages(pages, chapter_name, exporter) + exporter.close() + + def _process_chapter_pages(self, pages, chapter_name, exporter): + """ + Download and export images for all pages in a chapter with progress indication. + + Parameters: + pages (List): List of page objects that contain image URLs. + chapter_name (str): The chapter's name used for labeling the progress bar. + exporter: An exporter instance to handle the downloaded images. + """ + with click.progressbar(pages, label=chapter_name, show_pos=True) as progress_bar: + page_counter = count() + for page_index, page in zip(page_counter, progress_bar): + # Adjust page index for double-page types. + if PageType(page.type) == PageType.DOUBLE: + page_index = range(page_index, next(page_counter)) + if not exporter.skip_image(page_index): + image_blob = self._download_image(page.image_url) + exporter.add_image(image_blob, page_index) + + def _download_image(self, url: str) -> bytes: + """ + Download an image from the specified URL. + + Parameters: + url (str): The URL of the image to download. + + Returns: + bytes: The binary data of the downloaded image. + """ + response = self.session.get(url) + response.raise_for_status() # Raises an error for bad responses. + return response.content \ No newline at end of file diff --git a/mloader/manga_loader/init.py b/mloader/manga_loader/init.py new file mode 100644 index 0000000..f4f21d9 --- /dev/null +++ b/mloader/manga_loader/init.py @@ -0,0 +1,22 @@ +from .api import APILoaderMixin +from .normalization import NormalizationMixin +from .downloader import DownloadMixin +from .decryption import DecryptionMixin +from requests import Session + +class MangaLoader(APILoaderMixin, NormalizationMixin, DownloadMixin, DecryptionMixin): + """ + Main class for downloading manga. Composes functionality from API calls, + normalization, downloading, and decryption via mixins. + """ + def __init__(self, exporter, quality, split, session=Session(), api_url="https://jumpg-api.tokyo-cdn.com"): + self.exporter = exporter + self.quality = quality + self.split = split + self.session = session + self.session.headers.update( + { + "User-Agent": "JumpPlus/1 CFNetwork/1333.0.4 Darwin/21.5.0", + } + ) + self._api_url = api_url \ No newline at end of file diff --git a/mloader/manga_loader/normalization.py b/mloader/manga_loader/normalization.py new file mode 100644 index 0000000..d2a544c --- /dev/null +++ b/mloader/manga_loader/normalization.py @@ -0,0 +1,77 @@ +from collections import namedtuple +from itertools import chain +from typing import Collection, Dict, Set +from mloader.utils import chapter_name_to_int + +MangaList = Dict[int, Set[int]] + +class NormalizationMixin: + def _normalize_ids( + self, + title_ids: Collection[int], + chapter_ids: Collection[int], + min_chapter: int, + max_chapter: int, + last_chapter: bool = False, + ) -> 'MangaList': + """ + Normalize manga title and chapter IDs into a mapping. + + Merges provided title and chapter IDs into a single mapping where each title ID maps + to a set of chapter IDs that pass filtering based on the chapter range or last-chapter flag. + """ + if not any((title_ids, chapter_ids)): + raise ValueError("Expected at least one title or chapter id") + + remaining_title_ids = set(title_ids or []) + provided_chapter_ids = set(chapter_ids or []) + manga_mapping = {} + ChapterMetadata = namedtuple("ChapterMetadata", "id name") + + for chapter_id in provided_chapter_ids: + viewer = self._load_pages(chapter_id) + current_title_id = viewer.title_id + if current_title_id in remaining_title_ids: + remaining_title_ids.remove(current_title_id) + manga_mapping.setdefault(current_title_id, []).extend( + ChapterMetadata(ch.chapter_id, ch.name) for ch in viewer.chapters + ) + else: + manga_mapping.setdefault(current_title_id, []).append( + ChapterMetadata(viewer.chapter_id, viewer.chapter_name) + ) + + for title_id in remaining_title_ids: + title_details = self._get_title_details(title_id) + manga_mapping[title_id] = [ + ChapterMetadata(ch.chapter_id, ch.name) + for group in title_details.chapter_list_group + for ch in chain( + group.first_chapter_list, group.mid_chapter_list, group.last_chapter_list + ) + ] + + for title_id, chapters in manga_mapping.items(): + if last_chapter: + filtered_chapters = chapters[-1:] + else: + filtered_chapters = [ + ch for ch in chapters + if min_chapter <= (chapter_name_to_int(ch.name) or 0) <= max_chapter + ] + manga_mapping[title_id] = {ch.id for ch in filtered_chapters} + + return manga_mapping + + def _prepare_normalized_manga_list( + self, + title_ids: Collection[int], + chapter_ids: Collection[int], + min_chapter: int, + max_chapter: int, + last_chapter: bool, + ) -> 'MangaList': + """ + Prepare the normalized manga mapping from title and chapter IDs. + """ + return self._normalize_ids(title_ids, chapter_ids, min_chapter, max_chapter, last_chapter) \ No newline at end of file diff --git a/mloader/response_pb2.py b/mloader/response_pb2.py index 3133ed8..cb5c833 100644 --- a/mloader/response_pb2.py +++ b/mloader/response_pb2.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: response.proto - +# Protobuf Python Version: 4.25.3 +"""Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection +from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -13,1749 +14,66 @@ -DESCRIPTOR = _descriptor.FileDescriptor( - name='response.proto', - package='manga', - syntax='proto3', - serialized_options=None, - create_key=_descriptor._internal_create_key, - serialized_pb=b'\n\x0eresponse.proto\x12\x05manga\"P\n\x06\x42\x61nner\x12\x11\n\timage_url\x18\x01 \x01(\t\x12\'\n\x06\x61\x63tion\x18\x02 \x01(\x0b\x32\x17.manga.TransitionAction\x12\n\n\x02id\x18\x03 \x01(\r\"B\n\nBannerList\x12\x14\n\x0c\x62\x61nner_title\x18\x01 \x01(\t\x12\x1e\n\x07\x62\x61nners\x18\x02 \x03(\x0b\x32\r.manga.Banner\"/\n\x10TransitionAction\x12\x0e\n\x06method\x18\x01 \x01(\x05\x12\x0b\n\x03url\x18\x02 \x01(\t\"\xc9\x01\n\x07\x43hapter\x12\x10\n\x08title_id\x18\x01 \x01(\r\x12\x12\n\nchapter_id\x18\x02 \x01(\r\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tsub_title\x18\x04 \x01(\t\x12\x15\n\rthumbnail_url\x18\x05 \x01(\t\x12\x17\n\x0fstart_timestamp\x18\x06 \x01(\r\x12\x15\n\rend_timestamp\x18\x07 \x01(\r\x12\x16\n\x0e\x61lready_viewed\x18\x08 \x01(\x08\x12\x18\n\x10is_vertical_only\x18\t \x01(\x08\"\xa8\x01\n\x0c\x43hapterGroup\x12\x17\n\x0f\x63hapter_numbers\x18\x01 \x01(\t\x12*\n\x12\x66irst_chapter_list\x18\x02 \x03(\x0b\x32\x0e.manga.Chapter\x12(\n\x10mid_chapter_list\x18\x03 \x03(\x0b\x32\x0e.manga.Chapter\x12)\n\x11last_chapter_list\x18\x04 \x03(\x0b\x32\x0e.manga.Chapter\"\xaf\x01\n\x07\x43omment\x12\n\n\x02id\x18\x01 \x01(\r\x12\r\n\x05index\x18\x02 \x01(\r\x12\x11\n\tuser_name\x18\x03 \x01(\t\x12\x10\n\x08icon_url\x18\x04 \x01(\t\x12\x15\n\ris_my_comment\x18\x06 \x01(\x08\x12\x15\n\ralready_liked\x18\x07 \x01(\x08\x12\x17\n\x0fnumber_of_likes\x18\t \x01(\r\x12\x0c\n\x04\x62ody\x18\n \x01(\t\x12\x0f\n\x07\x63reated\x18\x0b \x01(\r\"\xfa\x03\n\rAdNetworkList\x12\x33\n\x0b\x61\x64_networks\x18\x01 \x01(\x0b\x32\x1e.manga.AdNetworkList.AdNetwork\x1a\xb3\x03\n\tAdNetwork\x12\x39\n\x08\x66\x61\x63\x65\x62ook\x18\x01 \x01(\x0b\x32\'.manga.AdNetworkList.AdNetwork.Facebook\x12\x33\n\x05\x61\x64mob\x18\x02 \x01(\x0b\x32$.manga.AdNetworkList.AdNetwork.Admob\x12\x33\n\x05mopub\x18\x03 \x01(\x0b\x32$.manga.AdNetworkList.AdNetwork.Mopub\x12\x37\n\x07\x61\x64sense\x18\x04 \x01(\x0b\x32&.manga.AdNetworkList.AdNetwork.Adsense\x12\x39\n\x08\x61pplovin\x18\x05 \x01(\x0b\x32\'.manga.AdNetworkList.AdNetwork.Applovin\x1a \n\x08\x46\x61\x63\x65\x62ook\x12\x14\n\x0cplacement_id\x18\x01 \x01(\t\x1a\x18\n\x05\x41\x64mob\x12\x0f\n\x07unit_id\x18\x01 \x01(\t\x1a\x18\n\x05Mopub\x12\x0f\n\x07unit_id\x18\x01 \x01(\t\x1a\x1a\n\x07\x41\x64sense\x12\x0f\n\x07unit_id\x18\x01 \x01(\t\x1a\x1b\n\x08\x41pplovin\x12\x0f\n\x07unit_id\x18\x01 \x01(\t\"\xb8\x04\n\x05Popup\x12*\n\nos_default\x18\x01 \x01(\x0b\x32\x16.manga.Popup.OSDefault\x12,\n\x0b\x61pp_default\x18\x02 \x01(\x0b\x32\x17.manga.Popup.AppDefault\x12.\n\x0cmovie_reward\x18\x03 \x01(\x0b\x32\x18.manga.Popup.MovieReward\x1a?\n\x06\x42utton\x12\x0c\n\x04text\x18\x01 \x01(\t\x12\'\n\x06\x61\x63tion\x18\x02 \x01(\x0b\x32\x17.manga.TransitionAction\x1a\xab\x01\n\tOSDefault\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x0c\n\x04\x62ody\x18\x02 \x01(\t\x12&\n\tok_button\x18\x03 \x01(\x0b\x32\x13.manga.Popup.Button\x12+\n\x0eneutral_button\x18\x04 \x01(\x0b\x32\x13.manga.Popup.Button\x12*\n\rcancel_button\x18\x05 \x01(\x0b\x32\x13.manga.Popup.Button\x1ag\n\nAppDefault\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x0c\n\x04\x62ody\x18\x02 \x01(\t\x12\'\n\x06\x61\x63tion\x18\x03 \x01(\x0b\x32\x17.manga.TransitionAction\x12\x11\n\timage_url\x18\x04 \x01(\t\x1aM\n\x0bMovieReward\x12\x11\n\timage_url\x18\x01 \x01(\t\x12+\n\radvertisement\x18\x02 \x01(\x0b\x32\x14.manga.AdNetworkList\"\x95\x02\n\x08LastPage\x12\'\n\x0f\x63urrent_chapter\x18\x01 \x01(\x0b\x32\x0e.manga.Chapter\x12$\n\x0cnext_chapter\x18\x02 \x01(\x0b\x32\x0e.manga.Chapter\x12$\n\x0ctop_comments\x18\x03 \x03(\x0b\x32\x0e.manga.Comment\x12\x15\n\ris_subscribed\x18\x04 \x01(\x08\x12\x16\n\x0enext_timestamp\x18\x05 \x01(\r\x12\x14\n\x0c\x63hapter_type\x18\x06 \x01(\x05\x12+\n\radvertisement\x18\x07 \x01(\x0b\x32\x14.manga.AdNetworkList\x12\"\n\x0cmovie_reward\x18\x08 \x01(\x0b\x32\x0c.manga.Popup\"c\n\tMangaPage\x12\x11\n\timage_url\x18\x01 \x01(\t\x12\r\n\x05width\x18\x02 \x01(\r\x12\x0e\n\x06height\x18\x03 \x01(\r\x12\x0c\n\x04type\x18\x04 \x01(\x05\x12\x16\n\x0e\x65ncryption_key\x18\x05 \x01(\t\"\xa5\x01\n\x04Page\x12$\n\nmanga_page\x18\x01 \x01(\x0b\x32\x10.manga.MangaPage\x12&\n\x0b\x62\x61nner_list\x18\x02 \x01(\x0b\x32\x11.manga.BannerList\x12\"\n\tlast_page\x18\x03 \x01(\x0b\x32\x0f.manga.LastPage\x12+\n\radvertisement\x18\x04 \x01(\x0b\x32\x14.manga.AdNetworkList\" \n\x03Sns\x12\x0c\n\x04\x62ody\x18\x01 \x01(\t\x12\x0b\n\x03url\x18\x02 \x01(\t\"\x84\x02\n\x0bMangaViewer\x12\x1a\n\x05pages\x18\x01 \x03(\x0b\x32\x0b.manga.Page\x12\x12\n\nchapter_id\x18\x02 \x01(\r\x12 \n\x08\x63hapters\x18\x03 \x03(\x0b\x32\x0e.manga.Chapter\x12\x17\n\x03sns\x18\x04 \x01(\x0b\x32\n.manga.Sns\x12\x12\n\ntitle_name\x18\x05 \x01(\t\x12\x14\n\x0c\x63hapter_name\x18\x06 \x01(\t\x12\x1a\n\x12number_of_comments\x18\x07 \x01(\r\x12\x18\n\x10is_vertical_only\x18\x08 \x01(\x08\x12\x10\n\x08title_id\x18\t \x01(\r\x12\x18\n\x10start_from_right\x18\n \x01(\x08\"\x96\x01\n\x05Title\x12\x10\n\x08title_id\x18\x01 \x01(\r\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06\x61uthor\x18\x03 \x01(\t\x12\x1a\n\x12portrait_image_url\x18\x04 \x01(\t\x12\x1b\n\x13landscape_image_url\x18\x05 \x01(\t\x12\x12\n\nview_count\x18\x06 \x01(\r\x12\x10\n\x08language\x18\x07 \x01(\x05\"\xce\x04\n\x0fTitleDetailView\x12\x1b\n\x05title\x18\x01 \x01(\x0b\x32\x0c.manga.Title\x12\x17\n\x0ftitle_image_url\x18\x02 \x01(\t\x12\x10\n\x08overview\x18\x03 \x01(\t\x12\x1c\n\x14\x62\x61\x63kground_image_url\x18\x04 \x01(\t\x12\x16\n\x0enext_timestamp\x18\x05 \x01(\r\x12\x15\n\rupdate_timing\x18\x06 \x01(\x05\x12\"\n\x1aviewing_period_description\x18\x07 \x01(\t\x12\x1b\n\x13non_appearance_info\x18\x08 \x01(\t\x12*\n\x12\x66irst_chapter_list\x18\t \x03(\x0b\x32\x0e.manga.Chapter\x12)\n\x11last_chapter_list\x18\n \x03(\x0b\x32\x0e.manga.Chapter\x12\x1e\n\x07\x62\x61nners\x18\x0b \x03(\x0b\x32\r.manga.Banner\x12,\n\x16recommended_title_list\x18\x0c \x03(\x0b\x32\x0c.manga.Title\x12\x17\n\x03sns\x18\r \x01(\x0b\x32\n.manga.Sns\x12\x19\n\x11is_simul_released\x18\x0e \x01(\x08\x12\x15\n\ris_subscribed\x18\x0f \x01(\x08\x12\x0e\n\x06rating\x18\x10 \x01(\x05\x12\x1b\n\x13\x63hapters_descending\x18\x11 \x01(\x08\x12\x17\n\x0fnumber_of_views\x18\x12 \x01(\r\x12/\n\x12\x63hapter_list_group\x18\x1c \x03(\x0b\x32\x13.manga.ChapterGroup\"l\n\rSuccessResult\x12\x31\n\x11title_detail_view\x18\x08 \x01(\x0b\x32\x16.manga.TitleDetailView\x12(\n\x0cmanga_viewer\x18\n \x01(\x0b\x32\x12.manga.MangaViewer\"1\n\x08Response\x12%\n\x07success\x18\x01 \x01(\x0b\x32\x14.manga.SuccessResultb\x06proto3' -) - - - - -_BANNER = _descriptor.Descriptor( - name='Banner', - full_name='manga.Banner', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='image_url', full_name='manga.Banner.image_url', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='action', full_name='manga.Banner.action', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='id', full_name='manga.Banner.id', index=2, - number=3, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=25, - serialized_end=105, -) - - -_BANNERLIST = _descriptor.Descriptor( - name='BannerList', - full_name='manga.BannerList', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='banner_title', full_name='manga.BannerList.banner_title', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='banners', full_name='manga.BannerList.banners', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=107, - serialized_end=173, -) - - -_TRANSITIONACTION = _descriptor.Descriptor( - name='TransitionAction', - full_name='manga.TransitionAction', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='method', full_name='manga.TransitionAction.method', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='url', full_name='manga.TransitionAction.url', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=175, - serialized_end=222, -) - - -_CHAPTER = _descriptor.Descriptor( - name='Chapter', - full_name='manga.Chapter', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='title_id', full_name='manga.Chapter.title_id', index=0, - number=1, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='chapter_id', full_name='manga.Chapter.chapter_id', index=1, - number=2, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='name', full_name='manga.Chapter.name', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='sub_title', full_name='manga.Chapter.sub_title', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='thumbnail_url', full_name='manga.Chapter.thumbnail_url', index=4, - number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='start_timestamp', full_name='manga.Chapter.start_timestamp', index=5, - number=6, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='end_timestamp', full_name='manga.Chapter.end_timestamp', index=6, - number=7, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='already_viewed', full_name='manga.Chapter.already_viewed', index=7, - number=8, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='is_vertical_only', full_name='manga.Chapter.is_vertical_only', index=8, - number=9, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=225, - serialized_end=426, -) - - -_CHAPTERGROUP = _descriptor.Descriptor( - name='ChapterGroup', - full_name='manga.ChapterGroup', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='chapter_numbers', full_name='manga.ChapterGroup.chapter_numbers', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='first_chapter_list', full_name='manga.ChapterGroup.first_chapter_list', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='mid_chapter_list', full_name='manga.ChapterGroup.mid_chapter_list', index=2, - number=3, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='last_chapter_list', full_name='manga.ChapterGroup.last_chapter_list', index=3, - number=4, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=429, - serialized_end=597, -) - - -_COMMENT = _descriptor.Descriptor( - name='Comment', - full_name='manga.Comment', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='manga.Comment.id', index=0, - number=1, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='index', full_name='manga.Comment.index', index=1, - number=2, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='user_name', full_name='manga.Comment.user_name', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='icon_url', full_name='manga.Comment.icon_url', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='is_my_comment', full_name='manga.Comment.is_my_comment', index=4, - number=6, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='already_liked', full_name='manga.Comment.already_liked', index=5, - number=7, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='number_of_likes', full_name='manga.Comment.number_of_likes', index=6, - number=9, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='body', full_name='manga.Comment.body', index=7, - number=10, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='created', full_name='manga.Comment.created', index=8, - number=11, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=600, - serialized_end=775, -) - - -_ADNETWORKLIST_ADNETWORK_FACEBOOK = _descriptor.Descriptor( - name='Facebook', - full_name='manga.AdNetworkList.AdNetwork.Facebook', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='placement_id', full_name='manga.AdNetworkList.AdNetwork.Facebook.placement_id', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1143, - serialized_end=1175, -) - -_ADNETWORKLIST_ADNETWORK_ADMOB = _descriptor.Descriptor( - name='Admob', - full_name='manga.AdNetworkList.AdNetwork.Admob', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='unit_id', full_name='manga.AdNetworkList.AdNetwork.Admob.unit_id', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1177, - serialized_end=1201, -) - -_ADNETWORKLIST_ADNETWORK_MOPUB = _descriptor.Descriptor( - name='Mopub', - full_name='manga.AdNetworkList.AdNetwork.Mopub', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='unit_id', full_name='manga.AdNetworkList.AdNetwork.Mopub.unit_id', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1203, - serialized_end=1227, -) - -_ADNETWORKLIST_ADNETWORK_ADSENSE = _descriptor.Descriptor( - name='Adsense', - full_name='manga.AdNetworkList.AdNetwork.Adsense', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='unit_id', full_name='manga.AdNetworkList.AdNetwork.Adsense.unit_id', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1229, - serialized_end=1255, -) - -_ADNETWORKLIST_ADNETWORK_APPLOVIN = _descriptor.Descriptor( - name='Applovin', - full_name='manga.AdNetworkList.AdNetwork.Applovin', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='unit_id', full_name='manga.AdNetworkList.AdNetwork.Applovin.unit_id', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1257, - serialized_end=1284, -) - -_ADNETWORKLIST_ADNETWORK = _descriptor.Descriptor( - name='AdNetwork', - full_name='manga.AdNetworkList.AdNetwork', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='facebook', full_name='manga.AdNetworkList.AdNetwork.facebook', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='admob', full_name='manga.AdNetworkList.AdNetwork.admob', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='mopub', full_name='manga.AdNetworkList.AdNetwork.mopub', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='adsense', full_name='manga.AdNetworkList.AdNetwork.adsense', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='applovin', full_name='manga.AdNetworkList.AdNetwork.applovin', index=4, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[_ADNETWORKLIST_ADNETWORK_FACEBOOK, _ADNETWORKLIST_ADNETWORK_ADMOB, _ADNETWORKLIST_ADNETWORK_MOPUB, _ADNETWORKLIST_ADNETWORK_ADSENSE, _ADNETWORKLIST_ADNETWORK_APPLOVIN, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=849, - serialized_end=1284, -) - -_ADNETWORKLIST = _descriptor.Descriptor( - name='AdNetworkList', - full_name='manga.AdNetworkList', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='ad_networks', full_name='manga.AdNetworkList.ad_networks', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[_ADNETWORKLIST_ADNETWORK, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=778, - serialized_end=1284, -) - - -_POPUP_BUTTON = _descriptor.Descriptor( - name='Button', - full_name='manga.Popup.Button', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='text', full_name='manga.Popup.Button.text', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='action', full_name='manga.Popup.Button.action', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1434, - serialized_end=1497, -) - -_POPUP_OSDEFAULT = _descriptor.Descriptor( - name='OSDefault', - full_name='manga.Popup.OSDefault', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='subject', full_name='manga.Popup.OSDefault.subject', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='body', full_name='manga.Popup.OSDefault.body', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='ok_button', full_name='manga.Popup.OSDefault.ok_button', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='neutral_button', full_name='manga.Popup.OSDefault.neutral_button', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='cancel_button', full_name='manga.Popup.OSDefault.cancel_button', index=4, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1500, - serialized_end=1671, -) - -_POPUP_APPDEFAULT = _descriptor.Descriptor( - name='AppDefault', - full_name='manga.Popup.AppDefault', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='subject', full_name='manga.Popup.AppDefault.subject', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='body', full_name='manga.Popup.AppDefault.body', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='action', full_name='manga.Popup.AppDefault.action', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='image_url', full_name='manga.Popup.AppDefault.image_url', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1673, - serialized_end=1776, -) - -_POPUP_MOVIEREWARD = _descriptor.Descriptor( - name='MovieReward', - full_name='manga.Popup.MovieReward', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='image_url', full_name='manga.Popup.MovieReward.image_url', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='advertisement', full_name='manga.Popup.MovieReward.advertisement', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1778, - serialized_end=1855, -) - -_POPUP = _descriptor.Descriptor( - name='Popup', - full_name='manga.Popup', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='os_default', full_name='manga.Popup.os_default', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='app_default', full_name='manga.Popup.app_default', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='movie_reward', full_name='manga.Popup.movie_reward', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[_POPUP_BUTTON, _POPUP_OSDEFAULT, _POPUP_APPDEFAULT, _POPUP_MOVIEREWARD, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1287, - serialized_end=1855, -) - - -_LASTPAGE = _descriptor.Descriptor( - name='LastPage', - full_name='manga.LastPage', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='current_chapter', full_name='manga.LastPage.current_chapter', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='next_chapter', full_name='manga.LastPage.next_chapter', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='top_comments', full_name='manga.LastPage.top_comments', index=2, - number=3, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='is_subscribed', full_name='manga.LastPage.is_subscribed', index=3, - number=4, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='next_timestamp', full_name='manga.LastPage.next_timestamp', index=4, - number=5, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='chapter_type', full_name='manga.LastPage.chapter_type', index=5, - number=6, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='advertisement', full_name='manga.LastPage.advertisement', index=6, - number=7, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='movie_reward', full_name='manga.LastPage.movie_reward', index=7, - number=8, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1858, - serialized_end=2135, -) - - -_MANGAPAGE = _descriptor.Descriptor( - name='MangaPage', - full_name='manga.MangaPage', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='image_url', full_name='manga.MangaPage.image_url', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='width', full_name='manga.MangaPage.width', index=1, - number=2, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='height', full_name='manga.MangaPage.height', index=2, - number=3, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='type', full_name='manga.MangaPage.type', index=3, - number=4, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='encryption_key', full_name='manga.MangaPage.encryption_key', index=4, - number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2137, - serialized_end=2236, -) - - -_PAGE = _descriptor.Descriptor( - name='Page', - full_name='manga.Page', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='manga_page', full_name='manga.Page.manga_page', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='banner_list', full_name='manga.Page.banner_list', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='last_page', full_name='manga.Page.last_page', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='advertisement', full_name='manga.Page.advertisement', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2239, - serialized_end=2404, -) - - -_SNS = _descriptor.Descriptor( - name='Sns', - full_name='manga.Sns', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='body', full_name='manga.Sns.body', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='url', full_name='manga.Sns.url', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2406, - serialized_end=2438, -) - - -_MANGAVIEWER = _descriptor.Descriptor( - name='MangaViewer', - full_name='manga.MangaViewer', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='pages', full_name='manga.MangaViewer.pages', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='chapter_id', full_name='manga.MangaViewer.chapter_id', index=1, - number=2, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='chapters', full_name='manga.MangaViewer.chapters', index=2, - number=3, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='sns', full_name='manga.MangaViewer.sns', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='title_name', full_name='manga.MangaViewer.title_name', index=4, - number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='chapter_name', full_name='manga.MangaViewer.chapter_name', index=5, - number=6, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='number_of_comments', full_name='manga.MangaViewer.number_of_comments', index=6, - number=7, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='is_vertical_only', full_name='manga.MangaViewer.is_vertical_only', index=7, - number=8, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='title_id', full_name='manga.MangaViewer.title_id', index=8, - number=9, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='start_from_right', full_name='manga.MangaViewer.start_from_right', index=9, - number=10, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2441, - serialized_end=2701, -) - - -_TITLE = _descriptor.Descriptor( - name='Title', - full_name='manga.Title', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='title_id', full_name='manga.Title.title_id', index=0, - number=1, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='name', full_name='manga.Title.name', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='author', full_name='manga.Title.author', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='portrait_image_url', full_name='manga.Title.portrait_image_url', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='landscape_image_url', full_name='manga.Title.landscape_image_url', index=4, - number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='view_count', full_name='manga.Title.view_count', index=5, - number=6, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='language', full_name='manga.Title.language', index=6, - number=7, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2704, - serialized_end=2854, -) - - -_TITLEDETAILVIEW = _descriptor.Descriptor( - name='TitleDetailView', - full_name='manga.TitleDetailView', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='title', full_name='manga.TitleDetailView.title', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='title_image_url', full_name='manga.TitleDetailView.title_image_url', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='overview', full_name='manga.TitleDetailView.overview', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='background_image_url', full_name='manga.TitleDetailView.background_image_url', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='next_timestamp', full_name='manga.TitleDetailView.next_timestamp', index=4, - number=5, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='update_timing', full_name='manga.TitleDetailView.update_timing', index=5, - number=6, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='viewing_period_description', full_name='manga.TitleDetailView.viewing_period_description', index=6, - number=7, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='non_appearance_info', full_name='manga.TitleDetailView.non_appearance_info', index=7, - number=8, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='first_chapter_list', full_name='manga.TitleDetailView.first_chapter_list', index=8, - number=9, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='last_chapter_list', full_name='manga.TitleDetailView.last_chapter_list', index=9, - number=10, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='banners', full_name='manga.TitleDetailView.banners', index=10, - number=11, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='recommended_title_list', full_name='manga.TitleDetailView.recommended_title_list', index=11, - number=12, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='sns', full_name='manga.TitleDetailView.sns', index=12, - number=13, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='is_simul_released', full_name='manga.TitleDetailView.is_simul_released', index=13, - number=14, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='is_subscribed', full_name='manga.TitleDetailView.is_subscribed', index=14, - number=15, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='rating', full_name='manga.TitleDetailView.rating', index=15, - number=16, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='chapters_descending', full_name='manga.TitleDetailView.chapters_descending', index=16, - number=17, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='number_of_views', full_name='manga.TitleDetailView.number_of_views', index=17, - number=18, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='chapter_list_group', full_name='manga.TitleDetailView.chapter_list_group', index=18, - number=28, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2857, - serialized_end=3447, -) - - -_SUCCESSRESULT = _descriptor.Descriptor( - name='SuccessResult', - full_name='manga.SuccessResult', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='title_detail_view', full_name='manga.SuccessResult.title_detail_view', index=0, - number=8, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='manga_viewer', full_name='manga.SuccessResult.manga_viewer', index=1, - number=10, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3449, - serialized_end=3557, -) - - -_RESPONSE = _descriptor.Descriptor( - name='Response', - full_name='manga.Response', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='success', full_name='manga.Response.success', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3559, - serialized_end=3608, -) - -_BANNER.fields_by_name['action'].message_type = _TRANSITIONACTION -_BANNERLIST.fields_by_name['banners'].message_type = _BANNER -_CHAPTERGROUP.fields_by_name['first_chapter_list'].message_type = _CHAPTER -_CHAPTERGROUP.fields_by_name['mid_chapter_list'].message_type = _CHAPTER -_CHAPTERGROUP.fields_by_name['last_chapter_list'].message_type = _CHAPTER -_ADNETWORKLIST_ADNETWORK_FACEBOOK.containing_type = _ADNETWORKLIST_ADNETWORK -_ADNETWORKLIST_ADNETWORK_ADMOB.containing_type = _ADNETWORKLIST_ADNETWORK -_ADNETWORKLIST_ADNETWORK_MOPUB.containing_type = _ADNETWORKLIST_ADNETWORK -_ADNETWORKLIST_ADNETWORK_ADSENSE.containing_type = _ADNETWORKLIST_ADNETWORK -_ADNETWORKLIST_ADNETWORK_APPLOVIN.containing_type = _ADNETWORKLIST_ADNETWORK -_ADNETWORKLIST_ADNETWORK.fields_by_name['facebook'].message_type = _ADNETWORKLIST_ADNETWORK_FACEBOOK -_ADNETWORKLIST_ADNETWORK.fields_by_name['admob'].message_type = _ADNETWORKLIST_ADNETWORK_ADMOB -_ADNETWORKLIST_ADNETWORK.fields_by_name['mopub'].message_type = _ADNETWORKLIST_ADNETWORK_MOPUB -_ADNETWORKLIST_ADNETWORK.fields_by_name['adsense'].message_type = _ADNETWORKLIST_ADNETWORK_ADSENSE -_ADNETWORKLIST_ADNETWORK.fields_by_name['applovin'].message_type = _ADNETWORKLIST_ADNETWORK_APPLOVIN -_ADNETWORKLIST_ADNETWORK.containing_type = _ADNETWORKLIST -_ADNETWORKLIST.fields_by_name['ad_networks'].message_type = _ADNETWORKLIST_ADNETWORK -_POPUP_BUTTON.fields_by_name['action'].message_type = _TRANSITIONACTION -_POPUP_BUTTON.containing_type = _POPUP -_POPUP_OSDEFAULT.fields_by_name['ok_button'].message_type = _POPUP_BUTTON -_POPUP_OSDEFAULT.fields_by_name['neutral_button'].message_type = _POPUP_BUTTON -_POPUP_OSDEFAULT.fields_by_name['cancel_button'].message_type = _POPUP_BUTTON -_POPUP_OSDEFAULT.containing_type = _POPUP -_POPUP_APPDEFAULT.fields_by_name['action'].message_type = _TRANSITIONACTION -_POPUP_APPDEFAULT.containing_type = _POPUP -_POPUP_MOVIEREWARD.fields_by_name['advertisement'].message_type = _ADNETWORKLIST -_POPUP_MOVIEREWARD.containing_type = _POPUP -_POPUP.fields_by_name['os_default'].message_type = _POPUP_OSDEFAULT -_POPUP.fields_by_name['app_default'].message_type = _POPUP_APPDEFAULT -_POPUP.fields_by_name['movie_reward'].message_type = _POPUP_MOVIEREWARD -_LASTPAGE.fields_by_name['current_chapter'].message_type = _CHAPTER -_LASTPAGE.fields_by_name['next_chapter'].message_type = _CHAPTER -_LASTPAGE.fields_by_name['top_comments'].message_type = _COMMENT -_LASTPAGE.fields_by_name['advertisement'].message_type = _ADNETWORKLIST -_LASTPAGE.fields_by_name['movie_reward'].message_type = _POPUP -_PAGE.fields_by_name['manga_page'].message_type = _MANGAPAGE -_PAGE.fields_by_name['banner_list'].message_type = _BANNERLIST -_PAGE.fields_by_name['last_page'].message_type = _LASTPAGE -_PAGE.fields_by_name['advertisement'].message_type = _ADNETWORKLIST -_MANGAVIEWER.fields_by_name['pages'].message_type = _PAGE -_MANGAVIEWER.fields_by_name['chapters'].message_type = _CHAPTER -_MANGAVIEWER.fields_by_name['sns'].message_type = _SNS -_TITLEDETAILVIEW.fields_by_name['title'].message_type = _TITLE -_TITLEDETAILVIEW.fields_by_name['first_chapter_list'].message_type = _CHAPTER -_TITLEDETAILVIEW.fields_by_name['last_chapter_list'].message_type = _CHAPTER -_TITLEDETAILVIEW.fields_by_name['banners'].message_type = _BANNER -_TITLEDETAILVIEW.fields_by_name['recommended_title_list'].message_type = _TITLE -_TITLEDETAILVIEW.fields_by_name['sns'].message_type = _SNS -_TITLEDETAILVIEW.fields_by_name['chapter_list_group'].message_type = _CHAPTERGROUP -_SUCCESSRESULT.fields_by_name['title_detail_view'].message_type = _TITLEDETAILVIEW -_SUCCESSRESULT.fields_by_name['manga_viewer'].message_type = _MANGAVIEWER -_RESPONSE.fields_by_name['success'].message_type = _SUCCESSRESULT -DESCRIPTOR.message_types_by_name['Banner'] = _BANNER -DESCRIPTOR.message_types_by_name['BannerList'] = _BANNERLIST -DESCRIPTOR.message_types_by_name['TransitionAction'] = _TRANSITIONACTION -DESCRIPTOR.message_types_by_name['Chapter'] = _CHAPTER -DESCRIPTOR.message_types_by_name['ChapterGroup'] = _CHAPTERGROUP -DESCRIPTOR.message_types_by_name['Comment'] = _COMMENT -DESCRIPTOR.message_types_by_name['AdNetworkList'] = _ADNETWORKLIST -DESCRIPTOR.message_types_by_name['Popup'] = _POPUP -DESCRIPTOR.message_types_by_name['LastPage'] = _LASTPAGE -DESCRIPTOR.message_types_by_name['MangaPage'] = _MANGAPAGE -DESCRIPTOR.message_types_by_name['Page'] = _PAGE -DESCRIPTOR.message_types_by_name['Sns'] = _SNS -DESCRIPTOR.message_types_by_name['MangaViewer'] = _MANGAVIEWER -DESCRIPTOR.message_types_by_name['Title'] = _TITLE -DESCRIPTOR.message_types_by_name['TitleDetailView'] = _TITLEDETAILVIEW -DESCRIPTOR.message_types_by_name['SuccessResult'] = _SUCCESSRESULT -DESCRIPTOR.message_types_by_name['Response'] = _RESPONSE -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -Banner = _reflection.GeneratedProtocolMessageType('Banner', (_message.Message,), { - 'DESCRIPTOR' : _BANNER, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.Banner) - }) -_sym_db.RegisterMessage(Banner) - -BannerList = _reflection.GeneratedProtocolMessageType('BannerList', (_message.Message,), { - 'DESCRIPTOR' : _BANNERLIST, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.BannerList) - }) -_sym_db.RegisterMessage(BannerList) - -TransitionAction = _reflection.GeneratedProtocolMessageType('TransitionAction', (_message.Message,), { - 'DESCRIPTOR' : _TRANSITIONACTION, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.TransitionAction) - }) -_sym_db.RegisterMessage(TransitionAction) - -Chapter = _reflection.GeneratedProtocolMessageType('Chapter', (_message.Message,), { - 'DESCRIPTOR' : _CHAPTER, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.Chapter) - }) -_sym_db.RegisterMessage(Chapter) - -ChapterGroup = _reflection.GeneratedProtocolMessageType('ChapterGroup', (_message.Message,), { - 'DESCRIPTOR' : _CHAPTERGROUP, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.ChapterGroup) - }) -_sym_db.RegisterMessage(ChapterGroup) - -Comment = _reflection.GeneratedProtocolMessageType('Comment', (_message.Message,), { - 'DESCRIPTOR' : _COMMENT, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.Comment) - }) -_sym_db.RegisterMessage(Comment) - -AdNetworkList = _reflection.GeneratedProtocolMessageType('AdNetworkList', (_message.Message,), { - - 'AdNetwork' : _reflection.GeneratedProtocolMessageType('AdNetwork', (_message.Message,), { - - 'Facebook' : _reflection.GeneratedProtocolMessageType('Facebook', (_message.Message,), { - 'DESCRIPTOR' : _ADNETWORKLIST_ADNETWORK_FACEBOOK, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.AdNetworkList.AdNetwork.Facebook) - }) - , - - 'Admob' : _reflection.GeneratedProtocolMessageType('Admob', (_message.Message,), { - 'DESCRIPTOR' : _ADNETWORKLIST_ADNETWORK_ADMOB, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.AdNetworkList.AdNetwork.Admob) - }) - , - - 'Mopub' : _reflection.GeneratedProtocolMessageType('Mopub', (_message.Message,), { - 'DESCRIPTOR' : _ADNETWORKLIST_ADNETWORK_MOPUB, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.AdNetworkList.AdNetwork.Mopub) - }) - , - - 'Adsense' : _reflection.GeneratedProtocolMessageType('Adsense', (_message.Message,), { - 'DESCRIPTOR' : _ADNETWORKLIST_ADNETWORK_ADSENSE, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.AdNetworkList.AdNetwork.Adsense) - }) - , - - 'Applovin' : _reflection.GeneratedProtocolMessageType('Applovin', (_message.Message,), { - 'DESCRIPTOR' : _ADNETWORKLIST_ADNETWORK_APPLOVIN, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.AdNetworkList.AdNetwork.Applovin) - }) - , - 'DESCRIPTOR' : _ADNETWORKLIST_ADNETWORK, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.AdNetworkList.AdNetwork) - }) - , - 'DESCRIPTOR' : _ADNETWORKLIST, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.AdNetworkList) - }) -_sym_db.RegisterMessage(AdNetworkList) -_sym_db.RegisterMessage(AdNetworkList.AdNetwork) -_sym_db.RegisterMessage(AdNetworkList.AdNetwork.Facebook) -_sym_db.RegisterMessage(AdNetworkList.AdNetwork.Admob) -_sym_db.RegisterMessage(AdNetworkList.AdNetwork.Mopub) -_sym_db.RegisterMessage(AdNetworkList.AdNetwork.Adsense) -_sym_db.RegisterMessage(AdNetworkList.AdNetwork.Applovin) - -Popup = _reflection.GeneratedProtocolMessageType('Popup', (_message.Message,), { - - 'Button' : _reflection.GeneratedProtocolMessageType('Button', (_message.Message,), { - 'DESCRIPTOR' : _POPUP_BUTTON, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.Popup.Button) - }) - , - - 'OSDefault' : _reflection.GeneratedProtocolMessageType('OSDefault', (_message.Message,), { - 'DESCRIPTOR' : _POPUP_OSDEFAULT, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.Popup.OSDefault) - }) - , - - 'AppDefault' : _reflection.GeneratedProtocolMessageType('AppDefault', (_message.Message,), { - 'DESCRIPTOR' : _POPUP_APPDEFAULT, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.Popup.AppDefault) - }) - , - - 'MovieReward' : _reflection.GeneratedProtocolMessageType('MovieReward', (_message.Message,), { - 'DESCRIPTOR' : _POPUP_MOVIEREWARD, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.Popup.MovieReward) - }) - , - 'DESCRIPTOR' : _POPUP, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.Popup) - }) -_sym_db.RegisterMessage(Popup) -_sym_db.RegisterMessage(Popup.Button) -_sym_db.RegisterMessage(Popup.OSDefault) -_sym_db.RegisterMessage(Popup.AppDefault) -_sym_db.RegisterMessage(Popup.MovieReward) - -LastPage = _reflection.GeneratedProtocolMessageType('LastPage', (_message.Message,), { - 'DESCRIPTOR' : _LASTPAGE, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.LastPage) - }) -_sym_db.RegisterMessage(LastPage) - -MangaPage = _reflection.GeneratedProtocolMessageType('MangaPage', (_message.Message,), { - 'DESCRIPTOR' : _MANGAPAGE, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.MangaPage) - }) -_sym_db.RegisterMessage(MangaPage) - -Page = _reflection.GeneratedProtocolMessageType('Page', (_message.Message,), { - 'DESCRIPTOR' : _PAGE, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.Page) - }) -_sym_db.RegisterMessage(Page) - -Sns = _reflection.GeneratedProtocolMessageType('Sns', (_message.Message,), { - 'DESCRIPTOR' : _SNS, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.Sns) - }) -_sym_db.RegisterMessage(Sns) - -MangaViewer = _reflection.GeneratedProtocolMessageType('MangaViewer', (_message.Message,), { - 'DESCRIPTOR' : _MANGAVIEWER, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.MangaViewer) - }) -_sym_db.RegisterMessage(MangaViewer) - -Title = _reflection.GeneratedProtocolMessageType('Title', (_message.Message,), { - 'DESCRIPTOR' : _TITLE, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.Title) - }) -_sym_db.RegisterMessage(Title) - -TitleDetailView = _reflection.GeneratedProtocolMessageType('TitleDetailView', (_message.Message,), { - 'DESCRIPTOR' : _TITLEDETAILVIEW, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.TitleDetailView) - }) -_sym_db.RegisterMessage(TitleDetailView) - -SuccessResult = _reflection.GeneratedProtocolMessageType('SuccessResult', (_message.Message,), { - 'DESCRIPTOR' : _SUCCESSRESULT, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.SuccessResult) - }) -_sym_db.RegisterMessage(SuccessResult) - -Response = _reflection.GeneratedProtocolMessageType('Response', (_message.Message,), { - 'DESCRIPTOR' : _RESPONSE, - '__module__' : 'response_pb2' - # @@protoc_insertion_point(class_scope:manga.Response) - }) -_sym_db.RegisterMessage(Response) - - +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0eresponse.proto\x12\x05manga\"P\n\x06\x42\x61nner\x12\x11\n\timage_url\x18\x01 \x01(\t\x12\'\n\x06\x61\x63tion\x18\x02 \x01(\x0b\x32\x17.manga.TransitionAction\x12\n\n\x02id\x18\x03 \x01(\r\"B\n\nBannerList\x12\x14\n\x0c\x62\x61nner_title\x18\x01 \x01(\t\x12\x1e\n\x07\x62\x61nners\x18\x02 \x03(\x0b\x32\r.manga.Banner\"/\n\x10TransitionAction\x12\x0e\n\x06method\x18\x01 \x01(\x05\x12\x0b\n\x03url\x18\x02 \x01(\t\"\xc9\x01\n\x07\x43hapter\x12\x10\n\x08title_id\x18\x01 \x01(\r\x12\x12\n\nchapter_id\x18\x02 \x01(\r\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tsub_title\x18\x04 \x01(\t\x12\x15\n\rthumbnail_url\x18\x05 \x01(\t\x12\x17\n\x0fstart_timestamp\x18\x06 \x01(\r\x12\x15\n\rend_timestamp\x18\x07 \x01(\r\x12\x16\n\x0e\x61lready_viewed\x18\x08 \x01(\x08\x12\x18\n\x10is_vertical_only\x18\t \x01(\x08\"\xa8\x01\n\x0c\x43hapterGroup\x12\x17\n\x0f\x63hapter_numbers\x18\x01 \x01(\t\x12*\n\x12\x66irst_chapter_list\x18\x02 \x03(\x0b\x32\x0e.manga.Chapter\x12(\n\x10mid_chapter_list\x18\x03 \x03(\x0b\x32\x0e.manga.Chapter\x12)\n\x11last_chapter_list\x18\x04 \x03(\x0b\x32\x0e.manga.Chapter\"\xdd\x01\n\x07\x43omment\x12\n\n\x02id\x18\x01 \x01(\r\x12\r\n\x05index\x18\x02 \x01(\r\x12\x11\n\tuser_name\x18\x03 \x01(\t\x12\x10\n\x08icon_url\x18\x04 \x01(\t\x12\x1a\n\ris_my_comment\x18\x06 \x01(\x08H\x00\x88\x01\x01\x12\x1a\n\ralready_liked\x18\x07 \x01(\x08H\x01\x88\x01\x01\x12\x17\n\x0fnumber_of_likes\x18\t \x01(\r\x12\x0c\n\x04\x62ody\x18\n \x01(\t\x12\x0f\n\x07\x63reated\x18\x0b \x01(\rB\x10\n\x0e_is_my_commentB\x10\n\x0e_already_liked\"\xfa\x03\n\rAdNetworkList\x12\x33\n\x0b\x61\x64_networks\x18\x01 \x01(\x0b\x32\x1e.manga.AdNetworkList.AdNetwork\x1a\xb3\x03\n\tAdNetwork\x12\x39\n\x08\x66\x61\x63\x65\x62ook\x18\x01 \x01(\x0b\x32\'.manga.AdNetworkList.AdNetwork.Facebook\x12\x33\n\x05\x61\x64mob\x18\x02 \x01(\x0b\x32$.manga.AdNetworkList.AdNetwork.Admob\x12\x33\n\x05mopub\x18\x03 \x01(\x0b\x32$.manga.AdNetworkList.AdNetwork.Mopub\x12\x37\n\x07\x61\x64sense\x18\x04 \x01(\x0b\x32&.manga.AdNetworkList.AdNetwork.Adsense\x12\x39\n\x08\x61pplovin\x18\x05 \x01(\x0b\x32\'.manga.AdNetworkList.AdNetwork.Applovin\x1a \n\x08\x46\x61\x63\x65\x62ook\x12\x14\n\x0cplacement_id\x18\x01 \x01(\t\x1a\x18\n\x05\x41\x64mob\x12\x0f\n\x07unit_id\x18\x01 \x01(\t\x1a\x18\n\x05Mopub\x12\x0f\n\x07unit_id\x18\x01 \x01(\t\x1a\x1a\n\x07\x41\x64sense\x12\x0f\n\x07unit_id\x18\x01 \x01(\t\x1a\x1b\n\x08\x41pplovin\x12\x0f\n\x07unit_id\x18\x01 \x01(\t\"\xb8\x04\n\x05Popup\x12*\n\nos_default\x18\x01 \x01(\x0b\x32\x16.manga.Popup.OSDefault\x12,\n\x0b\x61pp_default\x18\x02 \x01(\x0b\x32\x17.manga.Popup.AppDefault\x12.\n\x0cmovie_reward\x18\x03 \x01(\x0b\x32\x18.manga.Popup.MovieReward\x1a?\n\x06\x42utton\x12\x0c\n\x04text\x18\x01 \x01(\t\x12\'\n\x06\x61\x63tion\x18\x02 \x01(\x0b\x32\x17.manga.TransitionAction\x1a\xab\x01\n\tOSDefault\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x0c\n\x04\x62ody\x18\x02 \x01(\t\x12&\n\tok_button\x18\x03 \x01(\x0b\x32\x13.manga.Popup.Button\x12+\n\x0eneutral_button\x18\x04 \x01(\x0b\x32\x13.manga.Popup.Button\x12*\n\rcancel_button\x18\x05 \x01(\x0b\x32\x13.manga.Popup.Button\x1ag\n\nAppDefault\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x0c\n\x04\x62ody\x18\x02 \x01(\t\x12\'\n\x06\x61\x63tion\x18\x03 \x01(\x0b\x32\x17.manga.TransitionAction\x12\x11\n\timage_url\x18\x04 \x01(\t\x1aM\n\x0bMovieReward\x12\x11\n\timage_url\x18\x01 \x01(\t\x12+\n\radvertisement\x18\x02 \x01(\x0b\x32\x14.manga.AdNetworkList\"\x95\x02\n\x08LastPage\x12\'\n\x0f\x63urrent_chapter\x18\x01 \x01(\x0b\x32\x0e.manga.Chapter\x12$\n\x0cnext_chapter\x18\x02 \x01(\x0b\x32\x0e.manga.Chapter\x12$\n\x0ctop_comments\x18\x03 \x03(\x0b\x32\x0e.manga.Comment\x12\x15\n\ris_subscribed\x18\x04 \x01(\x08\x12\x16\n\x0enext_timestamp\x18\x05 \x01(\r\x12\x14\n\x0c\x63hapter_type\x18\x06 \x01(\x05\x12+\n\radvertisement\x18\x07 \x01(\x0b\x32\x14.manga.AdNetworkList\x12\"\n\x0cmovie_reward\x18\x08 \x01(\x0b\x32\x0c.manga.Popup\"c\n\tMangaPage\x12\x11\n\timage_url\x18\x01 \x01(\t\x12\r\n\x05width\x18\x02 \x01(\r\x12\x0e\n\x06height\x18\x03 \x01(\r\x12\x0c\n\x04type\x18\x04 \x01(\x05\x12\x16\n\x0e\x65ncryption_key\x18\x05 \x01(\t\"\xa5\x01\n\x04Page\x12$\n\nmanga_page\x18\x01 \x01(\x0b\x32\x10.manga.MangaPage\x12&\n\x0b\x62\x61nner_list\x18\x02 \x01(\x0b\x32\x11.manga.BannerList\x12\"\n\tlast_page\x18\x03 \x01(\x0b\x32\x0f.manga.LastPage\x12+\n\radvertisement\x18\x04 \x01(\x0b\x32\x14.manga.AdNetworkList\" \n\x03Sns\x12\x0c\n\x04\x62ody\x18\x01 \x01(\t\x12\x0b\n\x03url\x18\x02 \x01(\t\"\x84\x02\n\x0bMangaViewer\x12\x1a\n\x05pages\x18\x01 \x03(\x0b\x32\x0b.manga.Page\x12\x12\n\nchapter_id\x18\x02 \x01(\r\x12 \n\x08\x63hapters\x18\x03 \x03(\x0b\x32\x0e.manga.Chapter\x12\x17\n\x03sns\x18\x04 \x01(\x0b\x32\n.manga.Sns\x12\x12\n\ntitle_name\x18\x05 \x01(\t\x12\x14\n\x0c\x63hapter_name\x18\x06 \x01(\t\x12\x1a\n\x12number_of_comments\x18\x07 \x01(\r\x12\x18\n\x10is_vertical_only\x18\x08 \x01(\x08\x12\x10\n\x08title_id\x18\t \x01(\r\x12\x18\n\x10start_from_right\x18\n \x01(\x08\"\x96\x01\n\x05Title\x12\x10\n\x08title_id\x18\x01 \x01(\r\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06\x61uthor\x18\x03 \x01(\t\x12\x1a\n\x12portrait_image_url\x18\x04 \x01(\t\x12\x1b\n\x13landscape_image_url\x18\x05 \x01(\t\x12\x12\n\nview_count\x18\x06 \x01(\r\x12\x10\n\x08language\x18\x07 \x01(\x05\"\xce\x04\n\x0fTitleDetailView\x12\x1b\n\x05title\x18\x01 \x01(\x0b\x32\x0c.manga.Title\x12\x17\n\x0ftitle_image_url\x18\x02 \x01(\t\x12\x10\n\x08overview\x18\x03 \x01(\t\x12\x1c\n\x14\x62\x61\x63kground_image_url\x18\x04 \x01(\t\x12\x16\n\x0enext_timestamp\x18\x05 \x01(\r\x12\x15\n\rupdate_timing\x18\x06 \x01(\x05\x12\"\n\x1aviewing_period_description\x18\x07 \x01(\t\x12\x1b\n\x13non_appearance_info\x18\x08 \x01(\t\x12*\n\x12\x66irst_chapter_list\x18\t \x03(\x0b\x32\x0e.manga.Chapter\x12)\n\x11last_chapter_list\x18\n \x03(\x0b\x32\x0e.manga.Chapter\x12\x1e\n\x07\x62\x61nners\x18\x0b \x03(\x0b\x32\r.manga.Banner\x12,\n\x16recommended_title_list\x18\x0c \x03(\x0b\x32\x0c.manga.Title\x12\x17\n\x03sns\x18\r \x01(\x0b\x32\n.manga.Sns\x12\x19\n\x11is_simul_released\x18\x0e \x01(\x08\x12\x15\n\ris_subscribed\x18\x0f \x01(\x08\x12\x0e\n\x06rating\x18\x10 \x01(\x05\x12\x1b\n\x13\x63hapters_descending\x18\x11 \x01(\x08\x12\x17\n\x0fnumber_of_views\x18\x12 \x01(\r\x12/\n\x12\x63hapter_list_group\x18\x1c \x03(\x0b\x32\x13.manga.ChapterGroup\"l\n\rSuccessResult\x12\x31\n\x11title_detail_view\x18\x08 \x01(\x0b\x32\x16.manga.TitleDetailView\x12(\n\x0cmanga_viewer\x18\n \x01(\x0b\x32\x12.manga.MangaViewer\"1\n\x08Response\x12%\n\x07success\x18\x01 \x01(\x0b\x32\x14.manga.SuccessResultB\x0fH\x01Z\x0bmanga/protob\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'response_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + _globals['DESCRIPTOR']._options = None + _globals['DESCRIPTOR']._serialized_options = b'H\001Z\013manga/proto' + _globals['_BANNER']._serialized_start=25 + _globals['_BANNER']._serialized_end=105 + _globals['_BANNERLIST']._serialized_start=107 + _globals['_BANNERLIST']._serialized_end=173 + _globals['_TRANSITIONACTION']._serialized_start=175 + _globals['_TRANSITIONACTION']._serialized_end=222 + _globals['_CHAPTER']._serialized_start=225 + _globals['_CHAPTER']._serialized_end=426 + _globals['_CHAPTERGROUP']._serialized_start=429 + _globals['_CHAPTERGROUP']._serialized_end=597 + _globals['_COMMENT']._serialized_start=600 + _globals['_COMMENT']._serialized_end=821 + _globals['_ADNETWORKLIST']._serialized_start=824 + _globals['_ADNETWORKLIST']._serialized_end=1330 + _globals['_ADNETWORKLIST_ADNETWORK']._serialized_start=895 + _globals['_ADNETWORKLIST_ADNETWORK']._serialized_end=1330 + _globals['_ADNETWORKLIST_ADNETWORK_FACEBOOK']._serialized_start=1189 + _globals['_ADNETWORKLIST_ADNETWORK_FACEBOOK']._serialized_end=1221 + _globals['_ADNETWORKLIST_ADNETWORK_ADMOB']._serialized_start=1223 + _globals['_ADNETWORKLIST_ADNETWORK_ADMOB']._serialized_end=1247 + _globals['_ADNETWORKLIST_ADNETWORK_MOPUB']._serialized_start=1249 + _globals['_ADNETWORKLIST_ADNETWORK_MOPUB']._serialized_end=1273 + _globals['_ADNETWORKLIST_ADNETWORK_ADSENSE']._serialized_start=1275 + _globals['_ADNETWORKLIST_ADNETWORK_ADSENSE']._serialized_end=1301 + _globals['_ADNETWORKLIST_ADNETWORK_APPLOVIN']._serialized_start=1303 + _globals['_ADNETWORKLIST_ADNETWORK_APPLOVIN']._serialized_end=1330 + _globals['_POPUP']._serialized_start=1333 + _globals['_POPUP']._serialized_end=1901 + _globals['_POPUP_BUTTON']._serialized_start=1480 + _globals['_POPUP_BUTTON']._serialized_end=1543 + _globals['_POPUP_OSDEFAULT']._serialized_start=1546 + _globals['_POPUP_OSDEFAULT']._serialized_end=1717 + _globals['_POPUP_APPDEFAULT']._serialized_start=1719 + _globals['_POPUP_APPDEFAULT']._serialized_end=1822 + _globals['_POPUP_MOVIEREWARD']._serialized_start=1824 + _globals['_POPUP_MOVIEREWARD']._serialized_end=1901 + _globals['_LASTPAGE']._serialized_start=1904 + _globals['_LASTPAGE']._serialized_end=2181 + _globals['_MANGAPAGE']._serialized_start=2183 + _globals['_MANGAPAGE']._serialized_end=2282 + _globals['_PAGE']._serialized_start=2285 + _globals['_PAGE']._serialized_end=2450 + _globals['_SNS']._serialized_start=2452 + _globals['_SNS']._serialized_end=2484 + _globals['_MANGAVIEWER']._serialized_start=2487 + _globals['_MANGAVIEWER']._serialized_end=2747 + _globals['_TITLE']._serialized_start=2750 + _globals['_TITLE']._serialized_end=2900 + _globals['_TITLEDETAILVIEW']._serialized_start=2903 + _globals['_TITLEDETAILVIEW']._serialized_end=3493 + _globals['_SUCCESSRESULT']._serialized_start=3495 + _globals['_SUCCESSRESULT']._serialized_end=3603 + _globals['_RESPONSE']._serialized_start=3605 + _globals['_RESPONSE']._serialized_end=3654 # @@protoc_insertion_point(module_scope) diff --git a/mloader/utils.py b/mloader/utils.py index 33baf43..530097e 100644 --- a/mloader/utils.py +++ b/mloader/utils.py @@ -1,32 +1,98 @@ import re import string import sys -from typing import Optional +from typing import Optional, Collection + + +def _contains_keywords(text: str, keywords: Collection[str]) -> bool: + """ + Check if the given text contains all specified keywords (case-insensitive). + + Parameters: + text (str): The text in which to search for keywords. + keywords (Collection[str]): A collection of keywords to look for. + + Returns: + bool: True if all keywords are present in the text, False otherwise. + """ + lower_text = text.lower() + return all(keyword.lower() in lower_text for keyword in keywords) def is_oneshot(chapter_name: str, chapter_subtitle: str) -> bool: - chapter_number = chapter_name_to_int(chapter_name) + """ + Determine if a manga chapter is a one-shot based on its name and subtitle. + + The function first attempts to convert the chapter name into an integer. + If successful, the chapter is considered numbered and not a one-shot. + Otherwise, it checks if either the chapter name or subtitle contains both + the keywords "one" and "shot". + Parameters: + chapter_name (str): The primary name of the chapter. + chapter_subtitle (str): The subtitle of the chapter. + + Returns: + bool: True if the chapter is identified as a one-shot, False otherwise. + """ + # Attempt to parse the chapter name as an integer. + chapter_number = chapter_name_to_int(chapter_name) if chapter_number is not None: + # If conversion succeeded, it's a numbered chapter. return False - for name in (chapter_name, chapter_subtitle): - name = name.lower() - if "one" in name and "shot" in name: - return True + # Check if either the chapter name or subtitle includes the keywords "one" and "shot". + if _contains_keywords(chapter_name, ["one", "shot"]) or _contains_keywords(chapter_subtitle, ["one", "shot"]): + return True + return False def chapter_name_to_int(name: str) -> Optional[int]: + """ + Convert a chapter name to an integer chapter number, if possible. + + This function strips any leading '#' characters and attempts to convert the + remaining string into an integer. If the conversion fails, it returns None. + + Parameters: + name (str): The chapter name string to convert. + + Returns: + Optional[int]: The chapter number as an integer, or None if conversion fails. + """ try: + # Remove any leading '#' characters before conversion. return int(name.lstrip("#")) except ValueError: return None def escape_path(path: str) -> str: - return re.sub(r"[^\w]+", " ", path).strip(string.punctuation + " ") + """ + Normalize a filesystem path by removing or replacing problematic characters. + + This function replaces sequences of non-alphanumeric characters with a single space, + and then strips any leading or trailing punctuation and whitespace. The resulting + string is safer to use as a filename or directory name. + + Parameters: + path (str): The original filesystem path string. + + Returns: + str: The normalized path string. + """ + # Replace any sequence of non-alphanumeric characters (and underscores) with a space. + normalized = re.sub(r"[^\w]+", " ", path) + # Remove any leading or trailing punctuation and whitespace. + return normalized.strip(string.punctuation + " ") def is_windows() -> bool: - return sys.platform == "win32" + """ + Determine whether the current operating system is Windows. + + Returns: + bool: True if the current platform is Windows, False otherwise. + """ + return sys.platform == "win32" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 7fe08c5..4635877 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ -Click>=6.2 -protobuf~=3.6 -requests>=2 +Click>=8.1.8 +protobuf>=6.30.0 +requests>=2.32.3 +Pillow>=11.1.0 +python-dotenv>=1.0.1 \ No newline at end of file diff --git a/response.proto b/response.proto index 4951017..4ae1c1f 100644 --- a/response.proto +++ b/response.proto @@ -2,6 +2,9 @@ syntax = "proto3"; package manga; +option optimize_for = SPEED; +option go_package = "manga/proto"; // If using Go + message Banner { string image_url = 1; TransitionAction action = 2; @@ -42,8 +45,8 @@ message Comment { uint32 index = 2; string user_name = 3; string icon_url = 4; - bool is_my_comment = 6; - bool already_liked = 7; + optional bool is_my_comment = 6; + optional bool already_liked = 7; uint32 number_of_likes = 9; string body = 10; uint32 created = 11; @@ -54,7 +57,6 @@ message AdNetworkList { message Facebook { string placement_id = 1; } - message Admob { string unit_id = 1; } @@ -78,7 +80,6 @@ message AdNetworkList { AdNetwork ad_networks = 1; } - message Popup { message Button { string text = 1; @@ -91,7 +92,6 @@ message Popup { Button ok_button = 3; Button neutral_button = 4; Button cancel_button = 5; - } message AppDefault { @@ -111,7 +111,6 @@ message Popup { MovieReward movie_reward = 3; } - message LastPage { Chapter current_chapter = 1; Chapter next_chapter = 2; @@ -123,7 +122,6 @@ message LastPage { Popup movie_reward = 8; } -// MangaPage message MangaPage { string image_url = 1; uint32 width = 2; @@ -132,7 +130,6 @@ message MangaPage { string encryption_key = 5; } -// Page message Page { MangaPage manga_page = 1; BannerList banner_list = 2; @@ -145,7 +142,6 @@ message Sns { string url = 2; } -// MangaViewer message MangaViewer { repeated Page pages = 1; uint32 chapter_id = 2; @@ -198,4 +194,4 @@ message SuccessResult { message Response { SuccessResult success = 1; -} +} \ No newline at end of file diff --git a/setup.py b/setup.py index 20a4993..00d3359 100644 --- a/setup.py +++ b/setup.py @@ -25,23 +25,20 @@ long_description_content_type="text/markdown", url=about["__url__"], packages=find_packages(), - python_requires=">=3.6", + python_requires=">=3.12", install_requires=[ - "Click>=6.2", - "protobuf~=3.6", - "requests>=2" + "Click>=8.1.8", + "protobuf>=6.30.0", + "requests>=2.32.3", + "Pillow>=11.1.0", + "python-dotenv>=1.0.1", ], license=about["__license__"], zip_safe=False, classifiers=[ "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Programming Language :: Python", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", ], project_urls={"Source": about["__url__"]},