novel-downloader 1.4.0__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/linovelib/browser.py +15 -0
- novel_downloader/core/fetchers/linovelib/session.py +15 -0
- 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.0.dist-info → novel_downloader-1.4.2.dist-info}/METADATA +4 -2
- {novel_downloader-1.4.0.dist-info → novel_downloader-1.4.2.dist-info}/RECORD +25 -30
- 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.0.dist-info → novel_downloader-1.4.2.dist-info}/WHEEL +0 -0
- {novel_downloader-1.4.0.dist-info → novel_downloader-1.4.2.dist-info}/entry_points.txt +0 -0
- {novel_downloader-1.4.0.dist-info → novel_downloader-1.4.2.dist-info}/licenses/LICENSE +0 -0
- {novel_downloader-1.4.0.dist-info → novel_downloader-1.4.2.dist-info}/top_level.txt +0 -0
@@ -10,45 +10,46 @@ that outlines the expected behavior of any downloader class.
|
|
10
10
|
from collections.abc import Awaitable, Callable
|
11
11
|
from typing import Any, Protocol, runtime_checkable
|
12
12
|
|
13
|
+
from novel_downloader.models import BookConfig
|
14
|
+
|
13
15
|
|
14
16
|
@runtime_checkable
|
15
17
|
class DownloaderProtocol(Protocol):
|
16
18
|
"""
|
17
|
-
Protocol for
|
19
|
+
Protocol for async downloader implementations.
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
as well as optional pre-download hooks.
|
21
|
+
Uses BookConfig (with book_id, optional start_id/end_id/ignore_ids)
|
22
|
+
for both single and batch downloads.
|
22
23
|
"""
|
23
24
|
|
24
25
|
async def download(
|
25
26
|
self,
|
26
|
-
|
27
|
+
book: BookConfig,
|
27
28
|
*,
|
28
29
|
progress_hook: Callable[[int, int], Awaitable[None]] | None = None,
|
29
30
|
**kwargs: Any,
|
30
31
|
) -> None:
|
31
32
|
"""
|
32
|
-
Download
|
33
|
+
Download a single book.
|
33
34
|
|
34
|
-
:param
|
35
|
-
:param progress_hook:
|
35
|
+
:param book: BookConfig with at least 'book_id'.
|
36
|
+
:param progress_hook: Optional async callback after each chapter.
|
36
37
|
args: completed_count, total_count.
|
37
38
|
"""
|
38
39
|
...
|
39
40
|
|
40
41
|
async def download_many(
|
41
42
|
self,
|
42
|
-
|
43
|
+
books: list[BookConfig],
|
43
44
|
*,
|
44
45
|
progress_hook: Callable[[int, int], Awaitable[None]] | None = None,
|
45
46
|
**kwargs: Any,
|
46
47
|
) -> None:
|
47
48
|
"""
|
48
|
-
|
49
|
+
Download multiple books.
|
49
50
|
|
50
|
-
:param
|
51
|
-
:param progress_hook:
|
51
|
+
:param books: List of BookConfig entries.
|
52
|
+
:param progress_hook: Optional async callback after each chapter.
|
52
53
|
args: completed_count, total_count.
|
53
54
|
"""
|
54
55
|
...
|
novel_downloader/locales/en.json
CHANGED
@@ -66,6 +66,8 @@
|
|
66
66
|
"download_downloading": "Downloading book {book_id} from {site}...",
|
67
67
|
"download_prompt_parse": "Parse...",
|
68
68
|
"download_book_ids": "One or more book IDs to process",
|
69
|
+
"download_option_start": "Start chapter ID (applies to the first book ID only)",
|
70
|
+
"download_option_end": "End chapter ID (applies to the first book ID only)",
|
69
71
|
"login_description": "Description",
|
70
72
|
"login_hint": "Hint",
|
71
73
|
"login_manual_prompt": ">> Please complete login in your browser and press Enter to continue...",
|
novel_downloader/locales/zh.json
CHANGED
@@ -66,6 +66,8 @@
|
|
66
66
|
"download_downloading": "正在从 {site} 下载书籍 {book_id}...",
|
67
67
|
"download_prompt_parse": "结束...",
|
68
68
|
"download_book_ids": "要处理的一个或多个小说 ID",
|
69
|
+
"download_option_start": "起始章节 ID (仅用于第一个书籍 ID)",
|
70
|
+
"download_option_end": "结束章节 ID (仅用于第一个书籍 ID)",
|
69
71
|
"login_description": "说明",
|
70
72
|
"login_hint": "提示",
|
71
73
|
"login_manual_prompt": ">> 请在浏览器中完成登录后按回车继续...",
|
@@ -8,6 +8,7 @@ novel_downloader.models
|
|
8
8
|
from .browser import NewContextOptions
|
9
9
|
from .chapter import ChapterDict
|
10
10
|
from .config import (
|
11
|
+
BookConfig,
|
11
12
|
DownloaderConfig,
|
12
13
|
ExporterConfig,
|
13
14
|
FetcherConfig,
|
@@ -39,6 +40,7 @@ from .types import (
|
|
39
40
|
|
40
41
|
__all__ = [
|
41
42
|
"NewContextOptions",
|
43
|
+
"BookConfig",
|
42
44
|
"DownloaderConfig",
|
43
45
|
"ParserConfig",
|
44
46
|
"FetcherConfig",
|
@@ -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
|
@@ -62,13 +57,13 @@ novel_downloader/core/fetchers/esjzone/__init__.py,sha256=Cr30WpKEnCrG_vVqttfI9T
|
|
62
57
|
novel_downloader/core/fetchers/esjzone/browser.py,sha256=SPvbKURw2DkUAlOBLbklmRCFvDoI4tAUDwmBRo2Kuc8,6224
|
63
58
|
novel_downloader/core/fetchers/esjzone/session.py,sha256=hwvS9LMWm5PYHTTZqYkBgWqkVqkrCfQcaFh6NBICt1Q,7218
|
64
59
|
novel_downloader/core/fetchers/linovelib/__init__.py,sha256=sMNXSBvn8gaZxNX5x4Ork8RzXxL7PhuigquWx6zQ6A4,254
|
65
|
-
novel_downloader/core/fetchers/linovelib/browser.py,sha256=
|
66
|
-
novel_downloader/core/fetchers/linovelib/session.py,sha256=
|
60
|
+
novel_downloader/core/fetchers/linovelib/browser.py,sha256=9rQzmJwtu_FHDDQvgDrVIZdwHA7CjnBkIgdyBuy7X50,6086
|
61
|
+
novel_downloader/core/fetchers/linovelib/session.py,sha256=2BpYe7kqIhHLbrH6Lk4TA4f8d_OHVO6lxHzZ-VVuG30,6096
|
67
62
|
novel_downloader/core/fetchers/qianbi/__init__.py,sha256=h4Rve7fO1GcSJ-DlNC5zw7fjoldJy4chG9RZQf5DuCU,236
|
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
|