novel-downloader 2.0.0__py3-none-any.whl → 2.0.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 (57) hide show
  1. novel_downloader/__init__.py +1 -1
  2. novel_downloader/cli/download.py +3 -3
  3. novel_downloader/cli/export.py +1 -1
  4. novel_downloader/cli/ui.py +7 -7
  5. novel_downloader/config/adapter.py +191 -154
  6. novel_downloader/core/__init__.py +5 -6
  7. novel_downloader/core/exporters/common/txt.py +9 -9
  8. novel_downloader/core/exporters/linovelib/txt.py +9 -9
  9. novel_downloader/core/fetchers/qidian.py +20 -35
  10. novel_downloader/core/interfaces/fetcher.py +2 -2
  11. novel_downloader/core/interfaces/parser.py +2 -2
  12. novel_downloader/core/parsers/base.py +1 -0
  13. novel_downloader/core/parsers/eightnovel.py +2 -2
  14. novel_downloader/core/parsers/esjzone.py +3 -3
  15. novel_downloader/core/parsers/qidian/main_parser.py +747 -12
  16. novel_downloader/core/parsers/qidian/utils/__init__.py +2 -21
  17. novel_downloader/core/parsers/qidian/utils/node_decryptor.py +4 -4
  18. novel_downloader/core/parsers/xiguashuwu.py +6 -12
  19. novel_downloader/locales/en.json +3 -3
  20. novel_downloader/locales/zh.json +3 -3
  21. novel_downloader/utils/__init__.py +0 -2
  22. novel_downloader/utils/chapter_storage.py +2 -3
  23. novel_downloader/utils/constants.py +1 -3
  24. novel_downloader/utils/cookies.py +32 -17
  25. novel_downloader/utils/crypto_utils/__init__.py +0 -6
  26. novel_downloader/utils/crypto_utils/rc4.py +40 -50
  27. novel_downloader/utils/epub/__init__.py +2 -3
  28. novel_downloader/utils/epub/builder.py +6 -6
  29. novel_downloader/utils/epub/constants.py +5 -5
  30. novel_downloader/utils/epub/documents.py +7 -7
  31. novel_downloader/utils/epub/models.py +8 -8
  32. novel_downloader/utils/epub/utils.py +10 -10
  33. novel_downloader/utils/file_utils/io.py +48 -73
  34. novel_downloader/utils/file_utils/normalize.py +1 -7
  35. novel_downloader/utils/file_utils/sanitize.py +4 -11
  36. novel_downloader/utils/fontocr/__init__.py +13 -0
  37. novel_downloader/utils/{fontocr.py → fontocr/core.py} +70 -61
  38. novel_downloader/utils/fontocr/loader.py +50 -0
  39. novel_downloader/utils/logger.py +80 -56
  40. novel_downloader/utils/network.py +16 -40
  41. novel_downloader/utils/text_utils/text_cleaner.py +39 -30
  42. novel_downloader/utils/text_utils/truncate_utils.py +3 -14
  43. novel_downloader/utils/time_utils/sleep_utils.py +53 -43
  44. novel_downloader/web/main.py +1 -1
  45. novel_downloader/web/pages/search.py +3 -3
  46. {novel_downloader-2.0.0.dist-info → novel_downloader-2.0.1.dist-info}/METADATA +2 -1
  47. {novel_downloader-2.0.0.dist-info → novel_downloader-2.0.1.dist-info}/RECORD +51 -55
  48. novel_downloader/core/parsers/qidian/book_info_parser.py +0 -89
  49. novel_downloader/core/parsers/qidian/chapter_encrypted.py +0 -470
  50. novel_downloader/core/parsers/qidian/chapter_normal.py +0 -126
  51. novel_downloader/core/parsers/qidian/chapter_router.py +0 -68
  52. novel_downloader/core/parsers/qidian/utils/fontmap_recover.py +0 -143
  53. novel_downloader/core/parsers/qidian/utils/helpers.py +0 -110
  54. {novel_downloader-2.0.0.dist-info → novel_downloader-2.0.1.dist-info}/WHEEL +0 -0
  55. {novel_downloader-2.0.0.dist-info → novel_downloader-2.0.1.dist-info}/entry_points.txt +0 -0
  56. {novel_downloader-2.0.0.dist-info → novel_downloader-2.0.1.dist-info}/licenses/LICENSE +0 -0
  57. {novel_downloader-2.0.0.dist-info → novel_downloader-2.0.1.dist-info}/top_level.txt +0 -0
@@ -42,7 +42,7 @@ class TextCleaner(Cleaner):
42
42
  TextCleaner removes invisible characters, strips unwanted patterns,
43
43
  and applies literal replacements in a single pass using a combined regex.
44
44
 
45
- For regex that never matches, reference:
45
+ For regex that never matches (r"$^"), reference:
46
46
 
47
47
  https://stackoverflow.com/questions/2930182/regex-to-not-match-anything
48
48
  """
@@ -53,13 +53,14 @@ class TextCleaner(Cleaner):
53
53
  """
54
54
  Initialize TextCleaner with the given configuration.
55
55
 
56
- :param config: TextCleanerConfig instance containing:
56
+ Configuration fields (from ``TextCleanerConfig``):
57
+ * remove_invisible: whether to strip BOM/zero-width chars
58
+ * title_remove_patterns: list of regex patterns to delete from titles
59
+ * content_remove_patterns: list of regex patterns to delete from content
60
+ * title_replacements: dict of literal replacements for titles
61
+ * content_replacements: dict of literal replacements for content
57
62
 
58
- - remove_invisible: whether to strip BOM/zero-width chars
59
- - title_remove_patterns: list of regex patterns to delete from titles
60
- - content_remove_patterns: list of regex patterns to delete from content
61
- - title_replacements: dict of literal replacements for titles
62
- - content_replacements: dict of literal replacements for content
63
+ :param config: A ``TextCleanerConfig`` instance.
63
64
  """
64
65
  self._remove_invisible = config.remove_invisible
65
66
 
@@ -73,20 +74,23 @@ class TextCleaner(Cleaner):
73
74
 
74
75
  # Build a single combined regex for title:
75
76
  # all delete‐patterns OR all escaped replacement‐keys
76
- title_parts = title_remove + [re.escape(k) for k in self._title_repl_map]
77
- title_parts.sort(
78
- key=len, reverse=True
79
- ) # longer first to avoid prefix collisions
80
- title_pattern = "|".join(title_parts) if title_parts else r"$^"
81
- self._title_combined_rx: Pattern[str] = re.compile(title_pattern)
77
+ self._title_combined_rx: re.Pattern[str] | None = None
78
+ if title_remove or self._title_repl_map:
79
+ title_parts = title_remove + [re.escape(k) for k in self._title_repl_map]
80
+ # longer first to avoid prefix collisions
81
+ title_parts.sort(key=len, reverse=True)
82
+ self._title_combined_rx = re.compile("|".join(title_parts))
82
83
 
83
84
  # Build a single combined regex for content (multiline mode)
84
- content_parts = content_remove + [re.escape(k) for k in self._content_repl_map]
85
- content_parts.sort(key=len, reverse=True)
86
- content_pattern = "|".join(content_parts) if content_parts else r"$^"
87
- self._content_combined_rx: Pattern[str] = re.compile(
88
- content_pattern, flags=re.MULTILINE
89
- )
85
+ self._content_combined_rx: re.Pattern[str] | None = None
86
+ if content_remove or self._content_repl_map:
87
+ content_parts = content_remove + [
88
+ re.escape(k) for k in self._content_repl_map
89
+ ]
90
+ content_parts.sort(key=len, reverse=True)
91
+ self._content_combined_rx = re.compile(
92
+ "|".join(content_parts), flags=re.MULTILINE
93
+ )
90
94
 
91
95
  def clean_title(self, text: str) -> str:
92
96
  """
