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