novel-downloader 2.0.1__py3-none-any.whl → 2.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- novel_downloader/__init__.py +1 -1
- novel_downloader/cli/download.py +11 -8
- novel_downloader/cli/export.py +17 -17
- novel_downloader/cli/ui.py +28 -1
- novel_downloader/config/adapter.py +27 -1
- novel_downloader/core/archived/deqixs/fetcher.py +1 -28
- novel_downloader/core/downloaders/__init__.py +2 -0
- novel_downloader/core/downloaders/base.py +34 -85
- novel_downloader/core/downloaders/common.py +147 -171
- novel_downloader/core/downloaders/qianbi.py +30 -64
- novel_downloader/core/downloaders/qidian.py +157 -184
- novel_downloader/core/downloaders/qqbook.py +292 -0
- novel_downloader/core/downloaders/registry.py +2 -2
- novel_downloader/core/exporters/__init__.py +2 -0
- novel_downloader/core/exporters/base.py +37 -59
- novel_downloader/core/exporters/common.py +620 -0
- novel_downloader/core/exporters/linovelib.py +47 -0
- novel_downloader/core/exporters/qidian.py +41 -12
- novel_downloader/core/exporters/qqbook.py +28 -0
- novel_downloader/core/exporters/registry.py +2 -2
- novel_downloader/core/fetchers/__init__.py +4 -2
- novel_downloader/core/fetchers/aaatxt.py +2 -22
- novel_downloader/core/fetchers/b520.py +3 -23
- novel_downloader/core/fetchers/base.py +80 -105
- novel_downloader/core/fetchers/biquyuedu.py +2 -22
- novel_downloader/core/fetchers/dxmwx.py +10 -22
- novel_downloader/core/fetchers/esjzone.py +6 -29
- novel_downloader/core/fetchers/guidaye.py +2 -22
- novel_downloader/core/fetchers/hetushu.py +9 -29
- novel_downloader/core/fetchers/i25zw.py +2 -16
- novel_downloader/core/fetchers/ixdzs8.py +2 -16
- novel_downloader/core/fetchers/jpxs123.py +2 -16
- novel_downloader/core/fetchers/lewenn.py +2 -22
- novel_downloader/core/fetchers/linovelib.py +4 -20
- novel_downloader/core/fetchers/{eightnovel.py → n8novel.py} +12 -40
- novel_downloader/core/fetchers/piaotia.py +2 -16
- novel_downloader/core/fetchers/qbtr.py +2 -16
- novel_downloader/core/fetchers/qianbi.py +1 -20
- novel_downloader/core/fetchers/qidian.py +7 -33
- novel_downloader/core/fetchers/qqbook.py +177 -0
- novel_downloader/core/fetchers/quanben5.py +9 -29
- novel_downloader/core/fetchers/rate_limiter.py +22 -53
- novel_downloader/core/fetchers/sfacg.py +3 -16
- novel_downloader/core/fetchers/shencou.py +2 -16
- novel_downloader/core/fetchers/shuhaige.py +2 -22
- novel_downloader/core/fetchers/tongrenquan.py +2 -22
- novel_downloader/core/fetchers/ttkan.py +3 -14
- novel_downloader/core/fetchers/wanbengo.py +2 -22
- novel_downloader/core/fetchers/xiaoshuowu.py +2 -16
- novel_downloader/core/fetchers/xiguashuwu.py +4 -20
- novel_downloader/core/fetchers/xs63b.py +3 -15
- novel_downloader/core/fetchers/xshbook.py +2 -22
- novel_downloader/core/fetchers/yamibo.py +4 -28
- novel_downloader/core/fetchers/yibige.py +13 -26
- novel_downloader/core/interfaces/exporter.py +19 -7
- novel_downloader/core/interfaces/fetcher.py +21 -47
- novel_downloader/core/parsers/__init__.py +4 -2
- novel_downloader/core/parsers/b520.py +2 -2
- novel_downloader/core/parsers/base.py +4 -39
- novel_downloader/core/parsers/{eightnovel.py → n8novel.py} +5 -5
- novel_downloader/core/parsers/{qidian/main_parser.py → qidian.py} +147 -266
- novel_downloader/core/parsers/qqbook.py +709 -0
- novel_downloader/core/parsers/xiguashuwu.py +3 -4
- novel_downloader/core/searchers/__init__.py +2 -2
- novel_downloader/core/searchers/b520.py +1 -1
- novel_downloader/core/searchers/base.py +2 -2
- novel_downloader/core/searchers/{eightnovel.py → n8novel.py} +5 -5
- novel_downloader/models/__init__.py +2 -0
- novel_downloader/models/book.py +1 -0
- novel_downloader/models/config.py +12 -0
- novel_downloader/resources/config/settings.toml +23 -5
- novel_downloader/resources/js_scripts/expr_to_json.js +14 -0
- novel_downloader/resources/js_scripts/qidian_decrypt_node.js +21 -16
- novel_downloader/resources/js_scripts/qq_decrypt_node.js +92 -0
- novel_downloader/utils/constants.py +6 -0
- novel_downloader/utils/crypto_utils/aes_util.py +1 -1
- novel_downloader/utils/epub/constants.py +1 -6
- novel_downloader/utils/fontocr/core.py +2 -0
- novel_downloader/utils/fontocr/loader.py +10 -8
- novel_downloader/utils/node_decryptor/__init__.py +13 -0
- novel_downloader/utils/node_decryptor/decryptor.py +342 -0
- novel_downloader/{core/parsers/qidian/utils → utils/node_decryptor}/decryptor_fetcher.py +5 -6
- novel_downloader/web/pages/download.py +1 -1
- novel_downloader/web/pages/search.py +1 -1
- novel_downloader/web/services/task_manager.py +2 -0
- {novel_downloader-2.0.1.dist-info → novel_downloader-2.0.2.dist-info}/METADATA +4 -1
- {novel_downloader-2.0.1.dist-info → novel_downloader-2.0.2.dist-info}/RECORD +91 -94
- novel_downloader/core/exporters/common/__init__.py +0 -11
- novel_downloader/core/exporters/common/epub.py +0 -198
- novel_downloader/core/exporters/common/main_exporter.py +0 -64
- novel_downloader/core/exporters/common/txt.py +0 -146
- novel_downloader/core/exporters/epub_util.py +0 -215
- novel_downloader/core/exporters/linovelib/__init__.py +0 -11
- novel_downloader/core/exporters/linovelib/epub.py +0 -349
- novel_downloader/core/exporters/linovelib/main_exporter.py +0 -66
- novel_downloader/core/exporters/linovelib/txt.py +0 -139
- novel_downloader/core/exporters/txt_util.py +0 -67
- novel_downloader/core/parsers/qidian/__init__.py +0 -10
- novel_downloader/core/parsers/qidian/utils/__init__.py +0 -11
- novel_downloader/core/parsers/qidian/utils/node_decryptor.py +0 -175
- {novel_downloader-2.0.1.dist-info → novel_downloader-2.0.2.dist-info}/WHEEL +0 -0
- {novel_downloader-2.0.1.dist-info → novel_downloader-2.0.2.dist-info}/entry_points.txt +0 -0
- {novel_downloader-2.0.1.dist-info → novel_downloader-2.0.2.dist-info}/licenses/LICENSE +0 -0
- {novel_downloader-2.0.1.dist-info → novel_downloader-2.0.2.dist-info}/top_level.txt +0 -0
@@ -1,175 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
novel_downloader.core.parsers.qidian.utils.node_decryptor
|
4
|
-
---------------------------------------------------------
|
5
|
-
|
6
|
-
Provides QidianNodeDecryptor, which ensures a Node.js environment,
|
7
|
-
downloads or installs the required JS modules (Fock + decrypt script),
|
8
|
-
and invokes a Node.js subprocess to decrypt Qidian chapter content.
|
9
|
-
"""
|
10
|
-
|
11
|
-
import json
|
12
|
-
import logging
|
13
|
-
import shutil
|
14
|
-
import subprocess
|
15
|
-
import uuid
|
16
|
-
from pathlib import Path
|
17
|
-
|
18
|
-
from novel_downloader.utils.constants import (
|
19
|
-
JS_SCRIPT_DIR,
|
20
|
-
QD_DECRYPT_SCRIPT_PATH,
|
21
|
-
)
|
22
|
-
|
23
|
-
from .decryptor_fetcher import ensure_decryptor
|
24
|
-
|
25
|
-
logger = logging.getLogger(__name__)
|
26
|
-
|
27
|
-
|
28
|
-
class QidianNodeDecryptor:
|
29
|
-
"""
|
30
|
-
A decryptor that uses Node.js plus Qidian's Fock JavaScript module
|
31
|
-
to decrypt encrypted chapter payloads.
|
32
|
-
|
33
|
-
On initialization, this class will:
|
34
|
-
1. Verify that `node` is on PATH.
|
35
|
-
2. Copy our bundled `qidian_decrypt_node.js` into `JS_SCRIPT_DIR`.
|
36
|
-
3. Download the remote Fock module JS if not already present.
|
37
|
-
|
38
|
-
Calling `decrypt()` will:
|
39
|
-
* Write a temp JSON input file with [ciphertext, chapter_id, fkp, fuid].
|
40
|
-
* Spawn `node qidian_decrypt_node.js <in> <out>`.
|
41
|
-
* Read and return the decrypted text.
|
42
|
-
* Clean up the temp files.
|
43
|
-
"""
|
44
|
-
|
45
|
-
QIDIAN_FOCK_JS_URL: str = (
|
46
|
-
"https://cococdn.qidian.com/coco/s12062024/4819793b.qeooxh.js"
|
47
|
-
)
|
48
|
-
QIDIAN_FOCK_JS_PATH: Path = JS_SCRIPT_DIR / "4819793b.qeooxh.js"
|
49
|
-
QIDIAN_DECRYPT_SCRIPT_FILE: str = "qidian_decrypt_node.js"
|
50
|
-
QIDIAN_DECRYPT_SCRIPT_PATH: Path = JS_SCRIPT_DIR / QIDIAN_DECRYPT_SCRIPT_FILE
|
51
|
-
|
52
|
-
def __init__(self) -> None:
|
53
|
-
"""
|
54
|
-
Initialise the decryptor environment and decide which executable will be
|
55
|
-
used (`node` script or the pre-built binary).
|
56
|
-
"""
|
57
|
-
self.script_dir: Path = JS_SCRIPT_DIR
|
58
|
-
self.script_dir.mkdir(parents=True, exist_ok=True)
|
59
|
-
|
60
|
-
self._script_cmd: list[str] | None = None
|
61
|
-
self._check_environment()
|
62
|
-
|
63
|
-
def _check_environment(self) -> None:
|
64
|
-
"""
|
65
|
-
Decide which decryptor backend to use and make sure it is ready.
|
66
|
-
"""
|
67
|
-
try:
|
68
|
-
# 1) Check Node.js
|
69
|
-
if not shutil.which("node"):
|
70
|
-
raise OSError("Node.js is not installed or not in PATH.")
|
71
|
-
|
72
|
-
# 2) Copy bundled decrypt script into place if missing
|
73
|
-
if not self.QIDIAN_DECRYPT_SCRIPT_PATH.exists():
|
74
|
-
try:
|
75
|
-
resource = QD_DECRYPT_SCRIPT_PATH
|
76
|
-
shutil.copyfile(str(resource), str(self.QIDIAN_DECRYPT_SCRIPT_PATH))
|
77
|
-
except Exception as e:
|
78
|
-
logger.error("[decryptor] Failed to copy decrypt script: %s", e)
|
79
|
-
raise
|
80
|
-
|
81
|
-
# 3) Download the Fock JS module from Qidian CDN if missing
|
82
|
-
if not self.QIDIAN_FOCK_JS_PATH.exists():
|
83
|
-
from novel_downloader.utils.network import download
|
84
|
-
|
85
|
-
try:
|
86
|
-
download(
|
87
|
-
self.QIDIAN_FOCK_JS_URL,
|
88
|
-
self.script_dir,
|
89
|
-
on_exist="overwrite",
|
90
|
-
)
|
91
|
-
except Exception as e:
|
92
|
-
logger.error("[decryptor] Failed to download Fock JS module: %s", e)
|
93
|
-
raise
|
94
|
-
self._script_cmd = ["node", str(self.QIDIAN_DECRYPT_SCRIPT_PATH)]
|
95
|
-
return
|
96
|
-
except Exception:
|
97
|
-
try:
|
98
|
-
self._script_cmd = [str(ensure_decryptor(self.script_dir))]
|
99
|
-
except Exception as exc:
|
100
|
-
raise OSError(
|
101
|
-
"Neither Node.js nor fallback binary is available."
|
102
|
-
) from exc
|
103
|
-
|
104
|
-
def decrypt(
|
105
|
-
self,
|
106
|
-
ciphertext: str | bytes,
|
107
|
-
chapter_id: str,
|
108
|
-
fkp: str,
|
109
|
-
fuid: str,
|
110
|
-
) -> str:
|
111
|
-
"""
|
112
|
-
Decrypt a chapter payload via our Node.js script.
|
113
|
-
|
114
|
-
:param ciphertext: Base64-encoded encrypted content (str or bytes).
|
115
|
-
:param chapter_id: The chapter's numeric ID.
|
116
|
-
:param fkp: Base64-encoded Fock key param from the page.
|
117
|
-
:param fuid: Fock user ID param from the page.
|
118
|
-
:return: The decrypted plain-text content.
|
119
|
-
:raises RuntimeError: if the Node.js subprocess exits with a non-zero code.
|
120
|
-
"""
|
121
|
-
if not self._script_cmd:
|
122
|
-
return ""
|
123
|
-
if not (ciphertext and chapter_id and fkp and fuid):
|
124
|
-
return ""
|
125
|
-
# Normalize inputs
|
126
|
-
cipher_str = (
|
127
|
-
ciphertext.decode("utf-8")
|
128
|
-
if isinstance(ciphertext, (bytes | bytearray))
|
129
|
-
else str(ciphertext)
|
130
|
-
)
|
131
|
-
chapter_str = str(chapter_id)
|
132
|
-
|
133
|
-
# Create unique temp file names
|
134
|
-
task_id = uuid.uuid4().hex
|
135
|
-
input_path = self.script_dir / f"input_{task_id}.json"
|
136
|
-
output_path = self.script_dir / f"output_{task_id}.txt"
|
137
|
-
|
138
|
-
try:
|
139
|
-
# Write arguments as JSON array
|
140
|
-
input_path.write_text(
|
141
|
-
json.dumps([cipher_str, chapter_str, fkp, fuid]),
|
142
|
-
encoding="utf-8",
|
143
|
-
)
|
144
|
-
|
145
|
-
cmd = self._script_cmd + [input_path.name, output_path.name]
|
146
|
-
proc = subprocess.run(
|
147
|
-
cmd,
|
148
|
-
capture_output=True,
|
149
|
-
text=True,
|
150
|
-
cwd=str(self.script_dir),
|
151
|
-
)
|
152
|
-
|
153
|
-
if proc.returncode != 0:
|
154
|
-
raise RuntimeError(f"Node error: {proc.stderr.strip()}")
|
155
|
-
|
156
|
-
# Return decrypted content
|
157
|
-
return output_path.read_text(encoding="utf-8").strip()
|
158
|
-
|
159
|
-
finally:
|
160
|
-
# Clean up temp files
|
161
|
-
input_path.unlink(missing_ok=True)
|
162
|
-
output_path.unlink(missing_ok=True)
|
163
|
-
|
164
|
-
|
165
|
-
_decryptor: QidianNodeDecryptor | None = None
|
166
|
-
|
167
|
-
|
168
|
-
def get_decryptor() -> QidianNodeDecryptor:
|
169
|
-
"""
|
170
|
-
Return the singleton QidianNodeDecryptor, initializing it on first use.
|
171
|
-
"""
|
172
|
-
global _decryptor
|
173
|
-
if _decryptor is None:
|
174
|
-
_decryptor = QidianNodeDecryptor()
|
175
|
-
return _decryptor
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|