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.
Files changed (104) hide show
  1. novel_downloader/__init__.py +1 -1
  2. novel_downloader/cli/download.py +11 -8
  3. novel_downloader/cli/export.py +17 -17
  4. novel_downloader/cli/ui.py +28 -1
  5. novel_downloader/config/adapter.py +27 -1
  6. novel_downloader/core/archived/deqixs/fetcher.py +1 -28
  7. novel_downloader/core/downloaders/__init__.py +2 -0
  8. novel_downloader/core/downloaders/base.py +34 -85
  9. novel_downloader/core/downloaders/common.py +147 -171
  10. novel_downloader/core/downloaders/qianbi.py +30 -64
  11. novel_downloader/core/downloaders/qidian.py +157 -184
  12. novel_downloader/core/downloaders/qqbook.py +292 -0
  13. novel_downloader/core/downloaders/registry.py +2 -2
  14. novel_downloader/core/exporters/__init__.py +2 -0
  15. novel_downloader/core/exporters/base.py +37 -59
  16. novel_downloader/core/exporters/common.py +620 -0
  17. novel_downloader/core/exporters/linovelib.py +47 -0
  18. novel_downloader/core/exporters/qidian.py +41 -12
  19. novel_downloader/core/exporters/qqbook.py +28 -0
  20. novel_downloader/core/exporters/registry.py +2 -2
  21. novel_downloader/core/fetchers/__init__.py +4 -2
  22. novel_downloader/core/fetchers/aaatxt.py +2 -22
  23. novel_downloader/core/fetchers/b520.py +3 -23
  24. novel_downloader/core/fetchers/base.py +80 -105
  25. novel_downloader/core/fetchers/biquyuedu.py +2 -22
  26. novel_downloader/core/fetchers/dxmwx.py +10 -22
  27. novel_downloader/core/fetchers/esjzone.py +6 -29
  28. novel_downloader/core/fetchers/guidaye.py +2 -22
  29. novel_downloader/core/fetchers/hetushu.py +9 -29
  30. novel_downloader/core/fetchers/i25zw.py +2 -16
  31. novel_downloader/core/fetchers/ixdzs8.py +2 -16
  32. novel_downloader/core/fetchers/jpxs123.py +2 -16
  33. novel_downloader/core/fetchers/lewenn.py +2 -22
  34. novel_downloader/core/fetchers/linovelib.py +4 -20
  35. novel_downloader/core/fetchers/{eightnovel.py → n8novel.py} +12 -40
  36. novel_downloader/core/fetchers/piaotia.py +2 -16
  37. novel_downloader/core/fetchers/qbtr.py +2 -16
  38. novel_downloader/core/fetchers/qianbi.py +1 -20
  39. novel_downloader/core/fetchers/qidian.py +7 -33
  40. novel_downloader/core/fetchers/qqbook.py +177 -0
  41. novel_downloader/core/fetchers/quanben5.py +9 -29
  42. novel_downloader/core/fetchers/rate_limiter.py +22 -53
  43. novel_downloader/core/fetchers/sfacg.py +3 -16
  44. novel_downloader/core/fetchers/shencou.py +2 -16
  45. novel_downloader/core/fetchers/shuhaige.py +2 -22
  46. novel_downloader/core/fetchers/tongrenquan.py +2 -22
  47. novel_downloader/core/fetchers/ttkan.py +3 -14
  48. novel_downloader/core/fetchers/wanbengo.py +2 -22
  49. novel_downloader/core/fetchers/xiaoshuowu.py +2 -16
  50. novel_downloader/core/fetchers/xiguashuwu.py +4 -20
  51. novel_downloader/core/fetchers/xs63b.py +3 -15
  52. novel_downloader/core/fetchers/xshbook.py +2 -22
  53. novel_downloader/core/fetchers/yamibo.py +4 -28
  54. novel_downloader/core/fetchers/yibige.py +13 -26
  55. novel_downloader/core/interfaces/exporter.py +19 -7
  56. novel_downloader/core/interfaces/fetcher.py +21 -47
  57. novel_downloader/core/parsers/__init__.py +4 -2
  58. novel_downloader/core/parsers/b520.py +2 -2
  59. novel_downloader/core/parsers/base.py +4 -39
  60. novel_downloader/core/parsers/{eightnovel.py → n8novel.py} +5 -5
  61. novel_downloader/core/parsers/{qidian/main_parser.py → qidian.py} +147 -266
  62. novel_downloader/core/parsers/qqbook.py +709 -0
  63. novel_downloader/core/parsers/xiguashuwu.py +3 -4
  64. novel_downloader/core/searchers/__init__.py +2 -2
  65. novel_downloader/core/searchers/b520.py +1 -1
  66. novel_downloader/core/searchers/base.py +2 -2
  67. novel_downloader/core/searchers/{eightnovel.py → n8novel.py} +5 -5
  68. novel_downloader/models/__init__.py +2 -0
  69. novel_downloader/models/book.py +1 -0
  70. novel_downloader/models/config.py +12 -0
  71. novel_downloader/resources/config/settings.toml +23 -5
  72. novel_downloader/resources/js_scripts/expr_to_json.js +14 -0
  73. novel_downloader/resources/js_scripts/qidian_decrypt_node.js +21 -16
  74. novel_downloader/resources/js_scripts/qq_decrypt_node.js +92 -0
  75. novel_downloader/utils/constants.py +6 -0
  76. novel_downloader/utils/crypto_utils/aes_util.py +1 -1
  77. novel_downloader/utils/epub/constants.py +1 -6
  78. novel_downloader/utils/fontocr/core.py +2 -0
  79. novel_downloader/utils/fontocr/loader.py +10 -8
  80. novel_downloader/utils/node_decryptor/__init__.py +13 -0
  81. novel_downloader/utils/node_decryptor/decryptor.py +342 -0
  82. novel_downloader/{core/parsers/qidian/utils → utils/node_decryptor}/decryptor_fetcher.py +5 -6
  83. novel_downloader/web/pages/download.py +1 -1
  84. novel_downloader/web/pages/search.py +1 -1
  85. novel_downloader/web/services/task_manager.py +2 -0
  86. {novel_downloader-2.0.1.dist-info → novel_downloader-2.0.2.dist-info}/METADATA +4 -1
  87. {novel_downloader-2.0.1.dist-info → novel_downloader-2.0.2.dist-info}/RECORD +91 -94
  88. novel_downloader/core/exporters/common/__init__.py +0 -11
  89. novel_downloader/core/exporters/common/epub.py +0 -198
  90. novel_downloader/core/exporters/common/main_exporter.py +0 -64
  91. novel_downloader/core/exporters/common/txt.py +0 -146
  92. novel_downloader/core/exporters/epub_util.py +0 -215
  93. novel_downloader/core/exporters/linovelib/__init__.py +0 -11
  94. novel_downloader/core/exporters/linovelib/epub.py +0 -349
  95. novel_downloader/core/exporters/linovelib/main_exporter.py +0 -66
  96. novel_downloader/core/exporters/linovelib/txt.py +0 -139
  97. novel_downloader/core/exporters/txt_util.py +0 -67
  98. novel_downloader/core/parsers/qidian/__init__.py +0 -10
  99. novel_downloader/core/parsers/qidian/utils/__init__.py +0 -11
  100. novel_downloader/core/parsers/qidian/utils/node_decryptor.py +0 -175
  101. {novel_downloader-2.0.1.dist-info → novel_downloader-2.0.2.dist-info}/WHEEL +0 -0
  102. {novel_downloader-2.0.1.dist-info → novel_downloader-2.0.2.dist-info}/entry_points.txt +0 -0
  103. {novel_downloader-2.0.1.dist-info → novel_downloader-2.0.2.dist-info}/licenses/LICENSE +0 -0
  104. {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