novel-downloader 1.5.0__py3-none-any.whl → 2.0.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.
Files changed (241) hide show
  1. novel_downloader/__init__.py +1 -1
  2. novel_downloader/cli/__init__.py +1 -3
  3. novel_downloader/cli/clean.py +21 -88
  4. novel_downloader/cli/config.py +26 -21
  5. novel_downloader/cli/download.py +77 -64
  6. novel_downloader/cli/export.py +16 -20
  7. novel_downloader/cli/main.py +1 -1
  8. novel_downloader/cli/search.py +62 -65
  9. novel_downloader/cli/ui.py +156 -0
  10. novel_downloader/config/__init__.py +8 -5
  11. novel_downloader/config/adapter.py +65 -105
  12. novel_downloader/config/{loader.py → file_io.py} +53 -26
  13. novel_downloader/core/__init__.py +1 -0
  14. novel_downloader/core/archived/deqixs/fetcher.py +115 -0
  15. novel_downloader/core/archived/deqixs/parser.py +132 -0
  16. novel_downloader/core/archived/deqixs/searcher.py +89 -0
  17. novel_downloader/core/{searchers/qidian.py → archived/qidian/searcher.py} +12 -20
  18. novel_downloader/core/archived/wanbengo/searcher.py +98 -0
  19. novel_downloader/core/archived/xshbook/searcher.py +93 -0
  20. novel_downloader/core/downloaders/__init__.py +3 -24
  21. novel_downloader/core/downloaders/base.py +49 -23
  22. novel_downloader/core/downloaders/common.py +191 -137
  23. novel_downloader/core/downloaders/qianbi.py +187 -146
  24. novel_downloader/core/downloaders/qidian.py +187 -141
  25. novel_downloader/core/downloaders/registry.py +4 -2
  26. novel_downloader/core/downloaders/signals.py +46 -0
  27. novel_downloader/core/exporters/__init__.py +3 -20
  28. novel_downloader/core/exporters/base.py +33 -37
  29. novel_downloader/core/exporters/common/__init__.py +1 -2
  30. novel_downloader/core/exporters/common/epub.py +15 -10
  31. novel_downloader/core/exporters/common/main_exporter.py +19 -12
  32. novel_downloader/core/exporters/common/txt.py +14 -9
  33. novel_downloader/core/exporters/epub_util.py +59 -29
  34. novel_downloader/core/exporters/linovelib/__init__.py +1 -0
  35. novel_downloader/core/exporters/linovelib/epub.py +23 -25
  36. novel_downloader/core/exporters/linovelib/main_exporter.py +8 -12
  37. novel_downloader/core/exporters/linovelib/txt.py +17 -11
  38. novel_downloader/core/exporters/qidian.py +2 -8
  39. novel_downloader/core/exporters/registry.py +4 -2
  40. novel_downloader/core/exporters/txt_util.py +7 -7
  41. novel_downloader/core/fetchers/__init__.py +54 -48
  42. novel_downloader/core/fetchers/aaatxt.py +83 -0
  43. novel_downloader/core/fetchers/{biquge/session.py → b520.py} +6 -11
  44. novel_downloader/core/fetchers/{base/session.py → base.py} +37 -46
  45. novel_downloader/core/fetchers/{biquge/browser.py → biquyuedu.py} +12 -17
  46. novel_downloader/core/fetchers/dxmwx.py +110 -0
  47. novel_downloader/core/fetchers/eightnovel.py +139 -0
  48. novel_downloader/core/fetchers/{esjzone/session.py → esjzone.py} +19 -12
  49. novel_downloader/core/fetchers/guidaye.py +85 -0
  50. novel_downloader/core/fetchers/hetushu.py +92 -0
  51. novel_downloader/core/fetchers/{qianbi/browser.py → i25zw.py} +19 -28
  52. novel_downloader/core/fetchers/ixdzs8.py +113 -0
  53. novel_downloader/core/fetchers/jpxs123.py +101 -0
  54. novel_downloader/core/fetchers/lewenn.py +83 -0
  55. novel_downloader/core/fetchers/{linovelib/session.py → linovelib.py} +12 -13
  56. novel_downloader/core/fetchers/piaotia.py +105 -0
  57. novel_downloader/core/fetchers/qbtr.py +101 -0
  58. novel_downloader/core/fetchers/{qianbi/session.py → qianbi.py} +5 -10
  59. novel_downloader/core/fetchers/{qidian/session.py → qidian.py} +46 -39
  60. novel_downloader/core/fetchers/quanben5.py +92 -0
  61. novel_downloader/core/fetchers/{base/rate_limiter.py → rate_limiter.py} +2 -2
  62. novel_downloader/core/fetchers/registry.py +5 -16
  63. novel_downloader/core/fetchers/{sfacg/session.py → sfacg.py} +7 -10
  64. novel_downloader/core/fetchers/shencou.py +106 -0
  65. novel_downloader/core/fetchers/shuhaige.py +84 -0
  66. novel_downloader/core/fetchers/tongrenquan.py +84 -0
  67. novel_downloader/core/fetchers/ttkan.py +95 -0
  68. novel_downloader/core/fetchers/wanbengo.py +83 -0
  69. novel_downloader/core/fetchers/xiaoshuowu.py +106 -0
  70. novel_downloader/core/fetchers/xiguashuwu.py +177 -0
  71. novel_downloader/core/fetchers/xs63b.py +171 -0
  72. novel_downloader/core/fetchers/xshbook.py +85 -0
  73. novel_downloader/core/fetchers/{yamibo/session.py → yamibo.py} +19 -12
  74. novel_downloader/core/fetchers/yibige.py +114 -0
  75. novel_downloader/core/interfaces/__init__.py +1 -9
  76. novel_downloader/core/interfaces/downloader.py +6 -2
  77. novel_downloader/core/interfaces/exporter.py +7 -7
  78. novel_downloader/core/interfaces/fetcher.py +4 -17
  79. novel_downloader/core/interfaces/parser.py +5 -6
  80. novel_downloader/core/interfaces/searcher.py +9 -1
  81. novel_downloader/core/parsers/__init__.py +49 -12
  82. novel_downloader/core/parsers/aaatxt.py +132 -0
  83. novel_downloader/core/parsers/b520.py +116 -0
  84. novel_downloader/core/parsers/base.py +63 -12
  85. novel_downloader/core/parsers/biquyuedu.py +133 -0
  86. novel_downloader/core/parsers/dxmwx.py +162 -0
  87. novel_downloader/core/parsers/eightnovel.py +224 -0
  88. novel_downloader/core/parsers/esjzone.py +61 -66
  89. novel_downloader/core/parsers/guidaye.py +128 -0
  90. novel_downloader/core/parsers/hetushu.py +139 -0
  91. novel_downloader/core/parsers/i25zw.py +137 -0
  92. novel_downloader/core/parsers/ixdzs8.py +186 -0
  93. novel_downloader/core/parsers/jpxs123.py +137 -0
  94. novel_downloader/core/parsers/lewenn.py +142 -0
  95. novel_downloader/core/parsers/linovelib.py +48 -64
  96. novel_downloader/core/parsers/piaotia.py +189 -0
  97. novel_downloader/core/parsers/qbtr.py +136 -0
  98. novel_downloader/core/parsers/qianbi.py +48 -50
  99. novel_downloader/core/parsers/qidian/book_info_parser.py +58 -59
  100. novel_downloader/core/parsers/qidian/chapter_encrypted.py +272 -330
  101. novel_downloader/core/parsers/qidian/chapter_normal.py +24 -55
  102. novel_downloader/core/parsers/qidian/main_parser.py +11 -38
  103. novel_downloader/core/parsers/qidian/utils/__init__.py +1 -0
  104. novel_downloader/core/parsers/qidian/utils/decryptor_fetcher.py +1 -1
  105. novel_downloader/core/parsers/qidian/utils/fontmap_recover.py +143 -0
  106. novel_downloader/core/parsers/qidian/utils/helpers.py +0 -4
  107. novel_downloader/core/parsers/quanben5.py +103 -0
  108. novel_downloader/core/parsers/registry.py +5 -16
  109. novel_downloader/core/parsers/sfacg.py +38 -45
  110. novel_downloader/core/parsers/shencou.py +215 -0
  111. novel_downloader/core/parsers/shuhaige.py +111 -0
  112. novel_downloader/core/parsers/tongrenquan.py +116 -0
  113. novel_downloader/core/parsers/ttkan.py +132 -0
  114. novel_downloader/core/parsers/wanbengo.py +191 -0
  115. novel_downloader/core/parsers/xiaoshuowu.py +173 -0
  116. novel_downloader/core/parsers/xiguashuwu.py +435 -0
  117. novel_downloader/core/parsers/xs63b.py +161 -0
  118. novel_downloader/core/parsers/xshbook.py +134 -0
  119. novel_downloader/core/parsers/yamibo.py +87 -131
  120. novel_downloader/core/parsers/yibige.py +166 -0
  121. novel_downloader/core/searchers/__init__.py +34 -3
  122. novel_downloader/core/searchers/aaatxt.py +107 -0
  123. novel_downloader/core/searchers/{biquge.py → b520.py} +29 -28
  124. novel_downloader/core/searchers/base.py +112 -36
  125. novel_downloader/core/searchers/dxmwx.py +105 -0
  126. novel_downloader/core/searchers/eightnovel.py +84 -0
  127. novel_downloader/core/searchers/esjzone.py +43 -25
  128. novel_downloader/core/searchers/hetushu.py +92 -0
  129. novel_downloader/core/searchers/i25zw.py +93 -0
  130. novel_downloader/core/searchers/ixdzs8.py +107 -0
  131. novel_downloader/core/searchers/jpxs123.py +107 -0
  132. novel_downloader/core/searchers/piaotia.py +100 -0
  133. novel_downloader/core/searchers/qbtr.py +106 -0
  134. novel_downloader/core/searchers/qianbi.py +74 -40
  135. novel_downloader/core/searchers/quanben5.py +144 -0
  136. novel_downloader/core/searchers/registry.py +24 -8
  137. novel_downloader/core/searchers/shuhaige.py +124 -0
  138. novel_downloader/core/searchers/tongrenquan.py +110 -0
  139. novel_downloader/core/searchers/ttkan.py +92 -0
  140. novel_downloader/core/searchers/xiaoshuowu.py +122 -0
  141. novel_downloader/core/searchers/xiguashuwu.py +95 -0
  142. novel_downloader/core/searchers/xs63b.py +104 -0
  143. novel_downloader/locales/en.json +31 -82
  144. novel_downloader/locales/zh.json +32 -83
  145. novel_downloader/models/__init__.py +21 -22
  146. novel_downloader/models/book.py +44 -0
  147. novel_downloader/models/config.py +4 -37
  148. novel_downloader/models/login.py +1 -1
  149. novel_downloader/models/search.py +5 -0
  150. novel_downloader/resources/config/settings.toml +8 -70
  151. novel_downloader/resources/json/xiguashuwu.json +718 -0
  152. novel_downloader/utils/__init__.py +13 -22
  153. novel_downloader/utils/chapter_storage.py +3 -2
  154. novel_downloader/utils/constants.py +4 -29
  155. novel_downloader/utils/cookies.py +6 -18
  156. novel_downloader/utils/crypto_utils/__init__.py +13 -0
  157. novel_downloader/utils/crypto_utils/aes_util.py +90 -0
  158. novel_downloader/utils/crypto_utils/aes_v1.py +619 -0
  159. novel_downloader/utils/crypto_utils/aes_v2.py +1143 -0
  160. novel_downloader/utils/{crypto_utils.py → crypto_utils/rc4.py} +3 -10
  161. novel_downloader/utils/epub/__init__.py +1 -1
  162. novel_downloader/utils/epub/constants.py +57 -16
  163. novel_downloader/utils/epub/documents.py +88 -194
  164. novel_downloader/utils/epub/models.py +0 -14
  165. novel_downloader/utils/epub/utils.py +63 -96
  166. novel_downloader/utils/file_utils/__init__.py +2 -23
  167. novel_downloader/utils/file_utils/io.py +3 -113
  168. novel_downloader/utils/file_utils/sanitize.py +0 -4
  169. novel_downloader/utils/fontocr.py +207 -0
  170. novel_downloader/utils/logger.py +8 -16
  171. novel_downloader/utils/network.py +2 -2
  172. novel_downloader/utils/state.py +4 -90
  173. novel_downloader/utils/text_utils/__init__.py +1 -7
  174. novel_downloader/utils/text_utils/diff_display.py +5 -7
  175. novel_downloader/utils/time_utils/__init__.py +5 -11
  176. novel_downloader/utils/time_utils/datetime_utils.py +20 -29
  177. novel_downloader/utils/time_utils/sleep_utils.py +4 -8
  178. novel_downloader/web/__init__.py +13 -0
  179. novel_downloader/web/components/__init__.py +11 -0
  180. novel_downloader/web/components/navigation.py +35 -0
  181. novel_downloader/web/main.py +66 -0
  182. novel_downloader/web/pages/__init__.py +17 -0
  183. novel_downloader/web/pages/download.py +78 -0
  184. novel_downloader/web/pages/progress.py +147 -0
  185. novel_downloader/web/pages/search.py +329 -0
  186. novel_downloader/web/services/__init__.py +17 -0
  187. novel_downloader/web/services/client_dialog.py +164 -0
  188. novel_downloader/web/services/cred_broker.py +113 -0
  189. novel_downloader/web/services/cred_models.py +35 -0
  190. novel_downloader/web/services/task_manager.py +264 -0
  191. novel_downloader-2.0.0.dist-info/METADATA +171 -0
  192. novel_downloader-2.0.0.dist-info/RECORD +210 -0
  193. {novel_downloader-1.5.0.dist-info → novel_downloader-2.0.0.dist-info}/entry_points.txt +1 -1
  194. novel_downloader/core/downloaders/biquge.py +0 -29
  195. novel_downloader/core/downloaders/esjzone.py +0 -29
  196. novel_downloader/core/downloaders/linovelib.py +0 -29
  197. novel_downloader/core/downloaders/sfacg.py +0 -29
  198. novel_downloader/core/downloaders/yamibo.py +0 -29
  199. novel_downloader/core/exporters/biquge.py +0 -22
  200. novel_downloader/core/exporters/esjzone.py +0 -22
  201. novel_downloader/core/exporters/qianbi.py +0 -22
  202. novel_downloader/core/exporters/sfacg.py +0 -22
  203. novel_downloader/core/exporters/yamibo.py +0 -22
  204. novel_downloader/core/fetchers/base/__init__.py +0 -14
  205. novel_downloader/core/fetchers/base/browser.py +0 -422
  206. novel_downloader/core/fetchers/biquge/__init__.py +0 -14
  207. novel_downloader/core/fetchers/esjzone/__init__.py +0 -14
  208. novel_downloader/core/fetchers/esjzone/browser.py +0 -209
  209. novel_downloader/core/fetchers/linovelib/__init__.py +0 -14
  210. novel_downloader/core/fetchers/linovelib/browser.py +0 -198
  211. novel_downloader/core/fetchers/qianbi/__init__.py +0 -14
  212. novel_downloader/core/fetchers/qidian/__init__.py +0 -14
  213. novel_downloader/core/fetchers/qidian/browser.py +0 -326
  214. novel_downloader/core/fetchers/sfacg/__init__.py +0 -14
  215. novel_downloader/core/fetchers/sfacg/browser.py +0 -194
  216. novel_downloader/core/fetchers/yamibo/__init__.py +0 -14
  217. novel_downloader/core/fetchers/yamibo/browser.py +0 -234
  218. novel_downloader/core/parsers/biquge.py +0 -139
  219. novel_downloader/models/chapter.py +0 -25
  220. novel_downloader/models/types.py +0 -13
  221. novel_downloader/tui/__init__.py +0 -7
  222. novel_downloader/tui/app.py +0 -32
  223. novel_downloader/tui/main.py +0 -17
  224. novel_downloader/tui/screens/__init__.py +0 -14
  225. novel_downloader/tui/screens/home.py +0 -198
  226. novel_downloader/tui/screens/login.py +0 -74
  227. novel_downloader/tui/styles/home_layout.tcss +0 -79
  228. novel_downloader/tui/widgets/richlog_handler.py +0 -24
  229. novel_downloader/utils/cache.py +0 -24
  230. novel_downloader/utils/fontocr/__init__.py +0 -22
  231. novel_downloader/utils/fontocr/hash_store.py +0 -280
  232. novel_downloader/utils/fontocr/hash_utils.py +0 -103
  233. novel_downloader/utils/fontocr/model_loader.py +0 -69
  234. novel_downloader/utils/fontocr/ocr_v1.py +0 -315
  235. novel_downloader/utils/fontocr/ocr_v2.py +0 -764
  236. novel_downloader/utils/fontocr/ocr_v3.py +0 -744
  237. novel_downloader-1.5.0.dist-info/METADATA +0 -196
  238. novel_downloader-1.5.0.dist-info/RECORD +0 -164
  239. {novel_downloader-1.5.0.dist-info → novel_downloader-2.0.0.dist-info}/WHEEL +0 -0
  240. {novel_downloader-1.5.0.dist-info → novel_downloader-2.0.0.dist-info}/licenses/LICENSE +0 -0
  241. {novel_downloader-1.5.0.dist-info → novel_downloader-2.0.0.dist-info}/top_level.txt +0 -0
