@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2025 Mike Fährmann
|
||||
# Copyright 2025-2026 Mike Fährmann
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 2 as
|
||||
@@ -11,6 +11,7 @@
|
||||
from .common import ChapterExtractor, MangaExtractor
|
||||
from .. import text, exception
|
||||
from ..cache import memcache
|
||||
import binascii
|
||||
|
||||
BASE_PATTERN = r"(?:https?://)?(?:www\.)?mangafire\.to"
|
||||
|
||||
@@ -52,8 +53,9 @@ class MangafireChapterExtractor(MangafireBase, ChapterExtractor):
|
||||
|
||||
def images(self, page):
|
||||
url = f"{self.root}/ajax/read/{self.type}/{self.chapter_id}"
|
||||
params = {"vrf": generate_VRF(f"{self.type}@{self.chapter_id}")}
|
||||
headers = {"x-requested-with": "XMLHttpRequest"}
|
||||
data = self.request_json(url, headers=headers)
|
||||
data = self.request_json(url, params=params, headers=headers)
|
||||
|
||||
return [
|
||||
(image[0], None)
|
||||
@@ -129,8 +131,9 @@ def _manga_info(self, manga_path, page=None):
|
||||
def _manga_chapters(self, manga_info):
|
||||
manga_id, type, lang = manga_info
|
||||
url = f"{self.root}/ajax/read/{manga_id}/{type}/{lang}"
|
||||
params = {"vrf": generate_VRF(f"{manga_id}@{type}@{lang}")}
|
||||
headers = {"x-requested-with": "XMLHttpRequest"}
|
||||
data = self.request_json(url, headers=headers)
|
||||
data = self.request_json(url, params=params, headers=headers)
|
||||
|
||||
needle = f"{manga_id}/{lang}/"
|
||||
return {
|
||||
@@ -166,3 +169,122 @@ def _chapter_info(info):
|
||||
"title" : text.unescape(text.extr(info, 'title="', '"')),
|
||||
"lang" : lang,
|
||||
}
|
||||
|
||||
|
||||
###############################################################################
|
||||
# VRF generation utils
|
||||
#
|
||||
# adapted from dazedcat19/FMD2
|
||||
# https://github.com/dazedcat19/FMD2/blob/master/lua/modules/MangaFire.lua
|
||||
|
||||
def generate_VRF(input):
|
||||
input = text.quote(input).encode()
|
||||
|
||||
for key_b64, seed_b64, prefix_b64, schedule in (
|
||||
(key_l, seed_A, prefix_O, schedule_c),
|
||||
(key_g, seed_V, prefix_v, schedule_y),
|
||||
(key_B, seed_N, prefix_L, schedule_b),
|
||||
(key_m, seed_P, prefix_p, schedule_j),
|
||||
(key_F, seed_k, prefix_W, schedule_e),
|
||||
):
|
||||
input = transform(
|
||||
rc4(binascii.a2b_base64(key_b64), input),
|
||||
binascii.a2b_base64(seed_b64),
|
||||
binascii.a2b_base64(prefix_b64),
|
||||
schedule,
|
||||
)
|
||||
|
||||
return binascii.b2a_base64(bytes(input), newline=False).rstrip(
|
||||
b"=").replace(b"+", b"-").replace(b"/", b"_")
|
||||
|
||||
|
||||
def transform(input, seed, prefix, schedule):
|
||||
prefix_len = len(prefix)
|
||||
|
||||
out = []
|
||||
for idx, c in enumerate(input):
|
||||
if idx < prefix_len:
|
||||
out.append(prefix[idx] or 0)
|
||||
out.append(schedule[idx % 10]((c ^ seed[idx % 32]) & 255) & 255)
|
||||
return out
|
||||
|
||||
|
||||
def rc4(key, input):
|
||||
lkey = len(key)
|
||||
|
||||
j = 0
|
||||
s = list(range(256))
|
||||
for i in range(256):
|
||||
j = (j + s[i] + key[i % lkey]) & 255
|
||||
s[i], s[j] = s[j], s[i]
|
||||
|
||||
out = []
|
||||
i = j = 0
|
||||
for c in input:
|
||||
i = (i + 1) & 255
|
||||
j = (j + s[i]) & 255
|
||||
s[i], s[j] = s[j], s[i]
|
||||
k = s[(s[i] + s[j]) & 255]
|
||||
out.append(c ^ k)
|
||||
return out
|
||||
|
||||
|
||||
def add8(n):
|
||||
return lambda c: (c + n) & 255
|
||||
|
||||
|
||||
def sub8(n):
|
||||
return lambda c: (c - n + 256) & 255
|
||||
|
||||
|
||||
def xor8(n):
|
||||
return lambda c: (c ^ n) & 255
|
||||
|
||||
|
||||
def rotl8(n):
|
||||
return lambda c: ((c << n) | (c >> (8 - n))) & 255
|
||||
|
||||
|
||||
def rotr8(n):
|
||||
return lambda c: ((c >> n) | (c << (8 - n))) & 255
|
||||
|
||||
|
||||
schedule_c = (
|
||||
sub8(223), rotr8(4), rotr8(4), add8(234), rotr8(7),
|
||||
rotr8(2), rotr8(7), sub8(223), rotr8(7), rotr8(6),
|
||||
)
|
||||
schedule_y = (
|
||||
add8(19), rotr8(7), add8(19), rotr8(6), add8(19),
|
||||
rotr8(1), add8(19), rotr8(6), rotr8(7), rotr8(4),
|
||||
)
|
||||
schedule_b = (
|
||||
sub8(223), rotr8(1), add8(19), sub8(223), rotl8(2),
|
||||
sub8(223), add8(19), rotl8(1), rotl8(2), rotl8(1),
|
||||
)
|
||||
schedule_j = (
|
||||
add8(19), rotl8(1), rotl8(1), rotr8(1), add8(234),
|
||||
rotl8(1), sub8(223), rotl8(6), rotl8(4), rotl8(1),
|
||||
)
|
||||
schedule_e = (
|
||||
rotr8(1), rotl8(1), rotl8(6), rotr8(1), rotl8(2),
|
||||
rotr8(4), rotl8(1), rotl8(1), sub8(223), rotl8(2),
|
||||
)
|
||||
|
||||
|
||||
key_l = "FgxyJUQDPUGSzwbAq/ToWn4/e8jYzvabE+dLMb1XU1o="
|
||||
key_g = "CQx3CLwswJAnM1VxOqX+y+f3eUns03ulxv8Z+0gUyik="
|
||||
key_B = "fAS+otFLkKsKAJzu3yU+rGOlbbFVq+u+LaS6+s1eCJs="
|
||||
key_m = "Oy45fQVK9kq9019+VysXVlz1F9S1YwYKgXyzGlZrijo="
|
||||
key_F = "aoDIdXezm2l3HrcnQdkPJTDT8+W6mcl2/02ewBHfPzg="
|
||||
|
||||
seed_A = "yH6MXnMEcDVWO/9a6P9W92BAh1eRLVFxFlWTHUqQ474="
|
||||
seed_V = "RK7y4dZ0azs9Uqz+bbFB46Bx2K9EHg74ndxknY9uknA="
|
||||
seed_N = "rqr9HeTQOg8TlFiIGZpJaxcvAaKHwMwrkqojJCpcvoc="
|
||||
seed_P = "/4GPpmZXYpn5RpkP7FC/dt8SXz7W30nUZTe8wb+3xmU="
|
||||
seed_k = "wsSGSBXKWA9q1oDJpjtJddVxH+evCfL5SO9HZnUDFU8="
|
||||
|
||||
prefix_O = "l9PavRg="
|
||||
prefix_v = "Ml2v7ag1Jg=="
|
||||
prefix_L = "i/Va0UxrbMo="
|
||||
prefix_p = "WFjKAHGEkQM="
|
||||
prefix_W = "5Rr27rWd"
|
||||
|
||||
@@ -11,7 +11,7 @@ __tests__ = (
|
||||
{
|
||||
"#url" : "https://mangafire.to/read/moto-saikyou-yuusha-no-saishuushoku.qzq9j/en/chapter-4",
|
||||
"#class" : mangafire.MangafireChapterExtractor,
|
||||
"#pattern" : r"https://s3\.mfcdn2\.xyz/\w+/h/p\.jpg",
|
||||
"#pattern" : r"https://20l\.mfcdn2\.xyz/mf/\w+/h/p\.jpg",
|
||||
"#count" : 37,
|
||||
|
||||
"chapter" : 4,
|
||||
@@ -30,7 +30,7 @@ __tests__ = (
|
||||
"manga_slug" : "moto-saikyou-yuusha-no-saishuushoku",
|
||||
"published" : "Mar 31, 2022 to ?",
|
||||
"publisher" : ["Wild Hero's"],
|
||||
"score" : 9.0,
|
||||
"score" : float,
|
||||
"status" : "Releasing",
|
||||
"title" : "The Former Strongest Hero Makes a Choice",
|
||||
"type" : "Manga",
|
||||
@@ -54,7 +54,7 @@ __tests__ = (
|
||||
{
|
||||
"#url" : "https://mangafire.to/read/munou-wa-fuyou-to-iware-tokei-tsukai-no-boku-wa-shokuin-guild-kara-oidasareru-mo-dungeon-no-shinbu-de-shin-no-chikara-ni-kakusei-suru-the-comic.6wmv9/en/chapter-14.1",
|
||||
"#class" : mangafire.MangafireChapterExtractor,
|
||||
"#pattern" : r"https://s\d\.mfcdn\d\.xyz/\w+/h/p\.jpg",
|
||||
"#pattern" : r"https://\w+\.mfcdn\d\.xyz/mf/\w+/h/p\.jpg",
|
||||
"#count" : 13,
|
||||
|
||||
"chapter" : 14,
|
||||
@@ -73,7 +73,7 @@ __tests__ = (
|
||||
"manga_slug" : "munou-wa-fuyou-to-iware-tokei-tsukai-no-boku-wa-shokuin-guild-kara-oidasareru-mo-dungeon-no-shinbu-de-shin-no-chikara-ni-kakusei-suru-the-comic",
|
||||
"published" : "Feb 27, 2023 to ?",
|
||||
"publisher" : ["Comic Ride"],
|
||||
"score" : 9.0,
|
||||
"score" : range(5, 10),
|
||||
"status" : "Releasing",
|
||||
"title" : "Part 1 - Karim's Purpose",
|
||||
"type" : "Manga",
|
||||
@@ -96,7 +96,7 @@ __tests__ = (
|
||||
"#url" : "https://mangafire.to/read/munou-wa-fuyou-to-iware-tokei-tsukai-no-boku-wa-shokuin-guild-kara-oidasareru-mo-dungeon-no-shinbu-de-shin-no-chikara-ni-kakusei-suru-the-comic.6wmv9/en/volume-2",
|
||||
"#comment" : "volume",
|
||||
"#class" : mangafire.MangafireChapterExtractor,
|
||||
"#pattern" : r"https://s\d\.mfcdn\d\.xyz/\w+/h/p\.jpg",
|
||||
"#pattern" : r"https://\w+\.mfcdn\d\.xyz/mf/\w+/h/p\.jpg",
|
||||
"#count" : 154,
|
||||
|
||||
"volume" : 2,
|
||||
@@ -111,7 +111,7 @@ __tests__ = (
|
||||
"#url" : "https://mangafire.to/manga/my-noble-family-is-headed-for-ruin-so-i-may-as-well-study-magic-in-my-free-timee.xjj0w",
|
||||
"#class" : mangafire.MangafireMangaExtractor,
|
||||
"#pattern" : mangafire.MangafireChapterExtractor.pattern,
|
||||
"#count" : 31,
|
||||
"#count" : range(40, 60),
|
||||
|
||||
"chapter" : range(1, 30),
|
||||
"chapter_id" : int,
|
||||
@@ -156,7 +156,7 @@ __tests__ = (
|
||||
"#url" : "https://mangafire.to/manga/regressing-as-the-reincarnated-bastard-of-the-sword-clann.90vp0",
|
||||
"#class" : mangafire.MangafireMangaExtractor,
|
||||
"#pattern" : mangafire.MangafireChapterExtractor.pattern,
|
||||
"#count" : 62,
|
||||
"#count" : range(70, 120),
|
||||
|
||||
"author" : (),
|
||||
"chapter" : int,
|
||||
@@ -171,7 +171,7 @@ __tests__ = (
|
||||
"manga_slug" : "regressing-as-the-reincarnated-bastard-of-the-sword-clann",
|
||||
"published" : "2024 to ?",
|
||||
"publisher" : (),
|
||||
"score" : 9.0,
|
||||
"score" : range(8, 10),
|
||||
"status" : "Releasing",
|
||||
"title" : "",
|
||||
"type" : "Manhwa",
|
||||
|
||||
Reference in New Issue
Block a user