novel-downloader 1.4.4__py3-none-any.whl → 1.5.0__py3-none-any.whl
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/__init__.py +1 -1
- novel_downloader/cli/__init__.py +2 -2
- novel_downloader/cli/config.py +1 -83
- novel_downloader/cli/download.py +4 -5
- novel_downloader/cli/export.py +4 -1
- novel_downloader/cli/main.py +2 -0
- novel_downloader/cli/search.py +123 -0
- novel_downloader/config/__init__.py +3 -10
- novel_downloader/config/adapter.py +190 -54
- novel_downloader/config/loader.py +2 -3
- novel_downloader/core/__init__.py +13 -13
- novel_downloader/core/downloaders/__init__.py +10 -11
- novel_downloader/core/downloaders/base.py +152 -26
- novel_downloader/core/downloaders/biquge.py +5 -1
- novel_downloader/core/downloaders/common.py +157 -378
- novel_downloader/core/downloaders/esjzone.py +5 -1
- novel_downloader/core/downloaders/linovelib.py +5 -1
- novel_downloader/core/downloaders/qianbi.py +291 -4
- novel_downloader/core/downloaders/qidian.py +199 -285
- novel_downloader/core/downloaders/registry.py +67 -0
- novel_downloader/core/downloaders/sfacg.py +5 -1
- novel_downloader/core/downloaders/yamibo.py +5 -1
- novel_downloader/core/exporters/__init__.py +10 -11
- novel_downloader/core/exporters/base.py +87 -7
- novel_downloader/core/exporters/biquge.py +5 -8
- novel_downloader/core/exporters/common/__init__.py +2 -2
- novel_downloader/core/exporters/common/epub.py +82 -166
- novel_downloader/core/exporters/common/main_exporter.py +0 -60
- novel_downloader/core/exporters/common/txt.py +82 -83
- novel_downloader/core/exporters/epub_util.py +157 -1330
- novel_downloader/core/exporters/esjzone.py +5 -8
- novel_downloader/core/exporters/linovelib/__init__.py +2 -2
- novel_downloader/core/exporters/linovelib/epub.py +157 -212
- novel_downloader/core/exporters/linovelib/main_exporter.py +2 -59
- novel_downloader/core/exporters/linovelib/txt.py +67 -63
- novel_downloader/core/exporters/qianbi.py +5 -8
- novel_downloader/core/exporters/qidian.py +14 -4
- novel_downloader/core/exporters/registry.py +53 -0
- novel_downloader/core/exporters/sfacg.py +5 -8
- novel_downloader/core/exporters/txt_util.py +67 -0
- novel_downloader/core/exporters/yamibo.py +5 -8
- novel_downloader/core/fetchers/__init__.py +19 -24
- novel_downloader/core/fetchers/base/__init__.py +3 -3
- novel_downloader/core/fetchers/base/browser.py +23 -4
- novel_downloader/core/fetchers/base/session.py +30 -5
- novel_downloader/core/fetchers/biquge/__init__.py +3 -3
- novel_downloader/core/fetchers/biquge/browser.py +5 -0
- novel_downloader/core/fetchers/biquge/session.py +6 -1
- novel_downloader/core/fetchers/esjzone/__init__.py +3 -3
- novel_downloader/core/fetchers/esjzone/browser.py +5 -0
- novel_downloader/core/fetchers/esjzone/session.py +6 -1
- novel_downloader/core/fetchers/linovelib/__init__.py +3 -3
- novel_downloader/core/fetchers/linovelib/browser.py +6 -1
- novel_downloader/core/fetchers/linovelib/session.py +6 -1
- novel_downloader/core/fetchers/qianbi/__init__.py +3 -3
- novel_downloader/core/fetchers/qianbi/browser.py +5 -0
- novel_downloader/core/fetchers/qianbi/session.py +5 -0
- novel_downloader/core/fetchers/qidian/__init__.py +3 -3
- novel_downloader/core/fetchers/qidian/browser.py +12 -4
- novel_downloader/core/fetchers/qidian/session.py +11 -3
- novel_downloader/core/fetchers/registry.py +71 -0
- novel_downloader/core/fetchers/sfacg/__init__.py +3 -3
- novel_downloader/core/fetchers/sfacg/browser.py +5 -0
- novel_downloader/core/fetchers/sfacg/session.py +5 -0
- novel_downloader/core/fetchers/yamibo/__init__.py +3 -3
- novel_downloader/core/fetchers/yamibo/browser.py +5 -0
- novel_downloader/core/fetchers/yamibo/session.py +6 -1
- novel_downloader/core/interfaces/__init__.py +7 -5
- novel_downloader/core/interfaces/searcher.py +18 -0
- novel_downloader/core/parsers/__init__.py +10 -11
- novel_downloader/core/parsers/{biquge/main_parser.py → biquge.py} +7 -2
- novel_downloader/core/parsers/{esjzone/main_parser.py → esjzone.py} +7 -2
- novel_downloader/core/parsers/{linovelib/main_parser.py → linovelib.py} +7 -2
- novel_downloader/core/parsers/{qianbi/main_parser.py → qianbi.py} +7 -2
- novel_downloader/core/parsers/qidian/__init__.py +2 -2
- novel_downloader/core/parsers/qidian/chapter_encrypted.py +23 -21
- novel_downloader/core/parsers/qidian/chapter_normal.py +1 -1
- novel_downloader/core/parsers/qidian/main_parser.py +10 -21
- novel_downloader/core/parsers/qidian/utils/__init__.py +11 -11
- novel_downloader/core/parsers/qidian/utils/decryptor_fetcher.py +5 -6
- novel_downloader/core/parsers/qidian/utils/node_decryptor.py +2 -2
- novel_downloader/core/parsers/registry.py +68 -0
- novel_downloader/core/parsers/{sfacg/main_parser.py → sfacg.py} +7 -2
- novel_downloader/core/parsers/{yamibo/main_parser.py → yamibo.py} +7 -2
- novel_downloader/core/searchers/__init__.py +20 -0
- novel_downloader/core/searchers/base.py +92 -0
- novel_downloader/core/searchers/biquge.py +83 -0
- novel_downloader/core/searchers/esjzone.py +84 -0
- novel_downloader/core/searchers/qianbi.py +131 -0
- novel_downloader/core/searchers/qidian.py +87 -0
- novel_downloader/core/searchers/registry.py +63 -0
- novel_downloader/locales/en.json +12 -4
- novel_downloader/locales/zh.json +12 -4
- novel_downloader/models/__init__.py +4 -30
- novel_downloader/models/config.py +12 -6
- novel_downloader/models/search.py +16 -0
- novel_downloader/models/types.py +0 -2
- novel_downloader/resources/config/settings.toml +31 -4
- novel_downloader/resources/css_styles/intro.css +83 -0
- novel_downloader/resources/css_styles/main.css +30 -89
- novel_downloader/utils/__init__.py +52 -0
- novel_downloader/utils/chapter_storage.py +244 -224
- novel_downloader/utils/constants.py +1 -21
- novel_downloader/utils/epub/__init__.py +34 -0
- novel_downloader/utils/epub/builder.py +377 -0
- novel_downloader/utils/epub/constants.py +77 -0
- novel_downloader/utils/epub/documents.py +403 -0
- novel_downloader/utils/epub/models.py +134 -0
- novel_downloader/utils/epub/utils.py +212 -0
- novel_downloader/utils/file_utils/__init__.py +10 -14
- novel_downloader/utils/file_utils/io.py +20 -51
- novel_downloader/utils/file_utils/normalize.py +2 -2
- novel_downloader/utils/file_utils/sanitize.py +2 -3
- novel_downloader/utils/fontocr/__init__.py +5 -5
- novel_downloader/utils/{hash_store.py → fontocr/hash_store.py} +4 -3
- novel_downloader/utils/{hash_utils.py → fontocr/hash_utils.py} +2 -2
- novel_downloader/utils/fontocr/ocr_v1.py +13 -1
- novel_downloader/utils/fontocr/ocr_v2.py +13 -1
- novel_downloader/utils/fontocr/ocr_v3.py +744 -0
- novel_downloader/utils/i18n.py +2 -0
- novel_downloader/utils/logger.py +2 -0
- novel_downloader/utils/network.py +110 -251
- novel_downloader/utils/state.py +1 -0
- novel_downloader/utils/text_utils/__init__.py +18 -17
- novel_downloader/utils/text_utils/diff_display.py +4 -5
- novel_downloader/utils/text_utils/numeric_conversion.py +253 -0
- novel_downloader/utils/text_utils/text_cleaner.py +179 -0
- novel_downloader/utils/text_utils/truncate_utils.py +62 -0
- novel_downloader/utils/time_utils/__init__.py +3 -3
- novel_downloader/utils/time_utils/datetime_utils.py +4 -5
- novel_downloader/utils/time_utils/sleep_utils.py +2 -3
- {novel_downloader-1.4.4.dist-info → novel_downloader-1.5.0.dist-info}/METADATA +2 -2
- novel_downloader-1.5.0.dist-info/RECORD +164 -0
- novel_downloader/config/site_rules.py +0 -94
- novel_downloader/core/factory/__init__.py +0 -20
- novel_downloader/core/factory/downloader.py +0 -73
- novel_downloader/core/factory/exporter.py +0 -58
- novel_downloader/core/factory/fetcher.py +0 -96
- novel_downloader/core/factory/parser.py +0 -86
- novel_downloader/core/fetchers/common/__init__.py +0 -14
- novel_downloader/core/fetchers/common/browser.py +0 -79
- novel_downloader/core/fetchers/common/session.py +0 -79
- novel_downloader/core/parsers/biquge/__init__.py +0 -10
- novel_downloader/core/parsers/common/__init__.py +0 -13
- novel_downloader/core/parsers/common/helper.py +0 -323
- novel_downloader/core/parsers/common/main_parser.py +0 -106
- novel_downloader/core/parsers/esjzone/__init__.py +0 -10
- novel_downloader/core/parsers/linovelib/__init__.py +0 -10
- novel_downloader/core/parsers/qianbi/__init__.py +0 -10
- novel_downloader/core/parsers/sfacg/__init__.py +0 -10
- novel_downloader/core/parsers/yamibo/__init__.py +0 -10
- novel_downloader/models/browser.py +0 -21
- novel_downloader/models/site_rules.py +0 -99
- novel_downloader/models/tasks.py +0 -33
- novel_downloader/resources/css_styles/volume-intro.css +0 -56
- novel_downloader/resources/json/replace_word_map.json +0 -4
- novel_downloader/resources/text/blacklist.txt +0 -22
- novel_downloader/utils/text_utils/chapter_formatting.py +0 -46
- novel_downloader/utils/text_utils/font_mapping.py +0 -28
- novel_downloader/utils/text_utils/text_cleaning.py +0 -107
- novel_downloader-1.4.4.dist-info/RECORD +0 -165
- {novel_downloader-1.4.4.dist-info → novel_downloader-1.5.0.dist-info}/WHEEL +0 -0
- {novel_downloader-1.4.4.dist-info → novel_downloader-1.5.0.dist-info}/entry_points.txt +0 -0
- {novel_downloader-1.4.4.dist-info → novel_downloader-1.5.0.dist-info}/licenses/LICENSE +0 -0
- {novel_downloader-1.4.4.dist-info → novel_downloader-1.5.0.dist-info}/top_level.txt +0 -0
novel_downloader/__init__.py
CHANGED
novel_downloader/cli/__init__.py
CHANGED
novel_downloader/cli/config.py
CHANGED
@@ -11,14 +11,11 @@ from argparse import Namespace, _SubParsersAction
|
|
11
11
|
from importlib.resources import as_file
|
12
12
|
from pathlib import Path
|
13
13
|
|
14
|
-
from novel_downloader.config import save_config_file
|
14
|
+
from novel_downloader.config import save_config_file
|
15
15
|
from novel_downloader.utils.constants import DEFAULT_SETTINGS_PATHS
|
16
16
|
from novel_downloader.utils.i18n import t
|
17
|
-
from novel_downloader.utils.logger import setup_logging
|
18
17
|
from novel_downloader.utils.state import state_mgr
|
19
18
|
|
20
|
-
# from novel_downloader.utils.hash_store import img_hash_store
|
21
|
-
|
22
19
|
|
23
20
|
def register_config_subcommand(subparsers: _SubParsersAction) -> None: # type: ignore
|
24
21
|
parser = subparsers.add_parser("config", help=t("help_config"))
|
@@ -27,9 +24,6 @@ def register_config_subcommand(subparsers: _SubParsersAction) -> None: # type:
|
|
27
24
|
_register_init(config_subparsers)
|
28
25
|
_register_set_lang(config_subparsers)
|
29
26
|
_register_set_config(config_subparsers)
|
30
|
-
_register_update_rules(config_subparsers)
|
31
|
-
_register_set_cookies(config_subparsers)
|
32
|
-
# _register_add_hash(config_subparsers)
|
33
27
|
|
34
28
|
|
35
29
|
def _register_init(subparsers: _SubParsersAction) -> None: # type: ignore
|
@@ -52,27 +46,7 @@ def _register_set_config(subparsers: _SubParsersAction) -> None: # type: ignore
|
|
52
46
|
parser.set_defaults(func=_handle_set_config)
|
53
47
|
|
54
48
|
|
55
|
-
def _register_update_rules(subparsers: _SubParsersAction) -> None: # type: ignore
|
56
|
-
parser = subparsers.add_parser("update-rules", help=t("settings_update_rules_help"))
|
57
|
-
parser.add_argument("path", type=str, help="Path to TOML/YAML/JSON rule file")
|
58
|
-
parser.set_defaults(func=_handle_update_rules)
|
59
|
-
|
60
|
-
|
61
|
-
def _register_set_cookies(subparsers: _SubParsersAction) -> None: # type: ignore
|
62
|
-
parser = subparsers.add_parser("set-cookies", help=t("settings_set_cookies_help"))
|
63
|
-
parser.add_argument("site", nargs="?", help="Site identifier")
|
64
|
-
parser.add_argument("cookies", nargs="?", help="Cookies string")
|
65
|
-
parser.set_defaults(func=_handle_set_cookies)
|
66
|
-
|
67
|
-
|
68
|
-
# def _register_add_hash(subparsers: _SubParsersAction) -> None: # type: ignore
|
69
|
-
# parser = subparsers.add_parser("add-hash", help=t("settings_add_hash_help"))
|
70
|
-
# parser.add_argument("--path", type=str, help=t("settings_add_hash_path_help"))
|
71
|
-
# parser.set_defaults(func=_handle_add_hash)
|
72
|
-
|
73
|
-
|
74
49
|
def _handle_init(args: Namespace) -> None:
|
75
|
-
setup_logging()
|
76
50
|
cwd = Path.cwd()
|
77
51
|
|
78
52
|
for resource in DEFAULT_SETTINGS_PATHS:
|
@@ -119,59 +93,3 @@ def _handle_set_config(args: Namespace) -> None:
|
|
119
93
|
except Exception as e:
|
120
94
|
print(t("settings_set_config_fail", err=str(e)))
|
121
95
|
raise
|
122
|
-
|
123
|
-
|
124
|
-
def _handle_update_rules(args: Namespace) -> None:
|
125
|
-
try:
|
126
|
-
save_rules_as_json(args.path)
|
127
|
-
print(t("settings_update_rules", path=args.path))
|
128
|
-
except Exception as e:
|
129
|
-
print(t("settings_update_rules_fail", err=str(e)))
|
130
|
-
raise
|
131
|
-
|
132
|
-
|
133
|
-
def _handle_set_cookies(args: Namespace) -> None:
|
134
|
-
site = args.site or input(t("settings_set_cookies_prompt_site") + ": ").strip()
|
135
|
-
cookies = (
|
136
|
-
args.cookies or input(t("settings_set_cookies_prompt_payload") + ": ").strip()
|
137
|
-
)
|
138
|
-
|
139
|
-
try:
|
140
|
-
state_mgr.set_cookies(site, cookies)
|
141
|
-
print(t("settings_set_cookies_success", site=site))
|
142
|
-
except Exception as e:
|
143
|
-
print(t("settings_set_cookies_fail", err=str(e)))
|
144
|
-
raise
|
145
|
-
|
146
|
-
|
147
|
-
# def _handle_add_hash(args: Namespace) -> None:
|
148
|
-
# if args.path:
|
149
|
-
# try:
|
150
|
-
# img_hash_store.add_from_map(args.path)
|
151
|
-
# img_hash_store.save()
|
152
|
-
# print(t("settings_add_hash_loaded", path=args.path))
|
153
|
-
# except Exception as e:
|
154
|
-
# print(t("settings_add_hash_load_fail", err=str(e)))
|
155
|
-
# raise
|
156
|
-
# else:
|
157
|
-
# print(t("settings_add_hash_prompt_tip"))
|
158
|
-
# while True:
|
159
|
-
# img_path = input(t("settings_add_hash_prompt_img") + ": ").strip()
|
160
|
-
# if not img_path or img_path.lower() in {"exit", "quit"}:
|
161
|
-
# break
|
162
|
-
# if not Path(img_path).exists():
|
163
|
-
# print(t("settings_add_hash_path_invalid"))
|
164
|
-
# continue
|
165
|
-
|
166
|
-
# label = input(t("settings_add_hash_prompt_label") + ": ").strip()
|
167
|
-
# if not label or label.lower() in {"exit", "quit"}:
|
168
|
-
# break
|
169
|
-
|
170
|
-
# try:
|
171
|
-
# img_hash_store.add_image(img_path, label)
|
172
|
-
# print(t("settings_add_hash_added", img=img_path, label=label))
|
173
|
-
# except Exception as e:
|
174
|
-
# print(t("settings_add_hash_failed", err=str(e)))
|
175
|
-
|
176
|
-
# img_hash_store.save()
|
177
|
-
# print(t("settings_add_hash_saved"))
|
novel_downloader/cli/download.py
CHANGED
@@ -15,13 +15,13 @@ from pathlib import Path
|
|
15
15
|
from typing import Any
|
16
16
|
|
17
17
|
from novel_downloader.config import ConfigAdapter, load_config
|
18
|
-
from novel_downloader.core
|
18
|
+
from novel_downloader.core import (
|
19
|
+
FetcherProtocol,
|
19
20
|
get_downloader,
|
20
21
|
get_exporter,
|
21
22
|
get_fetcher,
|
22
23
|
get_parser,
|
23
24
|
)
|
24
|
-
from novel_downloader.core.interfaces import FetcherProtocol
|
25
25
|
from novel_downloader.models import BookConfig, LoginField
|
26
26
|
from novel_downloader.utils.cookies import resolve_cookies
|
27
27
|
from novel_downloader.utils.i18n import t
|
@@ -44,8 +44,6 @@ def register_download_subcommand(subparsers: _SubParsersAction) -> None: # type
|
|
44
44
|
|
45
45
|
|
46
46
|
def handle_download(args: Namespace) -> None:
|
47
|
-
setup_logging()
|
48
|
-
|
49
47
|
site: str = args.site
|
50
48
|
config_path: Path | None = Path(args.config) if args.config else None
|
51
49
|
book_ids: list[BookConfig] = _cli_args_to_book_configs(
|
@@ -143,10 +141,11 @@ async def _download(
|
|
143
141
|
fetcher_cfg = adapter.get_fetcher_config()
|
144
142
|
parser_cfg = adapter.get_parser_config()
|
145
143
|
exporter_cfg = adapter.get_exporter_config()
|
144
|
+
log_level = adapter.get_log_level()
|
146
145
|
|
147
146
|
parser = get_parser(site, parser_cfg)
|
148
147
|
exporter = get_exporter(site, exporter_cfg)
|
149
|
-
setup_logging()
|
148
|
+
setup_logging(log_level=log_level)
|
150
149
|
|
151
150
|
async with get_fetcher(site, fetcher_cfg) as fetcher:
|
152
151
|
if downloader_cfg.login_required and not await fetcher.load_state():
|
novel_downloader/cli/export.py
CHANGED
@@ -9,8 +9,9 @@ from argparse import Namespace, _SubParsersAction
|
|
9
9
|
from pathlib import Path
|
10
10
|
|
11
11
|
from novel_downloader.config import ConfigAdapter, load_config
|
12
|
-
from novel_downloader.core
|
12
|
+
from novel_downloader.core import get_exporter
|
13
13
|
from novel_downloader.utils.i18n import t
|
14
|
+
from novel_downloader.utils.logger import setup_logging
|
14
15
|
|
15
16
|
|
16
17
|
def register_export_subcommand(subparsers: _SubParsersAction) -> None: # type: ignore
|
@@ -57,7 +58,9 @@ def handle_export(args: Namespace) -> None:
|
|
57
58
|
|
58
59
|
adapter = ConfigAdapter(config=config_data, site=site)
|
59
60
|
exporter_cfg = adapter.get_exporter_config()
|
61
|
+
log_level = adapter.get_log_level()
|
60
62
|
exporter = get_exporter(site, exporter_cfg)
|
63
|
+
setup_logging(log_level=log_level)
|
61
64
|
|
62
65
|
for book_id in book_ids:
|
63
66
|
print(t("export_processing", book_id=book_id, format=export_format))
|
novel_downloader/cli/main.py
CHANGED
@@ -14,6 +14,7 @@ from .clean import register_clean_subcommand
|
|
14
14
|
from .config import register_config_subcommand
|
15
15
|
from .download import register_download_subcommand
|
16
16
|
from .export import register_export_subcommand
|
17
|
+
from .search import register_search_subcommand
|
17
18
|
|
18
19
|
|
19
20
|
def cli_main() -> None:
|
@@ -24,6 +25,7 @@ def cli_main() -> None:
|
|
24
25
|
register_config_subcommand(subparsers)
|
25
26
|
register_download_subcommand(subparsers)
|
26
27
|
register_export_subcommand(subparsers)
|
28
|
+
register_search_subcommand(subparsers)
|
27
29
|
|
28
30
|
args = parser.parse_args()
|
29
31
|
if hasattr(args, "func"):
|
@@ -0,0 +1,123 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
novel_downloader.cli.search
|
4
|
+
---------------------------
|
5
|
+
|
6
|
+
"""
|
7
|
+
|
8
|
+
import asyncio
|
9
|
+
from argparse import Namespace, _SubParsersAction
|
10
|
+
from collections.abc import Sequence
|
11
|
+
from pathlib import Path
|
12
|
+
|
13
|
+
from novel_downloader.cli.download import _download
|
14
|
+
from novel_downloader.config import ConfigAdapter, load_config
|
15
|
+
from novel_downloader.core import search
|
16
|
+
from novel_downloader.models import BookConfig, SearchResult
|
17
|
+
from novel_downloader.utils.i18n import t
|
18
|
+
|
19
|
+
|
20
|
+
def register_search_subcommand(subparsers: _SubParsersAction) -> None: # type: ignore
|
21
|
+
parser = subparsers.add_parser("search", help=t("help_search"))
|
22
|
+
|
23
|
+
parser.add_argument(
|
24
|
+
"--site",
|
25
|
+
"-s",
|
26
|
+
action="append",
|
27
|
+
metavar="SITE",
|
28
|
+
help=t("help_search_sites"),
|
29
|
+
)
|
30
|
+
parser.add_argument(
|
31
|
+
"keyword",
|
32
|
+
help=t("help_search_keyword"),
|
33
|
+
)
|
34
|
+
parser.add_argument(
|
35
|
+
"--config",
|
36
|
+
type=str,
|
37
|
+
help=t("help_config"),
|
38
|
+
)
|
39
|
+
parser.add_argument(
|
40
|
+
"--limit",
|
41
|
+
"-l",
|
42
|
+
type=int,
|
43
|
+
default=10,
|
44
|
+
metavar="N",
|
45
|
+
help=t("help_search_limit"),
|
46
|
+
)
|
47
|
+
parser.add_argument(
|
48
|
+
"--site-limit",
|
49
|
+
type=int,
|
50
|
+
default=5,
|
51
|
+
metavar="M",
|
52
|
+
help=t("help_search_site_limit"),
|
53
|
+
)
|
54
|
+
|
55
|
+
parser.set_defaults(func=handle_search)
|
56
|
+
|
57
|
+
|
58
|
+
def handle_search(args: Namespace) -> None:
|
59
|
+
"""
|
60
|
+
Handler for the `search` subcommand. Loads config, runs the search,
|
61
|
+
prompts the user to pick one result, then kicks off download.
|
62
|
+
"""
|
63
|
+
sites: Sequence[str] | None = args.site or None
|
64
|
+
keyword: str = args.keyword
|
65
|
+
overall_limit = max(1, args.limit)
|
66
|
+
per_site_limit = max(1, args.site_limit)
|
67
|
+
config_path: Path | None = Path(args.config) if args.config else None
|
68
|
+
|
69
|
+
try:
|
70
|
+
config_data = load_config(config_path)
|
71
|
+
except Exception as e:
|
72
|
+
print(t("download_config_load_fail", err=str(e)))
|
73
|
+
return
|
74
|
+
|
75
|
+
results = search(
|
76
|
+
keyword=keyword,
|
77
|
+
sites=sites,
|
78
|
+
limit=overall_limit,
|
79
|
+
per_site_limit=per_site_limit,
|
80
|
+
)
|
81
|
+
|
82
|
+
chosen = _prompt_user_select(results)
|
83
|
+
if chosen is None:
|
84
|
+
# user cancelled or no valid choice
|
85
|
+
return
|
86
|
+
|
87
|
+
adapter = ConfigAdapter(config=config_data, site=chosen["site"])
|
88
|
+
books: list[BookConfig] = [{"book_id": chosen["book_id"]}]
|
89
|
+
asyncio.run(_download(adapter, chosen["site"], books))
|
90
|
+
|
91
|
+
|
92
|
+
def _prompt_user_select(
|
93
|
+
results: Sequence[SearchResult],
|
94
|
+
max_attempts: int = 3,
|
95
|
+
) -> SearchResult | None:
|
96
|
+
"""
|
97
|
+
Display a numbered list of results and prompt the user to pick one.
|
98
|
+
|
99
|
+
:param results: A list of SearchResult dicts.
|
100
|
+
:param max_attempts: How many bad inputs to tolerate before giving up.
|
101
|
+
:return: The chosen SearchResult, or None if cancelled/failed.
|
102
|
+
"""
|
103
|
+
if not results:
|
104
|
+
print(t("no_results"))
|
105
|
+
return None
|
106
|
+
|
107
|
+
# Show choices
|
108
|
+
for i, r in enumerate(results, start=1):
|
109
|
+
print(f"[{i}] {r['title']} - {r['author']} ({r['site']}, id: {r['book_id']})")
|
110
|
+
|
111
|
+
attempts = 0
|
112
|
+
while attempts < max_attempts:
|
113
|
+
choice = input(t("prompt_select_index")).strip()
|
114
|
+
if choice == "":
|
115
|
+
return None
|
116
|
+
if choice.isdigit():
|
117
|
+
idx = int(choice)
|
118
|
+
if 1 <= idx <= len(results):
|
119
|
+
return results[idx - 1]
|
120
|
+
print(t("invalid_selection"))
|
121
|
+
attempts += 1
|
122
|
+
|
123
|
+
return None
|
@@ -8,20 +8,13 @@ Unified interface for loading and adapting configuration files.
|
|
8
8
|
This module provides:
|
9
9
|
- load_config: loads YAML config from file path with fallback support
|
10
10
|
- ConfigAdapter: maps raw config + site name to structured config models
|
11
|
-
- Configuration dataclasses: RequesterConfig, DownloaderConfig, etc.
|
12
11
|
"""
|
13
12
|
|
14
|
-
from .adapter import ConfigAdapter
|
15
|
-
from .loader import load_config, save_config_file
|
16
|
-
from .site_rules import (
|
17
|
-
load_site_rules,
|
18
|
-
save_rules_as_json,
|
19
|
-
)
|
20
|
-
|
21
13
|
__all__ = [
|
22
14
|
"load_config",
|
23
15
|
"save_config_file",
|
24
16
|
"ConfigAdapter",
|
25
|
-
"load_site_rules",
|
26
|
-
"save_rules_as_json",
|
27
17
|
]
|
18
|
+
|
19
|
+
from .adapter import ConfigAdapter
|
20
|
+
from .loader import load_config, save_config_file
|