novel-downloader 1.2.2__py3-none-any.whl → 1.3.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 -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 +306 -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.0.dist-info}/METADATA +14 -17
- novel_downloader-1.3.0.dist-info/RECORD +127 -0
- {novel_downloader-1.2.2.dist-info → novel_downloader-1.3.0.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.0.dist-info}/entry_points.txt +0 -0
- {novel_downloader-1.2.2.dist-info → novel_downloader-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {novel_downloader-1.2.2.dist-info → novel_downloader-1.3.0.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,7 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
-
# -*- coding: utf-8 -*-
|
3
2
|
"""
|
4
|
-
novel_downloader.core.savers.
|
5
|
-
|
3
|
+
novel_downloader.core.savers.common.txt
|
4
|
+
---------------------------------------
|
6
5
|
|
7
6
|
Contains the logic for exporting novel content as a single `.txt` file.
|
8
7
|
|
@@ -14,39 +13,17 @@ It is intended to be used by `CommonSaver` as part of the save/export process.
|
|
14
13
|
from __future__ import annotations
|
15
14
|
|
16
15
|
import json
|
17
|
-
import
|
18
|
-
from pathlib import Path
|
19
|
-
from typing import TYPE_CHECKING, List, Optional
|
16
|
+
from typing import TYPE_CHECKING
|
20
17
|
|
21
18
|
from novel_downloader.utils.file_utils import save_as_txt
|
22
|
-
from novel_downloader.utils.text_utils import
|
19
|
+
from novel_downloader.utils.text_utils import (
|
20
|
+
clean_chapter_title,
|
21
|
+
format_chapter,
|
22
|
+
)
|
23
23
|
|
24
24
|
if TYPE_CHECKING:
|
25
25
|
from .main_saver import CommonSaver
|
26
26
|
|
27
|
-
logger = logging.getLogger(__name__)
|
28
|
-
|
29
|
-
CHAPTER_FOLDERS: List[str] = [
|
30
|
-
"chapters",
|
31
|
-
"encrypted_chapters",
|
32
|
-
]
|
33
|
-
|
34
|
-
|
35
|
-
def _find_chapter_file(
|
36
|
-
raw_base: Path,
|
37
|
-
chapter_id: str,
|
38
|
-
) -> Optional[Path]:
|
39
|
-
"""
|
40
|
-
Search for `<chapter_id>.json` under each folder in CHAPTER_FOLDERS
|
41
|
-
inside raw_data_dir/site/book_id. Return the first existing Path,
|
42
|
-
or None if not found.
|
43
|
-
"""
|
44
|
-
for folder in CHAPTER_FOLDERS:
|
45
|
-
candidate = raw_base / folder / f"{chapter_id}.json"
|
46
|
-
if candidate.exists():
|
47
|
-
return candidate
|
48
|
-
return None
|
49
|
-
|
50
27
|
|
51
28
|
def common_save_as_txt(
|
52
29
|
saver: CommonSaver,
|
@@ -80,11 +57,11 @@ def common_save_as_txt(
|
|
80
57
|
info_text = info_path.read_text(encoding="utf-8")
|
81
58
|
book_info = json.loads(info_text)
|
82
59
|
except Exception as e:
|
83
|
-
logger.error("%s Failed to load %s: %s", TAG, info_path, e)
|
60
|
+
saver.logger.error("%s Failed to load %s: %s", TAG, info_path, e)
|
84
61
|
return
|
85
62
|
|
86
63
|
# --- Compile chapters ---
|
87
|
-
parts:
|
64
|
+
parts: list[str] = []
|
88
65
|
latest_chapter: str = ""
|
89
66
|
volumes = book_info.get("volumes", [])
|
90
67
|
|
@@ -94,18 +71,17 @@ def common_save_as_txt(
|
|
94
71
|
if vol_name:
|
95
72
|
volume_header = f"\n\n{'=' * 6} {vol_name} {'=' * 6}\n\n"
|
96
73
|
parts.append(volume_header)
|
97
|
-
logger.info("%s Processing volume: %s", TAG, vol_name)
|
74
|
+
saver.logger.info("%s Processing volume: %s", TAG, vol_name)
|
98
75
|
for chap in vol.get("chapters", []):
|
99
76
|
chap_id = chap.get("chapterId")
|
100
77
|
chap_title = chap.get("title", "")
|
101
78
|
if not chap_id:
|
102
|
-
logger.warning("%s Missing chapterId, skipping: %s", TAG, chap)
|
79
|
+
saver.logger.warning("%s Missing chapterId, skipping: %s", TAG, chap)
|
103
80
|
continue
|
104
81
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
logger.info(
|
82
|
+
chapter_data = saver._get_chapter(book_id, chap_id)
|
83
|
+
if not chapter_data:
|
84
|
+
saver.logger.info(
|
109
85
|
"%s Missing chapter file in: %s (%s), skipping.",
|
110
86
|
TAG,
|
111
87
|
chap_title,
|
@@ -113,12 +89,6 @@ def common_save_as_txt(
|
|
113
89
|
)
|
114
90
|
continue
|
115
91
|
|
116
|
-
try:
|
117
|
-
chapter_data = json.loads(json_path.read_text(encoding="utf-8"))
|
118
|
-
except Exception as e:
|
119
|
-
logger.error("%s Error reading %s: %s", TAG, json_path, e)
|
120
|
-
continue
|
121
|
-
|
122
92
|
# Extract structured fields
|
123
93
|
title = chapter_data.get("title", chap_title).strip()
|
124
94
|
content = chapter_data.get("content", "").strip()
|
@@ -170,7 +140,7 @@ def common_save_as_txt(
|
|
170
140
|
# --- Save final text ---
|
171
141
|
try:
|
172
142
|
save_as_txt(content=final_text, filepath=out_path)
|
173
|
-
logger.info("%s Novel saved to: %s", TAG, out_path)
|
143
|
+
saver.logger.info("%s Novel saved to: %s", TAG, out_path)
|
174
144
|
except Exception as e:
|
175
|
-
logger.error("%s Failed to save file: %s", TAG, e)
|
145
|
+
saver.logger.error("%s Failed to save file: %s", TAG, e)
|
176
146
|
return
|
@@ -1,5 +1,4 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
-
# -*- coding: utf-8 -*-
|
3
2
|
"""
|
4
3
|
novel_downloader.core.savers.epub_utils.css_builder
|
5
4
|
|
@@ -9,7 +8,7 @@ returning a list ready to be added to the EPUB.
|
|
9
8
|
|
10
9
|
import logging
|
11
10
|
from importlib.abc import Traversable
|
12
|
-
from typing import
|
11
|
+
from typing import TypedDict
|
13
12
|
|
14
13
|
from ebooklib import epub
|
15
14
|
|
@@ -21,16 +20,23 @@ from novel_downloader.utils.constants import (
|
|
21
20
|
logger = logging.getLogger(__name__)
|
22
21
|
|
23
22
|
|
23
|
+
class CssConfig(TypedDict):
|
24
|
+
include: bool
|
25
|
+
path: Traversable
|
26
|
+
uid: str
|
27
|
+
file_name: str
|
28
|
+
|
29
|
+
|
24
30
|
def create_css_items(
|
25
31
|
include_main: bool = True,
|
26
32
|
include_volume: bool = True,
|
27
|
-
) ->
|
33
|
+
) -> list[epub.EpubItem]:
|
28
34
|
"""
|
29
35
|
:param include_main: Whether to load the main stylesheet.
|
30
36
|
:param include_volume: Whether to load the “volume intro” stylesheet.
|
31
37
|
:returns: A list of epub.EpubItem ready to add to the book.
|
32
38
|
"""
|
33
|
-
css_config:
|
39
|
+
css_config: list[CssConfig] = [
|
34
40
|
{
|
35
41
|
"include": include_main,
|
36
42
|
"path": CSS_MAIN_PATH,
|
@@ -44,20 +50,20 @@ def create_css_items(
|
|
44
50
|
"file_name": "Styles/volume-intro.css",
|
45
51
|
},
|
46
52
|
]
|
47
|
-
css_items:
|
53
|
+
css_items: list[epub.EpubItem] = []
|
48
54
|
|
49
55
|
for css in css_config:
|
50
56
|
if css["include"]:
|
51
57
|
path = css["path"]
|
52
|
-
assert isinstance(path, Traversable)
|
53
58
|
try:
|
54
59
|
content: str = path.read_text(encoding="utf-8")
|
60
|
+
content_bytes: bytes = content.encode("utf-8")
|
55
61
|
css_items.append(
|
56
62
|
epub.EpubItem(
|
57
63
|
uid=css["uid"],
|
58
64
|
file_name=css["file_name"],
|
59
65
|
media_type="text/css",
|
60
|
-
content=
|
66
|
+
content=content_bytes,
|
61
67
|
)
|
62
68
|
)
|
63
69
|
except FileNotFoundError:
|
@@ -1,5 +1,4 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
-
# -*- coding: utf-8 -*-
|
3
2
|
"""
|
4
3
|
novel_downloader.core.savers.epub_utils.initializer
|
5
4
|
|
@@ -10,7 +9,7 @@ adds a cover, and prepares the initial spine and TOC entries.
|
|
10
9
|
|
11
10
|
import logging
|
12
11
|
from pathlib import Path
|
13
|
-
from typing import Any
|
12
|
+
from typing import Any
|
14
13
|
|
15
14
|
from ebooklib import epub
|
16
15
|
|
@@ -24,12 +23,12 @@ logger = logging.getLogger(__name__)
|
|
24
23
|
|
25
24
|
|
26
25
|
def init_epub(
|
27
|
-
book_info:
|
26
|
+
book_info: dict[str, Any],
|
28
27
|
book_id: str,
|
29
28
|
intro_html: str,
|
30
|
-
book_cover_path:
|
29
|
+
book_cover_path: Path | None = None,
|
31
30
|
include_toc: bool = False,
|
32
|
-
) ->
|
31
|
+
) -> tuple[epub.EpubBook, list[Any], list[Any]]:
|
33
32
|
"""
|
34
33
|
Initialize an EPUB book with metadata, optional cover, and intro page.
|
35
34
|
|
@@ -1,5 +1,4 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
-
# -*- coding: utf-8 -*-
|
3
2
|
"""
|
4
3
|
novel_downloader.core.savers.epub_utils.text_to_html
|
5
4
|
|
@@ -9,7 +8,7 @@ with automatic word correction and optional image/tag support.
|
|
9
8
|
|
10
9
|
import json
|
11
10
|
import logging
|
12
|
-
from typing import Any
|
11
|
+
from typing import Any
|
13
12
|
|
14
13
|
from novel_downloader.utils.constants import REPLACE_WORD_MAP_PATH
|
15
14
|
from novel_downloader.utils.text_utils import diff_inline_display
|
@@ -88,7 +87,7 @@ def chapter_txt_to_html(
|
|
88
87
|
return "\n".join(html_parts)
|
89
88
|
|
90
89
|
|
91
|
-
def generate_book_intro_html(book_info:
|
90
|
+
def generate_book_intro_html(book_info: dict[str, Any]) -> str:
|
92
91
|
"""
|
93
92
|
Generate HTML string for a book's information and summary.
|
94
93
|
|
@@ -1,5 +1,4 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
-
# -*- coding: utf-8 -*-
|
3
2
|
"""
|
4
3
|
novel_downloader.core.savers.epub_utils.volume_intro
|
5
4
|
|
@@ -7,12 +6,11 @@ Responsible for generating HTML code for volume introduction pages,
|
|
7
6
|
including two style variants and a unified entry point.
|
8
7
|
"""
|
9
8
|
|
10
|
-
from typing import Tuple
|
11
9
|
|
12
10
|
from novel_downloader.utils.constants import EPUB_IMAGE_FOLDER
|
13
11
|
|
14
12
|
|
15
|
-
def split_volume_title(volume_title: str) ->
|
13
|
+
def split_volume_title(volume_title: str) -> tuple[str, str]:
|
16
14
|
"""
|
17
15
|
Split volume title into two parts for better display.
|
18
16
|
|
@@ -1,8 +1,7 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
-
# -*- coding: utf-8 -*-
|
3
2
|
"""
|
4
|
-
novel_downloader.core.savers.
|
5
|
-
|
3
|
+
novel_downloader.core.savers.qidian
|
4
|
+
-----------------------------------
|
6
5
|
|
7
6
|
This module provides the `QidianSaver` class for handling the saving process
|
8
7
|
of novels sourced from Qidian (起点中文网). It implements the platform-specific
|
@@ -11,12 +10,19 @@ logic required to structure and export novel content into desired formats.
|
|
11
10
|
|
12
11
|
from novel_downloader.config.models import SaverConfig
|
13
12
|
|
14
|
-
from .
|
13
|
+
from .common import CommonSaver
|
15
14
|
|
16
15
|
|
17
16
|
class QidianSaver(CommonSaver):
|
18
|
-
def __init__(
|
19
|
-
|
17
|
+
def __init__(
|
18
|
+
self,
|
19
|
+
config: SaverConfig,
|
20
|
+
):
|
21
|
+
super().__init__(
|
22
|
+
config,
|
23
|
+
site="qidian",
|
24
|
+
chap_folders=["chapters", "encrypted_chapters"],
|
25
|
+
)
|
20
26
|
|
21
27
|
|
22
28
|
__all__ = ["QidianSaver"]
|
novel_downloader/locales/en.json
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
"help_config": "Path to config file",
|
4
4
|
"help_download": "Download novels",
|
5
5
|
"help_clean": "Clean cache and configuration files",
|
6
|
-
"main_no_command": "No command provided, entering interactive mode
|
6
|
+
"main_no_command": "No command provided, entering interactive mode...",
|
7
7
|
|
8
8
|
"settings_help": "Configure downloader settings.",
|
9
9
|
"settings_set_lang_help": "Switch language between Chinese and English.",
|
@@ -49,8 +49,8 @@
|
|
49
49
|
"interactive_option_preview": "Preview chapters",
|
50
50
|
"interactive_option_exit": "Exit",
|
51
51
|
"interactive_prompt_choice": "Enter your choice",
|
52
|
-
"interactive_browse_start": "Starting interactive browser
|
53
|
-
"interactive_preview_start": "Previewing chapters
|
52
|
+
"interactive_browse_start": "Starting interactive browser...",
|
53
|
+
"interactive_preview_start": "Previewing chapters...",
|
54
54
|
"interactive_exit": "Exiting.",
|
55
55
|
|
56
56
|
"download_help": "Download full novels by book IDs.",
|
@@ -69,11 +69,15 @@
|
|
69
69
|
"login_prompt_intro": "Manual login is required. Please switch to the browser and log in.",
|
70
70
|
"login_prompt_press_enter": "Attempt {attempt}/{max_retries}: Press Enter after completing login in the browser...",
|
71
71
|
|
72
|
+
"session_login_prompt_intro": "Failed to restore login from saved cookies. Please log in via browser, then paste the cookie string below.",
|
73
|
+
"session_login_prompt_paste_cookie": "Attempt {attempt}/{max_retries}: Paste your browser cookie string and press Enter:",
|
74
|
+
"session_login_prompt_invalid_cookie": "Invalid cookie. Please copy and paste again.",
|
75
|
+
|
72
76
|
"clean_logs": "Clean log directory",
|
73
77
|
"clean_cache": "Clean scripts and browser cache",
|
74
78
|
"clean_state": "Clean state files (state.json)",
|
75
79
|
"clean_data": "Clean data files (e.g. image hashes, browser data)",
|
76
|
-
"clean_config": "Clean config files (e.g. settings.
|
80
|
+
"clean_config": "Clean config files (e.g. settings.toml, site rules)",
|
77
81
|
"clean_models": "Clean downloaded model cache",
|
78
82
|
"clean_hf_cache": "Clear Hugging Face model cache",
|
79
83
|
"clean_hf_cache_done": "Hugging Face cache cleared",
|
novel_downloader/locales/zh.json
CHANGED
@@ -69,11 +69,15 @@
|
|
69
69
|
"login_prompt_intro": "需要手动登录, 请切换到浏览器窗口完成登录",
|
70
70
|
"login_prompt_press_enter": "第 {attempt}/{max_retries} 次尝试: 请在浏览器中完成登录后按回车键...",
|
71
71
|
|
72
|
+
"session_login_prompt_intro": "尝试使用历史 Cookie 恢复登录失败, 请在浏览器登录后从开发者工具复制 Cookie 粘贴至下方",
|
73
|
+
"session_login_prompt_paste_cookie": "第 {attempt}/{max_retries} 次尝试, 请粘贴 Cookie 字符串并回车:",
|
74
|
+
"session_login_prompt_invalid_cookie": "Cookie 格式不正确, 请重新复制粘贴",
|
75
|
+
|
72
76
|
"clean_logs": "清理日志目录",
|
73
77
|
"clean_cache": "清理脚本和浏览器缓存",
|
74
78
|
"clean_state": "清理状态文件 (state.json)",
|
75
79
|
"clean_data": "清理数据文件 (如 image hashes, 浏览器数据)",
|
76
|
-
"clean_config": "清理配置文件 (如 settings.
|
80
|
+
"clean_config": "清理配置文件 (如 settings.toml 和规则)",
|
77
81
|
"clean_models": "清理模型缓存目录",
|
78
82
|
"clean_hf_cache": "清理 Hugging Face 下载缓存",
|
79
83
|
"clean_hf_cache_done": "已清除 Hugging Face 本地缓存",
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# 网络请求层设置
|
2
|
+
[requests]
|
3
|
+
retry_times = 3 # 请求失败重试次数
|
4
|
+
backoff_factor = 2.0
|
5
|
+
timeout = 30.0 # 页面加载超时时间 (秒)
|
6
|
+
max_connections = 10 # 并发连接的最大数 (async)
|
7
|
+
# max_rps = # 最大请求速率 (requests per second), 为空则不限制
|
8
|
+
|
9
|
+
# DrissionPage 专用设置
|
10
|
+
headless = false # 是否以无头模式启动浏览器
|
11
|
+
user_data_folder = "" # 浏览器用户数据目录: 为空则使用默认目录
|
12
|
+
profile_name = "" # 使用的用户配置名称: 为空则使用默认配置
|
13
|
+
auto_close = true # 页面抓取完是否自动关闭浏览器
|
14
|
+
disable_images = false # 是否禁用图片加载 (加速)
|
15
|
+
mute_audio = true # 是否静音
|
16
|
+
|
17
|
+
# 全局通用设置
|
18
|
+
[general]
|
19
|
+
request_interval = 5.0 # 同一本书各章节请求间隔 (秒)
|
20
|
+
raw_data_dir = "./raw_data" # 原始章节 JSON/DB 存放目录
|
21
|
+
output_dir = "./downloads" # 最终输出文件存放目录
|
22
|
+
cache_dir = "./novel_cache" # 本地缓存目录 (字体 / 图片等)
|
23
|
+
download_workers = 4 # 并发下载线程数 (async)
|
24
|
+
parser_workers = 4 # 并发解析线程数
|
25
|
+
use_process_pool = false # 是否使用多进程池来处理任务
|
26
|
+
skip_existing = true # 是否跳过已存在章节
|
27
|
+
storage_backend = "sqlite" # 章节储存方法: json / sqlite
|
28
|
+
storage_batch_size = 30 # SQLite 批量提交的章节数量
|
29
|
+
|
30
|
+
[general.debug]
|
31
|
+
save_html = false # 是否将抓取到的原始 HTML 保留到磁盘
|
32
|
+
log_level = "INFO" # 日志级别: DEBUG, INFO, WARNING, ERROR
|
33
|
+
|
34
|
+
[general.font_ocr]
|
35
|
+
decode_font = false # 是否尝试本地解码混淆字体
|
36
|
+
use_freq = false # 是否使用频率分析
|
37
|
+
ocr_version = "v2.0" # "v1.0" / "v2.0"
|
38
|
+
use_ocr = true # 是否使用 OCR 辅助识别文本
|
39
|
+
use_vec = false # 是否使用 Vector 辅助识别文本
|
40
|
+
save_font_debug = false # 是否保存字体解码调试数据
|
41
|
+
batch_size = 32
|
42
|
+
gpu_mem = 500 # GPU 显存限制 (MB)
|
43
|
+
# gpu_id = # 使用哪个 GPU
|
44
|
+
ocr_weight = 0.5
|
45
|
+
vec_weight = 0.5
|
46
|
+
|
47
|
+
# 各站点的特定配置
|
48
|
+
[sites.qidian]
|
49
|
+
# 小说 ID 列表
|
50
|
+
# 例如: 访问 https://www.qidian.com/book/1010868264/
|
51
|
+
# 该小说的 ID 就是 1010868264
|
52
|
+
book_ids = [
|
53
|
+
"0000000000",
|
54
|
+
"0000000000"
|
55
|
+
]
|
56
|
+
mode = "browser" # browser / session
|
57
|
+
login_required = true # 是否需要登录才能访问
|
58
|
+
|
59
|
+
[sites.biquge]
|
60
|
+
book_ids = [
|
61
|
+
"0000000000",
|
62
|
+
"0000000000"
|
63
|
+
]
|
64
|
+
mode = "session" # async / session
|
65
|
+
login_required = false # 是否需要登录才能访问
|
66
|
+
|
67
|
+
[sites.common]
|
68
|
+
mode = "session" # async / session
|
69
|
+
login_required = false # 是否需要登录才能访问
|
70
|
+
|
71
|
+
# 输出文件格式及相关选项
|
72
|
+
[output]
|
73
|
+
clean_text = true # 是否对章节文本做清理
|
74
|
+
|
75
|
+
[output.formats]
|
76
|
+
make_txt = true # 是否生成完整 TXT 文件
|
77
|
+
make_epub = false # 是否生成 EPUB
|
78
|
+
make_md = false # 是否生成 Markdown (未实现)
|
79
|
+
make_pdf = false # 可能支持 PDF 输出 (未实现)
|
80
|
+
|
81
|
+
[output.naming]
|
82
|
+
append_timestamp = false # 在文件名中追加时间戳
|
83
|
+
filename_template = "{title}_{author}" # 文件命名规则
|
84
|
+
|
85
|
+
[output.epub]
|
86
|
+
include_cover = true # 是否在 EPUB 中包含封面
|
87
|
+
include_toc = false # 是否自动生成目录
|
88
|
+
include_picture = false # 是否下载章节图片 (体积较大)
|
novel_downloader/utils/cache.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
-
# -*- coding: utf-8 -*-
|
3
2
|
"""
|
4
3
|
novel_downloader.utils.cache
|
5
4
|
----------------------------
|
@@ -8,8 +7,9 @@ Provides decorators for caching function results,
|
|
8
7
|
specifically optimized for configuration loading functions.
|
9
8
|
"""
|
10
9
|
|
10
|
+
from collections.abc import Callable
|
11
11
|
from functools import lru_cache, wraps
|
12
|
-
from typing import Any,
|
12
|
+
from typing import Any, TypeVar, cast
|
13
13
|
|
14
14
|
T = TypeVar("T", bound=Callable[..., Any])
|
15
15
|
|