novel-downloader 1.4.2__tar.gz → 1.4.4__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 (172) hide show
  1. {novel_downloader-1.4.2 → novel_downloader-1.4.4}/PKG-INFO +2 -1
  2. {novel_downloader-1.4.2 → novel_downloader-1.4.4}/README.md +1 -0
  3. {novel_downloader-1.4.2 → novel_downloader-1.4.4}/pyproject.toml +4 -2
  4. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/__init__.py +1 -1
  5. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/cli/download.py +1 -1
  6. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/config/adapter.py +1 -0
  7. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/__init__.py +19 -1
  8. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/base.py +0 -7
  9. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/biquge.py +1 -3
  10. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/common.py +0 -2
  11. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/esjzone.py +1 -3
  12. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/linovelib.py +1 -3
  13. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/qianbi.py +1 -3
  14. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/qidian.py +1 -5
  15. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/sfacg.py +1 -3
  16. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/yamibo.py +1 -3
  17. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/factory/downloader.py +3 -6
  18. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/base/browser.py +32 -12
  19. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/esjzone/browser.py +8 -6
  20. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/qidian/browser.py +2 -2
  21. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/qidian/session.py +2 -1
  22. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/yamibo/browser.py +3 -3
  23. novel_downloader-1.4.4/src/novel_downloader/core/parsers/qidian/book_info_parser.py +90 -0
  24. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qidian/chapter_encrypted.py +11 -2
  25. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qidian/chapter_normal.py +8 -1
  26. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qidian/main_parser.py +7 -2
  27. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qidian/utils/__init__.py +2 -0
  28. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qidian/utils/helpers.py +9 -0
  29. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/models/config.py +1 -0
  30. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/resources/config/settings.toml +1 -0
  31. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/tui/screens/home.py +8 -2
  32. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/tui/screens/login.py +1 -1
  33. {novel_downloader-1.4.2/novel_downloader/utils → novel_downloader-1.4.4/src/novel_downloader/utils/fontocr}/model_loader.py +2 -2
  34. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/fontocr/ocr_v1.py +2 -1
  35. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/fontocr/ocr_v2.py +2 -1
  36. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/text_utils/__init__.py +8 -1
  37. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/text_utils/text_cleaning.py +51 -0
  38. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/time_utils/datetime_utils.py +1 -1
  39. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader.egg-info/PKG-INFO +2 -1
  40. novel_downloader-1.4.4/src/novel_downloader.egg-info/SOURCES.txt +168 -0
  41. novel_downloader-1.4.2/novel_downloader/core/parsers/qidian/book_info_parser.py +0 -113
  42. novel_downloader-1.4.2/novel_downloader.egg-info/SOURCES.txt +0 -168
  43. {novel_downloader-1.4.2 → novel_downloader-1.4.4}/LICENSE +0 -0
  44. {novel_downloader-1.4.2 → novel_downloader-1.4.4}/setup.cfg +0 -0
  45. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/cli/__init__.py +0 -0
  46. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/cli/clean.py +0 -0
  47. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/cli/config.py +0 -0
  48. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/cli/export.py +0 -0
  49. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/cli/main.py +0 -0
  50. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/config/__init__.py +0 -0
  51. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/config/loader.py +0 -0
  52. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/config/site_rules.py +0 -0
  53. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/__init__.py +0 -0
  54. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/__init__.py +0 -0
  55. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/base.py +0 -0
  56. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/biquge.py +0 -0
  57. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/common/__init__.py +0 -0
  58. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/common/epub.py +0 -0
  59. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/common/main_exporter.py +0 -0
  60. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/common/txt.py +0 -0
  61. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/epub_util.py +0 -0
  62. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/esjzone.py +0 -0
  63. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/linovelib/__init__.py +0 -0
  64. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/linovelib/epub.py +0 -0
  65. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/linovelib/main_exporter.py +0 -0
  66. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/linovelib/txt.py +0 -0
  67. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/qianbi.py +0 -0
  68. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/qidian.py +0 -0
  69. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/sfacg.py +0 -0
  70. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/yamibo.py +0 -0
  71. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/factory/__init__.py +0 -0
  72. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/factory/exporter.py +0 -0
  73. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/factory/fetcher.py +0 -0
  74. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/factory/parser.py +0 -0
  75. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/__init__.py +0 -0
  76. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/base/__init__.py +0 -0
  77. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/base/rate_limiter.py +0 -0
  78. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/base/session.py +0 -0
  79. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/biquge/__init__.py +0 -0
  80. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/biquge/browser.py +0 -0
  81. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/biquge/session.py +0 -0
  82. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/common/__init__.py +0 -0
  83. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/common/browser.py +0 -0
  84. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/common/session.py +0 -0
  85. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/esjzone/__init__.py +0 -0
  86. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/esjzone/session.py +0 -0
  87. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/linovelib/__init__.py +0 -0
  88. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/linovelib/browser.py +0 -0
  89. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/linovelib/session.py +0 -0
  90. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/qianbi/__init__.py +0 -0
  91. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/qianbi/browser.py +0 -0
  92. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/qianbi/session.py +0 -0
  93. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/qidian/__init__.py +0 -0
  94. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/sfacg/__init__.py +0 -0
  95. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/sfacg/browser.py +0 -0
  96. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/sfacg/session.py +0 -0
  97. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/yamibo/__init__.py +0 -0
  98. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/yamibo/session.py +0 -0
  99. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/interfaces/__init__.py +0 -0
  100. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/interfaces/downloader.py +0 -0
  101. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/interfaces/exporter.py +0 -0
  102. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/interfaces/fetcher.py +0 -0
  103. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/interfaces/parser.py +0 -0
  104. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/__init__.py +0 -0
  105. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/base.py +0 -0
  106. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/biquge/__init__.py +0 -0
  107. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/biquge/main_parser.py +0 -0
  108. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/common/__init__.py +0 -0
  109. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/common/helper.py +0 -0
  110. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/common/main_parser.py +0 -0
  111. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/esjzone/__init__.py +0 -0
  112. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/esjzone/main_parser.py +0 -0
  113. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/linovelib/__init__.py +0 -0
  114. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/linovelib/main_parser.py +0 -0
  115. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qianbi/__init__.py +0 -0
  116. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qianbi/main_parser.py +0 -0
  117. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qidian/__init__.py +0 -0
  118. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qidian/chapter_router.py +0 -0
  119. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qidian/utils/decryptor_fetcher.py +0 -0
  120. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qidian/utils/node_decryptor.py +0 -0
  121. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/sfacg/__init__.py +0 -0
  122. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/sfacg/main_parser.py +0 -0
  123. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/yamibo/__init__.py +0 -0
  124. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/yamibo/main_parser.py +0 -0
  125. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/locales/en.json +0 -0
  126. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/locales/zh.json +0 -0
  127. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/models/__init__.py +0 -0
  128. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/models/browser.py +0 -0
  129. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/models/chapter.py +0 -0
  130. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/models/login.py +0 -0
  131. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/models/site_rules.py +0 -0
  132. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/models/tasks.py +0 -0
  133. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/models/types.py +0 -0
  134. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/resources/css_styles/main.css +0 -0
  135. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/resources/css_styles/volume-intro.css +0 -0
  136. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/resources/images/volume_border.png +0 -0
  137. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/resources/js_scripts/qidian_decrypt_node.js +0 -0
  138. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/resources/json/linovelib_font_map.json +0 -0
  139. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/resources/json/replace_word_map.json +0 -0
  140. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/resources/text/blacklist.txt +0 -0
  141. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/tui/__init__.py +0 -0
  142. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/tui/app.py +0 -0
  143. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/tui/main.py +0 -0
  144. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/tui/screens/__init__.py +0 -0
  145. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/tui/styles/home_layout.tcss +0 -0
  146. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/tui/widgets/richlog_handler.py +0 -0
  147. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/__init__.py +0 -0
  148. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/cache.py +0 -0
  149. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/chapter_storage.py +0 -0
  150. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/constants.py +0 -0
  151. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/cookies.py +0 -0
  152. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/crypto_utils.py +0 -0
  153. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/file_utils/__init__.py +0 -0
  154. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/file_utils/io.py +0 -0
  155. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/file_utils/normalize.py +0 -0
  156. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/file_utils/sanitize.py +0 -0
  157. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/fontocr/__init__.py +0 -0
  158. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/hash_store.py +0 -0
  159. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/hash_utils.py +0 -0
  160. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/i18n.py +0 -0
  161. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/logger.py +0 -0
  162. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/network.py +0 -0
  163. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/state.py +0 -0
  164. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/text_utils/chapter_formatting.py +0 -0
  165. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/text_utils/diff_display.py +0 -0
  166. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/text_utils/font_mapping.py +0 -0
  167. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/time_utils/__init__.py +0 -0
  168. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/time_utils/sleep_utils.py +0 -0
  169. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader.egg-info/dependency_links.txt +0 -0
  170. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader.egg-info/entry_points.txt +0 -0
  171. {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader.egg-info/requires.txt +0 -0
  172. {novel_downloader-1.4.2 → novel_downloader-1.4.4/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: 1.4.2
3
+ Version: 1.4.4
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
  - [项目说明](#项目说明)
@@ -68,6 +68,7 @@ requires = ["setuptools>=61.0", "wheel"]
68
68
  build-backend = "setuptools.build_meta"
69
69
 
70
70
  [tool.setuptools.packages.find]
71
+ where = ["src"]
71
72
  include = ["novel_downloader", "novel_downloader.*"]
72
73
 
73
74
  [tool.setuptools.package-data]
@@ -110,13 +111,14 @@ strict = true
110
111
  [tool.pytest.ini_options]
111
112
  minversion = "7.1"
112
113
  addopts = "--strict-markers"
114
+ pythonpath = ["src"]
113
115
  testpaths = ["tests"]
114
116
 
115
117
  [tool.commitizen]
116
118
  name = "cz_conventional_commits"
117
- version = "1.4.2"
119
+ version = "1.4.4"
118
120
  version_files = [
119
- "novel_downloader/__init__.py"
121
+ "src/novel_downloader/__init__.py"
120
122
  ]
121
123
  update_changelog_on_bump = true
122
124
  changelog_file = "CHANGELOG.md"
@@ -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.4"
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
 
@@ -22,8 +22,8 @@ class QidianBrowser(BaseBrowser):
22
22
 
23
23
  HOMEPAGE_URL = "https://www.qidian.com/"
24
24
  BOOKCASE_URL = "https://my.qidian.com/bookcase/"
25
- BOOK_INFO_URL = "https://book.qidian.com/info/{book_id}/"
26
- # BOOK_INFO_URL = "https://www.qidian.com/book/{book_id}/"
25
+ # BOOK_INFO_URL = "https://book.qidian.com/info/{book_id}/"
26
+ BOOK_INFO_URL = "https://www.qidian.com/book/{book_id}/"
27
27
  CHAPTER_URL = "https://www.qidian.com/chapter/{book_id}/{chapter_id}/"
28
28
 
29
29
  LOGIN_URL = "https://passport.qidian.com/"
@@ -27,7 +27,8 @@ class QidianSession(BaseSession):
27
27
 
28
28
  HOMEPAGE_URL = "https://www.qidian.com/"
29
29
  BOOKCASE_URL = "https://my.qidian.com/bookcase/"
30
- BOOK_INFO_URL = "https://book.qidian.com/info/{book_id}/"
30
+ # BOOK_INFO_URL = "https://book.qidian.com/info/{book_id}/"
31
+ BOOK_INFO_URL = "https://www.qidian.com/book/{book_id}/"
31
32
  CHAPTER_URL = "https://www.qidian.com/chapter/{book_id}/{chapter_id}/"
32
33
 
33
34
  LOGIN_URL = "https://passport.qidian.com/"
@@ -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
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ novel_downloader.core.parsers.qidian.book_info_parser
4
+ -----------------------------------------------------
5
+
6
+ This module provides parsing of Qidian book info pages.
7
+
8
+ It extracts metadata such as title, author, cover URL, update
9
+ time, status, word count, summary, and volume-chapter structure.
10
+ """
11
+
12
+ import logging
13
+ import re
14
+ from datetime import datetime
15
+ from typing import Any
16
+
17
+ from lxml import html
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ def _chapter_url_to_id(url: str) -> str:
23
+ return url.rstrip("/").split("/")[-1]
24
+
25
+
26
+ def parse_book_info(html_str: str) -> dict[str, Any]:
27
+ """
28
+ Extract metadata: title, author, cover_url, update_time, status,
29
+ word_count, summary, and volumes with chapters.
30
+
31
+ :param html_str: Raw HTML of the book info page.
32
+ :return: A dict containing book metadata.
33
+ """
34
+ info: dict[str, Any] = {}
35
+ try:
36
+ doc = html.fromstring(html_str)
37
+
38
+ info["book_name"] = doc.xpath('string(//h1[@id="bookName"])').strip()
39
+
40
+ info["author"] = doc.xpath('string(//a[@class="writer-name"])').strip()
41
+
42
+ book_id = doc.xpath('//a[@id="bookImg"]/@data-bid')[0]
43
+ info[
44
+ "cover_url"
45
+ ] = f"https://bookcover.yuewen.com/qdbimg/349573/{book_id}/600.webp"
46
+
47
+ ut = (
48
+ doc.xpath('string(//span[@class="update-time"])')
49
+ .replace("更新时间:", "")
50
+ .strip()
51
+ )
52
+ if re.match(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$", ut):
53
+ info["update_time"] = ut
54
+ else:
55
+ info["update_time"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
56
+
57
+ info["serial_status"] = doc.xpath(
58
+ 'string(//p[@class="book-attribute"]/span[1])'
59
+ ).strip()
60
+
61
+ tags = doc.xpath('//p[contains(@class,"all-label")]//a/text()')
62
+ info["tags"] = [t.strip() for t in tags if t.strip()]
63
+
64
+ info["word_count"] = doc.xpath('string(//p[@class="count"]/em[1])').strip()
65
+
66
+ summary = doc.xpath('string(//p[@class="intro"])').strip()
67
+ info["summary_brief"] = summary
68
+
69
+ raw = doc.xpath('//p[@id="book-intro-detail"]//text()')
70
+ info["summary"] = "\n".join(line.strip() for line in raw if line.strip())
71
+
72
+ volumes = []
73
+ for vol in doc.xpath('//div[@id="allCatalog"]//div[@class="catalog-volume"]'):
74
+ vol_name = vol.xpath('string(.//h3[@class="volume-name"])').strip()
75
+ vol_name = vol_name.split(chr(183))[0].strip()
76
+ chapters = []
77
+ for li in vol.xpath('.//ul[contains(@class,"volume-chapters")]/li'):
78
+ a = li.xpath('.//a[@class="chapter-name"]')[0]
79
+ title = a.text.strip()
80
+ url = a.get("href")
81
+ chapters.append(
82
+ {"title": title, "url": url, "chapterId": _chapter_url_to_id(url)}
83
+ )
84
+ volumes.append({"volume_name": vol_name, "chapters": chapters})
85
+ info["volumes"] = volumes
86
+
87
+ except Exception as e:
88
+ logger.warning("[Parser] Error parsing book info: %s", e)
89
+
90
+ return info
@@ -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,