novel-downloader 1.4.5__py3-none-any.whl → 2.0.0__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/__init__.py +2 -4
- novel_downloader/cli/clean.py +21 -88
- novel_downloader/cli/config.py +27 -104
- novel_downloader/cli/download.py +78 -66
- novel_downloader/cli/export.py +20 -21
- novel_downloader/cli/main.py +3 -1
- novel_downloader/cli/search.py +120 -0
- novel_downloader/cli/ui.py +156 -0
- novel_downloader/config/__init__.py +10 -14
- novel_downloader/config/adapter.py +195 -99
- novel_downloader/config/{loader.py → file_io.py} +53 -27
- novel_downloader/core/__init__.py +14 -13
- novel_downloader/core/archived/deqixs/fetcher.py +115 -0
- novel_downloader/core/archived/deqixs/parser.py +132 -0
- novel_downloader/core/archived/deqixs/searcher.py +89 -0
- novel_downloader/core/archived/qidian/searcher.py +79 -0
- novel_downloader/core/archived/wanbengo/searcher.py +98 -0
- novel_downloader/core/archived/xshbook/searcher.py +93 -0
- novel_downloader/core/downloaders/__init__.py +8 -30
- novel_downloader/core/downloaders/base.py +182 -30
- novel_downloader/core/downloaders/common.py +217 -384
- novel_downloader/core/downloaders/qianbi.py +332 -4
- novel_downloader/core/downloaders/qidian.py +250 -290
- novel_downloader/core/downloaders/registry.py +69 -0
- novel_downloader/core/downloaders/signals.py +46 -0
- novel_downloader/core/exporters/__init__.py +8 -26
- novel_downloader/core/exporters/base.py +107 -31
- novel_downloader/core/exporters/common/__init__.py +3 -4
- novel_downloader/core/exporters/common/epub.py +92 -171
- novel_downloader/core/exporters/common/main_exporter.py +14 -67
- novel_downloader/core/exporters/common/txt.py +90 -86
- novel_downloader/core/exporters/epub_util.py +184 -1327
- novel_downloader/core/exporters/linovelib/__init__.py +3 -2
- novel_downloader/core/exporters/linovelib/epub.py +165 -222
- novel_downloader/core/exporters/linovelib/main_exporter.py +10 -71
- novel_downloader/core/exporters/linovelib/txt.py +76 -66
- novel_downloader/core/exporters/qidian.py +15 -11
- novel_downloader/core/exporters/registry.py +55 -0
- novel_downloader/core/exporters/txt_util.py +67 -0
- novel_downloader/core/fetchers/__init__.py +57 -56
- novel_downloader/core/fetchers/aaatxt.py +83 -0
- novel_downloader/core/fetchers/{biquge/session.py → b520.py} +10 -10
- novel_downloader/core/fetchers/{base/session.py → base.py} +63 -47
- novel_downloader/core/fetchers/biquyuedu.py +83 -0
- novel_downloader/core/fetchers/dxmwx.py +110 -0
- novel_downloader/core/fetchers/eightnovel.py +139 -0
- novel_downloader/core/fetchers/{esjzone/session.py → esjzone.py} +23 -11
- novel_downloader/core/fetchers/guidaye.py +85 -0
- novel_downloader/core/fetchers/hetushu.py +92 -0
- novel_downloader/core/fetchers/{qianbi/browser.py → i25zw.py} +22 -26
- novel_downloader/core/fetchers/ixdzs8.py +113 -0
- novel_downloader/core/fetchers/jpxs123.py +101 -0
- novel_downloader/core/fetchers/{biquge/browser.py → lewenn.py} +15 -15
- novel_downloader/core/fetchers/{linovelib/session.py → linovelib.py} +16 -12
- novel_downloader/core/fetchers/piaotia.py +105 -0
- novel_downloader/core/fetchers/qbtr.py +101 -0
- novel_downloader/core/fetchers/{qianbi/session.py → qianbi.py} +9 -9
- novel_downloader/core/fetchers/{qidian/session.py → qidian.py} +55 -40
- novel_downloader/core/fetchers/quanben5.py +92 -0
- novel_downloader/core/fetchers/{base/rate_limiter.py → rate_limiter.py} +2 -2
- novel_downloader/core/fetchers/registry.py +60 -0
- novel_downloader/core/fetchers/{sfacg/session.py → sfacg.py} +11 -9
- novel_downloader/core/fetchers/shencou.py +106 -0
- novel_downloader/core/fetchers/{common/browser.py → shuhaige.py} +24 -19
- novel_downloader/core/fetchers/tongrenquan.py +84 -0
- novel_downloader/core/fetchers/ttkan.py +95 -0
- novel_downloader/core/fetchers/{common/session.py → wanbengo.py} +21 -17
- novel_downloader/core/fetchers/xiaoshuowu.py +106 -0
- novel_downloader/core/fetchers/xiguashuwu.py +177 -0
- novel_downloader/core/fetchers/xs63b.py +171 -0
- novel_downloader/core/fetchers/xshbook.py +85 -0
- novel_downloader/core/fetchers/{yamibo/session.py → yamibo.py} +23 -11
- novel_downloader/core/fetchers/yibige.py +114 -0
- novel_downloader/core/interfaces/__init__.py +8 -14
- novel_downloader/core/interfaces/downloader.py +6 -2
- novel_downloader/core/interfaces/exporter.py +7 -7
- novel_downloader/core/interfaces/fetcher.py +4 -17
- novel_downloader/core/interfaces/parser.py +5 -6
- novel_downloader/core/interfaces/searcher.py +26 -0
- novel_downloader/core/parsers/__init__.py +58 -22
- novel_downloader/core/parsers/aaatxt.py +132 -0
- novel_downloader/core/parsers/b520.py +116 -0
- novel_downloader/core/parsers/base.py +63 -12
- novel_downloader/core/parsers/biquyuedu.py +133 -0
- novel_downloader/core/parsers/dxmwx.py +162 -0
- novel_downloader/core/parsers/eightnovel.py +224 -0
- novel_downloader/core/parsers/{esjzone/main_parser.py → esjzone.py} +67 -67
- novel_downloader/core/parsers/guidaye.py +128 -0
- novel_downloader/core/parsers/hetushu.py +139 -0
- novel_downloader/core/parsers/i25zw.py +137 -0
- novel_downloader/core/parsers/ixdzs8.py +186 -0
- novel_downloader/core/parsers/jpxs123.py +137 -0
- novel_downloader/core/parsers/lewenn.py +142 -0
- novel_downloader/core/parsers/{linovelib/main_parser.py → linovelib.py} +54 -65
- novel_downloader/core/parsers/piaotia.py +189 -0
- novel_downloader/core/parsers/qbtr.py +136 -0
- novel_downloader/core/parsers/{qianbi/main_parser.py → qianbi.py} +54 -51
- novel_downloader/core/parsers/qidian/__init__.py +2 -2
- novel_downloader/core/parsers/qidian/book_info_parser.py +58 -59
- novel_downloader/core/parsers/qidian/chapter_encrypted.py +290 -346
- novel_downloader/core/parsers/qidian/chapter_normal.py +25 -56
- novel_downloader/core/parsers/qidian/main_parser.py +19 -57
- novel_downloader/core/parsers/qidian/utils/__init__.py +12 -11
- novel_downloader/core/parsers/qidian/utils/decryptor_fetcher.py +6 -7
- novel_downloader/core/parsers/qidian/utils/fontmap_recover.py +143 -0
- novel_downloader/core/parsers/qidian/utils/helpers.py +0 -4
- novel_downloader/core/parsers/qidian/utils/node_decryptor.py +2 -2
- novel_downloader/core/parsers/quanben5.py +103 -0
- novel_downloader/core/parsers/registry.py +57 -0
- novel_downloader/core/parsers/{sfacg/main_parser.py → sfacg.py} +46 -48
- novel_downloader/core/parsers/shencou.py +215 -0
- novel_downloader/core/parsers/shuhaige.py +111 -0
- novel_downloader/core/parsers/tongrenquan.py +116 -0
- novel_downloader/core/parsers/ttkan.py +132 -0
- novel_downloader/core/parsers/wanbengo.py +191 -0
- novel_downloader/core/parsers/xiaoshuowu.py +173 -0
- novel_downloader/core/parsers/xiguashuwu.py +435 -0
- novel_downloader/core/parsers/xs63b.py +161 -0
- novel_downloader/core/parsers/xshbook.py +134 -0
- novel_downloader/core/parsers/yamibo.py +155 -0
- novel_downloader/core/parsers/yibige.py +166 -0
- novel_downloader/core/searchers/__init__.py +51 -0
- novel_downloader/core/searchers/aaatxt.py +107 -0
- novel_downloader/core/searchers/b520.py +84 -0
- novel_downloader/core/searchers/base.py +168 -0
- novel_downloader/core/searchers/dxmwx.py +105 -0
- novel_downloader/core/searchers/eightnovel.py +84 -0
- novel_downloader/core/searchers/esjzone.py +102 -0
- novel_downloader/core/searchers/hetushu.py +92 -0
- novel_downloader/core/searchers/i25zw.py +93 -0
- novel_downloader/core/searchers/ixdzs8.py +107 -0
- novel_downloader/core/searchers/jpxs123.py +107 -0
- novel_downloader/core/searchers/piaotia.py +100 -0
- novel_downloader/core/searchers/qbtr.py +106 -0
- novel_downloader/core/searchers/qianbi.py +165 -0
- novel_downloader/core/searchers/quanben5.py +144 -0
- novel_downloader/core/searchers/registry.py +79 -0
- novel_downloader/core/searchers/shuhaige.py +124 -0
- novel_downloader/core/searchers/tongrenquan.py +110 -0
- novel_downloader/core/searchers/ttkan.py +92 -0
- novel_downloader/core/searchers/xiaoshuowu.py +122 -0
- novel_downloader/core/searchers/xiguashuwu.py +95 -0
- novel_downloader/core/searchers/xs63b.py +104 -0
- novel_downloader/locales/en.json +36 -79
- novel_downloader/locales/zh.json +37 -80
- novel_downloader/models/__init__.py +23 -50
- novel_downloader/models/book.py +44 -0
- novel_downloader/models/config.py +16 -43
- novel_downloader/models/login.py +1 -1
- novel_downloader/models/search.py +21 -0
- novel_downloader/resources/config/settings.toml +39 -74
- novel_downloader/resources/css_styles/intro.css +83 -0
- novel_downloader/resources/css_styles/main.css +30 -89
- novel_downloader/resources/json/xiguashuwu.json +718 -0
- novel_downloader/utils/__init__.py +43 -0
- novel_downloader/utils/chapter_storage.py +247 -226
- novel_downloader/utils/constants.py +5 -50
- novel_downloader/utils/cookies.py +6 -18
- novel_downloader/utils/crypto_utils/__init__.py +13 -0
- novel_downloader/utils/crypto_utils/aes_util.py +90 -0
- novel_downloader/utils/crypto_utils/aes_v1.py +619 -0
- novel_downloader/utils/crypto_utils/aes_v2.py +1143 -0
- novel_downloader/utils/{crypto_utils.py → crypto_utils/rc4.py} +3 -10
- novel_downloader/utils/epub/__init__.py +34 -0
- novel_downloader/utils/epub/builder.py +377 -0
- novel_downloader/utils/epub/constants.py +118 -0
- novel_downloader/utils/epub/documents.py +297 -0
- novel_downloader/utils/epub/models.py +120 -0
- novel_downloader/utils/epub/utils.py +179 -0
- novel_downloader/utils/file_utils/__init__.py +5 -30
- novel_downloader/utils/file_utils/io.py +9 -150
- novel_downloader/utils/file_utils/normalize.py +2 -2
- novel_downloader/utils/file_utils/sanitize.py +2 -7
- novel_downloader/utils/fontocr.py +207 -0
- novel_downloader/utils/i18n.py +2 -0
- novel_downloader/utils/logger.py +10 -16
- novel_downloader/utils/network.py +111 -252
- novel_downloader/utils/state.py +5 -90
- novel_downloader/utils/text_utils/__init__.py +16 -21
- novel_downloader/utils/text_utils/diff_display.py +6 -9
- novel_downloader/utils/text_utils/numeric_conversion.py +253 -0
- novel_downloader/utils/text_utils/text_cleaner.py +179 -0
- novel_downloader/utils/text_utils/truncate_utils.py +62 -0
- novel_downloader/utils/time_utils/__init__.py +6 -12
- novel_downloader/utils/time_utils/datetime_utils.py +23 -33
- novel_downloader/utils/time_utils/sleep_utils.py +5 -10
- novel_downloader/web/__init__.py +13 -0
- novel_downloader/web/components/__init__.py +11 -0
- novel_downloader/web/components/navigation.py +35 -0
- novel_downloader/web/main.py +66 -0
- novel_downloader/web/pages/__init__.py +17 -0
- novel_downloader/web/pages/download.py +78 -0
- novel_downloader/web/pages/progress.py +147 -0
- novel_downloader/web/pages/search.py +329 -0
- novel_downloader/web/services/__init__.py +17 -0
- novel_downloader/web/services/client_dialog.py +164 -0
- novel_downloader/web/services/cred_broker.py +113 -0
- novel_downloader/web/services/cred_models.py +35 -0
- novel_downloader/web/services/task_manager.py +264 -0
- novel_downloader-2.0.0.dist-info/METADATA +171 -0
- novel_downloader-2.0.0.dist-info/RECORD +210 -0
- {novel_downloader-1.4.5.dist-info → novel_downloader-2.0.0.dist-info}/entry_points.txt +1 -1
- novel_downloader/config/site_rules.py +0 -94
- novel_downloader/core/downloaders/biquge.py +0 -25
- novel_downloader/core/downloaders/esjzone.py +0 -25
- novel_downloader/core/downloaders/linovelib.py +0 -25
- novel_downloader/core/downloaders/sfacg.py +0 -25
- novel_downloader/core/downloaders/yamibo.py +0 -25
- novel_downloader/core/exporters/biquge.py +0 -25
- novel_downloader/core/exporters/esjzone.py +0 -25
- novel_downloader/core/exporters/qianbi.py +0 -25
- novel_downloader/core/exporters/sfacg.py +0 -25
- novel_downloader/core/exporters/yamibo.py +0 -25
- novel_downloader/core/factory/__init__.py +0 -20
- novel_downloader/core/factory/downloader.py +0 -73
- novel_downloader/core/factory/exporter.py +0 -58
- novel_downloader/core/factory/fetcher.py +0 -96
- novel_downloader/core/factory/parser.py +0 -86
- novel_downloader/core/fetchers/base/__init__.py +0 -14
- novel_downloader/core/fetchers/base/browser.py +0 -403
- novel_downloader/core/fetchers/biquge/__init__.py +0 -14
- novel_downloader/core/fetchers/common/__init__.py +0 -14
- novel_downloader/core/fetchers/esjzone/__init__.py +0 -14
- novel_downloader/core/fetchers/esjzone/browser.py +0 -204
- novel_downloader/core/fetchers/linovelib/__init__.py +0 -14
- novel_downloader/core/fetchers/linovelib/browser.py +0 -193
- novel_downloader/core/fetchers/qianbi/__init__.py +0 -14
- novel_downloader/core/fetchers/qidian/__init__.py +0 -14
- novel_downloader/core/fetchers/qidian/browser.py +0 -318
- novel_downloader/core/fetchers/sfacg/__init__.py +0 -14
- novel_downloader/core/fetchers/sfacg/browser.py +0 -189
- novel_downloader/core/fetchers/yamibo/__init__.py +0 -14
- novel_downloader/core/fetchers/yamibo/browser.py +0 -229
- novel_downloader/core/parsers/biquge/__init__.py +0 -10
- novel_downloader/core/parsers/biquge/main_parser.py +0 -134
- novel_downloader/core/parsers/common/__init__.py +0 -13
- novel_downloader/core/parsers/common/helper.py +0 -323
- novel_downloader/core/parsers/common/main_parser.py +0 -106
- novel_downloader/core/parsers/esjzone/__init__.py +0 -10
- novel_downloader/core/parsers/linovelib/__init__.py +0 -10
- novel_downloader/core/parsers/qianbi/__init__.py +0 -10
- novel_downloader/core/parsers/sfacg/__init__.py +0 -10
- novel_downloader/core/parsers/yamibo/__init__.py +0 -10
- novel_downloader/core/parsers/yamibo/main_parser.py +0 -194
- novel_downloader/models/browser.py +0 -21
- novel_downloader/models/chapter.py +0 -25
- novel_downloader/models/site_rules.py +0 -99
- novel_downloader/models/tasks.py +0 -33
- novel_downloader/models/types.py +0 -15
- novel_downloader/resources/css_styles/volume-intro.css +0 -56
- novel_downloader/resources/json/replace_word_map.json +0 -4
- novel_downloader/resources/text/blacklist.txt +0 -22
- novel_downloader/tui/__init__.py +0 -7
- novel_downloader/tui/app.py +0 -32
- novel_downloader/tui/main.py +0 -17
- novel_downloader/tui/screens/__init__.py +0 -14
- novel_downloader/tui/screens/home.py +0 -198
- novel_downloader/tui/screens/login.py +0 -74
- novel_downloader/tui/styles/home_layout.tcss +0 -79
- novel_downloader/tui/widgets/richlog_handler.py +0 -24
- novel_downloader/utils/cache.py +0 -24
- novel_downloader/utils/fontocr/__init__.py +0 -22
- novel_downloader/utils/fontocr/model_loader.py +0 -69
- novel_downloader/utils/fontocr/ocr_v1.py +0 -303
- novel_downloader/utils/fontocr/ocr_v2.py +0 -752
- novel_downloader/utils/hash_store.py +0 -279
- novel_downloader/utils/hash_utils.py +0 -103
- novel_downloader/utils/text_utils/chapter_formatting.py +0 -46
- novel_downloader/utils/text_utils/font_mapping.py +0 -28
- novel_downloader/utils/text_utils/text_cleaning.py +0 -107
- novel_downloader-1.4.5.dist-info/METADATA +0 -196
- novel_downloader-1.4.5.dist-info/RECORD +0 -165
- {novel_downloader-1.4.5.dist-info → novel_downloader-2.0.0.dist-info}/WHEEL +0 -0
- {novel_downloader-1.4.5.dist-info → novel_downloader-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {novel_downloader-1.4.5.dist-info → novel_downloader-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
novel_downloader.core.downloaders.signals
|
4
|
+
-----------------------------------------
|
5
|
+
|
6
|
+
Utilities for signaling task termination and reporting async progress.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from __future__ import annotations
|
10
|
+
|
11
|
+
from collections.abc import Awaitable, Callable
|
12
|
+
from typing import Final, final
|
13
|
+
|
14
|
+
|
15
|
+
@final
|
16
|
+
class StopToken:
|
17
|
+
"""Typed sentinel used to end queues."""
|
18
|
+
|
19
|
+
__slots__ = ()
|
20
|
+
|
21
|
+
def __repr__(self) -> str:
|
22
|
+
return "STOP"
|
23
|
+
|
24
|
+
|
25
|
+
STOP: Final[StopToken] = StopToken()
|
26
|
+
|
27
|
+
# from typing_extensions import TypeIs
|
28
|
+
# def is_stop(x: object) -> TypeIs[StopToken]:
|
29
|
+
# """Type guard so `if is_stop(item)` narrows type to StopToken."""
|
30
|
+
# return isinstance(x, StopToken)
|
31
|
+
|
32
|
+
|
33
|
+
class Progress:
|
34
|
+
"""Lightweight progress reporter."""
|
35
|
+
|
36
|
+
__slots__ = ("done", "total", "hook")
|
37
|
+
|
38
|
+
def __init__(self, total: int, hook: Callable[[int, int], Awaitable[None]] | None):
|
39
|
+
self.done = 0
|
40
|
+
self.total = total
|
41
|
+
self.hook = hook
|
42
|
+
|
43
|
+
async def bump(self, n: int = 1) -> None:
|
44
|
+
self.done += n
|
45
|
+
if self.hook:
|
46
|
+
await self.hook(self.done, self.total)
|
@@ -3,35 +3,17 @@
|
|
3
3
|
novel_downloader.core.exporters
|
4
4
|
-------------------------------
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
Currently supported platforms:
|
9
|
-
- biquge (笔趣阁)
|
10
|
-
- esjzone (ESJ Zone)
|
11
|
-
- linovelib (哔哩轻小说)
|
12
|
-
- qianbi (铅笔小说)
|
13
|
-
- qidian (起点中文网)
|
14
|
-
- sfacg (SF轻小说)
|
15
|
-
- yamibo (百合会)
|
16
|
-
- common (通用架构)
|
6
|
+
Exporter implementations for saving books in various formats across different sources
|
17
7
|
"""
|
18
8
|
|
19
|
-
from .biquge import BiqugeExporter
|
20
|
-
from .common import CommonExporter
|
21
|
-
from .esjzone import EsjzoneExporter
|
22
|
-
from .linovelib import LinovelibExporter
|
23
|
-
from .qianbi import QianbiExporter
|
24
|
-
from .qidian import QidianExporter
|
25
|
-
from .sfacg import SfacgExporter
|
26
|
-
from .yamibo import YamiboExporter
|
27
|
-
|
28
9
|
__all__ = [
|
29
|
-
"
|
30
|
-
"
|
10
|
+
"get_exporter",
|
11
|
+
"CommonExporter",
|
31
12
|
"LinovelibExporter",
|
32
|
-
"QianbiExporter",
|
33
13
|
"QidianExporter",
|
34
|
-
"SfacgExporter",
|
35
|
-
"YamiboExporter",
|
36
|
-
"CommonExporter",
|
37
14
|
]
|
15
|
+
|
16
|
+
from .common import CommonExporter
|
17
|
+
from .linovelib import LinovelibExporter
|
18
|
+
from .qidian import QidianExporter
|
19
|
+
from .registry import get_exporter
|
@@ -3,20 +3,20 @@
|
|
3
3
|
novel_downloader.core.exporters.base
|
4
4
|
------------------------------------
|
5
5
|
|
6
|
-
|
7
|
-
the common interface and reusable logic for saving assembled novel
|
8
|
-
content into various output formats.
|
6
|
+
Abstract base class providing common structure and utilities for book exporters
|
9
7
|
"""
|
10
8
|
|
11
9
|
import abc
|
10
|
+
import json
|
12
11
|
import logging
|
13
12
|
import types
|
14
13
|
from datetime import datetime
|
15
14
|
from pathlib import Path
|
16
|
-
from typing import Any, Self
|
15
|
+
from typing import Any, Self, cast
|
17
16
|
|
18
17
|
from novel_downloader.core.interfaces import ExporterProtocol
|
19
|
-
from novel_downloader.models import ExporterConfig
|
18
|
+
from novel_downloader.models import BookInfoDict, ChapterDict, ExporterConfig
|
19
|
+
from novel_downloader.utils import ChapterStorage
|
20
20
|
|
21
21
|
|
22
22
|
class SafeDict(dict[str, Any]):
|
@@ -31,6 +31,11 @@ class BaseExporter(ExporterProtocol, abc.ABC):
|
|
31
31
|
such as TXT, EPUB, Markdown, or PDF.
|
32
32
|
"""
|
33
33
|
|
34
|
+
DEFAULT_SOURCE_ID = 0
|
35
|
+
PRIORITIES_MAP = {
|
36
|
+
DEFAULT_SOURCE_ID: 0,
|
37
|
+
}
|
38
|
+
|
34
39
|
def __init__(
|
35
40
|
self,
|
36
41
|
config: ExporterConfig,
|
@@ -39,64 +44,66 @@ class BaseExporter(ExporterProtocol, abc.ABC):
|
|
39
44
|
"""
|
40
45
|
Initialize the exporter with given configuration.
|
41
46
|
|
42
|
-
:param config:
|
43
|
-
|
47
|
+
:param config: Exporter configuration settings.
|
48
|
+
:param site: Identifier for the target website or source.
|
44
49
|
"""
|
45
50
|
self._config = config
|
46
51
|
self._site = site
|
52
|
+
self._storage_cache: dict[str, ChapterStorage] = {}
|
47
53
|
|
48
|
-
self._cache_dir = Path(config.cache_dir) / site
|
49
54
|
self._raw_data_dir = Path(config.raw_data_dir) / site
|
50
55
|
self._output_dir = Path(config.output_dir)
|
51
|
-
self._cache_dir.mkdir(parents=True, exist_ok=True)
|
52
56
|
self._output_dir.mkdir(parents=True, exist_ok=True)
|
53
57
|
|
54
58
|
self.logger = logging.getLogger(f"{self.__class__.__name__}")
|
55
59
|
|
56
|
-
def export(
|
57
|
-
self,
|
58
|
-
book_id: str,
|
59
|
-
) -> None:
|
60
|
+
def export(self, book_id: str) -> dict[str, Path]:
|
60
61
|
"""
|
61
62
|
Export the book in the formats specified in config.
|
62
|
-
If a method is not implemented or fails, log the error and continue.
|
63
63
|
|
64
64
|
:param book_id: The book identifier (used for filename, lookup, etc.)
|
65
65
|
"""
|
66
66
|
TAG = "[Exporter]"
|
67
|
+
results: dict[str, Path] = {}
|
68
|
+
|
67
69
|
actions = [
|
68
|
-
("make_txt", self.export_as_txt),
|
69
|
-
("make_epub", self.export_as_epub),
|
70
|
-
("make_md", self.export_as_md),
|
71
|
-
("make_pdf", self.export_as_pdf),
|
70
|
+
("make_txt", "txt", self.export_as_txt),
|
71
|
+
("make_epub", "epub", self.export_as_epub),
|
72
|
+
("make_md", "md", self.export_as_md),
|
73
|
+
("make_pdf", "pdf", self.export_as_pdf),
|
72
74
|
]
|
73
75
|
|
74
|
-
for flag_name, export_method in actions:
|
76
|
+
for flag_name, fmt_key, export_method in actions:
|
75
77
|
if getattr(self._config, flag_name, False):
|
76
78
|
try:
|
77
79
|
self.logger.info(
|
78
80
|
"%s Attempting to export book_id '%s' as %s...",
|
79
81
|
TAG,
|
80
82
|
book_id,
|
81
|
-
|
83
|
+
fmt_key,
|
82
84
|
)
|
83
|
-
export_method(book_id)
|
84
|
-
|
85
|
+
path = export_method(book_id)
|
86
|
+
|
87
|
+
if isinstance(path, Path):
|
88
|
+
results[fmt_key] = path
|
89
|
+
self.logger.info("%s Successfully saved as %s.", TAG, fmt_key)
|
90
|
+
|
85
91
|
except NotImplementedError as e:
|
86
92
|
self.logger.warning(
|
87
93
|
"%s Export method for %s not implemented: %s",
|
88
94
|
TAG,
|
89
|
-
|
95
|
+
fmt_key,
|
90
96
|
str(e),
|
91
97
|
)
|
92
98
|
except Exception as e:
|
93
99
|
self.logger.error(
|
94
|
-
"%s Error while saving as %s: %s", TAG,
|
100
|
+
"%s Error while saving as %s: %s", TAG, fmt_key, str(e)
|
95
101
|
)
|
96
|
-
|
102
|
+
|
103
|
+
return results
|
97
104
|
|
98
105
|
@abc.abstractmethod
|
99
|
-
def export_as_txt(self, book_id: str) -> None:
|
106
|
+
def export_as_txt(self, book_id: str) -> Path | None:
|
100
107
|
"""
|
101
108
|
Persist the assembled book as a .txt file.
|
102
109
|
|
@@ -106,7 +113,7 @@ class BaseExporter(ExporterProtocol, abc.ABC):
|
|
106
113
|
"""
|
107
114
|
...
|
108
115
|
|
109
|
-
def export_as_epub(self, book_id: str) -> None:
|
116
|
+
def export_as_epub(self, book_id: str) -> Path | None:
|
110
117
|
"""
|
111
118
|
Optional: Persist the assembled book as a EPUB (.epub) file.
|
112
119
|
|
@@ -115,7 +122,7 @@ class BaseExporter(ExporterProtocol, abc.ABC):
|
|
115
122
|
"""
|
116
123
|
raise NotImplementedError("EPUB export not supported by this Exporter.")
|
117
124
|
|
118
|
-
def export_as_md(self, book_id: str) -> None:
|
125
|
+
def export_as_md(self, book_id: str) -> Path | None:
|
119
126
|
"""
|
120
127
|
Optional: Persist the assembled book as a Markdown file.
|
121
128
|
|
@@ -124,7 +131,7 @@ class BaseExporter(ExporterProtocol, abc.ABC):
|
|
124
131
|
"""
|
125
132
|
raise NotImplementedError("Markdown export not supported by this Exporter.")
|
126
133
|
|
127
|
-
def export_as_pdf(self, book_id: str) -> None:
|
134
|
+
def export_as_pdf(self, book_id: str) -> Path | None:
|
128
135
|
"""
|
129
136
|
Optional: Persist the assembled book as a PDF file.
|
130
137
|
|
@@ -160,16 +167,84 @@ class BaseExporter(ExporterProtocol, abc.ABC):
|
|
160
167
|
|
161
168
|
return f"{name}.{ext}"
|
162
169
|
|
170
|
+
@property
|
171
|
+
def site(self) -> str:
|
172
|
+
"""
|
173
|
+
Get the site identifier.
|
174
|
+
|
175
|
+
:return: The site string.
|
176
|
+
"""
|
177
|
+
return self._site
|
178
|
+
|
163
179
|
@property
|
164
180
|
def output_dir(self) -> Path:
|
165
|
-
"""
|
181
|
+
"""
|
182
|
+
Access the output directory for saving files.
|
183
|
+
"""
|
166
184
|
return self._output_dir
|
167
185
|
|
168
186
|
@property
|
169
187
|
def filename_template(self) -> str:
|
170
|
-
"""
|
188
|
+
"""
|
189
|
+
Access the filename template.
|
190
|
+
"""
|
171
191
|
return self._config.filename_template
|
172
192
|
|
193
|
+
def _get_chapter(
|
194
|
+
self,
|
195
|
+
book_id: str,
|
196
|
+
chap_id: str,
|
197
|
+
) -> ChapterDict | None:
|
198
|
+
if book_id not in self._storage_cache:
|
199
|
+
return None
|
200
|
+
return self._storage_cache[book_id].get_best_chapter(chap_id)
|
201
|
+
|
202
|
+
def _get_chapters(
|
203
|
+
self,
|
204
|
+
book_id: str,
|
205
|
+
chap_ids: list[str],
|
206
|
+
) -> dict[str, ChapterDict | None]:
|
207
|
+
if book_id not in self._storage_cache:
|
208
|
+
return {}
|
209
|
+
return self._storage_cache[book_id].get_best_chapters(chap_ids)
|
210
|
+
|
211
|
+
def _load_book_info(self, book_id: str) -> BookInfoDict | None:
|
212
|
+
info_path = self._raw_data_dir / book_id / "book_info.json"
|
213
|
+
if not info_path.is_file():
|
214
|
+
self.logger.error("Missing metadata file: %s", info_path)
|
215
|
+
return None
|
216
|
+
|
217
|
+
try:
|
218
|
+
text = info_path.read_text(encoding="utf-8")
|
219
|
+
data: Any = json.loads(text)
|
220
|
+
if not isinstance(data, dict):
|
221
|
+
self.logger.error(
|
222
|
+
"Invalid JSON structure in %s: expected an object at the top",
|
223
|
+
info_path,
|
224
|
+
)
|
225
|
+
return None
|
226
|
+
return cast(BookInfoDict, data)
|
227
|
+
except json.JSONDecodeError as e:
|
228
|
+
self.logger.error("Corrupt JSON in %s: %s", info_path, e)
|
229
|
+
return None
|
230
|
+
|
231
|
+
def _init_chapter_storages(self, book_id: str) -> None:
|
232
|
+
if book_id in self._storage_cache:
|
233
|
+
return
|
234
|
+
self._storage_cache[book_id] = ChapterStorage(
|
235
|
+
raw_base=self._raw_data_dir / book_id,
|
236
|
+
priorities=self.PRIORITIES_MAP,
|
237
|
+
)
|
238
|
+
self._storage_cache[book_id].connect()
|
239
|
+
|
240
|
+
def _close_chapter_storages(self) -> None:
|
241
|
+
for storage in self._storage_cache.values():
|
242
|
+
try:
|
243
|
+
storage.close()
|
244
|
+
except Exception as e:
|
245
|
+
self.logger.warning("Failed to close storage %s: %s", storage, e)
|
246
|
+
self._storage_cache.clear()
|
247
|
+
|
173
248
|
def _on_close(self) -> None:
|
174
249
|
"""
|
175
250
|
Hook method called at the beginning of close().
|
@@ -182,6 +257,7 @@ class BaseExporter(ExporterProtocol, abc.ABC):
|
|
182
257
|
Shutdown and clean up the exporter.
|
183
258
|
"""
|
184
259
|
self._on_close()
|
260
|
+
self._close_chapter_storages()
|
185
261
|
|
186
262
|
def __enter__(self) -> Self:
|
187
263
|
return self
|
@@ -3,10 +3,9 @@
|
|
3
3
|
novel_downloader.core.exporters.common
|
4
4
|
--------------------------------------
|
5
5
|
|
6
|
-
|
7
|
-
handling the saving process of novels.
|
6
|
+
Shared exporter implementation for producing standard TXT and EPUB outputs.
|
8
7
|
"""
|
9
8
|
|
10
|
-
from .main_exporter import CommonExporter
|
11
|
-
|
12
9
|
__all__ = ["CommonExporter"]
|
10
|
+
|
11
|
+
from .main_exporter import CommonExporter
|