novel-downloader 2.0.1__tar.gz → 2.0.2__tar.gz

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 (225) hide show
  1. {novel_downloader-2.0.1/src/novel_downloader.egg-info → novel_downloader-2.0.2}/PKG-INFO +4 -1
  2. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/README.md +3 -0
  3. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/pyproject.toml +1 -1
  4. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/__init__.py +1 -1
  5. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/cli/download.py +11 -8
  6. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/cli/export.py +17 -17
  7. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/cli/ui.py +28 -1
  8. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/config/adapter.py +27 -1
  9. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/archived/deqixs/fetcher.py +1 -28
  10. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/downloaders/__init__.py +2 -0
  11. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/downloaders/base.py +34 -85
  12. novel_downloader-2.0.2/src/novel_downloader/core/downloaders/common.py +257 -0
  13. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/downloaders/qianbi.py +30 -64
  14. novel_downloader-2.0.2/src/novel_downloader/core/downloaders/qidian.py +309 -0
  15. novel_downloader-2.0.2/src/novel_downloader/core/downloaders/qqbook.py +292 -0
  16. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/downloaders/registry.py +2 -2
  17. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/exporters/__init__.py +2 -0
  18. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/exporters/base.py +37 -59
  19. novel_downloader-2.0.2/src/novel_downloader/core/exporters/common.py +620 -0
  20. novel_downloader-2.0.2/src/novel_downloader/core/exporters/linovelib.py +47 -0
  21. novel_downloader-2.0.2/src/novel_downloader/core/exporters/qidian.py +61 -0
  22. novel_downloader-2.0.2/src/novel_downloader/core/exporters/qqbook.py +28 -0
  23. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/exporters/registry.py +2 -2
  24. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/__init__.py +4 -2
  25. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/aaatxt.py +2 -22
  26. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/b520.py +3 -23
  27. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/base.py +80 -105
  28. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/biquyuedu.py +2 -22
  29. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/dxmwx.py +10 -22
  30. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/esjzone.py +6 -29
  31. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/guidaye.py +2 -22
  32. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/hetushu.py +9 -29
  33. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/i25zw.py +2 -16
  34. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/ixdzs8.py +2 -16
  35. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/jpxs123.py +2 -16
  36. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/lewenn.py +2 -22
  37. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/linovelib.py +4 -20
  38. novel_downloader-2.0.1/src/novel_downloader/core/fetchers/eightnovel.py → novel_downloader-2.0.2/src/novel_downloader/core/fetchers/n8novel.py +12 -40
  39. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/piaotia.py +2 -16
  40. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/qbtr.py +2 -16
  41. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/qianbi.py +1 -20
  42. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/qidian.py +7 -33
  43. novel_downloader-2.0.2/src/novel_downloader/core/fetchers/qqbook.py +177 -0
  44. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/quanben5.py +9 -29
  45. novel_downloader-2.0.2/src/novel_downloader/core/fetchers/rate_limiter.py +55 -0
  46. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/sfacg.py +3 -16
  47. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/shencou.py +2 -16
  48. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/shuhaige.py +2 -22
  49. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/tongrenquan.py +2 -22
  50. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/ttkan.py +3 -14
  51. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/wanbengo.py +2 -22
  52. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/xiaoshuowu.py +2 -16
  53. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/xiguashuwu.py +4 -20
  54. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/xs63b.py +3 -15
  55. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/xshbook.py +2 -22
  56. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/yamibo.py +4 -28
  57. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/yibige.py +13 -26
  58. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/interfaces/exporter.py +19 -7
  59. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/interfaces/fetcher.py +21 -47
  60. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/__init__.py +4 -2
  61. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/b520.py +2 -2
  62. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/base.py +4 -39
  63. novel_downloader-2.0.1/src/novel_downloader/core/parsers/eightnovel.py → novel_downloader-2.0.2/src/novel_downloader/core/parsers/n8novel.py +5 -5
  64. novel_downloader-2.0.1/src/novel_downloader/core/parsers/qidian/main_parser.py → novel_downloader-2.0.2/src/novel_downloader/core/parsers/qidian.py +147 -266
  65. novel_downloader-2.0.2/src/novel_downloader/core/parsers/qqbook.py +709 -0
  66. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/xiguashuwu.py +3 -4
  67. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/__init__.py +2 -2
  68. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/b520.py +1 -1
  69. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/base.py +2 -2
  70. novel_downloader-2.0.1/src/novel_downloader/core/searchers/eightnovel.py → novel_downloader-2.0.2/src/novel_downloader/core/searchers/n8novel.py +5 -5
  71. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/models/__init__.py +2 -0
  72. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/models/book.py +1 -0
  73. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/models/config.py +12 -0
  74. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/resources/config/settings.toml +23 -5
  75. novel_downloader-2.0.2/src/novel_downloader/resources/js_scripts/expr_to_json.js +14 -0
  76. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/resources/js_scripts/qidian_decrypt_node.js +21 -16
  77. novel_downloader-2.0.2/src/novel_downloader/resources/js_scripts/qq_decrypt_node.js +92 -0
  78. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/constants.py +6 -0
  79. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/crypto_utils/aes_util.py +1 -1
  80. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/epub/constants.py +1 -6
  81. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/fontocr/core.py +2 -0
  82. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/fontocr/loader.py +10 -8
  83. novel_downloader-2.0.2/src/novel_downloader/utils/node_decryptor/__init__.py +13 -0
  84. novel_downloader-2.0.2/src/novel_downloader/utils/node_decryptor/decryptor.py +342 -0
  85. {novel_downloader-2.0.1/src/novel_downloader/core/parsers/qidian/utils → novel_downloader-2.0.2/src/novel_downloader/utils/node_decryptor}/decryptor_fetcher.py +5 -6
  86. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/web/pages/download.py +1 -1
  87. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/web/pages/search.py +1 -1
  88. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/web/services/task_manager.py +2 -0
  89. {novel_downloader-2.0.1 → novel_downloader-2.0.2/src/novel_downloader.egg-info}/PKG-INFO +4 -1
  90. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader.egg-info/SOURCES.txt +15 -18
  91. novel_downloader-2.0.1/src/novel_downloader/core/downloaders/common.py +0 -281
  92. novel_downloader-2.0.1/src/novel_downloader/core/downloaders/qidian.py +0 -336
  93. novel_downloader-2.0.1/src/novel_downloader/core/exporters/common/__init__.py +0 -11
  94. novel_downloader-2.0.1/src/novel_downloader/core/exporters/common/epub.py +0 -198
  95. novel_downloader-2.0.1/src/novel_downloader/core/exporters/common/main_exporter.py +0 -64
  96. novel_downloader-2.0.1/src/novel_downloader/core/exporters/common/txt.py +0 -146
  97. novel_downloader-2.0.1/src/novel_downloader/core/exporters/epub_util.py +0 -215
  98. novel_downloader-2.0.1/src/novel_downloader/core/exporters/linovelib/__init__.py +0 -11
  99. novel_downloader-2.0.1/src/novel_downloader/core/exporters/linovelib/epub.py +0 -349
  100. novel_downloader-2.0.1/src/novel_downloader/core/exporters/linovelib/main_exporter.py +0 -66
  101. novel_downloader-2.0.1/src/novel_downloader/core/exporters/linovelib/txt.py +0 -139
  102. novel_downloader-2.0.1/src/novel_downloader/core/exporters/qidian.py +0 -32
  103. novel_downloader-2.0.1/src/novel_downloader/core/exporters/txt_util.py +0 -67
  104. novel_downloader-2.0.1/src/novel_downloader/core/fetchers/rate_limiter.py +0 -86
  105. novel_downloader-2.0.1/src/novel_downloader/core/parsers/qidian/__init__.py +0 -10
  106. novel_downloader-2.0.1/src/novel_downloader/core/parsers/qidian/utils/__init__.py +0 -11
  107. novel_downloader-2.0.1/src/novel_downloader/core/parsers/qidian/utils/node_decryptor.py +0 -175
  108. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/LICENSE +0 -0
  109. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/setup.cfg +0 -0
  110. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/cli/__init__.py +0 -0
  111. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/cli/clean.py +0 -0
  112. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/cli/config.py +0 -0
  113. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/cli/main.py +0 -0
  114. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/cli/search.py +0 -0
  115. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/config/__init__.py +0 -0
  116. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/config/file_io.py +0 -0
  117. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/__init__.py +0 -0
  118. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/archived/deqixs/parser.py +0 -0
  119. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/archived/deqixs/searcher.py +0 -0
  120. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/archived/qidian/searcher.py +0 -0
  121. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/archived/wanbengo/searcher.py +0 -0
  122. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/archived/xshbook/searcher.py +0 -0
  123. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/downloaders/signals.py +0 -0
  124. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/fetchers/registry.py +0 -0
  125. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/interfaces/__init__.py +0 -0
  126. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/interfaces/downloader.py +0 -0
  127. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/interfaces/parser.py +0 -0
  128. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/interfaces/searcher.py +0 -0
  129. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/aaatxt.py +0 -0
  130. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/biquyuedu.py +0 -0
  131. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/dxmwx.py +0 -0
  132. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/esjzone.py +0 -0
  133. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/guidaye.py +0 -0
  134. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/hetushu.py +0 -0
  135. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/i25zw.py +0 -0
  136. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/ixdzs8.py +0 -0
  137. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/jpxs123.py +0 -0
  138. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/lewenn.py +0 -0
  139. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/linovelib.py +0 -0
  140. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/piaotia.py +0 -0
  141. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/qbtr.py +0 -0
  142. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/qianbi.py +0 -0
  143. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/quanben5.py +0 -0
  144. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/registry.py +0 -0
  145. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/sfacg.py +0 -0
  146. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/shencou.py +0 -0
  147. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/shuhaige.py +0 -0
  148. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/tongrenquan.py +0 -0
  149. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/ttkan.py +0 -0
  150. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/wanbengo.py +0 -0
  151. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/xiaoshuowu.py +0 -0
  152. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/xs63b.py +0 -0
  153. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/xshbook.py +0 -0
  154. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/yamibo.py +0 -0
  155. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/parsers/yibige.py +0 -0
  156. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/aaatxt.py +0 -0
  157. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/dxmwx.py +0 -0
  158. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/esjzone.py +0 -0
  159. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/hetushu.py +0 -0
  160. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/i25zw.py +0 -0
  161. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/ixdzs8.py +0 -0
  162. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/jpxs123.py +0 -0
  163. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/piaotia.py +0 -0
  164. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/qbtr.py +0 -0
  165. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/qianbi.py +0 -0
  166. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/quanben5.py +0 -0
  167. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/registry.py +0 -0
  168. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/shuhaige.py +0 -0
  169. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/tongrenquan.py +0 -0
  170. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/ttkan.py +0 -0
  171. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/xiaoshuowu.py +0 -0
  172. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/xiguashuwu.py +0 -0
  173. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/core/searchers/xs63b.py +0 -0
  174. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/locales/en.json +0 -0
  175. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/locales/zh.json +0 -0
  176. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/models/login.py +0 -0
  177. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/models/search.py +0 -0
  178. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/resources/css_styles/intro.css +0 -0
  179. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/resources/css_styles/main.css +0 -0
  180. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/resources/images/volume_border.png +0 -0
  181. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/resources/json/linovelib_font_map.json +0 -0
  182. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/resources/json/xiguashuwu.json +0 -0
  183. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/__init__.py +0 -0
  184. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/chapter_storage.py +0 -0
  185. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/cookies.py +0 -0
  186. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/crypto_utils/__init__.py +0 -0
  187. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/crypto_utils/aes_v1.py +0 -0
  188. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/crypto_utils/aes_v2.py +0 -0
  189. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/crypto_utils/rc4.py +0 -0
  190. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/epub/__init__.py +0 -0
  191. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/epub/builder.py +0 -0
  192. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/epub/documents.py +0 -0
  193. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/epub/models.py +0 -0
  194. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/epub/utils.py +0 -0
  195. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/file_utils/__init__.py +0 -0
  196. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/file_utils/io.py +0 -0
  197. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/file_utils/normalize.py +0 -0
  198. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/file_utils/sanitize.py +0 -0
  199. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/fontocr/__init__.py +0 -0
  200. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/i18n.py +0 -0
  201. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/logger.py +0 -0
  202. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/network.py +0 -0
  203. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/state.py +0 -0
  204. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/text_utils/__init__.py +0 -0
  205. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/text_utils/diff_display.py +0 -0
  206. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/text_utils/numeric_conversion.py +0 -0
  207. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/text_utils/text_cleaner.py +0 -0
  208. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/text_utils/truncate_utils.py +0 -0
  209. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/time_utils/__init__.py +0 -0
  210. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/time_utils/datetime_utils.py +0 -0
  211. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/utils/time_utils/sleep_utils.py +0 -0
  212. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/web/__init__.py +0 -0
  213. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/web/components/__init__.py +0 -0
  214. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/web/components/navigation.py +0 -0
  215. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/web/main.py +0 -0
  216. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/web/pages/__init__.py +0 -0
  217. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/web/pages/progress.py +0 -0
  218. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/web/services/__init__.py +0 -0
  219. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/web/services/client_dialog.py +0 -0
  220. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/web/services/cred_broker.py +0 -0
  221. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader/web/services/cred_models.py +0 -0
  222. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader.egg-info/dependency_links.txt +0 -0
  223. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader.egg-info/entry_points.txt +0 -0
  224. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader.egg-info/requires.txt +0 -0
  225. {novel_downloader-2.0.1 → novel_downloader-2.0.2}/src/novel_downloader.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: novel-downloader