@@ -3,45 +3,36 @@
3
3
  novel_downloader.utils
4
4
  ----------------------
5
5
 
6
+ A collection of helper functions and classes.
6
7
  """
7
8
 
8
9
  __all__ = [
9
10
  "ChapterStorage",
10
11
  "TextCleaner",
11
- "resolve_cookies",
12
- "parse_cookie_expires",
13
- "find_cookie_value",
12
+ "parse_cookies",
13
+ "get_cookie_value",
14
14
  "rc4_crypt",
15
15
  "sanitize_filename",
16
- "save_as_json",
17
- "save_as_txt",
18
- "read_text_file",
19
- "read_json_file",
20
- "read_binary_file",
16
+ "write_file",
21
17
  "download",
22
18
  "get_cleaner",
23
19
  "content_prefix",
24
20
  "truncate_half_lines",
25
21
  "diff_inline_display",
26
- "calculate_time_difference",
27
- "async_sleep_with_random_delay",
28
- "sleep_with_random_delay",
22
+ "time_diff",
23
+ "async_jitter_sleep",
24
+ "jitter_sleep",
29
25
  ]
30
26
 
31
27
  from .chapter_storage import ChapterStorage
32
28
  from .cookies import (
33
- find_cookie_value,
34
- parse_cookie_expires,
35
- resolve_cookies,
29
+ get_cookie_value,
30
+ parse_cookies,
36
31
  )
37
32
  from .crypto_utils import rc4_crypt
38
33
  from .file_utils import (
39
- read_binary_file,
40
- read_json_file,
41
- read_text_file,
42
34
  sanitize_filename,
43
- save_as_json,
44
- save_as_txt,
35
+ write_file,
45
36
  )
46
37
  from .network import download
47
38
  from .text_utils import (
@@ -52,7 +43,7 @@ from .text_utils import (
52
43
  truncate_half_lines,
53
44
  )
54
45
  from .time_utils import (
55
- async_sleep_with_random_delay,
56
- calculate_time_difference,
57
- sleep_with_random_delay,
46
+ async_jitter_sleep,
47
+ jitter_sleep,
48
+ time_diff,
58
49
  )
@@ -3,10 +3,11 @@
3
3
  novel_downloader.utils.chapter_storage
4
4
  --------------------------------------
5
5
 
6
- Storage module for managing novel chapters in
7
- either JSON file form or an SQLite database.
6
+ Storage module for managing novel chapters in an SQLite database.
8
7
  """
