novel-downloader 1.4.5__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 (276) hide show
  1. novel_downloader/__init__.py +1 -1
  2. novel_downloader/cli/__init__.py +2 -4
  3. novel_downloader/cli/clean.py +21 -88
  4. novel_downloader/cli/config.py +27 -104
  5. novel_downloader/cli/download.py +78 -66
  6. novel_downloader/cli/export.py +20 -21
  7. novel_downloader/cli/main.py +3 -1
  8. novel_downloader/cli/search.py +120 -0
  9. novel_downloader/cli/ui.py +156 -0
  10. novel_downloader/config/__init__.py +10 -14
  11. novel_downloader/config/adapter.py +195 -99
  12. novel_downloader/config/{loader.py → file_io.py} +53 -27
  13. novel_downloader/core/__init__.py +14 -13
  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/archived/qidian/searcher.py +79 -0
  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 +8 -30
  21. novel_downloader/core/downloaders/base.py +182 -30
  22. novel_downloader/core/downloaders/common.py +217 -384
  23. novel_downloader/core/downloaders/qianbi.py +332 -4
  24. novel_downloader/core/downloaders/qidian.py +250 -290
  25. novel_downloader/core/downloaders/registry.py +69 -0
  26. novel_downloader/core/downloaders/signals.py +46 -0
  27. novel_downloader/core/exporters/__init__.py +8 -26
  28. novel_downloader/core/exporters/base.py +107 -31
  29. novel_downloader/core/exporters/common/__init__.py +3 -4
  30. novel_downloader/core/exporters/common/epub.py +92 -171
  31. novel_downloader/core/exporters/common/main_exporter.py +14 -67
  32. novel_downloader/core/exporters/common/txt.py +90 -86
  33. novel_downloader/core/exporters/epub_util.py +184 -1327
  34. novel_downloader/core/exporters/linovelib/__init__.py +3 -2
  35. novel_downloader/core/exporters/linovelib/epub.py +165 -222
  36. novel_downloader/core/exporters/linovelib/main_exporter.py +10 -71
  37. novel_downloader/core/exporters/linovelib/txt.py +76 -66
  38. novel_downloader/core/exporters/qidian.py +15 -11
  39. novel_downloader/core/exporters/registry.py +55 -0
  40. novel_downloader/core/exporters/txt_util.py +67 -0
  41. novel_downloader/core/fetchers/__init__.py +57 -56
  42. novel_downloader/core/fetchers/aaatxt.py +83 -0
  43. novel_downloader/core/fetchers/{biquge/session.py → b520.py} +10 -10
  44. novel_downloader/core/fetchers/{base/session.py → base.py} +63 -47
  45. novel_downloader/core/fetchers/biquyuedu.py +83 -0
  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} +23 -11
  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} +22 -26
  52. novel_downloader/core/fetchers/ixdzs8.py +113 -0
  53. novel_downloader/core/fetchers/jpxs123.py +101 -0
  54. novel_downloader/core/fetchers/{biquge/browser.py → lewenn.py} +15 -15
  55. novel_downloader/core/fetchers/{linovelib/session.py → linovelib.py} +16 -12
  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} +9 -9
  59. novel_downloader/core/fetchers/{qidian/session.py → qidian.py} +55 -40
  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 +60 -0
  63. novel_downloader/core/fetchers/{sfacg/session.py → sfacg.py} +11 -9
  64. novel_downloader/core/fetchers/shencou.py +106 -0
  65. novel_downloader/core/fetchers/{common/browser.py → shuhaige.py} +24 -19
  66. novel_downloader/core/fetchers/tongrenquan.py +84 -0
  67. novel_downloader/core/fetchers/ttkan.py +95 -0
  68. novel_downloader/core/fetchers/{common/session.py → wanbengo.py} +21 -17
  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} +23 -11
  74. novel_downloader/core/fetchers/yibige.py +114 -0
  75. novel_downloader/core/interfaces/__init__.py +8 -14
  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 +26 -0
  81. novel_downloader/core/parsers/__init__.py +58 -22
  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/main_parser.py → esjzone.py} +67 -67
  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/main_parser.py → linovelib.py} +54 -65
  96. novel_downloader/core/parsers/piaotia.py +189 -0
  97. novel_downloader/core/parsers/qbtr.py +136 -0
  98. novel_downloader/core/parsers/{qianbi/main_parser.py → qianbi.py} +54 -51
  99. novel_downloader/core/parsers/qidian/__init__.py +2 -2
  100. novel_downloader/core/parsers/qidian/book_info_parser.py +58 -59
  101. novel_downloader/core/parsers/qidian/chapter_encrypted.py +290 -346
  102. novel_downloader/core/parsers/qidian/chapter_normal.py +25 -56
  103. novel_downloader/core/parsers/qidian/main_parser.py +19 -57
  104. novel_downloader/core/parsers/qidian/utils/__init__.py +12 -11
  105. novel_downloader/core/parsers/qidian/utils/decryptor_fetcher.py +6 -7
  106. novel_downloader/core/parsers/qidian/utils/fontmap_recover.py +143 -0
  107. novel_downloader/core/parsers/qidian/utils/helpers.py +0 -4
  108. novel_downloader/core/parsers/qidian/utils/node_decryptor.py +2 -2
  109. novel_downloader/core/parsers/quanben5.py +103 -0
  110. novel_downloader/core/parsers/registry.py +57 -0
  111. novel_downloader/core/parsers/{sfacg/main_parser.py → sfacg.py} +46 -48
  112. novel_downloader/core/parsers/shencou.py +215 -0
  113. novel_downloader/core/parsers/shuhaige.py +111 -0
  114. novel_downloader/core/parsers/tongrenquan.py +116 -0
  115. novel_downloader/core/parsers/ttkan.py +132 -0
  116. novel_downloader/core/parsers/wanbengo.py +191 -0
  117. novel_downloader/core/parsers/xiaoshuowu.py +173 -0
  118. novel_downloader/core/parsers/xiguashuwu.py +435 -0
  119. novel_downloader/core/parsers/xs63b.py +161 -0
  120. novel_downloader/core/parsers/xshbook.py +134 -0
  121. novel_downloader/core/parsers/yamibo.py +155 -0
  122. novel_downloader/core/parsers/yibige.py +166 -0
  123. novel_downloader/core/searchers/__init__.py +51 -0
  124. novel_downloader/core/searchers/aaatxt.py +107 -0
  125. novel_downloader/core/searchers/b520.py +84 -0
  126. novel_downloader/core/searchers/base.py +168 -0
  127. novel_downloader/core/searchers/dxmwx.py +105 -0
  128. novel_downloader/core/searchers/eightnovel.py +84 -0
  129. novel_downloader/core/searchers/esjzone.py +102 -0
  130. novel_downloader/core/searchers/hetushu.py +92 -0
  131. novel_downloader/core/searchers/i25zw.py +93 -0
  132. novel_downloader/core/searchers/ixdzs8.py +107 -0
  133. novel_downloader/core/searchers/jpxs123.py +107 -0
  134. novel_downloader/core/searchers/piaotia.py +100 -0
  135. novel_downloader/core/searchers/qbtr.py +106 -0
  136. novel_downloader/core/searchers/qianbi.py +165 -0
  137. novel_downloader/core/searchers/quanben5.py +144 -0
  138. novel_downloader/core/searchers/registry.py +79 -0
  139. novel_downloader/core/searchers/shuhaige.py +124 -0
  140. novel_downloader/core/searchers/tongrenquan.py +110 -0
  141. novel_downloader/core/searchers/ttkan.py +92 -0
  142. novel_downloader/core/searchers/xiaoshuowu.py +122 -0
  143. novel_downloader/core/searchers/xiguashuwu.py +95 -0
  144. novel_downloader/core/searchers/xs63b.py +104 -0
  145. novel_downloader/locales/en.json +36 -79
  146. novel_downloader/locales/zh.json +37 -80
  147. novel_downloader/models/__init__.py +23 -50
  148. novel_downloader/models/book.py +44 -0
  149. novel_downloader/models/config.py +16 -43
  150. novel_downloader/models/login.py +1 -1
  151. novel_downloader/models/search.py +21 -0
  152. novel_downloader/resources/config/settings.toml +39 -74
  153. novel_downloader/resources/css_styles/intro.css +83 -0
  154. novel_downloader/resources/css_styles/main.css +30 -89
  155. novel_downloader/resources/json/xiguashuwu.json +718 -0
  156. novel_downloader/utils/__init__.py +43 -0
  157. novel_downloader/utils/chapter_storage.py +247 -226
  158. novel_downloader/utils/constants.py +5 -50
  159. novel_downloader/utils/cookies.py +6 -18
  160. novel_downloader/utils/crypto_utils/__init__.py +13 -0
  161. novel_downloader/utils/crypto_utils/aes_util.py +90 -0
  162. novel_downloader/utils/crypto_utils/aes_v1.py +619 -0
  163. novel_downloader/utils/crypto_utils/aes_v2.py +1143 -0
  164. novel_downloader/utils/{crypto_utils.py → crypto_utils/rc4.py} +3 -10
  165. novel_downloader/utils/epub/__init__.py +34 -0
  166. novel_downloader/utils/epub/builder.py +377 -0
  167. novel_downloader/utils/epub/constants.py +118 -0
  168. novel_downloader/utils/epub/documents.py +297 -0
  169. novel_downloader/utils/epub/models.py +120 -0
  170. novel_downloader/utils/epub/utils.py +179 -0
  171. novel_downloader/utils/file_utils/__init__.py +5 -30
  172. novel_downloader/utils/file_utils/io.py +9 -150
  173. novel_downloader/utils/file_utils/normalize.py +2 -2
  174. novel_downloader/utils/file_utils/sanitize.py +2 -7
  175. novel_downloader/utils/fontocr.py +207 -0
  176. novel_downloader/utils/i18n.py +2 -0
  177. novel_downloader/utils/logger.py +10 -16
  178. novel_downloader/utils/network.py +111 -252
  179. novel_downloader/utils/state.py +5 -90
  180. novel_downloader/utils/text_utils/__init__.py +16 -21
  181. novel_downloader/utils/text_utils/diff_display.py +6 -9
  182. novel_downloader/utils/text_utils/numeric_conversion.py +253 -0
  183. novel_downloader/utils/text_utils/text_cleaner.py +179 -0
  184. novel_downloader/utils/text_utils/truncate_utils.py +62 -0
  185. novel_downloader/utils/time_utils/__init__.py +6 -12
  186. novel_downloader/utils/time_utils/datetime_utils.py +23 -33
  187. novel_downloader/utils/time_utils/sleep_utils.py +5 -10
  188. novel_downloader/web/__init__.py +13 -0
  189. novel_downloader/web/components/__init__.py +11 -0
  190. novel_downloader/web/components/navigation.py +35 -0
  191. novel_downloader/web/main.py +66 -0
  192. novel_downloader/web/pages/__init__.py +17 -0
  193. novel_downloader/web/pages/download.py +78 -0
  194. novel_downloader/web/pages/progress.py +147 -0
  195. novel_downloader/web/pages/search.py +329 -0
  196. novel_downloader/web/services/__init__.py +17 -0
  197. novel_downloader/web/services/client_dialog.py +164 -0
  198. novel_downloader/web/services/cred_broker.py +113 -0
  199. novel_downloader/web/services/cred_models.py +35 -0
  200. novel_downloader/web/services/task_manager.py +264 -0
  201. novel_downloader-2.0.0.dist-info/METADATA +171 -0
  202. novel_downloader-2.0.0.dist-info/RECORD +210 -0
  203. {novel_downloader-1.4.5.dist-info → novel_downloader-2.0.0.dist-info}/entry_points.txt +1 -1
  204. novel_downloader/config/site_rules.py +0 -94
  205. novel_downloader/core/downloaders/biquge.py +0 -25
  206. novel_downloader/core/downloaders/esjzone.py +0 -25
  207. novel_downloader/core/downloaders/linovelib.py +0 -25
  208. novel_downloader/core/downloaders/sfacg.py +0 -25
  209. novel_downloader/core/downloaders/yamibo.py +0 -25
  210. novel_downloader/core/exporters/biquge.py +0 -25
  211. novel_downloader/core/exporters/esjzone.py +0 -25
  212. novel_downloader/core/exporters/qianbi.py +0 -25
  213. novel_downloader/core/exporters/sfacg.py +0 -25
  214. novel_downloader/core/exporters/yamibo.py +0 -25
  215. novel_downloader/core/factory/__init__.py +0 -20
  216. novel_downloader/core/factory/downloader.py +0 -73
  217. novel_downloader/core/factory/exporter.py +0 -58
  218. novel_downloader/core/factory/fetcher.py +0 -96
  219. novel_downloader/core/factory/parser.py +0 -86
  220. novel_downloader/core/fetchers/base/__init__.py +0 -14
  221. novel_downloader/core/fetchers/base/browser.py +0 -403
  222. novel_downloader/core/fetchers/biquge/__init__.py +0 -14
  223. novel_downloader/core/fetchers/common/__init__.py +0 -14
  224. novel_downloader/core/fetchers/esjzone/__init__.py +0 -14
  225. novel_downloader/core/fetchers/esjzone/browser.py +0 -204
  226. novel_downloader/core/fetchers/linovelib/__init__.py +0 -14
  227. novel_downloader/core/fetchers/linovelib/browser.py +0 -193
  228. novel_downloader/core/fetchers/qianbi/__init__.py +0 -14
  229. novel_downloader/core/fetchers/qidian/__init__.py +0 -14
  230. novel_downloader/core/fetchers/qidian/browser.py +0 -318
  231. novel_downloader/core/fetchers/sfacg/__init__.py +0 -14
  232. novel_downloader/core/fetchers/sfacg/browser.py +0 -189
  233. novel_downloader/core/fetchers/yamibo/__init__.py +0 -14
  234. novel_downloader/core/fetchers/yamibo/browser.py +0 -229
  235. novel_downloader/core/parsers/biquge/__init__.py +0 -10
  236. novel_downloader/core/parsers/biquge/main_parser.py +0 -134
  237. novel_downloader/core/parsers/common/__init__.py +0 -13
  238. novel_downloader/core/parsers/common/helper.py +0 -323
  239. novel_downloader/core/parsers/common/main_parser.py +0 -106
  240. novel_downloader/core/parsers/esjzone/__init__.py +0 -10
  241. novel_downloader/core/parsers/linovelib/__init__.py +0 -10
  242. novel_downloader/core/parsers/qianbi/__init__.py +0 -10
  243. novel_downloader/core/parsers/sfacg/__init__.py +0 -10
  244. novel_downloader/core/parsers/yamibo/__init__.py +0 -10
  245. novel_downloader/core/parsers/yamibo/main_parser.py +0 -194
  246. novel_downloader/models/browser.py +0 -21
  247. novel_downloader/models/chapter.py +0 -25
  248. novel_downloader/models/site_rules.py +0 -99
  249. novel_downloader/models/tasks.py +0 -33
  250. novel_downloader/models/types.py +0 -15
  251. novel_downloader/resources/css_styles/volume-intro.css +0 -56
  252. novel_downloader/resources/json/replace_word_map.json +0 -4
  253. novel_downloader/resources/text/blacklist.txt +0 -22
  254. novel_downloader/tui/__init__.py +0 -7
  255. novel_downloader/tui/app.py +0 -32
  256. novel_downloader/tui/main.py +0 -17
  257. novel_downloader/tui/screens/__init__.py +0 -14
  258. novel_downloader/tui/screens/home.py +0 -198
  259. novel_downloader/tui/screens/login.py +0 -74
  260. novel_downloader/tui/styles/home_layout.tcss +0 -79
  261. novel_downloader/tui/widgets/richlog_handler.py +0 -24
  262. novel_downloader/utils/cache.py +0 -24
  263. novel_downloader/utils/fontocr/__init__.py +0 -22
  264. novel_downloader/utils/fontocr/model_loader.py +0 -69
  265. novel_downloader/utils/fontocr/ocr_v1.py +0 -303
  266. novel_downloader/utils/fontocr/ocr_v2.py +0 -752
  267. novel_downloader/utils/hash_store.py +0 -279
  268. novel_downloader/utils/hash_utils.py +0 -103
  269. novel_downloader/utils/text_utils/chapter_formatting.py +0 -46
  270. novel_downloader/utils/text_utils/font_mapping.py +0 -28
  271. novel_downloader/utils/text_utils/text_cleaning.py +0 -107
  272. novel_downloader-1.4.5.dist-info/METADATA +0 -196
  273. novel_downloader-1.4.5.dist-info/RECORD +0 -165
  274. {novel_downloader-1.4.5.dist-info → novel_downloader-2.0.0.dist-info}/WHEEL +0 -0
  275. {novel_downloader-1.4.5.dist-info → novel_downloader-2.0.0.dist-info}/licenses/LICENSE +0 -0
  276. {novel_downloader-1.4.5.dist-info → novel_downloader-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ novel_downloader.core.fetchers.registry
4
+ ---------------------------------------
5
+
6
+ Registry and factory helpers for creating site-specific fetchers.
7
+ """
8
+
9
+ __all__ = ["register_fetcher", "get_fetcher"]
10
+
11
+ from collections.abc import Callable, Sequence
12
+ from typing import TypeVar
13
+
14
+ from novel_downloader.core.interfaces import FetcherProtocol
15
+ from novel_downloader.models import FetcherConfig
16
+
17
+ FetcherBuilder = Callable[[FetcherConfig], FetcherProtocol]
18
+
19
+ F = TypeVar("F", bound=FetcherProtocol)
20
+ _FETCHER_MAP: dict[str, FetcherBuilder] = {}
21
+
22
+
23
+ def register_fetcher(
24
+ site_keys: Sequence[str],
25
+ ) -> Callable[[type[F]], type[F]]:
26
+ """
27
+ Decorator to register a fetcher class under given keys.
28
+
29
+ :param site_keys: Sequence of site identifiers
30
+ :param backends: Sequence of backend types
31
+ :return: A class decorator that populates _FETCHER_MAP.
32
+ """
33
+
34
+ def decorator(cls: type[F]) -> type[F]:
35
+ for site in site_keys:
36
+ site_lower = site.lower()
37
+ _FETCHER_MAP[site_lower] = cls
38
+ return cls
39
+
40
+ return decorator
41
+
42
+
43
+ def get_fetcher(
44
+ site: str,
45
+ config: FetcherConfig,
46
+ ) -> FetcherProtocol:
47
+ """
48
+ Returns an FetcherProtocol for the given site.
49
+
50
+ :param site: Site name (e.g., 'qidian')
51
+ :param config: Configuration for the requester
52
+ :return: An instance of a requester class
53
+ """
54
+ site_key = site.lower()
55
+ try:
56
+ fetcher_cls = _FETCHER_MAP[site_key]
57
+ except KeyError as err:
58
+ raise ValueError(f"Unsupported site: {site!r}") from err
59
+
60
+ return fetcher_cls(config)
@@ -1,19 +1,23 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- novel_downloader.core.fetchers.sfacg.session
4
- --------------------------------------------
3
+ novel_downloader.core.fetchers.sfacg
4
+ ------------------------------------
5
5
 
6
6
  """
7
7
 
8
8
  from typing import Any
9
9
 
10
10
  from novel_downloader.core.fetchers.base import BaseSession
11
+ from novel_downloader.core.fetchers.registry import register_fetcher
11
12
  from novel_downloader.models import FetcherConfig, LoginField
12
13
 
13
14
 
15
+ @register_fetcher(
16
+ site_keys=["sfacg"],
17
+ )
14
18
  class SfacgSession(BaseSession):
15
19
  """
16
- A session class for interacting with the Sfacg (m.sfacg.com) novel website.
20
+ A session class for interacting with the SF轻小说 (m.sfacg.com) novel website.
17
21
  """
18
22
 
19
23
  LOGIN_URL = "https://m.sfacg.com/login"
@@ -60,8 +64,10 @@ class SfacgSession(BaseSession):
60
64
  """
61
65
  Fetch the raw HTML of the book info page asynchronously.
62
66
 
67
+ Order: [info, catalog]
68
+
63
69
  :param book_id: The book identifier.
64
- :return: The page content as a string.
70
+ :return: The page content as string list.
65
71
  """
66
72
  info_url = self.book_info_url(book_id=book_id)
67
73
  catalog_url = self.book_catalog_url(book_id=book_id)
@@ -82,7 +88,7 @@ class SfacgSession(BaseSession):
82
88
 
83
89
  :param book_id: The book identifier.
84
90
  :param chapter_id: The chapter identifier.
85
- :return: The chapter content as a string.
91
+ :return: The page content as string list.
86
92
  """
87
93
  url = self.chapter_url(book_id=book_id, chapter_id=chapter_id)
88
94
  return [await self.fetch(url, **kwargs)]
@@ -152,10 +158,6 @@ class SfacgSession(BaseSession):
152
158
  """
153
159
  return cls.CHAPTER_URL.format(chapter_id=chapter_id)
154
160
 
155
- @property
156
- def hostname(self) -> str:
157
- return "m.sfacg.com"
158
-
159
161
  async def _check_login_status(self) -> bool:
160
162
  """
161
163
  Check whether the user is currently logged in by
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ novel_downloader.core.fetchers.shencou
4
+ --------------------------------------
5
+
6
+ """
7
+
8
+ import asyncio
9
+ from typing import Any
10
+
11
+ from novel_downloader.core.fetchers.base import BaseSession
12
+ from novel_downloader.core.fetchers.registry import register_fetcher
13
+ from novel_downloader.models import FetcherConfig
14
+
15
+
16
+ @register_fetcher(
17
+ site_keys=["shencou"],
18
+ )
19
+ class ShencouSession(BaseSession):
20
+ """
21
+ A session class for interacting with the 神凑轻小说 (www.shencou.com) novel website.
22
+ """
23
+
24
+ BOOK_INFO_URL = "https://www.shencou.com/books/read_{book_id}.html"
25
+ BOOK_CATALOG_URL = "https://www.shencou.com/read/{book_id}/index.html"
26
+ CHAPTER_URL = "https://www.shencou.com/read/{book_id}/{chapter_id}.html"
27
+
28
+ def __init__(
29
+ self,
30
+ config: FetcherConfig,
31
+ cookies: dict[str, str] | None = None,
32
+ **kwargs: Any,
33
+ ) -> None:
34
+ super().__init__("shencou", config, cookies, **kwargs)
35
+
36
+ async def get_book_info(
37
+ self,
38
+ book_id: str,
39
+ **kwargs: Any,
40
+ ) -> list[str]:
41
+ """
42
+ Fetch the raw HTML of the book info page asynchronously.
43
+
44
+ Order: [info, catalog]
45
+
46
+ :param book_id: The book identifier.
47
+ :return: The page content as string list.
48
+ """
49
+ book_id = book_id.replace("-", "/")
50
+ info_url = self.book_info_url(book_id=book_id)
51
+ catalog_url = self.book_catalog_url(book_id=book_id)
52
+
53
+ info_html, catalog_html = await asyncio.gather(
54
+ self.fetch(info_url, **kwargs),
55
+ self.fetch(catalog_url, **kwargs),
56
+ )
57
+ return [info_html, catalog_html]
58
+
59
+ async def get_book_chapter(
60
+ self,
61
+ book_id: str,
62
+ chapter_id: str,
63
+ **kwargs: Any,
64
+ ) -> list[str]:
65
+ """
66
+ Fetch the raw HTML of a single chapter asynchronously.
67
+
68
+ :param book_id: The book identifier.
69
+ :param chapter_id: The chapter identifier.
70
+ :return: The page content as string list.
71
+ """
72
+ book_id = book_id.replace("-", "/")
73
+ url = self.chapter_url(book_id=book_id, chapter_id=chapter_id)
74
+ return [await self.fetch(url, **kwargs)]
75
+
76
+ @classmethod
77
+ def book_info_url(cls, book_id: str) -> str:
78
+ """
79
+ Construct the URL for fetching a book's info page.
80
+
81
+ :param book_id: The identifier of the book.
82
+ :return: Fully qualified URL for the book info page.
83
+ """
84
+ clean_id = book_id.rsplit("/", 1)[-1]
85
+ return cls.BOOK_INFO_URL.format(book_id=clean_id)
86
+
87
+ @classmethod
88
+ def book_catalog_url(cls, book_id: str) -> str:
89
+ """
90
+ Construct the URL for fetching a book's catalog page.
91
+
92
+ :param book_id: The identifier of the book.
93
+ :return: Fully qualified catalog page URL.
94
+ """
95
+ return cls.BOOK_CATALOG_URL.format(book_id=book_id)
96
+
97
+ @classmethod
98
+ def chapter_url(cls, book_id: str, chapter_id: str) -> str:
99
+ """
100
+ Construct the URL for fetching a specific chapter.
101
+
102
+ :param book_id: The identifier of the book.
103
+ :param chapter_id: The identifier of the chapter.
104
+ :return: Fully qualified chapter URL.
105
+ """
106
+ return cls.CHAPTER_URL.format(book_id=book_id, chapter_id=chapter_id)
@@ -1,31 +1,36 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- novel_downloader.core.fetchers.common.browser
4
- ---------------------------------------------
3
+ novel_downloader.core.fetchers.shuhaige
4
+ ---------------------------------------
5
5
 
6
6
  """
7
7
 
8
8
  from typing import Any
9
9
 
10
- from novel_downloader.core.fetchers.base import BaseBrowser
11
- from novel_downloader.models import FetcherConfig, SiteProfile
10
+ from novel_downloader.core.fetchers.base import BaseSession
11
+ from novel_downloader.core.fetchers.registry import register_fetcher
12
+ from novel_downloader.models import FetcherConfig
12
13
 
13
14
 
14
- class CommonBrowser(BaseBrowser):
15
+ @register_fetcher(
16
+ site_keys=["shuhaige"],
17
+ )
18
+ class ShuhaigeSession(BaseSession):
15
19
  """
16
- A common async browser for handling site-specific HTTP requests.
20
+ A session class for interacting with the
21
+ 书海阁小说网 (www.shuhaige.net) novel website.
17
22
  """
18
23
 
24
+ BOOK_INFO_URL = "https://www.shuhaige.net/{book_id}/"
25
+ CHAPTER_URL = "https://www.shuhaige.net/{book_id}/{chapter_id}.html"
26
+
19
27
  def __init__(
20
28
  self,
21
- site: str,
22
- profile: SiteProfile,
23
29
  config: FetcherConfig,
24
- reuse_page: bool = False,
30
+ cookies: dict[str, str] | None = None,
25
31
  **kwargs: Any,
26
32
  ) -> None:
27
- super().__init__(site, config, reuse_page, **kwargs)
28
- self._profile = profile
33
+ super().__init__("shuhaige", config, cookies, **kwargs)
29
34
 
30
35
  async def get_book_info(
31
36
  self,
@@ -36,7 +41,7 @@ class CommonBrowser(BaseBrowser):
36
41
  Fetch the raw HTML of the book info page asynchronously.
37
42
 
38
43
  :param book_id: The book identifier.
39
- :return: The page content as a string.
44
+ :return: The page content as string list.
40
45
  """
41
46
  url = self.book_info_url(book_id=book_id)
42
47
  return [await self.fetch(url, **kwargs)]
@@ -52,21 +57,23 @@ class CommonBrowser(BaseBrowser):
52
57
 
53
58
  :param book_id: The book identifier.
54
59
  :param chapter_id: The chapter identifier.
55
- :return: The chapter content as a string.
60
+ :return: The page content as string list.
56
61
  """
57
62
  url = self.chapter_url(book_id=book_id, chapter_id=chapter_id)
58
63
  return [await self.fetch(url, **kwargs)]
59
64
 
60
- def book_info_url(self, book_id: str) -> str:
65
+ @classmethod
66
+ def book_info_url(cls, book_id: str) -> str:
61
67
  """
62
68
  Construct the URL for fetching a book's info page.
63
69
 
64
70
  :param book_id: The identifier of the book.
65
71
  :return: Fully qualified URL for the book info page.
66
72
  """
67
- return self._profile["book_info_url"].format(book_id=book_id)
73
+ return cls.BOOK_INFO_URL.format(book_id=book_id)
68
74
 
69
- def chapter_url(self, book_id: str, chapter_id: str) -> str:
75
+ @classmethod
76
+ def chapter_url(cls, book_id: str, chapter_id: str) -> str:
70
77
  """
71
78
  Construct the URL for fetching a specific chapter.
72
79
 
@@ -74,6 +81,4 @@ class CommonBrowser(BaseBrowser):
74
81
  :param chapter_id: The identifier of the chapter.
75
82
  :return: Fully qualified chapter URL.
76
83
  """
77
- return self._profile["chapter_url"].format(
78
- book_id=book_id, chapter_id=chapter_id
79
- )
84
+ return cls.CHAPTER_URL.format(book_id=book_id, chapter_id=chapter_id)
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ novel_downloader.core.fetchers.tongrenquan
4
+ ------------------------------------------
5
+
6
+ """
7
+
8
+ from typing import Any
9
+
10
+ from novel_downloader.core.fetchers.base import BaseSession
11
+ from novel_downloader.core.fetchers.registry import register_fetcher
12
+ from novel_downloader.models import FetcherConfig
13
+
14
+
15
+ @register_fetcher(
16
+ site_keys=["tongrenquan"],
17
+ )
18
+ class TongrenquanSession(BaseSession):
19
+ """
20
+ A session class for interacting with the 同人圈 (www.tongrenquan.org) novel website.
21
+ """
22
+
23
+ BASE_URL = "https://www.tongrenquan.org"
24
+ BOOK_INFO_URL = "https://www.tongrenquan.org/tongren/{book_id}.html"
25
+ CHAPTER_URL = "https://www.tongrenquan.org/tongren/{book_id}/{chapter_id}.html"
26
+
27
+ def __init__(
28
+ self,
29
+ config: FetcherConfig,
30
+ cookies: dict[str, str] | None = None,
31
+ **kwargs: Any,
32
+ ) -> None:
33
+ super().__init__("tongrenquan", config, cookies, **kwargs)
34
+
35
+ async def get_book_info(
36
+ self,
37
+ book_id: str,
38
+ **kwargs: Any,
39
+ ) -> list[str]:
40
+ """
41
+ Fetch the raw HTML of the book info page asynchronously.
42
+
43
+ :param book_id: The book identifier.
44
+ :return: The page content as string list.
45
+ """
46
+ url = self.book_info_url(book_id=book_id)
47
+ return [await self.fetch(url, **kwargs)]
48
+
49
+ async def get_book_chapter(
50
+ self,
51
+ book_id: str,
52
+ chapter_id: str,
53
+ **kwargs: Any,
54
+ ) -> list[str]:
55
+ """
56
+ Fetch the raw HTML of a single chapter asynchronously.
57
+
58
+ :param book_id: The book identifier.
59
+ :param chapter_id: The chapter identifier.
60
+ :return: The page content as string list.
61
+ """
62
+ url = self.chapter_url(book_id=book_id, chapter_id=chapter_id)
63
+ return [await self.fetch(url, **kwargs)]
64
+
65
+ @classmethod
66
+ def book_info_url(cls, book_id: str) -> str:
67
+ """
68
+ Construct the URL for fetching a book's info page.
69
+
70
+ :param book_id: The identifier of the book.
71
+ :return: Fully qualified URL for the book info page.
72
+ """
73
+ return cls.BOOK_INFO_URL.format(book_id=book_id)
74
+
75
+ @classmethod
76
+ def chapter_url(cls, book_id: str, chapter_id: str) -> str:
77
+ """
78
+ Construct the URL for fetching a specific chapter.
79
+
80
+ :param book_id: The identifier of the book.
81
+ :param chapter_id: The identifier of the chapter.
82
+ :return: Fully qualified chapter URL.
83
+ """
84
+ return cls.CHAPTER_URL.format(book_id=book_id, chapter_id=chapter_id)
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ novel_downloader.core.fetchers.ttkan
4
+ ------------------------------------
5
+
6
+ """
7
+
8
+ from typing import Any
9
+
10
+ from novel_downloader.core.fetchers.base import BaseSession
11
+ from novel_downloader.core.fetchers.registry import register_fetcher
12
+ from novel_downloader.models import FetcherConfig
13
+
14
+
15
+ @register_fetcher(
16
+ site_keys=["ttkan"],
17
+ )
18
+ class TtkanSession(BaseSession):
19
+ """
20
+ A session class for interacting with the 天天看小说 (www.ttkan.co) novel website.
21
+ """
22
+
23
+ BOOK_INFO_URL = "https://{lang}.ttkan.co/novel/chapters/{book_id}"
24
+ CHAPTER_URL = "https://{lang}.wa01.com/novel/pagea/{book_id}_{chapter_id}.html"
25
+
26
+ def __init__(
27
+ self,
28
+ config: FetcherConfig,
29
+ cookies: dict[str, str] | None = None,
30
+ **kwargs: Any,
31
+ ) -> None:
32
+ super().__init__("ttkan", config, cookies, **kwargs)
33
+ self._lang = "cn" if config.locale_style == "simplified" else "tw"
34
+
35
+ async def get_book_info(
36
+ self,
37
+ book_id: str,
38
+ **kwargs: Any,
39
+ ) -> list[str]:
40
+ """
41
+ Fetch the raw HTML of the book info page asynchronously.
42
+
43
+ :param book_id: The book identifier.
44
+ :return: The page content as string list.
45
+ """
46
+ url = self.book_info_url(book_id=book_id)
47
+ return [await self.fetch(url, **kwargs)]
48
+
49
+ async def get_book_chapter(
50
+ self,
51
+ book_id: str,
52
+ chapter_id: str,
53
+ **kwargs: Any,
54
+ ) -> list[str]:
55
+ """
56
+ Fetch the raw HTML of a single chapter asynchronously.
57
+
58
+ :param book_id: The book identifier.
59
+ :param chapter_id: The chapter identifier.
60
+ :return: The page content as string list.
61
+ """
62
+ url = self.chapter_url(
63
+ book_id=book_id,
64
+ chapter_id=chapter_id,
65
+ lang=self._lang,
66
+ )
67
+ return [await self.fetch(url, **kwargs)]
68
+
69
+ @classmethod
70
+ def book_info_url(cls, book_id: str, lang: str = "cn") -> str:
71
+ """
72
+ Construct the URL for fetching a book's info page.
73
+
74
+ :param book_id: The identifier of the book.
75
+ :param lang: The language of the book. (cn / tw)
76
+ :return: Fully qualified URL for the book info page.
77
+ """
78
+ return cls.BOOK_INFO_URL.format(book_id=book_id, lang=lang)
79
+
80
+ @classmethod
81
+ def chapter_url(
82
+ cls,
83
+ book_id: str,
84
+ chapter_id: str,
85
+ lang: str = "cn",
86
+ ) -> str:
87
+ """
88
+ Construct the URL for fetching a specific chapter.
89
+
90
+ :param book_id: The identifier of the book.
91
+ :param chapter_id: The chapter identifier.
92
+ :param lang: The language of the book. (cn / tw)
93
+ :return: Fully qualified chapter URL.
94
+ """
95
+ return cls.CHAPTER_URL.format(book_id=book_id, chapter_id=chapter_id, lang=lang)
@@ -1,31 +1,35 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- novel_downloader.core.fetchers.common.session
4
- ---------------------------------------------
3
+ novel_downloader.core.fetchers.wanbengo
4
+ ---------------------------------------
5
5
 
6
6
  """
7
7
 
8
8
  from typing import Any
9
9
 
10
10
  from novel_downloader.core.fetchers.base import BaseSession
11
- from novel_downloader.models import FetcherConfig, SiteProfile
11
+ from novel_downloader.core.fetchers.registry import register_fetcher
12
+ from novel_downloader.models import FetcherConfig
12
13
 
13
14
 
14
- class CommonSession(BaseSession):
15
+ @register_fetcher(
16
+ site_keys=["wanbengo"],
17
+ )
18
+ class WanbengoSession(BaseSession):
15
19
  """
16
- A common async session for handling site-specific HTTP requests.
20
+ A session class for interacting with the 完本神站 (www.wanbengo.com) novel website.
17
21
  """
18
22
 
23
+ BOOK_INFO_URL = "https://www.wanbengo.com/{book_id}/"
24
+ CHAPTER_URL = "https://www.wanbengo.com/{book_id}/{chapter_id}.html"
25
+
19
26
  def __init__(
20
27
  self,
21
- site: str,
22
- profile: SiteProfile,
23
28
  config: FetcherConfig,
24
29
  cookies: dict[str, str] | None = None,
25
30
  **kwargs: Any,
26
31
  ) -> None:
27
- super().__init__(site, config, cookies, **kwargs)
28
- self._profile = profile
32
+ super().__init__("wanbengo", config, cookies, **kwargs)
29
33
 
30
34
  async def get_book_info(
31
35
  self,
@@ -36,7 +40,7 @@ class CommonSession(BaseSession):
36
40
  Fetch the raw HTML of the book info page asynchronously.
37
41
 
38
42
  :param book_id: The book identifier.
39
- :return: The page content as a string.
43
+ :return: The page content as string list.
40
44
  """
41
45
  url = self.book_info_url(book_id=book_id)
42
46
  return [await self.fetch(url, **kwargs)]
@@ -52,21 +56,23 @@ class CommonSession(BaseSession):
52
56
 
53
57
  :param book_id: The book identifier.
54
58
  :param chapter_id: The chapter identifier.
55
- :return: The chapter content as a string.
59
+ :return: The page content as string list.
56
60
  """
57
61
  url = self.chapter_url(book_id=book_id, chapter_id=chapter_id)
58
62
  return [await self.fetch(url, **kwargs)]
59
63
 
60
- def book_info_url(self, book_id: str) -> str:
64
+ @classmethod
65
+ def book_info_url(cls, book_id: str) -> str:
61
66
  """
62
67
  Construct the URL for fetching a book's info page.
63
68
 
64
69
  :param book_id: The identifier of the book.
65
70
  :return: Fully qualified URL for the book info page.
66
71
  """
67
- return self._profile["book_info_url"].format(book_id=book_id)
72
+ return cls.BOOK_INFO_URL.format(book_id=book_id)
68
73
 
69
- def chapter_url(self, book_id: str, chapter_id: str) -> str:
74
+ @classmethod
75
+ def chapter_url(cls, book_id: str, chapter_id: str) -> str:
70
76
  """
71
77
  Construct the URL for fetching a specific chapter.
72
78
 
@@ -74,6 +80,4 @@ class CommonSession(BaseSession):
74
80
  :param chapter_id: The identifier of the chapter.
75
81
  :return: Fully qualified chapter URL.
76
82
  """
77
- return self._profile["chapter_url"].format(
78
- book_id=book_id, chapter_id=chapter_id
79
- )
83
+ return cls.CHAPTER_URL.format(book_id=book_id, chapter_id=chapter_id)
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ novel_downloader.core.fetchers.xiaoshuowu
4
+ -----------------------------------------
5
+
6
+ """
7
+
8
+ import asyncio
9
+ from typing import Any
10
+
11
+ from novel_downloader.core.fetchers.base import BaseSession
12
+ from novel_downloader.core.fetchers.registry import register_fetcher
13
+ from novel_downloader.models import FetcherConfig
14
+
15
+
16
+ @register_fetcher(
17
+ site_keys=["xiaoshuowu", "xiaoshuoge"],
18
+ )
19
+ class XiaoshuowuSession(BaseSession):
20
+ """
21
+ A session class for interacting with the 小说屋 (www.xiaoshuoge.info) novel.
22
+ """
23
+
24
+ BOOK_INFO_URL = "http://www.xiaoshuoge.info/book/{book_id}/"
25
+ BOOK_CATALOG_URL = "http://www.xiaoshuoge.info/html/{book_id}/"
26
+ CHAPTER_URL = "http://www.xiaoshuoge.info/html/{book_id}/{chapter_id}.html"
27
+
28
+ def __init__(
29
+ self,
30
+ config: FetcherConfig,
31
+ cookies: dict[str, str] | None = None,
32
+ **kwargs: Any,
33
+ ) -> None:
34
+ super().__init__("xiaoshuowu", config, cookies, **kwargs)
35
+
36
+ async def get_book_info(
37
+ self,
38
+ book_id: str,
39
+ **kwargs: Any,
40
+ ) -> list[str]:
41
+ """
42
+ Fetch the raw HTML of the book info page asynchronously.
43
+
44
+ Order: [info, catalog]
45
+
46
+ :param book_id: The book identifier.
47
+ :return: The page content as string list.
48
+ """
49
+ book_id = book_id.replace("-", "/")
50
+ info_url = self.book_info_url(book_id=book_id)
51
+ catalog_url = self.book_catalog_url(book_id=book_id)
52
+
53
+ info_html, catalog_html = await asyncio.gather(
54
+ self.fetch(info_url, ssl=False, **kwargs),
55
+ self.fetch(catalog_url, ssl=False, **kwargs),
56
+ )
57
+ return [info_html, catalog_html]
58
+
59
+ async def get_book_chapter(
60
+ self,
61
+ book_id: str,
62
+ chapter_id: str,
63
+ **kwargs: Any,
64
+ ) -> list[str]:
65
+ """
66
+ Fetch the raw HTML of a single chapter asynchronously.
67
+
68
+ :param book_id: The book identifier.
69
+ :param chapter_id: The chapter identifier.
70
+ :return: The page content as string list.
71
+ """
72
+ book_id = book_id.replace("-", "/")
73
+ url = self.chapter_url(book_id=book_id, chapter_id=chapter_id)
74
+ return [await self.fetch(url, ssl=False, **kwargs)]
75
+
76
+ @classmethod
77
+ def book_info_url(cls, book_id: str) -> str:
78
+ """
79
+ Construct the URL for fetching a book's info page.
80
+
81
+ :param book_id: The identifier of the book.
82
+ :return: Fully qualified URL for the book info page.
83
+ """
84
+ clean_id = book_id.rsplit("/", 1)[-1]
85
+ return cls.BOOK_INFO_URL.format(book_id=clean_id)
86
+
87
+ @classmethod
88
+ def book_catalog_url(cls, book_id: str) -> str:
89
+ """
90
+ Construct the URL for fetching a book's catalog page.
91
+
92
+ :param book_id: The identifier of the book.
93
+ :return: Fully qualified catalog page URL.
94
+ """
95
+ return cls.BOOK_CATALOG_URL.format(book_id=book_id)
96
+
97
+ @classmethod
98
+ def chapter_url(cls, book_id: str, chapter_id: str) -> str:
99
+ """
100
+ Construct the URL for fetching a specific chapter.
101
+
102
+ :param book_id: The identifier of the book.
103
+ :param chapter_id: The identifier of the chapter.
104
+ :return: Fully qualified chapter URL.
105
+ """
106
+ return cls.CHAPTER_URL.format(book_id=book_id, chapter_id=chapter_id)