novel-downloader 1.4.1__py3-none-any.whl → 1.4.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 +69 -10
- novel_downloader/config/adapter.py +42 -9
- novel_downloader/core/downloaders/base.py +26 -22
- novel_downloader/core/downloaders/common.py +41 -5
- novel_downloader/core/downloaders/qidian.py +60 -32
- novel_downloader/core/exporters/common/epub.py +153 -68
- novel_downloader/core/exporters/epub_util.py +1358 -0
- novel_downloader/core/exporters/linovelib/epub.py +147 -190
- novel_downloader/core/fetchers/qidian/browser.py +62 -10
- novel_downloader/core/interfaces/downloader.py +13 -12
- novel_downloader/locales/en.json +2 -0
- novel_downloader/locales/zh.json +2 -0
- novel_downloader/models/__init__.py +2 -0
- novel_downloader/models/config.py +8 -0
- novel_downloader/tui/screens/home.py +5 -4
- novel_downloader/utils/constants.py +0 -29
- {novel_downloader-1.4.1.dist-info → novel_downloader-1.4.2.dist-info}/METADATA +4 -2
- {novel_downloader-1.4.1.dist-info → novel_downloader-1.4.2.dist-info}/RECORD +23 -28
- novel_downloader/core/exporters/epub_utils/__init__.py +0 -40
- novel_downloader/core/exporters/epub_utils/css_builder.py +0 -75
- novel_downloader/core/exporters/epub_utils/image_loader.py +0 -131
- novel_downloader/core/exporters/epub_utils/initializer.py +0 -100
- novel_downloader/core/exporters/epub_utils/text_to_html.py +0 -178
- novel_downloader/core/exporters/epub_utils/volume_intro.py +0 -60
- {novel_downloader-1.4.1.dist-info → novel_downloader-1.4.2.dist-info}/WHEEL +0 -0
- {novel_downloader-1.4.1.dist-info → novel_downloader-1.4.2.dist-info}/entry_points.txt +0 -0
- {novel_downloader-1.4.1.dist-info → novel_downloader-1.4.2.dist-info}/licenses/LICENSE +0 -0
- {novel_downloader-1.4.1.dist-info → novel_downloader-1.4.2.dist-info}/top_level.txt +0 -0
@@ -17,6 +17,7 @@ strongly typed Python objects for safer and cleaner access.
|
|
17
17
|
"""
|
18
18
|
|
19
19
|
from dataclasses import dataclass
|
20
|
+
from typing import NotRequired, TypedDict
|
20
21
|
|
21
22
|
from .types import (
|
22
23
|
BrowserType,
|
@@ -98,3 +99,10 @@ class ExporterConfig:
|
|
98
99
|
include_toc: bool = False
|
99
100
|
include_picture: bool = False
|
100
101
|
split_mode: SplitMode = "book"
|
102
|
+
|
103
|
+
|
104
|
+
class BookConfig(TypedDict):
|
105
|
+
book_id: str
|
106
|
+
start_id: NotRequired[str]
|
107
|
+
end_id: NotRequired[str]
|
108
|
+
ignore_ids: NotRequired[list[str]]
|
@@ -106,12 +106,12 @@ class HomeScreen(Screen): # type: ignore[misc]
|
|
106
106
|
self,
|
107
107
|
adapter: ConfigAdapter,
|
108
108
|
site: str,
|
109
|
-
|
109
|
+
book_ids: set[str],
|
110
110
|
) -> None:
|
111
111
|
btn = self.query_one("#download", Button)
|
112
112
|
btn.disabled = True
|
113
113
|
try:
|
114
|
-
logging.info(f"下载请求: {site} | {
|
114
|
+
logging.info(f"下载请求: {site} | {book_ids}")
|
115
115
|
downloader_cfg = adapter.get_downloader_config()
|
116
116
|
fetcher_cfg = adapter.get_fetcher_config()
|
117
117
|
parser_cfg = adapter.get_parser_config()
|
@@ -139,10 +139,11 @@ class HomeScreen(Screen): # type: ignore[misc]
|
|
139
139
|
config=downloader_cfg,
|
140
140
|
)
|
141
141
|
|
142
|
-
for book_id in
|
142
|
+
for book_id in book_ids:
|
143
143
|
logging.info(t("download_downloading", book_id=book_id, site=site))
|
144
144
|
await downloader.download(
|
145
|
-
book_id,
|
145
|
+
{"book_id": book_id},
|
146
|
+
progress_hook=self._update_progress,
|
146
147
|
)
|
147
148
|
|
148
149
|
if downloader_cfg.login_required and fetcher.is_logged_in:
|
@@ -116,35 +116,6 @@ QD_DECRYPT_SCRIPT_PATH = files("novel_downloader.resources.js_scripts").joinpath
|
|
116
116
|
# Text Files
|
117
117
|
BLACKLIST_PATH = files("novel_downloader.resources.text").joinpath("blacklist.txt")
|
118
118
|
|
119
|
-
# -----------------------------------------------------------------------------
|
120
|
-
# EPUB defaults
|
121
|
-
# -----------------------------------------------------------------------------
|
122
|
-
EPUB_IMAGE_FOLDER = "Images"
|
123
|
-
EPUB_TEXT_FOLDER = "Text"
|
124
|
-
|
125
|
-
EPUB_IMAGE_WRAPPER = (
|
126
|
-
'<div class="duokan-image-single illus"><img src="../Images/{filename}" /></div>'
|
127
|
-
)
|
128
|
-
|
129
|
-
EPUB_OPTIONS = {
|
130
|
-
# guide 是 EPUB 2 的一个部分, 包含封面, 目录, 索引等重要导航信息
|
131
|
-
"epub2_guide": True,
|
132
|
-
# landmark 是 EPUB 3 用来标识重要页面 (如目录, 封面, 起始页) 的 <nav> 结构
|
133
|
-
"epub3_landmark": True,
|
134
|
-
# EPUB 3 允许提供一个 page list, 让电子书在不同设备上仍然保持相对一致的分页结构
|
135
|
-
"epub3_pages": True,
|
136
|
-
# 这个名字会出现在 EPUB 阅读器的导航栏
|
137
|
-
"landmark_title": "Guide",
|
138
|
-
# 这个名字会显示在 EPUB 阅读器的分页导航栏
|
139
|
-
"pages_title": "Pages",
|
140
|
-
# 是否根据 book.spine 的排列顺序自动设置 EPUB 阅读器的 page-progression-direction
|
141
|
-
"spine_direction": True,
|
142
|
-
# 控制 EPUB 阅读器的默认翻页方向 (LTR 或 RTL)
|
143
|
-
"package_direction": False,
|
144
|
-
# 是否为 EPUB 书籍中的章节 添加播放顺序
|
145
|
-
"play_order": {"enabled": True, "start_from": 1},
|
146
|
-
}
|
147
|
-
|
148
119
|
# ---------------------------------------------------------------------
|
149
120
|
# Pretrained model registry (e.g. used in font recovery or OCR)
|
150
121
|
# ---------------------------------------------------------------------
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: novel-downloader
|
3
|
-
Version: 1.4.
|
3
|
+
Version: 1.4.2
|
4
4
|
Summary: A command-line tool for downloading Chinese web novels from Qidian and similar platforms.
|
5
5
|
Author-email: Saudade Z <saudadez217@gmail.com>
|
6
6
|
License: MIT License
|
@@ -46,7 +46,6 @@ Requires-Dist: aiohttp
|
|
46
46
|
Requires-Dist: playwright
|
47
47
|
Requires-Dist: lxml
|
48
48
|
Requires-Dist: platformdirs
|
49
|
-
Requires-Dist: ebooklib
|
50
49
|
Provides-Extra: dev
|
51
50
|
Requires-Dist: black; extra == "dev"
|
52
51
|
Requires-Dist: mypy; extra == "dev"
|
@@ -55,6 +54,7 @@ Requires-Dist: pytest; extra == "dev"
|
|
55
54
|
Requires-Dist: pytest-cov; extra == "dev"
|
56
55
|
Requires-Dist: pytest-mock; extra == "dev"
|
57
56
|
Requires-Dist: types-requests; extra == "dev"
|
57
|
+
Requires-Dist: types-lxml; extra == "dev"
|
58
58
|
Requires-Dist: types-PyYAML; extra == "dev"
|
59
59
|
Requires-Dist: pre-commit; extra == "dev"
|
60
60
|
Requires-Dist: commitizen; extra == "dev"
|
@@ -109,6 +109,8 @@ playwright install
|
|
109
109
|
pip install novel-downloader[font-recovery]
|
110
110
|
```
|
111
111
|
|
112
|
+
- 详细可见: [安装](https://github.com/BowenZ217/novel-downloader/blob/main/docs/1-installation.md)
|
113
|
+
|
112
114
|
---
|
113
115
|
|
114
116
|
### CLI 模式
|
@@ -1,45 +1,40 @@
|
|
1
|
-
novel_downloader/__init__.py,sha256=
|
1
|
+
novel_downloader/__init__.py,sha256=qYZxJQ-xYLPTjMwE7FYcQM5HYV9Oo_bqCJY55hAqlcE,218
|
2
2
|
novel_downloader/cli/__init__.py,sha256=-2HAut_U1e67MZGdvbpEJ1n5J-bRchzto6L4c-nWeXY,174
|
3
3
|
novel_downloader/cli/clean.py,sha256=hOk8SJQwBCw2oOObTdEI79wpnmZ25uB1s9LQK1-4LNU,4487
|
4
4
|
novel_downloader/cli/config.py,sha256=C6QLfegZLp4legmu8KenqyYKNdrk47bH0z86ujLP0pY,6509
|
5
|
-
novel_downloader/cli/download.py,sha256=
|
5
|
+
novel_downloader/cli/download.py,sha256=ff2KMsa9XnbsF_aUQcAc7a06hDYmk4-7yMv4iiXTWlI,6762
|
6
6
|
novel_downloader/cli/export.py,sha256=x9uvyLuvkuaDZGoH212aHZ7XyPT9b2S78AmTN6rkAu4,2283
|
7
7
|
novel_downloader/cli/main.py,sha256=9J8KMuYwL01X6chIaXpQNeS5d3pHnwB9vA9XjKd8RrM,919
|
8
8
|
novel_downloader/config/__init__.py,sha256=2mnf33MQOUnLGCnL1NtNV_rHBejNxBNIbobIGN0tw4E,666
|
9
|
-
novel_downloader/config/adapter.py,sha256=
|
9
|
+
novel_downloader/config/adapter.py,sha256=hPRAomAlqhyUlNemm82e4m0SAYa33woulb5ePHMRC3o,8330
|
10
10
|
novel_downloader/config/loader.py,sha256=jo_1rr3UKZRAFFYgO-oHpYLRhF431chmfx4fLGh0MKw,5743
|
11
11
|
novel_downloader/config/site_rules.py,sha256=CJksBSvVAC4sR6cEruf4pM1Jv0zTJb1lcHq0Yn6LPFM,2979
|
12
12
|
novel_downloader/core/__init__.py,sha256=zzrXjQfBUhfLmBD_95oHTjtTsR81NSRtHcxpYBxQlZ4,654
|
13
13
|
novel_downloader/core/downloaders/__init__.py,sha256=AK5zeetVXOn_irgHp-NORPYW65UJM4CsF8ysxf2vKD4,1064
|
14
|
-
novel_downloader/core/downloaders/base.py,sha256=
|
14
|
+
novel_downloader/core/downloaders/base.py,sha256=h2Y73o9OC8URvlnNNQgwNVx4kk4oqFjk2AsyfmAfshg,6706
|
15
15
|
novel_downloader/core/downloaders/biquge.py,sha256=PTm8eeaHVyw_nvtnuRpZ2hT7hLs2LEPV1Drd2O6idNM,651
|
16
|
-
novel_downloader/core/downloaders/common.py,sha256
|
16
|
+
novel_downloader/core/downloaders/common.py,sha256=-3lp6lLoCubVwqMA4lx6YtnSmKPMA85T1K5m0_vizmg,16323
|
17
17
|
novel_downloader/core/downloaders/esjzone.py,sha256=GBFJ3fIPov483QxkOTaKBU2wd2f8T4GdvzSbRaU9hDI,655
|
18
18
|
novel_downloader/core/downloaders/linovelib.py,sha256=Cd43_70SKbKM8_C8c4qev3pLUgyziW9413qrA8EcStY,663
|
19
19
|
novel_downloader/core/downloaders/qianbi.py,sha256=ARuk1xABOGCjlD_ct5hOKMgWloU187rUJBbICJ2yvkY,651
|
20
|
-
novel_downloader/core/downloaders/qidian.py,sha256=
|
20
|
+
novel_downloader/core/downloaders/qidian.py,sha256=PkUnOEq1TyKZea--b8nBgFqrhxD0UK8X4m9AUC_wFjk,13135
|
21
21
|
novel_downloader/core/downloaders/sfacg.py,sha256=ShlkIIR6OW_x2FhNDi79S_3AL2CWId2upQmRkApee_E,647
|
22
22
|
novel_downloader/core/downloaders/yamibo.py,sha256=9L0m5Zq0o1y44tCQ4ZRHHqdO3YresJkRAVvCtsQwouk,651
|
23
23
|
novel_downloader/core/exporters/__init__.py,sha256=ATkkdh6RUIaM19mG1XjFiaMnGRgFFGT3ixsqVkU13Q0,867
|
24
24
|
novel_downloader/core/exporters/base.py,sha256=duIdLnj9kKNZ9r2aJV0Rzl-rnQCP8olk12uzduT9PtE,6146
|
25
25
|
novel_downloader/core/exporters/biquge.py,sha256=SJCChYtDLJKrOpwDCT8IeR0v1LoiUQuTTejfGmRnxX8,462
|
26
|
+
novel_downloader/core/exporters/epub_util.py,sha256=uSMB_55v6xKJcKyFjJZghl0ZgCFsdGbJJHH22G-BkeQ,37377
|
26
27
|
novel_downloader/core/exporters/esjzone.py,sha256=UP9gtTOv6komWjgvwXkttqDYJJPjjbJdiG3yz9RxHSQ,467
|
27
28
|
novel_downloader/core/exporters/qianbi.py,sha256=x841MNIxpS5eY8u91ODC0cpIhpT-ENnFqs_Yx6HwtdM,462
|
28
29
|
novel_downloader/core/exporters/qidian.py,sha256=ecaZcgqXyLvJtT2J5QXw4qS9gY3d72mgUnV-0frJvhQ,724
|
29
30
|
novel_downloader/core/exporters/sfacg.py,sha256=j4_H-ixvDxd_OJfIvD4Lbs24LaiFjaYqhvhZcks-ItI,457
|
30
31
|
novel_downloader/core/exporters/yamibo.py,sha256=QaY13llqpyxIgmMnMVFtR5yFMIpyMHHdCmVDW4Dq-bo,462
|
31
32
|
novel_downloader/core/exporters/common/__init__.py,sha256=qIOZ_TgnnQZ6p45YQsxpTcB69e0Jq9XMG3IY0NBVsTM,274
|
32
|
-
novel_downloader/core/exporters/common/epub.py,sha256=
|
33
|
+
novel_downloader/core/exporters/common/epub.py,sha256=_hFoc3aVRf7xxrWCvukN7Iv5ulYbkSq53nQMfaZb75E,8833
|
33
34
|
novel_downloader/core/exporters/common/main_exporter.py,sha256=-MRZEpAdsfdRt3VxIv4krMaw2UJnrvCnyj5Jde93zuA,3926
|
34
35
|
novel_downloader/core/exporters/common/txt.py,sha256=b5cNvH0hrjaBO89Uddn1IUY1BFhkHJdTd7WjSWk4ndk,4719
|
35
|
-
novel_downloader/core/exporters/epub_utils/__init__.py,sha256=cPtuQDtuJZPgOHRf_T5sgkQE13jZnOyPoK9hHJeMEEo,993
|
36
|
-
novel_downloader/core/exporters/epub_utils/css_builder.py,sha256=qvVgHnyoILgqSUoVsHv-cHzIdtfsqxA8WHt9_89puic,2145
|
37
|
-
novel_downloader/core/exporters/epub_utils/image_loader.py,sha256=hbxoPvXkBIZnoZSIlHwIUx4zWR8QVppWiH1KotMrBXg,3953
|
38
|
-
novel_downloader/core/exporters/epub_utils/initializer.py,sha256=TGueFff1ydRLdJYBEJnqIkoVCV57ZiR3dGu_c5Hrp8g,3364
|
39
|
-
novel_downloader/core/exporters/epub_utils/text_to_html.py,sha256=VSYbm33PrwxFmcT-2f_U2j3kuHzdcSMR7PRqOWvZICU,5617
|
40
|
-
novel_downloader/core/exporters/epub_utils/volume_intro.py,sha256=cwHMVwJeK8TzXnOw4MYWXvbZGt4iWFjdesDm4n_V-Tk,1830
|
41
36
|
novel_downloader/core/exporters/linovelib/__init__.py,sha256=zD7A7OhluipwICssnp27c_oenYozevZH9g0Qj5WOMWY,195
|
42
|
-
novel_downloader/core/exporters/linovelib/epub.py,sha256=
|
37
|
+
novel_downloader/core/exporters/linovelib/epub.py,sha256=yVqv-lnc7Nh-COd4POvQ0vbsAw18NJ8oFO88-zKedb4,13048
|
43
38
|
novel_downloader/core/exporters/linovelib/main_exporter.py,sha256=cHkAp_jdF6uji5Avu1l8z6mR0nSrWYgX6ETDEezkjoE,3700
|
44
39
|
novel_downloader/core/exporters/linovelib/txt.py,sha256=ALlZUl5nNtg4OmYlurMC0acjmTOBV7G8c13DPrxbG4w,4407
|
45
40
|
novel_downloader/core/factory/__init__.py,sha256=_IY3N35onhWD_nw_TyxKOxa6e7Uak9Cv0bp4pK9yb0M,464
|
@@ -68,7 +63,7 @@ novel_downloader/core/fetchers/qianbi/__init__.py,sha256=h4Rve7fO1GcSJ-DlNC5zw7f
|
|
68
63
|
novel_downloader/core/fetchers/qianbi/browser.py,sha256=1EmrSwpqSYhEO_ID3RJbUaAOhcqvVnMnch6iOafXbTA,3162
|
69
64
|
novel_downloader/core/fetchers/qianbi/session.py,sha256=c3pJcgi9C1x9QYTBihvazHcgT7XTp2HBYfStTn6gSEg,3141
|
70
65
|
novel_downloader/core/fetchers/qidian/__init__.py,sha256=2LshlX82lFpWZMV6yujHsfue9KM0-F1O3HvMCopIv9M,236
|
71
|
-
novel_downloader/core/fetchers/qidian/browser.py,sha256=
|
66
|
+
novel_downloader/core/fetchers/qidian/browser.py,sha256=cUgJMrFe_MITHh4MGrDI2-a-MCqXSsoeJsbDus0l2LY,10448
|
72
67
|
novel_downloader/core/fetchers/qidian/session.py,sha256=WrHsov15PoRDYbb1ZRaScJQGRZv6axz9_hVEC1Wt1PM,9746
|
73
68
|
novel_downloader/core/fetchers/sfacg/__init__.py,sha256=bQAIwERsX9XOKrP2LteFKX8Jlhw4oeUNwpZTHXn5RRg,230
|
74
69
|
novel_downloader/core/fetchers/sfacg/browser.py,sha256=15PVS75PxEKR5W7mQbqVxoN0d4V1XVYVF0l1yy_sv_Y,5681
|
@@ -77,7 +72,7 @@ novel_downloader/core/fetchers/yamibo/__init__.py,sha256=5ds6DNNvpo6F6U5dboEaIsJ
|
|
77
72
|
novel_downloader/core/fetchers/yamibo/browser.py,sha256=7YRrWbA8_cOcT_z-VjMWP6FUg30TwV6eLW4zJZ_UxSE,7249
|
78
73
|
novel_downloader/core/fetchers/yamibo/session.py,sha256=434EArdKgEYIBZkb1nMub3PQXdRTS-Ov2_1u9MESjOs,7212
|
79
74
|
novel_downloader/core/interfaces/__init__.py,sha256=hB1SjBzuN7qnZx_h3RV4w_roj3ZwShbIG3CV9jGMB14,602
|
80
|
-
novel_downloader/core/interfaces/downloader.py,sha256=
|
75
|
+
novel_downloader/core/interfaces/downloader.py,sha256=H0S4o5MW22JcvNVECv1bCdwAN0drBwHY31IbvVHn-X0,1557
|
81
76
|
novel_downloader/core/interfaces/exporter.py,sha256=zwIaJ5FXo_JmKYg2UZV9FTWX7xZ2e0QuL4RTNGRtI04,1610
|
82
77
|
novel_downloader/core/interfaces/fetcher.py,sha256=XlP3d-Q_xvuVextZCdLOxDf8BwBbx799L_WLJx0WTB8,4096
|
83
78
|
novel_downloader/core/interfaces/parser.py,sha256=iZeUgNl-zE-wsf1eW4GVCPjEaLSvQrNJi_mVEOU9ulY,1353
|
@@ -108,12 +103,12 @@ novel_downloader/core/parsers/sfacg/__init__.py,sha256=O2nscvtOweMXHMONdvySTsLSy
|
|
108
103
|
novel_downloader/core/parsers/sfacg/main_parser.py,sha256=yW18MALAZisJdFO-7peI1h-4XVloEDXeCJvXUz5hJ1A,5899
|
109
104
|
novel_downloader/core/parsers/yamibo/__init__.py,sha256=KJ_fCcakoQrsG36OOd0paXXB1cAIwMirKaA8ZLUKUKI,173
|
110
105
|
novel_downloader/core/parsers/yamibo/main_parser.py,sha256=9sNXsDR2iDVyTYupP_7PyE7BXf2po3sZLq1XEFA8DrI,6554
|
111
|
-
novel_downloader/locales/en.json,sha256
|
112
|
-
novel_downloader/locales/zh.json,sha256=
|
113
|
-
novel_downloader/models/__init__.py,sha256=
|
106
|
+
novel_downloader/locales/en.json,sha256=mcGv24zHS2FFimwCYRLMD8CYzAzngTv1tODZP64GWvY,7002
|
107
|
+
novel_downloader/locales/zh.json,sha256=7kkKGt1fudSC9LvmFyAgXfyCC512XfCoyf5by8Egqnc,6849
|
108
|
+
novel_downloader/models/__init__.py,sha256=5aQ24IeU8OJDhBZdZ8Ov-xWoLlZm6Qg24r5FPyLTa6Q,1102
|
114
109
|
novel_downloader/models/browser.py,sha256=ly-jM7izQ77yTIG-oau51HJofDpBfrXpIJZJjoQyad8,435
|
115
110
|
novel_downloader/models/chapter.py,sha256=bdAQUDZIuuTVxoYjoOJrbS2u81b1B2mkuZkTSf0m2HQ,492
|
116
|
-
novel_downloader/models/config.py,sha256=
|
111
|
+
novel_downloader/models/config.py,sha256=fYExgkyylDuCUUW8nTFQLBX2rotiumvKV6uMZF1G5Ig,2981
|
117
112
|
novel_downloader/models/login.py,sha256=sY2Jom6PLpA9Z3Uy7plZKhda3Gq7awKOOIIaQ79PpWs,371
|
118
113
|
novel_downloader/models/site_rules.py,sha256=kzDB5F8lf4udAO0WVUrgBOR7ave3jsMBxt7cEoG0bnI,2721
|
119
114
|
novel_downloader/models/tasks.py,sha256=e4DYEXQQQewgQyCCHfc0UYnkPJ96LafmhG3oO5MQP0Q,465
|
@@ -130,14 +125,14 @@ novel_downloader/tui/__init__.py,sha256=8RB8tBrPcoBzm1tpQlgZqnOZXrdHmlzMRxuk9wsN
|
|
130
125
|
novel_downloader/tui/app.py,sha256=ytV1u15nGCRj_ff_GeAL3W1XlU6r5Lh_k3HBcAjPRx0,731
|
131
126
|
novel_downloader/tui/main.py,sha256=MBP8SrwEYTpGQm-V9W_4rKnTeslneORfkzFsU3Xj2yA,256
|
132
127
|
novel_downloader/tui/screens/__init__.py,sha256=QsUM5cUEKm7nluQh9acEt37xRWbkZU3vqIpBduepDCU,203
|
133
|
-
novel_downloader/tui/screens/home.py,sha256=
|
128
|
+
novel_downloader/tui/screens/home.py,sha256=izxzn9Aru_fzenEKI9sOY1FNhyWky9zgzv7GVUMoW_8,6736
|
134
129
|
novel_downloader/tui/screens/login.py,sha256=eEVmQFZKQX8mCt_qp96QK8t2pB15FeUrk_oV3jm_Mz4,2224
|
135
130
|
novel_downloader/tui/styles/home_layout.tcss,sha256=VNJs339qiwNUuqwwdK6VkYThCdsqlw-2mEoZVCspdrw,974
|
136
131
|
novel_downloader/tui/widgets/richlog_handler.py,sha256=bhFb0E7Z7-dRS07y_vMjxjPeCic9AD59MFPLhef_R5g,572
|
137
132
|
novel_downloader/utils/__init__.py,sha256=4iUXNUzxeAnGmpGWsB4K_jckUYEW0u_LqWp_OM7mtK8,78
|
138
133
|
novel_downloader/utils/cache.py,sha256=_jVEWAaMGY86kWVnAuuIvJA_hA8dH2ClI7o3p99mEvE,618
|
139
134
|
novel_downloader/utils/chapter_storage.py,sha256=wXXIzNjs1GfPPoqgYYPnQ9Two23McW6ZZNDmhfSC9aY,9831
|
140
|
-
novel_downloader/utils/constants.py,sha256=
|
135
|
+
novel_downloader/utils/constants.py,sha256=EaxQllQ_dIce5_eRTgAJ-OXRtEk3cMhLUbOKA359198,4435
|
141
136
|
novel_downloader/utils/cookies.py,sha256=WuPBt_z7cF4n7QPIw1julATWt6dbDRCbNdgXHSikmjo,2042
|
142
137
|
novel_downloader/utils/crypto_utils.py,sha256=BRlVR9Nvbu8TQXocJb3po1-kD5HkbCV0yyT84nq_XtI,2052
|
143
138
|
novel_downloader/utils/hash_store.py,sha256=HfzthzcKbHbVaHNpqjaAs2wDeq7iIeY8Mzkt_4v80jQ,9388
|
@@ -162,9 +157,9 @@ novel_downloader/utils/text_utils/text_cleaning.py,sha256=_Nahr8iv3341IyDXW-KpTn
|
|
162
157
|
novel_downloader/utils/time_utils/__init__.py,sha256=725vY2PvqFhjbAz0hCOuIuhSCK8HrEqQ_k3YwvubmXo,624
|
163
158
|
novel_downloader/utils/time_utils/datetime_utils.py,sha256=1eyX8lTEqkQ-rEej_GrhbUpaIR5tsFdVSKT-q8s9X-g,4927
|
164
159
|
novel_downloader/utils/time_utils/sleep_utils.py,sha256=C4XYeAtxoVZC9Ju6vhhP9sbOrSpdZG2Nm-x1IYO_OFA,3233
|
165
|
-
novel_downloader-1.4.
|
166
|
-
novel_downloader-1.4.
|
167
|
-
novel_downloader-1.4.
|
168
|
-
novel_downloader-1.4.
|
169
|
-
novel_downloader-1.4.
|
170
|
-
novel_downloader-1.4.
|
160
|
+
novel_downloader-1.4.2.dist-info/licenses/LICENSE,sha256=XgmnH0mBf-qEiizoVAfJQAKzPB9y3rBa-ni7M0Aqv4A,1066
|
161
|
+
novel_downloader-1.4.2.dist-info/METADATA,sha256=ogSqUunbmTtsABDdShe_XYxGYLPqyvGnmwKyuAJ82bI,7151
|
162
|
+
novel_downloader-1.4.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
163
|
+
novel_downloader-1.4.2.dist-info/entry_points.txt,sha256=u1Ns5xI_QJyL4HAFCgJvJdib9ugu7M9I2tnQwZjJxrk,112
|
164
|
+
novel_downloader-1.4.2.dist-info/top_level.txt,sha256=hP4jYWM2LTm1jxsW4hqEB8N0dsRvldO2QdhggJT917I,17
|
165
|
+
novel_downloader-1.4.2.dist-info/RECORD,,
|
@@ -1,40 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
novel_downloader.core.exporters.epub_utils
|
4
|
-
------------------------------------------
|
5
|
-
|
6
|
-
This package provides utility functions for constructing EPUB files,
|
7
|
-
including:
|
8
|
-
|
9
|
-
- CSS inclusion (css_builder)
|
10
|
-
- Image embedding (image_loader)
|
11
|
-
- EPUB book initialization (initializer)
|
12
|
-
- Chapter text-to-HTML conversion (text_to_html)
|
13
|
-
- Volume intro HTML generation (volume_intro)
|
14
|
-
"""
|
15
|
-
|
16
|
-
from .css_builder import create_css_items
|
17
|
-
from .image_loader import (
|
18
|
-
add_images_from_dir,
|
19
|
-
add_images_from_dirs,
|
20
|
-
add_images_from_list,
|
21
|
-
)
|
22
|
-
from .initializer import init_epub
|
23
|
-
from .text_to_html import (
|
24
|
-
chapter_txt_to_html,
|
25
|
-
generate_book_intro_html,
|
26
|
-
inline_remote_images,
|
27
|
-
)
|
28
|
-
from .volume_intro import create_volume_intro
|
29
|
-
|
30
|
-
__all__ = [
|
31
|
-
"create_css_items",
|
32
|
-
"add_images_from_dir",
|
33
|
-
"add_images_from_dirs",
|
34
|
-
"add_images_from_list",
|
35
|
-
"init_epub",
|
36
|
-
"chapter_txt_to_html",
|
37
|
-
"create_volume_intro",
|
38
|
-
"generate_book_intro_html",
|
39
|
-
"inline_remote_images",
|
40
|
-
]
|
@@ -1,75 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
novel_downloader.core.exporters.epub_utils.css_builder
|
4
|
-
------------------------------------------------------
|
5
|
-
|
6
|
-
Reads local CSS files and wraps them into epub.EpubItem objects,
|
7
|
-
returning a list ready to be added to the EPUB.
|
8
|
-
"""
|
9
|
-
|
10
|
-
import logging
|
11
|
-
from importlib.abc import Traversable
|
12
|
-
from typing import TypedDict
|
13
|
-
|
14
|
-
from ebooklib import epub
|
15
|
-
|
16
|
-
from novel_downloader.utils.constants import (
|
17
|
-
CSS_MAIN_PATH,
|
18
|
-
CSS_VOLUME_INTRO_PATH,
|
19
|
-
)
|
20
|
-
|
21
|
-
logger = logging.getLogger(__name__)
|
22
|
-
|
23
|
-
|
24
|
-
class CssConfig(TypedDict):
|
25
|
-
include: bool
|
26
|
-
path: Traversable
|
27
|
-
uid: str
|
28
|
-
file_name: str
|
29
|
-
|
30
|
-
|
31
|
-
def create_css_items(
|
32
|
-
include_main: bool = True,
|
33
|
-
include_volume: bool = True,
|
34
|
-
) -> list[epub.EpubItem]:
|
35
|
-
"""
|
36
|
-
:param include_main: Whether to load the main stylesheet.
|
37
|
-
:param include_volume: Whether to load the “volume intro” stylesheet.
|
38
|
-
:returns: A list of epub.EpubItem ready to add to the book.
|
39
|
-
"""
|
40
|
-
css_config: list[CssConfig] = [
|
41
|
-
{
|
42
|
-
"include": include_main,
|
43
|
-
"path": CSS_MAIN_PATH,
|
44
|
-
"uid": "style",
|
45
|
-
"file_name": "Styles/main.css",
|
46
|
-
},
|
47
|
-
{
|
48
|
-
"include": include_volume,
|
49
|
-
"path": CSS_VOLUME_INTRO_PATH,
|
50
|
-
"uid": "volume_style",
|
51
|
-
"file_name": "Styles/volume-intro.css",
|
52
|
-
},
|
53
|
-
]
|
54
|
-
css_items: list[epub.EpubItem] = []
|
55
|
-
|
56
|
-
for css in css_config:
|
57
|
-
if css["include"]:
|
58
|
-
path = css["path"]
|
59
|
-
try:
|
60
|
-
content: str = path.read_text(encoding="utf-8")
|
61
|
-
content_bytes: bytes = content.encode("utf-8")
|
62
|
-
css_items.append(
|
63
|
-
epub.EpubItem(
|
64
|
-
uid=css["uid"],
|
65
|
-
file_name=css["file_name"],
|
66
|
-
media_type="text/css",
|
67
|
-
content=content_bytes,
|
68
|
-
)
|
69
|
-
)
|
70
|
-
except FileNotFoundError:
|
71
|
-
logger.info(f"[epub] CSS 文件不存在: {css['path']}")
|
72
|
-
except UnicodeDecodeError:
|
73
|
-
logger.info(f"[epub] 无法解码文件: {css['path']}")
|
74
|
-
|
75
|
-
return css_items
|
@@ -1,131 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
novel_downloader.core.exporters.epub_utils.image_loader
|
4
|
-
-------------------------------------------------------
|
5
|
-
|
6
|
-
Utilities for embedding image files into an EpubBook.
|
7
|
-
"""
|
8
|
-
|
9
|
-
import logging
|
10
|
-
from collections.abc import Iterable, Sequence
|
11
|
-
from pathlib import Path
|
12
|
-
|
13
|
-
from ebooklib import epub
|
14
|
-
|
15
|
-
from novel_downloader.utils.constants import EPUB_IMAGE_FOLDER
|
16
|
-
|
17
|
-
logger = logging.getLogger(__name__)
|
18
|
-
|
19
|
-
_SUPPORTED_IMAGE_MEDIA_TYPES: dict[str, str] = {
|
20
|
-
"png": "image/png",
|
21
|
-
"jpg": "image/jpeg",
|
22
|
-
"jpeg": "image/jpeg",
|
23
|
-
"gif": "image/gif",
|
24
|
-
"svg": "image/svg+xml",
|
25
|
-
"webp": "image/webp",
|
26
|
-
}
|
27
|
-
_DEFAULT_IMAGE_MEDIA_TYPE = "image/jpeg"
|
28
|
-
|
29
|
-
|
30
|
-
def add_images_from_list(
|
31
|
-
book: epub.EpubBook,
|
32
|
-
image_list: Sequence[str | Path],
|
33
|
-
) -> epub.EpubBook:
|
34
|
-
"""
|
35
|
-
Add a list of image files to the EPUB's image folder.
|
36
|
-
|
37
|
-
:param book: The EpubBook object to modify.
|
38
|
-
:param image_list: List of paths to image files.
|
39
|
-
:return: The same EpubBook instance, with images added.
|
40
|
-
"""
|
41
|
-
for img_path in image_list:
|
42
|
-
img_path = Path(img_path)
|
43
|
-
if not img_path.is_file():
|
44
|
-
continue
|
45
|
-
|
46
|
-
suffix = img_path.suffix.lower().lstrip(".")
|
47
|
-
media_type = _SUPPORTED_IMAGE_MEDIA_TYPES.get(suffix)
|
48
|
-
if media_type is None:
|
49
|
-
media_type = _DEFAULT_IMAGE_MEDIA_TYPE
|
50
|
-
logger.warning(
|
51
|
-
"Unknown image suffix '%s' - defaulting media_type to %s",
|
52
|
-
suffix,
|
53
|
-
media_type,
|
54
|
-
)
|
55
|
-
|
56
|
-
try:
|
57
|
-
content = img_path.read_bytes()
|
58
|
-
item = epub.EpubItem(
|
59
|
-
uid=f"img_{img_path.stem}",
|
60
|
-
file_name=f"{EPUB_IMAGE_FOLDER}/{img_path.name}",
|
61
|
-
media_type=media_type,
|
62
|
-
content=content,
|
63
|
-
)
|
64
|
-
book.add_item(item)
|
65
|
-
logger.debug("Embedded image: %s", img_path.name)
|
66
|
-
except Exception:
|
67
|
-
logger.exception("Failed to embed image %s", img_path)
|
68
|
-
|
69
|
-
return book
|
70
|
-
|
71
|
-
|
72
|
-
def add_images_from_dir(
|
73
|
-
book: epub.EpubBook,
|
74
|
-
image_dir: str | Path,
|
75
|
-
) -> epub.EpubBook:
|
76
|
-
"""
|
77
|
-
Load every file in `image_dir` into the EPUB's image folder.
|
78
|
-
|
79
|
-
:param book: The EpubBook object to modify.
|
80
|
-
:param image_dir: Path to the directory containing image files.
|
81
|
-
:return: The same EpubBook instance, with images added.
|
82
|
-
"""
|
83
|
-
image_dir = Path(image_dir)
|
84
|
-
if not image_dir.is_dir():
|
85
|
-
logger.warning("Image directory not found or not a directory: %s", image_dir)
|
86
|
-
return book
|
87
|
-
|
88
|
-
for img_path in image_dir.iterdir():
|
89
|
-
if not img_path.is_file():
|
90
|
-
continue
|
91
|
-
|
92
|
-
suffix = img_path.suffix.lower().lstrip(".")
|
93
|
-
media_type = _SUPPORTED_IMAGE_MEDIA_TYPES.get(suffix)
|
94
|
-
if media_type is None:
|
95
|
-
media_type = _DEFAULT_IMAGE_MEDIA_TYPE
|
96
|
-
logger.warning(
|
97
|
-
"Unknown image suffix '%s' - defaulting media_type to %s",
|
98
|
-
suffix,
|
99
|
-
media_type,
|
100
|
-
)
|
101
|
-
|
102
|
-
try:
|
103
|
-
content = img_path.read_bytes()
|
104
|
-
item = epub.EpubItem(
|
105
|
-
uid=f"img_{img_path.stem}",
|
106
|
-
file_name=f"{EPUB_IMAGE_FOLDER}/{img_path.name}",
|
107
|
-
media_type=media_type,
|
108
|
-
content=content,
|
109
|
-
)
|
110
|
-
book.add_item(item)
|
111
|
-
logger.debug("Embedded image: %s", img_path.name)
|
112
|
-
except Exception:
|
113
|
-
logger.exception("Failed to embed image %s", img_path)
|
114
|
-
|
115
|
-
return book
|
116
|
-
|
117
|
-
|
118
|
-
def add_images_from_dirs(
|
119
|
-
book: epub.EpubBook,
|
120
|
-
image_dirs: Iterable[str | Path],
|
121
|
-
) -> epub.EpubBook:
|
122
|
-
"""
|
123
|
-
Add all images from multiple directories into the given EpubBook.
|
124
|
-
|
125
|
-
:param book: The EpubBook object to modify.
|
126
|
-
:param image_dirs: An iterable of directory paths to scan for images.
|
127
|
-
:return: The same EpubBook instance, with all images added.
|
128
|
-
"""
|
129
|
-
for img_dir in image_dirs:
|
130
|
-
book = add_images_from_dir(book, img_dir)
|
131
|
-
return book
|
@@ -1,100 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
novel_downloader.core.exporters.epub_utils.initializer
|
4
|
-
------------------------------------------------------
|
5
|
-
|
6
|
-
Initializes an epub.EpubBook object, sets metadata
|
7
|
-
(identifier, title, author, language, description),
|
8
|
-
adds a cover, and prepares the initial spine and TOC entries.
|
9
|
-
"""
|
10
|
-
|
11
|
-
import logging
|
12
|
-
from pathlib import Path
|
13
|
-
from typing import Any
|
14
|
-
|
15
|
-
from ebooklib import epub
|
16
|
-
|
17
|
-
from novel_downloader.utils.constants import (
|
18
|
-
EPUB_IMAGE_FOLDER,
|
19
|
-
EPUB_TEXT_FOLDER,
|
20
|
-
VOLUME_BORDER_IMAGE_PATH,
|
21
|
-
)
|
22
|
-
|
23
|
-
logger = logging.getLogger(__name__)
|
24
|
-
|
25
|
-
|
26
|
-
def init_epub(
|
27
|
-
book_info: dict[str, Any],
|
28
|
-
book_id: str,
|
29
|
-
intro_html: str,
|
30
|
-
book_cover_path: Path | None = None,
|
31
|
-
include_toc: bool = False,
|
32
|
-
) -> tuple[epub.EpubBook, list[Any], list[Any]]:
|
33
|
-
"""
|
34
|
-
Initialize an EPUB book with metadata, optional cover, and intro page.
|
35
|
-
|
36
|
-
:param book_info: Dict with keys 'book_name', 'author', 'summary'.
|
37
|
-
:param book_id: Book identifier (numeric or string).
|
38
|
-
:param intro_html: Intro content in XHTML format.
|
39
|
-
:param book_cover_path: Optional Path to the cover image file.
|
40
|
-
:param include_toc: Whether to include the <nav> item in the spine.
|
41
|
-
:return: (book, spine, toc_list)
|
42
|
-
"""
|
43
|
-
book = epub.EpubBook()
|
44
|
-
book.set_identifier(str(book_id))
|
45
|
-
book_name = book_info.get("book_name") or book_info.get("volume_name", "未找到书名")
|
46
|
-
book.set_title(book_name)
|
47
|
-
book.set_language("zh-CN")
|
48
|
-
book.add_author(book_info.get("author", "未找到作者"))
|
49
|
-
desc = book_info.get("summary") or book_info.get("volume_intro", "未找到作品简介")
|
50
|
-
book.add_metadata("DC", "description", desc)
|
51
|
-
|
52
|
-
spine = []
|
53
|
-
|
54
|
-
# cover
|
55
|
-
if book_cover_path:
|
56
|
-
try:
|
57
|
-
cover_bytes = book_cover_path.read_bytes()
|
58
|
-
ext = book_cover_path.suffix.lower()
|
59
|
-
ext = ext if ext in [".jpg", ".jpeg", ".png"] else ".jpeg"
|
60
|
-
filename = f"{EPUB_IMAGE_FOLDER}/cover{ext}"
|
61
|
-
book.set_cover(filename, cover_bytes)
|
62
|
-
spine.append("cover")
|
63
|
-
except FileNotFoundError:
|
64
|
-
logger.info(f"[epub] 封面图片不存在: {book_cover_path}")
|
65
|
-
except Exception as e:
|
66
|
-
logger.info(f"[epub] 读取封面失败: {book_cover_path},错误:{e}")
|
67
|
-
|
68
|
-
# 导航菜单
|
69
|
-
if include_toc:
|
70
|
-
spine.append("nav")
|
71
|
-
|
72
|
-
# 简介页面
|
73
|
-
intro = epub.EpubHtml(
|
74
|
-
title="书籍简介",
|
75
|
-
file_name=f"{EPUB_TEXT_FOLDER}/intro.xhtml",
|
76
|
-
lang="zh-CN",
|
77
|
-
uid="intro",
|
78
|
-
)
|
79
|
-
intro.content = intro_html
|
80
|
-
intro.add_link(href="../Styles/main.css", rel="stylesheet", type="text/css")
|
81
|
-
book.add_item(intro)
|
82
|
-
spine.append(intro)
|
83
|
-
|
84
|
-
# 添加卷边框图像 (volume_border.png)
|
85
|
-
try:
|
86
|
-
border_bytes = VOLUME_BORDER_IMAGE_PATH.read_bytes()
|
87
|
-
border_item = epub.EpubItem(
|
88
|
-
uid="volume-border",
|
89
|
-
file_name=f"{EPUB_IMAGE_FOLDER}/volume_border.png",
|
90
|
-
media_type="image/png",
|
91
|
-
content=border_bytes,
|
92
|
-
)
|
93
|
-
book.add_item(border_item)
|
94
|
-
except FileNotFoundError:
|
95
|
-
logger.info(f"[epub] 卷边框图片不存在: {VOLUME_BORDER_IMAGE_PATH}")
|
96
|
-
except Exception as e:
|
97
|
-
logger.info(f"[epub] 读取卷边框失败: {VOLUME_BORDER_IMAGE_PATH}: {e}")
|
98
|
-
|
99
|
-
toc_list = [epub.Link(intro.file_name, "书籍简介", intro.id)]
|
100
|
-
return book, spine, toc_list
|