novel-downloader 1.4.5__py3-none-any.whl → 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. novel_downloader/__init__.py +1 -1
  2. novel_downloader/cli/__init__.py +2 -4
  3. novel_downloader/cli/clean.py +21 -88
  4. novel_downloader/cli/config.py +27 -104
  5. novel_downloader/cli/download.py +78 -66
  6. novel_downloader/cli/export.py +20 -21
  7. novel_downloader/cli/main.py +3 -1
  8. novel_downloader/cli/search.py +120 -0
  9. novel_downloader/cli/ui.py +156 -0
  10. novel_downloader/config/__init__.py +10 -14
  11. novel_downloader/config/adapter.py +195 -99
  12. novel_downloader/config/{loader.py → file_io.py} +53 -27
  13. novel_downloader/core/__init__.py +14 -13
  14. novel_downloader/core/archived/deqixs/fetcher.py +115 -0
  15. novel_downloader/core/archived/deqixs/parser.py +132 -0
  16. novel_downloader/core/archived/deqixs/searcher.py +89 -0
  17. novel_downloader/core/archived/qidian/searcher.py +79 -0
  18. novel_downloader/core/archived/wanbengo/searcher.py +98 -0
  19. novel_downloader/core/archived/xshbook/searcher.py +93 -0
  20. novel_downloader/core/downloaders/__init__.py +8 -30
  21. novel_downloader/core/downloaders/base.py +182 -30
  22. novel_downloader/core/downloaders/common.py +217 -384
  23. novel_downloader/core/downloaders/qianbi.py +332 -4
  24. novel_downloader/core/downloaders/qidian.py +250 -290
  25. novel_downloader/core/downloaders/registry.py +69 -0
  26. novel_downloader/core/downloaders/signals.py +46 -0
  27. novel_downloader/core/exporters/__init__.py +8 -26
  28. novel_downloader/core/exporters/base.py +107 -31
  29. novel_downloader/core/exporters/common/__init__.py +3 -4
  30. novel_downloader/core/exporters/common/epub.py +92 -171
  31. novel_downloader/core/exporters/common/main_exporter.py +14 -67
  32. novel_downloader/core/exporters/common/txt.py +90 -86
  33. novel_downloader/core/exporters/epub_util.py +184 -1327
  34. novel_downloader/core/exporters/linovelib/__init__.py +3 -2
  35. novel_downloader/core/exporters/linovelib/epub.py +165 -222
  36. novel_downloader/core/exporters/linovelib/main_exporter.py +10 -71
  37. novel_downloader/core/exporters/linovelib/txt.py +76 -66
  38. novel_downloader/core/exporters/qidian.py +15 -11
  39. novel_downloader/core/exporters/registry.py +55 -0
  40. novel_downloader/core/exporters/txt_util.py +67 -0
  41. novel_downloader/core/fetchers/__init__.py +57 -56
  42. novel_downloader/core/fetchers/aaatxt.py +83 -0
  43. novel_downloader/core/fetchers/{biquge/session.py → b520.py} +10 -10
  44. novel_downloader/core/fetchers/{base/session.py → base.py} +63 -47
  45. novel_downloader/core/fetchers/biquyuedu.py +83 -0
  46. novel_downloader/core/fetchers/dxmwx.py +110 -0
  47. novel_downloader/core/fetchers/eightnovel.py +139 -0
  48. novel_downloader/core/fetchers/{esjzone/session.py → esjzone.py} +23 -11
  49. novel_downloader/core/fetchers/guidaye.py +85 -0
  50. novel_downloader/core/fetchers/hetushu.py +92 -0
  51. novel_downloader/core/fetchers/{qianbi/browser.py → i25zw.py} +22 -26
  52. novel_downloader/core/fetchers/ixdzs8.py +113 -0
  53. novel_downloader/core/fetchers/jpxs123.py +101 -0
  54. novel_downloader/core/fetchers/{biquge/browser.py → lewenn.py} +15 -15
  55. novel_downloader/core/fetchers/{linovelib/session.py → linovelib.py} +16 -12
  56. novel_downloader/core/fetchers/piaotia.py +105 -0
  57. novel_downloader/core/fetchers/qbtr.py +101 -0
  58. novel_downloader/core/fetchers/{qianbi/session.py → qianbi.py} +9 -9
  59. novel_downloader/core/fetchers/{qidian/session.py → qidian.py} +55 -40
  60. novel_downloader/core/fetchers/quanben5.py +92 -0
  61. novel_downloader/core/fetchers/{base/rate_limiter.py → rate_limiter.py} +2 -2
  62. novel_downloader/core/fetchers/registry.py +60 -0
  63. novel_downloader/core/fetchers/{sfacg/session.py → sfacg.py} +11 -9
  64. novel_downloader/core/fetchers/shencou.py +106 -0
  65. novel_downloader/core/fetchers/{common/browser.py → shuhaige.py} +24 -19
  66. novel_downloader/core/fetchers/tongrenquan.py +84 -0
  67. novel_downloader/core/fetchers/ttkan.py +95 -0
  68. novel_downloader/core/fetchers/{common/session.py → wanbengo.py} +21 -17
  69. novel_downloader/core/fetchers/xiaoshuowu.py +106 -0
  70. novel_downloader/core/fetchers/xiguashuwu.py +177 -0
  71. novel_downloader/core/fetchers/xs63b.py +171 -0
  72. novel_downloader/core/fetchers/xshbook.py +85 -0
  73. novel_downloader/core/fetchers/{yamibo/session.py → yamibo.py} +23 -11
  74. novel_downloader/core/fetchers/yibige.py +114 -0
  75. novel_downloader/core/interfaces/__init__.py +8 -14
  76. novel_downloader/core/interfaces/downloader.py +6 -2
  77. novel_downloader/core/interfaces/exporter.py +7 -7
  78. novel_downloader/core/interfaces/fetcher.py +4 -17
  79. novel_downloader/core/interfaces/parser.py +5 -6
  80. novel_downloader/core/interfaces/searcher.py +26 -0
  81. novel_downloader/core/parsers/__init__.py +58 -22
  82. novel_downloader/core/parsers/aaatxt.py +132 -0
  83. novel_downloader/core/parsers/b520.py +116 -0
  84. novel_downloader/core/parsers/base.py +63 -12
  85. novel_downloader/core/parsers/biquyuedu.py +133 -0
  86. novel_downloader/core/parsers/dxmwx.py +162 -0
  87. novel_downloader/core/parsers/eightnovel.py +224 -0
  88. novel_downloader/core/parsers/{esjzone/main_parser.py → esjzone.py} +67 -67
  89. novel_downloader/core/parsers/guidaye.py +128 -0
  90. novel_downloader/core/parsers/hetushu.py +139 -0
  91. novel_downloader/core/parsers/i25zw.py +137 -0
  92. novel_downloader/core/parsers/ixdzs8.py +186 -0
  93. novel_downloader/core/parsers/jpxs123.py +137 -0
  94. novel_downloader/core/parsers/lewenn.py +142 -0
  95. novel_downloader/core/parsers/{linovelib/main_parser.py → linovelib.py} +54 -65
  96. novel_downloader/core/parsers/piaotia.py +189 -0
  97. novel_downloader/core/parsers/qbtr.py +136 -0
  98. novel_downloader/core/parsers/{qianbi/main_parser.py → qianbi.py} +54 -51
  99. novel_downloader/core/parsers/qidian/__init__.py +2 -2
  100. novel_downloader/core/parsers/qidian/book_info_parser.py +58 -59
  101. novel_downloader/core/parsers/qidian/chapter_encrypted.py +290 -346
  102. novel_downloader/core/parsers/qidian/chapter_normal.py +25 -56
  103. novel_downloader/core/parsers/qidian/main_parser.py +19 -57
  104. novel_downloader/core/parsers/qidian/utils/__init__.py +12 -11
  105. novel_downloader/core/parsers/qidian/utils/decryptor_fetcher.py +6 -7
  106. novel_downloader/core/parsers/qidian/utils/fontmap_recover.py +143 -0
  107. novel_downloader/core/parsers/qidian/utils/helpers.py +0 -4
  108. novel_downloader/core/parsers/qidian/utils/node_decryptor.py +2 -2
  109. novel_downloader/core/parsers/quanben5.py +103 -0
  110. novel_downloader/core/parsers/registry.py +57 -0
  111. novel_downloader/core/parsers/{sfacg/main_parser.py → sfacg.py} +46 -48
  112. novel_downloader/core/parsers/shencou.py +215 -0
  113. novel_downloader/core/parsers/shuhaige.py +111 -0
  114. novel_downloader/core/parsers/tongrenquan.py +116 -0
  115. novel_downloader/core/parsers/ttkan.py +132 -0
  116. novel_downloader/core/parsers/wanbengo.py +191 -0
  117. novel_downloader/core/parsers/xiaoshuowu.py +173 -0
  118. novel_downloader/core/parsers/xiguashuwu.py +435 -0
  119. novel_downloader/core/parsers/xs63b.py +161 -0
  120. novel_downloader/core/parsers/xshbook.py +134 -0
  121. novel_downloader/core/parsers/yamibo.py +155 -0
  122. novel_downloader/core/parsers/yibige.py +166 -0
  123. novel_downloader/core/searchers/__init__.py +51 -0
  124. novel_downloader/core/searchers/aaatxt.py +107 -0
  125. novel_downloader/core/searchers/b520.py +84 -0
  126. novel_downloader/core/searchers/base.py +168 -0
  127. novel_downloader/core/searchers/dxmwx.py +105 -0
  128. novel_downloader/core/searchers/eightnovel.py +84 -0
  129. novel_downloader/core/searchers/esjzone.py +102 -0
  130. novel_downloader/core/searchers/hetushu.py +92 -0
  131. novel_downloader/core/searchers/i25zw.py +93 -0
  132. novel_downloader/core/searchers/ixdzs8.py +107 -0
  133. novel_downloader/core/searchers/jpxs123.py +107 -0
  134. novel_downloader/core/searchers/piaotia.py +100 -0
  135. novel_downloader/core/searchers/qbtr.py +106 -0
  136. novel_downloader/core/searchers/qianbi.py +165 -0
  137. novel_downloader/core/searchers/quanben5.py +144 -0
  138. novel_downloader/core/searchers/registry.py +79 -0
  139. novel_downloader/core/searchers/shuhaige.py +124 -0
  140. novel_downloader/core/searchers/tongrenquan.py +110 -0
  141. novel_downloader/core/searchers/ttkan.py +92 -0
  142. novel_downloader/core/searchers/xiaoshuowu.py +122 -0
  143. novel_downloader/core/searchers/xiguashuwu.py +95 -0
  144. novel_downloader/core/searchers/xs63b.py +104 -0
  145. novel_downloader/locales/en.json +36 -79
  146. novel_downloader/locales/zh.json +37 -80
  147. novel_downloader/models/__init__.py +23 -50
  148. novel_downloader/models/book.py +44 -0
  149. novel_downloader/models/config.py +16 -43
  150. novel_downloader/models/login.py +1 -1
  151. novel_downloader/models/search.py +21 -0
  152. novel_downloader/resources/config/settings.toml +39 -74
  153. novel_downloader/resources/css_styles/intro.css +83 -0
  154. novel_downloader/resources/css_styles/main.css +30 -89
  155. novel_downloader/resources/json/xiguashuwu.json +718 -0
  156. novel_downloader/utils/__init__.py +43 -0
  157. novel_downloader/utils/chapter_storage.py +247 -226
  158. novel_downloader/utils/constants.py +5 -50
  159. novel_downloader/utils/cookies.py +6 -18
  160. novel_downloader/utils/crypto_utils/__init__.py +13 -0
  161. novel_downloader/utils/crypto_utils/aes_util.py +90 -0
  162. novel_downloader/utils/crypto_utils/aes_v1.py +619 -0
  163. novel_downloader/utils/crypto_utils/aes_v2.py +1143 -0
  164. novel_downloader/utils/{crypto_utils.py → crypto_utils/rc4.py} +3 -10
  165. novel_downloader/utils/epub/__init__.py +34 -0
  166. novel_downloader/utils/epub/builder.py +377 -0
  167. novel_downloader/utils/epub/constants.py +118 -0
  168. novel_downloader/utils/epub/documents.py +297 -0
  169. novel_downloader/utils/epub/models.py +120 -0
  170. novel_downloader/utils/epub/utils.py +179 -0
  171. novel_downloader/utils/file_utils/__init__.py +5 -30
  172. novel_downloader/utils/file_utils/io.py +9 -150
  173. novel_downloader/utils/file_utils/normalize.py +2 -2
  174. novel_downloader/utils/file_utils/sanitize.py +2 -7
  175. novel_downloader/utils/fontocr.py +207 -0
  176. novel_downloader/utils/i18n.py +2 -0
  177. novel_downloader/utils/logger.py +10 -16
  178. novel_downloader/utils/network.py +111 -252
  179. novel_downloader/utils/state.py +5 -90
  180. novel_downloader/utils/text_utils/__init__.py +16 -21
  181. novel_downloader/utils/text_utils/diff_display.py +6 -9
  182. novel_downloader/utils/text_utils/numeric_conversion.py +253 -0
  183. novel_downloader/utils/text_utils/text_cleaner.py +179 -0
  184. novel_downloader/utils/text_utils/truncate_utils.py +62 -0
  185. novel_downloader/utils/time_utils/__init__.py +6 -12
  186. novel_downloader/utils/time_utils/datetime_utils.py +23 -33
  187. novel_downloader/utils/time_utils/sleep_utils.py +5 -10
  188. novel_downloader/web/__init__.py +13 -0
  189. novel_downloader/web/components/__init__.py +11 -0
  190. novel_downloader/web/components/navigation.py +35 -0
  191. novel_downloader/web/main.py +66 -0
  192. novel_downloader/web/pages/__init__.py +17 -0
  193. novel_downloader/web/pages/download.py +78 -0
  194. novel_downloader/web/pages/progress.py +147 -0
  195. novel_downloader/web/pages/search.py +329 -0
  196. novel_downloader/web/services/__init__.py +17 -0
  197. novel_downloader/web/services/client_dialog.py +164 -0
  198. novel_downloader/web/services/cred_broker.py +113 -0
  199. novel_downloader/web/services/cred_models.py +35 -0
  200. novel_downloader/web/services/task_manager.py +264 -0
  201. novel_downloader-2.0.0.dist-info/METADATA +171 -0
  202. novel_downloader-2.0.0.dist-info/RECORD +210 -0
  203. {novel_downloader-1.4.5.dist-info → novel_downloader-2.0.0.dist-info}/entry_points.txt +1 -1
  204. novel_downloader/config/site_rules.py +0 -94
  205. novel_downloader/core/downloaders/biquge.py +0 -25
  206. novel_downloader/core/downloaders/esjzone.py +0 -25
  207. novel_downloader/core/downloaders/linovelib.py +0 -25
  208. novel_downloader/core/downloaders/sfacg.py +0 -25
  209. novel_downloader/core/downloaders/yamibo.py +0 -25
  210. novel_downloader/core/exporters/biquge.py +0 -25
  211. novel_downloader/core/exporters/esjzone.py +0 -25
  212. novel_downloader/core/exporters/qianbi.py +0 -25
  213. novel_downloader/core/exporters/sfacg.py +0 -25
  214. novel_downloader/core/exporters/yamibo.py +0 -25
  215. novel_downloader/core/factory/__init__.py +0 -20
  216. novel_downloader/core/factory/downloader.py +0 -73
  217. novel_downloader/core/factory/exporter.py +0 -58
  218. novel_downloader/core/factory/fetcher.py +0 -96
  219. novel_downloader/core/factory/parser.py +0 -86
  220. novel_downloader/core/fetchers/base/__init__.py +0 -14
  221. novel_downloader/core/fetchers/base/browser.py +0 -403
  222. novel_downloader/core/fetchers/biquge/__init__.py +0 -14
  223. novel_downloader/core/fetchers/common/__init__.py +0 -14
  224. novel_downloader/core/fetchers/esjzone/__init__.py +0 -14
  225. novel_downloader/core/fetchers/esjzone/browser.py +0 -204
  226. novel_downloader/core/fetchers/linovelib/__init__.py +0 -14
  227. novel_downloader/core/fetchers/linovelib/browser.py +0 -193
  228. novel_downloader/core/fetchers/qianbi/__init__.py +0 -14
  229. novel_downloader/core/fetchers/qidian/__init__.py +0 -14
  230. novel_downloader/core/fetchers/qidian/browser.py +0 -318
  231. novel_downloader/core/fetchers/sfacg/__init__.py +0 -14
  232. novel_downloader/core/fetchers/sfacg/browser.py +0 -189
  233. novel_downloader/core/fetchers/yamibo/__init__.py +0 -14
  234. novel_downloader/core/fetchers/yamibo/browser.py +0 -229
  235. novel_downloader/core/parsers/biquge/__init__.py +0 -10
  236. novel_downloader/core/parsers/biquge/main_parser.py +0 -134
  237. novel_downloader/core/parsers/common/__init__.py +0 -13
  238. novel_downloader/core/parsers/common/helper.py +0 -323
  239. novel_downloader/core/parsers/common/main_parser.py +0 -106
  240. novel_downloader/core/parsers/esjzone/__init__.py +0 -10
  241. novel_downloader/core/parsers/linovelib/__init__.py +0 -10
  242. novel_downloader/core/parsers/qianbi/__init__.py +0 -10
  243. novel_downloader/core/parsers/sfacg/__init__.py +0 -10
  244. novel_downloader/core/parsers/yamibo/__init__.py +0 -10
  245. novel_downloader/core/parsers/yamibo/main_parser.py +0 -194
  246. novel_downloader/models/browser.py +0 -21
  247. novel_downloader/models/chapter.py +0 -25
  248. novel_downloader/models/site_rules.py +0 -99
  249. novel_downloader/models/tasks.py +0 -33
  250. novel_downloader/models/types.py +0 -15
  251. novel_downloader/resources/css_styles/volume-intro.css +0 -56
  252. novel_downloader/resources/json/replace_word_map.json +0 -4
  253. novel_downloader/resources/text/blacklist.txt +0 -22
  254. novel_downloader/tui/__init__.py +0 -7
  255. novel_downloader/tui/app.py +0 -32
  256. novel_downloader/tui/main.py +0 -17
  257. novel_downloader/tui/screens/__init__.py +0 -14
  258. novel_downloader/tui/screens/home.py +0 -198
  259. novel_downloader/tui/screens/login.py +0 -74
  260. novel_downloader/tui/styles/home_layout.tcss +0 -79
  261. novel_downloader/tui/widgets/richlog_handler.py +0 -24
  262. novel_downloader/utils/cache.py +0 -24
  263. novel_downloader/utils/fontocr/__init__.py +0 -22
  264. novel_downloader/utils/fontocr/model_loader.py +0 -69
  265. novel_downloader/utils/fontocr/ocr_v1.py +0 -303
  266. novel_downloader/utils/fontocr/ocr_v2.py +0 -752
  267. novel_downloader/utils/hash_store.py +0 -279
  268. novel_downloader/utils/hash_utils.py +0 -103
  269. novel_downloader/utils/text_utils/chapter_formatting.py +0 -46
  270. novel_downloader/utils/text_utils/font_mapping.py +0 -28
  271. novel_downloader/utils/text_utils/text_cleaning.py +0 -107
  272. novel_downloader-1.4.5.dist-info/METADATA +0 -196
  273. novel_downloader-1.4.5.dist-info/RECORD +0 -165
  274. {novel_downloader-1.4.5.dist-info → novel_downloader-2.0.0.dist-info}/WHEEL +0 -0
  275. {novel_downloader-1.4.5.dist-info → novel_downloader-2.0.0.dist-info}/licenses/LICENSE +0 -0
  276. {novel_downloader-1.4.5.dist-info → novel_downloader-2.0.0.dist-info}/top_level.txt +0 -0