9
8
 
9
+ __all__ = ["ChapterStorage"]
10
+
10
11
  import contextlib
11
12
  import json
12
13
  import sqlite3
@@ -16,7 +16,7 @@ from platformdirs import user_config_path
16
16
  # -----------------------------------------------------------------------------
17
17
  PACKAGE_NAME = "novel_downloader" # Python package name
18
18
  APP_NAME = "NovelDownloader" # Display name
19
- APP_DIR_NAME = "novel_downloader" # Directory name for platformdirs
19
+ APP_DIR_NAME = PACKAGE_NAME # Directory name for platformdirs
20
20
  LOGGER_NAME = PACKAGE_NAME # Root logger name
21
21
 
22
22
  # -----------------------------------------------------------------------------
@@ -33,20 +33,17 @@ LOGGER_DIR = WORK_DIR / "logs"
33
33
  JS_SCRIPT_DIR = BASE_CONFIG_DIR / "scripts"
34
34
  DATA_DIR = BASE_CONFIG_DIR / "data"
35
35
  CONFIG_DIR = BASE_CONFIG_DIR / "config"
36
- MODEL_CACHE_DIR = BASE_CONFIG_DIR / "models"
37
36
 
38
37
  # -----------------------------------------------------------------------------
39
38
  # Default file paths
