novel-downloader 1.2.2__py3-none-any.whl → 1.3.1__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 -2
- novel_downloader/cli/__init__.py +0 -1
- novel_downloader/cli/clean.py +2 -10
- novel_downloader/cli/download.py +16 -22
- novel_downloader/cli/interactive.py +0 -1
- novel_downloader/cli/main.py +1 -3
- novel_downloader/cli/settings.py +8 -8
- novel_downloader/config/__init__.py +0 -1
- novel_downloader/config/adapter.py +32 -27
- novel_downloader/config/loader.py +116 -108
- novel_downloader/config/models.py +35 -29
- novel_downloader/config/site_rules.py +2 -4
- novel_downloader/core/__init__.py +0 -1
- novel_downloader/core/downloaders/__init__.py +4 -4
- novel_downloader/core/downloaders/base/__init__.py +14 -0
- novel_downloader/core/downloaders/{base_async_downloader.py → base/base_async.py} +49 -53
- novel_downloader/core/downloaders/{base_downloader.py → base/base_sync.py} +64 -43
- novel_downloader/core/downloaders/biquge/__init__.py +12 -0
- novel_downloader/core/downloaders/biquge/biquge_sync.py +25 -0
- novel_downloader/core/downloaders/common/__init__.py +14 -0
- novel_downloader/core/downloaders/{common_asynb_downloader.py → common/common_async.py} +42 -33
- novel_downloader/core/downloaders/{common_downloader.py → common/common_sync.py} +33 -21
- novel_downloader/core/downloaders/qidian/__init__.py +10 -0
- novel_downloader/core/downloaders/{qidian_downloader.py → qidian/qidian_sync.py} +79 -62
- novel_downloader/core/factory/__init__.py +4 -5
- novel_downloader/core/factory/{downloader_factory.py → downloader.py} +25 -26
- novel_downloader/core/factory/{parser_factory.py → parser.py} +12 -14
- novel_downloader/core/factory/{requester_factory.py → requester.py} +29 -16
- novel_downloader/core/factory/{saver_factory.py → saver.py} +4 -9
- novel_downloader/core/interfaces/__init__.py +8 -9
- novel_downloader/core/interfaces/{async_downloader_protocol.py → async_downloader.py} +4 -5
- novel_downloader/core/interfaces/{async_requester_protocol.py → async_requester.py} +23 -12
- novel_downloader/core/interfaces/{parser_protocol.py → parser.py} +11 -6
- novel_downloader/core/interfaces/{saver_protocol.py → saver.py} +2 -3
- novel_downloader/core/interfaces/{downloader_protocol.py → sync_downloader.py} +6 -7
- novel_downloader/core/interfaces/{requester_protocol.py → sync_requester.py} +31 -17
- novel_downloader/core/parsers/__init__.py +5 -4
- novel_downloader/core/parsers/{base_parser.py → base.py} +18 -9
- novel_downloader/core/parsers/biquge/__init__.py +10 -0
- novel_downloader/core/parsers/biquge/main_parser.py +126 -0
- novel_downloader/core/parsers/{common_parser → common}/__init__.py +2 -3
- novel_downloader/core/parsers/{common_parser → common}/helper.py +13 -13
- novel_downloader/core/parsers/{common_parser → common}/main_parser.py +15 -9
- novel_downloader/core/parsers/{qidian_parser → qidian}/__init__.py +2 -3
- novel_downloader/core/parsers/{qidian_parser → qidian}/browser/__init__.py +2 -3
- novel_downloader/core/parsers/{qidian_parser → qidian}/browser/chapter_encrypted.py +40 -48
- novel_downloader/core/parsers/{qidian_parser → qidian}/browser/chapter_normal.py +17 -21
- novel_downloader/core/parsers/{qidian_parser → qidian}/browser/chapter_router.py +10 -9
- novel_downloader/core/parsers/{qidian_parser → qidian}/browser/main_parser.py +14 -10
- novel_downloader/core/parsers/{qidian_parser → qidian}/session/__init__.py +2 -3
- novel_downloader/core/parsers/{qidian_parser → qidian}/session/chapter_encrypted.py +36 -44
- novel_downloader/core/parsers/{qidian_parser → qidian}/session/chapter_normal.py +19 -23
- novel_downloader/core/parsers/{qidian_parser → qidian}/session/chapter_router.py +10 -9
- novel_downloader/core/parsers/{qidian_parser → qidian}/session/main_parser.py +14 -10
- novel_downloader/core/parsers/{qidian_parser → qidian}/session/node_decryptor.py +7 -10
- novel_downloader/core/parsers/{qidian_parser → qidian}/shared/__init__.py +2 -3
- novel_downloader/core/parsers/{qidian_parser → qidian}/shared/book_info_parser.py +5 -6
- novel_downloader/core/parsers/{qidian_parser → qidian}/shared/helpers.py +7 -8
- novel_downloader/core/requesters/__init__.py +9 -5
- novel_downloader/core/requesters/base/__init__.py +16 -0
- novel_downloader/core/requesters/{base_async_session.py → base/async_session.py} +177 -73
- novel_downloader/core/requesters/base/browser.py +340 -0
- novel_downloader/core/requesters/base/session.py +364 -0
- novel_downloader/core/requesters/biquge/__init__.py +12 -0
- novel_downloader/core/requesters/biquge/session.py +90 -0
- novel_downloader/core/requesters/{common_requester → common}/__init__.py +4 -5
- novel_downloader/core/requesters/common/async_session.py +96 -0
- novel_downloader/core/requesters/common/session.py +113 -0
- novel_downloader/core/requesters/qidian/__init__.py +21 -0
- novel_downloader/core/requesters/qidian/broswer.py +307 -0
- novel_downloader/core/requesters/qidian/session.py +287 -0
- novel_downloader/core/savers/__init__.py +5 -3
- novel_downloader/core/savers/{base_saver.py → base.py} +12 -13
- novel_downloader/core/savers/biquge.py +25 -0
- novel_downloader/core/savers/{common_saver → common}/__init__.py +2 -3
- novel_downloader/core/savers/{common_saver/common_epub.py → common/epub.py} +23 -51
- novel_downloader/core/savers/{common_saver → common}/main_saver.py +43 -9
- novel_downloader/core/savers/{common_saver/common_txt.py → common/txt.py} +16 -46
- novel_downloader/core/savers/epub_utils/__init__.py +0 -1
- novel_downloader/core/savers/epub_utils/css_builder.py +13 -7
- novel_downloader/core/savers/epub_utils/initializer.py +4 -5
- novel_downloader/core/savers/epub_utils/text_to_html.py +2 -3
- novel_downloader/core/savers/epub_utils/volume_intro.py +1 -3
- novel_downloader/core/savers/{qidian_saver.py → qidian.py} +12 -6
- novel_downloader/locales/en.json +8 -4
- novel_downloader/locales/zh.json +5 -1
- novel_downloader/resources/config/settings.toml +88 -0
- novel_downloader/utils/cache.py +2 -2
- novel_downloader/utils/chapter_storage.py +340 -0
- novel_downloader/utils/constants.py +6 -4
- novel_downloader/utils/crypto_utils.py +3 -3
- novel_downloader/utils/file_utils/__init__.py +0 -1
- novel_downloader/utils/file_utils/io.py +12 -17
- novel_downloader/utils/file_utils/normalize.py +1 -3
- novel_downloader/utils/file_utils/sanitize.py +2 -9
- novel_downloader/utils/fontocr/__init__.py +0 -1
- novel_downloader/utils/fontocr/ocr_v1.py +19 -22
- novel_downloader/utils/fontocr/ocr_v2.py +147 -60
- novel_downloader/utils/hash_store.py +19 -20
- novel_downloader/utils/hash_utils.py +0 -1
- novel_downloader/utils/i18n.py +3 -4
- novel_downloader/utils/logger.py +5 -6
- novel_downloader/utils/model_loader.py +5 -8
- novel_downloader/utils/network.py +9 -10
- novel_downloader/utils/state.py +6 -7
- novel_downloader/utils/text_utils/__init__.py +0 -1
- novel_downloader/utils/text_utils/chapter_formatting.py +2 -7
- novel_downloader/utils/text_utils/diff_display.py +0 -1
- novel_downloader/utils/text_utils/font_mapping.py +1 -4
- novel_downloader/utils/text_utils/text_cleaning.py +0 -1
- novel_downloader/utils/time_utils/__init__.py +0 -1
- novel_downloader/utils/time_utils/datetime_utils.py +8 -10
- novel_downloader/utils/time_utils/sleep_utils.py +1 -3
- {novel_downloader-1.2.2.dist-info → novel_downloader-1.3.1.dist-info}/METADATA +14 -17
- novel_downloader-1.3.1.dist-info/RECORD +127 -0
- {novel_downloader-1.2.2.dist-info → novel_downloader-1.3.1.dist-info}/WHEEL +1 -1
- novel_downloader/core/requesters/base_browser.py +0 -214
- novel_downloader/core/requesters/base_session.py +0 -246
- novel_downloader/core/requesters/common_requester/common_async_session.py +0 -98
- novel_downloader/core/requesters/common_requester/common_session.py +0 -126
- novel_downloader/core/requesters/qidian_requester/__init__.py +0 -22
- novel_downloader/core/requesters/qidian_requester/qidian_broswer.py +0 -396
- novel_downloader/core/requesters/qidian_requester/qidian_session.py +0 -202
- novel_downloader/resources/config/settings.yaml +0 -76
- novel_downloader-1.2.2.dist-info/RECORD +0 -115
- {novel_downloader-1.2.2.dist-info → novel_downloader-1.3.1.dist-info}/entry_points.txt +0 -0
- {novel_downloader-1.2.2.dist-info → novel_downloader-1.3.1.dist-info}/licenses/LICENSE +0 -0
- {novel_downloader-1.2.2.dist-info → novel_downloader-1.3.1.dist-info}/top_level.txt +0 -0
novel_downloader/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
-
# -*- coding: utf-8 -*-
|
3
2
|
"""
|
4
3
|
novel_downloader
|
5
4
|
----------------
|
@@ -7,7 +6,7 @@ novel_downloader
|
|
7
6
|
Core package for the Novel Downloader project.
|
8
7
|
"""
|
9
8
|
|
10
|
-
__version__ = "1.
|
9
|
+
__version__ = "1.3.1"
|
11
10
|
|
12
11
|
__author__ = "Saudade Z"
|
13
12
|
__email__ = "saudadez217@gmail.com"
|
novel_downloader/cli/__init__.py
CHANGED
novel_downloader/cli/clean.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
-
# -*- coding: utf-8 -*-
|
3
2
|
"""
|
4
3
|
novel_downloader.cli.clean
|
5
4
|
-----------------------------
|
@@ -8,7 +7,6 @@ novel_downloader.cli.clean
|
|
8
7
|
|
9
8
|
import shutil
|
10
9
|
from pathlib import Path
|
11
|
-
from typing import List, Optional
|
12
10
|
|
13
11
|
import click
|
14
12
|
|
@@ -19,7 +17,6 @@ from novel_downloader.utils.constants import (
|
|
19
17
|
LOGGER_DIR,
|
20
18
|
MODEL_CACHE_DIR,
|
21
19
|
REC_CHAR_MODEL_REPO,
|
22
|
-
STATE_DIR,
|
23
20
|
)
|
24
21
|
from novel_downloader.utils.i18n import t
|
25
22
|
|
@@ -35,7 +32,7 @@ def delete_path(p: Path) -> None:
|
|
35
32
|
click.echo(f"[clean] {t('clean_not_found')}: {p}")
|
36
33
|
|
37
34
|
|
38
|
-
def clean_model_repo_cache(repo_id:
|
35
|
+
def clean_model_repo_cache(repo_id: str | None = None, all: bool = False) -> bool:
|
39
36
|
"""
|
40
37
|
Delete Hugging Face cache for a specific repo.
|
41
38
|
"""
|
@@ -61,7 +58,6 @@ def clean_model_repo_cache(repo_id: Optional[str] = None, all: bool = False) ->
|
|
61
58
|
@click.command(name="clean", help=t("help_clean")) # type: ignore
|
62
59
|
@click.option("--logs", is_flag=True, help=t("clean_logs")) # type: ignore
|
63
60
|
@click.option("--cache", is_flag=True, help=t("clean_cache")) # type: ignore
|
64
|
-
@click.option("--state", is_flag=True, help=t("clean_state")) # type: ignore
|
65
61
|
@click.option("--data", is_flag=True, help=t("clean_data")) # type: ignore
|
66
62
|
@click.option("--config", is_flag=True, help=t("clean_config")) # type: ignore
|
67
63
|
@click.option("--models", is_flag=True, help=t("clean_models")) # type: ignore
|
@@ -72,7 +68,6 @@ def clean_model_repo_cache(repo_id: Optional[str] = None, all: bool = False) ->
|
|
72
68
|
def clean_cli(
|
73
69
|
logs: bool,
|
74
70
|
cache: bool,
|
75
|
-
state: bool,
|
76
71
|
data: bool,
|
77
72
|
config: bool,
|
78
73
|
models: bool,
|
@@ -81,7 +76,7 @@ def clean_cli(
|
|
81
76
|
all: bool,
|
82
77
|
yes: bool,
|
83
78
|
) -> None:
|
84
|
-
targets:
|
79
|
+
targets: list[Path] = []
|
85
80
|
|
86
81
|
if all:
|
87
82
|
if not yes:
|
@@ -92,7 +87,6 @@ def clean_cli(
|
|
92
87
|
targets = [
|
93
88
|
LOGGER_DIR,
|
94
89
|
JS_SCRIPT_DIR,
|
95
|
-
STATE_DIR,
|
96
90
|
DATA_DIR,
|
97
91
|
CONFIG_DIR,
|
98
92
|
MODEL_CACHE_DIR,
|
@@ -102,8 +96,6 @@ def clean_cli(
|
|
102
96
|
targets.append(LOGGER_DIR)
|
103
97
|
if cache:
|
104
98
|
targets.append(JS_SCRIPT_DIR)
|
105
|
-
if state:
|
106
|
-
targets.append(STATE_DIR)
|
107
99
|
if data:
|
108
100
|
targets.append(DATA_DIR)
|
109
101
|
if config:
|
novel_downloader/cli/download.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
-
# -*- coding: utf-8 -*-
|
3
2
|
"""
|
4
3
|
novel_downloader.cli.download
|
5
4
|
-----------------------------
|
@@ -8,7 +7,7 @@ Download full novels by book IDs
|
|
8
7
|
(supports config files, site switching, and localization prompts).
|
9
8
|
"""
|
10
9
|
|
11
|
-
|
10
|
+
import asyncio
|
12
11
|
|
13
12
|
import click
|
14
13
|
from click import Context
|
@@ -41,7 +40,7 @@ from novel_downloader.utils.logger import setup_logging
|
|
41
40
|
help=t("download_option_site", default="qidian"),
|
42
41
|
) # type: ignore
|
43
42
|
@click.pass_context # type: ignore
|
44
|
-
def download_cli(ctx: Context, book_ids:
|
43
|
+
def download_cli(ctx: Context, book_ids: list[str], site: str) -> None:
|
45
44
|
"""Download full novels by book IDs."""
|
46
45
|
config_path = ctx.obj.get("config_path")
|
47
46
|
|
@@ -69,7 +68,7 @@ def download_cli(ctx: Context, book_ids: List[str], site: str) -> None:
|
|
69
68
|
|
70
69
|
# Filter out placeholder/example IDs
|
71
70
|
invalid_ids = {"0000000000"}
|
72
|
-
valid_book_ids =
|
71
|
+
valid_book_ids = set(book_ids) - invalid_ids
|
73
72
|
|
74
73
|
if not book_ids:
|
75
74
|
click.echo(t("download_no_ids"))
|
@@ -82,21 +81,20 @@ def download_cli(ctx: Context, book_ids: List[str], site: str) -> None:
|
|
82
81
|
|
83
82
|
# Initialize the requester, parser, saver, and downloader components
|
84
83
|
if downloader_cfg.mode == "async":
|
85
|
-
import asyncio
|
86
|
-
|
87
|
-
async_requester = get_async_requester(site, requester_cfg)
|
88
|
-
async_parser = get_parser(site, parser_cfg)
|
89
|
-
async_saver = get_saver(site, saver_cfg)
|
90
84
|
setup_logging()
|
91
|
-
async_downloader = get_async_downloader(
|
92
|
-
requester=async_requester,
|
93
|
-
parser=async_parser,
|
94
|
-
saver=async_saver,
|
95
|
-
site=site,
|
96
|
-
config=downloader_cfg,
|
97
|
-
)
|
98
85
|
|
99
86
|
async def async_download_all() -> None:
|
87
|
+
async_requester = get_async_requester(site, requester_cfg)
|
88
|
+
async_parser = get_parser(site, parser_cfg)
|
89
|
+
async_saver = get_saver(site, saver_cfg)
|
90
|
+
async_downloader = get_async_downloader(
|
91
|
+
requester=async_requester,
|
92
|
+
parser=async_parser,
|
93
|
+
saver=async_saver,
|
94
|
+
site=site,
|
95
|
+
config=downloader_cfg,
|
96
|
+
)
|
97
|
+
|
100
98
|
prepare = getattr(async_downloader, "prepare", None)
|
101
99
|
if prepare and asyncio.iscoroutinefunction(prepare):
|
102
100
|
await prepare()
|
@@ -105,9 +103,7 @@ def download_cli(ctx: Context, book_ids: List[str], site: str) -> None:
|
|
105
103
|
click.echo(t("download_downloading", book_id=book_id, site=site))
|
106
104
|
await async_downloader.download_one(book_id)
|
107
105
|
|
108
|
-
|
109
|
-
input(t("download_prompt_parse"))
|
110
|
-
await async_requester.shutdown()
|
106
|
+
await async_requester.close()
|
111
107
|
|
112
108
|
asyncio.run(async_download_all())
|
113
109
|
else:
|
@@ -127,8 +123,6 @@ def download_cli(ctx: Context, book_ids: List[str], site: str) -> None:
|
|
127
123
|
click.echo(t("download_downloading", book_id=book_id, site=site))
|
128
124
|
sync_downloader.download_one(book_id)
|
129
125
|
|
130
|
-
|
131
|
-
input(t("download_prompt_parse"))
|
132
|
-
sync_requester.shutdown()
|
126
|
+
sync_requester.close()
|
133
127
|
|
134
128
|
return
|
novel_downloader/cli/main.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
-
# -*- coding: utf-8 -*-
|
3
2
|
"""
|
4
3
|
novel_downloader.cli.main
|
5
4
|
--------------------------
|
@@ -7,7 +6,6 @@ novel_downloader.cli.main
|
|
7
6
|
Unified CLI entry point. Parses arguments and delegates to parser or interactive.
|
8
7
|
"""
|
9
8
|
|
10
|
-
from typing import Optional
|
11
9
|
|
12
10
|
import click
|
13
11
|
from click import Context
|
@@ -24,7 +22,7 @@ from novel_downloader.utils.i18n import t
|
|
24
22
|
help=t("help_config"),
|
25
23
|
) # type: ignore
|
26
24
|
@click.pass_context # type: ignore
|
27
|
-
def cli_main(ctx: Context, config:
|
25
|
+
def cli_main(ctx: Context, config: str | None) -> None:
|
28
26
|
"""Novel Downloader CLI."""
|
29
27
|
ctx.ensure_object(dict)
|
30
28
|
ctx.obj["config_path"] = config
|
novel_downloader/cli/settings.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
-
# -*- coding: utf-8 -*-
|
3
2
|
"""
|
4
3
|
novel_downloader.cli.settings
|
5
4
|
-----------------------------
|
@@ -10,7 +9,6 @@ Commands to configure novel downloader settings.
|
|
10
9
|
import shutil
|
11
10
|
from importlib.resources import as_file
|
12
11
|
from pathlib import Path
|
13
|
-
from typing import Optional
|
14
12
|
|
15
13
|
import click
|
16
14
|
from click import Context
|
@@ -61,7 +59,7 @@ def init_settings(force: bool) -> None:
|
|
61
59
|
except Exception as e:
|
62
60
|
raise click.ClickException(
|
63
61
|
t("settings_init_error", filename=resource.name, err=e)
|
64
|
-
)
|
62
|
+
) from e
|
65
63
|
|
66
64
|
|
67
65
|
@settings_cli.command(name="set-lang", help=t("settings_set_lang_help")) # type: ignore
|
@@ -81,7 +79,7 @@ def set_config(path: str) -> None:
|
|
81
79
|
save_config_file(path)
|
82
80
|
click.echo(t("settings_set_config", path=path))
|
83
81
|
except Exception as e:
|
84
|
-
raise click.ClickException(t("settings_set_config_fail", err=e))
|
82
|
+
raise click.ClickException(t("settings_set_config_fail", err=e)) from e
|
85
83
|
|
86
84
|
|
87
85
|
@settings_cli.command(name="update-rules", help=t("settings_update_rules_help")) # type: ignore
|
@@ -92,7 +90,7 @@ def update_rules(path: str) -> None:
|
|
92
90
|
save_rules_as_json(path)
|
93
91
|
click.echo(t("settings_update_rules", path=path))
|
94
92
|
except Exception as e:
|
95
|
-
raise click.ClickException(t("settings_update_rules_fail", err=e))
|
93
|
+
raise click.ClickException(t("settings_update_rules_fail", err=e)) from e
|
96
94
|
|
97
95
|
|
98
96
|
@settings_cli.command(
|
@@ -120,7 +118,7 @@ def set_cookies(ctx: Context, site: str, cookies: str) -> None:
|
|
120
118
|
state_mgr.set_cookies(site, cookies)
|
121
119
|
click.echo(t("settings_set_cookies_success", site=site))
|
122
120
|
except Exception as e:
|
123
|
-
raise click.ClickException(t("settings_set_cookies_fail", err=e))
|
121
|
+
raise click.ClickException(t("settings_set_cookies_fail", err=e)) from e
|
124
122
|
|
125
123
|
|
126
124
|
@settings_cli.command(name="add-hash", help=t("settings_add_hash_help")) # type: ignore
|
@@ -129,7 +127,7 @@ def set_cookies(ctx: Context, site: str, cookies: str) -> None:
|
|
129
127
|
type=click.Path(exists=True, dir_okay=False),
|
130
128
|
help=t("settings_add_hash_path_help"),
|
131
129
|
) # type: ignore
|
132
|
-
def add_image_hashes(path:
|
130
|
+
def add_image_hashes(path: str | None) -> None:
|
133
131
|
"""
|
134
132
|
Add image hashes to internal store for matching.
|
135
133
|
Can be run in interactive mode (no --path), or with a JSON file.
|
@@ -142,7 +140,9 @@ def add_image_hashes(path: Optional[str]) -> None:
|
|
142
140
|
img_hash_store.save()
|
143
141
|
click.echo(t("settings_add_hash_loaded", path=path))
|
144
142
|
except Exception as e:
|
145
|
-
raise click.ClickException(
|
143
|
+
raise click.ClickException(
|
144
|
+
t("settings_add_hash_load_fail", err=str(e))
|
145
|
+
) from e
|
146
146
|
else:
|
147
147
|
click.echo(t("settings_add_hash_prompt_tip"))
|
148
148
|
while True:
|
@@ -1,5 +1,4 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
-
# -*- coding: utf-8 -*-
|
3
2
|
"""
|
4
3
|
novel_downloader.config.adapter
|
5
4
|
-------------------------------
|
@@ -15,7 +14,9 @@ Supported mappings:
|
|
15
14
|
- sites[site] -> book_ids list
|
16
15
|
"""
|
17
16
|
|
18
|
-
from typing import Any
|
17
|
+
from typing import Any
|
18
|
+
|
19
|
+
from novel_downloader.utils.constants import SUPPORTED_SITES
|
19
20
|
|
20
21
|
from .models import (
|
21
22
|
DownloaderConfig,
|
@@ -31,7 +32,7 @@ class ConfigAdapter:
|
|
31
32
|
Adapter to map a raw config dict + site name into structured dataclass configs.
|
32
33
|
"""
|
33
34
|
|
34
|
-
def __init__(self, config:
|
35
|
+
def __init__(self, config: dict[str, Any], site: str):
|
35
36
|
"""
|
36
37
|
:param config: 完整加载的配置 dict
|
37
38
|
:param site: 当前站点名称 (e.g. "qidian")
|
@@ -40,33 +41,33 @@ class ConfigAdapter:
|
|
40
41
|
self._site = site
|
41
42
|
|
42
43
|
site_rules = load_site_rules() # -> Dict[str, SiteRules]
|
43
|
-
self._supported_sites = set(site_rules.keys())
|
44
|
+
self._supported_sites = set(site_rules.keys()) | SUPPORTED_SITES
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
@property
|
47
|
+
def site(self) -> str:
|
48
|
+
return self._site
|
49
|
+
|
50
|
+
@site.setter
|
51
|
+
def site(self, value: str) -> None:
|
52
|
+
self._site = value
|
50
53
|
|
51
|
-
def _get_site_cfg(self) ->
|
54
|
+
def _get_site_cfg(self, site: str | None = None) -> dict[str, Any]:
|
52
55
|
"""
|
53
|
-
|
56
|
+
获取指定站点的配置 (默认为当前适配站点)
|
54
57
|
|
55
|
-
1.
|
56
|
-
2.
|
58
|
+
1. 如果有 site-specific 配置, 优先返回它
|
59
|
+
2. 否则, 如果该站点在支持站点中, 尝试返回 'common' 配置
|
57
60
|
3. 否则返回空 dict
|
58
61
|
"""
|
62
|
+
site = site or self._site
|
59
63
|
sites_cfg = self._config.get("sites", {}) or {}
|
60
64
|
|
61
|
-
|
62
|
-
|
63
|
-
return sites_cfg[self._site] or {}
|
65
|
+
if site in sites_cfg:
|
66
|
+
return sites_cfg[site] or {}
|
64
67
|
|
65
|
-
|
66
|
-
if self._site in self._supported_sites:
|
68
|
+
if site in self._supported_sites:
|
67
69
|
return sites_cfg.get("common", {}) or {}
|
68
70
|
|
69
|
-
# 3. completely unsupported site
|
70
71
|
return {}
|
71
72
|
|
72
73
|
def get_requester_config(self) -> RequesterConfig:
|
@@ -77,10 +78,11 @@ class ConfigAdapter:
|
|
77
78
|
req = self._config.get("requests", {})
|
78
79
|
site_cfg = self._get_site_cfg()
|
79
80
|
return RequesterConfig(
|
80
|
-
wait_time=req.get("wait_time", 5),
|
81
81
|
retry_times=req.get("retry_times", 3),
|
82
|
-
|
83
|
-
timeout=req.get("timeout", 30),
|
82
|
+
backoff_factor=req.get("backoff_factor", 2.0),
|
83
|
+
timeout=req.get("timeout", 30.0),
|
84
|
+
max_connections=req.get("max_connections", 10),
|
85
|
+
max_rps=req.get("max_rps", None),
|
84
86
|
headless=req.get("headless", True),
|
85
87
|
user_data_folder=req.get("user_data_folder", "./user_data"),
|
86
88
|
profile_name=req.get("profile_name", "Profile_1"),
|
@@ -88,7 +90,6 @@ class ConfigAdapter:
|
|
88
90
|
disable_images=req.get("disable_images", True),
|
89
91
|
mute_audio=req.get("mute_audio", True),
|
90
92
|
mode=site_cfg.get("mode", "session"),
|
91
|
-
max_rps=site_cfg.get("max_rps", None),
|
92
93
|
)
|
93
94
|
|
94
95
|
def get_downloader_config(self) -> DownloaderConfig:
|
@@ -100,9 +101,9 @@ class ConfigAdapter:
|
|
100
101
|
debug = gen.get("debug", {})
|
101
102
|
site_cfg = self._get_site_cfg()
|
102
103
|
return DownloaderConfig(
|
103
|
-
request_interval=gen.get("request_interval", 5),
|
104
|
+
request_interval=gen.get("request_interval", 5.0),
|
104
105
|
raw_data_dir=gen.get("raw_data_dir", "./raw_data"),
|
105
|
-
cache_dir=gen.get("cache_dir", "./
|
106
|
+
cache_dir=gen.get("cache_dir", "./novel_cache"),
|
106
107
|
download_workers=gen.get("download_workers", 4),
|
107
108
|
parser_workers=gen.get("parser_workers", 4),
|
108
109
|
use_process_pool=gen.get("use_process_pool", True),
|
@@ -110,6 +111,8 @@ class ConfigAdapter:
|
|
110
111
|
login_required=site_cfg.get("login_required", False),
|
111
112
|
save_html=debug.get("save_html", False),
|
112
113
|
mode=site_cfg.get("mode", "session"),
|
114
|
+
storage_backend=gen.get("storage_backend", "json"),
|
115
|
+
storage_batch_size=gen.get("storage_batch_size", 1),
|
113
116
|
)
|
114
117
|
|
115
118
|
def get_parser_config(self) -> ParserConfig:
|
@@ -121,7 +124,7 @@ class ConfigAdapter:
|
|
121
124
|
font_ocr = gen.get("font_ocr", {})
|
122
125
|
site_cfg = self._get_site_cfg()
|
123
126
|
return ParserConfig(
|
124
|
-
cache_dir=gen.get("cache_dir", "./
|
127
|
+
cache_dir=gen.get("cache_dir", "./novel_cache"),
|
125
128
|
decode_font=font_ocr.get("decode_font", False),
|
126
129
|
use_freq=font_ocr.get("use_freq", False),
|
127
130
|
use_ocr=font_ocr.get("use_ocr", True),
|
@@ -149,6 +152,7 @@ class ConfigAdapter:
|
|
149
152
|
return SaverConfig(
|
150
153
|
raw_data_dir=gen.get("raw_data_dir", "./raw_data"),
|
151
154
|
output_dir=gen.get("output_dir", "./downloads"),
|
155
|
+
storage_backend=gen.get("storage_backend", "json"),
|
152
156
|
clean_text=out.get("clean_text", True),
|
153
157
|
make_txt=fmt.get("make_txt", True),
|
154
158
|
make_epub=fmt.get("make_epub", False),
|
@@ -158,9 +162,10 @@ class ConfigAdapter:
|
|
158
162
|
filename_template=naming.get("filename_template", "{title}_{author}"),
|
159
163
|
include_cover=epub_opts.get("include_cover", True),
|
160
164
|
include_toc=epub_opts.get("include_toc", False),
|
165
|
+
include_picture=epub_opts.get("include_picture", False),
|
161
166
|
)
|
162
167
|
|
163
|
-
def get_book_ids(self) ->
|
168
|
+
def get_book_ids(self) -> list[str]:
|
164
169
|
"""
|
165
170
|
从 config["sites"][site]["book_ids"] 中提取目标书籍列表
|
166
171
|
"""
|