novel-downloader 1.4.2__tar.gz → 1.4.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {novel_downloader-1.4.2 → novel_downloader-1.4.4}/PKG-INFO +2 -1
- {novel_downloader-1.4.2 → novel_downloader-1.4.4}/README.md +1 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4}/pyproject.toml +4 -2
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/__init__.py +1 -1
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/cli/download.py +1 -1
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/config/adapter.py +1 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/__init__.py +19 -1
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/base.py +0 -7
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/biquge.py +1 -3
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/common.py +0 -2
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/esjzone.py +1 -3
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/linovelib.py +1 -3
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/qianbi.py +1 -3
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/qidian.py +1 -5
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/sfacg.py +1 -3
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/yamibo.py +1 -3
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/factory/downloader.py +3 -6
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/base/browser.py +32 -12
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/esjzone/browser.py +8 -6
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/qidian/browser.py +2 -2
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/qidian/session.py +2 -1
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/yamibo/browser.py +3 -3
- novel_downloader-1.4.4/src/novel_downloader/core/parsers/qidian/book_info_parser.py +90 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qidian/chapter_encrypted.py +11 -2
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qidian/chapter_normal.py +8 -1
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qidian/main_parser.py +7 -2
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qidian/utils/__init__.py +2 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qidian/utils/helpers.py +9 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/models/config.py +1 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/resources/config/settings.toml +1 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/tui/screens/home.py +8 -2
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/tui/screens/login.py +1 -1
- {novel_downloader-1.4.2/novel_downloader/utils → novel_downloader-1.4.4/src/novel_downloader/utils/fontocr}/model_loader.py +2 -2
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/fontocr/ocr_v1.py +2 -1
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/fontocr/ocr_v2.py +2 -1
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/text_utils/__init__.py +8 -1
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/text_utils/text_cleaning.py +51 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/time_utils/datetime_utils.py +1 -1
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader.egg-info/PKG-INFO +2 -1
- novel_downloader-1.4.4/src/novel_downloader.egg-info/SOURCES.txt +168 -0
- novel_downloader-1.4.2/novel_downloader/core/parsers/qidian/book_info_parser.py +0 -113
- novel_downloader-1.4.2/novel_downloader.egg-info/SOURCES.txt +0 -168
- {novel_downloader-1.4.2 → novel_downloader-1.4.4}/LICENSE +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4}/setup.cfg +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/cli/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/cli/clean.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/cli/config.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/cli/export.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/cli/main.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/config/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/config/loader.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/config/site_rules.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/base.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/biquge.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/common/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/common/epub.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/common/main_exporter.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/common/txt.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/epub_util.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/esjzone.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/linovelib/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/linovelib/epub.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/linovelib/main_exporter.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/linovelib/txt.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/qianbi.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/qidian.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/sfacg.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/exporters/yamibo.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/factory/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/factory/exporter.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/factory/fetcher.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/factory/parser.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/base/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/base/rate_limiter.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/base/session.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/biquge/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/biquge/browser.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/biquge/session.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/common/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/common/browser.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/common/session.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/esjzone/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/esjzone/session.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/linovelib/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/linovelib/browser.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/linovelib/session.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/qianbi/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/qianbi/browser.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/qianbi/session.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/qidian/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/sfacg/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/sfacg/browser.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/sfacg/session.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/yamibo/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/yamibo/session.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/interfaces/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/interfaces/downloader.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/interfaces/exporter.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/interfaces/fetcher.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/interfaces/parser.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/base.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/biquge/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/biquge/main_parser.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/common/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/common/helper.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/common/main_parser.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/esjzone/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/esjzone/main_parser.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/linovelib/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/linovelib/main_parser.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qianbi/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qianbi/main_parser.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qidian/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qidian/chapter_router.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qidian/utils/decryptor_fetcher.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/qidian/utils/node_decryptor.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/sfacg/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/sfacg/main_parser.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/yamibo/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/parsers/yamibo/main_parser.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/locales/en.json +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/locales/zh.json +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/models/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/models/browser.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/models/chapter.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/models/login.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/models/site_rules.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/models/tasks.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/models/types.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/resources/css_styles/main.css +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/resources/css_styles/volume-intro.css +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/resources/images/volume_border.png +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/resources/js_scripts/qidian_decrypt_node.js +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/resources/json/linovelib_font_map.json +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/resources/json/replace_word_map.json +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/resources/text/blacklist.txt +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/tui/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/tui/app.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/tui/main.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/tui/screens/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/tui/styles/home_layout.tcss +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/tui/widgets/richlog_handler.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/cache.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/chapter_storage.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/constants.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/cookies.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/crypto_utils.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/file_utils/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/file_utils/io.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/file_utils/normalize.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/file_utils/sanitize.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/fontocr/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/hash_store.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/hash_utils.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/i18n.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/logger.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/network.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/state.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/text_utils/chapter_formatting.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/text_utils/diff_display.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/text_utils/font_mapping.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/time_utils/__init__.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/utils/time_utils/sleep_utils.py +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader.egg-info/dependency_links.txt +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader.egg-info/entry_points.txt +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader.egg-info/requires.txt +0 -0
- {novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: novel-downloader
|
3
|
-
Version: 1.4.
|
3
|
+
Version: 1.4.4
|
4
4
|
Summary: A command-line tool for downloading Chinese web novels from Qidian and similar platforms.
|
5
5
|
Author-email: Saudade Z <saudadez217@gmail.com>
|
6
6
|
License: MIT License
|
@@ -182,6 +182,7 @@ pip install .
|
|
182
182
|
- [CLI 使用示例](https://github.com/BowenZ217/novel-downloader/blob/main/docs/6-cli-usage-examples.md)
|
183
183
|
- [复制 Cookies](https://github.com/BowenZ217/novel-downloader/blob/main/docs/copy-cookies.md)
|
184
184
|
- [文件保存](https://github.com/BowenZ217/novel-downloader/blob/main/docs/file-saving.md)
|
185
|
+
- [模块与接口文档](https://github.com/BowenZ217/novel-downloader/blob/main/docs/api/README.md)
|
185
186
|
- [TODO](https://github.com/BowenZ217/novel-downloader/blob/main/docs/todo.md)
|
186
187
|
- [开发](https://github.com/BowenZ217/novel-downloader/blob/main/docs/develop.md)
|
187
188
|
- [项目说明](#项目说明)
|
@@ -111,6 +111,7 @@ pip install .
|
|
111
111
|
- [CLI 使用示例](https://github.com/BowenZ217/novel-downloader/blob/main/docs/6-cli-usage-examples.md)
|
112
112
|
- [复制 Cookies](https://github.com/BowenZ217/novel-downloader/blob/main/docs/copy-cookies.md)
|
113
113
|
- [文件保存](https://github.com/BowenZ217/novel-downloader/blob/main/docs/file-saving.md)
|
114
|
+
- [模块与接口文档](https://github.com/BowenZ217/novel-downloader/blob/main/docs/api/README.md)
|
114
115
|
- [TODO](https://github.com/BowenZ217/novel-downloader/blob/main/docs/todo.md)
|
115
116
|
- [开发](https://github.com/BowenZ217/novel-downloader/blob/main/docs/develop.md)
|
116
117
|
- [项目说明](#项目说明)
|
@@ -68,6 +68,7 @@ requires = ["setuptools>=61.0", "wheel"]
|
|
68
68
|
build-backend = "setuptools.build_meta"
|
69
69
|
|
70
70
|
[tool.setuptools.packages.find]
|
71
|
+
where = ["src"]
|
71
72
|
include = ["novel_downloader", "novel_downloader.*"]
|
72
73
|
|
73
74
|
[tool.setuptools.package-data]
|
@@ -110,13 +111,14 @@ strict = true
|
|
110
111
|
[tool.pytest.ini_options]
|
111
112
|
minversion = "7.1"
|
112
113
|
addopts = "--strict-markers"
|
114
|
+
pythonpath = ["src"]
|
113
115
|
testpaths = ["tests"]
|
114
116
|
|
115
117
|
[tool.commitizen]
|
116
118
|
name = "cz_conventional_commits"
|
117
|
-
version = "1.4.
|
119
|
+
version = "1.4.4"
|
118
120
|
version_files = [
|
119
|
-
"novel_downloader/__init__.py"
|
121
|
+
"src/novel_downloader/__init__.py"
|
120
122
|
]
|
121
123
|
update_changelog_on_bump = true
|
122
124
|
changelog_file = "CHANGELOG.md"
|
@@ -161,7 +161,6 @@ async def _download(
|
|
161
161
|
downloader = get_downloader(
|
162
162
|
fetcher=fetcher,
|
163
163
|
parser=parser,
|
164
|
-
exporter=exporter,
|
165
164
|
site=site,
|
166
165
|
config=downloader_cfg,
|
167
166
|
)
|
@@ -172,6 +171,7 @@ async def _download(
|
|
172
171
|
book,
|
173
172
|
progress_hook=_print_progress,
|
174
173
|
)
|
174
|
+
await asyncio.to_thread(exporter.export, book["book_id"])
|
175
175
|
|
176
176
|
if downloader_cfg.login_required and fetcher.is_logged_in:
|
177
177
|
await fetcher.save_state()
|
@@ -127,6 +127,7 @@ class ConfigAdapter:
|
|
127
127
|
site_cfg = self._get_site_cfg()
|
128
128
|
return ParserConfig(
|
129
129
|
cache_dir=gen.get("cache_dir", "./novel_cache"),
|
130
|
+
use_truncation=site_cfg.get("use_truncation", True),
|
130
131
|
decode_font=font_ocr.get("decode_font", False),
|
131
132
|
use_freq=font_ocr.get("use_freq", False),
|
132
133
|
use_ocr=font_ocr.get("use_ocr", True),
|
@@ -14,8 +14,26 @@ downloading and processing online novel content, including:
|
|
14
14
|
- Exporter: Responsible for exporting downloaded data into various output formats.
|
15
15
|
"""
|
16
16
|
|
17
|
-
from .factory import
|
17
|
+
from .factory import (
|
18
|
+
get_downloader,
|
19
|
+
get_exporter,
|
20
|
+
get_fetcher,
|
21
|
+
get_parser,
|
22
|
+
)
|
23
|
+
from .interfaces import (
|
24
|
+
DownloaderProtocol,
|
25
|
+
ExporterProtocol,
|
26
|
+
FetcherProtocol,
|
27
|
+
ParserProtocol,
|
28
|
+
)
|
18
29
|
|
19
30
|
__all__ = [
|
31
|
+
"get_downloader",
|
32
|
+
"get_exporter",
|
33
|
+
"get_fetcher",
|
20
34
|
"get_parser",
|
35
|
+
"DownloaderProtocol",
|
36
|
+
"ExporterProtocol",
|
37
|
+
"FetcherProtocol",
|
38
|
+
"ParserProtocol",
|
21
39
|
]
|
{novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/base.py
RENAMED
@@ -15,7 +15,6 @@ from typing import Any
|
|
15
15
|
|
16
16
|
from novel_downloader.core.interfaces import (
|
17
17
|
DownloaderProtocol,
|
18
|
-
ExporterProtocol,
|
19
18
|
FetcherProtocol,
|
20
19
|
ParserProtocol,
|
21
20
|
)
|
@@ -34,13 +33,11 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
|
|
34
33
|
self,
|
35
34
|
fetcher: FetcherProtocol,
|
36
35
|
parser: ParserProtocol,
|
37
|
-
exporter: ExporterProtocol,
|
38
36
|
config: DownloaderConfig,
|
39
37
|
site: str,
|
40
38
|
):
|
41
39
|
self._fetcher = fetcher
|
42
40
|
self._parser = parser
|
43
|
-
self._exporter = exporter
|
44
41
|
self._config = config
|
45
42
|
self._site = site
|
46
43
|
|
@@ -158,10 +155,6 @@ class BaseDownloader(DownloaderProtocol, abc.ABC):
|
|
158
155
|
def parser(self) -> ParserProtocol:
|
159
156
|
return self._parser
|
160
157
|
|
161
|
-
@property
|
162
|
-
def exporter(self) -> ExporterProtocol:
|
163
|
-
return self._exporter
|
164
|
-
|
165
158
|
@property
|
166
159
|
def config(self) -> DownloaderConfig:
|
167
160
|
return self._config
|
{novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/biquge.py
RENAMED
@@ -7,7 +7,6 @@ novel_downloader.core.downloaders.biquge
|
|
7
7
|
|
8
8
|
from novel_downloader.core.downloaders.common import CommonDownloader
|
9
9
|
from novel_downloader.core.interfaces import (
|
10
|
-
ExporterProtocol,
|
11
10
|
FetcherProtocol,
|
12
11
|
ParserProtocol,
|
13
12
|
)
|
@@ -21,7 +20,6 @@ class BiqugeDownloader(CommonDownloader):
|
|
21
20
|
self,
|
22
21
|
fetcher: FetcherProtocol,
|
23
22
|
parser: ParserProtocol,
|
24
|
-
exporter: ExporterProtocol,
|
25
23
|
config: DownloaderConfig,
|
26
24
|
):
|
27
|
-
super().__init__(fetcher, parser,
|
25
|
+
super().__init__(fetcher, parser, config, "biquge")
|
{novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/esjzone.py
RENAMED
@@ -7,7 +7,6 @@ novel_downloader.core.downloaders.esjzone
|
|
7
7
|
|
8
8
|
from novel_downloader.core.downloaders.common import CommonDownloader
|
9
9
|
from novel_downloader.core.interfaces import (
|
10
|
-
ExporterProtocol,
|
11
10
|
FetcherProtocol,
|
12
11
|
ParserProtocol,
|
13
12
|
)
|
@@ -21,7 +20,6 @@ class EsjzoneDownloader(CommonDownloader):
|
|
21
20
|
self,
|
22
21
|
fetcher: FetcherProtocol,
|
23
22
|
parser: ParserProtocol,
|
24
|
-
exporter: ExporterProtocol,
|
25
23
|
config: DownloaderConfig,
|
26
24
|
):
|
27
|
-
super().__init__(fetcher, parser,
|
25
|
+
super().__init__(fetcher, parser, config, "esjzone")
|
{novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/linovelib.py
RENAMED
@@ -7,7 +7,6 @@ novel_downloader.core.downloaders.linovelib
|
|
7
7
|
|
8
8
|
from novel_downloader.core.downloaders.common import CommonDownloader
|
9
9
|
from novel_downloader.core.interfaces import (
|
10
|
-
ExporterProtocol,
|
11
10
|
FetcherProtocol,
|
12
11
|
ParserProtocol,
|
13
12
|
)
|
@@ -21,7 +20,6 @@ class LinovelibDownloader(CommonDownloader):
|
|
21
20
|
self,
|
22
21
|
fetcher: FetcherProtocol,
|
23
22
|
parser: ParserProtocol,
|
24
|
-
exporter: ExporterProtocol,
|
25
23
|
config: DownloaderConfig,
|
26
24
|
):
|
27
|
-
super().__init__(fetcher, parser,
|
25
|
+
super().__init__(fetcher, parser, config, "linovelib")
|
{novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/qianbi.py
RENAMED
@@ -7,7 +7,6 @@ novel_downloader.core.downloaders.qianbi
|
|
7
7
|
|
8
8
|
from novel_downloader.core.downloaders.common import CommonDownloader
|
9
9
|
from novel_downloader.core.interfaces import (
|
10
|
-
ExporterProtocol,
|
11
10
|
FetcherProtocol,
|
12
11
|
ParserProtocol,
|
13
12
|
)
|
@@ -21,7 +20,6 @@ class QianbiDownloader(CommonDownloader):
|
|
21
20
|
self,
|
22
21
|
fetcher: FetcherProtocol,
|
23
22
|
parser: ParserProtocol,
|
24
|
-
exporter: ExporterProtocol,
|
25
23
|
config: DownloaderConfig,
|
26
24
|
):
|
27
|
-
super().__init__(fetcher, parser,
|
25
|
+
super().__init__(fetcher, parser, config, "qianbi")
|
{novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/qidian.py
RENAMED
@@ -13,7 +13,6 @@ from typing import Any, cast
|
|
13
13
|
|
14
14
|
from novel_downloader.core.downloaders.base import BaseDownloader
|
15
15
|
from novel_downloader.core.interfaces import (
|
16
|
-
ExporterProtocol,
|
17
16
|
FetcherProtocol,
|
18
17
|
ParserProtocol,
|
19
18
|
)
|
@@ -41,11 +40,10 @@ class QidianDownloader(BaseDownloader):
|
|
41
40
|
self,
|
42
41
|
fetcher: FetcherProtocol,
|
43
42
|
parser: ParserProtocol,
|
44
|
-
exporter: ExporterProtocol,
|
45
43
|
config: DownloaderConfig,
|
46
44
|
):
|
47
45
|
config.request_interval = max(1.0, config.request_interval)
|
48
|
-
super().__init__(fetcher, parser,
|
46
|
+
super().__init__(fetcher, parser, config, "qidian")
|
49
47
|
|
50
48
|
async def _download_one(
|
51
49
|
self,
|
@@ -351,8 +349,6 @@ class QidianDownloader(BaseDownloader):
|
|
351
349
|
normal_cs.close()
|
352
350
|
encrypted_cs.close()
|
353
351
|
|
354
|
-
await asyncio.to_thread(self.exporter.export, book_id)
|
355
|
-
|
356
352
|
self.logger.info(
|
357
353
|
"%s Novel '%s' download completed.",
|
358
354
|
TAG,
|
{novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/sfacg.py
RENAMED
@@ -7,7 +7,6 @@ novel_downloader.core.downloaders.sfacg
|
|
7
7
|
|
8
8
|
from novel_downloader.core.downloaders.common import CommonDownloader
|
9
9
|
from novel_downloader.core.interfaces import (
|
10
|
-
ExporterProtocol,
|
11
10
|
FetcherProtocol,
|
12
11
|
ParserProtocol,
|
13
12
|
)
|
@@ -21,7 +20,6 @@ class SfacgDownloader(CommonDownloader):
|
|
21
20
|
self,
|
22
21
|
fetcher: FetcherProtocol,
|
23
22
|
parser: ParserProtocol,
|
24
|
-
exporter: ExporterProtocol,
|
25
23
|
config: DownloaderConfig,
|
26
24
|
):
|
27
|
-
super().__init__(fetcher, parser,
|
25
|
+
super().__init__(fetcher, parser, config, "sfacg")
|
{novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/downloaders/yamibo.py
RENAMED
@@ -7,7 +7,6 @@ novel_downloader.core.downloaders.yamibo
|
|
7
7
|
|
8
8
|
from novel_downloader.core.downloaders.common import CommonDownloader
|
9
9
|
from novel_downloader.core.interfaces import (
|
10
|
-
ExporterProtocol,
|
11
10
|
FetcherProtocol,
|
12
11
|
ParserProtocol,
|
13
12
|
)
|
@@ -21,7 +20,6 @@ class YamiboDownloader(CommonDownloader):
|
|
21
20
|
self,
|
22
21
|
fetcher: FetcherProtocol,
|
23
22
|
parser: ParserProtocol,
|
24
|
-
exporter: ExporterProtocol,
|
25
23
|
config: DownloaderConfig,
|
26
24
|
):
|
27
|
-
super().__init__(fetcher, parser,
|
25
|
+
super().__init__(fetcher, parser, config, "yamibo")
|
{novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/factory/downloader.py
RENAMED
@@ -22,14 +22,13 @@ from novel_downloader.core.downloaders import (
|
|
22
22
|
)
|
23
23
|
from novel_downloader.core.interfaces import (
|
24
24
|
DownloaderProtocol,
|
25
|
-
ExporterProtocol,
|
26
25
|
FetcherProtocol,
|
27
26
|
ParserProtocol,
|
28
27
|
)
|
29
28
|
from novel_downloader.models import DownloaderConfig
|
30
29
|
|
31
30
|
DownloaderBuilder = Callable[
|
32
|
-
[FetcherProtocol, ParserProtocol,
|
31
|
+
[FetcherProtocol, ParserProtocol, DownloaderConfig],
|
33
32
|
DownloaderProtocol,
|
34
33
|
]
|
35
34
|
|
@@ -47,7 +46,6 @@ _site_map: dict[str, DownloaderBuilder] = {
|
|
47
46
|
def get_downloader(
|
48
47
|
fetcher: FetcherProtocol,
|
49
48
|
parser: ParserProtocol,
|
50
|
-
exporter: ExporterProtocol,
|
51
49
|
site: str,
|
52
50
|
config: DownloaderConfig,
|
53
51
|
) -> DownloaderProtocol:
|
@@ -56,7 +54,6 @@ def get_downloader(
|
|
56
54
|
|
57
55
|
:param fetcher: Fetcher implementation
|
58
56
|
:param parser: Parser implementation
|
59
|
-
:param exporter: Exporter implementation
|
60
57
|
:param site: Site name (e.g., 'qidian')
|
61
58
|
:param config: Downloader configuration
|
62
59
|
|
@@ -66,11 +63,11 @@ def get_downloader(
|
|
66
63
|
|
67
64
|
# site-specific
|
68
65
|
if site_key in _site_map:
|
69
|
-
return _site_map[site_key](fetcher, parser,
|
66
|
+
return _site_map[site_key](fetcher, parser, config)
|
70
67
|
|
71
68
|
# fallback
|
72
69
|
site_rules = load_site_rules()
|
73
70
|
if site_key not in site_rules:
|
74
71
|
raise ValueError(f"Unsupported site: {site}")
|
75
72
|
|
76
|
-
return CommonDownloader(fetcher, parser,
|
73
|
+
return CommonDownloader(fetcher, parser, config, site_key)
|
{novel_downloader-1.4.2 → novel_downloader-1.4.4/src}/novel_downloader/core/fetchers/base/browser.py
RENAMED
@@ -201,19 +201,9 @@ class BaseBrowser(FetcherProtocol, abc.ABC):
|
|
201
201
|
**kwargs: Any,
|
202
202
|
) -> str:
|
203
203
|
if self._reuse_page:
|
204
|
-
|
205
|
-
self._page = await self.context.new_page()
|
206
|
-
page = self._page
|
204
|
+
return await self._fetch_with_reuse(url, wait_until, referer, **kwargs)
|
207
205
|
else:
|
208
|
-
|
209
|
-
|
210
|
-
await page.goto(url, wait_until=wait_until, referer=referer)
|
211
|
-
content = await page.content()
|
212
|
-
|
213
|
-
if not self._reuse_page:
|
214
|
-
await page.close()
|
215
|
-
|
216
|
-
return str(content)
|
206
|
+
return await self._fetch_with_new(url, wait_until, referer, **kwargs)
|
217
207
|
|
218
208
|
async def load_state(self) -> bool:
|
219
209
|
""" """
|
@@ -286,6 +276,36 @@ class BaseBrowser(FetcherProtocol, abc.ABC):
|
|
286
276
|
await self.init(headless=headless)
|
287
277
|
self.logger.debug("[browser] Browser restarted (headless=%s).", headless)
|
288
278
|
|
279
|
+
async def _fetch_with_new(
|
280
|
+
self,
|
281
|
+
url: str,
|
282
|
+
wait_until: Literal["commit", "domcontentloaded", "load", "networkidle"]
|
283
|
+
| None = "load",
|
284
|
+
referer: str | None = None,
|
285
|
+
**kwargs: Any,
|
286
|
+
) -> str:
|
287
|
+
page = await self.context.new_page()
|
288
|
+
try:
|
289
|
+
await page.goto(url, wait_until=wait_until, referer=referer, **kwargs)
|
290
|
+
html: str = await page.content()
|
291
|
+
return html
|
292
|
+
finally:
|
293
|
+
await page.close()
|
294
|
+
|
295
|
+
async def _fetch_with_reuse(
|
296
|
+
self,
|
297
|
+
url: str,
|
298
|
+
wait_until: Literal["commit", "domcontentloaded", "load", "networkidle"]
|
299
|
+
| None = "load",
|
300
|
+
referer: str | None = None,
|
301
|
+
**kwargs: Any,
|
302
|
+
) -> str:
|
303
|
+
if not self._page:
|
304
|
+
self._page = await self.context.new_page()
|
305
|
+
await self._page.goto(url, wait_until=wait_until, referer=referer, **kwargs)
|
306
|
+
html: str = await self._page.content()
|
307
|
+
return html
|
308
|
+
|
289
309
|
@property
|
290
310
|
def hostname(self) -> str:
|
291
311
|
return ""
|
@@ -49,15 +49,17 @@ class EsjzoneBrowser(BaseBrowser):
|
|
49
49
|
|
50
50
|
login_page = await self.context.new_page()
|
51
51
|
|
52
|
-
|
52
|
+
try:
|
53
|
+
await login_page.goto(self.API_LOGIN_URL_1, wait_until="networkidle")
|
53
54
|
|
54
|
-
|
55
|
-
|
55
|
+
await login_page.fill('input[name="email"]', username)
|
56
|
+
await login_page.fill('input[name="pwd"]', password)
|
56
57
|
|
57
|
-
|
58
|
+
await login_page.click('a.btn-send[data-send="mem_login"]')
|
58
59
|
|
59
|
-
|
60
|
-
|
60
|
+
await login_page.wait_for_load_state("networkidle")
|
61
|
+
finally:
|
62
|
+
await login_page.close()
|
61
63
|
|
62
64
|
self._is_logged_in = await self._check_login_status()
|
63
65
|
|
@@ -22,8 +22,8 @@ class QidianBrowser(BaseBrowser):
|
|
22
22
|
|
23
23
|
HOMEPAGE_URL = "https://www.qidian.com/"
|
24
24
|
BOOKCASE_URL = "https://my.qidian.com/bookcase/"
|
25
|
-
BOOK_INFO_URL = "https://book.qidian.com/info/{book_id}/"
|
26
|
-
|
25
|
+
# BOOK_INFO_URL = "https://book.qidian.com/info/{book_id}/"
|
26
|
+
BOOK_INFO_URL = "https://www.qidian.com/book/{book_id}/"
|
27
27
|
CHAPTER_URL = "https://www.qidian.com/chapter/{book_id}/{chapter_id}/"
|
28
28
|
|
29
29
|
LOGIN_URL = "https://passport.qidian.com/"
|
@@ -27,7 +27,8 @@ class QidianSession(BaseSession):
|
|
27
27
|
|
28
28
|
HOMEPAGE_URL = "https://www.qidian.com/"
|
29
29
|
BOOKCASE_URL = "https://my.qidian.com/bookcase/"
|
30
|
-
BOOK_INFO_URL = "https://book.qidian.com/info/{book_id}/"
|
30
|
+
# BOOK_INFO_URL = "https://book.qidian.com/info/{book_id}/"
|
31
|
+
BOOK_INFO_URL = "https://www.qidian.com/book/{book_id}/"
|
31
32
|
CHAPTER_URL = "https://www.qidian.com/chapter/{book_id}/{chapter_id}/"
|
32
33
|
|
33
34
|
LOGIN_URL = "https://passport.qidian.com/"
|
@@ -48,8 +48,8 @@ class YamiboBrowser(BaseBrowser):
|
|
48
48
|
return False
|
49
49
|
|
50
50
|
for i in range(1, attempt + 1):
|
51
|
+
login_page = await self.context.new_page()
|
51
52
|
try:
|
52
|
-
login_page = await self.context.new_page()
|
53
53
|
await login_page.goto(self.LOGIN_URL, wait_until="networkidle")
|
54
54
|
|
55
55
|
await login_page.fill("#loginform-username", username)
|
@@ -68,8 +68,6 @@ class YamiboBrowser(BaseBrowser):
|
|
68
68
|
f"[auth] No URL change after login attempt {i}: {e}"
|
69
69
|
)
|
70
70
|
|
71
|
-
await login_page.close()
|
72
|
-
|
73
71
|
self._is_logged_in = await self._check_login_status()
|
74
72
|
if self._is_logged_in:
|
75
73
|
self.logger.info(f"[auth] Login successful on attempt {i}.")
|
@@ -83,6 +81,8 @@ class YamiboBrowser(BaseBrowser):
|
|
83
81
|
self.logger.error(
|
84
82
|
f"[auth] Unexpected error during login attempt {i}: {e}"
|
85
83
|
)
|
84
|
+
finally:
|
85
|
+
await login_page.close()
|
86
86
|
|
87
87
|
self.logger.error(f"[auth] Login failed after {attempt} attempt(s).")
|
88
88
|
return False
|
@@ -0,0 +1,90 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
novel_downloader.core.parsers.qidian.book_info_parser
|
4
|
+
-----------------------------------------------------
|
5
|
+
|
6
|
+
This module provides parsing of Qidian book info pages.
|
7
|
+
|
8
|
+
It extracts metadata such as title, author, cover URL, update
|
9
|
+
time, status, word count, summary, and volume-chapter structure.
|
10
|
+
"""
|
11
|
+
|
12
|
+
import logging
|
13
|
+
import re
|
14
|
+
from datetime import datetime
|
15
|
+
from typing import Any
|
16
|
+
|
17
|
+
from lxml import html
|
18
|
+
|
19
|
+
logger = logging.getLogger(__name__)
|
20
|
+
|
21
|
+
|
22
|
+
def _chapter_url_to_id(url: str) -> str:
|
23
|
+
return url.rstrip("/").split("/")[-1]
|
24
|
+
|
25
|
+
|
26
|
+
def parse_book_info(html_str: str) -> dict[str, Any]:
|
27
|
+
"""
|
28
|
+
Extract metadata: title, author, cover_url, update_time, status,
|
29
|
+
word_count, summary, and volumes with chapters.
|
30
|
+
|
31
|
+
:param html_str: Raw HTML of the book info page.
|
32
|
+
:return: A dict containing book metadata.
|
33
|
+
"""
|
34
|
+
info: dict[str, Any] = {}
|
35
|
+
try:
|
36
|
+
doc = html.fromstring(html_str)
|
37
|
+
|
38
|
+
info["book_name"] = doc.xpath('string(//h1[@id="bookName"])').strip()
|
39
|
+
|
40
|
+
info["author"] = doc.xpath('string(//a[@class="writer-name"])').strip()
|
41
|
+
|
42
|
+
book_id = doc.xpath('//a[@id="bookImg"]/@data-bid')[0]
|
43
|
+
info[
|
44
|
+
"cover_url"
|
45
|
+
] = f"https://bookcover.yuewen.com/qdbimg/349573/{book_id}/600.webp"
|
46
|
+
|
47
|
+
ut = (
|
48
|
+
doc.xpath('string(//span[@class="update-time"])')
|
49
|
+
.replace("更新时间:", "")
|
50
|
+
.strip()
|
51
|
+
)
|
52
|
+
if re.match(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$", ut):
|
53
|
+
info["update_time"] = ut
|
54
|
+
else:
|
55
|
+
info["update_time"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
56
|
+
|
57
|
+
info["serial_status"] = doc.xpath(
|
58
|
+
'string(//p[@class="book-attribute"]/span[1])'
|
59
|
+
).strip()
|
60
|
+
|
61
|
+
tags = doc.xpath('//p[contains(@class,"all-label")]//a/text()')
|
62
|
+
info["tags"] = [t.strip() for t in tags if t.strip()]
|
63
|
+
|
64
|
+
info["word_count"] = doc.xpath('string(//p[@class="count"]/em[1])').strip()
|
65
|
+
|
66
|
+
summary = doc.xpath('string(//p[@class="intro"])').strip()
|
67
|
+
info["summary_brief"] = summary
|
68
|
+
|
69
|
+
raw = doc.xpath('//p[@id="book-intro-detail"]//text()')
|
70
|
+
info["summary"] = "\n".join(line.strip() for line in raw if line.strip())
|
71
|
+
|
72
|
+
volumes = []
|
73
|
+
for vol in doc.xpath('//div[@id="allCatalog"]//div[@class="catalog-volume"]'):
|
74
|
+
vol_name = vol.xpath('string(.//h3[@class="volume-name"])').strip()
|
75
|
+
vol_name = vol_name.split(chr(183))[0].strip()
|
76
|
+
chapters = []
|
77
|
+
for li in vol.xpath('.//ul[contains(@class,"volume-chapters")]/li'):
|
78
|
+
a = li.xpath('.//a[@class="chapter-name"]')[0]
|
79
|
+
title = a.text.strip()
|
80
|
+
url = a.get("href")
|
81
|
+
chapters.append(
|
82
|
+
{"title": title, "url": url, "chapterId": _chapter_url_to_id(url)}
|
83
|
+
)
|
84
|
+
volumes.append({"volume_name": vol_name, "chapters": chapters})
|
85
|
+
info["volumes"] = volumes
|
86
|
+
|
87
|
+
except Exception as e:
|
88
|
+
logger.warning("[Parser] Error parsing book info: %s", e)
|
89
|
+
|
90
|
+
return info
|
@@ -19,12 +19,16 @@ from lxml import html
|
|
19
19
|
|
20
20
|
from novel_downloader.models import ChapterDict
|
21
21
|
from novel_downloader.utils.network import download_font_file
|
22
|
-
from novel_downloader.utils.text_utils import
|
22
|
+
from novel_downloader.utils.text_utils import (
|
23
|
+
apply_font_mapping,
|
24
|
+
truncate_half_lines,
|
25
|
+
)
|
23
26
|
|
24
27
|
from .utils import (
|
25
28
|
extract_chapter_info,
|
26
29
|
find_ssr_page_context,
|
27
30
|
get_decryptor,
|
31
|
+
is_duplicated,
|
28
32
|
vip_status,
|
29
33
|
)
|
30
34
|
|
@@ -76,6 +80,7 @@ def parse_encrypted_chapter(
|
|
76
80
|
fixedFontWoff2_url = chapter_info["fixedFontWoff2"]
|
77
81
|
|
78
82
|
title = chapter_info.get("chapterName", "Untitled")
|
83
|
+
duplicated = is_duplicated(ssr_data)
|
79
84
|
raw_html = chapter_info.get("content", "")
|
80
85
|
chapter_id = chapter_info.get("chapterId", chapter_id)
|
81
86
|
fkp = chapter_info.get("fkp", "")
|
@@ -83,7 +88,7 @@ def parse_encrypted_chapter(
|
|
83
88
|
update_time = chapter_info.get("updateTime", "")
|
84
89
|
update_timestamp = chapter_info.get("updateTimestamp", 0)
|
85
90
|
modify_time = chapter_info.get("modifyTime", 0)
|
86
|
-
word_count = chapter_info.get("
|
91
|
+
word_count = chapter_info.get("actualWords", 0)
|
87
92
|
seq = chapter_info.get("seq", None)
|
88
93
|
volume = chapter_info.get("extra", {}).get("volumeName", "")
|
89
94
|
|
@@ -177,6 +182,9 @@ def parse_encrypted_chapter(
|
|
177
182
|
final_paragraphs_str = "\n\n".join(
|
178
183
|
line.strip() for line in original_text.splitlines() if line.strip()
|
179
184
|
)
|
185
|
+
if parser._use_truncation and duplicated:
|
186
|
+
final_paragraphs_str = truncate_half_lines(final_paragraphs_str)
|
187
|
+
|
180
188
|
return {
|
181
189
|
"id": str(chapter_id),
|
182
190
|
"title": str(title),
|
@@ -187,6 +195,7 @@ def parse_encrypted_chapter(
|
|
187
195
|
"update_timestamp": update_timestamp,
|
188
196
|
"modify_time": modify_time,
|
189
197
|
"word_count": word_count,
|
198
|
+
"duplicated": duplicated,
|
190
199
|
"seq": seq,
|
191
200
|
"volume": volume,
|
192
201
|
"encrypted": True,
|
@@ -15,11 +15,13 @@ from typing import TYPE_CHECKING
|
|
15
15
|
from lxml import html
|
16
16
|
|
17
17
|
from novel_downloader.models import ChapterDict
|
18
|
+
from novel_downloader.utils.text_utils import truncate_half_lines
|
18
19
|
|
19
20
|
from .utils import (
|
20
21
|
extract_chapter_info,
|
21
22
|
find_ssr_page_context,
|
22
23
|
get_decryptor,
|
24
|
+
is_duplicated,
|
23
25
|
vip_status,
|
24
26
|
)
|
25
27
|
|
@@ -51,6 +53,7 @@ def parse_normal_chapter(
|
|
51
53
|
return None
|
52
54
|
|
53
55
|
title = chapter_info.get("chapterName", "Untitled")
|
56
|
+
duplicated = is_duplicated(ssr_data)
|
54
57
|
raw_html = chapter_info.get("content", "")
|
55
58
|
chapter_id = chapter_info.get("chapterId", chapter_id)
|
56
59
|
fkp = chapter_info.get("fkp", "")
|
@@ -58,7 +61,7 @@ def parse_normal_chapter(
|
|
58
61
|
update_time = chapter_info.get("updateTime", "")
|
59
62
|
update_timestamp = chapter_info.get("updateTimestamp", 0)
|
60
63
|
modify_time = chapter_info.get("modifyTime", 0)
|
61
|
-
word_count = chapter_info.get("
|
64
|
+
word_count = chapter_info.get("actualWords", 0)
|
62
65
|
seq = chapter_info.get("seq", None)
|
63
66
|
volume = chapter_info.get("extra", {}).get("volumeName", "")
|
64
67
|
|
@@ -74,6 +77,9 @@ def parse_normal_chapter(
|
|
74
77
|
if not chapter_text:
|
75
78
|
return None
|
76
79
|
|
80
|
+
if parser._use_truncation and duplicated:
|
81
|
+
chapter_text = truncate_half_lines(chapter_text)
|
82
|
+
|
77
83
|
return {
|
78
84
|
"id": str(chapter_id),
|
79
85
|
"title": title,
|
@@ -84,6 +90,7 @@ def parse_normal_chapter(
|
|
84
90
|
"update_timestamp": update_timestamp,
|
85
91
|
"modify_time": modify_time,
|
86
92
|
"word_count": word_count,
|
93
|
+
"duplicated": duplicated,
|
87
94
|
"seq": seq,
|
88
95
|
"volume": volume,
|
89
96
|
"encrypted": False,
|