@@ -3,23 +3,20 @@
3
3
  novel_downloader.core.exporters.common.txt
4
4
  ------------------------------------------
5
5
 
6
- Contains the logic for exporting novel content as a single `.txt` file.
7
-
8
- This module defines `common_save_as_txt` function, which assembles and formats
9
- a novel based on metadata and chapter files found in the raw data directory.
10
- It is intended to be used by `CommonExporter` as part of the save/export process.
6
+ Defines `common_export_as_txt` to assemble and export a novel
7
+ into a single `.txt` file. Intended for use by `CommonExporter`.
11
8
  """
12
9
 
13
10
  from __future__ import annotations
14
11
 
15
- import json
12
+ from pathlib import Path
16
13
  from typing import TYPE_CHECKING
17
14
 
18
- from novel_downloader.utils.file_utils import save_as_txt
19
- from novel_downloader.utils.text_utils import (
20
- clean_chapter_title,
21
- format_chapter,
15
+ from novel_downloader.core.exporters.txt_util import (
16
+ build_txt_chapter,
17
+ build_txt_header,
22
18
  )
19
+ from novel_downloader.utils import get_cleaner, write_file
23
20
 
24
21
  if TYPE_CHECKING:
25
22
  from .main_exporter import CommonExporter
@@ -28,104 +25,106 @@ if TYPE_CHECKING:
28
25
  def common_export_as_txt(
29
26
  exporter: CommonExporter,
30
27
  book_id: str,
31
- ) -> None:
28
+ ) -> Path | None:
32
29
  """
