diff options
author | dec05eba <dec05eba@protonmail.com> | 2023-10-06 21:03:02 +0200 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2023-10-06 21:03:02 +0200 |
commit | aaf36275048b201e876841dfc456b3f2f341c8a9 (patch) | |
tree | e476e9fa2b62a6f7baa9dc07ddedd92d6e6a22f6 | |
parent | 664b5b97f03258887f9643a8b488b86068c336e7 (diff) |
Fix mangaplus after latest update, no longer depend on protobuf
-rw-r--r-- | README.md | 2 | ||||
-rwxr-xr-x | plugins/mangaplus.py | 210 |
2 files changed, 58 insertions, 154 deletions
@@ -18,7 +18,7 @@ An easy way to track anime/manga is to use [QuickMedia](https://git.dec05eba.com ## System curl, transmission-cli, notify-send (optional) ## Python 3 -lxml, requests, pure_protobuf (optional, used with mangaplus.shueisha.co.jp) +lxml, requests # Requirements when using tools/open_media.py ## System dmenu, sxiv (for manga), mpv (for anime) diff --git a/plugins/mangaplus.py b/plugins/mangaplus.py index 9b11eda..7106903 100755 --- a/plugins/mangaplus.py +++ b/plugins/mangaplus.py @@ -11,136 +11,19 @@ import re import requests import json -from pure_protobuf.dataclasses_ import field, message -from pure_protobuf.types import int32 - -from dataclasses import dataclass -from enum import IntEnum -from typing import List - RE_ENCRYPTION_KEY = re.compile('.{1,2}') api_url = 'https://jumpg-webapi.tokyo-cdn.com/api' -api_manga_url = api_url + '/title_detail?title_id={0}' -api_chapter_url = api_url + '/manga_viewer?chapter_id={0}&split=yes&img_quality=high' - -# Protocol Buffers messages used to deserialize API responses -# https://gist.github.com/ZaneHannanAU/437531300c4df524bdb5fd8a13fbab50 - -class ActionEnum(IntEnum): - DEFAULT = 0 - UNAUTHORIZED = 1 - MAINTAINENCE = 2 - GEOIP_BLOCKING = 3 - -class LanguageEnum(IntEnum): - ENGLISH = 0 - SPANISH = 1 - -class UpdateTimingEnum(IntEnum): - NOT_REGULARLY = 0 - MONDAY = 1 - TUESDAY = 2 - WEDNESDAY = 3 - THURSDAY = 4 - FRIDAY = 5 - SATURDAY = 6 - SUNDAY = 7 - DAY = 8 - -@message -@dataclass -class Popup: - subject: str = field(1) - body: str = field(2) - -@message -@dataclass -class ErrorResult: - action: ActionEnum = field(1) - english_popup: Popup = field(2) - spanish_popup: Popup = field(3) - debug_info: str = field(4) - -@message -@dataclass -class MangaPage: - image_url: str = field(1) - width: int32 = field(2) - height: int32 = field(3) - encryption_key: str = field(5, default=None) - -@message -@dataclass -class Page: - page: MangaPage = field(1, default=None) - -@message -@dataclass -class MangaViewer: - pages: List[Page] = field(1, default_factory=list) - -@message -@dataclass +api_manga_url = api_url + '/title_detailV3?title_id={0}&format=json' +api_chapter_url = api_url + '/manga_viewer?chapter_id={0}&split=yes&img_quality=high&format=json' + class Chapter: - title_id: int32 = field(1) - id: int32 = field(2) - name: str = field(3) - subtitle: str = field(4, default=None) - start_timestamp: int32 = field(6, default=None) - end_timestamp: int32 = field(7, default=None) - -@message -@dataclass -class Title: - id: int32 = field(1) - name: str = field(2) - author: str = field(3) - portrait_image_url: str = field(4) - landscape_image_url: str = field(5) - view_count: int32 = field(6) - language: LanguageEnum = field(7, default=LanguageEnum.ENGLISH) - -@message -@dataclass -class TitleDetail: - title: Title = field(1) - title_image_url: str = field(2) - synopsis: str = field(3) - background_image_url: str = field(4) - next_timestamp: int32 = field(5, default=0) - update_timimg: UpdateTimingEnum = field(6, default=UpdateTimingEnum.DAY) - viewing_period_description: str = field(7, default=None) - first_chapters: List[Chapter] = field(9, default_factory=List) - last_chapters: List[Chapter] = field(10, default_factory=list) - is_simul_related: bool = field(14, default=True) - chapters_descending: bool = field(17, default=True) - -@message -@dataclass -class TitlesAll: - titles: List[Title] = field(1) - -@message -@dataclass -class TitlesRanking: - titles: List[Title] = field(1) - -@message -@dataclass -class SuccessResult: - is_featured_updated: bool = field(1, default=False) - titles_all: TitlesAll = field(5, default=None) - titles_ranking: TitlesRanking = field(6, default=None) - title_detail: TitleDetail = field(8, default=None) - manga_viewer: MangaViewer = field(10, default=None) - -@message -@dataclass -class MangaplusResponse: - success: SuccessResult = field(1, default=None) - error: ErrorResult = field(2, default=None) + id = "" + title = "" +class MangaPage: + url = "" + encryption_key = None def usage(): print("mangaplus.py command") @@ -166,10 +49,11 @@ def usage_download(): if len(sys.argv) < 2: usage() -def download_file(url, page, save_path): - if page.page.encryption_key is not None: +# Encryption is done with symetric key and the key is provided in the json response.... +def download_file(url, encryption_key, save_path): + if encryption_key is not None: # Decryption - key_stream = [int(v, 16) for v in RE_ENCRYPTION_KEY.findall(page.page.encryption_key)] + key_stream = [int(v, 16) for v in RE_ENCRYPTION_KEY.findall(encryption_key)] block_size_in_bytes = len(key_stream) index = 0 @@ -197,6 +81,20 @@ def title_url_extract_manga_id(url): if result and len(result.groups()) > 0: return result.groups()[0] +def parse_chapters(chapters_json): + result = [] + + if not chapters_json: + return result + + for chapter_json in chapters_json: + chapter = Chapter() + chapter.id = chapter_json["chapterId"] + chapter.title = chapter_json["subTitle"] + result.append(chapter) + + return result + def list_chapters(url, chapter_list_input): manga_id = title_url_extract_manga_id(url) if not manga_id: @@ -208,15 +106,23 @@ def list_chapters(url, chapter_list_input): response = requests.get(url, timeout=30) response.raise_for_status() - resp = MangaplusResponse.loads(response.content) - if resp.error: - print("Mangaplus response error: %s" % str(resp.error)) - exit(1) + resp_json = response.json() + + all_chapters = [] + chapter_list_groups = resp_json["success"]["titleDetailView"]["chapterListGroup"] + for chapter_list_group in chapter_list_groups: + first_chapter_list = chapter_list_group.get("firstChapterList") + mid_chapter_list = chapter_list_group.get("midChapterList") + last_chapter_list = chapter_list_group.get("lastChapterList") + + all_chapters.extend(parse_chapters(first_chapter_list)) + all_chapters.extend(parse_chapters(mid_chapter_list)) + all_chapters.extend(parse_chapters(last_chapter_list)) seen_titles = set() for item in chapter_list_input: title = item.get("title") - if title and len(title) > 0: + if len(title) > 0: seen_titles.add(title.lower().replace(" ", "").replace("/", "_")) seen_urls = set() @@ -225,15 +131,9 @@ def list_chapters(url, chapter_list_input): if chapter_url and len(chapter_url) > 0: seen_urls.add(chapter_url) - resp_data = resp.success.title_detail - all_chapters = [] - for resp_chapters in (resp_data.first_chapters, resp_data.last_chapters): - for chapter in resp_chapters: - all_chapters.append(chapter) - chapters = [] for chapter in reversed(all_chapters): - title = chapter.subtitle.replace("/", "_") + title = chapter.title.replace("/", "_") url = "https://mangaplus.shueisha.co.jp/viewer/{0}".format(chapter.id) if title.lower().replace(" ", "") in seen_titles or url in seen_urls: break @@ -257,27 +157,31 @@ def download_chapter(url, download_dir): response = requests.get(url, timeout=30) response.raise_for_status() + resp_json = response.json() + + manga_pages = [] + pages = resp_json["success"]["mangaViewer"]["pages"] + for page in pages: + manga_page_json = page.get("mangaPage") + if manga_page_json: + manga_page = MangaPage() + manga_page.url = manga_page_json["imageUrl"] + manga_page.encryption_key = manga_page_json.get("encryptionKey") + manga_pages.append(manga_page) + in_progress_filepath = os.path.join(download_dir, ".in_progress") with open(in_progress_filepath, "w") as file: file.write(request_url) - resp = MangaplusResponse.loads(response.content) - if resp.error: - print("Mangaplus response error: %s" % str(resp.error)) - exit(1) - img_number = 1 - for page in resp.success.manga_viewer.pages: - if page.page is None: - continue - - image_name = page.page.image_url.split('?')[0].split('/')[-1] + for manga_page in manga_pages: + image_name = manga_page.url.split('?')[0].split('/')[-1] ext = image_name[image_name.rfind("."):] image_name = str(img_number) + ext image_path = os.path.join(download_dir, image_name) - print("Downloading {} to {}".format(page.page.image_url, image_path)) - if not download_file(page.page.image_url, page, image_path): - print("Failed to download image: %s" % page.page.image_url) + print("Downloading {} to {}".format(manga_page.url, image_path)) + if not download_file(manga_page.url, manga_page.encryption_key, image_path): + print("Failed to download image: %s" % manga_page.url) os.remove(in_progress_filepath) exit(2) img_number += 1 |