@@ -132,11 +136,11 @@ class TextCleaner(Cleaner):
132
136
  Remove BOM and zero-width/invisible characters from the text.
133
137
 
134
138
  Matches:
135
- - U+FEFF (BOM)
136
- - U+200B ZERO WIDTH SPACE
137
- - U+200C ZERO WIDTH NON-JOINER
138
- - U+200D ZERO WIDTH JOINER
139
- - U+2060 WORD JOINER
139
+ * U+FEFF (BOM)
140
+ * U+200B ZERO WIDTH SPACE
141
+ * U+200C ZERO WIDTH NON-JOINER
142
+ * U+200D ZERO WIDTH JOINER
143
+ * U+2060 WORD JOINER
140
144
 
141
145
  :param text: Input string possibly containing invisible chars.
142
146
  :return: String with those characters stripped.
@@ -146,7 +150,7 @@ class TextCleaner(Cleaner):
146
150
  def _do_clean(
147
151
  self,
148
152
  text: str,
149
- combined_rx: Pattern[str],
153
+ combined_rx: Pattern[str] | None,
150
154
  repl_map: dict[str, str],
151
155
  ) -> str:
152
156
  """
@@ -158,17 +162,22 @@ class TextCleaner(Cleaner):
158
162
  :param repl_map: Mapping from matched token to replacement text.
159
163
  :return: Cleaned text.
160
164
  """
165
+ if not self._remove_invisible and not combined_rx:
166
+ return text.strip()
167
+
161
168
  # Strip invisible chars if configured
162
169
  if self._remove_invisible:
163
170
  text = self._remove_bom_and_invisible(text)
164
171
 
165
172
  # Single‐pass removal & replacement
166
- def _sub(match: Match[str]) -> str:
167
- token = match.group(0)
168
- # If token in repl_map -> replacement; else -> delete (empty string)
169
- return repl_map.get(token, "")
173
+ if combined_rx:
174
+
175
+ def _sub(match: Match[str]) -> str:
176
+ # If token in repl_map -> replacement; else -> delete (empty string)
177
+ return repl_map.get(match.group(0), "")
178
+
179
+ text = combined_rx.sub(_sub, text)
170
180
 
171
- text = combined_rx.sub(_sub, text)
172
181
  return text.strip()
173
182
 
174
183
 
@@ -11,8 +11,6 @@ __all__ = [
11
11
  "truncate_half_lines",
12
12
  ]
13
13
 
14
- import math
15
-
16
14
 
