aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2023-10-06 21:03:02 +0200
committerdec05eba <dec05eba@protonmail.com>2023-10-06 21:03:02 +0200
commitaaf36275048b201e876841dfc456b3f2f341c8a9 (patch)
treee476e9fa2b62a6f7baa9dc07ddedd92d6e6a22f6
parent664b5b97f03258887f9643a8b488b86068c336e7 (diff)
Fix mangaplus after latest update, no longer depend on protobuf
-rw-r--r--README.md2
-rwxr-xr-xplugins/mangaplus.py210
2 files changed, 58 insertions, 154 deletions
diff --git a/README.md b/README.md
index 0c78c07..084998d 100644
--- a/README.md
+++ b/README.md
@@ -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