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.
Files changed (128) hide show
  1. novel_downloader/__init__.py +1 -2
  2. novel_downloader/cli/__init__.py +0 -1
  3. novel_downloader/cli/clean.py +2 -10
  4. novel_downloader/cli/download.py +16 -22
  5. novel_downloader/cli/interactive.py +0 -1
  6. novel_downloader/cli/main.py +1 -3
  7. novel_downloader/cli/settings.py +8 -8
  8. novel_downloader/config/__init__.py +0 -1
  9. novel_downloader/config/adapter.py +32 -27
  10. novel_downloader/config/loader.py +116 -108
  11. novel_downloader/config/models.py +35 -29
  12. novel_downloader/config/site_rules.py +2 -4
  13. novel_downloader/core/__init__.py +0 -1
  14. novel_downloader/core/downloaders/__init__.py +4 -4
  15. novel_downloader/core/downloaders/base/__init__.py +14 -0
  16. novel_downloader/core/downloaders/{base_async_downloader.py → base/base_async.py} +49 -53
  17. novel_downloader/core/downloaders/{base_downloader.py → base/base_sync.py} +64 -43
  18. novel_downloader/core/downloaders/biquge/__init__.py +12 -0
  19. novel_downloader/core/downloaders/biquge/biquge_sync.py +25 -0
  20. novel_downloader/core/downloaders/common/__init__.py +14 -0
  21. novel_downloader/core/downloaders/{common_asynb_downloader.py → common/common_async.py} +42 -33
  22. novel_downloader/core/downloaders/{common_downloader.py → common/common_sync.py} +33 -21
  23. novel_downloader/core/downloaders/qidian/__init__.py +10 -0
  24. novel_downloader/core/downloaders/{qidian_downloader.py → qidian/qidian_sync.py} +79 -62
  25. novel_downloader/core/factory/__init__.py +4 -5
  26. novel_downloader/core/factory/{downloader_factory.py → downloader.py} +25 -26
  27. novel_downloader/core/factory/{parser_factory.py → parser.py} +12 -14
  28. novel_downloader/core/factory/{requester_factory.py → requester.py} +29 -16
  29. novel_downloader/core/factory/{saver_factory.py → saver.py} +4 -9
  30. novel_downloader/core/interfaces/__init__.py +8 -9
  31. novel_downloader/core/interfaces/{async_downloader_protocol.py → async_downloader.py} +4 -5
  32. novel_downloader/core/interfaces/{async_requester_protocol.py → async_requester.py} +23 -12
  33. novel_downloader/core/interfaces/{parser_protocol.py → parser.py} +11 -6
  34. novel_downloader/core/interfaces/{saver_protocol.py → saver.py} +2 -3
  35. novel_downloader/core/interfaces/{downloader_protocol.py → sync_downloader.py} +6 -7
  36. novel_downloader/core/interfaces/{requester_protocol.py → sync_requester.py} +31 -17
  37. novel_downloader/core/parsers/__init__.py +5 -4
  38. novel_downloader/core/parsers/{base_parser.py → base.py} +18 -9
  39. novel_downloader/core/parsers/biquge/__init__.py +10 -0
  40. novel_downloader/core/parsers/biquge/main_parser.py +126 -0
  41. novel_downloader/core/parsers/{common_parser → common}/__init__.py +2 -3
  42. novel_downloader/core/parsers/{common_parser → common}/helper.py +13 -13
  43. novel_downloader/core/parsers/{common_parser → common}/main_parser.py +15 -9
  44. novel_downloader/core/parsers/{qidian_parser → qidian}/__init__.py +2 -3
  45. novel_downloader/core/parsers/{qidian_parser → qidian}/browser/__init__.py +2 -3
  46. novel_downloader/core/parsers/{qidian_parser → qidian}/browser/chapter_encrypted.py +40 -48
  47. novel_downloader/core/parsers/{qidian_parser → qidian}/browser/chapter_normal.py +17 -21
  48. novel_downloader/core/parsers/{qidian_parser → qidian}/browser/chapter_router.py +10 -9
  49. novel_downloader/core/parsers/{qidian_parser → qidian}/browser/main_parser.py +14 -10
  50. novel_downloader/core/parsers/{qidian_parser → qidian}/session/__init__.py +2 -3
  51. novel_downloader/core/parsers/{qidian_parser → qidian}/session/chapter_encrypted.py +36 -44
  52. novel_downloader/core/parsers/{qidian_parser → qidian}/session/chapter_normal.py +19 -23
  53. novel_downloader/core/parsers/{qidian_parser → qidian}/session/chapter_router.py +10 -9
  54. novel_downloader/core/parsers/{qidian_parser → qidian}/session/main_parser.py +14 -10
  55. novel_downloader/core/parsers/{qidian_parser → qidian}/session/node_decryptor.py +7 -10
  56. novel_downloader/core/parsers/{qidian_parser → qidian}/shared/__init__.py +2 -3
  57. novel_downloader/core/parsers/{qidian_parser → qidian}/shared/book_info_parser.py +5 -6
  58. novel_downloader/core/parsers/{qidian_parser → qidian}/shared/helpers.py +7 -8
  59. novel_downloader/core/requesters/__init__.py +9 -5
  60. novel_downloader/core/requesters/base/__init__.py +16 -0
  61. novel_downloader/core/requesters/{base_async_session.py → base/async_session.py} +177 -73
  62. novel_downloader/core/requesters/base/browser.py +340 -0
  63. novel_downloader/core/requesters/base/session.py +364 -0
  64. novel_downloader/core/requesters/biquge/__init__.py +12 -0
  65. novel_downloader/core/requesters/biquge/session.py +90 -0
  66. novel_downloader/core/requesters/{common_requester → common}/__init__.py +4 -5
  67. novel_downloader/core/requesters/common/async_session.py +96 -0
  68. novel_downloader/core/requesters/common/session.py +113 -0
  69. novel_downloader/core/requesters/qidian/__init__.py +21 -0
  70. novel_downloader/core/requesters/qidian/broswer.py +307 -0
  71. novel_downloader/core/requesters/qidian/session.py +287 -0
  72. novel_downloader/core/savers/__init__.py +5 -3
  73. novel_downloader/core/savers/{base_saver.py → base.py} +12 -13
  74. novel_downloader/core/savers/biquge.py +25 -0
  75. novel_downloader/core/savers/{common_saver → common}/__init__.py +2 -3
  76. novel_downloader/core/savers/{common_saver/common_epub.py → common/epub.py} +23 -51
  77. novel_downloader/core/savers/{common_saver → common}/main_saver.py +43 -9
  78. novel_downloader/core/savers/{common_saver/common_txt.py → common/txt.py} +16 -46
  79. novel_downloader/core/savers/epub_utils/__init__.py +0 -1
  80. novel_downloader/core/savers/epub_utils/css_builder.py +13 -7
  81. novel_downloader/core/savers/epub_utils/initializer.py +4 -5
  82. novel_downloader/core/savers/epub_utils/text_to_html.py +2 -3
  83. novel_downloader/core/savers/epub_utils/volume_intro.py +1 -3
  84. novel_downloader/core/savers/{qidian_saver.py → qidian.py} +12 -6
  85. novel_downloader/locales/en.json +8 -4
  86. novel_downloader/locales/zh.json +5 -1
  87. novel_downloader/resources/config/settings.toml +88 -0
  88. novel_downloader/utils/cache.py +2 -2
  89. novel_downloader/utils/chapter_storage.py +340 -0
  90. novel_downloader/utils/constants.py +6 -4
  91. novel_downloader/utils/crypto_utils.py +3 -3
  92. novel_downloader/utils/file_utils/__init__.py +0 -1
  93. novel_downloader/utils/file_utils/io.py +12 -17
  94. novel_downloader/utils/file_utils/normalize.py +1 -3
  95. novel_downloader/utils/file_utils/sanitize.py +2 -9
  96. novel_downloader/utils/fontocr/__init__.py +0 -1
  97. novel_downloader/utils/fontocr/ocr_v1.py +19 -22
  98. novel_downloader/utils/fontocr/ocr_v2.py +147 -60
  99. novel_downloader/utils/hash_store.py +19 -20
  100. novel_downloader/utils/hash_utils.py +0 -1
  101. novel_downloader/utils/i18n.py +3 -4
  102. novel_downloader/utils/logger.py +5 -6
  103. novel_downloader/utils/model_loader.py +5 -8
  104. novel_downloader/utils/network.py +9 -10
  105. novel_downloader/utils/state.py +6 -7
  106. novel_downloader/utils/text_utils/__init__.py +0 -1
  107. novel_downloader/utils/text_utils/chapter_formatting.py +2 -7
  108. novel_downloader/utils/text_utils/diff_display.py +0 -1
  109. novel_downloader/utils/text_utils/font_mapping.py +1 -4
  110. novel_downloader/utils/text_utils/text_cleaning.py +0 -1
  111. novel_downloader/utils/time_utils/__init__.py +0 -1
  112. novel_downloader/utils/time_utils/datetime_utils.py +8 -10
  113. novel_downloader/utils/time_utils/sleep_utils.py +1 -3
  114. {novel_downloader-1.2.2.dist-info → novel_downloader-1.3.1.dist-info}/METADATA +14 -17
  115. novel_downloader-1.3.1.dist-info/RECORD +127 -0
  116. {novel_downloader-1.2.2.dist-info → novel_downloader-1.3.1.dist-info}/WHEEL +1 -1
  117. novel_downloader/core/requesters/base_browser.py +0 -214
  118. novel_downloader/core/requesters/base_session.py +0 -246
  119. novel_downloader/core/requesters/common_requester/common_async_session.py +0 -98
  120. novel_downloader/core/requesters/common_requester/common_session.py +0 -126
  121. novel_downloader/core/requesters/qidian_requester/__init__.py +0 -22
  122. novel_downloader/core/requesters/qidian_requester/qidian_broswer.py +0 -396
  123. novel_downloader/core/requesters/qidian_requester/qidian_session.py +0 -202
  124. novel_downloader/resources/config/settings.yaml +0 -76
  125. novel_downloader-1.2.2.dist-info/RECORD +0 -115
  126. {novel_downloader-1.2.2.dist-info → novel_downloader-1.3.1.dist-info}/entry_points.txt +0 -0
  127. {novel_downloader-1.2.2.dist-info → novel_downloader-1.3.1.dist-info}/licenses/LICENSE +0 -0
  128. {novel_downloader-1.2.2.dist-info → novel_downloader-1.3.1.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.common_saver.qidian_txt
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 logging
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 clean_chapter_title, format_chapter
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: List[str] = []
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
- # Find the JSON file in one of the known subfolders
106
- json_path = _find_chapter_file(raw_base, chap_id)
107
- if json_path is None:
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
5
4
  ---------------------------------------
@@ -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 Dict, List, Union
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
- ) -> List[epub.EpubItem]:
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: List[Dict[str, Union[str, bool, Traversable]]] = [
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: List[epub.EpubItem] = []
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=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, Dict, List, Optional, Tuple
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: Dict[str, Any],
26
+ book_info: dict[str, Any],
28
27
  book_id: str,
29
28
  intro_html: str,
30
- book_cover_path: Optional[Path] = None,
29
+ book_cover_path: Path | None = None,
31
30
  include_toc: bool = False,
32
- ) -> Tuple[epub.EpubBook, List[Any], List[Any]]:
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, Dict
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: Dict[str, Any]) -> str:
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) -> Tuple[str, 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.qidian_saver
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 .common_saver import CommonSaver
13
+ from .common import CommonSaver
15
14
 
16
15
 
17
16
  class QidianSaver(CommonSaver):
18
- def __init__(self, config: SaverConfig):
19
- super().__init__(config, site="qidian")
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"]
@@ -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.yaml, site rules)",
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",
@@ -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.yaml 和规则)",
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 # 是否下载章节图片 (体积较大)
@@ -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, Callable, TypeVar, cast
12
+ from typing import Any, TypeVar, cast
13
13
 
14
14
  T = TypeVar("T", bound=Callable[..., Any])
15
15