33
- save_path 文件夹中该小说的所有章节 json 文件合并保存为一个完整的 txt 文件,
34
- 并保存到 out_path 下
35
-
36
- 处理流程:
37
- 1. book_info.json 中加载书籍信息 (包含书名、作者、简介及卷章节列表)
38
- 2. 遍历各卷, 每个卷先追加卷标题, 然后依次追加该卷下各章节的标题和内容
39
- 3. 将书籍元信息 (书名、作者、原文截至、内容简介) 与所有章节内容拼接
40
- 4. 将最终结果保存到 out_path (例如:`{book_name}.txt`)
41
-
42
- :param book_id: Identifier of the novel (used as subdirectory name).
30
+ Export a novel as a single text file by merging all chapter data.
31
+
32
+ Steps:
33
+ 1. Load book metadata.
34
+ 2. For each volume:
35
+ a. Append the volume title.
36
+ b. Batch-fetch all chapters in that volume to minimize SQLite calls.
37
+ c. Append each chapter's title, content, and optional author note.
38
+ 3. Build a header with book metadata and the latest chapter title.
39
+ 4. Concatenate header and all chapter contents.
40
+ 5. Save the resulting .txt file to the output directory
41
+ (e.g., '{book_name}.txt').
42
+
43
+ :param exporter: The CommonExporter instance managing paths and config.
44
+ :param book_id: Identifier of the novel (subdirectory under raw data).
43
45
  """
44
46
  TAG = "[Exporter]"
45
47
  # --- Paths & options ---
46
- raw_base = exporter._raw_data_dir / book_id
47
48
  out_dir = exporter.output_dir
48
49
  out_dir.mkdir(parents=True, exist_ok=True)
50
+ cleaner = get_cleaner(
51
+ enabled=exporter._config.clean_text,
52
+ config=exporter._config.cleaner_cfg,
53
+ )
49
54
 
50
55
  # --- Load book_info.json ---
51
- info_path = raw_base / "book_info.json"
52
- try:
53
- info_text = info_path.read_text(encoding="utf-8")
54
- book_info = json.loads(info_text)
55
- except Exception as e:
56
- exporter.logger.error("%s Failed to load %s: %s", TAG, info_path, e)
57
- return
56
+ book_info = exporter._load_book_info(book_id)
57
+ if not book_info:
58
+ return None
58
59
 
59
60
  # --- Compile chapters ---
60
61
  parts: list[str] = []
61
- latest_chapter: str = ""
62
- volumes = book_info.get("volumes", [])
62
+ latest_chapter_title: str = ""
63
63
 
64
- for vol in volumes:
65
- vol_name = vol.get("volume_name", "").strip()
66
- vol_name = clean_chapter_title(vol_name)
64
+ for vol in book_info.get("volumes", []):
65
+ vol_name = cleaner.clean_title(vol.get("volume_name", ""))
67
66
  if vol_name:
68
- volume_header = f"\n\n{'=' * 6} {vol_name} {'=' * 6}\n\n"
69
- parts.append(volume_header)
67
+ # e.g. "\n\n====== 第三卷 ======\n\n"
68
+ parts.append(f"\n\n{'=' * 6} {vol_name} {'=' * 6}\n\n")
70
69
  exporter.logger.info("%s Processing volume: %s", TAG, vol_name)
71
- for chap in vol.get("chapters", []):
72
- chap_id = chap.get("chapterId")
73
- chap_title = chap.get("title", "")
70
+
71
+ # Batch-fetch chapters for this volume
72
+ chap_ids = [
73
+ chap["chapterId"]
74
+ for chap in vol.get("chapters", [])
75
+ if chap.get("chapterId")
76
+ ]
77
+ chap_map = exporter._get_chapters(book_id, chap_ids)
78
+
79
+ for chap_meta in vol.get("chapters", []):
80
+ chap_id = chap_meta.get("chapterId")
74
81
  if not chap_id:
75
- exporter.logger.warning("%s Missing chapterId, skipping: %s", TAG, chap)
82
+ exporter.logger.warning(
83
+ "%s Missing chapterId, skipping: %s", TAG, chap_meta
84
+ )
76
85
  continue
77
86
 
78
- chapter_data = exporter._get_chapter(book_id, chap_id)
79
- if not chapter_data:
87
+ chap_title = chap_meta.get("title", "")
88
+ data = chap_map.get(chap_id)
89
+ if not data:
80
90
  exporter.logger.info(
81
- "%s Missing chapter file in: %s (%s), skipping.",
91
+ "%s Missing chapter: %s (%s), skipping.",
82
92
  TAG,
83
93
  chap_title,
84
94
  chap_id,
85
95
  )
86
96
  continue
87
97
 
88
- # Extract structured fields
89
- title = chapter_data.get("title", chap_title).strip()
90
- content = chapter_data.get("content", "").strip()
91
- author_say = chapter_data.get("author_say", "").strip()
92
- clean_title = clean_chapter_title(title)
93
-
94
- parts.append(format_chapter(clean_title, content, author_say))
95
- latest_chapter = clean_title
96
-
97
- # --- Build header ---
98
- name = book_info.get("book_name")
99
- author = book_info.get("author")
100
- words = book_info.get("word_count")
101
- updated = book_info.get("update_time")
102
- summary = book_info.get("summary")
103
-
104
- header_lines = []
105
-
106
- if name:
107
- header_lines.append(f"书名: {name}")
108
-
109
- if author:
110
- header_lines.append(f"作者: {author}")
98
+ # Extract and clean fields
99
+ title = cleaner.clean_title(data.get("title", chap_title))
100
+ content = cleaner.clean_content(data.get("content", ""))
101
+ extra = data.get("extra", {})
102
+ author_note = cleaner.clean_content(extra.get("author_say", ""))
111
103
 
112
- if words:
113
- header_lines.append(f"总字数: {words}")
104
+ extras = {"作者说": author_note} if author_note else {}
105
+ parts.append(
106
+ build_txt_chapter(title=title, paragraphs=content, extras=extras)
107
+ )
114
108
 
115
- if updated:
116
- header_lines.append(f"更新日期: {updated}")
109
+ latest_chapter_title = title
117
110
 
118
- header_lines.append(f"原文截至: {latest_chapter}")
119
-
120
- if summary:
121
- header_lines.append("内容简介:")
122
- header_lines.append(summary)
123
-
124
- header_lines.append("")
125
- header_lines.append("-" * 10)
126
- header_lines.append("")
127
-
128
- header = "\n".join(header_lines)
111
+ # --- Build header ---
112
+ name = book_info.get("book_name") or ""
113
+ author = book_info.get("author") or ""
114
+ words = book_info.get("word_count") or ""
115
+ updated = book_info.get("update_time") or ""
116
+ summary = book_info.get("summary") or ""
117
+
118
+ header_fields = [
119
+ ("书名", name),
120
+ ("作者", author),
121
+ ("总字数", words),
122
+ ("更新日期", updated),
123
+ ("原文截至", latest_chapter_title),
124
+ ("内容简介", summary),
125
+ ]
126
+
127
+ header = build_txt_header(header_fields)
129
128
 
130
129
  final_text = header + "\n\n" + "\n\n".join(parts).strip()
131
130
 
@@ -134,9 +133,14 @@ def common_export_as_txt(
134
133
  out_path = out_dir / out_name
135
134
 
136
135
  # --- Save final text ---
137
- try:
138
- save_as_txt(content=final_text, filepath=out_path)
136
+ result = write_file(
137
+ content=final_text,
138
+ filepath=out_path,
139
+ write_mode="w",
140
+ on_exist="overwrite",
141
+ )
142
+ if result:
139
143
  exporter.logger.info("%s Novel saved to: %s", TAG, out_path)
140
- except Exception as e:
141
- exporter.logger.error("%s Failed to save file: %s", TAG, e)
142
- return
144
+ else:
145
+ exporter.logger.error("%s Failed to write novel to %s", TAG, out_path)
146
+ return result