novel-downloader 1.3.3__py3-none-any.whl → 1.4.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 (211) hide show
  1. novel_downloader/__init__.py +1 -1
  2. novel_downloader/cli/clean.py +97 -78
  3. novel_downloader/cli/config.py +177 -0
  4. novel_downloader/cli/download.py +132 -87
  5. novel_downloader/cli/export.py +77 -0
  6. novel_downloader/cli/main.py +21 -28
  7. novel_downloader/config/__init__.py +1 -25
  8. novel_downloader/config/adapter.py +32 -31
  9. novel_downloader/config/loader.py +3 -3
  10. novel_downloader/config/site_rules.py +1 -2
  11. novel_downloader/core/__init__.py +3 -6
  12. novel_downloader/core/downloaders/__init__.py +10 -13
  13. novel_downloader/core/downloaders/base.py +233 -0
  14. novel_downloader/core/downloaders/biquge.py +27 -0
  15. novel_downloader/core/downloaders/common.py +414 -0
  16. novel_downloader/core/downloaders/esjzone.py +27 -0
  17. novel_downloader/core/downloaders/linovelib.py +27 -0
  18. novel_downloader/core/downloaders/qianbi.py +27 -0
  19. novel_downloader/core/downloaders/qidian.py +352 -0
  20. novel_downloader/core/downloaders/sfacg.py +27 -0
  21. novel_downloader/core/downloaders/yamibo.py +27 -0
  22. novel_downloader/core/exporters/__init__.py +37 -0
  23. novel_downloader/core/{savers → exporters}/base.py +73 -39
  24. novel_downloader/core/exporters/biquge.py +25 -0
  25. novel_downloader/core/exporters/common/__init__.py +12 -0
  26. novel_downloader/core/{savers → exporters}/common/epub.py +22 -22
  27. novel_downloader/core/{savers/common/main_saver.py → exporters/common/main_exporter.py} +35 -40
  28. novel_downloader/core/{savers → exporters}/common/txt.py +20 -23
  29. novel_downloader/core/{savers → exporters}/epub_utils/__init__.py +8 -3
  30. novel_downloader/core/{savers → exporters}/epub_utils/css_builder.py +2 -2
  31. novel_downloader/core/{savers → exporters}/epub_utils/image_loader.py +46 -4
  32. novel_downloader/core/{savers → exporters}/epub_utils/initializer.py +6 -4
  33. novel_downloader/core/{savers → exporters}/epub_utils/text_to_html.py +3 -3
  34. novel_downloader/core/{savers → exporters}/epub_utils/volume_intro.py +2 -2
  35. novel_downloader/core/exporters/esjzone.py +25 -0
  36. novel_downloader/core/exporters/linovelib/__init__.py +10 -0
  37. novel_downloader/core/exporters/linovelib/epub.py +449 -0
  38. novel_downloader/core/exporters/linovelib/main_exporter.py +127 -0
  39. novel_downloader/core/exporters/linovelib/txt.py +129 -0
  40. novel_downloader/core/exporters/qianbi.py +25 -0
  41. novel_downloader/core/{savers → exporters}/qidian.py +8 -8
  42. novel_downloader/core/exporters/sfacg.py +25 -0
  43. novel_downloader/core/exporters/yamibo.py +25 -0
  44. novel_downloader/core/factory/__init__.py +5 -17
  45. novel_downloader/core/factory/downloader.py +24 -126
  46. novel_downloader/core/factory/exporter.py +58 -0
  47. novel_downloader/core/factory/fetcher.py +96 -0
  48. novel_downloader/core/factory/parser.py +17 -12
  49. novel_downloader/core/{requesters → fetchers}/__init__.py +22 -15
  50. novel_downloader/core/{requesters → fetchers}/base/__init__.py +2 -4
  51. novel_downloader/core/fetchers/base/browser.py +383 -0
  52. novel_downloader/core/fetchers/base/rate_limiter.py +86 -0
  53. novel_downloader/core/fetchers/base/session.py +419 -0
  54. novel_downloader/core/fetchers/biquge/__init__.py +14 -0
  55. novel_downloader/core/{requesters/biquge/async_session.py → fetchers/biquge/browser.py} +18 -6
  56. novel_downloader/core/{requesters → fetchers}/biquge/session.py +23 -30
  57. novel_downloader/core/fetchers/common/__init__.py +14 -0
  58. novel_downloader/core/fetchers/common/browser.py +79 -0
  59. novel_downloader/core/{requesters/common/async_session.py → fetchers/common/session.py} +8 -25
  60. novel_downloader/core/fetchers/esjzone/__init__.py +14 -0
  61. novel_downloader/core/fetchers/esjzone/browser.py +202 -0
  62. novel_downloader/core/{requesters/esjzone/async_session.py → fetchers/esjzone/session.py} +62 -42
  63. novel_downloader/core/fetchers/linovelib/__init__.py +14 -0
  64. novel_downloader/core/fetchers/linovelib/browser.py +178 -0
  65. novel_downloader/core/fetchers/linovelib/session.py +178 -0
  66. novel_downloader/core/fetchers/qianbi/__init__.py +14 -0
  67. novel_downloader/core/{requesters/qianbi/session.py → fetchers/qianbi/browser.py} +30 -48
  68. novel_downloader/core/{requesters/qianbi/async_session.py → fetchers/qianbi/session.py} +18 -6
  69. novel_downloader/core/fetchers/qidian/__init__.py +14 -0
  70. novel_downloader/core/fetchers/qidian/browser.py +266 -0
  71. novel_downloader/core/fetchers/qidian/session.py +326 -0
  72. novel_downloader/core/fetchers/sfacg/__init__.py +14 -0
  73. novel_downloader/core/fetchers/sfacg/browser.py +189 -0
  74. novel_downloader/core/{requesters/sfacg/async_session.py → fetchers/sfacg/session.py} +43 -73
  75. novel_downloader/core/fetchers/yamibo/__init__.py +14 -0
  76. novel_downloader/core/fetchers/yamibo/browser.py +229 -0
  77. novel_downloader/core/{requesters/yamibo/async_session.py → fetchers/yamibo/session.py} +62 -44
  78. novel_downloader/core/interfaces/__init__.py +8 -12
  79. novel_downloader/core/interfaces/downloader.py +54 -0
  80. novel_downloader/core/interfaces/{saver.py → exporter.py} +12 -12
  81. novel_downloader/core/interfaces/fetcher.py +162 -0
  82. novel_downloader/core/interfaces/parser.py +6 -7
  83. novel_downloader/core/parsers/__init__.py +5 -6
  84. novel_downloader/core/parsers/base.py +9 -13
  85. novel_downloader/core/parsers/biquge/main_parser.py +12 -13
  86. novel_downloader/core/parsers/common/helper.py +3 -3
  87. novel_downloader/core/parsers/common/main_parser.py +39 -34
  88. novel_downloader/core/parsers/esjzone/main_parser.py +20 -14
  89. novel_downloader/core/parsers/linovelib/__init__.py +10 -0
  90. novel_downloader/core/parsers/linovelib/main_parser.py +210 -0
  91. novel_downloader/core/parsers/qianbi/main_parser.py +21 -15
  92. novel_downloader/core/parsers/qidian/__init__.py +2 -11
  93. novel_downloader/core/parsers/qidian/book_info_parser.py +113 -0
  94. novel_downloader/core/parsers/qidian/{browser/chapter_encrypted.py → chapter_encrypted.py} +162 -135
  95. novel_downloader/core/parsers/qidian/chapter_normal.py +150 -0
  96. novel_downloader/core/parsers/qidian/{session/chapter_router.py → chapter_router.py} +15 -15
  97. novel_downloader/core/parsers/qidian/{browser/main_parser.py → main_parser.py} +49 -40
  98. novel_downloader/core/parsers/qidian/utils/__init__.py +27 -0
  99. novel_downloader/core/parsers/qidian/utils/decryptor_fetcher.py +145 -0
  100. novel_downloader/core/parsers/qidian/{shared → utils}/helpers.py +41 -68
  101. novel_downloader/core/parsers/qidian/{session → utils}/node_decryptor.py +64 -50
  102. novel_downloader/core/parsers/sfacg/main_parser.py +12 -12
  103. novel_downloader/core/parsers/yamibo/main_parser.py +10 -10
  104. novel_downloader/locales/en.json +18 -2
  105. novel_downloader/locales/zh.json +18 -2
  106. novel_downloader/models/__init__.py +64 -0
  107. novel_downloader/models/browser.py +21 -0
  108. novel_downloader/models/chapter.py +25 -0
  109. novel_downloader/models/config.py +100 -0
  110. novel_downloader/models/login.py +20 -0
  111. novel_downloader/models/site_rules.py +99 -0
  112. novel_downloader/models/tasks.py +33 -0
  113. novel_downloader/models/types.py +15 -0
  114. novel_downloader/resources/config/settings.toml +31 -25
  115. novel_downloader/resources/json/linovelib_font_map.json +3573 -0
  116. novel_downloader/tui/__init__.py +7 -0
  117. novel_downloader/tui/app.py +32 -0
  118. novel_downloader/tui/main.py +17 -0
  119. novel_downloader/tui/screens/__init__.py +14 -0
  120. novel_downloader/tui/screens/home.py +191 -0
  121. novel_downloader/tui/screens/login.py +74 -0
  122. novel_downloader/tui/styles/home_layout.tcss +79 -0
  123. novel_downloader/tui/widgets/richlog_handler.py +24 -0
  124. novel_downloader/utils/__init__.py +6 -0
  125. novel_downloader/utils/chapter_storage.py +25 -38
  126. novel_downloader/utils/constants.py +11 -5
  127. novel_downloader/utils/cookies.py +66 -0
  128. novel_downloader/utils/crypto_utils.py +1 -74
  129. novel_downloader/utils/fontocr/ocr_v1.py +2 -1
  130. novel_downloader/utils/fontocr/ocr_v2.py +2 -2
  131. novel_downloader/utils/hash_store.py +10 -18
  132. novel_downloader/utils/hash_utils.py +3 -2
  133. novel_downloader/utils/logger.py +2 -3
  134. novel_downloader/utils/network.py +2 -1
  135. novel_downloader/utils/text_utils/chapter_formatting.py +6 -1
  136. novel_downloader/utils/text_utils/font_mapping.py +1 -1
  137. novel_downloader/utils/text_utils/text_cleaning.py +1 -1
  138. novel_downloader/utils/time_utils/datetime_utils.py +3 -3
  139. novel_downloader/utils/time_utils/sleep_utils.py +1 -1
  140. {novel_downloader-1.3.3.dist-info → novel_downloader-1.4.0.dist-info}/METADATA +69 -35
  141. novel_downloader-1.4.0.dist-info/RECORD +170 -0
  142. {novel_downloader-1.3.3.dist-info → novel_downloader-1.4.0.dist-info}/WHEEL +1 -1
  143. {novel_downloader-1.3.3.dist-info → novel_downloader-1.4.0.dist-info}/entry_points.txt +1 -0
  144. novel_downloader/cli/interactive.py +0 -66
  145. novel_downloader/cli/settings.py +0 -177
  146. novel_downloader/config/models.py +0 -187
  147. novel_downloader/core/downloaders/base/__init__.py +0 -14
  148. novel_downloader/core/downloaders/base/base_async.py +0 -153
  149. novel_downloader/core/downloaders/base/base_sync.py +0 -208
  150. novel_downloader/core/downloaders/biquge/__init__.py +0 -14
  151. novel_downloader/core/downloaders/biquge/biquge_async.py +0 -27
  152. novel_downloader/core/downloaders/biquge/biquge_sync.py +0 -27
  153. novel_downloader/core/downloaders/common/__init__.py +0 -14
  154. novel_downloader/core/downloaders/common/common_async.py +0 -210
  155. novel_downloader/core/downloaders/common/common_sync.py +0 -202
  156. novel_downloader/core/downloaders/esjzone/__init__.py +0 -14
  157. novel_downloader/core/downloaders/esjzone/esjzone_async.py +0 -27
  158. novel_downloader/core/downloaders/esjzone/esjzone_sync.py +0 -27
  159. novel_downloader/core/downloaders/qianbi/__init__.py +0 -14
  160. novel_downloader/core/downloaders/qianbi/qianbi_async.py +0 -27
  161. novel_downloader/core/downloaders/qianbi/qianbi_sync.py +0 -27
  162. novel_downloader/core/downloaders/qidian/__init__.py +0 -10
  163. novel_downloader/core/downloaders/qidian/qidian_sync.py +0 -219
  164. novel_downloader/core/downloaders/sfacg/__init__.py +0 -14
  165. novel_downloader/core/downloaders/sfacg/sfacg_async.py +0 -27
  166. novel_downloader/core/downloaders/sfacg/sfacg_sync.py +0 -27
  167. novel_downloader/core/downloaders/yamibo/__init__.py +0 -14
  168. novel_downloader/core/downloaders/yamibo/yamibo_async.py +0 -27
  169. novel_downloader/core/downloaders/yamibo/yamibo_sync.py +0 -27
  170. novel_downloader/core/factory/requester.py +0 -144
  171. novel_downloader/core/factory/saver.py +0 -56
  172. novel_downloader/core/interfaces/async_downloader.py +0 -36
  173. novel_downloader/core/interfaces/async_requester.py +0 -84
  174. novel_downloader/core/interfaces/sync_downloader.py +0 -36
  175. novel_downloader/core/interfaces/sync_requester.py +0 -82
  176. novel_downloader/core/parsers/qidian/browser/__init__.py +0 -12
  177. novel_downloader/core/parsers/qidian/browser/chapter_normal.py +0 -93
  178. novel_downloader/core/parsers/qidian/browser/chapter_router.py +0 -71
  179. novel_downloader/core/parsers/qidian/session/__init__.py +0 -12
  180. novel_downloader/core/parsers/qidian/session/chapter_encrypted.py +0 -443
  181. novel_downloader/core/parsers/qidian/session/chapter_normal.py +0 -115
  182. novel_downloader/core/parsers/qidian/session/main_parser.py +0 -128
  183. novel_downloader/core/parsers/qidian/shared/__init__.py +0 -37
  184. novel_downloader/core/parsers/qidian/shared/book_info_parser.py +0 -150
  185. novel_downloader/core/requesters/base/async_session.py +0 -410
  186. novel_downloader/core/requesters/base/browser.py +0 -337
  187. novel_downloader/core/requesters/base/session.py +0 -378
  188. novel_downloader/core/requesters/biquge/__init__.py +0 -14
  189. novel_downloader/core/requesters/common/__init__.py +0 -17
  190. novel_downloader/core/requesters/common/session.py +0 -113
  191. novel_downloader/core/requesters/esjzone/__init__.py +0 -13
  192. novel_downloader/core/requesters/esjzone/session.py +0 -235
  193. novel_downloader/core/requesters/qianbi/__init__.py +0 -13
  194. novel_downloader/core/requesters/qidian/__init__.py +0 -21
  195. novel_downloader/core/requesters/qidian/broswer.py +0 -307
  196. novel_downloader/core/requesters/qidian/session.py +0 -290
  197. novel_downloader/core/requesters/sfacg/__init__.py +0 -13
  198. novel_downloader/core/requesters/sfacg/session.py +0 -242
  199. novel_downloader/core/requesters/yamibo/__init__.py +0 -13
  200. novel_downloader/core/requesters/yamibo/session.py +0 -237
  201. novel_downloader/core/savers/__init__.py +0 -34
  202. novel_downloader/core/savers/biquge.py +0 -25
  203. novel_downloader/core/savers/common/__init__.py +0 -12
  204. novel_downloader/core/savers/esjzone.py +0 -25
  205. novel_downloader/core/savers/qianbi.py +0 -25
  206. novel_downloader/core/savers/sfacg.py +0 -25
  207. novel_downloader/core/savers/yamibo.py +0 -25
  208. novel_downloader/resources/config/rules.toml +0 -196
  209. novel_downloader-1.3.3.dist-info/RECORD +0 -166
  210. {novel_downloader-1.3.3.dist-info → novel_downloader-1.4.0.dist-info}/licenses/LICENSE +0 -0
  211. {novel_downloader-1.3.3.dist-info → novel_downloader-1.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,170 @@
1
+ novel_downloader/__init__.py,sha256=TLa2eWlJhpY9lBjsolzhKpaZ7PWwhQj34Tj7RUSLlOY,218
2
+ novel_downloader/cli/__init__.py,sha256=-2HAut_U1e67MZGdvbpEJ1n5J-bRchzto6L4c-nWeXY,174
3
+ novel_downloader/cli/clean.py,sha256=hOk8SJQwBCw2oOObTdEI79wpnmZ25uB1s9LQK1-4LNU,4487
4
+ novel_downloader/cli/config.py,sha256=C6QLfegZLp4legmu8KenqyYKNdrk47bH0z86ujLP0pY,6509
5
+ novel_downloader/cli/download.py,sha256=HKCxufqnj4vDzCdM7soH_Y_eiVNRGpV17Mz08ADYwqQ,5279
6
+ novel_downloader/cli/export.py,sha256=x9uvyLuvkuaDZGoH212aHZ7XyPT9b2S78AmTN6rkAu4,2283
7
+ novel_downloader/cli/main.py,sha256=9J8KMuYwL01X6chIaXpQNeS5d3pHnwB9vA9XjKd8RrM,919
8
+ novel_downloader/config/__init__.py,sha256=2mnf33MQOUnLGCnL1NtNV_rHBejNxBNIbobIGN0tw4E,666
9
+ novel_downloader/config/adapter.py,sha256=UYtDlpKFcKmvBBN9ZOI3W7u0a72Fhrpk-DkZmKoKpqk,7281
10
+ novel_downloader/config/loader.py,sha256=jo_1rr3UKZRAFFYgO-oHpYLRhF431chmfx4fLGh0MKw,5743
11
+ novel_downloader/config/site_rules.py,sha256=CJksBSvVAC4sR6cEruf4pM1Jv0zTJb1lcHq0Yn6LPFM,2979
12
+ novel_downloader/core/__init__.py,sha256=zzrXjQfBUhfLmBD_95oHTjtTsR81NSRtHcxpYBxQlZ4,654
13
+ novel_downloader/core/downloaders/__init__.py,sha256=AK5zeetVXOn_irgHp-NORPYW65UJM4CsF8ysxf2vKD4,1064
14
+ novel_downloader/core/downloaders/base.py,sha256=H3Y9NnSv4lQA6ilKCd7HqAX24R79wXBMkLG5StK8znc,6420
15
+ novel_downloader/core/downloaders/biquge.py,sha256=PTm8eeaHVyw_nvtnuRpZ2hT7hLs2LEPV1Drd2O6idNM,651
16
+ novel_downloader/core/downloaders/common.py,sha256=1YNo-e5tuK5bGvuzX3eVmEJznD7f5JMo7a_abIdmEkM,15504
17
+ novel_downloader/core/downloaders/esjzone.py,sha256=GBFJ3fIPov483QxkOTaKBU2wd2f8T4GdvzSbRaU9hDI,655
18
+ novel_downloader/core/downloaders/linovelib.py,sha256=Cd43_70SKbKM8_C8c4qev3pLUgyziW9413qrA8EcStY,663
19
+ novel_downloader/core/downloaders/qianbi.py,sha256=ARuk1xABOGCjlD_ct5hOKMgWloU187rUJBbICJ2yvkY,651
20
+ novel_downloader/core/downloaders/qidian.py,sha256=YrX5HdJDopEjc1kvEJRjMc1S4iR049IGvzUNtgE-LPE,12590
21
+ novel_downloader/core/downloaders/sfacg.py,sha256=ShlkIIR6OW_x2FhNDi79S_3AL2CWId2upQmRkApee_E,647
22
+ novel_downloader/core/downloaders/yamibo.py,sha256=9L0m5Zq0o1y44tCQ4ZRHHqdO3YresJkRAVvCtsQwouk,651
23
+ novel_downloader/core/exporters/__init__.py,sha256=ATkkdh6RUIaM19mG1XjFiaMnGRgFFGT3ixsqVkU13Q0,867
24
+ novel_downloader/core/exporters/base.py,sha256=duIdLnj9kKNZ9r2aJV0Rzl-rnQCP8olk12uzduT9PtE,6146
25
+ novel_downloader/core/exporters/biquge.py,sha256=SJCChYtDLJKrOpwDCT8IeR0v1LoiUQuTTejfGmRnxX8,462
26
+ novel_downloader/core/exporters/esjzone.py,sha256=UP9gtTOv6komWjgvwXkttqDYJJPjjbJdiG3yz9RxHSQ,467
27
+ novel_downloader/core/exporters/qianbi.py,sha256=x841MNIxpS5eY8u91ODC0cpIhpT-ENnFqs_Yx6HwtdM,462
28
+ novel_downloader/core/exporters/qidian.py,sha256=ecaZcgqXyLvJtT2J5QXw4qS9gY3d72mgUnV-0frJvhQ,724
29
+ novel_downloader/core/exporters/sfacg.py,sha256=j4_H-ixvDxd_OJfIvD4Lbs24LaiFjaYqhvhZcks-ItI,457
30
+ novel_downloader/core/exporters/yamibo.py,sha256=QaY13llqpyxIgmMnMVFtR5yFMIpyMHHdCmVDW4Dq-bo,462
31
+ 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/main_exporter.py,sha256=-MRZEpAdsfdRt3VxIv4krMaw2UJnrvCnyj5Jde93zuA,3926
34
+ 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
+ novel_downloader/core/exporters/linovelib/__init__.py,sha256=zD7A7OhluipwICssnp27c_oenYozevZH9g0Qj5WOMWY,195
42
+ novel_downloader/core/exporters/linovelib/epub.py,sha256=wxfNxOX6kxSaove-YlDlNw1cmsBJFwysk6bS6Gsk8Cw,14503
43
+ novel_downloader/core/exporters/linovelib/main_exporter.py,sha256=cHkAp_jdF6uji5Avu1l8z6mR0nSrWYgX6ETDEezkjoE,3700
44
+ novel_downloader/core/exporters/linovelib/txt.py,sha256=ALlZUl5nNtg4OmYlurMC0acjmTOBV7G8c13DPrxbG4w,4407
45
+ novel_downloader/core/factory/__init__.py,sha256=_IY3N35onhWD_nw_TyxKOxa6e7Uak9Cv0bp4pK9yb0M,464
46
+ novel_downloader/core/factory/downloader.py,sha256=hNCp3IlZQzeTSLBMuO_Y_EzrAn9_-8SLBHcUwMw8ijM,2060
47
+ novel_downloader/core/factory/exporter.py,sha256=CjDJGnWBDk-S1zYntIDAEo1hLM2q55tlJOjTXKn0hAI,1533
48
+ novel_downloader/core/factory/fetcher.py,sha256=stfRJnh5ZXLqRsDtQC1BDeTe0yaZI-mgm3Qx03nWUP4,2402
49
+ novel_downloader/core/factory/parser.py,sha256=0PXepJhlE6aGs9_t81vyho1eCw84-6XRBGb98_phvSQ,2237
50
+ novel_downloader/core/fetchers/__init__.py,sha256=C1OykEdCzj3fpLRRhVrvwClpzz-pzTzTilH306crgYg,1440
51
+ novel_downloader/core/fetchers/base/__init__.py,sha256=p9be-q2YjiHcQhv4_KMeZmHgaAYYyUDWoBo6Gvwughc,224
52
+ novel_downloader/core/fetchers/base/browser.py,sha256=oR3ZMw71_LJrgTZXduVtZuXEkr7PZ9nxlS5ZlYavN_0,11153
53
+ novel_downloader/core/fetchers/base/rate_limiter.py,sha256=zUYH_PjnKfUzJpcbUPtMkwXxIlF0SH-ZTFlbCUrq060,2724
54
+ novel_downloader/core/fetchers/base/session.py,sha256=Elfpov2cqojunCrLaaSL9ZgWLNVanUDZvgOoIfk5sSc,13251
55
+ novel_downloader/core/fetchers/biquge/__init__.py,sha256=9EW4eerGeob4QGoDr11A8Mv7xvcfWVFU57M3VT9vzPI,236
56
+ novel_downloader/core/fetchers/biquge/browser.py,sha256=vplaCRAz8ECxk-yXm7DxSxW3EyX8GCYJMeImUut53RI,2390
57
+ novel_downloader/core/fetchers/biquge/session.py,sha256=waxv_dPsBl2SeudaINjt-eUwpW-FcDP2zFN_1-NXhr8,2400
58
+ novel_downloader/core/fetchers/common/__init__.py,sha256=ur_zQHrmJdPsFvpyC8AWjsZptHLbBHLb3EuKLt6LRVc,236
59
+ novel_downloader/core/fetchers/common/browser.py,sha256=RgNOizfgi_59Xaee6lTlpfiCMGjz_4luNDlHLQqUpl0,2289
60
+ novel_downloader/core/fetchers/common/session.py,sha256=Ydtwun9lPN4VEIIEIA7Skm6_GOiF_9VDUPkjGQQTdtM,2299
61
+ novel_downloader/core/fetchers/esjzone/__init__.py,sha256=Cr30WpKEnCrG_vVqttfI9T0zdkwDsLFOnCxQz4EAQQA,242
62
+ novel_downloader/core/fetchers/esjzone/browser.py,sha256=SPvbKURw2DkUAlOBLbklmRCFvDoI4tAUDwmBRo2Kuc8,6224
63
+ novel_downloader/core/fetchers/esjzone/session.py,sha256=hwvS9LMWm5PYHTTZqYkBgWqkVqkrCfQcaFh6NBICt1Q,7218
64
+ 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
67
+ novel_downloader/core/fetchers/qianbi/__init__.py,sha256=h4Rve7fO1GcSJ-DlNC5zw7fjoldJy4chG9RZQf5DuCU,236
68
+ novel_downloader/core/fetchers/qianbi/browser.py,sha256=1EmrSwpqSYhEO_ID3RJbUaAOhcqvVnMnch6iOafXbTA,3162
69
+ novel_downloader/core/fetchers/qianbi/session.py,sha256=c3pJcgi9C1x9QYTBihvazHcgT7XTp2HBYfStTn6gSEg,3141
70
+ novel_downloader/core/fetchers/qidian/__init__.py,sha256=2LshlX82lFpWZMV6yujHsfue9KM0-F1O3HvMCopIv9M,236
71
+ novel_downloader/core/fetchers/qidian/browser.py,sha256=JuFncpirI4HLyfIkR0xZmHL6AZZy7tQOzkzsC24b9ss,8578
72
+ novel_downloader/core/fetchers/qidian/session.py,sha256=WrHsov15PoRDYbb1ZRaScJQGRZv6axz9_hVEC1Wt1PM,9746
73
+ novel_downloader/core/fetchers/sfacg/__init__.py,sha256=bQAIwERsX9XOKrP2LteFKX8Jlhw4oeUNwpZTHXn5RRg,230
74
+ novel_downloader/core/fetchers/sfacg/browser.py,sha256=15PVS75PxEKR5W7mQbqVxoN0d4V1XVYVF0l1yy_sv_Y,5681
75
+ novel_downloader/core/fetchers/sfacg/session.py,sha256=9K4emQCRq45vzYn-ZDX549tK2F92x2CBMp4ODohNOjc,5085
76
+ novel_downloader/core/fetchers/yamibo/__init__.py,sha256=5ds6DNNvpo6F6U5dboEaIsJoKSPorkPte_HWVnXMdXo,236
77
+ novel_downloader/core/fetchers/yamibo/browser.py,sha256=7YRrWbA8_cOcT_z-VjMWP6FUg30TwV6eLW4zJZ_UxSE,7249
78
+ novel_downloader/core/fetchers/yamibo/session.py,sha256=434EArdKgEYIBZkb1nMub3PQXdRTS-Ov2_1u9MESjOs,7212
79
+ novel_downloader/core/interfaces/__init__.py,sha256=hB1SjBzuN7qnZx_h3RV4w_roj3ZwShbIG3CV9jGMB14,602
80
+ novel_downloader/core/interfaces/downloader.py,sha256=XozFf-7OOtBQDlWNs3IRcBjwTPGFWwseFLGta-sZZmU,1560
81
+ novel_downloader/core/interfaces/exporter.py,sha256=zwIaJ5FXo_JmKYg2UZV9FTWX7xZ2e0QuL4RTNGRtI04,1610
82
+ novel_downloader/core/interfaces/fetcher.py,sha256=XlP3d-Q_xvuVextZCdLOxDf8BwBbx799L_WLJx0WTB8,4096
83
+ novel_downloader/core/interfaces/parser.py,sha256=iZeUgNl-zE-wsf1eW4GVCPjEaLSvQrNJi_mVEOU9ulY,1353
84
+ novel_downloader/core/parsers/__init__.py,sha256=lrpt5E1YMiI0WY_cco1lEDhBQZJ_ZEV4b5fR6Fs3ZwA,832
85
+ novel_downloader/core/parsers/base.py,sha256=GifJANFm0auYyzYuqYox9LFgDskW8G2vR0DaMrRcG1w,2962
86
+ novel_downloader/core/parsers/biquge/__init__.py,sha256=oaLgBLdMW7DcdsEGXW-S91Z6_p5XmBcBIp7TX79hmV4,173
87
+ novel_downloader/core/parsers/biquge/main_parser.py,sha256=mbrEjJ8iSvWZ7SU4RH8pHxs1jnEwwlag_KkhTYoU0sY,4572
88
+ novel_downloader/core/parsers/common/__init__.py,sha256=MzNUUxvf7jmeO0LvQ_FRW0QLKuYVXkkTgJ4CxWZKF-Y,341
89
+ novel_downloader/core/parsers/common/helper.py,sha256=U5SzhVJJ5lGRNhLoOdEQw9LfBuPv17dmyT_MbdgnCl0,12052
90
+ novel_downloader/core/parsers/common/main_parser.py,sha256=znuXTJC-TXsgBTB3Hyqi87L65bqyM90TYyjU1KEhswc,3229
91
+ novel_downloader/core/parsers/esjzone/__init__.py,sha256=RSsUdOvaiqv-rTaYVc-qO25jytRCj9X2EYErMltA_b8,177
92
+ novel_downloader/core/parsers/esjzone/main_parser.py,sha256=jF7WPAwN5S2uUOdXk3fCkM3PSS5DPhI-SzmP00GFItw,8520
93
+ novel_downloader/core/parsers/linovelib/__init__.py,sha256=t5xzMEb4Q9MZxOvedOnot2HR6nCtU5YnAxGUk2lJoMA,185
94
+ novel_downloader/core/parsers/linovelib/main_parser.py,sha256=LqC6W-Lk7sdbiqhYvqQAh2z31v23OtMzyp1kAelsugI,7565
95
+ novel_downloader/core/parsers/qianbi/__init__.py,sha256=CNmoER8U2u4-ix5S0DDq-pHTtkLR0IZf2SLaTTYXee4,173
96
+ novel_downloader/core/parsers/qianbi/main_parser.py,sha256=kMjGew_dmqjI9oIThyHgxThZZgS3IaESB_tNf1nkCKk,5069
97
+ novel_downloader/core/parsers/qidian/__init__.py,sha256=fWWFeyythX0gpDCJ-2AslrRl2hq4vW2bx9hHh1W7mAw,173
98
+ novel_downloader/core/parsers/qidian/book_info_parser.py,sha256=h5oAVJOOSZMV4U2GnSezVHPJOEUKUic_oD2gl94mEPQ,3721
99
+ novel_downloader/core/parsers/qidian/chapter_encrypted.py,sha256=VhPDERJq1ROJaBlaJuCXJGffisOxkeYFDUKXHo31HfU,18263
100
+ novel_downloader/core/parsers/qidian/chapter_normal.py,sha256=6mD86OalpxiCDhjzUjcfFYsgmnTRU_rMIgFN8_Ceu50,4453
101
+ novel_downloader/core/parsers/qidian/chapter_router.py,sha256=foVMlWtE-qUOvJD_4EDiuAVaNkFdeV_ZTCvS5IL7Orc,1957
102
+ novel_downloader/core/parsers/qidian/main_parser.py,sha256=SPU6sUO8e4Gp_1q2WypuUQYEyHhoXM7OiRsthhFT6SM,4396
103
+ novel_downloader/core/parsers/qidian/utils/__init__.py,sha256=HfjXrGAwH_ceRSE4m88Dl9qb7vHMGcPFxxTU3qrTld8,548
104
+ novel_downloader/core/parsers/qidian/utils/decryptor_fetcher.py,sha256=8ytJnAfiJIxj0wlke9UwYA6vngUyLxVZt8PbfkNUhss,4687
105
+ novel_downloader/core/parsers/qidian/utils/helpers.py,sha256=-7vd1BMu5pVFCRySfPWyrItVbZ2wrHPNY8iAQEP7T_8,3264
106
+ novel_downloader/core/parsers/qidian/utils/node_decryptor.py,sha256=gjqirr5RECScFw0C6DET7ZMaLTcqGcPOmlkwFUJbTHQ,5965
107
+ novel_downloader/core/parsers/sfacg/__init__.py,sha256=O2nscvtOweMXHMONdvySTsLSy1ulhv53WTp4r6J47tI,169
108
+ novel_downloader/core/parsers/sfacg/main_parser.py,sha256=yW18MALAZisJdFO-7peI1h-4XVloEDXeCJvXUz5hJ1A,5899
109
+ novel_downloader/core/parsers/yamibo/__init__.py,sha256=KJ_fCcakoQrsG36OOd0paXXB1cAIwMirKaA8ZLUKUKI,173
110
+ 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
114
+ novel_downloader/models/browser.py,sha256=ly-jM7izQ77yTIG-oau51HJofDpBfrXpIJZJjoQyad8,435
115
+ novel_downloader/models/chapter.py,sha256=bdAQUDZIuuTVxoYjoOJrbS2u81b1B2mkuZkTSf0m2HQ,492
116
+ novel_downloader/models/config.py,sha256=d5G8sob9oNA8rr1N7BvDEJRVSCKJUtD_QBcvWZvGMyw,2792
117
+ novel_downloader/models/login.py,sha256=sY2Jom6PLpA9Z3Uy7plZKhda3Gq7awKOOIIaQ79PpWs,371
118
+ novel_downloader/models/site_rules.py,sha256=kzDB5F8lf4udAO0WVUrgBOR7ave3jsMBxt7cEoG0bnI,2721
119
+ novel_downloader/models/tasks.py,sha256=e4DYEXQQQewgQyCCHfc0UYnkPJ96LafmhG3oO5MQP0Q,465
120
+ novel_downloader/models/types.py,sha256=q1KDuGW0SVxQILKKoPXKRecfaKe2m8jUM0nyaeDJ6dE,394
121
+ novel_downloader/resources/config/settings.toml,sha256=A2B_EMh0zjTLi6UBCjhX__igz7xLCXnCqDzJMhiZn8E,4491
122
+ novel_downloader/resources/css_styles/main.css,sha256=WM6GePwdOGgM86fbbOxQ0_0oerTBDZeQHt8zRVfcJp8,1617
123
+ novel_downloader/resources/css_styles/volume-intro.css,sha256=6gaUnNKkrb2w8tYJRq1BGD1FwbhT1I5W2GI_Zelo9G4,1156
124
+ novel_downloader/resources/images/volume_border.png,sha256=2dEVimnTHKOfLMhi7bhkh_5joWNnrqg8duomLSNOZx4,28613
125
+ novel_downloader/resources/js_scripts/qidian_decrypt_node.js,sha256=spNrk_gXI7pPW9abr4XGc2LASMe1UuN4BUe4cH24L8s,2195
126
+ novel_downloader/resources/json/linovelib_font_map.json,sha256=F1IlEcvXGcagnZCu4Gw2vQ2NLnhy6cJOLhabukqnftw,67854
127
+ novel_downloader/resources/json/replace_word_map.json,sha256=ptL9sGO9aK7rnnAaOIyZ0OiH7gaT0BhFzficzYZSDks,55
128
+ novel_downloader/resources/text/blacklist.txt,sha256=sovK9JgARZP3lud5b1EZgvv8LSVKPthf4ADpCSZZgQ8,154
129
+ novel_downloader/tui/__init__.py,sha256=8RB8tBrPcoBzm1tpQlgZqnOZXrdHmlzMRxuk9wsN4m0,115
130
+ novel_downloader/tui/app.py,sha256=ytV1u15nGCRj_ff_GeAL3W1XlU6r5Lh_k3HBcAjPRx0,731
131
+ novel_downloader/tui/main.py,sha256=MBP8SrwEYTpGQm-V9W_4rKnTeslneORfkzFsU3Xj2yA,256
132
+ novel_downloader/tui/screens/__init__.py,sha256=QsUM5cUEKm7nluQh9acEt37xRWbkZU3vqIpBduepDCU,203
133
+ novel_downloader/tui/screens/home.py,sha256=ximy5vwHGrkUD88U6Gr9h3kT-wtOMMLBzB9aEZPuA5w,6716
134
+ novel_downloader/tui/screens/login.py,sha256=eEVmQFZKQX8mCt_qp96QK8t2pB15FeUrk_oV3jm_Mz4,2224
135
+ novel_downloader/tui/styles/home_layout.tcss,sha256=VNJs339qiwNUuqwwdK6VkYThCdsqlw-2mEoZVCspdrw,974
136
+ novel_downloader/tui/widgets/richlog_handler.py,sha256=bhFb0E7Z7-dRS07y_vMjxjPeCic9AD59MFPLhef_R5g,572
137
+ novel_downloader/utils/__init__.py,sha256=4iUXNUzxeAnGmpGWsB4K_jckUYEW0u_LqWp_OM7mtK8,78
138
+ novel_downloader/utils/cache.py,sha256=_jVEWAaMGY86kWVnAuuIvJA_hA8dH2ClI7o3p99mEvE,618
139
+ novel_downloader/utils/chapter_storage.py,sha256=wXXIzNjs1GfPPoqgYYPnQ9Two23McW6ZZNDmhfSC9aY,9831
140
+ novel_downloader/utils/constants.py,sha256=1emMOmGRuat0yX8ozZMM1pAUGdU0kYBhDVNW_6dDtps,5700
141
+ novel_downloader/utils/cookies.py,sha256=WuPBt_z7cF4n7QPIw1julATWt6dbDRCbNdgXHSikmjo,2042
142
+ novel_downloader/utils/crypto_utils.py,sha256=BRlVR9Nvbu8TQXocJb3po1-kD5HkbCV0yyT84nq_XtI,2052
143
+ novel_downloader/utils/hash_store.py,sha256=HfzthzcKbHbVaHNpqjaAs2wDeq7iIeY8Mzkt_4v80jQ,9388
144
+ novel_downloader/utils/hash_utils.py,sha256=7eC7WsO_kl25OnRYWzIXbCXsewxvCcvRCzetsf-wbTo,3023
145
+ novel_downloader/utils/i18n.py,sha256=pdAcSIA5Tp-uPEBwNByHL7C1NayTnpOsl7zFv9p2G1k,1033
146
+ novel_downloader/utils/logger.py,sha256=9h1iFS8_auiquNgOBd-Q2pbbcnAhAKL39yf3PKadu00,3339
147
+ novel_downloader/utils/model_loader.py,sha256=JKgRFrr4HlAW9zuDUBAuuo_Kk_T_g9dWiU8E3zYk0vo,1996
148
+ novel_downloader/utils/network.py,sha256=W0SVr55MSUjTmMPkUvvkH10SRgFx3GWrCN_fDavfk4A,9143
149
+ novel_downloader/utils/state.py,sha256=FcNJ85GvBu7uEIjy0QHGr4sXMbHPEMkCjwUKNg5EabI,5132
150
+ novel_downloader/utils/file_utils/__init__.py,sha256=zvOm2qSEmWd_mRGJceGBZb5MYMSDAlWYjS5MkVQNZgI,1159
151
+ novel_downloader/utils/file_utils/io.py,sha256=AZ3NUe6lifGsYt3iYyXyQ2BO41WV8j013LpZy4KSjJQ,7473
152
+ novel_downloader/utils/file_utils/normalize.py,sha256=MrsCq4FqmskKRkHRV_J0z0dmn69OerMum-9sqx2XOGM,2023
153
+ novel_downloader/utils/file_utils/sanitize.py,sha256=rE-u4vpDL10zH8FT8d9wqwWsz-7dR6PJ-LE45K8VaeE,2112
154
+ novel_downloader/utils/fontocr/__init__.py,sha256=fe-04om3xxBvFKt5BBCApXCzv-Z0K_AY7lv9IB1jEHM,543
155
+ novel_downloader/utils/fontocr/ocr_v1.py,sha256=bwkvqKXUcKsgBCrag8jHuIl8AOM_MbV28ZSQ_aO_X6s,11245
156
+ novel_downloader/utils/fontocr/ocr_v2.py,sha256=OJ_egORHa9KwVJX4kEABR25D7RRF0jVCRV4WgHUal7M,27307
157
+ novel_downloader/utils/text_utils/__init__.py,sha256=JQtEnJ22K9o_syY5PV4Bf_p4BKZonX5g9-A5eqlxpZs,806
158
+ novel_downloader/utils/text_utils/chapter_formatting.py,sha256=WAAEAcI7zI_uIeARDybZfXDdMvGio3VIkANFrK8-8Os,1378
159
+ novel_downloader/utils/text_utils/diff_display.py,sha256=aUvjMcYO-1_P8ZYiYbmYbJOByKo2bWoBV_ifRuAqwb8,2528
160
+ novel_downloader/utils/text_utils/font_mapping.py,sha256=Aos5skBhowDdPgnYmK0bpLtNm2hZg3RolNlTkxC9kO8,865
161
+ novel_downloader/utils/text_utils/text_cleaning.py,sha256=_Nahr8iv3341IyDXW-KpTn4XNG2hB-HajSM52pysPu8,1630
162
+ novel_downloader/utils/time_utils/__init__.py,sha256=725vY2PvqFhjbAz0hCOuIuhSCK8HrEqQ_k3YwvubmXo,624
163
+ novel_downloader/utils/time_utils/datetime_utils.py,sha256=1eyX8lTEqkQ-rEej_GrhbUpaIR5tsFdVSKT-q8s9X-g,4927
164
+ 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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
2
  novel-cli = novel_downloader.cli.main:cli_main
3
+ novel-tui = novel_downloader.tui.main:tui_main
@@ -1,66 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- novel_downloader.cli.interactive
4
- --------------------------------
5
-
6
- Interactive CLI mode for novel_downloader.
7
- Supports multilingual prompt, input validation, and quit control.
8
- """
9
-
10
- import click
11
- from click import Context
12
-
13
- from novel_downloader.cli.download import download_cli
14
- from novel_downloader.utils.i18n import t
15
-
16
-
17
- @click.group( # type: ignore
18
- name="interactive", help=t("interactive_help"), invoke_without_command=True
19
- )
20
- @click.pass_context # type: ignore
21
- def interactive_cli(ctx: Context) -> None:
22
- """Interactive mode for novel selection and preview."""
23
- if ctx.invoked_subcommand is None:
24
- click.echo(t("interactive_no_sub"))
25
-
26
- options = [
27
- t("interactive_option_download"),
28
- t("interactive_option_browse"),
29
- t("interactive_option_preview"),
30
- t("interactive_option_exit"),
31
- ]
32
- for idx, opt in enumerate(options, 1):
33
- click.echo(f"{idx}. {opt}")
34
-
35
- choice = click.prompt(t("interactive_prompt_choice"), type=int)
36
-
37
- if choice == 1:
38
- default_site = "qidian"
39
- site: str = click.prompt(
40
- t("download_option_site", default=default_site),
41
- default_site,
42
- )
43
- ids_input: str = click.prompt(t("interactive_prompt_book_ids"))
44
- book_ids = ids_input.strip().split()
45
- ctx.invoke(download_cli, book_ids=book_ids, site=site)
46
- elif choice == 2:
47
- ctx.invoke(browse)
48
- elif choice == 3:
49
- ctx.invoke(preview)
50
- else:
51
- click.echo(t("interactive_exit"))
52
- return
53
-
54
-
55
- @interactive_cli.command(help=t("interactive_browse_help")) # type: ignore
56
- @click.pass_context # type: ignore
57
- def browse(ctx: Context) -> None:
58
- """Browse available novels interactively."""
59
- click.echo(t("interactive_browse_start"))
60
-
61
-
62
- @interactive_cli.command(help=t("interactive_preview_help")) # type: ignore
63
- @click.pass_context # type: ignore
64
- def preview(ctx: Context) -> None:
65
- """Preview chapters before downloading."""
66
- click.echo(t("interactive_preview_start"))
@@ -1,177 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- novel_downloader.cli.settings
4
- -----------------------------
5
-
6
- Commands to configure novel downloader settings.
7
- """
8
-
9
- import shutil
10
- from importlib.resources import as_file
11
- from pathlib import Path
12
-
13
- import click
14
- from click import Context
15
-
16
- from novel_downloader.config import save_config_file, save_rules_as_json
17
- from novel_downloader.utils.constants import DEFAULT_SETTINGS_PATHS
18
- from novel_downloader.utils.i18n import t
19
- from novel_downloader.utils.logger import setup_logging
20
- from novel_downloader.utils.state import state_mgr
21
-
22
-
23
- @click.group(name="settings", help=t("settings_help")) # type: ignore
24
- def settings_cli() -> None:
25
- """Configure downloader settings."""
26
- setup_logging()
27
- pass
28
-
29
-
30
- @settings_cli.command(name="init", help=t("settings_init_help")) # type: ignore
31
- @click.option("--force", is_flag=True, help=t("settings_init_force_help")) # type: ignore
32
- def init_settings(force: bool) -> None:
33
- """Initialize default settings and rules in the current directory."""
34
- cwd = Path.cwd()
35
-
36
- for resource in DEFAULT_SETTINGS_PATHS:
37
- target_path = cwd / resource.name
38
- should_copy = True
39
-
40
- if target_path.exists():
41
- if force:
42
- should_copy = True
43
- click.echo(t("settings_init_overwrite", filename=resource.name))
44
- else:
45
- click.echo(t("settings_init_exists", filename=resource.name))
46
- should_copy = click.confirm(
47
- t("settings_init_confirm_overwrite", filename=resource.name),
48
- default=False,
49
- )
50
-
51
- if not should_copy:
52
- click.echo(t("settings_init_skip", filename=resource.name))
53
- continue
54
-
55
- try:
56
- with as_file(resource) as actual_path:
57
- shutil.copy(actual_path, target_path)
58
- click.echo(t("settings_init_copy", filename=resource.name))
59
- except Exception as e:
60
- raise click.ClickException(
61
- t("settings_init_error", filename=resource.name, err=e)
62
- ) from e
63
-
64
-
65
- @settings_cli.command(name="set-lang", help=t("settings_set_lang_help")) # type: ignore
66
- @click.argument("lang", type=click.Choice(["zh", "en"])) # type: ignore
67
- @click.pass_context # type: ignore
68
- def set_language(ctx: Context, lang: str) -> None:
69
- """Switch language between Chinese and English."""
70
- state_mgr.set_language(lang)
71
- click.echo(t("settings_set_lang", lang=lang))
72
-
73
-
74
- @settings_cli.command(name="set-config", help=t("settings_set_config_help")) # type: ignore
75
- @click.argument("path", type=click.Path(exists=True, dir_okay=False, resolve_path=True)) # type: ignore
76
- def set_config(path: str) -> None:
77
- """Set and save a custom YAML configuration file."""
78
- try:
79
- save_config_file(path)
80
- click.echo(t("settings_set_config", path=path))
81
- except Exception as e:
82
- raise click.ClickException(t("settings_set_config_fail", err=e)) from e
83
-
84
-
85
- @settings_cli.command(name="update-rules", help=t("settings_update_rules_help")) # type: ignore
86
- @click.argument("path", type=click.Path(exists=True, dir_okay=False, resolve_path=True)) # type: ignore
87
- def update_rules(path: str) -> None:
88
- """Update site rules from a TOML/YAML/JSON file."""
89
- try:
90
- save_rules_as_json(path)
91
- click.echo(t("settings_update_rules", path=path))
92
- except Exception as e:
93
- raise click.ClickException(t("settings_update_rules_fail", err=e)) from e
94
-
95
-
96
- @settings_cli.command(
97
- name="set-cookies", help=t("settings_set_cookies_help")
98
- ) # type: ignore
99
- @click.argument("site", required=False) # type: ignore
100
- @click.argument("cookies", required=False) # type: ignore
101
- @click.pass_context # type: ignore
102
- def set_cookies(ctx: Context, site: str, cookies: str) -> None:
103
- """
104
- Set or update cookies for a site.
105
-
106
- :param site: Site identifier (e.g. 'qidian', 'bqg').
107
- If omitted, you will be prompted to enter it.
108
- :param cookies: Cookie payload. Can be a JSON string (e.g. '{"k":"v"}')
109
- or a browser-style string 'k1=v1; k2=v2'.
110
- If omitted, you will be prompted to enter it.
111
- """
112
- if not site:
113
- site = click.prompt(t("settings_set_cookies_prompt_site"), type=str)
114
- if not cookies:
115
- cookies = click.prompt(t("settings_set_cookies_prompt_payload"), type=str)
116
-
117
- try:
118
- state_mgr.set_cookies(site, cookies)
119
- click.echo(t("settings_set_cookies_success", site=site))
120
- except Exception as e:
121
- raise click.ClickException(t("settings_set_cookies_fail", err=e)) from e
122
-
123
-
124
- @settings_cli.command(name="add-hash", help=t("settings_add_hash_help")) # type: ignore
125
- @click.option(
126
- "--path",
127
- type=click.Path(exists=True, dir_okay=False),
128
- help=t("settings_add_hash_path_help"),
129
- ) # type: ignore
130
- def add_image_hashes(path: str | None) -> None:
131
- """
132
- Add image hashes to internal store for matching.
133
- Can be run in interactive mode (no --path), or with a JSON file.
134
- """
135
- from novel_downloader.utils.hash_store import img_hash_store
136
-
137
- if path:
138
- try:
139
- img_hash_store.add_from_map(path)
140
- img_hash_store.save()
141
- click.echo(t("settings_add_hash_loaded", path=path))
142
- except Exception as e:
143
- raise click.ClickException(
144
- t("settings_add_hash_load_fail", err=str(e))
145
- ) from e
146
- else:
147
- click.echo(t("settings_add_hash_prompt_tip"))
148
- while True:
149
- img_path = click.prompt(
150
- t("settings_add_hash_prompt_img"),
151
- type=str,
152
- default="",
153
- show_default=False,
154
- ).strip()
155
- if not img_path or img_path.lower() in {"exit", "quit"}:
156
- break
157
- if not Path(img_path).exists():
158
- click.echo(t("settings_add_hash_path_invalid"))
159
- continue
160
-
161
- label = click.prompt(
162
- t("settings_add_hash_prompt_label"),
163
- type=str,
164
- default="",
165
- show_default=False,
166
- ).strip()
167
- if not label or label.lower() in {"exit", "quit"}:
168
- break
169
-
170
- try:
171
- img_hash_store.add_image(img_path, label)
172
- click.echo(t("settings_add_hash_added", img=img_path, label=label))
173
- except Exception as e:
174
- click.echo(t("settings_add_hash_failed", err=str(e)))
175
-
176
- img_hash_store.save()
177
- click.echo(t("settings_add_hash_saved"))
@@ -1,187 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- novel_downloader.config.models
4
- ------------------------------
5
-
6
- Defines structured configuration models using dataclasses for each
7
- major component in the novel_downloader pipeline.
8
-
9
- Each config section corresponds to a specific stage of the pipeline:
10
- - RequesterConfig: network settings for requests and DrissionPage
11
- - DownloaderConfig: chapter download behavior and local raw data paths
12
- - ParserConfig: font decoding, cache handling, and debug options
13
- - SaverConfig: output formatting, export formats, and filename templates
14
-
15
- These models are used to map loaded YAML or JSON config data into
16
- strongly typed Python objects for safer and cleaner access.
17
- """
18
-
19
- from dataclasses import dataclass
20
- from typing import Any, Literal, TypedDict
21
-
22
- ModeType = Literal["browser", "session", "async"]
23
- StorageBackend = Literal["json", "sqlite"]
24
-
25
-
26
- # === Requesters ===
27
- @dataclass
28
- class RequesterConfig:
29
- retry_times: int = 3
30
- backoff_factor: float = 2.0
31
- timeout: float = 30.0
32
- headless: bool = True
33
- user_data_folder: str = ""
34
- profile_name: str = ""
35
- auto_close: bool = True
36
- disable_images: bool = True
37
- mute_audio: bool = True
38
- mode: ModeType = "session"
39
- max_connections: int = 10
40
- max_rps: float | None = None # Maximum requests per second
41
- username: str = ""
42
- password: str = ""
43
-
44
-
45
- # === Downloaders ===
46
- @dataclass
47
- class DownloaderConfig:
48
- request_interval: float = 5.0
49
- raw_data_dir: str = "./raw_data"
50
- cache_dir: str = "./novel_cache"
51
- download_workers: int = 4
52
- parser_workers: int = 4
53
- use_process_pool: bool = False
54
- skip_existing: bool = True
55
- login_required: bool = False
56
- save_html: bool = False
57
- mode: ModeType = "session"
58
- storage_backend: StorageBackend = "json"
59
- storage_batch_size: int = 1
60
-
61
-
62
- # === Parsers ===
63
- @dataclass
64
- class ParserConfig:
65
- cache_dir: str = "./novel_cache"
66
- decode_font: bool = False
67
- use_freq: bool = False
68
- use_ocr: bool = True
69
- use_vec: bool = False
70
- ocr_version: str = "v1.0"
71
- batch_size: int = 32
72
- gpu_mem: int = 500
73
- gpu_id: int | None = None
74
- ocr_weight: float = 0.6
75
- vec_weight: float = 0.4
76
- save_font_debug: bool = False
77
- mode: ModeType = "session"
78
-
79
-
80
- # === Savers ===
81
- @dataclass
82
- class SaverConfig:
83
- cache_dir: str = "./novel_cache"
84
- raw_data_dir: str = "./raw_data"
85
- output_dir: str = "./downloads"
86
- storage_backend: StorageBackend = "json"
87
- clean_text: bool = True
88
- make_txt: bool = True
89
- make_epub: bool = False
90
- make_md: bool = False
91
- make_pdf: bool = False
92
- append_timestamp: bool = True
93
- filename_template: str = "{title}_{author}"
94
- include_cover: bool = True
95
- include_toc: bool = False
96
- include_picture: bool = False
97
-
98
-
99
- class RuleStep(TypedDict, total=False):
100
- # —— 操作类型 —— #
101
- type: Literal[
102
- "attr",
103
- "select_one",
104
- "select",
105
- "find",
106
- "find_all",
107
- "exclude",
108
- "regex",
109
- "text",
110
- "strip",
111
- "replace",
112
- "split",
113
- "join",
114
- ]
115
-
116
- # —— BeautifulSoup 相关 —— #
117
- selector: str | None # CSS 选择器, 用于 select/select_one/exclude
118
- name: str | None # 标签名称, 用于 find/find_all
119
- attrs: dict[str, Any] | None # 属性过滤, 用于 find/find_all
120
- limit: int | None # find_all 的最大匹配数
121
- attr: str | None # 从元素获取属性值 (select/select_one/select_all)
122
-
123
- # —— 正则相关 —— #
124
- pattern: str | None # 正则表达式
125
- flags: int | None # re.I, re.M 等
126
- group: int | None # 匹配结果中的第几个分组 (默认 0)
127
- template: str | None # 自定义组合, 比如 "$1$2字"
128
-
129
- # —— 文本处理 —— #
130
- chars: str | None # strip 要去除的字符集
131
- old: str | None # replace 中要被替换的子串
132
- new: str | None # replace 中新的子串
133
- count: int | None # replace 中的最大替换次数
134
- sep: str | None # split/join 的分隔符
135
- index: int | None # split/select_all/select 之后取第几个元素
136
-
137
-
138
- class FieldRules(TypedDict):
139
- steps: list[RuleStep]
140
-
141
-
142
- class ChapterFieldRules(TypedDict):
143
- key: str
144
- steps: list[RuleStep]
145
-
146
-
147
- class VolumesRulesOptional(TypedDict, total=False):
148
- volume_selector: str # 有卷时选择 volume 块的 selector
149
- volume_name_steps: list[RuleStep]
150
- volume_mode: str # Optional: "normal" (default) or "mixed"
151
- list_selector: str # Optional: If "mixed" mode, parent container selector
152
-
153
-
154
- class VolumesRules(VolumesRulesOptional):
155
- has_volume: bool # 是否存在卷,false=未分卷
156
- chapter_selector: str # 选择 chapter 节点的 selector
157
- chapter_steps: list[ChapterFieldRules] # 提取章节信息的步骤列表
158
-
159
-
160
- class BookInfoRules(TypedDict, total=False):
161
- book_name: FieldRules
162
- author: FieldRules
163
- cover_url: FieldRules
164
- update_time: FieldRules
165
- serial_status: FieldRules
166
- word_count: FieldRules
167
- summary: FieldRules
168
- volumes: VolumesRules
169
-
170
-
171
- class ChapterRules(TypedDict, total=False):
172
- title: FieldRules
173
- content: FieldRules
174
-
175
-
176
- class SiteProfile(TypedDict):
177
- book_info_url: str
178
- chapter_url: str
179
-
180
-
181
- class SiteRules(TypedDict):
182
- profile: SiteProfile
183
- book_info: BookInfoRules
184
- chapter: ChapterRules
185
-
186
-
187
- SiteRulesDict = dict[str, SiteRules]
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- novel_downloader.core.downloaders.base
4
- --------------------------------------
5
-
6
- """
7
-
8
- from .base_async import BaseAsyncDownloader
9
- from .base_sync import BaseDownloader
10
-
11
- __all__ = [
12
- "BaseAsyncDownloader",
13
- "BaseDownloader",
14
- ]