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
@@ -1,21 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
novel_downloader.models.browser
|
4
|
-
-------------------------------
|
5
|
-
|
6
|
-
"""
|
7
|
-
|
8
|
-
from pathlib import Path
|
9
|
-
from typing import TypedDict
|
10
|
-
|
11
|
-
from playwright.async_api import ViewportSize
|
12
|
-
|
13
|
-
|
14
|
-
class NewContextOptions(TypedDict, total=False):
|
15
|
-
user_agent: str
|
16
|
-
locale: str
|
17
|
-
storage_state: Path
|
18
|
-
viewport: ViewportSize
|
19
|
-
java_script_enabled: bool
|
20
|
-
ignore_https_errors: bool
|
21
|
-
extra_http_headers: dict[str, str]
|
@@ -1,25 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
novel_downloader.models.chapter
|
4
|
-
-------------------------------
|
5
|
-
|
6
|
-
"""
|
7
|
-
|
8
|
-
from typing import Any, TypedDict
|
9
|
-
|
10
|
-
|
11
|
-
class ChapterDict(TypedDict, total=True):
|
12
|
-
"""
|
13
|
-
TypedDict for a novel chapter.
|
14
|
-
|
15
|
-
Fields:
|
16
|
-
id -- Unique chapter identifier
|
17
|
-
title -- Chapter title
|
18
|
-
content -- Chapter text
|
19
|
-
extra -- Arbitrary metadata (e.g. author remarks, timestamps)
|
20
|
-
"""
|
21
|
-
|
22
|
-
id: str
|
23
|
-
title: str
|
24
|
-
content: str
|
25
|
-
extra: dict[str, Any]
|
@@ -1,99 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
novel_downloader.models.site_rules
|
4
|
-
----------------------------------
|
5
|
-
|
6
|
-
"""
|
7
|
-
|
8
|
-
from typing import Any, Literal, TypedDict
|
9
|
-
|
10
|
-
|
11
|
-
class RuleStep(TypedDict, total=False):
|
12
|
-
# —— 操作类型 —— #
|
13
|
-
type: Literal[
|
14
|
-
"attr",
|
15
|
-
"select_one",
|
16
|
-
"select",
|
17
|
-
"find",
|
18
|
-
"find_all",
|
19
|
-
"exclude",
|
20
|
-
"regex",
|
21
|
-
"text",
|
22
|
-
"strip",
|
23
|
-
"replace",
|
24
|
-
"split",
|
25
|
-
"join",
|
26
|
-
]
|
27
|
-
|
28
|
-
# —— BeautifulSoup 相关 —— #
|
29
|
-
selector: str | None # CSS 选择器, 用于 select/select_one/exclude
|
30
|
-
name: str | None # 标签名称, 用于 find/find_all
|
31
|
-
attrs: dict[str, Any] | None # 属性过滤, 用于 find/find_all
|
32
|
-
limit: int | None # find_all 的最大匹配数
|
33
|
-
attr: str | None # 从元素获取属性值 (select/select_one/select_all)
|
34
|
-
|
35
|
-
# —— 正则相关 —— #
|
36
|
-
pattern: str | None # 正则表达式
|
37
|
-
flags: int | None # re.I, re.M 等
|
38
|
-
group: int | None # 匹配结果中的第几个分组 (默认 0)
|
39
|
-
template: str | None # 自定义组合, 比如 "$1$2字"
|
40
|
-
|
41
|
-
# —— 文本处理 —— #
|
42
|
-
chars: str | None # strip 要去除的字符集
|
43
|
-
old: str | None # replace 中要被替换的子串
|
44
|
-
new: str | None # replace 中新的子串
|
45
|
-
count: int | None # replace 中的最大替换次数
|
46
|
-
sep: str | None # split/join 的分隔符
|
47
|
-
index: int | None # split/select_all/select 之后取第几个元素
|
48
|
-
|
49
|
-
|
50
|
-
class FieldRules(TypedDict):
|
51
|
-
steps: list[RuleStep]
|
52
|
-
|
53
|
-
|
54
|
-
class ChapterFieldRules(TypedDict):
|
55
|
-
key: str
|
56
|
-
steps: list[RuleStep]
|
57
|
-
|
58
|
-
|
59
|
-
class VolumesRulesOptional(TypedDict, total=False):
|
60
|
-
volume_selector: str # 有卷时选择 volume 块的 selector
|
61
|
-
volume_name_steps: list[RuleStep]
|
62
|
-
volume_mode: str # Optional: "normal" (default) or "mixed"
|
63
|
-
list_selector: str # Optional: If "mixed" mode, parent container selector
|
64
|
-
|
65
|
-
|
66
|
-
class VolumesRules(VolumesRulesOptional):
|
67
|
-
has_volume: bool # 是否存在卷,false=未分卷
|
68
|
-
chapter_selector: str # 选择 chapter 节点的 selector
|
69
|
-
chapter_steps: list[ChapterFieldRules] # 提取章节信息的步骤列表
|
70
|
-
|
71
|
-
|
72
|
-
class BookInfoRules(TypedDict, total=False):
|
73
|
-
book_name: FieldRules
|
74
|
-
author: FieldRules
|
75
|
-
cover_url: FieldRules
|
76
|
-
update_time: FieldRules
|
77
|
-
serial_status: FieldRules
|
78
|
-
word_count: FieldRules
|
79
|
-
summary: FieldRules
|
80
|
-
volumes: VolumesRules
|
81
|
-
|
82
|
-
|
83
|
-
class ChapterRules(TypedDict, total=False):
|
84
|
-
title: FieldRules
|
85
|
-
content: FieldRules
|
86
|
-
|
87
|
-
|
88
|
-
class SiteProfile(TypedDict):
|
89
|
-
book_info_url: str
|
90
|
-
chapter_url: str
|
91
|
-
|
92
|
-
|
93
|
-
class SiteRules(TypedDict):
|
94
|
-
profile: SiteProfile
|
95
|
-
book_info: BookInfoRules
|
96
|
-
chapter: ChapterRules
|
97
|
-
|
98
|
-
|
99
|
-
SiteRulesDict = dict[str, SiteRules]
|
novel_downloader/models/tasks.py
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
novel_downloader.models.tasks
|
4
|
-
-----------------------------
|
5
|
-
|
6
|
-
"""
|
7
|
-
|
8
|
-
from dataclasses import dataclass
|
9
|
-
|
10
|
-
|
11
|
-
@dataclass
|
12
|
-
class CidTask:
|
13
|
-
prev_cid: str | None
|
14
|
-
cid: str
|
15
|
-
retry: int = 0
|
16
|
-
vol_idx: int = 0
|
17
|
-
chap_idx: int = 0
|
18
|
-
|
19
|
-
|
20
|
-
@dataclass
|
21
|
-
class RestoreTask:
|
22
|
-
vol_idx: int
|
23
|
-
chap_idx: int
|
24
|
-
prev_cid: str
|
25
|
-
|
26
|
-
|
27
|
-
@dataclass
|
28
|
-
class HtmlTask:
|
29
|
-
cid: str
|
30
|
-
retry: int
|
31
|
-
html_list: list[str]
|
32
|
-
vol_idx: int = 0
|
33
|
-
chap_idx: int = 0
|
novel_downloader/models/types.py
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
novel_downloader.models.types
|
4
|
-
-----------------------------
|
5
|
-
|
6
|
-
"""
|
7
|
-
|
8
|
-
from typing import Literal
|
9
|
-
|
10
|
-
ModeType = Literal["browser", "session"]
|
11
|
-
SaveMode = Literal["overwrite", "skip"]
|
12
|
-
StorageBackend = Literal["json", "sqlite"]
|
13
|
-
SplitMode = Literal["book", "volume"]
|
14
|
-
LogLevel = Literal["DEBUG", "INFO", "WARNING", "ERROR"]
|
15
|
-
BrowserType = Literal["chromium", "firefox", "webkit"]
|
@@ -1,56 +0,0 @@
|
|
1
|
-
/*标题页*/
|
2
|
-
|
3
|
-
h1.volume-title-line1 {
|
4
|
-
font-size: 1.25rem; /* 第一行较大 */
|
5
|
-
font-weight: bold;
|
6
|
-
text-align: center;
|
7
|
-
text-indent: 0em;
|
8
|
-
duokan-text-indent: 0em;
|
9
|
-
color: #6e471c;
|
10
|
-
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.8);
|
11
|
-
margin: 0;
|
12
|
-
display: block;
|
13
|
-
}
|
14
|
-
|
15
|
-
p.volume-title-line2 {
|
16
|
-
font-size: 1rem; /* 第二行稍小 */
|
17
|
-
font-weight: bold;
|
18
|
-
text-align: center;
|
19
|
-
text-indent: 0em;
|
20
|
-
duokan-text-indent: 0em;
|
21
|
-
color: #6e471c;
|
22
|
-
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.8);
|
23
|
-
margin: 0;
|
24
|
-
display: block;
|
25
|
-
}
|
26
|
-
|
27
|
-
div.border1 {
|
28
|
-
margin: 35% auto 0 auto;
|
29
|
-
text-align: center;
|
30
|
-
text-indent: 0em;
|
31
|
-
duokan-text-indent: 0em;
|
32
|
-
}
|
33
|
-
|
34
|
-
div.border2 {
|
35
|
-
margin: 0 auto 0 auto;
|
36
|
-
text-align: center;
|
37
|
-
text-indent: 0em;
|
38
|
-
duokan-text-indent: 0em;
|
39
|
-
transform: rotate(180deg);
|
40
|
-
-ms-transform: rotate(180deg);
|
41
|
-
/* IE 9 */
|
42
|
-
-moz-transform: rotate(180deg);
|
43
|
-
/* Firefox */
|
44
|
-
-webkit-transform: rotate(180deg);
|
45
|
-
/* Safari 和 Chrome */
|
46
|
-
-o-transform: rotate(180deg);
|
47
|
-
/* Opera */;
|
48
|
-
}
|
49
|
-
|
50
|
-
img.border1 {
|
51
|
-
width: 100%;
|
52
|
-
}
|
53
|
-
|
54
|
-
img.border2 {
|
55
|
-
width: 80%;
|
56
|
-
}
|
novel_downloader/tui/__init__.py
DELETED
novel_downloader/tui/app.py
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
novel_downloader.tui.app
|
4
|
-
------------------------
|
5
|
-
|
6
|
-
"""
|
7
|
-
|
8
|
-
from typing import Any
|
9
|
-
|
10
|
-
from textual.app import App, ComposeResult
|
11
|
-
from textual.containers import Container
|
12
|
-
from textual.widgets import Footer, Header
|
13
|
-
|
14
|
-
from novel_downloader.config import load_config
|
15
|
-
from novel_downloader.tui.screens import HomeScreen
|
16
|
-
|
17
|
-
|
18
|
-
class NovelDownloaderTUI(App): # type: ignore[misc]
|
19
|
-
TITLE = "Novel Downloader TUI"
|
20
|
-
SCREENS = {
|
21
|
-
"home": HomeScreen,
|
22
|
-
}
|
23
|
-
config: dict[str, Any]
|
24
|
-
|
25
|
-
def compose(self) -> ComposeResult:
|
26
|
-
yield Header()
|
27
|
-
yield Container(id="main_area")
|
28
|
-
yield Footer()
|
29
|
-
|
30
|
-
def on_mount(self) -> None:
|
31
|
-
self.config = load_config()
|
32
|
-
self.push_screen("home")
|
novel_downloader/tui/main.py
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
novel_downloader.tui.main
|
4
|
-
-------------------------
|
5
|
-
|
6
|
-
"""
|
7
|
-
|
8
|
-
from novel_downloader.tui.app import NovelDownloaderTUI
|
9
|
-
|
10
|
-
|
11
|
-
def tui_main() -> None:
|
12
|
-
app = NovelDownloaderTUI()
|
13
|
-
app.run()
|
14
|
-
|
15
|
-
|
16
|
-
if __name__ == "__main__":
|
17
|
-
tui_main()
|
@@ -1,198 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
novel_downloader.tui.screens.home
|
4
|
-
---------------------------------
|
5
|
-
|
6
|
-
"""
|
7
|
-
|
8
|
-
import asyncio
|
9
|
-
import logging
|
10
|
-
from typing import Any
|
11
|
-
|
12
|
-
from textual.app import ComposeResult
|
13
|
-
from textual.containers import Horizontal, Vertical
|
14
|
-
from textual.screen import Screen
|
15
|
-
from textual.widgets import Button, Input, ProgressBar, RichLog, Select, Static
|
16
|
-
|
17
|
-
from novel_downloader.config import ConfigAdapter
|
18
|
-
from novel_downloader.core.factory import (
|
19
|
-
get_downloader,
|
20
|
-
get_exporter,
|
21
|
-
get_fetcher,
|
22
|
-
get_parser,
|
23
|
-
)
|
24
|
-
from novel_downloader.core.interfaces import FetcherProtocol
|
25
|
-
from novel_downloader.models import LoginField
|
26
|
-
from novel_downloader.tui.widgets.richlog_handler import RichLogHandler
|
27
|
-
from novel_downloader.utils.i18n import t
|
28
|
-
|
29
|
-
|
30
|
-
class HomeScreen(Screen): # type: ignore[misc]
|
31
|
-
CSS_PATH = "../styles/home_layout.tcss"
|
32
|
-
|
33
|
-
def compose(self) -> ComposeResult:
|
34
|
-
yield Vertical(
|
35
|
-
self._make_title_bar(),
|
36
|
-
self._make_input_row(),
|
37
|
-
ProgressBar(id="prog", name="下载进度"),
|
38
|
-
Static("下载进度: 0/0 章", id="label-progress"),
|
39
|
-
RichLog(id="log", highlight=True, markup=False),
|
40
|
-
id="main-layout",
|
41
|
-
)
|
42
|
-
|
43
|
-
def on_mount(self) -> None:
|
44
|
-
log_widget = self.query_one("#log", RichLog)
|
45
|
-
|
46
|
-
self._log_handler = RichLogHandler(log_widget)
|
47
|
-
self._log_handler.setLevel(logging.INFO)
|
48
|
-
self._log_handler.setFormatter(logging.Formatter("[%(levelname)s] %(message)s"))
|
49
|
-
|
50
|
-
self._setup_logging(self._log_handler)
|
51
|
-
|
52
|
-
async def on_button_pressed(self, event: Button.Pressed) -> None:
|
53
|
-
if event.button.id == "exit":
|
54
|
-
logging.info("退出应用")
|
55
|
-
self.app.exit()
|
56
|
-
|
57
|
-
elif event.button.id == "settings":
|
58
|
-
logging.info("设置功能暂未实现")
|
59
|
-
|
60
|
-
elif event.button.id == "download":
|
61
|
-
site = self.query_one("#site", Select).value
|
62
|
-
ids = self.query_one("#book_ids", Input).value
|
63
|
-
if not site or not ids.strip():
|
64
|
-
logging.warning("请填写完整信息")
|
65
|
-
return
|
66
|
-
id_list = {x.strip() for x in ids.split(",") if x.strip()}
|
67
|
-
adapter = ConfigAdapter(config=self.app.config, site=str(site))
|
68
|
-
# asyncio.create_task(self._download(adapter, str(site), id_list))
|
69
|
-
self.run_worker(
|
70
|
-
self._download(adapter, str(site), id_list),
|
71
|
-
name="download",
|
72
|
-
group="downloads",
|
73
|
-
description="正在下载书籍...",
|
74
|
-
)
|
75
|
-
|
76
|
-
def _make_title_bar(self) -> Horizontal:
|
77
|
-
return Horizontal(
|
78
|
-
Static("小说下载器", id="title"),
|
79
|
-
Button("设置", id="settings"),
|
80
|
-
Button("关闭", id="exit"),
|
81
|
-
id="title-bar",
|
82
|
-
)
|
83
|
-
|
84
|
-
def _make_input_row(self) -> Horizontal:
|
85
|
-
return Horizontal(
|
86
|
-
Vertical(self._make_site_select(), classes="left"),
|
87
|
-
Vertical(
|
88
|
-
Input(placeholder="输入书籍ID (支持逗号分隔)", id="book_ids"),
|
89
|
-
classes="middle",
|
90
|
-
),
|
91
|
-
Vertical(Button("下载", id="download"), classes="right"),
|
92
|
-
id="input-row",
|
93
|
-
)
|
94
|
-
|
95
|
-
def _make_site_select(self) -> Select:
|
96
|
-
return Select(
|
97
|
-
options=[
|
98
|
-
("起点中文网", "qidian"),
|
99
|
-
("笔趣阁", "biquge"),
|
100
|
-
("铅笔小说", "qianbi"),
|
101
|
-
("SF轻小说", "sfacg"),
|
102
|
-
("ESJ Zone", "esjzone"),
|
103
|
-
("百合会", "yamibo"),
|
104
|
-
("哔哩轻小说", "linovelib"),
|
105
|
-
],
|
106
|
-
prompt="选择站点",
|
107
|
-
value="qidian",
|
108
|
-
id="site",
|
109
|
-
)
|
110
|
-
|
111
|
-
async def _download(
|
112
|
-
self,
|
113
|
-
adapter: ConfigAdapter,
|
114
|
-
site: str,
|
115
|
-
book_ids: set[str],
|
116
|
-
) -> None:
|
117
|
-
btn = self.query_one("#download", Button)
|
118
|
-
btn.disabled = True
|
119
|
-
try:
|
120
|
-
logging.info(f"下载请求: {site} | {book_ids}")
|
121
|
-
downloader_cfg = adapter.get_downloader_config()
|
122
|
-
fetcher_cfg = adapter.get_fetcher_config()
|
123
|
-
parser_cfg = adapter.get_parser_config()
|
124
|
-
exporter_cfg = adapter.get_exporter_config()
|
125
|
-
|
126
|
-
parser = get_parser(site, parser_cfg)
|
127
|
-
exporter = get_exporter(site, exporter_cfg)
|
128
|
-
self._setup_logging(self._log_handler)
|
129
|
-
|
130
|
-
async with get_fetcher(site, fetcher_cfg) as fetcher:
|
131
|
-
if downloader_cfg.login_required and not await fetcher.load_state():
|
132
|
-
login_data = await self._prompt_login_fields(
|
133
|
-
fetcher, fetcher.login_fields, downloader_cfg
|
134
|
-
)
|
135
|
-
if not await fetcher.login(**login_data):
|
136
|
-
logging.info(t("download_login_failed"))
|
137
|
-
return
|
138
|
-
await fetcher.save_state()
|
139
|
-
|
140
|
-
downloader = get_downloader(
|
141
|
-
fetcher=fetcher,
|
142
|
-
parser=parser,
|
143
|
-
site=site,
|
144
|
-
config=downloader_cfg,
|
145
|
-
)
|
146
|
-
|
147
|
-
for book_id in book_ids:
|
148
|
-
logging.info(t("download_downloading", book_id=book_id, site=site))
|
149
|
-
await downloader.download(
|
150
|
-
{"book_id": book_id},
|
151
|
-
progress_hook=self._update_progress,
|
152
|
-
)
|
153
|
-
await asyncio.to_thread(exporter.export, book_id)
|
154
|
-
|
155
|
-
if downloader_cfg.login_required and fetcher.is_logged_in:
|
156
|
-
await fetcher.save_state()
|
157
|
-
finally:
|
158
|
-
btn.disabled = False
|
159
|
-
|
160
|
-
async def _prompt_login_fields(
|
161
|
-
self,
|
162
|
-
fetcher: FetcherProtocol,
|
163
|
-
fields: list[LoginField],
|
164
|
-
cfg: Any = None,
|
165
|
-
) -> dict[str, Any]:
|
166
|
-
"""
|
167
|
-
Push a LoginScreen to collect all required fields,
|
168
|
-
then return the dict of values when the user submits.
|
169
|
-
"""
|
170
|
-
# cfg_dict = asdict(cfg) if cfg else {}
|
171
|
-
# login_screen = LoginScreen(fields, cfg_dict)
|
172
|
-
# await self.app.push_screen(login_screen)
|
173
|
-
# await self.app.pop_screen()
|
174
|
-
return {}
|
175
|
-
|
176
|
-
def _setup_logging(self, handler: logging.Handler) -> None:
|
177
|
-
"""
|
178
|
-
Attach the given handler to the root logger.
|
179
|
-
"""
|
180
|
-
ft_logger = logging.getLogger("fontTools.ttLib.tables._p_o_s_t")
|
181
|
-
ft_logger.setLevel(logging.ERROR)
|
182
|
-
ft_logger.propagate = False
|
183
|
-
|
184
|
-
logger = logging.getLogger()
|
185
|
-
logger.setLevel(logging.INFO)
|
186
|
-
|
187
|
-
logger.handlers = [
|
188
|
-
h for h in logger.handlers if not isinstance(h, RichLogHandler)
|
189
|
-
]
|
190
|
-
logger.addHandler(handler)
|
191
|
-
|
192
|
-
async def _update_progress(self, done: int, total: int) -> None:
|
193
|
-
prog = self.query_one("#prog", ProgressBar)
|
194
|
-
label = self.query_one("#label-progress", Static)
|
195
|
-
|
196
|
-
prog.update(total=total, progress=min(done, total))
|
197
|
-
|
198
|
-
label.update(f"下载进度: {done}/{total} 章")
|
@@ -1,74 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
novel_downloader.tui.screens.login
|
4
|
-
----------------------------------
|
5
|
-
|
6
|
-
"""
|
7
|
-
|
8
|
-
from typing import Any
|
9
|
-
|
10
|
-
from textual.app import ComposeResult
|
11
|
-
from textual.containers import Vertical
|
12
|
-
from textual.screen import Screen
|
13
|
-
from textual.widgets import Button, Input, Static
|
14
|
-
|
15
|
-
from novel_downloader.models import LoginField
|
16
|
-
|
17
|
-
|
18
|
-
class LoginScreen(Screen): # type: ignore[misc]
|
19
|
-
"""
|
20
|
-
A modal screen that gathers login fields, then fires LoginScreen.Submitted.
|
21
|
-
"""
|
22
|
-
|
23
|
-
BINDINGS = [("escape", "app.pop_screen", "取消")]
|
24
|
-
|
25
|
-
def __init__(
|
26
|
-
self,
|
27
|
-
fields: list[LoginField],
|
28
|
-
cfg: dict[str, Any] | None = None,
|
29
|
-
) -> None:
|
30
|
-
super().__init__()
|
31
|
-
self.fields = fields
|
32
|
-
self.cfg = cfg or {}
|
33
|
-
|
34
|
-
def compose(self) -> ComposeResult:
|
35
|
-
widgets = []
|
36
|
-
for field in self.fields:
|
37
|
-
# show label and optional description
|
38
|
-
widgets.append(Static(field.label))
|
39
|
-
if field.description:
|
40
|
-
widgets.append(Static(f"[i]{field.description}[/]"))
|
41
|
-
|
42
|
-
# pick input type
|
43
|
-
if field.type == "password":
|
44
|
-
inp = Input(
|
45
|
-
placeholder=field.placeholder or "",
|
46
|
-
password=True,
|
47
|
-
id=field.name,
|
48
|
-
)
|
49
|
-
else:
|
50
|
-
inp = Input(
|
51
|
-
placeholder=field.placeholder or "",
|
52
|
-
id=field.name,
|
53
|
-
)
|
54
|
-
|
55
|
-
# pre-fill from config if present
|
56
|
-
existing = self.cfg.get(field.name, "").strip()
|
57
|
-
if existing:
|
58
|
-
inp.value = existing
|
59
|
-
|
60
|
-
widgets.append(inp)
|
61
|
-
|
62
|
-
# submit button at the end
|
63
|
-
widgets.append(Button("提交", id="submit"))
|
64
|
-
yield Vertical(*widgets, id="login-form")
|
65
|
-
|
66
|
-
async def on_button_pressed(self, event: Button.Pressed) -> None:
|
67
|
-
if event.button.id == "submit":
|
68
|
-
data: dict[str, Any] = {}
|
69
|
-
for field in self.fields:
|
70
|
-
inp = self.query_one(f"#{field.name}", Input)
|
71
|
-
value = inp.value
|
72
|
-
if not value and self.cfg.get(field.name):
|
73
|
-
value = self.cfg[field.name]
|
74
|
-
data[field.name] = value
|