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.
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/PKG-INFO +14 -14
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/README.md +13 -13
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/__init__.py +1 -1
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/common/common_async.py +0 -8
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/common/common_sync.py +0 -8
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/qidian/qidian_sync.py +0 -8
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/esjzone/main_parser.py +4 -3
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/base.py +2 -7
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/common/epub.py +21 -33
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/common/main_saver.py +3 -1
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/common/txt.py +1 -2
- novel_downloader-1.3.3/novel_downloader/core/savers/epub_utils/__init__.py +35 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/epub_utils/css_builder.py +1 -0
- novel_downloader-1.3.3/novel_downloader/core/savers/epub_utils/image_loader.py +89 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/epub_utils/initializer.py +1 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/epub_utils/text_to_html.py +48 -1
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/epub_utils/volume_intro.py +1 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/constants.py +4 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/file_utils/io.py +1 -1
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/network.py +51 -38
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/time_utils/sleep_utils.py +2 -2
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader.egg-info/PKG-INFO +14 -14
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader.egg-info/SOURCES.txt +1 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/pyproject.toml +1 -1
- novel_downloader-1.3.2/novel_downloader/core/savers/epub_utils/__init__.py +0 -26
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/LICENSE +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/cli/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/cli/clean.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/cli/download.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/cli/interactive.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/cli/main.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/cli/settings.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/config/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/config/adapter.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/config/loader.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/config/models.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/config/site_rules.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/base/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/base/base_async.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/base/base_sync.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/biquge/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/biquge/biquge_async.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/biquge/biquge_sync.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/common/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/esjzone/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/esjzone/esjzone_async.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/esjzone/esjzone_sync.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/qianbi/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/qianbi/qianbi_async.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/qianbi/qianbi_sync.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/qidian/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/sfacg/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/sfacg/sfacg_async.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/sfacg/sfacg_sync.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/yamibo/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/yamibo/yamibo_async.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/downloaders/yamibo/yamibo_sync.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/factory/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/factory/downloader.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/factory/parser.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/factory/requester.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/factory/saver.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/interfaces/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/interfaces/async_downloader.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/interfaces/async_requester.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/interfaces/parser.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/interfaces/saver.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/interfaces/sync_downloader.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/interfaces/sync_requester.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/base.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/biquge/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/biquge/main_parser.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/common/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/common/helper.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/common/main_parser.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/esjzone/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qianbi/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qianbi/main_parser.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/browser/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/browser/chapter_encrypted.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/browser/chapter_normal.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/browser/chapter_router.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/browser/main_parser.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/session/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/session/chapter_encrypted.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/session/chapter_normal.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/session/chapter_router.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/session/main_parser.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/session/node_decryptor.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/shared/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/shared/book_info_parser.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/qidian/shared/helpers.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/sfacg/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/sfacg/main_parser.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/yamibo/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/parsers/yamibo/main_parser.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/base/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/base/async_session.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/base/browser.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/base/session.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/biquge/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/biquge/async_session.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/biquge/session.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/common/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/common/async_session.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/common/session.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/esjzone/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/esjzone/async_session.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/esjzone/session.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/qianbi/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/qianbi/async_session.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/qianbi/session.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/qidian/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/qidian/broswer.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/qidian/session.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/sfacg/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/sfacg/async_session.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/sfacg/session.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/yamibo/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/yamibo/async_session.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/requesters/yamibo/session.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/biquge.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/common/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/esjzone.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/qianbi.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/qidian.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/sfacg.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/yamibo.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/locales/en.json +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/locales/zh.json +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/resources/config/rules.toml +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/resources/config/settings.toml +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/resources/css_styles/main.css +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/resources/css_styles/volume-intro.css +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/resources/images/volume_border.png +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/resources/js_scripts/qidian_decrypt_node.js +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/resources/json/replace_word_map.json +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/resources/text/blacklist.txt +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/cache.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/chapter_storage.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/crypto_utils.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/file_utils/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/file_utils/normalize.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/file_utils/sanitize.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/fontocr/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/fontocr/ocr_v1.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/fontocr/ocr_v2.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/hash_store.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/hash_utils.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/i18n.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/logger.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/model_loader.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/state.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/text_utils/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/text_utils/chapter_formatting.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/text_utils/diff_display.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/text_utils/font_mapping.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/text_utils/text_cleaning.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/time_utils/__init__.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/utils/time_utils/datetime_utils.py +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader.egg-info/dependency_links.txt +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader.egg-info/entry_points.txt +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader.egg-info/requires.txt +0 -0
- {novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader.egg-info/top_level.txt +0 -0
- {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.
|
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**
|
79
|
-
-
|
78
|
+
**novel-downloader** 支持多种小说网站的章节抓取与合并导出,
|
79
|
+
- **轻量化抓取**: 绝大多数站点仅依赖 `requests` 实现 HTTP 请求, 无需额外浏览器驱动
|
80
80
|
- 对于起点中文网 (Qidian), 可在配置中选择:
|
81
81
|
- `mode: session` : 纯 Requests 模式
|
82
|
-
- `mode: browser` : 基于 DrissionPage 驱动 Chrome 的浏览器模式 (可处理更复杂的 JS/加密)。
|
83
|
-
-
|
84
|
-
-
|
85
|
-
-
|
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**
|
10
|
-
-
|
9
|
+
**novel-downloader** 支持多种小说网站的章节抓取与合并导出,
|
10
|
+
- **轻量化抓取**: 绝大多数站点仅依赖 `requests` 实现 HTTP 请求, 无需额外浏览器驱动
|
11
11
|
- 对于起点中文网 (Qidian), 可在配置中选择:
|
12
12
|
- `mode: session` : 纯 Requests 模式
|
13
|
-
- `mode: browser` : 基于 DrissionPage 驱动 Chrome 的浏览器模式 (可处理更复杂的 JS/加密)。
|
14
|
-
-
|
15
|
-
-
|
16
|
-
-
|
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
|
|
@@ -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
|
-
|
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.
|
43
|
+
self._base_raw_data_dir = Path(config.raw_data_dir)
|
44
44
|
self._output_dir = Path(config.output_dir)
|
45
|
-
self.
|
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."""
|
{novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/common/epub.py
RENAMED
@@ -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.
|
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
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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=
|
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
|
{novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/common/main_saver.py
RENAMED
@@ -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.
|
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,
|
{novel_downloader-1.3.2 → novel_downloader-1.3.3}/novel_downloader/core/savers/common/txt.py
RENAMED
@@ -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.
|
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
|
+
]
|
@@ -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.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
|
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.
|
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)
|