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.
Files changed (31) hide show
  1. novel_downloader/__init__.py +1 -1
  2. novel_downloader/cli/download.py +69 -10
  3. novel_downloader/config/adapter.py +42 -9
  4. novel_downloader/core/downloaders/base.py +26 -22
  5. novel_downloader/core/downloaders/common.py +41 -5
  6. novel_downloader/core/downloaders/qidian.py +60 -32
  7. novel_downloader/core/exporters/common/epub.py +153 -68
  8. novel_downloader/core/exporters/epub_util.py +1358 -0
  9. novel_downloader/core/exporters/linovelib/epub.py +147 -190
  10. novel_downloader/core/fetchers/linovelib/browser.py +15 -0
  11. novel_downloader/core/fetchers/linovelib/session.py +15 -0
  12. novel_downloader/core/fetchers/qidian/browser.py +62 -10
  13. novel_downloader/core/interfaces/downloader.py +13 -12
  14. novel_downloader/locales/en.json +2 -0
  15. novel_downloader/locales/zh.json +2 -0
  16. novel_downloader/models/__init__.py +2 -0
  17. novel_downloader/models/config.py +8 -0
  18. novel_downloader/tui/screens/home.py +5 -4
  19. novel_downloader/utils/constants.py +0 -29
  20. {novel_downloader-1.4.0.dist-info → novel_downloader-1.4.2.dist-info}/METADATA +4 -2
  21. {novel_downloader-1.4.0.dist-info → novel_downloader-1.4.2.dist-info}/RECORD +25 -30
  22. novel_downloader/core/exporters/epub_utils/__init__.py +0 -40
  23. novel_downloader/core/exporters/epub_utils/css_builder.py +0 -75
  24. novel_downloader/core/exporters/epub_utils/image_loader.py +0 -131
  25. novel_downloader/core/exporters/epub_utils/initializer.py +0 -100
  26. novel_downloader/core/exporters/epub_utils/text_to_html.py +0 -178
  27. novel_downloader/core/exporters/epub_utils/volume_intro.py +0 -60
  28. {novel_downloader-1.4.0.dist-info → novel_downloader-1.4.2.dist-info}/WHEEL +0 -0
  29. {novel_downloader-1.4.0.dist-info → novel_downloader-1.4.2.dist-info}/entry_points.txt +0 -0
  30. {novel_downloader-1.4.0.dist-info → novel_downloader-1.4.2.dist-info}/licenses/LICENSE +0 -0
  31. {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 fully-asynchronous downloader classes.
19
+ Protocol for async downloader implementations.
18
20
 
19
- Defines the expected interface for any downloader implementation,
20
- including both batch and single book downloads,
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
- book_id: str,
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 logic for a single book.
33
+ Download a single book.
33
34
 
34
- :param book_id: The identifier of the book.
35
- :param progress_hook: (optional) Called after each chapter;
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
- book_ids: list[str],
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
- Batch download entry point.
49
+ Download multiple books.
49
50
 
50
- :param book_ids: List of book IDs to download.
51
- :param progress_hook: (optional) Called after each chapter;
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
  ...
@@ -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...",
@@ -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
- valid_book_ids: set[str],
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} | {valid_book_ids}")
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 valid_book_ids:
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, progress_hook=self._update_progress
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.0
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=TLa2eWlJhpY9lBjsolzhKpaZ7PWwhQj34Tj7RUSLlOY,218
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=HKCxufqnj4vDzCdM7soH_Y_eiVNRGpV17Mz08ADYwqQ,5279
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=UYtDlpKFcKmvBBN9ZOI3W7u0a72Fhrpk-DkZmKoKpqk,7281
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=H3Y9NnSv4lQA6ilKCd7HqAX24R79wXBMkLG5StK8znc,6420
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=1YNo-e5tuK5bGvuzX3eVmEJznD7f5JMo7a_abIdmEkM,15504
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=YrX5HdJDopEjc1kvEJRjMc1S4iR049IGvzUNtgE-LPE,12590
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=1BhaZCZLehX-v8Z7spatyizhHoMfCF08ZfZGpPfB5Kw,6331
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=wxfNxOX6kxSaove-YlDlNw1cmsBJFwysk6bS6Gsk8Cw,14503
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=pXasjPCG18CXmWkXFh7Uw24HNlTiocSup5tKK42YGck,5515
66
- novel_downloader/core/fetchers/linovelib/session.py,sha256=y-R2FzKf0dpffUATI-xTKvq1S2wvQWA50H1avuqUrGM,5525
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=JuFncpirI4HLyfIkR0xZmHL6AZZy7tQOzkzsC24b9ss,8578
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=XozFf-7OOtBQDlWNs3IRcBjwTPGFWwseFLGta-sZZmU,1560
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=-bKxndkwJCTCt7ZZGRRKzNaulC7z8XB6Tu7T1SR-1S8,6840
112
- novel_downloader/locales/zh.json,sha256=xzbUsKtkdQvQkehTgIURlQMPepOSWbQBgzTJ4SSJNZc,6699
113
- novel_downloader/models/__init__.py,sha256=G5fYGdaFquJBcllFJ7-YgqkvP4vD3xVEI9MeYcrCr_s,1068
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=d5G8sob9oNA8rr1N7BvDEJRVSCKJUtD_QBcvWZvGMyw,2792
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=ximy5vwHGrkUD88U6Gr9h3kT-wtOMMLBzB9aEZPuA5w,6716
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=1emMOmGRuat0yX8ozZMM1pAUGdU0kYBhDVNW_6dDtps,5700
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.0.dist-info/licenses/LICENSE,sha256=XgmnH0mBf-qEiizoVAfJQAKzPB9y3rBa-ni7M0Aqv4A,1066
166
- novel_downloader-1.4.0.dist-info/METADATA,sha256=iFXirg0_BDhczHRV37P5QgtbuUawakAx1kzY1aAfQeU,7027
167
- novel_downloader-1.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
168
- novel_downloader-1.4.0.dist-info/entry_points.txt,sha256=u1Ns5xI_QJyL4HAFCgJvJdib9ugu7M9I2tnQwZjJxrk,112
169
- novel_downloader-1.4.0.dist-info/top_level.txt,sha256=hP4jYWM2LTm1jxsW4hqEB8N0dsRvldO2QdhggJT917I,17
170
- novel_downloader-1.4.0.dist-info/RECORD,,
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