novel-downloader 1.3.2__tar.gz → 1.3.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 (172) hide show
  1. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/PKG-INFO +14 -14
  2. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/README.md +13 -13
  3. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/__init__.py +1 -1
  4. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/common/common_async.py +0 -8
  5. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/common/common_sync.py +0 -8
  6. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/qidian/qidian_sync.py +0 -8
  7. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/esjzone/main_parser.py +4 -3
  8. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/base.py +2 -7
  9. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/common/epub.py +21 -33
  10. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/common/main_saver.py +3 -1
  11. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/common/txt.py +1 -2
  12. novel_downloader-1.3.3/novel_downloader/core/savers/epub_utils/__init__.py +35 -0
  13. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/epub_utils/css_builder.py +1 -0
  14. novel_downloader-1.3.3/novel_downloader/core/savers/epub_utils/image_loader.py +89 -0
  15. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/epub_utils/initializer.py +1 -0
  16. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/epub_utils/text_to_html.py +48 -1
  17. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/epub_utils/volume_intro.py +1 -0
  18. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/constants.py +4 -0
  19. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/file_utils/io.py +1 -1
  20. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/network.py +51 -38
  21. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/time_utils/sleep_utils.py +2 -2
  22. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader.egg-info/PKG-INFO +14 -14
  23. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader.egg-info/SOURCES.txt +1 -0
  24. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/pyproject.toml +1 -1
  25. novel_downloader-1.3.2/novel_downloader/core/savers/epub_utils/__init__.py +0 -26
  26. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/LICENSE +0 -0
  27. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/cli/__init__.py +0 -0
  28. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/cli/clean.py +0 -0
  29. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/cli/download.py +0 -0
  30. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/cli/interactive.py +0 -0
  31. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/cli/main.py +0 -0
  32. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/cli/settings.py +0 -0
  33. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/config/__init__.py +0 -0
  34. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/config/adapter.py +0 -0
  35. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/config/loader.py +0 -0
  36. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/config/models.py +0 -0
  37. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/config/site_rules.py +0 -0
  38. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/__init__.py +0 -0
  39. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/__init__.py +0 -0
  40. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/base/__init__.py +0 -0
  41. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/base/base_async.py +0 -0
  42. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/base/base_sync.py +0 -0
  43. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/biquge/__init__.py +0 -0
  44. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/biquge/biquge_async.py +0 -0
  45. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/biquge/biquge_sync.py +0 -0
  46. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/common/__init__.py +0 -0
  47. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/esjzone/__init__.py +0 -0
  48. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/esjzone/esjzone_async.py +0 -0
  49. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/esjzone/esjzone_sync.py +0 -0
  50. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/qianbi/__init__.py +0 -0
  51. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/qianbi/qianbi_async.py +0 -0
  52. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/qianbi/qianbi_sync.py +0 -0
  53. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/qidian/__init__.py +0 -0
  54. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/sfacg/__init__.py +0 -0
  55. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/sfacg/sfacg_async.py +0 -0
  56. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/sfacg/sfacg_sync.py +0 -0
  57. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/yamibo/__init__.py +0 -0
  58. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/yamibo/yamibo_async.py +0 -0
  59. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/yamibo/yamibo_sync.py +0 -0
  60. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/factory/__init__.py +0 -0
  61. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/factory/downloader.py +0 -0
  62. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/factory/parser.py +0 -0
  63. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/factory/requester.py +0 -0
  64. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/factory/saver.py +0 -0
  65. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/interfaces/__init__.py +0 -0
  66. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/interfaces/async_downloader.py +0 -0
  67. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/interfaces/async_requester.py +0 -0
  68. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/interfaces/parser.py +0 -0
  69. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/interfaces/saver.py +0 -0
  70. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/interfaces/sync_downloader.py +0 -0
  71. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/interfaces/sync_requester.py +0 -0
  72. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/__init__.py +0 -0
  73. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/base.py +0 -0
  74. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/biquge/__init__.py +0 -0
  75. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/biquge/main_parser.py +0 -0
  76. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/common/__init__.py +0 -0
  77. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/common/helper.py +0 -0
  78. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/common/main_parser.py +0 -0
  79. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/esjzone/__init__.py +0 -0
  80. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qianbi/__init__.py +0 -0
  81. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qianbi/main_parser.py +0 -0
  82. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/__init__.py +0 -0
  83. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/browser/__init__.py +0 -0
  84. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/browser/chapter_encrypted.py +0 -0
  85. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/browser/chapter_normal.py +0 -0
  86. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/browser/chapter_router.py +0 -0
  87. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/browser/main_parser.py +0 -0
  88. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/session/__init__.py +0 -0
  89. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/session/chapter_encrypted.py +0 -0
  90. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/session/chapter_normal.py +0 -0
  91. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/session/chapter_router.py +0 -0
  92. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/session/main_parser.py +0 -0
  93. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/session/node_decryptor.py +0 -0
  94. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/shared/__init__.py +0 -0
  95. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/shared/book_info_parser.py +0 -0
  96. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/shared/helpers.py +0 -0
  97. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/sfacg/__init__.py +0 -0
  98. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/sfacg/main_parser.py +0 -0
  99. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/yamibo/__init__.py +0 -0
  100. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/yamibo/main_parser.py +0 -0
  101. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/__init__.py +0 -0
  102. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/base/__init__.py +0 -0
  103. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/base/async_session.py +0 -0
  104. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/base/browser.py +0 -0
  105. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/base/session.py +0 -0
  106. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/biquge/__init__.py +0 -0
  107. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/biquge/async_session.py +0 -0
  108. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/biquge/session.py +0 -0
  109. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/common/__init__.py +0 -0
  110. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/common/async_session.py +0 -0
  111. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/common/session.py +0 -0
  112. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/esjzone/__init__.py +0 -0
  113. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/esjzone/async_session.py +0 -0
  114. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/esjzone/session.py +0 -0
  115. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/qianbi/__init__.py +0 -0
  116. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/qianbi/async_session.py +0 -0
  117. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/qianbi/session.py +0 -0
  118. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/qidian/__init__.py +0 -0
  119. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/qidian/broswer.py +0 -0
  120. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/qidian/session.py +0 -0
  121. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/sfacg/__init__.py +0 -0
  122. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/sfacg/async_session.py +0 -0
  123. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/sfacg/session.py +0 -0
  124. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/yamibo/__init__.py +0 -0
  125. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/yamibo/async_session.py +0 -0
  126. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/yamibo/session.py +0 -0
  127. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/__init__.py +0 -0
  128. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/biquge.py +0 -0
  129. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/common/__init__.py +0 -0
  130. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/esjzone.py +0 -0
  131. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/qianbi.py +0 -0
  132. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/qidian.py +0 -0
  133. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/sfacg.py +0 -0
  134. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/yamibo.py +0 -0
  135. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/locales/en.json +0 -0
  136. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/locales/zh.json +0 -0
  137. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/resources/config/rules.toml +0 -0
  138. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/resources/config/settings.toml +0 -0
  139. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/resources/css_styles/main.css +0 -0
  140. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/resources/css_styles/volume-intro.css +0 -0
  141. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/resources/images/volume_border.png +0 -0
  142. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/resources/js_scripts/qidian_decrypt_node.js +0 -0
  143. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/resources/json/replace_word_map.json +0 -0
  144. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/resources/text/blacklist.txt +0 -0
  145. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/__init__.py +0 -0
  146. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/cache.py +0 -0
  147. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/chapter_storage.py +0 -0
  148. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/crypto_utils.py +0 -0
  149. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/file_utils/__init__.py +0 -0
  150. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/file_utils/normalize.py +0 -0
  151. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/file_utils/sanitize.py +0 -0
  152. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/fontocr/__init__.py +0 -0
  153. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/fontocr/ocr_v1.py +0 -0
  154. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/fontocr/ocr_v2.py +0 -0
  155. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/hash_store.py +0 -0
  156. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/hash_utils.py +0 -0
  157. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/i18n.py +0 -0
  158. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/logger.py +0 -0
  159. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/model_loader.py +0 -0
  160. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/state.py +0 -0
  161. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/text_utils/__init__.py +0 -0
  162. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/text_utils/chapter_formatting.py +0 -0
  163. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/text_utils/diff_display.py +0 -0
  164. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/text_utils/font_mapping.py +0 -0
  165. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/text_utils/text_cleaning.py +0 -0
  166. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/time_utils/__init__.py +0 -0
  167. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/time_utils/datetime_utils.py +0 -0
  168. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader.egg-info/dependency_links.txt +0 -0
  169. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader.egg-info/entry_points.txt +0 -0
  170. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader.egg-info/requires.txt +0 -0
  171. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader.egg-info/top_level.txt +0 -0
  172. {novel_downloader-1.3.2 → novel_downloader-1.3.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: novel-downloader
3
- Version: 1.3.2
3
+ Version: 1.3.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
@@ -69,33 +69,33 @@ Dynamic: license-file
69
69
 
70
70
  # novel-downloader
71
71
 
72
- 一个基于 [DrissionPage](https://www.drissionpage.cn) 和 [requests](https://github.com/psf/requests) 的小说下载器。
72
+ 一个基于 [DrissionPage](https://www.drissionpage.cn) 和 [requests](https://github.com/psf/requests) 的小说下载工具/库。
73
73
 
74
74
  ---
75
75
 
76
76
  ## 项目简介
77
77
 
78
- **novel-downloader** 是一个通用的小说下载库 / CLI 工具,
79
- - 大多数支持的站点仅依赖 [`requests`](https://github.com/psf/requests) 进行 HTTP 抓取
78
+ **novel-downloader** 支持多种小说网站的章节抓取与合并导出,
79
+ - **轻量化抓取**: 绝大多数站点仅依赖 `requests` 实现 HTTP 请求, 无需额外浏览器驱动
80
80
  - 对于起点中文网 (Qidian), 可在配置中选择:
81
81
  - `mode: session` : 纯 Requests 模式
82
- - `mode: browser` : 基于 DrissionPage 驱动 Chrome 的浏览器模式 (可处理更复杂的 JS/加密)。
83
- - 若配置 `login_required: true`, 程序会在运行时自动检查登录状态, 支持自动重用历史 Cookie, 仅在首次登录或 Cookie 失效时需要人工介入:
84
- - 若使用 `browser` 模式, 请在程序打开的浏览器窗口登录, 登录后回车继续
85
- - 若使用 `session` 模式, 请根据程序提示粘贴浏览器中登录成功后的 Cookie ([复制 Cookies](https://github.com/BowenZ217/novel-downloader/blob/main/docs/copy-cookies.md))
82
+ - `mode: browser` : 基于 `DrissionPage` 驱动 Chrome 的浏览器模式 (可处理更复杂的 JS/加密)。
83
+ - **自动登录** (可选)
84
+ - 配置 `login_required: true` 后自动检测并重用历史 Cookie
85
+ - 首次登录或 Cookie 失效时:
86
+ - **browser** 模式: 在程序打开的浏览器窗口登录, 登录后回车继续
87
+ - **session** 模式: 根据提示粘贴浏览器中已登录的 Cookie (参考 [复制 Cookies](https://github.com/BowenZ217/novel-downloader/blob/main/docs/copy-cookies.md))
86
88
 
87
89
  ## 功能特性
88
90
 
89
- - 爬取起点中文网的小说章节内容 (支持免费与已订阅章节)
90
- - 爬取部分小说网站
91
- - 断点续爬
92
- - 自动整合所有章节并导出为
91
+ - 抓取起点中文网免费及已订阅章节内容
92
+ - 支持断点续爬, 自动续传未完成任务
93
+ - 自动整合所有章节并导出为:
93
94
  - TXT
94
- - EPUB
95
+ - EPUB (可选包含章节插图)
95
96
  - 支持活动广告过滤:
96
97
  - [x] 章节标题
97
98
  - [ ] 章节正文
98
- - [ ] 作者说
99
99
 
100
100
  ---
101
101
 
@@ -1,32 +1,32 @@
1
1
  # novel-downloader
2
2
 
3
- 一个基于 [DrissionPage](https://www.drissionpage.cn) 和 [requests](https://github.com/psf/requests) 的小说下载器。
3
+ 一个基于 [DrissionPage](https://www.drissionpage.cn) 和 [requests](https://github.com/psf/requests) 的小说下载工具/库。
4
4
 
5
5
  ---
6
6
 
7
7
  ## 项目简介
8
8
 
9
- **novel-downloader** 是一个通用的小说下载库 / CLI 工具,
10
- - 大多数支持的站点仅依赖 [`requests`](https://github.com/psf/requests) 进行 HTTP 抓取
9
+ **novel-downloader** 支持多种小说网站的章节抓取与合并导出,
10
+ - **轻量化抓取**: 绝大多数站点仅依赖 `requests` 实现 HTTP 请求, 无需额外浏览器驱动
11
11
  - 对于起点中文网 (Qidian), 可在配置中选择:
12
12
  - `mode: session` : 纯 Requests 模式
13
- - `mode: browser` : 基于 DrissionPage 驱动 Chrome 的浏览器模式 (可处理更复杂的 JS/加密)。
14
- - 若配置 `login_required: true`, 程序会在运行时自动检查登录状态, 支持自动重用历史 Cookie, 仅在首次登录或 Cookie 失效时需要人工介入:
15
- - 若使用 `browser` 模式, 请在程序打开的浏览器窗口登录, 登录后回车继续
16
- - 若使用 `session` 模式, 请根据程序提示粘贴浏览器中登录成功后的 Cookie ([复制 Cookies](https://github.com/BowenZ217/novel-downloader/blob/main/docs/copy-cookies.md))
13
+ - `mode: browser` : 基于 `DrissionPage` 驱动 Chrome 的浏览器模式 (可处理更复杂的 JS/加密)。
14
+ - **自动登录** (可选)
15
+ - 配置 `login_required: true` 后自动检测并重用历史 Cookie
16
+ - 首次登录或 Cookie 失效时:
17
+ - **browser** 模式: 在程序打开的浏览器窗口登录, 登录后回车继续
18
+ - **session** 模式: 根据提示粘贴浏览器中已登录的 Cookie (参考 [复制 Cookies](https://github.com/BowenZ217/novel-downloader/blob/main/docs/copy-cookies.md))
17
19
 
18
20
  ## 功能特性
19
21
 
20
- - 爬取起点中文网的小说章节内容 (支持免费与已订阅章节)
21
- - 爬取部分小说网站
22
- - 断点续爬
23
- - 自动整合所有章节并导出为
22
+ - 抓取起点中文网免费及已订阅章节内容
23
+ - 支持断点续爬, 自动续传未完成任务
24
+ - 自动整合所有章节并导出为:
24
25
  - TXT
25
- - EPUB
26
+ - EPUB (可选包含章节插图)
26
27
  - 支持活动广告过滤:
27
28
  - [x] 章节标题
28
29
  - [ ] 章节正文
29
- - [ ] 作者说
30
30
 
31
31
  ---
32
32
 
@@ -6,7 +6,7 @@ novel_downloader
6
6
  Core package for the Novel Downloader project.
7
7
  """
8
8
 
9
- __version__ = "1.3.2"
9
+ __version__ = "1.3.3"
10
10
 
11
11
  __author__ = "Saudade Z"
12
12
  __email__ = "saudadez217@gmail.com"
@@ -20,7 +20,6 @@ from novel_downloader.core.interfaces import (
20
20
  )
21
21
  from novel_downloader.utils.chapter_storage import ChapterDict, ChapterStorage
22
22
  from novel_downloader.utils.file_utils import save_as_json, save_as_txt
23
- from novel_downloader.utils.network import download_image_as_bytes
24
23
  from novel_downloader.utils.time_utils import calculate_time_difference
25
24
 
26
25
  logger = logging.getLogger(__name__)
@@ -107,13 +106,6 @@ class CommonAsyncDownloader(BaseAsyncDownloader):
107
106
  else:
108
107
  book_info = json.loads(info_path.read_text("utf-8"))
109
108
 
110
- # download cover
111
- cover_url = book_info.get("cover_url", "")
112
- if cover_url:
113
- await asyncio.get_running_loop().run_in_executor(
114
- None, download_image_as_bytes, cover_url, raw_base
115
- )
116
-
117
109
  # setup queue, semaphore, executor
118
110
  semaphore = asyncio.Semaphore(self.download_workers)
119
111
  queue: asyncio.Queue[tuple[str, list[str]]] = asyncio.Queue()
@@ -19,7 +19,6 @@ from novel_downloader.core.interfaces import (
19
19
  )
20
20
  from novel_downloader.utils.chapter_storage import ChapterStorage
21
21
  from novel_downloader.utils.file_utils import save_as_json, save_as_txt
22
- from novel_downloader.utils.network import download_image_as_bytes
23
22
  from novel_downloader.utils.time_utils import (
24
23
  calculate_time_difference,
25
24
  sleep_with_random_delay,
@@ -119,13 +118,6 @@ class CommonDownloader(BaseDownloader):
119
118
  save_as_json(book_info, info_path)
120
119
  sleep_with_random_delay(wait_time, mul_spread=1.1, max_sleep=wait_time + 2)
121
120
 
122
- # download cover
123
- cover_url = book_info.get("cover_url", "")
124
- if cover_url:
125
- cover_bytes = download_image_as_bytes(cover_url, raw_base)
126
- if not cover_bytes:
127
- logger.warning("%s Failed to download cover: %s", TAG, cover_url)
128
-
129
121
  # enqueue chapters
130
122
  for vol in book_info.get("volumes", []):
131
123
  vol_name = vol.get("volume_name", "")
@@ -19,7 +19,6 @@ from novel_downloader.core.interfaces import (
19
19
  )
20
20
  from novel_downloader.utils.chapter_storage import ChapterStorage
21
21
  from novel_downloader.utils.file_utils import save_as_json, save_as_txt
22
- from novel_downloader.utils.network import download_image_as_bytes
23
22
  from novel_downloader.utils.state import state_mgr
24
23
  from novel_downloader.utils.time_utils import (
25
24
  calculate_time_difference,
@@ -111,13 +110,6 @@ class QidianDownloader(BaseDownloader):
111
110
  save_as_json(book_info, info_path)
112
111
  sleep_with_random_delay(wait_time, mul_spread=1.1, max_sleep=wait_time + 2)
113
112
 
114
- # download cover
115
- cover_url = book_info.get("cover_url", "")
116
- if cover_url:
117
- cover_bytes = download_image_as_bytes(cover_url, raw_base)
118
- if not cover_bytes:
119
- self.logger.warning("%s Failed to download cover: %s", TAG, cover_url)
120
-
121
113
  # enqueue chapters
122
114
  for vol in book_info.get("volumes", []):
123
115
  vol_name = vol.get("volume_name", "")
@@ -85,9 +85,10 @@ class EsjzoneParser(BaseParser):
85
85
 
86
86
  _start_volume("單卷")
87
87
 
88
- nodes = tree.xpath('//div[@id="chapterList"]/details') + tree.xpath(
89
- '//div[@id="chapterList"]/*[not(self::details)]'
90
- )
88
+ # nodes = tree.xpath('//div[@id="chapterList"]/details') + tree.xpath(
89
+ # '//div[@id="chapterList"]/*[not(self::details)]'
90
+ # )
91
+ nodes = tree.xpath('//div[@id="chapterList"]/*')
91
92
 
92
93
  for node in nodes:
93
94
  tag = node.tag.lower()
@@ -40,9 +40,9 @@ class BaseSaver(SaverProtocol, abc.ABC):
40
40
  self._config = config
41
41
 
42
42
  self._base_cache_dir = Path(config.cache_dir)
43
- self._raw_data_dir = Path(config.raw_data_dir)
43
+ self._base_raw_data_dir = Path(config.raw_data_dir)
44
44
  self._output_dir = Path(config.output_dir)
45
- self._raw_data_dir.mkdir(parents=True, exist_ok=True)
45
+ self._base_cache_dir.mkdir(parents=True, exist_ok=True)
46
46
  self._output_dir.mkdir(parents=True, exist_ok=True)
47
47
 
48
48
  self._filename_template = config.filename_template
@@ -158,11 +158,6 @@ class BaseSaver(SaverProtocol, abc.ABC):
158
158
  """Access the output directory for saving files."""
159
159
  return self._output_dir
160
160
 
161
- @property
162
- def raw_data_dir(self) -> Path:
163
- """Access the raw data directory."""
164
- return self._raw_data_dir
165
-
166
161
  @property
167
162
  def filename_template(self) -> str:
168
163
  """Access the filename template."""
@@ -11,53 +11,30 @@ from __future__ import annotations
11
11
  import json
12
12
  from pathlib import Path
13
13
  from typing import TYPE_CHECKING
14
- from urllib.parse import unquote, urlparse
15
14
 
16
15
  from ebooklib import epub
17
16
 
18
17
  from novel_downloader.core.savers.epub_utils import (
18
+ add_images_from_dir,
19
19
  chapter_txt_to_html,
20
20
  create_css_items,
21
21
  create_volume_intro,
22
22
  generate_book_intro_html,
23
23
  init_epub,
24
+ inline_remote_images,
24
25
  )
25
26
  from novel_downloader.utils.constants import (
26
- DEFAULT_IMAGE_SUFFIX,
27
27
  EPUB_OPTIONS,
28
28
  EPUB_TEXT_FOLDER,
29
29
  )
30
30
  from novel_downloader.utils.file_utils import sanitize_filename
31
+ from novel_downloader.utils.network import download_image
31
32
  from novel_downloader.utils.text_utils import clean_chapter_title
32
33
 
33
34
  if TYPE_CHECKING:
34
35
  from .main_saver import CommonSaver
35
36
 
36
37
 
37
- def _image_url_to_filename(url: str) -> str:
38
- """
39
- Parse and sanitize a image filename from a URL.
40
- If no filename or suffix exists, fallback to default name and extension.
41
-
42
- :param url: URL string
43
- :return: Safe filename string
44
- """
45
- if not url:
46
- return ""
47
-
48
- parsed_url = urlparse(url)
49
- path = unquote(parsed_url.path)
50
- filename = Path(path).name
51
-
52
- if not filename:
53
- filename = "image"
54
-
55
- if not Path(filename).suffix:
56
- filename += DEFAULT_IMAGE_SUFFIX
57
-
58
- return filename
59
-
60
-
61
38
  def common_save_as_epub(
62
39
  saver: CommonSaver,
63
40
  book_id: str,
@@ -76,11 +53,12 @@ def common_save_as_epub(
76
53
  :param book_id: Identifier of the novel (used as subdirectory name).
77
54
  """
78
55
  TAG = "[saver]"
79
- site = saver.site
80
56
  config = saver._config
81
57
  # --- Paths & options ---
82
- raw_base = saver.raw_data_dir / site / book_id
58
+ raw_base = saver._raw_data_dir / book_id
59
+ img_dir = saver._cache_dir / book_id / "images"
83
60
  out_dir = saver.output_dir
61
+ img_dir.mkdir(parents=True, exist_ok=True)
84
62
  out_dir.mkdir(parents=True, exist_ok=True)
85
63
 
86
64
  # --- Load book_info.json ---
@@ -100,10 +78,16 @@ def common_save_as_epub(
100
78
  # --- Generate intro + cover ---
101
79
  intro_html = generate_book_intro_html(book_info)
102
80
  cover_path: Path | None = None
103
- if config.include_cover:
104
- cover_filename = _image_url_to_filename(book_info.get("cover_url", ""))
105
- if cover_filename:
106
- cover_path = raw_base / cover_filename
81
+ cover_url = book_info.get("cover_url", "")
82
+ if config.include_cover and cover_url:
83
+ cover_path = download_image(
84
+ cover_url,
85
+ raw_base,
86
+ target_name="cover",
87
+ on_exist="overwrite",
88
+ )
89
+ if not cover_path:
90
+ saver.logger.warning("Failed to download cover from %s", cover_url)
107
91
 
108
92
  # --- Initialize EPUB ---
109
93
  book, spine, toc_list = init_epub(
@@ -162,9 +146,11 @@ def common_save_as_epub(
162
146
  continue
163
147
 
164
148
  title = clean_chapter_title(chapter_data.get("title", "")) or chap_id
149
+ content: str = chapter_data.get("content", "")
150
+ content = inline_remote_images(content, img_dir)
165
151
  chap_html = chapter_txt_to_html(
166
152
  chapter_title=title,
167
- chapter_text=chapter_data.get("content", ""),
153
+ chapter_text=content,
168
154
  author_say=chapter_data.get("author_say", ""),
169
155
  )
170
156
 
@@ -182,6 +168,8 @@ def common_save_as_epub(
182
168
 
183
169
  toc_list.append((section, chapter_items))
184
170
 
171
+ book = add_images_from_dir(book, img_dir)
172
+
185
173
  # --- 5. Finalize EPUB ---
186
174
  saver.logger.info("%s Building TOC and spine...", TAG)
187
175
  book.toc = toc_list
@@ -41,6 +41,8 @@ class CommonSaver(BaseSaver):
41
41
  """
42
42
  super().__init__(config)
43
43
  self._site = site
44
+ self._raw_data_dir = self._base_raw_data_dir / site
45
+ self._cache_dir = self._base_cache_dir / site
44
46
  self._chapter_storage_cache: dict[str, list[ChapterStorage]] = {}
45
47
  self._chap_folders: list[str] = chap_folders or ["chapters"]
46
48
 
@@ -109,7 +111,7 @@ class CommonSaver(BaseSaver):
109
111
  return {}
110
112
 
111
113
  def _init_chapter_storages(self, book_id: str) -> None:
112
- raw_base = self.raw_data_dir / self._site / book_id
114
+ raw_base = self._raw_data_dir / book_id
113
115
  self._chapter_storage_cache[book_id] = [
114
116
  ChapterStorage(
115
117
  raw_base=raw_base,
@@ -45,9 +45,8 @@ def common_save_as_txt(
45
45
  :param book_id: Identifier of the novel (used as subdirectory name).
46
46
  """
47
47
  TAG = "[saver]"
48
- site = saver.site
49
48
  # --- Paths & options ---
50
- raw_base = saver.raw_data_dir / site / book_id
49
+ raw_base = saver._raw_data_dir / book_id
51
50
  out_dir = saver.output_dir
52
51
  out_dir.mkdir(parents=True, exist_ok=True)
53
52
 
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ novel_downloader.core.savers.epub_utils
4
+ ---------------------------------------
5
+
6
+ This package provides utility functions for constructing EPUB files,
7
+ including:
8
+
9
+ - CSS inclusion (css_builder)
10
+ - Image embedding (image_loader)
11
+ - EPUB book initialization (initializer)
12
+ - Chapter text-to-HTML conversion (text_to_html)
13
+ - Volume intro HTML generation (volume_intro)
14
+ """
15
+
16
+ from .css_builder import create_css_items
17
+ from .image_loader import add_images_from_dir, add_images_from_dirs
18
+ from .initializer import init_epub
19
+ from .text_to_html import (
20
+ chapter_txt_to_html,
21
+ generate_book_intro_html,
22
+ inline_remote_images,
23
+ )
24
+ from .volume_intro import create_volume_intro
25
+
26
+ __all__ = [
27
+ "create_css_items",
28
+ "add_images_from_dir",
29
+ "add_images_from_dirs",
30
+ "init_epub",
31
+ "chapter_txt_to_html",
32
+ "create_volume_intro",
33
+ "generate_book_intro_html",
34
+ "inline_remote_images",
35
+ ]
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
3
  novel_downloader.core.savers.epub_utils.css_builder
4
+ ---------------------------------------------------
4
5
 
5
6
  Reads local CSS files and wraps them into epub.EpubItem objects,
6
7
  returning a list ready to be added to the EPUB.
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ novel_downloader.core.savers.epub_utils.image_loader
4
+ ----------------------------------------------------
5
+
6
+ Utilities for embedding image files into an EpubBook.
7
+ """
8
+
9
+ import logging
10
+ from collections.abc import Iterable
11
+ from pathlib import Path
12
+
13
+ from ebooklib import epub
14
+
15
+ from novel_downloader.utils.constants import EPUB_IMAGE_FOLDER
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ _SUPPORTED_IMAGE_MEDIA_TYPES: dict[str, str] = {
20
+ "png": "image/png",
21
+ "jpg": "image/jpeg",
22
+ "jpeg": "image/jpeg",
23
+ "gif": "image/gif",
24
+ "svg": "image/svg+xml",
25
+ "webp": "image/webp",
26
+ }
27
+ _DEFAULT_IMAGE_MEDIA_TYPE = "image/jpeg"
28
+
29
+
30
+ def add_images_from_dir(
31
+ book: epub.EpubBook,
32
+ image_dir: str | Path,
33
+ ) -> epub.EpubBook:
34
+ """
35
+ Load every file in `image_dir` into the EPUB's image folder.
36
+
37
+ :param book: The EpubBook object to modify.
38
+ :param image_dir: Path to the directory containing image files.
39
+ :return: The same EpubBook instance, with images added.
40
+ """
41
+ image_dir = Path(image_dir)
42
+ if not image_dir.is_dir():
43
+ logger.warning("Image directory not found or not a directory: %s", image_dir)
44
+ return book
45
+
46
+ for img_path in image_dir.iterdir():
47
+ if not img_path.is_file():
48
+ continue
49
+
50
+ suffix = img_path.suffix.lower().lstrip(".")
51
+ media_type = _SUPPORTED_IMAGE_MEDIA_TYPES.get(suffix)
52
+ if media_type is None:
53
+ media_type = _DEFAULT_IMAGE_MEDIA_TYPE
54
+ logger.warning(
55
+ "Unknown image suffix '%s' - defaulting media_type to %s",
56
+ suffix,
57
+ media_type,
58
+ )
59
+
60
+ try:
61
+ content = img_path.read_bytes()
62
+ item = epub.EpubItem(
63
+ uid=f"img_{img_path.stem}",
64
+ file_name=f"{EPUB_IMAGE_FOLDER}/{img_path.name}",
65
+ media_type=media_type,
66
+ content=content,
67
+ )
68
+ book.add_item(item)
69
+ logger.info("Embedded image: %s", img_path.name)
70
+ except Exception:
71
+ logger.exception("Failed to embed image %s", img_path)
72
+
73
+ return book
74
+
75
+
76
+ def add_images_from_dirs(
77
+ book: epub.EpubBook,
78
+ image_dirs: Iterable[str | Path],
79
+ ) -> epub.EpubBook:
80
+ """
81
+ Add all images from multiple directories into the given EpubBook.
82
+
83
+ :param book: The EpubBook object to modify.
84
+ :param image_dirs: An iterable of directory paths to scan for images.
85
+ :return: The same EpubBook instance, with all images added.
86
+ """
87
+ for img_dir in image_dirs:
88
+ book = add_images_from_dir(book, img_dir)
89
+ return book
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
3
  novel_downloader.core.savers.epub_utils.initializer
4
+ ---------------------------------------------------
4
5
 
5
6
  Initializes an epub.EpubBook object, sets metadata
6
7
  (identifier, title, author, language, description),
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
3
  novel_downloader.core.savers.epub_utils.text_to_html
4
+ ----------------------------------------------------
4
5
 
5
6
  Module for converting raw chapter text to formatted HTML,
6
7
  with automatic word correction and optional image/tag support.
@@ -8,13 +9,23 @@ with automatic word correction and optional image/tag support.
8
9
 
9
10
  import json
10
11
  import logging
12
+ import re
13
+ from pathlib import Path
11
14
  from typing import Any
12
15
 
13
- from novel_downloader.utils.constants import REPLACE_WORD_MAP_PATH
16
+ from novel_downloader.utils.constants import (
17
+ EPUB_IMAGE_WRAPPER,
18
+ REPLACE_WORD_MAP_PATH,
19
+ )
20
+ from novel_downloader.utils.network import download_image
14
21
  from novel_downloader.utils.text_utils import diff_inline_display
15
22
 
16
23
  logger = logging.getLogger(__name__)
17
24
 
25
+ _IMG_TAG_PATTERN = re.compile(
26
+ r'<img\s+[^>]*src=[\'"]([^\'"]+)[\'"][^>]*>', re.IGNORECASE
27
+ )
28
+
18
29
 
19
30
  # Load and sort replacement map from JSON
20
31
  try:
@@ -87,6 +98,42 @@ def chapter_txt_to_html(
87
98
  return "\n".join(html_parts)
88
99
 
89
100
 
101
+ def inline_remote_images(
102
+ content: str,
103
+ image_dir: str | Path,
104
+ ) -> str:
105
+ """
106
+ Download every remote <img src="…"> in `content` into `image_dir`,
107
+ and replace the original tag with EPUB_IMAGE_WRAPPER
108
+ pointing to the local filename.
109
+
110
+ :param content: HTML/text of the chapter containing <img> tags.
111
+ :param image_dir: Directory to save downloaded images into.
112
+ :return: Modified content with local image references.
113
+ """
114
+
115
+ def _replace(match: re.Match[str]) -> str:
116
+ url = match.group(1)
117
+ try:
118
+ # download_image returns a Path or None
119
+ local_path = download_image(
120
+ url, image_dir, target_name=None, on_exist="skip"
121
+ )
122
+ if not local_path:
123
+ logger.warning(
124
+ "Failed to download image, leaving original tag: %s", url
125
+ )
126
+ return match.group(0)
127
+
128
+ # wrap with the EPUB_IMAGE_WRAPPER, inserting just the filename
129
+ return EPUB_IMAGE_WRAPPER.format(filename=local_path.name)
130
+ except Exception:
131
+ logger.exception("Error processing image URL: %s", url)
132
+ return match.group(0)
133
+
134
+ return _IMG_TAG_PATTERN.sub(_replace, content)
135
+
136
+
90
137
  def generate_book_intro_html(book_info: dict[str, Any]) -> str:
91
138
  """
92
139
  Generate HTML string for a book's information and summary.
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
3
  novel_downloader.core.savers.epub_utils.volume_intro
4
+ ----------------------------------------------------
4
5
 
5
6
  Responsible for generating HTML code for volume introduction pages,
6
7
  including two style variants and a unified entry point.
@@ -116,6 +116,10 @@ BLACKLIST_PATH = files("novel_downloader.resources.text").joinpath("blacklist.tx
116
116
  EPUB_IMAGE_FOLDER = "Images"
117
117
  EPUB_TEXT_FOLDER = "Text"
118
118
 
119
+ EPUB_IMAGE_WRAPPER = (
120
+ '<div class="duokan-image-single illus"><img src="../Images/{filename}" /></div>'
121
+ )
122
+
119
123
  EPUB_OPTIONS = {
120
124
  # guide 是 EPUB 2 的一个部分, 包含封面, 目录, 索引等重要导航信息
121
125
  "epub2_guide": True,
@@ -103,7 +103,7 @@ def _write_file(
103
103
  tmp.write(content_to_write)
104
104
  tmp_path = Path(tmp.name)
105
105
  tmp_path.replace(path)
106
- logger.info("[file] '%s' written successfully", path)
106
+ logger.debug("[file] '%s' written successfully", path)
107
107
  return True
108
108
  except Exception as exc:
109
109
  logger.warning("[file] Error writing %r: %s", path, exc)