novel-downloader 1.4.2__tar.gz → 1.4.3__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 (170) hide show
  1. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/PKG-INFO +2 -1
  2. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/README.md +1 -0
  3. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/__init__.py +1 -1
  4. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/cli/download.py +1 -1
  5. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/config/adapter.py +1 -0
  6. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/__init__.py +19 -1
  7. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/downloaders/base.py +0 -7
  8. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/downloaders/biquge.py +1 -3
  9. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/downloaders/common.py +0 -2
  10. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/downloaders/esjzone.py +1 -3
  11. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/downloaders/linovelib.py +1 -3
  12. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/downloaders/qianbi.py +1 -3
  13. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/downloaders/qidian.py +1 -5
  14. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/downloaders/sfacg.py +1 -3
  15. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/downloaders/yamibo.py +1 -3
  16. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/factory/downloader.py +3 -6
  17. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/base/browser.py +32 -12
  18. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/esjzone/browser.py +8 -6
  19. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/yamibo/browser.py +3 -3
  20. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/qidian/chapter_encrypted.py +11 -2
  21. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/qidian/chapter_normal.py +8 -1
  22. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/qidian/main_parser.py +7 -2
  23. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/qidian/utils/__init__.py +2 -0
  24. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/qidian/utils/helpers.py +9 -0
  25. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/models/config.py +1 -0
  26. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/resources/config/settings.toml +1 -0
  27. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/tui/screens/home.py +8 -2
  28. {novel_downloader-1.4.2/novel_downloader/utils → novel_downloader-1.4.3/novel_downloader/utils/fontocr}/model_loader.py +2 -2
  29. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/fontocr/ocr_v1.py +2 -1
  30. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/fontocr/ocr_v2.py +2 -1
  31. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/text_utils/__init__.py +8 -1
  32. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/text_utils/text_cleaning.py +51 -0
  33. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader.egg-info/PKG-INFO +2 -1
  34. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader.egg-info/SOURCES.txt +1 -1
  35. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/pyproject.toml +1 -1
  36. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/LICENSE +0 -0
  37. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/cli/__init__.py +0 -0
  38. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/cli/clean.py +0 -0
  39. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/cli/config.py +0 -0
  40. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/cli/export.py +0 -0
  41. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/cli/main.py +0 -0
  42. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/config/__init__.py +0 -0
  43. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/config/loader.py +0 -0
  44. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/config/site_rules.py +0 -0
  45. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/downloaders/__init__.py +0 -0
  46. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/exporters/__init__.py +0 -0
  47. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/exporters/base.py +0 -0
  48. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/exporters/biquge.py +0 -0
  49. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/exporters/common/__init__.py +0 -0
  50. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/exporters/common/epub.py +0 -0
  51. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/exporters/common/main_exporter.py +0 -0
  52. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/exporters/common/txt.py +0 -0
  53. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/exporters/epub_util.py +0 -0
  54. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/exporters/esjzone.py +0 -0
  55. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/exporters/linovelib/__init__.py +0 -0
  56. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/exporters/linovelib/epub.py +0 -0
  57. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/exporters/linovelib/main_exporter.py +0 -0
  58. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/exporters/linovelib/txt.py +0 -0
  59. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/exporters/qianbi.py +0 -0
  60. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/exporters/qidian.py +0 -0
  61. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/exporters/sfacg.py +0 -0
  62. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/exporters/yamibo.py +0 -0
  63. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/factory/__init__.py +0 -0
  64. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/factory/exporter.py +0 -0
  65. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/factory/fetcher.py +0 -0
  66. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/factory/parser.py +0 -0
  67. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/__init__.py +0 -0
  68. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/base/__init__.py +0 -0
  69. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/base/rate_limiter.py +0 -0
  70. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/base/session.py +0 -0
  71. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/biquge/__init__.py +0 -0
  72. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/biquge/browser.py +0 -0
  73. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/biquge/session.py +0 -0
  74. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/common/__init__.py +0 -0
  75. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/common/browser.py +0 -0
  76. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/common/session.py +0 -0
  77. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/esjzone/__init__.py +0 -0
  78. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/esjzone/session.py +0 -0
  79. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/linovelib/__init__.py +0 -0
  80. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/linovelib/browser.py +0 -0
  81. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/linovelib/session.py +0 -0
  82. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/qianbi/__init__.py +0 -0
  83. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/qianbi/browser.py +0 -0
  84. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/qianbi/session.py +0 -0
  85. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/qidian/__init__.py +0 -0
  86. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/qidian/browser.py +0 -0
  87. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/qidian/session.py +0 -0
  88. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/sfacg/__init__.py +0 -0
  89. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/sfacg/browser.py +0 -0
  90. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/sfacg/session.py +0 -0
  91. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/yamibo/__init__.py +0 -0
  92. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/fetchers/yamibo/session.py +0 -0
  93. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/interfaces/__init__.py +0 -0
  94. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/interfaces/downloader.py +0 -0
  95. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/interfaces/exporter.py +0 -0
  96. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/interfaces/fetcher.py +0 -0
  97. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/interfaces/parser.py +0 -0
  98. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/__init__.py +0 -0
  99. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/base.py +0 -0
  100. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/biquge/__init__.py +0 -0
  101. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/biquge/main_parser.py +0 -0
  102. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/common/__init__.py +0 -0
  103. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/common/helper.py +0 -0
  104. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/common/main_parser.py +0 -0
  105. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/esjzone/__init__.py +0 -0
  106. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/esjzone/main_parser.py +0 -0
  107. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/linovelib/__init__.py +0 -0
  108. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/linovelib/main_parser.py +0 -0
  109. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/qianbi/__init__.py +0 -0
  110. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/qianbi/main_parser.py +0 -0
  111. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/qidian/__init__.py +0 -0
  112. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/qidian/book_info_parser.py +0 -0
  113. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/qidian/chapter_router.py +0 -0
  114. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/qidian/utils/decryptor_fetcher.py +0 -0
  115. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/qidian/utils/node_decryptor.py +0 -0
  116. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/sfacg/__init__.py +0 -0
  117. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/sfacg/main_parser.py +0 -0
  118. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/yamibo/__init__.py +0 -0
  119. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/core/parsers/yamibo/main_parser.py +0 -0
  120. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/locales/en.json +0 -0
  121. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/locales/zh.json +0 -0
  122. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/models/__init__.py +0 -0
  123. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/models/browser.py +0 -0
  124. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/models/chapter.py +0 -0
  125. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/models/login.py +0 -0
  126. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/models/site_rules.py +0 -0
  127. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/models/tasks.py +0 -0
  128. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/models/types.py +0 -0
  129. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/resources/css_styles/main.css +0 -0
  130. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/resources/css_styles/volume-intro.css +0 -0
  131. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/resources/images/volume_border.png +0 -0
  132. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/resources/js_scripts/qidian_decrypt_node.js +0 -0
  133. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/resources/json/linovelib_font_map.json +0 -0
  134. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/resources/json/replace_word_map.json +0 -0
  135. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/resources/text/blacklist.txt +0 -0
  136. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/tui/__init__.py +0 -0
  137. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/tui/app.py +0 -0
  138. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/tui/main.py +0 -0
  139. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/tui/screens/__init__.py +0 -0
  140. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/tui/screens/login.py +0 -0
  141. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/tui/styles/home_layout.tcss +0 -0
  142. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/tui/widgets/richlog_handler.py +0 -0
  143. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/__init__.py +0 -0
  144. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/cache.py +0 -0
  145. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/chapter_storage.py +0 -0
  146. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/constants.py +0 -0
  147. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/cookies.py +0 -0
  148. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/crypto_utils.py +0 -0
  149. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/file_utils/__init__.py +0 -0
  150. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/file_utils/io.py +0 -0
  151. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/file_utils/normalize.py +0 -0
  152. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/file_utils/sanitize.py +0 -0
  153. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/fontocr/__init__.py +0 -0
  154. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/hash_store.py +0 -0
  155. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/hash_utils.py +0 -0
  156. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/i18n.py +0 -0
  157. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/logger.py +0 -0
  158. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/network.py +0 -0
  159. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/state.py +0 -0
  160. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/text_utils/chapter_formatting.py +0 -0
  161. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/text_utils/diff_display.py +0 -0
  162. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/text_utils/font_mapping.py +0 -0
  163. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/time_utils/__init__.py +0 -0
  164. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/time_utils/datetime_utils.py +0 -0
  165. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader/utils/time_utils/sleep_utils.py +0 -0
  166. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader.egg-info/dependency_links.txt +0 -0
  167. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader.egg-info/entry_points.txt +0 -0
  168. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader.egg-info/requires.txt +0 -0
  169. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/novel_downloader.egg-info/top_level.txt +0 -0
  170. {novel_downloader-1.4.2 → novel_downloader-1.4.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: novel-downloader
3
- Version: 1.4.2
3
+ Version: 1.4.3
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
@@ -182,6 +182,7 @@ pip install .
182
182
  - [CLI 使用示例](https://github.com/BowenZ217/novel-downloader/blob/main/docs/6-cli-usage-examples.md)
183
183
  - [复制 Cookies](https://github.com/BowenZ217/novel-downloader/blob/main/docs/copy-cookies.md)
184
184
  - [文件保存](https://github.com/BowenZ217/novel-downloader/blob/main/docs/file-saving.md)
185
+ - [模块与接口文档](https://github.com/BowenZ217/novel-downloader/blob/main/docs/api/README.md)
185
186
  - [TODO](https://github.com/BowenZ217/novel-downloader/blob/main/docs/todo.md)
186
187
  - [开发](https://github.com/BowenZ217/novel-downloader/blob/main/docs/develop.md)
187
188
  - [项目说明](#项目说明)
@@ -111,6 +111,7 @@ pip install .
111
111
  - [CLI 使用示例](https://github.com/BowenZ217/novel-downloader/blob/main/docs/6-cli-usage-examples.md)
112
112
  - [复制 Cookies](https://github.com/BowenZ217/novel-downloader/blob/main/docs/copy-cookies.md)
113
113
  - [文件保存](https://github.com/BowenZ217/novel-downloader/blob/main/docs/file-saving.md)
114
+ - [模块与接口文档](https://github.com/BowenZ217/novel-downloader/blob/main/docs/api/README.md)
114
115
  - [TODO](https://github.com/BowenZ217/novel-downloader/blob/main/docs/todo.md)
115
116
  - [开发](https://github.com/BowenZ217/novel-downloader/blob/main/docs/develop.md)
116
117
  - [项目说明](#项目说明)
@@ -6,7 +6,7 @@ novel_downloader
6
6
  Core package for the Novel Downloader project.
7
7
  """
8
8
 
9
- __version__ = "1.4.2"
9
+ __version__ = "1.4.3"
10
10
 
11
11
  __author__ = "Saudade Z"
12
12
  __email__ = "saudadez217@gmail.com"
@@ -161,7 +161,6 @@ async def _download(
161
161
  downloader = get_downloader(
162
162
  fetcher=fetcher,
163
163
  parser=parser,
164
- exporter=exporter,
165
164
  site=site,
166
165
  config=downloader_cfg,
167
166
  )
@@ -172,6 +171,7 @@ async def _download(
172
171
  book,
173
172
  progress_hook=_print_progress,
174
173
  )
174
+ await asyncio.to_thread(exporter.export, book["book_id"])
175
175
 
176
176
  if downloader_cfg.login_required and fetcher.is_logged_in:
177
177
  await fetcher.save_state()
@@ -127,6 +127,7 @@ class ConfigAdapter:
127
127
  site_cfg = self._get_site_cfg()
128
128
  return ParserConfig(
129
129
  cache_dir=gen.get("cache_dir", "./novel_cache"),
130
+ use_truncation=site_cfg.get("use_truncation", True),
130
131
  decode_font=font_ocr.get("decode_font", False),
131
132
  use_freq=font_ocr.get("use_freq", False),
132
133
  use_ocr=font_ocr.get("use_ocr", True),
@@ -14,8 +14,26 @@ downloading and processing online novel content, including:
14
14
  - Exporter: Responsible for exporting downloaded data into various output formats.
15
15
  """
16
16
 
17
- from .factory import get_parser
17
+ from .factory import (
18
+ get_downloader,
19
+ get_exporter,
20
+ get_fetcher,
21
+ get_parser,
22
+ )
23
+ from .interfaces import (
24
+ DownloaderProtocol,
25
+ ExporterProtocol,
26
+ FetcherProtocol,
27
+ ParserProtocol,
28
+ )
18
29
 
19
30
  __all__ = [
31
+ "get_downloader",
32
+ "get_exporter",
33
+ "get_fetcher",
20
34
  "get_parser",
35
+ "DownloaderProtocol",
36
+ "ExporterProtocol",
37
+ "FetcherProtocol",
38
+ "ParserProtocol",
21
39
  ]
@@ -15,7 +15,6 @@ from typing import Any
15
15
 
16
16
  from novel_downloader.core.interfaces import (
17
17
  DownloaderProtocol,
18
- ExporterProtocol,
19
18
  FetcherProtocol,
20
19
  ParserProtocol,
21
20
  )
@@ -34,13 +33,11 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
34
33
  self,
35
34
  fetcher: FetcherProtocol,
36
35
  parser: ParserProtocol,
37
- exporter: ExporterProtocol,
38
36
  config: DownloaderConfig,
39
37
  site: str,
40
38
  ):
41
39
  self._fetcher = fetcher
42
40
  self._parser = parser
43
- self._exporter = exporter
44
41
  self._config = config
45
42
  self._site = site
46
43
 
@@ -158,10 +155,6 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
158
155
  def parser(self) -> ParserProtocol:
159
156
  return self._parser
160
157
 
161
- @property
162
- def exporter(self) -> ExporterProtocol:
163
- return self._exporter
164
-
165
158
  @property
166
159
  def config(self) -> DownloaderConfig:
167
160
  return self._config
@@ -7,7 +7,6 @@ novel_downloader.core.downloaders.biquge
7
7
 
8
8
  from novel_downloader.core.downloaders.common import CommonDownloader
9
9
  from novel_downloader.core.interfaces import (
10
- ExporterProtocol,
11
10
  FetcherProtocol,
12
11
  ParserProtocol,
13
12
  )
@@ -21,7 +20,6 @@ class BiqugeDownloader(CommonDownloader):
21
20
  self,
22
21
  fetcher: FetcherProtocol,
23
22
  parser: ParserProtocol,
24
- exporter: ExporterProtocol,
25
23
  config: DownloaderConfig,
26
24
  ):
27
- super().__init__(fetcher, parser, exporter, config, "biquge")
25
+ super().__init__(fetcher, parser, config, "biquge")
@@ -440,8 +440,6 @@ class CommonDownloader(BaseDownloader):
440
440
  normal_cs.close()
441
441
  save_as_json(book_info, info_path)
442
442
 
443
- await asyncio.to_thread(self.exporter.export, book_id)
444
-
445
443
  self.logger.info(
446
444
  "%s Novel '%s' download completed.",
447
445
  TAG,
@@ -7,7 +7,6 @@ novel_downloader.core.downloaders.esjzone
7
7
 
8
8
  from novel_downloader.core.downloaders.common import CommonDownloader
9
9
  from novel_downloader.core.interfaces import (
10
- ExporterProtocol,
11
10
  FetcherProtocol,
12
11
  ParserProtocol,
13
12
  )
@@ -21,7 +20,6 @@ class EsjzoneDownloader(CommonDownloader):
21
20
  self,
22
21
  fetcher: FetcherProtocol,
23
22
  parser: ParserProtocol,
24
- exporter: ExporterProtocol,
25
23
  config: DownloaderConfig,
26
24
  ):
27
- super().__init__(fetcher, parser, exporter, config, "esjzone")
25
+ super().__init__(fetcher, parser, config, "esjzone")
@@ -7,7 +7,6 @@ novel_downloader.core.downloaders.linovelib
7
7
 
8
8
  from novel_downloader.core.downloaders.common import CommonDownloader
9
9
  from novel_downloader.core.interfaces import (
10
- ExporterProtocol,
11
10
  FetcherProtocol,
12
11
  ParserProtocol,
13
12
  )
@@ -21,7 +20,6 @@ class LinovelibDownloader(CommonDownloader):
21
20
  self,
22
21
  fetcher: FetcherProtocol,
23
22
  parser: ParserProtocol,
24
- exporter: ExporterProtocol,
25
23
  config: DownloaderConfig,
26
24
  ):
27
- super().__init__(fetcher, parser, exporter, config, "linovelib")
25
+ super().__init__(fetcher, parser, config, "linovelib")
@@ -7,7 +7,6 @@ novel_downloader.core.downloaders.qianbi
7
7
 
8
8
  from novel_downloader.core.downloaders.common import CommonDownloader
9
9
  from novel_downloader.core.interfaces import (
10
- ExporterProtocol,
11
10
  FetcherProtocol,
12
11
  ParserProtocol,
13
12
  )
@@ -21,7 +20,6 @@ class QianbiDownloader(CommonDownloader):
21
20
  self,
22
21
  fetcher: FetcherProtocol,
23
22
  parser: ParserProtocol,
24
- exporter: ExporterProtocol,
25
23
  config: DownloaderConfig,
26
24
  ):
27
- super().__init__(fetcher, parser, exporter, config, "qianbi")
25
+ super().__init__(fetcher, parser, config, "qianbi")
@@ -13,7 +13,6 @@ from typing import Any, cast
13
13
 
14
14
  from novel_downloader.core.downloaders.base import BaseDownloader
15
15
  from novel_downloader.core.interfaces import (
16
- ExporterProtocol,
17
16
  FetcherProtocol,
18
17
  ParserProtocol,
19
18
  )
@@ -41,11 +40,10 @@ class QidianDownloader(BaseDownloader):
41
40
  self,
42
41
  fetcher: FetcherProtocol,
43
42
  parser: ParserProtocol,
44
- exporter: ExporterProtocol,
45
43
  config: DownloaderConfig,
46
44
  ):
47
45
  config.request_interval = max(1.0, config.request_interval)
48
- super().__init__(fetcher, parser, exporter, config, "qidian")
46
+ super().__init__(fetcher, parser, config, "qidian")
49
47
 
50
48
  async def _download_one(
51
49
  self,
@@ -351,8 +349,6 @@ class QidianDownloader(BaseDownloader):
351
349
  normal_cs.close()
352
350
  encrypted_cs.close()
353
351
 
354
- await asyncio.to_thread(self.exporter.export, book_id)
355
-
356
352
  self.logger.info(
357
353
  "%s Novel '%s' download completed.",
358
354
  TAG,
@@ -7,7 +7,6 @@ novel_downloader.core.downloaders.sfacg
7
7
 
8
8
  from novel_downloader.core.downloaders.common import CommonDownloader
9
9
  from novel_downloader.core.interfaces import (
10
- ExporterProtocol,
11
10
  FetcherProtocol,
12
11
  ParserProtocol,
13
12
  )
@@ -21,7 +20,6 @@ class SfacgDownloader(CommonDownloader):
21
20
  self,
22
21
  fetcher: FetcherProtocol,
23
22
  parser: ParserProtocol,
24
- exporter: ExporterProtocol,
25
23
  config: DownloaderConfig,
26
24
  ):
27
- super().__init__(fetcher, parser, exporter, config, "sfacg")
25
+ super().__init__(fetcher, parser, config, "sfacg")
@@ -7,7 +7,6 @@ novel_downloader.core.downloaders.yamibo
7
7
 
8
8
  from novel_downloader.core.downloaders.common import CommonDownloader
9
9
  from novel_downloader.core.interfaces import (
10
- ExporterProtocol,
11
10
  FetcherProtocol,
12
11
  ParserProtocol,
13
12
  )
@@ -21,7 +20,6 @@ class YamiboDownloader(CommonDownloader):
21
20
  self,
22
21
  fetcher: FetcherProtocol,
23
22
  parser: ParserProtocol,
24
- exporter: ExporterProtocol,
25
23
  config: DownloaderConfig,
26
24
  ):
27
- super().__init__(fetcher, parser, exporter, config, "yamibo")
25
+ super().__init__(fetcher, parser, config, "yamibo")
@@ -22,14 +22,13 @@ from novel_downloader.core.downloaders import (
22
22
  )
23
23
  from novel_downloader.core.interfaces import (
24
24
  DownloaderProtocol,
25
- ExporterProtocol,
26
25
  FetcherProtocol,
27
26
  ParserProtocol,
28
27
  )
29
28
  from novel_downloader.models import DownloaderConfig
30
29
 
31
30
  DownloaderBuilder = Callable[
32
- [FetcherProtocol, ParserProtocol, ExporterProtocol, DownloaderConfig],
31
+ [FetcherProtocol, ParserProtocol, DownloaderConfig],
33
32
  DownloaderProtocol,
34
33
  ]
35
34
 
@@ -47,7 +46,6 @@ _site_map: dict[str, DownloaderBuilder] = {
47
46
  def get_downloader(
48
47
  fetcher: FetcherProtocol,
49
48
  parser: ParserProtocol,
50
- exporter: ExporterProtocol,
51
49
  site: str,
52
50
  config: DownloaderConfig,
53
51
  ) -> DownloaderProtocol:
@@ -56,7 +54,6 @@ def get_downloader(
56
54
 
57
55
  :param fetcher: Fetcher implementation
58
56
  :param parser: Parser implementation
59
- :param exporter: Exporter implementation
60
57
  :param site: Site name (e.g., 'qidian')
61
58
  :param config: Downloader configuration
62
59
 
@@ -66,11 +63,11 @@ def get_downloader(
66
63
 
67
64
  # site-specific
68
65
  if site_key in _site_map:
69
- return _site_map[site_key](fetcher, parser, exporter, config)
66
+ return _site_map[site_key](fetcher, parser, config)
70
67
 
71
68
  # fallback
72
69
  site_rules = load_site_rules()
73
70
  if site_key not in site_rules:
74
71
  raise ValueError(f"Unsupported site: {site}")
75
72
 
76
- return CommonDownloader(fetcher, parser, exporter, config, site_key)
73
+ return CommonDownloader(fetcher, parser, config, site_key)
@@ -201,19 +201,9 @@ class BaseBrowser(FetcherProtocol, abc.ABC):
201
201
  **kwargs: Any,
202
202
  ) -> str:
203
203
  if self._reuse_page:
204
- if not self._page:
205
- self._page = await self.context.new_page()
206
- page = self._page
204
+ return await self._fetch_with_reuse(url, wait_until, referer, **kwargs)
207
205
  else:
208
- page = await self.context.new_page()
209
-
210
- await page.goto(url, wait_until=wait_until, referer=referer)
211
- content = await page.content()
212
-
213
- if not self._reuse_page:
214
- await page.close()
215
-
216
- return str(content)
206
+ return await self._fetch_with_new(url, wait_until, referer, **kwargs)
217
207
 
218
208
  async def load_state(self) -> bool:
219
209
  """ """
@@ -286,6 +276,36 @@ class BaseBrowser(FetcherProtocol, abc.ABC):
286
276
  await self.init(headless=headless)
287
277
  self.logger.debug("[browser] Browser restarted (headless=%s).", headless)
288
278
 
279
+ async def _fetch_with_new(
280
+ self,
281
+ url: str,
282
+ wait_until: Literal["commit", "domcontentloaded", "load", "networkidle"]
283
+ | None = "load",
284
+ referer: str | None = None,
285
+ **kwargs: Any,
286
+ ) -> str:
287
+ page = await self.context.new_page()
288
+ try:
289
+ await page.goto(url, wait_until=wait_until, referer=referer, **kwargs)
290
+ html: str = await page.content()
291
+ return html
292
+ finally:
293
+ await page.close()
294
+
295
+ async def _fetch_with_reuse(
296
+ self,
297
+ url: str,
298
+ wait_until: Literal["commit", "domcontentloaded", "load", "networkidle"]
299
+ | None = "load",
300
+ referer: str | None = None,
301
+ **kwargs: Any,
302
+ ) -> str:
303
+ if not self._page:
304
+ self._page = await self.context.new_page()
305
+ await self._page.goto(url, wait_until=wait_until, referer=referer, **kwargs)
306
+ html: str = await self._page.content()
307
+ return html
308
+
289
309
  @property
290
310
  def hostname(self) -> str:
291
311
  return ""
@@ -49,15 +49,17 @@ class EsjzoneBrowser(BaseBrowser):
49
49
 
50
50
  login_page = await self.context.new_page()
51
51
 
52
- await login_page.goto(self.API_LOGIN_URL_1, wait_until="networkidle")
52
+ try:
53
+ await login_page.goto(self.API_LOGIN_URL_1, wait_until="networkidle")
53
54
 
54
- await login_page.fill('input[name="email"]', username)
55
- await login_page.fill('input[name="pwd"]', password)
55
+ await login_page.fill('input[name="email"]', username)
56
+ await login_page.fill('input[name="pwd"]', password)
56
57
 
57
- await login_page.click('a.btn-send[data-send="mem_login"]')
58
+ await login_page.click('a.btn-send[data-send="mem_login"]')
58
59
 
59
- await login_page.wait_for_load_state("networkidle")
60
- await login_page.close()
60
+ await login_page.wait_for_load_state("networkidle")
61
+ finally:
62
+ await login_page.close()
61
63
 
62
64
  self._is_logged_in = await self._check_login_status()
63
65
 
@@ -48,8 +48,8 @@ class YamiboBrowser(BaseBrowser):
48
48
  return False
49
49
 
50
50
  for i in range(1, attempt + 1):
51
+ login_page = await self.context.new_page()
51
52
  try:
52
- login_page = await self.context.new_page()
53
53
  await login_page.goto(self.LOGIN_URL, wait_until="networkidle")
54
54
 
55
55
  await login_page.fill("#loginform-username", username)
@@ -68,8 +68,6 @@ class YamiboBrowser(BaseBrowser):
68
68
  f"[auth] No URL change after login attempt {i}: {e}"
69
69
  )
70
70
 
71
- await login_page.close()
72
-
73
71
  self._is_logged_in = await self._check_login_status()
74
72
  if self._is_logged_in:
75
73
  self.logger.info(f"[auth] Login successful on attempt {i}.")
@@ -83,6 +81,8 @@ class YamiboBrowser(BaseBrowser):
83
81
  self.logger.error(
84
82
  f"[auth] Unexpected error during login attempt {i}: {e}"
85
83
  )
84
+ finally:
85
+ await login_page.close()
86
86
 
87
87
  self.logger.error(f"[auth] Login failed after {attempt} attempt(s).")
88
88
  return False
@@ -19,12 +19,16 @@ from lxml import html
19
19
 
20
20
  from novel_downloader.models import ChapterDict
21
21
  from novel_downloader.utils.network import download_font_file
22
- from novel_downloader.utils.text_utils import apply_font_mapping
22
+ from novel_downloader.utils.text_utils import (
23
+ apply_font_mapping,
24
+ truncate_half_lines,
25
+ )
23
26
 
24
27
  from .utils import (
25
28
  extract_chapter_info,
26
29
  find_ssr_page_context,
27
30
  get_decryptor,
31
+ is_duplicated,
28
32
  vip_status,
29
33
  )
30
34
 
@@ -76,6 +80,7 @@ def parse_encrypted_chapter(
76
80
  fixedFontWoff2_url = chapter_info["fixedFontWoff2"]
77
81
 
78
82
  title = chapter_info.get("chapterName", "Untitled")
83
+ duplicated = is_duplicated(ssr_data)
79
84
  raw_html = chapter_info.get("content", "")
80
85
  chapter_id = chapter_info.get("chapterId", chapter_id)
81
86
  fkp = chapter_info.get("fkp", "")
@@ -83,7 +88,7 @@ def parse_encrypted_chapter(
83
88
  update_time = chapter_info.get("updateTime", "")
84
89
  update_timestamp = chapter_info.get("updateTimestamp", 0)
85
90
  modify_time = chapter_info.get("modifyTime", 0)
86
- word_count = chapter_info.get("wordsCount", 0)
91
+ word_count = chapter_info.get("actualWords", 0)
87
92
  seq = chapter_info.get("seq", None)
88
93
  volume = chapter_info.get("extra", {}).get("volumeName", "")
89
94
 
@@ -177,6 +182,9 @@ def parse_encrypted_chapter(
177
182
  final_paragraphs_str = "\n\n".join(
178
183
  line.strip() for line in original_text.splitlines() if line.strip()
179
184
  )
185
+ if parser._use_truncation and duplicated:
186
+ final_paragraphs_str = truncate_half_lines(final_paragraphs_str)
187
+
180
188
  return {
181
189
  "id": str(chapter_id),
182
190
  "title": str(title),
@@ -187,6 +195,7 @@ def parse_encrypted_chapter(
187
195
  "update_timestamp": update_timestamp,
188
196
  "modify_time": modify_time,
189
197
  "word_count": word_count,
198
+ "duplicated": duplicated,
190
199
  "seq": seq,
191
200
  "volume": volume,
192
201
  "encrypted": True,
@@ -15,11 +15,13 @@ from typing import TYPE_CHECKING
15
15
  from lxml import html
16
16
 
17
17
  from novel_downloader.models import ChapterDict
18
+ from novel_downloader.utils.text_utils import truncate_half_lines
18
19
 
19
20
  from .utils import (
20
21
  extract_chapter_info,
21
22
  find_ssr_page_context,
22
23
  get_decryptor,
24
+ is_duplicated,
23
25
  vip_status,
24
26
  )
25
27
 
@@ -51,6 +53,7 @@ def parse_normal_chapter(
51
53
  return None
52
54
 
53
55
  title = chapter_info.get("chapterName", "Untitled")
56
+ duplicated = is_duplicated(ssr_data)
54
57
  raw_html = chapter_info.get("content", "")
55
58
  chapter_id = chapter_info.get("chapterId", chapter_id)
56
59
  fkp = chapter_info.get("fkp", "")
@@ -58,7 +61,7 @@ def parse_normal_chapter(
58
61
  update_time = chapter_info.get("updateTime", "")
59
62
  update_timestamp = chapter_info.get("updateTimestamp", 0)
60
63
  modify_time = chapter_info.get("modifyTime", 0)
61
- word_count = chapter_info.get("wordsCount", 0)
64
+ word_count = chapter_info.get("actualWords", 0)
62
65
  seq = chapter_info.get("seq", None)
63
66
  volume = chapter_info.get("extra", {}).get("volumeName", "")
64
67
 
@@ -74,6 +77,9 @@ def parse_normal_chapter(
74
77
  if not chapter_text:
75
78
  return None
76
79
 
80
+ if parser._use_truncation and duplicated:
81
+ chapter_text = truncate_half_lines(chapter_text)
82
+
77
83
  return {
78
84
  "id": str(chapter_id),
79
85
  "title": title,
@@ -84,6 +90,7 @@ def parse_normal_chapter(
84
90
  "update_timestamp": update_timestamp,
85
91
  "modify_time": modify_time,
86
92
  "word_count": word_count,
93
+ "duplicated": duplicated,
87
94
  "seq": seq,
88
95
  "volume": volume,
89
96
  "encrypted": False,
@@ -32,7 +32,11 @@ class QidianParser(BaseParser):
32
32
  Parser for Qidian site.
33
33
  """
34
34
 
35
- def __init__(self, config: ParserConfig):
35
+ def __init__(
36
+ self,
37
+ config: ParserConfig,
38
+ fuid: str = "",
39
+ ):
36
40
  """
37
41
  Initialize the QidianParser with the given configuration.
38
42
 
@@ -41,6 +45,7 @@ class QidianParser(BaseParser):
41
45
  super().__init__(config)
42
46
 
43
47
  # Extract and store parser flags from config
48
+ self._use_truncation = config.use_truncation
44
49
  self._decode_font: bool = config.decode_font
45
50
  self._save_font_debug: bool = config.save_font_debug
46
51
 
@@ -52,7 +57,7 @@ class QidianParser(BaseParser):
52
57
  DATA_DIR / "qidian" / "browser_state.cookies",
53
58
  DATA_DIR / "qidian" / "session_state.cookies",
54
59
  ]
55
- self._fuid: str = find_cookie_value(state_files, "ywguid")
60
+ self._fuid: str = fuid or find_cookie_value(state_files, "ywguid")
56
61
 
57
62
  self._font_ocr: FontOCR | None = None
58
63
  if self._decode_font:
@@ -9,6 +9,7 @@ from .helpers import (
9
9
  can_view_chapter,
10
10
  extract_chapter_info,
11
11
  find_ssr_page_context,
12
+ is_duplicated,
12
13
  is_encrypted,
13
14
  is_restricted_page,
14
15
  vip_status,
@@ -22,6 +23,7 @@ __all__ = [
22
23
  "vip_status",
23
24
  "can_view_chapter",
24
25
  "is_encrypted",
26
+ "is_duplicated",
25
27
  "QidianNodeDecryptor",
26
28
  "get_decryptor",
27
29
  ]
@@ -89,6 +89,15 @@ def can_view_chapter(ssr_data: dict[str, Any]) -> bool:
89
89
  return not (vip_status == 1 and is_buy == 0)
90
90
 
91
91
 
92
+ def is_duplicated(ssr_data: dict[str, Any]) -> bool:
93
+ """
94
+ Check if chapter is marked as duplicated (eFW = 1).
95
+ """
96
+ chapter_info = extract_chapter_info(ssr_data)
97
+ efw_flag = chapter_info.get("eFW", 0)
98
+ return bool(efw_flag == 1)
99
+
100
+
92
101
  def is_encrypted(content: str | dict[str, Any]) -> bool:
93
102
  """
94
103
  Return True if content is encrypted.
@@ -68,6 +68,7 @@ class DownloaderConfig:
68
68
  @dataclass
69
69
  class ParserConfig:
70
70
  cache_dir: str = "./novel_cache"
71
+ use_truncation: bool = True
71
72
  decode_font: bool = False
72
73
  use_freq: bool = False
73
74
  use_ocr: bool = True
@@ -52,6 +52,7 @@ book_ids = [
52
52
  ]
53
53
  mode = "session" # browser / session
54
54
  login_required = true # 是否需要登录才能访问
55
+ use_truncation = true # 是否基于章节长度截断以避免重复内容
55
56
 
56
57
  [sites.biquge] # 笔趣阁
57
58
  book_ids = [
@@ -65,7 +65,13 @@ class HomeScreen(Screen): # type: ignore[misc]
65
65
  return
66
66
  id_list = {x.strip() for x in ids.split(",") if x.strip()}
67
67
  adapter = ConfigAdapter(config=self.app.config, site=str(site))
68
- asyncio.create_task(self._download(adapter, str(site), id_list))
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
+ )
69
75
 
70
76
  def _make_title_bar(self) -> Horizontal:
71
77
  return Horizontal(
@@ -134,7 +140,6 @@ class HomeScreen(Screen): # type: ignore[misc]
134
140
  downloader = get_downloader(
135
141
  fetcher=fetcher,
136
142
  parser=parser,
137
- exporter=exporter,
138
143
  site=site,
139
144
  config=downloader_cfg,
140
145
  )
@@ -145,6 +150,7 @@ class HomeScreen(Screen): # type: ignore[misc]
145
150
  {"book_id": book_id},
146
151
  progress_hook=self._update_progress,
147
152
  )
153
+ await asyncio.to_thread(exporter.export, book_id)
148
154
 
149
155
  if downloader_cfg.login_required and fetcher.is_logged_in:
150
156
  await fetcher.save_state()
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- novel_downloader.utils.model_loader
4
- -----------------------------------
3
+ novel_downloader.utils.fontocr.model_loader
4
+ -------------------------------------------
5
5
 
6
6
  Utility functions for managing pre-trained model downloads.
7
7
 
@@ -25,7 +25,8 @@ from novel_downloader.utils.constants import (
25
25
  REC_IMAGE_SHAPE_MAP,
26
26
  )
27
27
  from novel_downloader.utils.hash_store import img_hash_store
28
- from novel_downloader.utils.model_loader import get_rec_chinese_char_model_dir
28
+
29
+ from .model_loader import get_rec_chinese_char_model_dir
29
30
 
30
31
  logger = logging.getLogger(__name__)
31
32