From e2fc52f88cb8019fd240a2f9e3501b49ab104dc8 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Wed, 19 Aug 2026 14:37:21 +0800 Subject: [PATCH] Remove WebView --- data/info.febvre.Komikku.gresource.xml.in | 1 - data/meson.build | 1 - data/ui/preferences.blp | 1 + data/ui/webview.blp | 18 - komikku/application.py | 2 - komikku/debug_info.py | 4 - komikku/servers/comichubfree/__init__.py | 290 ------ komikku/servers/comix/__init__.py | 686 -------------- komikku/servers/goldenmangas/__init__.py | 204 ----- komikku/servers/japscan/__init__.py | 300 ------- komikku/servers/kaliscan/__init__.py | 112 --- komikku/servers/mangaball/__init__.py | 431 --------- komikku/servers/mangademon/__init__.py | 302 ------- komikku/servers/mangafire/__init__.py | 501 ----------- komikku/servers/mangafreak/__init__.py | 241 ----- komikku/servers/mangahere/__init__.py | 289 ------ komikku/servers/mangakawaii/__init__.py | 425 --------- komikku/servers/mangamana/__init__.py | 296 ------ komikku/servers/manganelo/__init__.py | 246 ----- komikku/servers/mangatube/__init__.py | 368 -------- komikku/servers/mangaworld/__init__.py | 251 ------ komikku/servers/manhwahentai/__init__.py | 83 -- komikku/servers/multi/heancms/__init__.py | 7 - komikku/servers/multi/keyoapp/__init__.py | 6 - komikku/servers/multi/madara/__init__.py | 8 - komikku/servers/multi/madtheme/__init__.py | 7 - .../servers/multi/manga_stream/__init__.py | 7 - komikku/servers/multi/paprika/__init__.py | 7 - komikku/servers/multi/peachscan/__init__.py | 7 - komikku/servers/multi/wpcomics/__init__.py | 7 - komikku/servers/nhentai/__init__.py | 202 ----- komikku/servers/phenixscans/__init__.py | 204 ----- komikku/servers/raijinscan/__init__.py | 304 ------- komikku/servers/rawmanga/__init__.py | 172 ---- komikku/servers/readcomiconline/__init__.py | 304 ------- komikku/servers/readcomicsonline/__init__.py | 257 ------ komikku/servers/remanga/__init__.py | 266 ------ komikku/servers/scanmanga/__init__.py | 286 ------ komikku/servers/teamx/__init__.py | 232 ----- komikku/trackers/anilist/__init__.py | 362 -------- komikku/trackers/myanimelist/__init__.py | 283 ------ komikku/webview.py | 845 ------------------ 42 files changed, 1 insertion(+), 8824 deletions(-) delete mode 100644 data/ui/webview.blp delete mode 100644 komikku/servers/comichubfree/__init__.py delete mode 100644 komikku/servers/comix/__init__.py delete mode 100644 komikku/servers/goldenmangas/__init__.py delete mode 100644 komikku/servers/japscan/__init__.py delete mode 100644 komikku/servers/kaliscan/__init__.py delete mode 100644 komikku/servers/mangaball/__init__.py delete mode 100644 komikku/servers/mangademon/__init__.py delete mode 100644 komikku/servers/mangafire/__init__.py delete mode 100644 komikku/servers/mangafreak/__init__.py delete mode 100644 komikku/servers/mangahere/__init__.py delete mode 100644 komikku/servers/mangakawaii/__init__.py delete mode 100644 komikku/servers/mangamana/__init__.py delete mode 100644 komikku/servers/manganelo/__init__.py delete mode 100644 komikku/servers/mangatube/__init__.py delete mode 100644 komikku/servers/mangaworld/__init__.py delete mode 100644 komikku/servers/manhwahentai/__init__.py delete mode 100644 komikku/servers/nhentai/__init__.py delete mode 100644 komikku/servers/phenixscans/__init__.py delete mode 100644 komikku/servers/raijinscan/__init__.py delete mode 100644 komikku/servers/rawmanga/__init__.py delete mode 100644 komikku/servers/readcomiconline/__init__.py delete mode 100644 komikku/servers/readcomicsonline/__init__.py delete mode 100644 komikku/servers/remanga/__init__.py delete mode 100644 komikku/servers/scanmanga/__init__.py delete mode 100644 komikku/servers/teamx/__init__.py delete mode 100644 komikku/trackers/anilist/__init__.py delete mode 100644 komikku/trackers/myanimelist/__init__.py delete mode 100644 komikku/webview.py diff --git a/data/info.febvre.Komikku.gresource.xml.in b/data/info.febvre.Komikku.gresource.xml.in index 5908c536..9f0546b0 100644 --- a/data/info.febvre.Komikku.gresource.xml.in +++ b/data/info.febvre.Komikku.gresource.xml.in @@ -25,7 +25,6 @@ ui/reader_settings.ui ui/shortcuts.ui ui/support.ui - ui/webview.ui ui/menu/main.xml diff --git a/data/meson.build b/data/meson.build index 269b2dfc..fe3025bd 100644 --- a/data/meson.build +++ b/data/meson.build @@ -141,7 +141,6 @@ blueprints = custom_target('blueprints', 'ui/reader_settings.blp', 'ui/shortcuts.blp', 'ui/support.blp', - 'ui/webview.blp', ), output: '.', command: [find_program('blueprint-compiler'), 'batch-compile', '--minify', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'], diff --git a/data/ui/preferences.blp b/data/ui/preferences.blp index cdf8c0d1..e93af79a 100644 --- a/data/ui/preferences.blp +++ b/data/ui/preferences.blp @@ -449,6 +449,7 @@ template $PreferencesDialog : Adw.PreferencesDialog { } Adw.PreferencesGroup { +visible: false; title: _("WebView"); Adw.ActionRow clear_webview_data_actionrow { diff --git a/data/ui/webview.blp b/data/ui/webview.blp deleted file mode 100644 index 1c4e2372..00000000 --- a/data/ui/webview.blp +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2025 Valéry Febvre -// -// SPDX-License-Identifier: GPL-3.0-or-later - -using Gtk 4.0; -using Adw 1; - -template $WebviewPage : Adw.NavigationPage { - tag: "webview"; - title: _("WebView"); - child: Adw.ToolbarView toolbarview { - [top] - Adw.HeaderBar { - title-widget: Adw.WindowTitle title { - }; - } - }; -} diff --git a/komikku/application.py b/komikku/application.py index 9397ecf9..a2dd9a5a 100644 --- a/komikku/application.py +++ b/komikku/application.py @@ -47,7 +47,6 @@ from komikku.support import SupportPage from komikku.trackers import Trackers from komikku.updater import Updater from komikku.utils import random_palette -from komikku.webview import WebviewPage BANNER = """ ██╗ ██╗ ██████╗ ███╗ ███╗██╗██╗ ██╗██╗ ██╗██╗ ██╗ @@ -310,7 +309,6 @@ class ApplicationWindow(Adw.ApplicationWindow): self.explorer = Explorer(self) self.history = HistoryPage(self) self.support = SupportPage(self) - self.webview = WebviewPage(self) # Init dialogs self.preferences = PreferencesDialog(self) diff --git a/komikku/debug_info.py b/komikku/debug_info.py index 69adfc14..1745c31d 100644 --- a/komikku/debug_info.py +++ b/komikku/debug_info.py @@ -12,14 +12,12 @@ gi.require_version('Adw', '1') gi.require_version('GdkPixbuf', '2.0') gi.require_version('Gtk', '4.0') gi.require_version('Soup', '3.0') -gi.require_version('WebKit', '6.0') from gi.repository import Adw from gi.repository import GdkPixbuf from gi.repository import GLib from gi.repository import Gtk from gi.repository import Soup -from gi.repository import WebKit from komikku.models.database import VERSION as DB_VERSION from komikku.utils import check_cmdline_tool @@ -115,14 +113,12 @@ class DebugInfo: info += f'- GdkPixbuf: {GdkPixbuf.PIXBUF_VERSION}\n' info += f'- GTK: {Gtk.MAJOR_VERSION}.{Gtk.MINOR_VERSION}.{Gtk.MICRO_VERSION}\n' info += f'- Adwaita: {Adw.VERSION_S}\n' - info += f'- WebKitGTK: {WebKit.MAJOR_VERSION}.{WebKit.MINOR_VERSION}.{WebKit.MICRO_VERSION}\n' info += f'- Soup: {Soup.MAJOR_VERSION}.{Soup.MINOR_VERSION}.{Soup.MICRO_VERSION}\n' info += '\n' info += 'Running against:\n' info += f'- GTK: {Gtk.get_major_version()}.{Gtk.get_minor_version()}.{Gtk.get_micro_version()}\n' info += f'- Adwaita: {Adw.get_major_version()}.{Adw.get_minor_version()}.{Adw.get_micro_version()}\n' - info += f'- WebKitGTK: {WebKit.get_major_version()}.{WebKit.get_minor_version()}.{WebKit.get_micro_version()}\n' info += f'- Soup: {Soup.get_major_version()}.{Soup.get_minor_version()}.{Soup.get_micro_version()}\n' info += f'- Magic: {magic.version()}\n' info += '\n' diff --git a/komikku/servers/comichubfree/__init__.py b/komikku/servers/comichubfree/__init__.py deleted file mode 100644 index 7b4012de..00000000 --- a/komikku/servers/comichubfree/__init__.py +++ /dev/null @@ -1,290 +0,0 @@ -# SPDX-FileCopyrightText: 2019-2025 Valéry Febvre -# SPDX-License-Identifier: GPL-3.0-or-later -# Author: Valéry Febvre - -import time - -from bs4 import BeautifulSoup - -from komikku.consts import DOWNLOAD_MAX_DELAY -from komikku.servers import Server -from komikku.servers.utils import convert_date_string -from komikku.utils import get_buffer_mime_type -from komikku.utils import get_response_elapsed -from komikku.utils import is_number -from komikku.webview import CompleteChallenge - -# Similar to dead server comicextra - - -class Comichubfree(Server): - id = 'comichubfree' - name = 'ComicHubFree' - lang = 'en' - - has_cf = True - - base_url = 'https://comichubfree.com' - logo_url = base_url + '/favicon.png' - search_url = base_url + '/search-comic' - latest_updates_url = base_url + '/comic-updates' - most_populars_url = base_url + '/popular-comic' - manga_url = base_url + '/comic/{0}' - chapter_url = base_url + '/{0}/{1}/all' - - def __init__(self): - self.session = None - - @CompleteChallenge() - def get_manga_data(self, initial_data): - """ - Returns comic data by scraping manga HTML page content - - Initial data should contain at least comic's slug (provided by search) - """ - assert 'slug' in initial_data, 'Slug is missing in initial data' - - r = self.session_get(self.manga_url.format(initial_data['slug'])) - if r.status_code != 200: - return None - - mime_type = get_buffer_mime_type(r.content) - if mime_type != 'text/html': - return None - - data = initial_data.copy() - data.update(dict( - authors=[], - scanlators=[], # not available - genres=[], - status=None, - synopsis=None, - chapters=[], - server_id=self.id, - cover=None, - )) - - soup = BeautifulSoup(r.text, 'lxml') - - data['name'] = soup.select_one('.breadcrumb li:last-child span[itemprop="name"]').text.strip() - data['cover'] = soup.select_one('.movie-image img').get('data-src') - - for element in soup.select('.movie-dl dd.status'): - status = element.text.strip().lower() - if status == 'completed': - data['status'] = 'complete' - elif status == 'ongoing': - data['status'] = 'ongoing' - - if element := soup.select_one('.movie-meta-info dt:-soup-contains("Author")'): - data['authors'].append(element.find_next_siblings()[0].text.strip()) - - if element := soup.select_one('.movie-meta-info dt:-soup-contains("Genres")'): - for a_element in element.find_next_siblings()[0].select('a'): - data['genres'].append(a_element.text.strip()) - - data['synopsis'] = soup.select_one('#film-content').text.strip() - - # Chapters (Issues) - data['chapters'] = self.get_manga_chapters_data(data['slug'], soup=soup) - - return data - - def get_manga_chapters_data(self, slug, page=None, soup=None, chapters=None): - if chapters is None: - chapters = [] - - if soup is None: - r = self.session_get(self.manga_url.format(slug), params=dict(page=page)) - if r.status_code != 200: - return None - - mime_type = get_buffer_mime_type(r.content) - if mime_type != 'text/html': - return None - - rtime = get_response_elapsed(r) - soup = BeautifulSoup(r.text, 'lxml') - else: - rtime = None - - for tr_element in soup.select('#list tr'): - a_element = tr_element.select_one('a') - td_elements = tr_element.select('td') - - chapter_slug = a_element.get('href').split('/')[-1] - num = chapter_slug.replace('issue-', '') - - chapters.append(dict( - slug=chapter_slug, - title=a_element.text.strip(), - num=num if is_number(num) else None, - date=convert_date_string(td_elements[1].text.strip(), '%d-%b-%Y', languages=[self.lang]), - )) - - if next_element := soup.select_one('ul.pagination > li > a[rel="next"]'): - if rtime: - time.sleep(min(rtime * 4, DOWNLOAD_MAX_DELAY)) - - next_page = next_element.get('href').split('=')[-1] - self.get_manga_chapters_data(slug, page=next_page, chapters=chapters) - - return list(reversed(chapters)) - - @CompleteChallenge() - def get_manga_chapter_data(self, manga_slug, manga_name, chapter_slug, chapter_url): - """ - Returns comic chapter data - - Currently, only pages are expected. - """ - r = self.session_get(self.chapter_url.format(manga_slug, chapter_slug)) - if r.status_code != 200: - return None - - soup = BeautifulSoup(r.text, 'lxml') - - data = dict( - pages=[], - ) - for index, img_element in enumerate(soup.select('.chapter-container .page-chapter img')): - data['pages'].append(dict( - image=img_element.get('data-src').strip(), - slug=None, - index=index + 1, - )) - - return data - - @CompleteChallenge() - def get_manga_chapter_page_image(self, manga_slug, manga_name, chapter_slug, page): - """ - Returns chapter page scan (image) content - """ - r = self.session_get( - page['image'], - headers={ - 'Referer': self.chapter_url.format(manga_slug, chapter_slug), - } - ) - if r.status_code != 200: - return None - - mime_type = get_buffer_mime_type(r.content) - if not mime_type.startswith('image'): - return None - - return dict( - buffer=r.content, - mime_type=mime_type, - name=f"{page['index']}.{mime_type.split('/')[1]}", - ) - - def get_manga_url(self, slug, url): - """ - Returns comic absolute URL - """ - return self.manga_url.format(slug) - - @CompleteChallenge() - def get_latest_updates(self): - """ - Returns daily updates - """ - r = self.session.get( - self.latest_updates_url, - headers={ - 'Referer': f'{self.base_url}/', - } - ) - if r.status_code != 200: - return None - - mime_type = get_buffer_mime_type(r.content) - if mime_type != 'text/html': - return None - - soup = BeautifulSoup(r.text, 'lxml') - - results = [] - for element in soup.select('.hl-box'): - a_element = element.select_one('a') - last_a_element = element.select_one('.hlb-list a') - - results.append(dict( - name=a_element.text.strip(), - slug=a_element.get('href').split('/')[-1], - last_chapter=last_a_element.text.strip() if last_a_element else None, - )) - - return results - - @CompleteChallenge() - def get_most_populars(self): - """ - Returns popular comics - """ - r = self.session.get( - self.most_populars_url, - headers={ - 'Referer': f'{self.base_url}/', - } - ) - if r.status_code != 200: - return None - - mime_type = get_buffer_mime_type(r.content) - if mime_type != 'text/html': - return None - - soup = BeautifulSoup(r.text, 'lxml') - - results = [] - for element in soup.select('.cartoon-box'): - a_element = element.select_one('.mb-right h3 a') - img_element = element.select_one('.image img') - nb_chapters_element = element.select_one('.mb-right .detail') - - results.append(dict( - name=a_element.text.strip(), - slug=a_element.get('href').split('/')[-1], - cover=img_element.get('data-src'), - nb_chapters=nb_chapters_element.text.split()[0], - )) - - return results - - @CompleteChallenge() - def search(self, term): - r = self.session.get( - self.search_url, - params=dict( - key=term, - ), - headers={ - 'Referer': f'{self.base_url}/', - } - ) - if r.status_code != 200: - return None - - mime_type = get_buffer_mime_type(r.content) - if mime_type != 'text/html': - return None - - soup = BeautifulSoup(r.text, 'lxml') - - results = [] - for element in soup.select('.cartoon-box'): - a_element = element.select_one('.mb-right h3 a') - img_element = element.select_one('.image img') - nb_chapters_element = element.select_one('.mb-right .detail') - - results.append(dict( - name=a_element.text.strip(), - slug=a_element.get('href').split('/')[-1], - cover=img_element.get('data-src'), - nb_chapters=nb_chapters_element.text.split()[0], - )) - - return results diff --git a/komikku/servers/comix/__init__.py b/komikku/servers/comix/__init__.py deleted file mode 100644 index 122aacc2..00000000 --- a/komikku/servers/comix/__init__.py +++ /dev/null @@ -1,686 +0,0 @@ -# SPDX-FileCopyrightText: 2019-2025 Valéry Febvre -# SPDX-License-Identifier: GPL-3.0-or-later -# Author: Valéry Febvre - -import base64 -from gettext import gettext as _ -import json -import time -import urllib.parse - -from bs4 import BeautifulSoup - -from komikku.consts import DOWNLOAD_MAX_DELAY -from komikku.servers import Server -from komikku.servers.utils import convert_date_string -from komikku.utils import get_buffer_mime_type -from komikku.utils import get_response_elapsed -from komikku.utils import is_number -from komikku.webview import CompleteChallenge - -SEARCH_RESULTS_PAGES = 4 - -KEYS = { - 0: '13YDu67uDgFczo3DnuTIURqas4lfMEPADY6Jaeqky+w=', # 0 RC4 key round 1 - 1: 'yEy7wBfBc+gsYPiQL/4Dfd0pIBZFzMwrtlRQGwMXy3Q=', # 1 mut_key round 1 - 2: 'yrP+EVA1Dw==', # 2 pref_key round 1 - 3: 'vZ23RT7pbSlxwiygkHd1dhToIku8SNHPC6V36L4cnwM=', # 3 RC4 key round 2 - 4: 'QX0sLahOByWLcWGnv6l98vQudWqdRI3DOXBdit9bxCE=', # 4 mut_key round 2 - 5: 'WJwgqCmf', # 5 pref_key round 2 - 6: 'BkWI8feqSlDZKMq6awfzWlUypl88nz65KVRmpH0RWIc=', # 6 RC4 key round 3 - 7: 'v7EIpiQQjd2BGuJzMbBA0qPWDSS+wTJRQ7uGzZ6rJKs=', # 7 mut_key round 3 - 8: '1SUReYlCRA==', # 8 pref_key round 3 - 9: 'RougjiFHkSKs20DZ6BWXiWwQUGZXtseZIyQWKz5eG34=', # 9 RC4 key round 4 - 10: 'LL97cwoDoG5cw8QmhI+KSWzfW+8VehIh+inTxnVJ2ps=', # 10 mut_key round 4 - 11: '52iDqjzlqe8=', # 11 pref_key round 4 - 12: 'U9LRYFL2zXU4TtALIYDj+lCATRk/EJtH7/y7qYYNlh8=', # 12 RC4 key round 5 - 13: 'e/GtffFDTvnw7LBRixAD+iGixjqTq9kIZ1m0Hj+s6fY=', # 13 mut_key round 5 - 14: 'xb2XwHNB', # 14 pref_key round 5 -} - - -def generate_hash(path, body_size=0, time=1): - """ - :param path: API path, e.g. '/manga/some-hash/chapters' - :param body_size: encodeURIComponent(body) length for POST, or 0 for GET - :param time: 1 for GET manga requests, `System.currentTimeMillis()` for POST - """ - - def get_key_bytes(index): - b64 = KEYS.get(index) - if not b64: - return [] - - key = [] - for c in base64.b64decode(b64): - key.append(int(c) & 0xFF) - - return key - - def get_mut_key(mk, idx): - if mk and (idx % 32) < len(mk): - return mk[idx % 32] - return 0 - - def mut_s(e): - return (e + 143) % 256 - - def mut_l(e): - return ((e >> 1) | (e << 7)) & 255 - - def mut_c(e): - return (e + 115) % 256 - - def mut_m(e): - return e ^ 177 - - def mut_f(e): - return (e - 188 + 256) % 256 - - def mut_g(e): - return ((e << 2) | (e >> 6)) & 255 - - def mut_h(e): - return (e - 42 + 256) % 256 - - def mut_dollar(e): - return ((e << 4) | (e >> 4)) & 255 - - def mut_b(e): - return (e - 12 + 256) % 256 - - def mut_underscore(e): - return (e - 20 + 256) % 256 - - def mut_y(e): - return ((e >> 1) | (e << 7)) & 255 - - def mut_k(e): - return (e - 241 + 256) % 256 - - def RC4(key, data): - if not key: - return data - s = list(range(256)) - j = 0 - for i in range(256): - j = (j + s[i] + key[i % len(key)]) % 256 - temp = s[i] - s[i] = s[j] - s[j] = temp - - i = 0 - j = 0 - out = [0] * len(data) - for k in range(len(data)): - i = (i + 1) % 256 - j = (j + s[i]) % 256 - temp = s[i] - s[i] = s[j] - s[j] = temp - out[k] = data[k] ^ s[(s[i] + s[j]) % 256] - - return out - - def round1(data): - enc = RC4(get_key_bytes(0), data) - mut_key = get_key_bytes(1) - pref_key = get_key_bytes(2) - out = [] - - for i, c in enumerate(enc): - if i < 7 and i < len(pref_key): - out.append(pref_key[i]) - - v = c ^ get_mut_key(mut_key, i) - if i % 10 in (0, 9): - v = mut_c(v) - elif i % 10 == 1: - v = mut_b(v) - elif i % 10 == 2: - v = mut_y(v) - elif i % 10 == 3: - v = mut_dollar(v) - elif i % 10 in (4, 6): - v = mut_h(v) - elif i % 10 == 5: - v = mut_s(v) - elif i % 10 == 7: - v = mut_k(v) - elif i % 10 == 8: - v = mut_l(v) - - out.append(v & 255) - - return out - - def round2(data): - enc = RC4(get_key_bytes(3), data) - mut_key = get_key_bytes(4) - pref_key = get_key_bytes(5) - out = [] - - for i, c in enumerate(enc): - if i < 6 and i < len(pref_key): - out.append(pref_key[i]) - - v = c ^ get_mut_key(mut_key, i) - if i % 10 in (0, 8): - v = mut_c(v) - elif i % 10 == 1: - v = mut_b(v) - elif i % 10 in (2, 6): - v = mut_dollar(v) - elif i % 10 == 3: - v = mut_h(v) - elif i % 10 in (4, 9): - v = mut_s(v) - elif i % 10 == 5: - v = mut_k(v) - elif i % 10 == 7: - v = mut_underscore(v) - - out.append(v & 255) - - return out - - def round3(data): - enc = RC4(get_key_bytes(6), data) - mut_key = get_key_bytes(7) - pref_key = get_key_bytes(8) - out = [] - - for i, c in enumerate(enc): - if i < 7 and i < len(pref_key): - out.append(pref_key[i]) - - v = c ^ get_mut_key(mut_key, i) - if i % 10 == 0: - v = mut_c(v) - elif i % 10 == 1: - v = mut_f(v) - elif i % 10 in (2, 8): - v = mut_s(v) - elif i % 10 == 3: - v = mut_g(v) - elif i % 10 == 4: - v = mut_y(v) - elif i % 10 == 5: - v = mut_m(v) - elif i % 10 == 6: - v = mut_dollar(v) - elif i % 10 == 7: - v = mut_k(v) - elif i % 10 == 9: - v = mut_b(v) - - out.append(v & 255) - - return out - - def round4(data): - enc = RC4(get_key_bytes(9), data) - mut_key = get_key_bytes(10) - pref_key = get_key_bytes(11) - out = [] - - for i, c in enumerate(enc): - if i < 8 and i < len(pref_key): - out.append(pref_key[i]) - - v = c ^ get_mut_key(mut_key, i) - if i % 10 == 0: - v = mut_b(v) - elif i % 10 in (1, 9): - v = mut_m(v) - elif i % 10 in (2, 7): - v = mut_l(v) - elif i % 10 in (3, 5): - v = mut_s(v) - elif i % 10 in (4, 6): - v = mut_underscore(v) - elif i % 10 == 8: - v = mut_y(v) - - out.append(v & 255) - - return out - - def round5(data): - enc = RC4(get_key_bytes(12), data) - mut_key = get_key_bytes(13) - pref_key = get_key_bytes(14) - out = [] - - for i, c in enumerate(enc): - if i < 6 and i < len(pref_key): - out.append(pref_key[i]) - - v = c ^ get_mut_key(mut_key, i) - if i % 10 == 0: - v = mut_underscore(v) - elif i % 10 in (1, 7): - v = mut_s(v) - elif i % 10 == 2: - v = mut_c(v) - elif i % 10 in (3, 5): - v = mut_m(v) - elif i % 10 == 4: - v = mut_b(v) - elif i % 10 == 6: - v = mut_f(v) - elif i % 10 == 8: - v = mut_dollar(v) - elif i % 10 == 9: - v = mut_g(v) - - out.append(v & 255) - - return out - - encoded = urllib.parse.quote_plus(path).replace('+', '%20').replace('*', '%2A').replace('%7E', '~') - - initial_bytes = [] - for i in encoded.encode(): - initial_bytes.append(int(i) & 0xFF) - - r1 = round1(initial_bytes) - r2 = round2(r1) - r3 = round3(r2) - r4 = round4(r3) - r5 = round5(r4) - - final_bytes = [] - for c in r5: - final_bytes.append(c.to_bytes(1)) - - return base64.b64encode(b''.join(final_bytes), altchars=b'-_').replace(b'=', b'') - - -class Comix(Server): - id = 'comix' - name = 'Comix' - lang = 'en' - is_nsfw = True - - has_cf = True - - base_url = 'https://comix.to' - logo_url = base_url + '/icon.png?icon.530a1f27.png' - manga_url = base_url + '/title/{0}' - chapter_url = base_url + '/title/{0}/{1}' - api_url = 'https://comix.to/api/v1' - api_search_url = api_url + '/manga' - api_chapters_url = api_url + '/manga/{0}/chapters' - api_chapter_url = api_url + '/chapters/{0}' - - filters = [ - { - 'key': 'statuses', - 'type': 'select', - 'name': _('Status'), - 'description': _('Filter by Statuses'), - 'value_type': 'multiple', - 'options': [ - {'key': 'releasing', 'name': _('Ongoing'), 'default': False}, - {'key': 'finished', 'name': _('Completed'), 'default': False}, - {'key': 'on_hiatus', 'name': _('Hiatus'), 'default': False}, - {'key': 'discontinued', 'name': _('Canceled'), 'default': False}, - ] - }, - { - 'key': 'types', - 'type': 'select', - 'name': _('Type'), - 'description': _('Filter by Types'), - 'value_type': 'multiple', - 'options': [ - {'key': 'manga', 'name': _('Manga'), 'default': False}, - {'key': 'manhwa', 'name': _('Manhwa'), 'default': False}, - {'key': 'manhua', 'name': _('Manhua'), 'default': False}, - {'key': 'other', 'name': _('Other'), 'default': False}, - ], - }, - { - 'key': 'demographics', - 'type': 'select', - 'name': _('Publication Demographic'), - 'description': _('Filter by Publication Demographics'), - 'value_type': 'multiple', - 'options': [ - {'key': 3, 'name': _('Josei'), 'default': False}, - {'key': 4, 'name': _('Seinen'), 'default': False}, - {'key': 1, 'name': _('Shoujo'), 'default': False}, - {'key': 2, 'name': _('Shounen'), 'default': False}, - ] - }, - ] - - long_strip_genres = ['Manhua', 'Manhwa'] - - params = [ - { - 'key': 'hide_nsfw', - 'type': 'checkbox', - 'name': _('Hide NSFW Content'), - 'description': _('Hide NSFW content from popular, latest, and search lists'), - 'default': True, - }, - ] - - def __init__(self): - self.session = None - - @CompleteChallenge() - def get_manga_data(self, initial_data): - """ - Returns manga data by scraping manga HTML page content and API for chapters - - Initial data should contain at least manga's slug (provided by search) - """ - assert 'slug' in initial_data, 'Manga slug is missing in initial data' - - r = self.session_get(self.manga_url.format(initial_data['slug'])) - - if r.status_code != 200: - return None - - soup = BeautifulSoup(r.text, 'lxml') - - if script_element := soup.select_one('script#initial-data'): - manga_data = json.loads(script_element.string) - hid = manga_data['manga']['hid'] - detail = manga_data['queries'][f'["manga","detail","{hid}"]'] # noqa - groups = manga_data['queries'].get(f'["manga","groups","{hid}"]', []) # noqa - else: - return None - - data = initial_data.copy() - data.update(dict( - name=detail['title'], - authors=[], - scanlators=[], - genres=[], - status=None, - synopsis=detail.get('synopsis'), - chapters=[], - server_id=self.id, - cover=None, - )) - - if posters := detail.get('poster'): - data['cover'] = posters['medium'] - - # Details - if status := detail.get('status'): - if status == 'finished': - data['status'] = 'complete' - elif status == 'releasing': - data['status'] = 'ongoing' - elif status == 'on_hiatus': - data['status'] = 'hiatus' - elif status == 'discontinued': - data['status'] = 'suspended' - - for author in detail.get('authors', []): - data['authors'].append(author['title'].strip()) - for artist in detail.get('artists', []): - title = artist['title'].strip() - if title not in data['authors']: - data['authors'].append(title) - - for genre in detail.get('genres', []): - data['genres'].append(genre['title'].strip()) - for demographic in detail.get('demographics', []): - data['genres'].append(demographic['title'].strip()) - if type_ := detail.get('type'): - data['genres'].append(type_.capitalize()) - if year := detail.get('year'): - data['genres'].append(str(year)) - - for group in groups: - name = group['name'].strip() - if name.lower() in ('official?', 'unknown group'): - continue - data['scanlators'].append(name) - - # Chapters - data['chapters'] = self.get_manga_chapters_data(data['slug']) - - return data - - @CompleteChallenge() - def get_manga_chapter_data(self, manga_slug, manga_name, chapter_slug, chapter_url): - """ - Returns manga chapter data using API - - Pages URLs are available in a