40
39
  # -----------------------------------------------------------------------------
41
40
  STATE_FILE = DATA_DIR / "state.json"
42
41
  SETTING_FILE = CONFIG_DIR / "settings.json"
43
- DEFAULT_USER_DATA_DIR = DATA_DIR / "browser_data"
44
42
 
45
43
 
46
44
  # -----------------------------------------------------------------------------
47
45
  # Default preferences & headers
48
46
  # -----------------------------------------------------------------------------
49
- DEFAULT_USER_PROFILE_NAME = "Profile_1"
50
47
  DEFAULT_IMAGE_SUFFIX = ".jpg"
51
48
 
52
49
  DEFAULT_USER_AGENT = (
@@ -90,33 +87,11 @@ VOLUME_BORDER_IMAGE_PATH = files("novel_downloader.resources.images").joinpath(
90
87
  LINOVELIB_FONT_MAP_PATH = files("novel_downloader.resources.json").joinpath(
91
88
  "linovelib_font_map.json"
92
89
  )
90
+ XIGUASHUWU_FONT_MAP_PATH = files("novel_downloader.resources.json").joinpath(
91
+ "xiguashuwu.json"
92
+ )
93
93
 
94
94
  # JavaScript
95
95
  QD_DECRYPT_SCRIPT_PATH = files("novel_downloader.resources.js_scripts").joinpath(
96
96
  "qidian_decrypt_node.js"
97
97
  )
98
-
99
- # ---------------------------------------------------------------------
100
- # Pretrained model registry (e.g. used in font recovery or OCR)
101
- # ---------------------------------------------------------------------
102
-
103
- # Hugging Face model repo for character recognition
104
- REC_CHAR_MODEL_REPO = "saudadez/rec_chinese_char"
105
-
106
- # Required files to be downloaded for the model
107
- REC_CHAR_MODEL_FILES = [
108
- "inference.pdmodel",
109
- "inference.pdiparams",
110
- "rec_custom_keys.txt",
111
- "char_freq.json",
112
- ]
113
-
114
- REC_CHAR_VECTOR_FILES = [
115
- "char_vectors.npy",
116
- "char_vectors.txt",
117
- ]
118
-
119
- REC_IMAGE_SHAPE_MAP = {
120
- "v1.0": "3,32,32",
121
- "v2.0": "3,48,48",
122
- }
@@ -6,20 +6,21 @@ novel_downloader.utils.cookies
6
6
  Utility for normalizing cookie input from user configuration.
7
7
  """
8
8
 
9
+ __all__ = ["parse_cookies", "get_cookie_value"]
10
+
9
11
  import json
10
12
  from collections.abc import Mapping
11
- from email.utils import parsedate_to_datetime
12
13
  from http.cookies import SimpleCookie
13
14
  from pathlib import Path
14
15
 
15
16
 
16
- def resolve_cookies(cookies: str | Mapping[str, str]) -> dict[str, str]:
17
+ def parse_cookies(cookies: str | Mapping[str, str]) -> dict[str, str]:
17
18
  """
18
19
  Parse cookies from a string or dictionary into a standard dictionary.
19
20
 
20
21
  Supports input like:
21
- - "key1=value1; key2=value2"
22
- - {"key1": "value1", "key2": "value2"}
22
+ * `"key1=value1; key2=value2"`
23
+ * `{"key1": "value1", "key2": "value2"}`
23
24
 
24
25
  :param cookies: Cookie string or dict-like object (e.g., from config)
25
26
  :return: A normalized cookie dictionary (key -> value)
@@ -35,20 +36,7 @@ def resolve_cookies(cookies: str | Mapping[str, str]) -> dict[str, str]:
35
36
  raise TypeError("Unsupported cookie format: must be str or dict-like")
36
37
 
37
38
 
38
- def parse_cookie_expires(value: str | None) -> int:
39
- if not value:
40
- return -1
41
- try:
42
- return int(value)
43
- except (ValueError, TypeError):
44
- try:
45
- dt = parsedate_to_datetime(value)
46
- return int(dt.timestamp())
47
- except Exception:
48
- return -1
49
-
50
-
51
- def find_cookie_value(state_files: list[Path], key: str) -> str:
39
+ def get_cookie_value(state_files: list[Path], key: str) -> str:
52
40
  for state_file in state_files:
53
41
  try:
54
42
  with state_file.open("r", encoding="utf-8") as f:
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ novel_downloader.utils.crypto_utils
4
+ -----------------------------------
5
+
6
+ Generic cryptographic utilities
7
+ """
8
+
9
+ __all__ = [
10
+ "rc4_crypt",
11
+ ]
12
+
13
+ from .rc4 import rc4_crypt
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ novel_downloader.utils.crypto_utils.aes_util
4
+ --------------------------------------------
5
+
6
+ AES decrypt functions.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ __all__ = ["aes_cbc_decrypt"]
12
+
13
+ from typing import Any
14
+
15
+ _BLOCK = 16
16
+ _VALID_KEY_SIZES = (16, 24, 32)
17
+
18
+
19
+ def _as_bytes(name: str, b: Any) -> bytes:
20
+ if isinstance(b, bytes):
21
+ return b
22
+ if isinstance(b, bytearray | memoryview):
23
+ return bytes(b)
24
+ raise TypeError(f"{name} must be bytes-like, got {type(b).__name__}")
25
+
26
+
27
+ def _validate_inputs(key: bytes, iv: bytes, data: bytes) -> None:
28
+ if len(iv) != _BLOCK:
29
+ raise ValueError(f"iv must be {_BLOCK} bytes, got {len(iv)}")
30
+ if len(key) not in _VALID_KEY_SIZES:
31
+ raise ValueError(
32
+ f"key length must be one of {_VALID_KEY_SIZES} bytes, got {len(key)}"
33
+ )
34
+ if len(data) % _BLOCK != 0:
35
+ raise ValueError(
36
+ f"ciphertext length must be a multiple of {_BLOCK} bytes, got {len(data)}"
37
+ )
38
+
39
+
40
+ try:
41
+ from Crypto.Cipher import AES as _PyAES
42
+ from Crypto.Util.Padding import unpad as _py_unpad
43
+
44
+ def aes_cbc_decrypt(
45
+ key: bytes, iv: bytes, data: bytes, block_size: int = _BLOCK
46
+ ) -> bytes:
47
+ """
48
+ AES-CBC decrypt + PKCS#7 unpad (PyCryptodome).
49
+
50
+ :param key: AES key (16/24/32 bytes)
51
+ :param iv: Initialization vector (16 bytes)
52
+ :param data: Ciphertext, length multiple of 16
53
+ :return: Plaintext bytes (unpadded)
54
+ :raises TypeError, ValueError: on invalid inputs
55
+ """
56
+ key_b = _as_bytes("key", key)
57
+ iv_b = _as_bytes("iv", iv)
58
+ data_b = _as_bytes("data", data)
59
+ if not data_b:
60
+ return b""
61
+ _validate_inputs(key_b, iv_b, data_b)
62
+ pt = _PyAES.new(key_b, _PyAES.MODE_CBC, iv_b).decrypt(data_b)
63
+ return _py_unpad(pt, block_size, style="pkcs7") # type: ignore[no-any-return]
64
+
65
+ except ImportError:
66
+ print(
67
+ "[crypto_utils] Falling back to pure-Python AES_CBC.\n"
68
+ "Tip: pip install pycryptodome for ~800x faster speed."
69
+ )
70
+ from novel_downloader.utils.crypto_utils.aes_v2 import AES_CBC
71
+
72
+ def aes_cbc_decrypt(
73
+ key: bytes, iv: bytes, data: bytes, block_size: int = _BLOCK
74
+ ) -> bytes:
75
+ """
76
+ AES-CBC decrypt + PKCS#7 unpad (handled by AES_CBC internally).
77
+
78
+ :param key: AES key (16/24/32 bytes)
79
+ :param iv: Initialization vector (16 bytes)
80
+ :param data: Ciphertext, length multiple of 16
81
+ :return: Plaintext bytes (unpadded)
82
+ :raises TypeError, ValueError: on invalid inputs
83
+ """
84
+ key_b = _as_bytes("key", key)
85
+ iv_b = _as_bytes("iv", iv)
86
+ data_b = _as_bytes("data", data)
87
+ if not data_b:
88
+ return b""
89
+ _validate_inputs(key_b, iv_b, data_b)
90
+ return AES_CBC(key_b, iv_b).decrypt_padded(data_b, block_size)