3
- Version: 2.0.1
3
+ Version: 2.0.2
4
4
  Summary: A command-line tool for downloading Chinese web novels from Qidian and similar platforms.
5
5
  Author-email: Saudade Z <saudadez217@gmail.com>
6
6
  License: MIT License
@@ -123,6 +123,7 @@ novel-cli download 123456
123
123
 
124
124
  * 支持站点见: [支持站点列表](https://github.com/saudadez21/novel-downloader/blob/main/docs/4-supported-sites.md)
125
125
  * 更多示例见: [CLI 使用示例](https://github.com/saudadez21/novel-downloader/blob/main/docs/5-cli-usage-examples.md)
126
+ * 运行中可使用 `CTRL+C` 取消任务
126
127
 
127
128
  ### 3. 图形界面 (GUI / Web)
128
129
 
@@ -134,6 +135,8 @@ novel-web
134
135
  # novel-web --listen public
135
136
  ```
136
137
 
138
+ * 运行中可使用 `CTRL+C` 停止服务
139
+
137
140
  ---
138
141
 
139
142
  ## 从源码安装 (开发版)
@@ -56,6 +56,7 @@ novel-cli download 123456
56
56
 
57
57
  * 支持站点见: [支持站点列表](https://github.com/saudadez21/novel-downloader/blob/main/docs/4-supported-sites.md)
58
58
  * 更多示例见: [CLI 使用示例](https://github.com/saudadez21/novel-downloader/blob/main/docs/5-cli-usage-examples.md)
59
+ * 运行中可使用 `CTRL+C` 取消任务
59
60
 
60
61
  ### 3. 图形界面 (GUI / Web)
61
62
 
@@ -67,6 +68,8 @@ novel-web
67
68
  # novel-web --listen public
68
69
  ```
69
70
 
71
+ * 运行中可使用 `CTRL+C` 停止服务
72
+
70
73
  ---
71
74
 
72
75
  ## 从源码安装 (开发版)
@@ -111,7 +111,7 @@ testpaths = ["tests"]
111
111
 
112
112
  [tool.commitizen]
113
113
  name = "cz_conventional_commits"
114
- version = "2.0.1"
114
+ version = "2.0.2"
115
115
  version_files = [
116
116
  "src/novel_downloader/__init__.py"
117
117
  ]
@@ -6,7 +6,7 @@ novel_downloader
6
6
  Core package for the Novel Downloader project.
7
7
  """
8
8
 
9
- __version__ = "2.0.1"
9
+ __version__ = "2.0.2"
10
10
 
11
11
  __author__ = "Saudade Z"
12
12
  __email__ = "saudadez217@gmail.com"
@@ -178,7 +178,14 @@ async def _download(
178
178
 
179
179
  for book in valid_books:
180
180
  ui.info(t("download_downloading", book_id=book["book_id"], site=site))
181
- await downloader.download(book, progress_hook=_print_progress)
181
+
182
+ hook, close = ui.create_progress_hook(
183
+ prefix=t("download_progress_prefix"), unit="chapters"
184
+ )
185
+ try:
186
+ await downloader.download(book, progress_hook=hook)
187
+ finally:
188
+ close()
182
189
 
183
190
  if not no_export and exporter is not None:
184
191
  await asyncio.to_thread(exporter.export, book["book_id"])
@@ -186,6 +193,9 @@ async def _download(
186
193
  if downloader_cfg.login_required and fetcher.is_logged_in:
187
194
  await fetcher.save_state()
188
195
 
196
+ if exporter is not None:
197
+ exporter.close()
198
+
189
199
 
190
200
  async def _prompt_login_fields(
191
201
  fields: list[LoginField],
@@ -235,10 +245,3 @@ async def _prompt_login_fields(
235
245
  result[field.name] = value
236
246
 
237
247
  return result
238
-
239
-
240
- async def _print_progress(done: int, total: int) -> None:
241
- """Progress hook passed into the downloader."""
242
- ui.print_progress(
243
- done, total, prefix=t("download_progress_prefix"), unit="chapters"
244
- )
@@ -55,22 +55,22 @@ def handle_export(args: Namespace) -> None:
55
55
  adapter = ConfigAdapter(config=config_data, site=site)
56
56
  exporter_cfg = adapter.get_exporter_config()
57
57
  log_level = adapter.get_log_level()
58
- exporter = get_exporter(site, exporter_cfg)
59
58
  setup_logging(console_level=log_level)
60
59
 
61
- for book_id in book_ids:
62
- ui.info(t("export_processing", book_id=book_id, format=export_format))
63
-
64
- if export_format in {"txt", "all"}:
65
- try:
66
- exporter.export_as_txt(book_id)
67
- ui.success(t("export_success_txt", book_id=book_id))
68
- except Exception as e:
69
- ui.error(t("export_failed_txt", book_id=book_id, err=str(e)))
70
-
71
- if export_format in {"epub", "all"}:
72
- try:
73
- exporter.export_as_epub(book_id)
74
- ui.success(t("export_success_epub", book_id=book_id))
75
- except Exception as e:
76
- ui.error(t("export_failed_epub", book_id=book_id, err=str(e)))
60
+ with get_exporter(site, exporter_cfg) as exporter:
61
+ for book_id in book_ids:
62
+ ui.info(t("export_processing", book_id=book_id, format=export_format))
63
+
64
+ if export_format in {"txt", "all"}:
65
+ try:
66
+ exporter.export_as_txt(book_id)
67
+ ui.success(t("export_success_txt", book_id=book_id))
68
+ except Exception as e:
69
+ ui.error(t("export_failed_txt", book_id=book_id, err=str(e)))
70
+
71
+ if export_format in {"epub", "all"}:
72
+ try:
73
+ exporter.export_as_epub(book_id)
74
+ ui.success(t("export_success_epub", book_id=book_id))
75
+ except Exception as e:
76
+ ui.error(t("export_failed_epub", book_id=book_id, err=str(e)))
@@ -17,9 +17,10 @@ Public API:
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- from collections.abc import Iterable, Sequence
20
+ from collections.abc import Awaitable, Callable, Iterable, Sequence
21
21
 
22
22
  from rich.console import Console
23
+ from rich.progress import Progress, TaskID
23
24
  from rich.prompt import Confirm, Prompt
24
25
  from rich.table import Table
25
26
 
@@ -154,3 +155,29 @@ def print_progress(
154
155
  total = max(1, total)
155
156
  pct = done / total * 100.0
156
157
  _CONSOLE.print(f"[dim]{prefix}[/] {done}/{total} {unit} ({pct:.2f}%)")
158
+
159
+
160
+ def create_progress_hook(
161
+ prefix: str = "Progress",
162
+ unit: str = "item",
163
+ ) -> tuple[Callable[[int, int], Awaitable[None]], Callable[[], None]]:
164
+ progress = Progress(console=_CONSOLE)
165
+ task_id: TaskID | None = None
166
+
167
+ async def hook(done: int, total: int) -> None:
168
+ nonlocal task_id
169
+ if task_id is None:
170
+ progress.start()
171
+ task_id = progress.add_task(f"[cyan]{prefix}[/]", total=max(1, total))
172
+
173
+ progress.update(
174
+ task_id,
175
+ completed=done,
176
+ total=max(1, total),
177
+ description=f"{prefix} ({done}/{total} {unit})",
178
+ )
179
+
180
+ def close() -> None:
181
+ progress.stop()
182
+
183
+ return hook, close
@@ -17,6 +17,7 @@ from novel_downloader.models import (
17
17
  DownloaderConfig,
18
18
  ExporterConfig,
19
19
  FetcherConfig,
20
+ FontOCRConfig,
20
21
  ParserConfig,
21
22
  TextCleanerConfig,
22
23
  )
@@ -97,13 +98,16 @@ class ConfigAdapter:
97
98
  """
98
99
  g = self._gen_cfg
99
100
  s = self._site_cfg
100
- font_ocr = g.get("font_ocr") or {}
101
+ g_font = g.get("font_ocr") or {}
102
+ s_font = s.get("font_ocr") or {}
103
+ font_ocr: dict[str, Any] = {**g_font, **s_font}
101
104
  return ParserConfig(
102
105
  cache_dir=g.get("cache_dir", "./novel_cache"),
103
106
  use_truncation=bool(s.get("use_truncation", True)),
104
107
  decode_font=bool(font_ocr.get("decode_font", False)),
105
108
  save_font_debug=bool(font_ocr.get("save_font_debug", False)),
106
109
  batch_size=int(font_ocr.get("batch_size", 32)),
110
+ fontocr_cfg=self._dict_to_fontocr_cfg(font_ocr),
107
111
  )
108
112
 
109
113
  def get_exporter_config(self) -> ExporterConfig:
@@ -283,6 +287,28 @@ class ConfigAdapter:
283
287
  out["ignore_ids"] = [str(x) for x in data["ignore_ids"]]
284
288
  return out
285
289
 
290
+ @staticmethod
291
+ def _dict_to_fontocr_cfg(data: dict[str, Any]) -> FontOCRConfig:
292
+ """
293
+ Convert a raw ``font_ocr`` dict into a :class:`FontOCRConfig`.
294
+ """
295
+ if not isinstance(data, dict):
296
+ return FontOCRConfig()
297
+
298
+ ishape = data.get("input_shape")
299
+ if isinstance(ishape, list):
300
+ ishape = tuple(ishape) # [C, H, W] -> (C, H, W)
301
+
302
+ return FontOCRConfig(
303
+ model_name=data.get("model_name"),
304
+ model_dir=data.get("model_dir"),
305
+ input_shape=ishape,
306
+ device=data.get("device"),
307
+ precision=data.get("precision", "fp32"),
308
+ cpu_threads=data.get("cpu_threads", 10),
309
+ enable_hpi=data.get("enable_hpi", False),
310
+ )
311
+
286
312
  @classmethod
287
313
  def _dict_to_cleaner_cfg(cls, cfg: dict[str, Any]) -> TextCleanerConfig:
288
314
  """
@@ -8,8 +8,6 @@ novel_downloader.core.archived.deqixs.fetcher
8
8
  from typing import Any
9
9
 
10
10
  from novel_downloader.core.fetchers.base import BaseSession
11
- from novel_downloader.models import FetcherConfig
12
- from novel_downloader.utils import async_jitter_sleep
13
11
 
14
12
  # from novel_downloader.core.fetchers.registry import register_fetcher
15
13
 
@@ -26,25 +24,11 @@ class DeqixsSession(BaseSession):
26
24
  BOOK_INFO_URL = "https://www.deqixs.com/xiaoshuo/{book_id}/"
27
25
  CHAPTER_URL = "https://www.deqixs.com/xiaoshuo/{book_id}/{chapter_id}.html"
28
26
 
29
- def __init__(
30
- self,
31
- config: FetcherConfig,
32
- cookies: dict[str, str] | None = None,
33
- **kwargs: Any,
34
- ) -> None:
35
- super().__init__("deqixs", config, cookies, **kwargs)
36
-
37
27
  async def get_book_info(
38
28
  self,
39
29
  book_id: str,
40
30
  **kwargs: Any,
41
31
  ) -> list[str]:
42
- """
43
- Fetch the raw HTML of the book info page asynchronously.
44
-
45
- :param book_id: The book identifier.
46
- :return: The page content as a string.
47
- """
48
32
  url = self.book_info_url(book_id=book_id)
49
33
  return [await self.fetch(url, **kwargs)]
50
34
 
@@ -54,13 +38,6 @@ class DeqixsSession(BaseSession):
54
38
  chapter_id: str,
55
39
  **kwargs: Any,
56
40
  ) -> list[str]:
57
- """
58
- Fetch the raw HTML of a single chapter asynchronously.
59
-
60
- :param book_id: The book identifier.
61
- :param chapter_id: The chapter identifier.
62
- :return: The chapter content as a string.
63
- """
64
41
  html_pages: list[str] = []
65
42
  idx = 1
66
43
 
@@ -85,11 +62,7 @@ class DeqixsSession(BaseSession):
85
62
 
86
63
  html_pages.append(html)
87
64
  idx += 1
88
- await async_jitter_sleep(
89
- self.request_interval,
90
- mul_spread=1.1,
91
- max_sleep=self.request_interval + 2,
92
- )
65
+ await self._sleep()
93
66
 
94
67
  return html_pages
95
68
 
@@ -11,9 +11,11 @@ __all__ = [
11
11
  "CommonDownloader",
12
12
  "QianbiDownloader",
13
13
  "QidianDownloader",
14
+ "QqbookDownloader",
14
15
  ]
15
16
 
16
17
  from .common import CommonDownloader
17
18
  from .qianbi import QianbiDownloader
18
19
  from .qidian import QidianDownloader
20
+ from .qqbook import QqbookDownloader
19
21
  from .registry import get_downloader
@@ -10,15 +10,11 @@ import abc
10
10
  import asyncio
11
11
  import json
12
12
  import logging
13
- from collections.abc import AsyncIterator, Awaitable, Callable, Sequence
13
+ from collections.abc import Awaitable, Callable, Sequence
14
14
  from pathlib import Path
15
- from typing import Any, cast
15
+ from typing import Any, ClassVar, cast
16
16
 
17
- from novel_downloader.core.interfaces import (
18
- DownloaderProtocol,
19
- FetcherProtocol,
20
- ParserProtocol,
21
- )
17
+ from novel_downloader.core.interfaces import FetcherProtocol, ParserProtocol
22
18
  from novel_downloader.models import (
23
19
  BookConfig,
24
20
  BookInfoDict,
@@ -28,7 +24,7 @@ from novel_downloader.models import (
28
24
  from novel_downloader.utils import time_diff
29
25
 
30
26
 
31
- class BaseDownloader(DownloaderProtocol, abc.ABC):
27
+ class BaseDownloader(abc.ABC):
32
28
  """
33
29
  Abstract base class for novel downloaders.
34
30
 
@@ -39,8 +35,8 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
39
35
  a single book, using the provided fetcher and parser components.
40
36
  """
41
37
 
42
- DEFAULT_SOURCE_ID = 0
43
- PRIORITIES_MAP = {
38
+ DEFAULT_SOURCE_ID: ClassVar[int] = 0
39
+ PRIORITIES_MAP: ClassVar[dict[int, int]] = {
44
40
  DEFAULT_SOURCE_ID: 0,
45
41
  }
46
42
 
@@ -61,15 +57,23 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
61
57
  """
62
58
  self._fetcher = fetcher
63
59
  self._parser = parser
64
- self._config = config
65
60
  self._site = site
66
61
 
62
+ self._save_html = config.save_html
63
+ self._skip_existing = config.skip_existing
64
+ self._login_required = config.login_required
65
+ self._request_interval = config.request_interval
66
+ self._retry_times = config.retry_times
67
+ self._backoff_factor = config.backoff_factor
68
+ self._workers = config.workers
69
+ self._storage_batch_size = max(1, config.storage_batch_size)
70
+
67
71
  self._raw_data_dir = Path(config.raw_data_dir) / site
68
72
  self._raw_data_dir.mkdir(parents=True, exist_ok=True)
69
73
  self._debug_dir = Path.cwd() / "debug" / site
70
74
  self._debug_dir.mkdir(parents=True, exist_ok=True)
71
75
 
72
- self.logger = logging.getLogger(f"{self.__class__.__name__}")
76
+ self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
73
77
 
74
78
  async def download_many(
75
79
  self,
@@ -87,7 +91,7 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
87
91
  args: completed_count, total_count.
88
92
  :param cancel_event: Optional asyncio.Event to allow cancellation.
89
93
  """
90
- if not await self._ensure_ready():
94
+ if not self._check_login():
91
95
  book_ids = [b["book_id"] for b in books]
92
96
  self.logger.warning(
93
97
  "[%s] login failed, skipping download of books: %s",
@@ -116,8 +120,6 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
116
120
  except Exception as e:
117
121
  self._handle_download_exception(book, e)
118
122
 
119
- await self._finalize()
120
-
121
123
  async def download(
122
124
  self,
123
125
  book: BookConfig,
@@ -134,7 +136,7 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
134
136
  args: completed_count, total_count.
135
137
  :param cancel_event: Optional asyncio.Event to allow cancellation.
136
138
  """
137
- if not await self._ensure_ready():
139
+ if not self._check_login():
138
140
  self.logger.warning(
139
141
  "[%s] login failed, skipping download of book: %s (%s-%s)",
140
142
  self._site,
@@ -142,6 +144,7 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
142
144
  book.get("start_id", "-"),
143
145
  book.get("end_id", "-"),
144
146
  )
147
+ return
145
148
 
146
149
  # if already cancelled before starting
147
150
  if cancel_event and cancel_event.is_set():
@@ -162,8 +165,6 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
162
165
  except Exception as e:
163
166
  self._handle_download_exception(book, e)
164
167
 
165
- await self._finalize()
166
-
167
168
  async def load_book_info(
168
169
  self,
169
170
  book_id: str,
@@ -200,23 +201,6 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
200
201
  """
201
202
  ...
202
203
 
203
- async def _prepare(self) -> None:
204
- """
205
- Optional hook called before downloading.
206
-
207
- Subclasses can override this method to perform pre-download setup.
208
- """
209
- return
210
-
211
- async def _finalize(self) -> None:
212
- """
213
- Optional hook called after downloading is complete.
214
-
215
- Subclasses can override this method to perform post-download tasks,
216
- such as saving state or releasing resources.
217
- """
218
- return
219
-
220
204
  def _load_book_info(
221
205
  self,
222
206
  book_id: str,
@@ -283,25 +267,22 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
283
267
  :param filename: used as filename prefix
284
268
  :param html_list: list of HTML strings to save
285
269
  """
286
- if not self.save_html:
270
+ if not self._save_html:
287
271
  return
288
-
289
272
  html_dir.mkdir(parents=True, exist_ok=True)
290
273
  for i, html in enumerate(html_list):
291
- file_path = html_dir / f"{filename}_{i}.html"
292
- file_path.write_text(html, encoding="utf-8")
274
+ (html_dir / f"{filename}_{i}.html").write_text(html, encoding="utf-8")
293
275
 
294
276
  @staticmethod
295
- async def _chapter_ids(
296
- volumes: list[VolumeInfoDict],
277
+ def _planned_chapter_ids(
278
+ vols: list[VolumeInfoDict],
297
279
  start_id: str | None,
298
280
  end_id: str | None,
299
- ) -> AsyncIterator[str]:
300
- """
301
- Yield each chapterId in order, respecting start/end bounds.
302
- """
281
+ ignore: set[str],
282
+ ) -> list[str]:
303
283
  seen_start = start_id is None
304
- for vol in volumes:
284
+ out: list[str] = []
285
+ for vol in vols:
305
286
  for chap in vol["chapters"]:
306
287
  cid = chap.get("chapterId")
307
288
  if not cid:
@@ -311,9 +292,11 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
311
292
  seen_start = True
312
293
  else:
313
294
  continue
314
- yield cid
295
+ if cid not in ignore:
296
+ out.append(cid)
315
297
  if end_id is not None and cid == end_id:
316
- return
298
+ return out
299
+ return out
317
300
 
318
301
  @property
319
302
  def fetcher(self) -> FetcherProtocol:
@@ -323,38 +306,6 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
323
306
  def parser(self) -> ParserProtocol:
324
307
  return self._parser
325
308
 
326
- @property
327
- def save_html(self) -> bool:
328
- return self._config.save_html
329
-
330
- @property
331
- def skip_existing(self) -> bool:
332
- return self._config.skip_existing
333
-
334
- @property
335
- def login_required(self) -> bool:
336
- return self._config.login_required
337
-
338
- @property
339
- def request_interval(self) -> float:
340
- return self._config.request_interval
341
-
342
- @property
343
- def retry_times(self) -> int:
344
- return self._config.retry_times
345
-
346
- @property
347
- def backoff_factor(self) -> float:
348
- return self._config.backoff_factor
349
-
350
- @property
351
- def workers(self) -> int:
352
- return self._config.workers
353
-
354
- @property
355
- def storage_batch_size(self) -> int:
356
- return max(1, self._config.storage_batch_size)
357
-
358
309
  def _handle_download_exception(self, book: BookConfig, error: Exception) -> None:
359
310
  """
360
311
  Handle download errors in a consistent way.
@@ -373,10 +324,8 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
373
324
  error,
374
325
  )
375
326
 
376
- async def _ensure_ready(self) -> bool:
327
+ def _check_login(self) -> bool:
377
328
  """
378
- Run pre-download preparation and check login if needed.
329
+ Check login if needed.
379
330
  """
380
- await self._prepare()
381
-
382
- return self.fetcher.is_logged_in if self.login_required else True
331
+ return self.fetcher.is_logged_in if self._login_required else True