novel-downloader 2.0.0__py3-none-any.whl → 2.0.2__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 (137) hide show
  1. novel_downloader/__init__.py +1 -1
  2. novel_downloader/cli/download.py +14 -11
  3. novel_downloader/cli/export.py +19 -19
  4. novel_downloader/cli/ui.py +35 -8
  5. novel_downloader/config/adapter.py +216 -153
  6. novel_downloader/core/__init__.py +5 -6
  7. novel_downloader/core/archived/deqixs/fetcher.py +1 -28
  8. novel_downloader/core/downloaders/__init__.py +2 -0
  9. novel_downloader/core/downloaders/base.py +34 -85
  10. novel_downloader/core/downloaders/common.py +147 -171
  11. novel_downloader/core/downloaders/qianbi.py +30 -64
  12. novel_downloader/core/downloaders/qidian.py +157 -184
  13. novel_downloader/core/downloaders/qqbook.py +292 -0
  14. novel_downloader/core/downloaders/registry.py +2 -2
  15. novel_downloader/core/exporters/__init__.py +2 -0
  16. novel_downloader/core/exporters/base.py +37 -59
  17. novel_downloader/core/exporters/common.py +620 -0
  18. novel_downloader/core/exporters/linovelib.py +47 -0
  19. novel_downloader/core/exporters/qidian.py +41 -12
  20. novel_downloader/core/exporters/qqbook.py +28 -0
  21. novel_downloader/core/exporters/registry.py +2 -2
  22. novel_downloader/core/fetchers/__init__.py +4 -2
  23. novel_downloader/core/fetchers/aaatxt.py +2 -22
  24. novel_downloader/core/fetchers/b520.py +3 -23
  25. novel_downloader/core/fetchers/base.py +80 -105
  26. novel_downloader/core/fetchers/biquyuedu.py +2 -22
  27. novel_downloader/core/fetchers/dxmwx.py +10 -22
  28. novel_downloader/core/fetchers/esjzone.py +6 -29
  29. novel_downloader/core/fetchers/guidaye.py +2 -22
  30. novel_downloader/core/fetchers/hetushu.py +9 -29
  31. novel_downloader/core/fetchers/i25zw.py +2 -16
  32. novel_downloader/core/fetchers/ixdzs8.py +2 -16
  33. novel_downloader/core/fetchers/jpxs123.py +2 -16
  34. novel_downloader/core/fetchers/lewenn.py +2 -22
  35. novel_downloader/core/fetchers/linovelib.py +4 -20
  36. novel_downloader/core/fetchers/{eightnovel.py → n8novel.py} +12 -40
  37. novel_downloader/core/fetchers/piaotia.py +2 -16
  38. novel_downloader/core/fetchers/qbtr.py +2 -16
  39. novel_downloader/core/fetchers/qianbi.py +1 -20
  40. novel_downloader/core/fetchers/qidian.py +27 -68
  41. novel_downloader/core/fetchers/qqbook.py +177 -0
  42. novel_downloader/core/fetchers/quanben5.py +9 -29
  43. novel_downloader/core/fetchers/rate_limiter.py +22 -53
  44. novel_downloader/core/fetchers/sfacg.py +3 -16
  45. novel_downloader/core/fetchers/shencou.py +2 -16
  46. novel_downloader/core/fetchers/shuhaige.py +2 -22
  47. novel_downloader/core/fetchers/tongrenquan.py +2 -22
  48. novel_downloader/core/fetchers/ttkan.py +3 -14
  49. novel_downloader/core/fetchers/wanbengo.py +2 -22
  50. novel_downloader/core/fetchers/xiaoshuowu.py +2 -16
  51. novel_downloader/core/fetchers/xiguashuwu.py +4 -20
  52. novel_downloader/core/fetchers/xs63b.py +3 -15
  53. novel_downloader/core/fetchers/xshbook.py +2 -22
  54. novel_downloader/core/fetchers/yamibo.py +4 -28
  55. novel_downloader/core/fetchers/yibige.py +13 -26
  56. novel_downloader/core/interfaces/exporter.py +19 -7
  57. novel_downloader/core/interfaces/fetcher.py +23 -49
  58. novel_downloader/core/interfaces/parser.py +2 -2
  59. novel_downloader/core/parsers/__init__.py +4 -2
  60. novel_downloader/core/parsers/b520.py +2 -2
  61. novel_downloader/core/parsers/base.py +5 -39
  62. novel_downloader/core/parsers/esjzone.py +3 -3
  63. novel_downloader/core/parsers/{eightnovel.py → n8novel.py} +7 -7
  64. novel_downloader/core/parsers/qidian.py +717 -0
  65. novel_downloader/core/parsers/qqbook.py +709 -0
  66. novel_downloader/core/parsers/xiguashuwu.py +8 -15
  67. novel_downloader/core/searchers/__init__.py +2 -2
  68. novel_downloader/core/searchers/b520.py +1 -1
  69. novel_downloader/core/searchers/base.py +2 -2
  70. novel_downloader/core/searchers/{eightnovel.py → n8novel.py} +5 -5
  71. novel_downloader/locales/en.json +3 -3
  72. novel_downloader/locales/zh.json +3 -3
  73. novel_downloader/models/__init__.py +2 -0
  74. novel_downloader/models/book.py +1 -0
  75. novel_downloader/models/config.py +12 -0
  76. novel_downloader/resources/config/settings.toml +23 -5
  77. novel_downloader/resources/js_scripts/expr_to_json.js +14 -0
  78. novel_downloader/resources/js_scripts/qidian_decrypt_node.js +21 -16
  79. novel_downloader/resources/js_scripts/qq_decrypt_node.js +92 -0
  80. novel_downloader/utils/__init__.py +0 -2
  81. novel_downloader/utils/chapter_storage.py +2 -3
  82. novel_downloader/utils/constants.py +7 -3
  83. novel_downloader/utils/cookies.py +32 -17
  84. novel_downloader/utils/crypto_utils/__init__.py +0 -6
  85. novel_downloader/utils/crypto_utils/aes_util.py +1 -1
  86. novel_downloader/utils/crypto_utils/rc4.py +40 -50
  87. novel_downloader/utils/epub/__init__.py +2 -3
  88. novel_downloader/utils/epub/builder.py +6 -6
  89. novel_downloader/utils/epub/constants.py +1 -6
  90. novel_downloader/utils/epub/documents.py +7 -7
  91. novel_downloader/utils/epub/models.py +8 -8
  92. novel_downloader/utils/epub/utils.py +10 -10
  93. novel_downloader/utils/file_utils/io.py +48 -73
  94. novel_downloader/utils/file_utils/normalize.py +1 -7
  95. novel_downloader/utils/file_utils/sanitize.py +4 -11
  96. novel_downloader/utils/fontocr/__init__.py +13 -0
  97. novel_downloader/utils/{fontocr.py → fontocr/core.py} +72 -61
  98. novel_downloader/utils/fontocr/loader.py +52 -0
  99. novel_downloader/utils/logger.py +80 -56
  100. novel_downloader/utils/network.py +16 -40
  101. novel_downloader/utils/node_decryptor/__init__.py +13 -0
  102. novel_downloader/utils/node_decryptor/decryptor.py +342 -0
  103. novel_downloader/{core/parsers/qidian/utils → utils/node_decryptor}/decryptor_fetcher.py +5 -6
  104. novel_downloader/utils/text_utils/text_cleaner.py +39 -30
  105. novel_downloader/utils/text_utils/truncate_utils.py +3 -14
  106. novel_downloader/utils/time_utils/sleep_utils.py +53 -43
  107. novel_downloader/web/main.py +1 -1
  108. novel_downloader/web/pages/download.py +1 -1
  109. novel_downloader/web/pages/search.py +4 -4
  110. novel_downloader/web/services/task_manager.py +2 -0
  111. {novel_downloader-2.0.0.dist-info → novel_downloader-2.0.2.dist-info}/METADATA +5 -1
  112. novel_downloader-2.0.2.dist-info/RECORD +203 -0
  113. novel_downloader/core/exporters/common/__init__.py +0 -11
  114. novel_downloader/core/exporters/common/epub.py +0 -198
  115. novel_downloader/core/exporters/common/main_exporter.py +0 -64
  116. novel_downloader/core/exporters/common/txt.py +0 -146
  117. novel_downloader/core/exporters/epub_util.py +0 -215
  118. novel_downloader/core/exporters/linovelib/__init__.py +0 -11
  119. novel_downloader/core/exporters/linovelib/epub.py +0 -349
  120. novel_downloader/core/exporters/linovelib/main_exporter.py +0 -66
  121. novel_downloader/core/exporters/linovelib/txt.py +0 -139
  122. novel_downloader/core/exporters/txt_util.py +0 -67
  123. novel_downloader/core/parsers/qidian/__init__.py +0 -10
  124. novel_downloader/core/parsers/qidian/book_info_parser.py +0 -89
  125. novel_downloader/core/parsers/qidian/chapter_encrypted.py +0 -470
  126. novel_downloader/core/parsers/qidian/chapter_normal.py +0 -126
  127. novel_downloader/core/parsers/qidian/chapter_router.py +0 -68
  128. novel_downloader/core/parsers/qidian/main_parser.py +0 -101
  129. novel_downloader/core/parsers/qidian/utils/__init__.py +0 -30
  130. novel_downloader/core/parsers/qidian/utils/fontmap_recover.py +0 -143
  131. novel_downloader/core/parsers/qidian/utils/helpers.py +0 -110
  132. novel_downloader/core/parsers/qidian/utils/node_decryptor.py +0 -175
  133. novel_downloader-2.0.0.dist-info/RECORD +0 -210
  134. {novel_downloader-2.0.0.dist-info → novel_downloader-2.0.2.dist-info}/WHEEL +0 -0
  135. {novel_downloader-2.0.0.dist-info → novel_downloader-2.0.2.dist-info}/entry_points.txt +0 -0
  136. {novel_downloader-2.0.0.dist-info → novel_downloader-2.0.2.dist-info}/licenses/LICENSE +0 -0
  137. {novel_downloader-2.0.0.dist-info → novel_downloader-2.0.2.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,7 @@ novel_downloader
6
6
  Core package for the Novel Downloader project.
7
7
  """
8
8
 
9
- __version__ = "2.0.0"
9
+ __version__ = "2.0.2"
10
10
 
11
11
  __author__ = "Saudade Z"
12
12
  __email__ = "saudadez217@gmail.com"
@@ -155,7 +155,7 @@ async def _download(
155
155
  exporter_cfg = adapter.get_exporter_config()
156
156
  login_cfg = adapter.get_login_config()
157
157
  log_level = adapter.get_log_level()
158
- setup_logging(log_level=log_level)
158
+ setup_logging(console_level=log_level)
159
159
 
160
160
  parser = get_parser(site, parser_cfg)
161
161
  exporter = None
@@ -178,7 +178,14 @@ async def _download(
178
178
 
179
179
  for book in valid_books:
180
180
  ui.info(t("download_downloading", book_id=book["book_id"], site=site))
181
- await downloader.download(book, progress_hook=_print_progress)
181
+
182
+ hook, close = ui.create_progress_hook(
183
+ prefix=t("download_progress_prefix"), unit="chapters"
184
+ )
185
+ try:
186
+ await downloader.download(book, progress_hook=hook)
187
+ finally:
188
+ close()
182
189
 
183
190
  if not no_export and exporter is not None:
184
191
  await asyncio.to_thread(exporter.export, book["book_id"])
@@ -186,6 +193,9 @@ async def _download(
186
193
  if downloader_cfg.login_required and fetcher.is_logged_in:
187
194
  await fetcher.save_state()
188
195
 
196
+ if exporter is not None:
197
+ exporter.close()
198
+
189
199
 
190
200
  async def _prompt_login_fields(
191
201
  fields: list[LoginField],
@@ -214,8 +224,8 @@ async def _prompt_login_fields(
214
224
  ui.info(t("login_use_config"))
215
225
  continue
216
226
 
217
- value: str | dict[str, str]
218
- while True:
227
+ value: str | dict[str, str] = ""
228
+ for _ in range(5):
219
229
  if field.type == "password":
220
230
  value = ui.prompt_password(t("login_enter_password"))
221
231
  elif field.type == "cookie":
@@ -235,10 +245,3 @@ async def _prompt_login_fields(
235
245
  result[field.name] = value
236
246
 
237
247
  return result
238
-
239
-
240
- async def _print_progress(done: int, total: int) -> None:
241
- """Progress hook passed into the downloader."""
242
- ui.print_progress(
243
- done, total, prefix=t("download_progress_prefix"), unit="chapters"
244
- )
@@ -55,22 +55,22 @@ def handle_export(args: Namespace) -> None:
55
55
  adapter = ConfigAdapter(config=config_data, site=site)
56
56
  exporter_cfg = adapter.get_exporter_config()
57
57
  log_level = adapter.get_log_level()
58
- exporter = get_exporter(site, exporter_cfg)
59
- setup_logging(log_level=log_level)
60
-
61
- for book_id in book_ids:
62
- ui.info(t("export_processing", book_id=book_id, format=export_format))
63
-
64
- if export_format in {"txt", "all"}:
65
- try:
66
- exporter.export_as_txt(book_id)
67
- ui.success(t("export_success_txt", book_id=book_id))
68
- except Exception as e:
69
- ui.error(t("export_failed_txt", book_id=book_id, err=str(e)))
70
-
71
- if export_format in {"epub", "all"}:
72
- try:
73
- exporter.export_as_epub(book_id)
74
- ui.success(t("export_success_epub", book_id=book_id))
75
- except Exception as e:
76
- ui.error(t("export_failed_epub", book_id=book_id, err=str(e)))
58
+ setup_logging(console_level=log_level)
59
+
60
+ with get_exporter(site, exporter_cfg) as exporter:
61
+ for book_id in book_ids:
62
+ ui.info(t("export_processing", book_id=book_id, format=export_format))
63
+
64
+ if export_format in {"txt", "all"}:
65
+ try:
66
+ exporter.export_as_txt(book_id)
67
+ ui.success(t("export_success_txt", book_id=book_id))
68
+ except Exception as e:
69
+ ui.error(t("export_failed_txt", book_id=book_id, err=str(e)))
70
+
71
+ if export_format in {"epub", "all"}:
72
+ try:
73
+ exporter.export_as_epub(book_id)
74
+ ui.success(t("export_success_epub", book_id=book_id))
75
+ except Exception as e:
76
+ ui.error(t("export_failed_epub", book_id=book_id, err=str(e)))
@@ -7,19 +7,20 @@ A small set of Rich-based helpers to keep CLI presentation and prompts
7
7
  consistent across subcommands.
8
8
 
9
9
  Public API:
10
- - info, success, warn, error
11
- - confirm
12
- - prompt, prompt_password
13
- - render_table
14
- - select_index
15
- - print_progress
10
+ * info, success, warn, error
11
+ * confirm
12
+ * prompt, prompt_password
13
+ * render_table
14
+ * select_index
15
+ * print_progress
16
16
  """
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- from collections.abc import Iterable, Sequence
20
+ from collections.abc import Awaitable, Callable, Iterable, Sequence
21
21
 
22
22
  from rich.console import Console
23
+ from rich.progress import Progress, TaskID
23
24
  from rich.prompt import Confirm, Prompt
24
25
  from rich.table import Table
25
26
 
@@ -71,7 +72,7 @@ def prompt(message: str, *, default: str | None = None) -> str:
71
72
  :return: The user's input.
72
73
  """
73
74
  try:
74
- result: str = Prompt.ask(message, default=default or "")
75
+ result: str = Prompt.ask(message, default=default or "", show_default=False)
75
76
  return result
76
77
  except (KeyboardInterrupt, EOFError):
77
78
  warn("Cancelled.")
@@ -154,3 +155,29 @@ def print_progress(
154
155
  total = max(1, total)
155
156
  pct = done / total * 100.0
156
157
  _CONSOLE.print(f"[dim]{prefix}[/] {done}/{total} {unit} ({pct:.2f}%)")
158
+
159
+
160
+ def create_progress_hook(
161
+ prefix: str = "Progress",
162
+ unit: str = "item",
163
+ ) -> tuple[Callable[[int, int], Awaitable[None]], Callable[[], None]]:
164
+ progress = Progress(console=_CONSOLE)
165
+ task_id: TaskID | None = None
166
+
167
+ async def hook(done: int, total: int) -> None:
168
+ nonlocal task_id
169
+ if task_id is None:
170
+ progress.start()
171
+ task_id = progress.add_task(f"[cyan]{prefix}[/]", total=max(1, total))
172
+
173
+ progress.update(
174
+ task_id,
175
+ completed=done,
176
+ total=max(1, total),
177
+ description=f"{prefix} ({done}/{total} {unit})",
178
+ )
179
+
180
+ def close() -> None:
181
+ progress.stop()
182
+
183
+ return hook, close