novel-downloader 1.4.1__tar.gz → 1.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/PKG-INFO +4 -2
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/README.md +2 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/__init__.py +1 -1
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/cli/download.py +69 -10
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/config/adapter.py +42 -9
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/downloaders/base.py +26 -22
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/downloaders/common.py +41 -5
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/downloaders/qidian.py +60 -32
- novel_downloader-1.4.2/novel_downloader/core/exporters/common/epub.py +277 -0
- novel_downloader-1.4.2/novel_downloader/core/exporters/epub_util.py +1358 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/exporters/linovelib/epub.py +147 -190
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/qidian/browser.py +62 -10
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/interfaces/downloader.py +13 -12
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/locales/en.json +2 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/locales/zh.json +2 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/models/__init__.py +2 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/models/config.py +8 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/tui/screens/home.py +5 -4
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/constants.py +0 -29
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader.egg-info/PKG-INFO +4 -2
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader.egg-info/SOURCES.txt +1 -6
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader.egg-info/requires.txt +1 -1
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/pyproject.toml +3 -3
- novel_downloader-1.4.1/novel_downloader/core/exporters/common/epub.py +0 -192
- novel_downloader-1.4.1/novel_downloader/core/exporters/epub_utils/__init__.py +0 -40
- novel_downloader-1.4.1/novel_downloader/core/exporters/epub_utils/css_builder.py +0 -75
- novel_downloader-1.4.1/novel_downloader/core/exporters/epub_utils/image_loader.py +0 -131
- novel_downloader-1.4.1/novel_downloader/core/exporters/epub_utils/initializer.py +0 -100
- novel_downloader-1.4.1/novel_downloader/core/exporters/epub_utils/text_to_html.py +0 -178
- novel_downloader-1.4.1/novel_downloader/core/exporters/epub_utils/volume_intro.py +0 -60
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/LICENSE +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/cli/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/cli/clean.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/cli/config.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/cli/export.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/cli/main.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/config/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/config/loader.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/config/site_rules.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/downloaders/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/downloaders/biquge.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/downloaders/esjzone.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/downloaders/linovelib.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/downloaders/qianbi.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/downloaders/sfacg.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/downloaders/yamibo.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/exporters/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/exporters/base.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/exporters/biquge.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/exporters/common/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/exporters/common/main_exporter.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/exporters/common/txt.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/exporters/esjzone.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/exporters/linovelib/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/exporters/linovelib/main_exporter.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/exporters/linovelib/txt.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/exporters/qianbi.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/exporters/qidian.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/exporters/sfacg.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/exporters/yamibo.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/factory/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/factory/downloader.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/factory/exporter.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/factory/fetcher.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/factory/parser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/base/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/base/browser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/base/rate_limiter.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/base/session.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/biquge/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/biquge/browser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/biquge/session.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/common/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/common/browser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/common/session.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/esjzone/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/esjzone/browser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/esjzone/session.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/linovelib/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/linovelib/browser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/linovelib/session.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/qianbi/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/qianbi/browser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/qianbi/session.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/qidian/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/qidian/session.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/sfacg/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/sfacg/browser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/sfacg/session.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/yamibo/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/yamibo/browser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/fetchers/yamibo/session.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/interfaces/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/interfaces/exporter.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/interfaces/fetcher.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/interfaces/parser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/base.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/biquge/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/biquge/main_parser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/common/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/common/helper.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/common/main_parser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/esjzone/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/esjzone/main_parser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/linovelib/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/linovelib/main_parser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/qianbi/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/qianbi/main_parser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/qidian/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/qidian/book_info_parser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/qidian/chapter_encrypted.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/qidian/chapter_normal.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/qidian/chapter_router.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/qidian/main_parser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/qidian/utils/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/qidian/utils/decryptor_fetcher.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/qidian/utils/helpers.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/qidian/utils/node_decryptor.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/sfacg/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/sfacg/main_parser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/yamibo/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/parsers/yamibo/main_parser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/models/browser.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/models/chapter.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/models/login.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/models/site_rules.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/models/tasks.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/models/types.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/resources/config/settings.toml +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/resources/css_styles/main.css +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/resources/css_styles/volume-intro.css +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/resources/images/volume_border.png +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/resources/js_scripts/qidian_decrypt_node.js +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/resources/json/linovelib_font_map.json +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/resources/json/replace_word_map.json +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/resources/text/blacklist.txt +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/tui/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/tui/app.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/tui/main.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/tui/screens/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/tui/screens/login.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/tui/styles/home_layout.tcss +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/tui/widgets/richlog_handler.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/cache.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/chapter_storage.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/cookies.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/crypto_utils.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/file_utils/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/file_utils/io.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/file_utils/normalize.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/file_utils/sanitize.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/fontocr/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/fontocr/ocr_v1.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/fontocr/ocr_v2.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/hash_store.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/hash_utils.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/i18n.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/logger.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/model_loader.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/network.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/state.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/text_utils/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/text_utils/chapter_formatting.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/text_utils/diff_display.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/text_utils/font_mapping.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/text_utils/text_cleaning.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/time_utils/__init__.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/time_utils/datetime_utils.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/utils/time_utils/sleep_utils.py +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader.egg-info/dependency_links.txt +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader.egg-info/entry_points.txt +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader.egg-info/top_level.txt +0 -0
- {novel_downloader-1.4.1 → novel_downloader-1.4.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: novel-downloader
|
3
|
-
Version: 1.4.
|
3
|
+
Version: 1.4.2
|
4
4
|
Summary: A command-line tool for downloading Chinese web novels from Qidian and similar platforms.
|
5
5
|
Author-email: Saudade Z <saudadez217@gmail.com>
|
6
6
|
License: MIT License
|
@@ -46,7 +46,6 @@ Requires-Dist: aiohttp
|
|
46
46
|
Requires-Dist: playwright
|
47
47
|
Requires-Dist: lxml
|
48
48
|
Requires-Dist: platformdirs
|
49
|
-
Requires-Dist: ebooklib
|
50
49
|
Provides-Extra: dev
|
51
50
|
Requires-Dist: black; extra == "dev"
|
52
51
|
Requires-Dist: mypy; extra == "dev"
|
@@ -55,6 +54,7 @@ Requires-Dist: pytest; extra == "dev"
|
|
55
54
|
Requires-Dist: pytest-cov; extra == "dev"
|
56
55
|
Requires-Dist: pytest-mock; extra == "dev"
|
57
56
|
Requires-Dist: types-requests; extra == "dev"
|
57
|
+
Requires-Dist: types-lxml; extra == "dev"
|
58
58
|
Requires-Dist: types-PyYAML; extra == "dev"
|
59
59
|
Requires-Dist: pre-commit; extra == "dev"
|
60
60
|
Requires-Dist: commitizen; extra == "dev"
|
@@ -109,6 +109,8 @@ playwright install
|
|
109
109
|
pip install novel-downloader[font-recovery]
|
110
110
|
```
|
111
111
|
|
112
|
+
- 详细可见: [安装](https://github.com/BowenZ217/novel-downloader/blob/main/docs/1-installation.md)
|
113
|
+
|
112
114
|
---
|
113
115
|
|
114
116
|
### CLI 模式
|
@@ -9,6 +9,7 @@ Download novels from supported sites via CLI.
|
|
9
9
|
import asyncio
|
10
10
|
import getpass
|
11
11
|
from argparse import Namespace, _SubParsersAction
|
12
|
+
from collections.abc import Iterable
|
12
13
|
from dataclasses import asdict
|
13
14
|
from pathlib import Path
|
14
15
|
from typing import Any
|
@@ -21,7 +22,7 @@ from novel_downloader.core.factory import (
|
|
21
22
|
get_parser,
|
22
23
|
)
|
23
24
|
from novel_downloader.core.interfaces import FetcherProtocol
|
24
|
-
from novel_downloader.models import LoginField
|
25
|
+
from novel_downloader.models import BookConfig, LoginField
|
25
26
|
from novel_downloader.utils.cookies import resolve_cookies
|
26
27
|
from novel_downloader.utils.i18n import t
|
27
28
|
from novel_downloader.utils.logger import setup_logging
|
@@ -36,6 +37,9 @@ def register_download_subcommand(subparsers: _SubParsersAction) -> None: # type
|
|
36
37
|
)
|
37
38
|
parser.add_argument("--config", type=str, help=t("help_config"))
|
38
39
|
|
40
|
+
parser.add_argument("--start", type=str, help=t("download_option_start"))
|
41
|
+
parser.add_argument("--end", type=str, help=t("download_option_end"))
|
42
|
+
|
39
43
|
parser.set_defaults(func=handle_download)
|
40
44
|
|
41
45
|
|
@@ -44,7 +48,11 @@ def handle_download(args: Namespace) -> None:
|
|
44
48
|
|
45
49
|
site: str = args.site
|
46
50
|
config_path: Path | None = Path(args.config) if args.config else None
|
47
|
-
book_ids: list[
|
51
|
+
book_ids: list[BookConfig] = _cli_args_to_book_configs(
|
52
|
+
args.book_ids,
|
53
|
+
args.start,
|
54
|
+
args.end,
|
55
|
+
)
|
48
56
|
|
49
57
|
print(t("download_site_info", site=site))
|
50
58
|
|
@@ -63,25 +71,73 @@ def handle_download(args: Namespace) -> None:
|
|
63
71
|
print(t("download_fail_get_ids", err=str(e)))
|
64
72
|
return
|
65
73
|
|
66
|
-
|
67
|
-
valid_book_ids = set(book_ids) - invalid_ids
|
74
|
+
valid_books = _filter_valid_book_configs(book_ids)
|
68
75
|
|
69
76
|
if not book_ids:
|
70
77
|
print(t("download_no_ids"))
|
71
78
|
return
|
72
79
|
|
73
|
-
if not
|
80
|
+
if not valid_books:
|
74
81
|
print(t("download_only_example", example="0000000000"))
|
75
82
|
print(t("download_edit_config"))
|
76
83
|
return
|
77
84
|
|
78
|
-
asyncio.run(_download(adapter, site,
|
85
|
+
asyncio.run(_download(adapter, site, valid_books))
|
86
|
+
|
87
|
+
|
88
|
+
def _cli_args_to_book_configs(
|
89
|
+
book_ids: list[str],
|
90
|
+
start_id: str | None,
|
91
|
+
end_id: str | None,
|
92
|
+
) -> list[BookConfig]:
|
93
|
+
"""
|
94
|
+
Convert CLI book_ids and optional --start/--end into a list of BookConfig.
|
95
|
+
Only the first book_id uses start/end; others are minimal.
|
96
|
+
"""
|
97
|
+
if not book_ids:
|
98
|
+
return []
|
99
|
+
|
100
|
+
result: list[BookConfig] = []
|
101
|
+
|
102
|
+
first: BookConfig = {"book_id": book_ids[0]}
|
103
|
+
if start_id:
|
104
|
+
first["start_id"] = start_id
|
105
|
+
if end_id:
|
106
|
+
first["end_id"] = end_id
|
107
|
+
result.append(first)
|
108
|
+
|
109
|
+
for book_id in book_ids[1:]:
|
110
|
+
result.append({"book_id": book_id})
|
111
|
+
|
112
|
+
return result
|
113
|
+
|
114
|
+
|
115
|
+
def _filter_valid_book_configs(
|
116
|
+
books: list[BookConfig],
|
117
|
+
invalid_ids: Iterable[str] = ("0000000000",),
|
118
|
+
) -> list[BookConfig]:
|
119
|
+
"""
|
120
|
+
Filter a list of BookConfig:
|
121
|
+
- Removes entries with invalid or placeholder book_ids
|
122
|
+
- Deduplicates based on book_id while preserving order
|
123
|
+
"""
|
124
|
+
seen = set(invalid_ids)
|
125
|
+
result: list[BookConfig] = []
|
126
|
+
|
127
|
+
for book in books:
|
128
|
+
book_id = book["book_id"]
|
129
|
+
if book_id in seen:
|
130
|
+
continue
|
131
|
+
seen.add(book_id)
|
132
|
+
result.append(book)
|
133
|
+
|
134
|
+
return result
|
79
135
|
|
80
136
|
|
81
137
|
async def _download(
|
82
138
|
adapter: ConfigAdapter,
|
83
139
|
site: str,
|
84
|
-
|
140
|
+
valid_books: list[BookConfig],
|
85
141
|
) -> None:
|
86
142
|
downloader_cfg = adapter.get_downloader_config()
|
87
143
|
fetcher_cfg = adapter.get_fetcher_config()
|
@@ -110,9 +166,12 @@ async def _download(
|
|
110
166
|
config=downloader_cfg,
|
111
167
|
)
|
112
168
|
|
113
|
-
for
|
114
|
-
print(t("download_downloading", book_id=book_id, site=site))
|
115
|
-
await downloader.download(
|
169
|
+
for book in valid_books:
|
170
|
+
print(t("download_downloading", book_id=book["book_id"], site=site))
|
171
|
+
await downloader.download(
|
172
|
+
book,
|
173
|
+
progress_hook=_print_progress,
|
174
|
+
)
|
116
175
|
|
117
176
|
if downloader_cfg.login_required and fetcher.is_logged_in:
|
118
177
|
await fetcher.save_state()
|
@@ -10,6 +10,7 @@ site name into structured dataclass-based config models.
|
|
10
10
|
from typing import Any
|
11
11
|
|
12
12
|
from novel_downloader.models import (
|
13
|
+
BookConfig,
|
13
14
|
DownloaderConfig,
|
14
15
|
ExporterConfig,
|
15
16
|
FetcherConfig,
|
@@ -169,22 +170,54 @@ class ConfigAdapter:
|
|
169
170
|
split_mode=site_cfg.get("split_mode", "book"),
|
170
171
|
)
|
171
172
|
|
172
|
-
def get_book_ids(self) -> list[
|
173
|
+
def get_book_ids(self) -> list[BookConfig]:
|
173
174
|
"""
|
174
175
|
从 config["sites"][site]["book_ids"] 中提取目标书籍列表
|
175
176
|
"""
|
176
177
|
site_cfg = self._get_site_cfg()
|
177
|
-
|
178
|
+
raw = site_cfg.get("book_ids", [])
|
178
179
|
|
179
|
-
if isinstance(
|
180
|
-
return [
|
180
|
+
if isinstance(raw, str | int):
|
181
|
+
return [{"book_id": str(raw)}]
|
181
182
|
|
182
|
-
if isinstance(
|
183
|
-
return [
|
183
|
+
if isinstance(raw, dict):
|
184
|
+
return [self._dict_to_book_config(raw)]
|
184
185
|
|
185
|
-
if not isinstance(
|
186
|
+
if not isinstance(raw, list):
|
186
187
|
raise ValueError(
|
187
|
-
f"book_ids must be a list or string, got {type(
|
188
|
+
f"book_ids must be a list or string, got {type(raw).__name__}"
|
188
189
|
)
|
189
190
|
|
190
|
-
|
191
|
+
result: list[BookConfig] = []
|
192
|
+
for item in raw:
|
193
|
+
try:
|
194
|
+
if isinstance(item, str | int):
|
195
|
+
result.append({"book_id": str(item)})
|
196
|
+
elif isinstance(item, dict):
|
197
|
+
result.append(self._dict_to_book_config(item))
|
198
|
+
except ValueError:
|
199
|
+
continue
|
200
|
+
|
201
|
+
return result
|
202
|
+
|
203
|
+
@staticmethod
|
204
|
+
def _dict_to_book_config(data: dict[str, Any]) -> BookConfig:
|
205
|
+
"""
|
206
|
+
Converts a dict to BookConfig with type normalization.
|
207
|
+
Raises ValueError if 'book_id' is missing.
|
208
|
+
"""
|
209
|
+
if "book_id" not in data:
|
210
|
+
raise ValueError("Missing required field 'book_id'")
|
211
|
+
|
212
|
+
result: BookConfig = {"book_id": str(data["book_id"])}
|
213
|
+
|
214
|
+
if "start_id" in data:
|
215
|
+
result["start_id"] = str(data["start_id"])
|
216
|
+
|
217
|
+
if "end_id" in data:
|
218
|
+
result["end_id"] = str(data["end_id"])
|
219
|
+
|
220
|
+
if "ignore_ids" in data:
|
221
|
+
result["ignore_ids"] = [str(x) for x in data["ignore_ids"]]
|
222
|
+
|
223
|
+
return result
|
@@ -19,7 +19,7 @@ from novel_downloader.core.interfaces import (
|
|
19
19
|
FetcherProtocol,
|
20
20
|
ParserProtocol,
|
21
21
|
)
|
22
|
-
from novel_downloader.models import DownloaderConfig
|
22
|
+
from novel_downloader.models import BookConfig, DownloaderConfig
|
23
23
|
|
24
24
|
|
25
25
|
class BaseDownloader(DownloaderProtocol, abc.ABC):
|
@@ -53,7 +53,7 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
|
|
53
53
|
|
54
54
|
async def download_many(
|
55
55
|
self,
|
56
|
-
|
56
|
+
books: list[BookConfig],
|
57
57
|
*,
|
58
58
|
progress_hook: Callable[[int, int], Awaitable[None]] | None = None,
|
59
59
|
**kwargs: Any,
|
@@ -61,33 +61,34 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
|
|
61
61
|
"""
|
62
62
|
Download multiple books with pre-download hook and error handling.
|
63
63
|
|
64
|
-
:param
|
65
|
-
:param progress_hook:
|
64
|
+
:param books: List of BookConfig entries.
|
65
|
+
:param progress_hook: Optional async callback after each chapter.
|
66
66
|
args: completed_count, total_count.
|
67
67
|
"""
|
68
68
|
if not await self._ensure_ready():
|
69
|
+
book_ids = [b["book_id"] for b in books]
|
69
70
|
self.logger.warning(
|
70
|
-
"[%s] login failed, skipping download of %s",
|
71
|
+
"[%s] login failed, skipping download of books: %s",
|
71
72
|
self._site,
|
72
|
-
book_ids,
|
73
|
+
", ".join(book_ids) or "<none>",
|
73
74
|
)
|
74
75
|
return
|
75
76
|
|
76
|
-
for
|
77
|
+
for book in books:
|
77
78
|
try:
|
78
79
|
await self._download_one(
|
79
|
-
|
80
|
+
book,
|
80
81
|
progress_hook=progress_hook,
|
81
82
|
**kwargs,
|
82
83
|
)
|
83
84
|
except Exception as e:
|
84
|
-
self._handle_download_exception(
|
85
|
+
self._handle_download_exception(book, e)
|
85
86
|
|
86
87
|
await self._finalize()
|
87
88
|
|
88
89
|
async def download(
|
89
90
|
self,
|
90
|
-
|
91
|
+
book: BookConfig,
|
91
92
|
*,
|
92
93
|
progress_hook: Callable[[int, int], Awaitable[None]] | None = None,
|
93
94
|
**kwargs: Any,
|
@@ -95,33 +96,34 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
|
|
95
96
|
"""
|
96
97
|
Download a single book with pre-download hook and error handling.
|
97
98
|
|
98
|
-
:param
|
99
|
-
:param progress_hook:
|
99
|
+
:param book: BookConfig with at least 'book_id'.
|
100
|
+
:param progress_hook: Optional async callback after each chapter.
|
100
101
|
args: completed_count, total_count.
|
101
102
|
"""
|
102
103
|
if not await self._ensure_ready():
|
103
104
|
self.logger.warning(
|
104
|
-
"[%s] login failed, skipping download of %s",
|
105
|
+
"[%s] login failed, skipping download of book: %s (%s-%s)",
|
105
106
|
self._site,
|
106
|
-
book_id,
|
107
|
+
book["book_id"],
|
108
|
+
book.get("start_id", "-"),
|
109
|
+
book.get("end_id", "-"),
|
107
110
|
)
|
108
|
-
return
|
109
111
|
|
110
112
|
try:
|
111
113
|
await self._download_one(
|
112
|
-
|
114
|
+
book,
|
113
115
|
progress_hook=progress_hook,
|
114
116
|
**kwargs,
|
115
117
|
)
|
116
118
|
except Exception as e:
|
117
|
-
self._handle_download_exception(
|
119
|
+
self._handle_download_exception(book, e)
|
118
120
|
|
119
121
|
await self._finalize()
|
120
122
|
|
121
123
|
@abc.abstractmethod
|
122
124
|
async def _download_one(
|
123
125
|
self,
|
124
|
-
|
126
|
+
book: BookConfig,
|
125
127
|
*,
|
126
128
|
progress_hook: Callable[[int, int], Awaitable[None]] | None = None,
|
127
129
|
**kwargs: Any,
|
@@ -208,19 +210,21 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
|
|
208
210
|
def download_workers(self) -> int:
|
209
211
|
return self._config.download_workers
|
210
212
|
|
211
|
-
def _handle_download_exception(self,
|
213
|
+
def _handle_download_exception(self, book: BookConfig, error: Exception) -> None:
|
212
214
|
"""
|
213
215
|
Handle download errors in a consistent way.
|
214
216
|
|
215
217
|
This method can be overridden or extended to implement retry logic, etc.
|
216
218
|
|
217
|
-
:param
|
219
|
+
:param book: The book that failed.
|
218
220
|
:param error: The exception raised during download.
|
219
221
|
"""
|
220
222
|
self.logger.warning(
|
221
|
-
"[%s] Failed to download
|
223
|
+
"[%s] Failed to download (book_id=%s, start=%s, end=%s): %s",
|
222
224
|
self.__class__.__name__,
|
223
|
-
book_id,
|
225
|
+
book.get("book_id", "<unknown>"),
|
226
|
+
book.get("start_id", "-"),
|
227
|
+
book.get("end_id", "-"),
|
224
228
|
error,
|
225
229
|
)
|
226
230
|
|
{novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/downloaders/common.py
RENAMED
@@ -12,7 +12,13 @@ from contextlib import suppress
|
|
12
12
|
from typing import Any, cast
|
13
13
|
|
14
14
|
from novel_downloader.core.downloaders.base import BaseDownloader
|
15
|
-
from novel_downloader.models import
|
15
|
+
from novel_downloader.models import (
|
16
|
+
BookConfig,
|
17
|
+
ChapterDict,
|
18
|
+
CidTask,
|
19
|
+
HtmlTask,
|
20
|
+
RestoreTask,
|
21
|
+
)
|
16
22
|
from novel_downloader.utils.chapter_storage import ChapterStorage
|
17
23
|
from novel_downloader.utils.file_utils import save_as_json, save_as_txt
|
18
24
|
from novel_downloader.utils.time_utils import (
|
@@ -28,7 +34,7 @@ class CommonDownloader(BaseDownloader):
|
|
28
34
|
|
29
35
|
async def _download_one(
|
30
36
|
self,
|
31
|
-
|
37
|
+
book: BookConfig,
|
32
38
|
*,
|
33
39
|
progress_hook: Callable[[int, int], Awaitable[None]] | None = None,
|
34
40
|
**kwargs: Any,
|
@@ -36,9 +42,13 @@ class CommonDownloader(BaseDownloader):
|
|
36
42
|
"""
|
37
43
|
The full download logic for a single book.
|
38
44
|
|
39
|
-
:param
|
45
|
+
:param book: BookConfig with at least 'book_id'.
|
40
46
|
"""
|
41
47
|
TAG = "[Downloader]"
|
48
|
+
book_id = book["book_id"]
|
49
|
+
start_id = book.get("start_id")
|
50
|
+
end_id = book.get("end_id")
|
51
|
+
ignore_set = set(book.get("ignore_ids", []))
|
42
52
|
|
43
53
|
raw_base = self.raw_data_dir / book_id
|
44
54
|
cache_base = self.cache_dir / book_id
|
@@ -145,6 +155,10 @@ class CommonDownloader(BaseDownloader):
|
|
145
155
|
cid_queue.task_done()
|
146
156
|
continue
|
147
157
|
|
158
|
+
if cid in ignore_set:
|
159
|
+
cid_queue.task_done()
|
160
|
+
continue
|
161
|
+
|
148
162
|
try:
|
149
163
|
async with semaphore:
|
150
164
|
html_list = await self.fetcher.get_book_chapter(book_id, cid)
|
@@ -369,15 +383,33 @@ class CommonDownloader(BaseDownloader):
|
|
369
383
|
)
|
370
384
|
)
|
371
385
|
|
386
|
+
found_start = start_id is None
|
387
|
+
stop_early = False
|
372
388
|
last_cid: str | None = None
|
389
|
+
|
373
390
|
for vol_idx, vol in enumerate(vols):
|
374
391
|
chapters = vol.get("chapters", [])
|
375
392
|
for chap_idx, chap in enumerate(chapters):
|
393
|
+
if stop_early:
|
394
|
+
break
|
395
|
+
|
376
396
|
cid = chap.get("chapterId")
|
397
|
+
|
398
|
+
# Skip until reaching start_id
|
399
|
+
if not found_start:
|
400
|
+
if cid == start_id:
|
401
|
+
found_start = True
|
402
|
+
else:
|
403
|
+
completed_count += 1
|
404
|
+
last_cid = cid
|
405
|
+
continue
|
406
|
+
|
407
|
+
# Stop when reaching end_id
|
408
|
+
if end_id is not None and cid == end_id:
|
409
|
+
stop_early = True
|
410
|
+
|
377
411
|
if cid and normal_cs.exists(cid) and self.skip_existing:
|
378
412
|
completed_count += 1
|
379
|
-
if progress_hook:
|
380
|
-
await progress_hook(completed_count, total_chapters)
|
381
413
|
last_cid = cid
|
382
414
|
continue
|
383
415
|
|
@@ -389,8 +421,12 @@ class CommonDownloader(BaseDownloader):
|
|
389
421
|
prev_cid=last_cid,
|
390
422
|
)
|
391
423
|
)
|
424
|
+
|
392
425
|
last_cid = cid
|
393
426
|
|
427
|
+
if stop_early:
|
428
|
+
break
|
429
|
+
|
394
430
|
await restore_queue.join()
|
395
431
|
await cid_queue.join()
|
396
432
|
await html_queue.join()
|
{novel_downloader-1.4.1 → novel_downloader-1.4.2}/novel_downloader/core/downloaders/qidian.py
RENAMED
@@ -18,6 +18,7 @@ from novel_downloader.core.interfaces import (
|
|
18
18
|
ParserProtocol,
|
19
19
|
)
|
20
20
|
from novel_downloader.models import (
|
21
|
+
BookConfig,
|
21
22
|
ChapterDict,
|
22
23
|
CidTask,
|
23
24
|
DownloaderConfig,
|
@@ -48,7 +49,7 @@ class QidianDownloader(BaseDownloader):
|
|
48
49
|
|
49
50
|
async def _download_one(
|
50
51
|
self,
|
51
|
-
|
52
|
+
book: BookConfig,
|
52
53
|
*,
|
53
54
|
progress_hook: Callable[[int, int], Awaitable[None]] | None = None,
|
54
55
|
**kwargs: Any,
|
@@ -56,9 +57,13 @@ class QidianDownloader(BaseDownloader):
|
|
56
57
|
"""
|
57
58
|
The full download logic for a single book.
|
58
59
|
|
59
|
-
:param
|
60
|
+
:param book: BookConfig with at least 'book_id'.
|
60
61
|
"""
|
61
62
|
TAG = "[Downloader]"
|
63
|
+
book_id = book["book_id"]
|
64
|
+
start_id = book.get("start_id")
|
65
|
+
end_id = book.get("end_id")
|
66
|
+
ignore_set = set(book.get("ignore_ids", []))
|
62
67
|
|
63
68
|
raw_base = self.raw_data_dir / book_id
|
64
69
|
cache_base = self.cache_dir / book_id
|
@@ -140,6 +145,10 @@ class QidianDownloader(BaseDownloader):
|
|
140
145
|
cid_queue.task_done()
|
141
146
|
continue
|
142
147
|
|
148
|
+
if cid in ignore_set:
|
149
|
+
cid_queue.task_done()
|
150
|
+
continue
|
151
|
+
|
143
152
|
try:
|
144
153
|
html_list = await self.fetcher.get_book_chapter(book_id, cid)
|
145
154
|
await html_queue.put(
|
@@ -194,40 +203,39 @@ class QidianDownloader(BaseDownloader):
|
|
194
203
|
skip_retry = False
|
195
204
|
try:
|
196
205
|
chap_json: ChapterDict | None = None
|
197
|
-
if self.
|
206
|
+
if self.check_restricted(task.html_list):
|
198
207
|
self.logger.info(
|
199
208
|
"[Parser] Skipped restricted page for cid %s", task.cid
|
200
209
|
)
|
201
210
|
skip_retry = True
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
211
|
+
raise ValueError("Restricted content detected")
|
212
|
+
|
213
|
+
is_encrypted = self.check_encrypted(task.html_list)
|
214
|
+
chap_json = await asyncio.to_thread(
|
215
|
+
self.parser.parse_chapter,
|
216
|
+
task.html_list,
|
217
|
+
task.cid,
|
218
|
+
)
|
219
|
+
if is_encrypted:
|
220
|
+
skip_retry = True
|
221
|
+
if self.save_html:
|
222
|
+
folder = chapters_html_dir / (
|
223
|
+
"html_encrypted" if is_encrypted else "html_plain"
|
224
|
+
)
|
225
|
+
html_path = folder / f"{task.cid}.html"
|
226
|
+
save_as_txt(task.html_list[0], html_path, on_exist="skip")
|
227
|
+
self.logger.debug(
|
228
|
+
"%s Saved raw HTML for chapter %s to %s",
|
229
|
+
TAG,
|
206
230
|
task.cid,
|
231
|
+
html_path,
|
207
232
|
)
|
208
|
-
if self.check_encrypted(task.html_list):
|
209
|
-
skip_retry = True
|
210
233
|
if chap_json:
|
211
234
|
await save_queue.put(chap_json)
|
212
235
|
self.logger.info(
|
213
236
|
"[Parser] saved chapter %s",
|
214
237
|
task.cid,
|
215
238
|
)
|
216
|
-
if self.save_html:
|
217
|
-
is_encrypted = chap_json.get("extra", {}).get(
|
218
|
-
"encrypted", False
|
219
|
-
)
|
220
|
-
folder = chapters_html_dir / (
|
221
|
-
"html_encrypted" if is_encrypted else "html_plain"
|
222
|
-
)
|
223
|
-
html_path = folder / f"{task.cid}.html"
|
224
|
-
save_as_txt(task.html_list[0], html_path, on_exist="skip")
|
225
|
-
self.logger.debug(
|
226
|
-
"%s Saved raw HTML for chapter %s to %s",
|
227
|
-
TAG,
|
228
|
-
task.cid,
|
229
|
-
html_path,
|
230
|
-
)
|
231
239
|
else:
|
232
240
|
raise ValueError("Empty parse result")
|
233
241
|
except Exception as e:
|
@@ -296,20 +304,40 @@ class QidianDownloader(BaseDownloader):
|
|
296
304
|
)
|
297
305
|
)
|
298
306
|
|
299
|
-
|
307
|
+
found_start = start_id is None
|
308
|
+
stop_early = False
|
309
|
+
|
300
310
|
for vol in book_info.get("volumes", []):
|
301
311
|
chapters = vol.get("chapters", [])
|
302
312
|
for chap in chapters:
|
313
|
+
if stop_early:
|
314
|
+
break
|
315
|
+
|
303
316
|
cid = chap.get("chapterId")
|
304
|
-
if
|
317
|
+
if not cid:
|
318
|
+
continue
|
319
|
+
|
320
|
+
if not found_start:
|
321
|
+
if cid == start_id:
|
322
|
+
found_start = True
|
323
|
+
else:
|
324
|
+
completed_count += 1
|
325
|
+
continue
|
326
|
+
|
327
|
+
if end_id is not None and cid == end_id:
|
328
|
+
stop_early = True
|
329
|
+
|
330
|
+
if cid in ignore_set:
|
331
|
+
continue
|
332
|
+
|
333
|
+
if normal_cs.exists(cid) and self.skip_existing:
|
305
334
|
completed_count += 1
|
306
|
-
if progress_hook:
|
307
|
-
await progress_hook(completed_count, total_chapters)
|
308
|
-
last_cid = cid
|
309
335
|
continue
|
310
336
|
|
311
|
-
await cid_queue.put(CidTask(cid=cid, prev_cid=
|
312
|
-
|
337
|
+
await cid_queue.put(CidTask(cid=cid, prev_cid=None))
|
338
|
+
|
339
|
+
if stop_early:
|
340
|
+
break
|
313
341
|
|
314
342
|
await cid_queue.join()
|
315
343
|
await html_queue.join()
|
@@ -333,7 +361,7 @@ class QidianDownloader(BaseDownloader):
|
|
333
361
|
return
|
334
362
|
|
335
363
|
@staticmethod
|
336
|
-
def
|
364
|
+
def check_restricted(html_list: list[str]) -> bool:
|
337
365
|
"""
|
338
366
|
Return True if page content indicates access restriction
|
339
367
|
(e.g. not subscribed/purchased).
|