novel-downloader 2.0.0__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 +14 -11
- novel_downloader/cli/export.py +19 -19
- novel_downloader/cli/ui.py +35 -8
- novel_downloader/config/adapter.py +216 -153
- novel_downloader/core/__init__.py +5 -6
- 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 +27 -68
- 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 +23 -49
- novel_downloader/core/interfaces/parser.py +2 -2
- novel_downloader/core/parsers/__init__.py +4 -2
- novel_downloader/core/parsers/b520.py +2 -2
- novel_downloader/core/parsers/base.py +5 -39
- novel_downloader/core/parsers/esjzone.py +3 -3
- novel_downloader/core/parsers/{eightnovel.py → n8novel.py} +7 -7
- novel_downloader/core/parsers/qidian.py +717 -0
- novel_downloader/core/parsers/qqbook.py +709 -0
- novel_downloader/core/parsers/xiguashuwu.py +8 -15
- 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/locales/en.json +3 -3
- novel_downloader/locales/zh.json +3 -3
- 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/__init__.py +0 -2
- novel_downloader/utils/chapter_storage.py +2 -3
- novel_downloader/utils/constants.py +7 -3
- novel_downloader/utils/cookies.py +32 -17
- novel_downloader/utils/crypto_utils/__init__.py +0 -6
- novel_downloader/utils/crypto_utils/aes_util.py +1 -1
- novel_downloader/utils/crypto_utils/rc4.py +40 -50
- novel_downloader/utils/epub/__init__.py +2 -3
- novel_downloader/utils/epub/builder.py +6 -6
- novel_downloader/utils/epub/constants.py +1 -6
- novel_downloader/utils/epub/documents.py +7 -7
- novel_downloader/utils/epub/models.py +8 -8
- novel_downloader/utils/epub/utils.py +10 -10
- novel_downloader/utils/file_utils/io.py +48 -73
- novel_downloader/utils/file_utils/normalize.py +1 -7
- novel_downloader/utils/file_utils/sanitize.py +4 -11
- novel_downloader/utils/fontocr/__init__.py +13 -0
- novel_downloader/utils/{fontocr.py → fontocr/core.py} +72 -61
- novel_downloader/utils/fontocr/loader.py +52 -0
- novel_downloader/utils/logger.py +80 -56
- novel_downloader/utils/network.py +16 -40
- 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/utils/text_utils/text_cleaner.py +39 -30
- novel_downloader/utils/text_utils/truncate_utils.py +3 -14
- novel_downloader/utils/time_utils/sleep_utils.py +53 -43
- novel_downloader/web/main.py +1 -1
- novel_downloader/web/pages/download.py +1 -1
- novel_downloader/web/pages/search.py +4 -4
- novel_downloader/web/services/task_manager.py +2 -0
- {novel_downloader-2.0.0.dist-info → novel_downloader-2.0.2.dist-info}/METADATA +5 -1
- novel_downloader-2.0.2.dist-info/RECORD +203 -0
- 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/book_info_parser.py +0 -89
- novel_downloader/core/parsers/qidian/chapter_encrypted.py +0 -470
- novel_downloader/core/parsers/qidian/chapter_normal.py +0 -126
- novel_downloader/core/parsers/qidian/chapter_router.py +0 -68
- novel_downloader/core/parsers/qidian/main_parser.py +0 -101
- novel_downloader/core/parsers/qidian/utils/__init__.py +0 -30
- novel_downloader/core/parsers/qidian/utils/fontmap_recover.py +0 -143
- novel_downloader/core/parsers/qidian/utils/helpers.py +0 -110
- novel_downloader/core/parsers/qidian/utils/node_decryptor.py +0 -175
- novel_downloader-2.0.0.dist-info/RECORD +0 -210
- {novel_downloader-2.0.0.dist-info → novel_downloader-2.0.2.dist-info}/WHEEL +0 -0
- {novel_downloader-2.0.0.dist-info → novel_downloader-2.0.2.dist-info}/entry_points.txt +0 -0
- {novel_downloader-2.0.0.dist-info → novel_downloader-2.0.2.dist-info}/licenses/LICENSE +0 -0
- {novel_downloader-2.0.0.dist-info → novel_downloader-2.0.2.dist-info}/top_level.txt +0 -0
novel_downloader/__init__.py
CHANGED
novel_downloader/cli/download.py
CHANGED
@@ -155,7 +155,7 @@ async def _download(
|
|
155
155
|
exporter_cfg = adapter.get_exporter_config()
|
156
156
|
login_cfg = adapter.get_login_config()
|
157
157
|
log_level = adapter.get_log_level()
|
158
|
-
setup_logging(
|
158
|
+
setup_logging(console_level=log_level)
|
159
159
|
|
160
160
|
parser = get_parser(site, parser_cfg)
|
161
161
|
exporter = None
|
@@ -178,7 +178,14 @@ async def _download(
|
|
178
178
|
|
179
179
|
for book in valid_books:
|
180
180
|
ui.info(t("download_downloading", book_id=book["book_id"], site=site))
|
181
|
-
|
181
|
+
|
182
|
+
hook, close = ui.create_progress_hook(
|
183
|
+
prefix=t("download_progress_prefix"), unit="chapters"
|
184
|
+
)
|
185
|
+
try:
|
186
|
+
await downloader.download(book, progress_hook=hook)
|
187
|
+
finally:
|
188
|
+
close()
|
182
189
|
|
183
190
|
if not no_export and exporter is not None:
|
184
191
|
await asyncio.to_thread(exporter.export, book["book_id"])
|
@@ -186,6 +193,9 @@ async def _download(
|
|
186
193
|
if downloader_cfg.login_required and fetcher.is_logged_in:
|
187
194
|
await fetcher.save_state()
|
188
195
|
|
196
|
+
if exporter is not None:
|
197
|
+
exporter.close()
|
198
|
+
|
189
199
|
|
190
200
|
async def _prompt_login_fields(
|
191
201
|
fields: list[LoginField],
|
@@ -214,8 +224,8 @@ async def _prompt_login_fields(
|
|
214
224
|
ui.info(t("login_use_config"))
|
215
225
|
continue
|
216
226
|
|
217
|
-
value: str | dict[str, str]
|
218
|
-
|
227
|
+
value: str | dict[str, str] = ""
|
228
|
+
for _ in range(5):
|
219
229
|
if field.type == "password":
|
220
230
|
value = ui.prompt_password(t("login_enter_password"))
|
221
231
|
elif field.type == "cookie":
|
@@ -235,10 +245,3 @@ async def _prompt_login_fields(
|
|
235
245
|
result[field.name] = value
|
236
246
|
|
237
247
|
return result
|
238
|
-
|
239
|
-
|
240
|
-
async def _print_progress(done: int, total: int) -> None:
|
241
|
-
"""Progress hook passed into the downloader."""
|
242
|
-
ui.print_progress(
|
243
|
-
done, total, prefix=t("download_progress_prefix"), unit="chapters"
|
244
|
-
)
|
novel_downloader/cli/export.py
CHANGED
@@ -55,22 +55,22 @@ def handle_export(args: Namespace) -> None:
|
|
55
55
|
adapter = ConfigAdapter(config=config_data, site=site)
|
56
56
|
exporter_cfg = adapter.get_exporter_config()
|
57
57
|
log_level = adapter.get_log_level()
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
58
|
+
setup_logging(console_level=log_level)
|
59
|
+
|
60
|
+
with get_exporter(site, exporter_cfg) as exporter:
|
61
|
+
for book_id in book_ids:
|
62
|
+
ui.info(t("export_processing", book_id=book_id, format=export_format))
|
63
|
+
|
64
|
+
if export_format in {"txt", "all"}:
|
65
|
+
try:
|
66
|
+
exporter.export_as_txt(book_id)
|
67
|
+
ui.success(t("export_success_txt", book_id=book_id))
|
68
|
+
except Exception as e:
|
69
|
+
ui.error(t("export_failed_txt", book_id=book_id, err=str(e)))
|
70
|
+
|
71
|
+
if export_format in {"epub", "all"}:
|
72
|
+
try:
|
73
|
+
exporter.export_as_epub(book_id)
|
74
|
+
ui.success(t("export_success_epub", book_id=book_id))
|
75
|
+
except Exception as e:
|
76
|
+
ui.error(t("export_failed_epub", book_id=book_id, err=str(e)))
|
novel_downloader/cli/ui.py
CHANGED
@@ -7,19 +7,20 @@ A small set of Rich-based helpers to keep CLI presentation and prompts
|
|
7
7
|
consistent across subcommands.
|
8
8
|
|
9
9
|
Public API:
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
10
|
+
* info, success, warn, error
|
11
|
+
* confirm
|
12
|
+
* prompt, prompt_password
|
13
|
+
* render_table
|
14
|
+
* select_index
|
15
|
+
* print_progress
|
16
16
|
"""
|
17
17
|
|
18
18
|
from __future__ import annotations
|
19
19
|
|
20
|
-
from collections.abc import Iterable, Sequence
|
20
|
+
from collections.abc import Awaitable, Callable, Iterable, Sequence
|
21
21
|
|
22
22
|
from rich.console import Console
|
23
|
+
from rich.progress import Progress, TaskID
|
23
24
|
from rich.prompt import Confirm, Prompt
|
24
25
|
from rich.table import Table
|
25
26
|
|
@@ -71,7 +72,7 @@ def prompt(message: str, *, default: str | None = None) -> str:
|
|
71
72
|
:return: The user's input.
|
72
73
|
"""
|
73
74
|
try:
|
74
|
-
result: str = Prompt.ask(message, default=default or "")
|
75
|
+
result: str = Prompt.ask(message, default=default or "", show_default=False)
|
75
76
|
return result
|
76
77
|
except (KeyboardInterrupt, EOFError):
|
77
78
|
warn("Cancelled.")
|
@@ -154,3 +155,29 @@ def print_progress(
|
|
154
155
|
total = max(1, total)
|
155
156
|
pct = done / total * 100.0
|
156
157
|
_CONSOLE.print(f"[dim]{prefix}[/] {done}/{total} {unit} ({pct:.2f}%)")
|
158
|
+
|
159
|
+
|
160
|
+
def create_progress_hook(
|
161
|
+
prefix: str = "Progress",
|
162
|
+
unit: str = "item",
|
163
|
+
) -> tuple[Callable[[int, int], Awaitable[None]], Callable[[], None]]:
|
164
|
+
progress = Progress(console=_CONSOLE)
|
165
|
+
task_id: TaskID | None = None
|
166
|
+
|
167
|
+
async def hook(done: int, total: int) -> None:
|
168
|
+
nonlocal task_id
|
169
|
+
if task_id is None:
|
170
|
+
progress.start()
|
171
|
+
task_id = progress.add_task(f"[cyan]{prefix}[/]", total=max(1, total))
|
172
|
+
|
173
|
+
progress.update(
|
174
|
+
task_id,
|
175
|
+
completed=done,
|
176
|
+
total=max(1, total),
|
177
|
+
description=f"{prefix} ({done}/{total} {unit})",
|
178
|
+
)
|
179
|
+
|
180
|
+
def close() -> None:
|
181
|
+
progress.stop()
|
182
|
+
|
183
|
+
return hook, close
|