17
15
  def content_prefix(
18
16
  text: str,
@@ -41,22 +39,13 @@ def content_prefix(
41
39
 
42
40
  def truncate_half_lines(text: str) -> str:
43
41
  """
44
- Keep the first half of the lines (rounded up), preserving line breaks.
42
+ Keep the first half of the lines.
45
43
 
46
44
  :param text: Full input text
47
45
  :return: Truncated text with first half of lines
48
46
  """
49
47
  lines = text.splitlines()
50
48
  non_empty_lines = [line for line in lines if line.strip()]
51
- keep_count = math.ceil(len(non_empty_lines) / 2)
52
-
53
- result_lines = []
54
- count = 0
55
- for line in lines:
56
- result_lines.append(line)
57
- if line.strip():
58
- count += 1
59
- if count >= keep_count:
60
- break
61
-
49
+ keep_count = (len(non_empty_lines) + 1) // 2
50
+ result_lines = non_empty_lines[:keep_count]
62
51
  return "\n".join(result_lines)
@@ -16,50 +16,51 @@ import time
16
16
  logger = logging.getLogger(__name__)
17
17
 
18
18
 
19
- def jitter_sleep(
19
+ def _calc_sleep_duration(
20
20
  base: float,
21
- add_spread: float = 0.0,
22
- mul_spread: float = 1.0,
23
- *,
21
+ add_spread: float,
22
+ mul_spread: float,
24
23
  max_sleep: float | None = None,
25
- ) -> None:
24
+ *,
25
+ log_prefix: str = "sleep",
26
+ ) -> float | None:
26
27
  """
27
- Sleep for a random duration by combining multiplicative and additive jitter.
28
-
29
- The total sleep time is computed as:
30
-
31
- duration = base * uniform(1.0, mul_spread) + uniform(0, add_spread)
28
+ Compute the jittered sleep duration (in seconds) or return None if params invalid.
32
29
 
33
- If `max_sleep` is provided, the duration will be capped at that value.
30
+ duration = base * uniform(1.0, mul_spread) + uniform(0, add_spread)
34
31
 
35
- :param base: Base sleep time in seconds. Must be >= 0.
36
- :param add_spread: Maximum extra seconds to add after scaling base.
37
- :param mul_spread: Maximum multiplier factor for base; drawn from [1.0, mul_spread].
38
- :param max_sleep: Optional upper limit for the final sleep duration.
32
+ then optionally capped by max_sleep.
39
33
  """
40
34
  if base < 0 or add_spread < 0 or mul_spread < 1.0:
41
35
  logger.warning(
42
- "[sleep] Invalid parameters: base=%s, add_spread=%s, mul_spread=%s",
36
+ "[%s] Invalid parameters: base=%s, add_spread=%s, mul_spread=%s",
37
+ log_prefix,
43
38
  base,
44
39
  add_spread,
45
40
  mul_spread,
46
41
  )
47
- return
42
+ return None
48
43
 
49
- # Calculate the raw duration
50
44
  multiplicative_jitter = random.uniform(1.0, mul_spread)
51
- additive_jitter = random.uniform(0, add_spread)
45
+ additive_jitter = random.uniform(0.0, add_spread)
52
46
  duration = base * multiplicative_jitter + additive_jitter
53
47
 
54
48
  if max_sleep is not None:
55
49
  duration = min(duration, max_sleep)
56
50
 
57
- logger.debug("[time] Sleeping for %.2f seconds", duration)
58
- time.sleep(duration)
59
- return
51
+ logger.debug(
52
+ "[%s] base=%.3f mul=%.3f add=%.3f max=%s -> duration=%.3f",
53
+ log_prefix,
54
+ base,
55
+ multiplicative_jitter,
56
+ additive_jitter,
57
+ max_sleep,
58
+ duration,
59
+ )
60
+ return duration
60
61
 
61
62
 
62
- async def async_jitter_sleep(
63
+ def jitter_sleep(
63
64
  base: float,
64
65
  add_spread: float = 0.0,
65
66
  mul_spread: float = 1.0,
@@ -67,34 +68,43 @@ async def async_jitter_sleep(
67
68
  max_sleep: float | None = None,
68
69
  ) -> None:
69
70
  """
70
- Async sleep for a random duration by combining multiplicative and additive jitter.
71
-
72
- The total sleep time is computed as:
73
-
74
- duration = base * uniform(1.0, mul_spread) + uniform(0, add_spread)
75
-
76
- If `max_sleep` is provided, the duration will be capped at that value.
71
+ Sleep for a random duration by combining multiplicative and additive jitter.
77
72
 
78
73
  :param base: Base sleep time in seconds. Must be >= 0.
79
74
  :param add_spread: Maximum extra seconds to add after scaling base.
80
75
  :param mul_spread: Maximum multiplier factor for base; drawn from [1.0, mul_spread].
81
76
  :param max_sleep: Optional upper limit for the final sleep duration.
82
77
  """
83
- if base < 0 or add_spread < 0 or mul_spread < 1.0:
84
- logger.warning(
85
- "[async sleep] Invalid parameters: base=%s, add_spread=%s, mul_spread=%s",
86
- base,
87
- add_spread,
88
- mul_spread,
89
- )
78
+ duration = _calc_sleep_duration(
79
+ base,
80
+ add_spread,
81
+ mul_spread,
82
+ max_sleep,
83
+ log_prefix="sleep",
84
+ )
85
+ if duration is None:
90
86
  return
87
+ time.sleep(duration)
91
88
 
92
- multiplicative_jitter = random.uniform(1.0, mul_spread)
93
- additive_jitter = random.uniform(0, add_spread)
94
- duration = base * multiplicative_jitter + additive_jitter
95
89
 
96
- if max_sleep is not None:
97
- duration = min(duration, max_sleep)
90
+ async def async_jitter_sleep(
91
+ base: float,
92
+ add_spread: float = 0.0,
93
+ mul_spread: float = 1.0,
94
+ *,
95
+ max_sleep: float | None = None,
96
+ ) -> None:
97
+ """
98
+ Async sleep for a random duration by combining multiplicative and additive jitter.
98
99
 
99
- logger.debug("[async time] Sleeping for %.2f seconds", duration)
100
+ :param base: Base sleep time in seconds. Must be >= 0.
101
+ :param add_spread: Maximum extra seconds to add after scaling base.
102
+ :param mul_spread: Maximum multiplier factor for base; drawn from [1.0, mul_spread].
103
+ :param max_sleep: Optional upper limit for the final sleep duration.
104
+ """
105
+ duration = _calc_sleep_duration(
106
+ base, add_spread, mul_spread, max_sleep, log_prefix="async sleep"
107
+ )
108
+ if duration is None:
109
+ return
100
110
  await asyncio.sleep(duration)
@@ -56,7 +56,7 @@ def web_main() -> None:
56
56
  host = "127.0.0.1" if args.listen == "local" else "0.0.0.0"
57
57
 
58
58
  log_level = get_config_value(["general", "debug", "log_level"], "INFO")
59
- setup_logging(log_level=log_level)
59
+ setup_logging(console_level=log_level)
60
60
 
61
61
  app.on_startup(mount_exports)
62
62
  ui.run(host=host, port=args.port, reload=args.reload)
@@ -145,9 +145,9 @@ def _build_settings_dropdown(
145
145
  Create settings button + anchored menu with initial values from state.
146
146
 
147
147
  Returns a tuple of getter functions:
148
- - get_sites(): list of site keys, or None if none selected
149
- - get_psl(): per-site limit (int)
150
- - get_timeout(): timeout (float)
148
+ * get_sites(): list of site keys, or None if none selected
149
+ * get_psl(): per-site limit (int)
150
+ * get_timeout(): timeout (float)
151
151
  """
152
152
  site_cbs: dict[str, Any] = {}
153
153
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: novel-downloader
3
- Version: 2.0.0
3
+ Version: 2.0.1
4
4
  Summary: A command-line tool for downloading Chinese web novels from Qidian and similar platforms.
5
5
  Author-email: Saudade Z <saudadez217@gmail.com>
6
6
  License: MIT License
@@ -47,6 +47,7 @@ Requires-Dist: aiohttp
47
47
  Requires-Dist: lxml
48
48
  Requires-Dist: platformdirs
49
49
  Provides-Extra: font-recovery
50
+ Requires-Dist: brotli; extra == "font-recovery"
50
51
  Requires-Dist: numpy; extra == "font-recovery"
51
52
  Requires-Dist: fonttools; extra == "font-recovery"
52
53
  Requires-Dist: pillow; extra == "font-recovery"
@@ -1,16 +1,16 @@
1
- novel_downloader/__init__.py,sha256=gvhm67MQNWoL_G7z3QqXrhgp7Cw3xc5KqU8-UpCKQ0s,218
1
+ novel_downloader/__init__.py,sha256=rCDXxBxrYs3WYnanTn5QZYhg2-rG48Ry2DS7urOkIws,218
2
2
  novel_downloader/cli/__init__.py,sha256=okUd_09fBWx8lz3zx3329dIK0xNRlNQJkUzDXYoSE1U,167
3
3
  novel_downloader/cli/clean.py,sha256=VaiJox75-ZuXvSDFMaiAUawEbdtuEUsnsVWZGKuirJ4,2395
4
4
  novel_downloader/cli/config.py,sha256=W4-f59dK5jdlg_9XDwGIyzd6VtpyNpB9U4qcxfso8Xg,3598
5
- novel_downloader/cli/download.py,sha256=-59icT24iU_l6LhAJqQaxLAogJAr0hRxd-_RfcjQbdI,7587
6
- novel_downloader/cli/export.py,sha256=iSIbSXreYRTs_KfZS4eJ0cJoMgoutwdza88JGoxQMlw,2573
5
+ novel_downloader/cli/download.py,sha256=2iEW137yI-YbBE-4Um7h4IQRZzX0qbPILwCQeTEfU8o,7603
6
+ novel_downloader/cli/export.py,sha256=u1Cu-2PcVBny8cMVPS0lTCs34XXcVljkXBZpNUCQQKw,2577
7
7
  novel_downloader/cli/main.py,sha256=GKtxeumw_VvWManp52Am538jL2t7xz7yVrGIaxrvSVk,1009
8
8
  novel_downloader/cli/search.py,sha256=_Ci4fa1ECiYmBZI2OZBxzwddYGIvdUyIp4AOIJiL1Ds,3641
9
- novel_downloader/cli/ui.py,sha256=Gme3J4M_eK23cDWepMs8-c0zfIanSSXERnYSWYkerE4,3992
9
+ novel_downloader/cli/ui.py,sha256=XyvgTlsiV1NkGLWfCt6WvTIm8h8hXq5_RSfCdUwy8P0,4024
10
10
  novel_downloader/config/__init__.py,sha256=gwaDMlcBX7Lw8Hi8dY2HxNEt9ysMhIFNhkziyjramWo,405
11
- novel_downloader/config/adapter.py,sha256=Y8Dt5GflbCiOwjBbTN7D3O5JPnkssbJDYGtYOYzEt1M,11561
11
+ novel_downloader/config/adapter.py,sha256=4pZo-9PA7g2H1tybqdUeGOODZCKtpVyAKUg23VX-LBA,13444
12
12
  novel_downloader/config/file_io.py,sha256=ncKk7rj07UnYYVEPR_RHS2D5igMSv8xaHySNvJQWhB8,6495
13
- novel_downloader/core/__init__.py,sha256=20zZKxemHFQv8noyftPTcen6Okq-AJS5GIlwn8Zc7AA,1157
13
+ novel_downloader/core/__init__.py,sha256=oCmWj9t7CEyNfew5O25YeaMPeMvXt082jprKAPMffKk,1166
14
14
  novel_downloader/core/archived/deqixs/fetcher.py,sha256=JGTLWI-d5jLULB-vS2VUeajwp7tHlb9trUCF4lJrdiQ,3432
15
15
  novel_downloader/core/archived/deqixs/parser.py,sha256=udM6MYdunPVYwUNjEoBDjffHR_SsXPvv_EqsCMJJVrE,4169
16
16
  novel_downloader/core/archived/deqixs/searcher.py,sha256=ezXCQ0pM8AqmTXmkzLrOSebTvHg_GouIywIE23c5hwY,2920
@@ -33,11 +33,11 @@ novel_downloader/core/exporters/txt_util.py,sha256=-pkwMFh-69FHEziTW4LiW3-u6f1Gq
33
33
  novel_downloader/core/exporters/common/__init__.py,sha256=8qVMdtM_hLo3D3bMMQYYL10I0evHy1BgaceqOMTu9OE,259
34
34
  novel_downloader/core/exporters/common/epub.py,sha256=OZV8KCE9otYLjlDKEogFQ5KElUAXGJwV5zSCtm59FOw,5961
35
35
  novel_downloader/core/exporters/common/main_exporter.py,sha256=uBx67rBaeijZWt3cZDR7xipf-P_3ShyypZROxe8CjEQ,2165
36
- novel_downloader/core/exporters/common/txt.py,sha256=Nzzo60ksECYkBvjSooZYPUFNbeD7hbz6jmTUSYVhHxk,4721
36
+ novel_downloader/core/exporters/common/txt.py,sha256=Nm4MW0rEIzX-7jzOFTaS4fdkzB1T8zBrIHpM-W7niN0,4755
37
37
  novel_downloader/core/exporters/linovelib/__init__.py,sha256=AFjdUyFalrU8n_OjxqYENZ5zVcfYPONgWbvFkI0Sgls,250
38
38
  novel_downloader/core/exporters/linovelib/epub.py,sha256=BiYgX61lZdgYeipnNuzMHmAjjSXnofs1eKoPR9UduDg,10703
39
39
  novel_downloader/core/exporters/linovelib/main_exporter.py,sha256=qFMKfPobNWTDzW8CLr6FcNpkdqAuH5PMjqAQA_Jb78w,1938
40
- novel_downloader/core/exporters/linovelib/txt.py,sha256=5k7ZKVPOtalOeTr9xFca3zkofNKa4Wg0NdKk-lujC58,4433
40
+ novel_downloader/core/exporters/linovelib/txt.py,sha256=cdB_5oZs1SSPZ-TpemZv89goSVpj3jV23A52DPXhYDc,4467
41
41
  novel_downloader/core/fetchers/__init__.py,sha256=V0BXwjULLODbS69YRRfvivEB4HVdUxo4tFMZ44qT6mU,1982
42
42
  novel_downloader/core/fetchers/aaatxt.py,sha256=fupx1NLqHc3PH61_cSbZ0lafkmLmcs2R3FI5z56YyC4,2417
43
43
  novel_downloader/core/fetchers/b520.py,sha256=IuIci62rHLtad3eIU-iR9be56vw7w3W4ctpvQpkOg3I,2458
@@ -56,7 +56,7 @@ novel_downloader/core/fetchers/linovelib.py,sha256=5oUji6UkM0XXkDmOPiWelhSo6C0Nk
56
56
  novel_downloader/core/fetchers/piaotia.py,sha256=DPtBSBFmrU3Y3ha7u83sx5S9GXTpNxFj5VZtsP6pkmk,3220
57
57
  novel_downloader/core/fetchers/qbtr.py,sha256=EGw8p-J01wu1iJmz4Vm6wjXPuTgVt6Y4BIY8Aun7zsQ,3094
58
58
  novel_downloader/core/fetchers/qianbi.py,sha256=sWMpF78ZXBBVYs-DdUp0LG7khO84AMJzFYkDu_GSqfA,3174
59
- novel_downloader/core/fetchers/qidian.py,sha256=pk_uLdCCnCYRD_OaJNhR17UCwmI6iH7mKMeKfEofgZY,10330
59
+ novel_downloader/core/fetchers/qidian.py,sha256=9CqpWu1qguAgYb6NmhSIA2O59S_S700H_PsbMf2iZsI,10329
60
60
  novel_downloader/core/fetchers/quanben5.py,sha256=TkLFJOfpMOpKNkq2D9R0HI33zzAV-KuHccXquu6CZZ0,2776
61
61
  novel_downloader/core/fetchers/rate_limiter.py,sha256=Z05dZOATi_-Su14qRmhVinArfLJ0QWpzgDShs2jHSR8,2714
62
62
  novel_downloader/core/fetchers/registry.py,sha256=z9DDTUjc2r-Xiq3MqaPfV_3EmQBXr2TK_G8eARnblkA,1580
@@ -75,17 +75,17 @@ novel_downloader/core/fetchers/yibige.py,sha256=CJO26gMj_36XQMFVVnXDr8aoTQAqBA6A
75
75
  novel_downloader/core/interfaces/__init__.py,sha256=YwDOPw_UCjuAE5u_LpUYxXjOH4X2kNMXrmUoBa5jp2Q,490
76
76
  novel_downloader/core/interfaces/downloader.py,sha256=nGY2LYCCBzrKhs612L5tevGG8kJp_XfCyKvqjXEIeoI,1763
77
77
  novel_downloader/core/interfaces/exporter.py,sha256=R62GPGzfGQu0B-7nu6y-PTnYF_TkrGir3XG19AW6jNE,1649
78
- novel_downloader/core/interfaces/fetcher.py,sha256=XZ320Fi72nDORQKFUBbtybWmQ2ZoJ63gpVXBurodOrQ,3669
79
- novel_downloader/core/interfaces/parser.py,sha256=NzpKjh-P2IwNE0BUmaEKU49qhPNbLhodZMCj2uSkOkg,1317
78
+ novel_downloader/core/interfaces/fetcher.py,sha256=2ESTszn-GrXj9rxYr_cQy86R1iCvWH-LbJx63j933YM,3669
79
+ novel_downloader/core/interfaces/parser.py,sha256=B_HsE_PshnUjz-QibBpuNht6whN9gboW-k1BAsnMe6M,1317
80
80
  novel_downloader/core/interfaces/searcher.py,sha256=1orneqFtwcqM5sFBKmhpWFbHk5bV7oEp0mN_77lgy6E,587
81
81
  novel_downloader/core/parsers/__init__.py,sha256=VfGEMS0PUQLkmc32xiz7l5rCPgzuy3Vz-1ZDxaYNp1Q,1927
82
82
  novel_downloader/core/parsers/aaatxt.py,sha256=1NCrhbbpd5FUi0ybMX2LYf6Sss4pY3eDXTXk7MEkrkk,3633
83
83
  novel_downloader/core/parsers/b520.py,sha256=SUiY1IYOZmu-tY-owbQHMKUyDFxwwOI6bgdjyNw_daM,3254
84
- novel_downloader/core/parsers/base.py,sha256=tdq7MuNezN5xwZJ5DUYKUOki9hisPJ_wyt9LdG3ocVo,4634
84
+ novel_downloader/core/parsers/base.py,sha256=R6xbT9ezFWcdFhqCItj2iigwQSB3MNrH9joC14vx2AI,4689
85
85
  novel_downloader/core/parsers/biquyuedu.py,sha256=IpZfL2AC0oUq5Gx8iqwTH8sItxtzpxvDRKaMmtYf_xU,3867
86
86
  novel_downloader/core/parsers/dxmwx.py,sha256=uj3tj36d8lnXbgFvmpHzrvv9oDYuMXWZCb0d8I32GfA,5006
87
- novel_downloader/core/parsers/eightnovel.py,sha256=Yw9tt24SsAe3Y6xyXVgo1EUAs36_twPhoF1toUZZjAc,7290
88
- novel_downloader/core/parsers/esjzone.py,sha256=vGcziiCmrkJeNO0BUBrFJOKg83k4buvysqInUVIJXJ4,7958
87
+ novel_downloader/core/parsers/eightnovel.py,sha256=M-BFJcqaBjNlf5PhoD-qscTR9o0ydRhQQAMbHzh1Z-o,7294
88
+ novel_downloader/core/parsers/esjzone.py,sha256=WeBwFn8waUtiZ3rzGJaiuB21ga7mnMnuBCA-0XnXOQI,7958
89
89
  novel_downloader/core/parsers/guidaye.py,sha256=o1d987B5hkCEIVHYhszSavwKJFAErZVwpDaUPkSNUh4,3879
90
90
  novel_downloader/core/parsers/hetushu.py,sha256=WUG4E9lvDJsa_l1xezYt0epPPJ4V9y_C4wwqWB8sxJs,4047
91
91
  novel_downloader/core/parsers/i25zw.py,sha256=qILX6HQifu0hLJDNFc7XCqUNSQpfALhVgPC9jEGQt4g,4221
@@ -105,22 +105,16 @@ novel_downloader/core/parsers/tongrenquan.py,sha256=y3ok9g-BzA2YVM4KqoNPPpclE0wd
105
105
  novel_downloader/core/parsers/ttkan.py,sha256=_8nc6diogbb9uGHm_lXYJT-RHR9p76o7ARn4NPom2-E,3564
106
106
  novel_downloader/core/parsers/wanbengo.py,sha256=boEnK1gLLpqmf5EUFY9gVEhBxqxYrVka9ou_mDGZ4co,6407
107
107
  novel_downloader/core/parsers/xiaoshuowu.py,sha256=k8j0TY5t7s0n1f-FWuRTZ-eN3A8p-_xnKy7-kp2deK4,5328
108
- novel_downloader/core/parsers/xiguashuwu.py,sha256=i_CpFKjfcGviOjT3fveDvDsaMmxHub9MOrCbWRySbH8,14448
108
+ novel_downloader/core/parsers/xiguashuwu.py,sha256=1Xq00oRcb-vGskisbkNJuSmBGWNK7NpPOHbQHdMks_k,14223
109
109
  novel_downloader/core/parsers/xs63b.py,sha256=A02abnMtsnWTomx-Hcfr97HFnNO38X9mmqKxHKrS610,5312
110
110
  novel_downloader/core/parsers/xshbook.py,sha256=WwIGXEBAY8h-MLeApB1oVko_g3HF1Uj0X-Hko3TTrJ8,3995
111
111
  novel_downloader/core/parsers/yamibo.py,sha256=B2ijBHCS0JW0I387LH8gDjevwakOE0RRKaKRsV4zo7A,5018
112
112
  novel_downloader/core/parsers/yibige.py,sha256=K_nQ1SwVKLndgzlRvoTd2wd0tWXKb-T02c0sE6-bS5M,4846
113
113
  novel_downloader/core/parsers/qidian/__init__.py,sha256=ScY9penE8b1m5HZGu6C1XwbzuqKbeGVCvli0J-xzzKs,173
114
- novel_downloader/core/parsers/qidian/book_info_parser.py,sha256=RTg6Z2jaLEUVJXKzcdxG1rqBdAhu_gmxsq0zcD9vyAE,3043
115
- novel_downloader/core/parsers/qidian/chapter_encrypted.py,sha256=eTIOTwlu_7DiAkul58tPOOlCSxMymcSfr_m4AlnkEdU,15386
116
- novel_downloader/core/parsers/qidian/chapter_normal.py,sha256=JitsHCz9FSvslAbFLPdtF0FNr5V311Uj7K0zq7MUDwA,3738
117
- novel_downloader/core/parsers/qidian/chapter_router.py,sha256=foVMlWtE-qUOvJD_4EDiuAVaNkFdeV_ZTCvS5IL7Orc,1957
118
- novel_downloader/core/parsers/qidian/main_parser.py,sha256=w6s9RkcatA34chyzi4buFKew4-bIexlSHAIR1zBGIxE,2754
119
- novel_downloader/core/parsers/qidian/utils/__init__.py,sha256=FuV7f-h76sk1g5BaqWOhsgh9uTykXA7MZ97kmkOB_lY,664
114
+ novel_downloader/core/parsers/qidian/main_parser.py,sha256=3JpvTFbL3yzGE7sfjgRHtUTid8_TVNaqBYyMNVnTwkM,29833
115
+ novel_downloader/core/parsers/qidian/utils/__init__.py,sha256=7aCxmMDQ3wkAPPtVIQ_YYprzE8V1IcHh2LK5S2rEc4U,266
120
116
  novel_downloader/core/parsers/qidian/utils/decryptor_fetcher.py,sha256=idwsRx9Er5SRb3b_tmKwNxfOmUWR0QwpVf_zpDRJxuw,4687
121
- novel_downloader/core/parsers/qidian/utils/fontmap_recover.py,sha256=kuEPRnGn4GHVh3uQ8u8sg55LUaYYuavftxO9gyAR3ZM,4918
122
- novel_downloader/core/parsers/qidian/utils/helpers.py,sha256=_HAOWW3HHtq2FtYlYU1FIebZdRmCo1Rfmh_WC9p_ZLU,3325
123
- novel_downloader/core/parsers/qidian/utils/node_decryptor.py,sha256=MQQUI9nNpM23K0sA9bRVhs7pbQX6OvMc1NgvM28gHsU,5949
117
+ novel_downloader/core/parsers/qidian/utils/node_decryptor.py,sha256=WY2rG2S7GlIqDiFBBJaGW4XJorCwVHfbiYIeytkkmNo,5949
124
118
  novel_downloader/core/searchers/__init__.py,sha256=pCCK1rxfQP8djdsRnpxoVgANe6CTpbP7DNbNond_1P4,1379
125
119
  novel_downloader/core/searchers/aaatxt.py,sha256=KHaX1dpDBaAJz-_7UlaTtnMbFlTplWHaFyOfCkn4gX4,3717
126
120
  novel_downloader/core/searchers/b520.py,sha256=L2fkf1_LxUREdPJJXbSN5c00EXwWVeMwWpXKN144k9c,2621
@@ -143,8 +137,8 @@ novel_downloader/core/searchers/ttkan.py,sha256=Y-qjePD7zQx8RUgASTuz4yu5fzT5MOJg
143
137
  novel_downloader/core/searchers/xiaoshuowu.py,sha256=gAS3OpoMwfksEtlXPL5XPrfD7KVprl8YA7HGn3gGANg,4132
144
138
  novel_downloader/core/searchers/xiguashuwu.py,sha256=YWxOQpXUhMMsgxt7zc51rtdbe6mn9yv5Vzeg63OCJlQ,2952
145
139
  novel_downloader/core/searchers/xs63b.py,sha256=T4QUQphYFvnbnENxFbYVNtZMi2QhXypxOJU-FjjVYwU,3443
146
- novel_downloader/locales/en.json,sha256=T0Apt75RC9Uua6quGckZPMzkh0XB3uy_JkXzRxwRllk,3948
147
- novel_downloader/locales/zh.json,sha256=LU691ypbrR903ie2BKHLgI1U4G6_fYpT4kcztMYO5-8,3785
140
+ novel_downloader/locales/en.json,sha256=w3TA-AtsQE2C1EVf5tNiOQRnaaXEaXX88-0Bb3xWdU8,3942
141
+ novel_downloader/locales/zh.json,sha256=XRku9SHycZ8zP0YYBwchBAg4yYjCWpgCLDItW8cmUcI,3779
148
142
  novel_downloader/models/__init__.py,sha256=U7ks1ieTm-VzmH3NcH0Fym7c1zfywIZcBGPOUBWuVLo,690
149
143
  novel_downloader/models/book.py,sha256=kim6Tt0pDDvOhGx6jnRp28BP79yhhsyNiUp56I0cAP8,875
150
144
  novel_downloader/models/config.py,sha256=QNiw1XhbzjAYbfLAZ1bTIQ21nciNxnYszGVtXVG3Uk0,2211
@@ -157,54 +151,56 @@ novel_downloader/resources/images/volume_border.png,sha256=2dEVimnTHKOfLMhi7bhkh
157
151
  novel_downloader/resources/js_scripts/qidian_decrypt_node.js,sha256=spNrk_gXI7pPW9abr4XGc2LASMe1UuN4BUe4cH24L8s,2195
158
152
  novel_downloader/resources/json/linovelib_font_map.json,sha256=F1IlEcvXGcagnZCu4Gw2vQ2NLnhy6cJOLhabukqnftw,67854
159
153
  novel_downloader/resources/json/xiguashuwu.json,sha256=AT8KSFBWuiHj4Ggz3O1LqED0Tw22k0A4-V4OziHbqfo,16471
160
- novel_downloader/utils/__init__.py,sha256=Mu3FyLH_9wp7zHtIHfSTWvdNkXYzOAHYMoGMDJKk4cg,920
161
- novel_downloader/utils/chapter_storage.py,sha256=2l5mRHYQd7rarBB4UeUo4Lwood-cqY0LHvXb2KPyFqQ,10365
162
- novel_downloader/utils/constants.py,sha256=KeTL_MygD0VdvEUeDFaHuqITIzU_LdD5huNQHC7uCDM,3312
163
- novel_downloader/utils/cookies.py,sha256=diWQj-cMvH7eT2P1X-lzGzsWLcfhaPW2wvMVnwCQwkQ,1735
164
- novel_downloader/utils/fontocr.py,sha256=gaNH4isYY7zFE--gqtM1YN6bIjWTA-G-60XCAsbja8o,6494
154
+ novel_downloader/utils/__init__.py,sha256=_l77NlxmGA3HHGSwK68o4_Nc7xRaVP8Bo8SUuImP5Uk,867
155
+ novel_downloader/utils/chapter_storage.py,sha256=7jpVdOguuylnjsJi_yQ-jCMIPljtJWsq_NqXMDDv0lQ,10315
156
+ novel_downloader/utils/constants.py,sha256=uff08ll68TD8HwITR-uZ7j_2bQjKPso0-ryKmYhtjAU,3196
157
+ novel_downloader/utils/cookies.py,sha256=Z_9mjJYfhv6xPVgsm7BzCpWeMTcPKoxAoUFWZYuEFWM,2117
165
158
  novel_downloader/utils/i18n.py,sha256=86Wz4zT0_YTnmOSS6cliqO_uMgJJdfC-hNLyqZnXgvU,1050
166
- novel_downloader/utils/logger.py,sha256=exOM7pUb8n_AchmIxLNlaZySqsMQjViL4KEMilZ8t04,3013
167
- novel_downloader/utils/network.py,sha256=UzqK6xuQQfV4gFFbmNgy09L3QAlqX-CFeF6urfKRJyg,4675
159
+ novel_downloader/utils/logger.py,sha256=QuPyblYOIXEa4X0YBHLZgwbv1ZhkPNq159qyGmFKOno,3551
160
+ novel_downloader/utils/network.py,sha256=AIbM2kEfA9OjkMluFRKqGZIY2ZfNxVAoPnUHUkKkzgw,3625
168
161
  novel_downloader/utils/state.py,sha256=QHfvS9CrO08fRZLNM0B_s0n5XIz7ap20NdAfesQI0Ek,1852
169
- novel_downloader/utils/crypto_utils/__init__.py,sha256=33j0dezsuX3LhkfXj-FImEgU7G1IVmjjZW-4FrDDT0o,196
162
+ novel_downloader/utils/crypto_utils/__init__.py,sha256=MOl8pb73dRk8yQDuh2RZULnM3mn9VnJnOkh0SWcvaPs,136
170
163
  novel_downloader/utils/crypto_utils/aes_util.py,sha256=46vKY1CxEnHgJE9t2CRzsrGyUoLRjBysCJCeSF5AiqU,2878
171
164
  novel_downloader/utils/crypto_utils/aes_v1.py,sha256=_jW86_WyUTJfOzqYbZ6QvNPF-V8YRaqaOsyLjMsO610,34247
172
165
  novel_downloader/utils/crypto_utils/aes_v2.py,sha256=OAsMnsztkJilqo8jM6nkrPMyujPqgGhohd-TcwW6fVA,48945
173
- novel_downloader/utils/crypto_utils/rc4.py,sha256=3h9PBLraTow7TGiV60jI3zh4ZQH2ohaizJ8LThB55Pk,1934
174
- novel_downloader/utils/epub/__init__.py,sha256=SaygR4gIuUWhFxgmmSzv4unRMxNMZPyIX4x2-_26yvc,722
175
- novel_downloader/utils/epub/builder.py,sha256=apuzG7UzYcEEreMpf9Dez06-laQQg6EGGvl3PQGpBFA,11664
176
- novel_downloader/utils/epub/constants.py,sha256=3fjJIr-5msO1sV2hjYHBiUpEvm0mQm5AsUlMOR03O_M,3080
177
- novel_downloader/utils/epub/documents.py,sha256=8IgznHBLzaLgzxWJCHGDM4QuE8JdEDI7T3zuA9IHAbE,9868
178
- novel_downloader/utils/epub/models.py,sha256=x9la6RKmhOTUkcsLSzM7qzThmRbyWZxqaRf39kY6IPo,2479
179
- novel_downloader/utils/epub/utils.py,sha256=Jg1Xjii6KMVNMKbp6IkWxbwq_ILaEpgqp2vHN8i7P40,5084
166
+ novel_downloader/utils/crypto_utils/rc4.py,sha256=SbC0oAdg2N7LWMnq9YbNh-zChI8SfpeBiijTPxFkYFc,1319
167
+ novel_downloader/utils/epub/__init__.py,sha256=77WorOLdsaxNoiuDPVxvQkiU49NW774bf3E7erPBv1I,725
168
+ novel_downloader/utils/epub/builder.py,sha256=2Vd7qD12vWqu05hVHrcgn5gfRzxGklY88nv8unuXF4M,11676
169
+ novel_downloader/utils/epub/constants.py,sha256=8r0TmDkGhBVpLNQXS4eIhC2WYwOvFEmpVDF7Mxzm5sA,3090
170
+ novel_downloader/utils/epub/documents.py,sha256=UijMa8yHpleZLUtRrwwTsOPZlqYlC5-qqXEuKh_StPI,9882
171
+ novel_downloader/utils/epub/models.py,sha256=CppM1qIC_G53L-lbouJO-pBBKLMTRfMvOM2PUOSPT78,2489
172
+ novel_downloader/utils/epub/utils.py,sha256=7eRYXUZFZ3NQU69UDwLK_yxiPQWpRhxBPhzKkQSTS0s,5090
180
173
  novel_downloader/utils/file_utils/__init__.py,sha256=PIE96UKkI73wNr5v8kbP159BIbkEwsOQa9iEFr5RGUw,366
181
- novel_downloader/utils/file_utils/io.py,sha256=Q5s2p4-HIAEV1_PLrpmHkGuXGVYPWkKoHOwjRVgru-w,3397
182
- novel_downloader/utils/file_utils/normalize.py,sha256=v0ZRGcuZ9KMj0GTgAMPCwSBnYkEz8LCnW5dJYjf_jQk,2023
183
- novel_downloader/utils/file_utils/sanitize.py,sha256=2hoBAleu6n3VhL6IP1df2x0-7mLVAxg1ef6Ogg-ZLu4,1922
174
+ novel_downloader/utils/file_utils/io.py,sha256=bF1q38BdwjTkM8ylfQGczWAcAMHI0gBB5xNkYwTYqrk,2167
175
+ novel_downloader/utils/file_utils/normalize.py,sha256=j8d6kmiQyafa-E8NjTv33uUrnNDoxyJ18orXfS5J_Gw,1708
176
+ novel_downloader/utils/file_utils/sanitize.py,sha256=IuJA2JjcrxxOnJtehPLBpBNOuyNbj97TUZvw-vXB9PE,1713
177
+ novel_downloader/utils/fontocr/__init__.py,sha256=lWT8hVKeuYatEanyw6EjlgPidlyWSMkgCiRliEG_VtA,314
178
+ novel_downloader/utils/fontocr/core.py,sha256=6-JhRo6fvdFxxJBsGSaKcyTh3PZSlGSkAIdJwzvShas,7475
179
+ novel_downloader/utils/fontocr/loader.py,sha256=TGSv9qq-jomky4h6ezr6uoUTgF3-FP385vP-LS7YlBY,1300
184
180
  novel_downloader/utils/text_utils/__init__.py,sha256=cr0HCvgQFJE2ZEXqjkkmrOA7PnH50dKegrjdpIytUJs,601
185
181
  novel_downloader/utils/text_utils/diff_display.py,sha256=5zvVtzsLbRxmUnn_dWUsXFaq6hch02OlOpqKYP8GB3g,2491
186
182
  novel_downloader/utils/text_utils/numeric_conversion.py,sha256=RJ3L5GBxgNEQYJ7xFoihLVsjGcObBfpUAk_Gz5MxlkA,6627
187
- novel_downloader/utils/text_utils/text_cleaner.py,sha256=9-Ymi6H9BDBv1Sjk2gE9fCG4G3A8Lut-n2sfAyeyH2I,6124
188
- novel_downloader/utils/text_utils/truncate_utils.py,sha256=GeQCczW0jAHSeDMxx0qCRtnvT-CH1-KjqJq2JBpIv7U,1483
183
+ novel_downloader/utils/text_utils/text_cleaner.py,sha256=9GbJChhZQGW06LApnHNzy6t5gS73AA9BTBigBn9qgcs,6410
184
+ novel_downloader/utils/text_utils/truncate_utils.py,sha256=xg5nlQvq6O1g3K-I64aTZvtnesN0P5QBGUVCPj09g8s,1284
189
185
  novel_downloader/utils/time_utils/__init__.py,sha256=XbBeSbpNqiJy7Lsv-_udz29hPnIY53F0Drbuh730y44,331
190
186
  novel_downloader/utils/time_utils/datetime_utils.py,sha256=ECidI5yvWVia8BWWLu7U0O0YYMIKgJwzWQmOuA_un3U,4499
191
- novel_downloader/utils/time_utils/sleep_utils.py,sha256=g31qs1Uyng8GBhyqrvMpxBK2Xqr054xDlA1g8X5cZhk,3062
187
+ novel_downloader/utils/time_utils/sleep_utils.py,sha256=uwQfnhFHqNOku8UwUJ7btD2N0NgDjY925QKDzT-9_sE,3000
192
188
  novel_downloader/web/__init__.py,sha256=ga8qRQhYcGEiL0WKhSSyldLxkOEfrq-UU71T7MYv7EU,174
193
- novel_downloader/web/main.py,sha256=p8iq5VejxuvcJsgM20qDahBsrxK4PeL96O7_65Fa3JI,1779
189
+ novel_downloader/web/main.py,sha256=ZzQcpJkAZb7tRlBkub6LGggNrbM4gtrI4nb-MyiUAlY,1783
194
190
  novel_downloader/web/components/__init__.py,sha256=fxBI7rpAQvdZU3obR6_-bFqgmA7nzvRpW05Rw3b4jp8,193
195
191
  novel_downloader/web/components/navigation.py,sha256=m-rjbNRlYjqXsu2dq5PdCzpqy7oW6_otra2ZufRsh1c,1156
196
192
  novel_downloader/web/pages/__init__.py,sha256=KP_aH8y8dtCeRze8jDJ4tC3Ft7UTGhlW8ZKDdKPdwcM,383
197
193
  novel_downloader/web/pages/download.py,sha256=Qqq4GAZaiMRAuCjBvWiof6kwzSz0yrAj5LWN5EEldqo,2600
198
194
  novel_downloader/web/pages/progress.py,sha256=whWduqLvQ1PPZpDNVfvFCVaFgFhD87LT4CDb8JuqD_w,5207
199
- novel_downloader/web/pages/search.py,sha256=Tgy4XgMoD2tovkGvSj9mMWBzWtO_FUGQATHEpzCSgx0,10235
195
+ novel_downloader/web/pages/search.py,sha256=qV_jgtQtNnzT_EWXUeEZfw1ieOxbaXcx4JSs-UQuS4Q,10235
200
196
  novel_downloader/web/services/__init__.py,sha256=kLW0uyqudnlHtSsMrrghmL6P1-VDrseggNdB3cR5jLA,316
201
197
  novel_downloader/web/services/client_dialog.py,sha256=JwYV8bjxPxLQsuheC8rk_jaeODolz5X9Wo64MsXZoyc,5679
202
198
  novel_downloader/web/services/cred_broker.py,sha256=_R2Szqg5qRBHrFujTlPxrrMpQsnfNozjLUxJ5HhD8_c,2939
203
199
  novel_downloader/web/services/cred_models.py,sha256=8nXbVoAIz3QokctrCVm-8lSnKgCIFhMM6TuaNq6bFGU,846
204
200
  novel_downloader/web/services/task_manager.py,sha256=V6Pp92ZB9aNlliZbdwcaNUojioY1_BGAMGCo9Okob7w,8624
205
- novel_downloader-2.0.0.dist-info/licenses/LICENSE,sha256=XgmnH0mBf-qEiizoVAfJQAKzPB9y3rBa-ni7M0Aqv4A,1066
206
- novel_downloader-2.0.0.dist-info/METADATA,sha256=V1Xc8HvvlY-J2S6aqP2Hll5vVxZs3aUwrPVQXOGsXV0,6454
207
- novel_downloader-2.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
208
- novel_downloader-2.0.0.dist-info/entry_points.txt,sha256=nIgnurS4J6_ce3oIBFV87I1pPwBofuRQvxkoRGn4yEU,112
209
- novel_downloader-2.0.0.dist-info/top_level.txt,sha256=hP4jYWM2LTm1jxsW4hqEB8N0dsRvldO2QdhggJT917I,17
210
- novel_downloader-2.0.0.dist-info/RECORD,,
201
+ novel_downloader-2.0.1.dist-info/licenses/LICENSE,sha256=XgmnH0mBf-qEiizoVAfJQAKzPB9y3rBa-ni7M0Aqv4A,1066
202
+ novel_downloader-2.0.1.dist-info/METADATA,sha256=1Mw3aNRkbCiKXaTXJvxzICtInOZEVTpY4kGok3CZ19U,6502
203
+ novel_downloader-2.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
204
+ novel_downloader-2.0.1.dist-info/entry_points.txt,sha256=nIgnurS4J6_ce3oIBFV87I1pPwBofuRQvxkoRGn4yEU,112
205
+ novel_downloader-2.0.1.dist-info/top_level.txt,sha256=hP4jYWM2LTm1jxsW4hqEB8N0dsRvldO2QdhggJT917I,17
206
+ novel_downloader-2.0.1.dist-info/RECORD,,
@@ -1,89 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- novel_downloader.core.parsers.qidian.book_info_parser
4
- -----------------------------------------------------
5
-
6
- This module provides parsing of Qidian book info pages.
7
-
8
- It extracts metadata such as title, author, cover URL, update
9
- time, status, word count, summary, and volume-chapter structure.
10
- """
11
-
12
- import logging
13
- import re
14
- from datetime import datetime
15
-
16
- from lxml import html
17
-
18
- from novel_downloader.models import BookInfoDict, ChapterInfoDict, VolumeInfoDict
19
-
20
- logger = logging.getLogger(__name__)
21
-
22
-
23
- def _chapter_url_to_id(url: str) -> str:
24
- return url.rstrip("/").split("/")[-1]
25
-
26
-
27
- def parse_book_info(html_str: str) -> BookInfoDict | None:
28
- """
29
- Extract metadata: title, author, cover_url, update_time, status,
30
- word_count, summary, and volumes with chapters.
31
-
32
- :param html_str: Raw HTML of the book info page.
33
- :return: A dict containing book metadata.
34
- """
35
- doc = html.fromstring(html_str)
36
-
37
- book_name = doc.xpath('string(//h1[@id="bookName"])').strip()
38
-
39
- author = doc.xpath('string(//a[@class="writer-name"])').strip()
40
-
41
- book_id = doc.xpath('//a[@id="bookImg"]/@data-bid')[0]
42
- cover_url = f"https://bookcover.yuewen.com/qdbimg/349573/{book_id}/600.webp"
43
-
44
- ut = doc.xpath('string(//span[@class="update-time"])')
45
- ut = ut.replace("更新时间:", "").strip()
46
- if re.match(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$", ut):
47
- update_time = ut
48
- else:
49
- update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
50
-
51
- serial_status = doc.xpath('string(//p[@class="book-attribute"]/span[1])').strip()
52
-
53
- tags_elem = doc.xpath('//p[contains(@class,"all-label")]//a/text()')
54
- tags = [t.strip() for t in tags_elem if t.strip()]
55
-
56
- word_count = doc.xpath('string(//p[@class="count"]/em[1])').strip()
57
-
58
- summary_brief = doc.xpath('string(//p[@class="intro"])').strip()
59
-
60
- raw = doc.xpath('//p[@id="book-intro-detail"]//text()')
61
- summary = "\n".join(line.strip() for line in raw if line.strip())
62
-
63
- volumes: list[VolumeInfoDict] = []
64
- for vol in doc.xpath('//div[@id="allCatalog"]//div[@class="catalog-volume"]'):
65
- vol_name = vol.xpath('string(.//h3[@class="volume-name"])').strip()
66
- vol_name = vol_name.split(chr(183))[0].strip()
67
- chapters: list[ChapterInfoDict] = []
68
- for li in vol.xpath('.//ul[contains(@class,"volume-chapters")]/li'):
69
- a = li.xpath('.//a[@class="chapter-name"]')[0]
70
- title = a.text.strip()
71
- url = a.get("href")
72
- chapters.append(
73
- {"title": title, "url": url, "chapterId": _chapter_url_to_id(url)}
74
- )
75
- volumes.append({"volume_name": vol_name, "chapters": chapters})
76
-
77
- return {
78
- "book_name": book_name,
79
- "author": author,
80
- "cover_url": cover_url,
81
- "update_time": update_time,
82
- "word_count": word_count,
83
- "serial_status": serial_status,
84
- "tags": tags,
85
- "summary_brief": summary_brief,
86
- "summary": summary,
87
- "volumes": volumes,
88
- "extra": {},
89
- }