novel-downloader 1.3.3__py3-none-any.whl → 1.4.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 (211) hide show
  1. novel_downloader/__init__.py +1 -1
  2. novel_downloader/cli/clean.py +97 -78
  3. novel_downloader/cli/config.py +177 -0
  4. novel_downloader/cli/download.py +132 -87
  5. novel_downloader/cli/export.py +77 -0
  6. novel_downloader/cli/main.py +21 -28
  7. novel_downloader/config/__init__.py +1 -25
  8. novel_downloader/config/adapter.py +32 -31
  9. novel_downloader/config/loader.py +3 -3
  10. novel_downloader/config/site_rules.py +1 -2
  11. novel_downloader/core/__init__.py +3 -6
  12. novel_downloader/core/downloaders/__init__.py +10 -13
  13. novel_downloader/core/downloaders/base.py +233 -0
  14. novel_downloader/core/downloaders/biquge.py +27 -0
  15. novel_downloader/core/downloaders/common.py +414 -0
  16. novel_downloader/core/downloaders/esjzone.py +27 -0
  17. novel_downloader/core/downloaders/linovelib.py +27 -0
  18. novel_downloader/core/downloaders/qianbi.py +27 -0
  19. novel_downloader/core/downloaders/qidian.py +352 -0
  20. novel_downloader/core/downloaders/sfacg.py +27 -0
  21. novel_downloader/core/downloaders/yamibo.py +27 -0
  22. novel_downloader/core/exporters/__init__.py +37 -0
  23. novel_downloader/core/{savers → exporters}/base.py +73 -39
  24. novel_downloader/core/exporters/biquge.py +25 -0
  25. novel_downloader/core/exporters/common/__init__.py +12 -0
  26. novel_downloader/core/{savers → exporters}/common/epub.py +22 -22
  27. novel_downloader/core/{savers/common/main_saver.py → exporters/common/main_exporter.py} +35 -40
  28. novel_downloader/core/{savers → exporters}/common/txt.py +20 -23
  29. novel_downloader/core/{savers → exporters}/epub_utils/__init__.py +8 -3
  30. novel_downloader/core/{savers → exporters}/epub_utils/css_builder.py +2 -2
  31. novel_downloader/core/{savers → exporters}/epub_utils/image_loader.py +46 -4
  32. novel_downloader/core/{savers → exporters}/epub_utils/initializer.py +6 -4
  33. novel_downloader/core/{savers → exporters}/epub_utils/text_to_html.py +3 -3
  34. novel_downloader/core/{savers → exporters}/epub_utils/volume_intro.py +2 -2
  35. novel_downloader/core/exporters/esjzone.py +25 -0
  36. novel_downloader/core/exporters/linovelib/__init__.py +10 -0
  37. novel_downloader/core/exporters/linovelib/epub.py +449 -0
  38. novel_downloader/core/exporters/linovelib/main_exporter.py +127 -0
  39. novel_downloader/core/exporters/linovelib/txt.py +129 -0
  40. novel_downloader/core/exporters/qianbi.py +25 -0
  41. novel_downloader/core/{savers → exporters}/qidian.py +8 -8
  42. novel_downloader/core/exporters/sfacg.py +25 -0
  43. novel_downloader/core/exporters/yamibo.py +25 -0
  44. novel_downloader/core/factory/__init__.py +5 -17
  45. novel_downloader/core/factory/downloader.py +24 -126
  46. novel_downloader/core/factory/exporter.py +58 -0
  47. novel_downloader/core/factory/fetcher.py +96 -0
  48. novel_downloader/core/factory/parser.py +17 -12
  49. novel_downloader/core/{requesters → fetchers}/__init__.py +22 -15
  50. novel_downloader/core/{requesters → fetchers}/base/__init__.py +2 -4
  51. novel_downloader/core/fetchers/base/browser.py +383 -0
  52. novel_downloader/core/fetchers/base/rate_limiter.py +86 -0
  53. novel_downloader/core/fetchers/base/session.py +419 -0
  54. novel_downloader/core/fetchers/biquge/__init__.py +14 -0
  55. novel_downloader/core/{requesters/biquge/async_session.py → fetchers/biquge/browser.py} +18 -6
  56. novel_downloader/core/{requesters → fetchers}/biquge/session.py +23 -30
  57. novel_downloader/core/fetchers/common/__init__.py +14 -0
  58. novel_downloader/core/fetchers/common/browser.py +79 -0
  59. novel_downloader/core/{requesters/common/async_session.py → fetchers/common/session.py} +8 -25
  60. novel_downloader/core/fetchers/esjzone/__init__.py +14 -0
  61. novel_downloader/core/fetchers/esjzone/browser.py +202 -0
  62. novel_downloader/core/{requesters/esjzone/async_session.py → fetchers/esjzone/session.py} +62 -42
  63. novel_downloader/core/fetchers/linovelib/__init__.py +14 -0
  64. novel_downloader/core/fetchers/linovelib/browser.py +193 -0
  65. novel_downloader/core/fetchers/linovelib/session.py +193 -0
  66. novel_downloader/core/fetchers/qianbi/__init__.py +14 -0
  67. novel_downloader/core/{requesters/qianbi/session.py → fetchers/qianbi/browser.py} +30 -48
  68. novel_downloader/core/{requesters/qianbi/async_session.py → fetchers/qianbi/session.py} +18 -6
  69. novel_downloader/core/fetchers/qidian/__init__.py +14 -0
  70. novel_downloader/core/fetchers/qidian/browser.py +266 -0
  71. novel_downloader/core/fetchers/qidian/session.py +326 -0
  72. novel_downloader/core/fetchers/sfacg/__init__.py +14 -0
  73. novel_downloader/core/fetchers/sfacg/browser.py +189 -0
  74. novel_downloader/core/{requesters/sfacg/async_session.py → fetchers/sfacg/session.py} +43 -73
  75. novel_downloader/core/fetchers/yamibo/__init__.py +14 -0
  76. novel_downloader/core/fetchers/yamibo/browser.py +229 -0
  77. novel_downloader/core/{requesters/yamibo/async_session.py → fetchers/yamibo/session.py} +62 -44
  78. novel_downloader/core/interfaces/__init__.py +8 -12
  79. novel_downloader/core/interfaces/downloader.py +54 -0
  80. novel_downloader/core/interfaces/{saver.py → exporter.py} +12 -12
  81. novel_downloader/core/interfaces/fetcher.py +162 -0
  82. novel_downloader/core/interfaces/parser.py +6 -7
  83. novel_downloader/core/parsers/__init__.py +5 -6
  84. novel_downloader/core/parsers/base.py +9 -13
  85. novel_downloader/core/parsers/biquge/main_parser.py +12 -13
  86. novel_downloader/core/parsers/common/helper.py +3 -3
  87. novel_downloader/core/parsers/common/main_parser.py +39 -34
  88. novel_downloader/core/parsers/esjzone/main_parser.py +20 -14
  89. novel_downloader/core/parsers/linovelib/__init__.py +10 -0
  90. novel_downloader/core/parsers/linovelib/main_parser.py +210 -0
  91. novel_downloader/core/parsers/qianbi/main_parser.py +21 -15
  92. novel_downloader/core/parsers/qidian/__init__.py +2 -11
  93. novel_downloader/core/parsers/qidian/book_info_parser.py +113 -0
  94. novel_downloader/core/parsers/qidian/{browser/chapter_encrypted.py → chapter_encrypted.py} +162 -135
  95. novel_downloader/core/parsers/qidian/chapter_normal.py +150 -0
  96. novel_downloader/core/parsers/qidian/{session/chapter_router.py → chapter_router.py} +15 -15
  97. novel_downloader/core/parsers/qidian/{browser/main_parser.py → main_parser.py} +49 -40
  98. novel_downloader/core/parsers/qidian/utils/__init__.py +27 -0
  99. novel_downloader/core/parsers/qidian/utils/decryptor_fetcher.py +145 -0
  100. novel_downloader/core/parsers/qidian/{shared → utils}/helpers.py +41 -68
  101. novel_downloader/core/parsers/qidian/{session → utils}/node_decryptor.py +64 -50
  102. novel_downloader/core/parsers/sfacg/main_parser.py +12 -12
  103. novel_downloader/core/parsers/yamibo/main_parser.py +10 -10
  104. novel_downloader/locales/en.json +18 -2
  105. novel_downloader/locales/zh.json +18 -2
  106. novel_downloader/models/__init__.py +64 -0
  107. novel_downloader/models/browser.py +21 -0
  108. novel_downloader/models/chapter.py +25 -0
  109. novel_downloader/models/config.py +100 -0
  110. novel_downloader/models/login.py +20 -0
  111. novel_downloader/models/site_rules.py +99 -0
  112. novel_downloader/models/tasks.py +33 -0
  113. novel_downloader/models/types.py +15 -0
  114. novel_downloader/resources/config/settings.toml +31 -25
  115. novel_downloader/resources/json/linovelib_font_map.json +3573 -0
  116. novel_downloader/tui/__init__.py +7 -0
  117. novel_downloader/tui/app.py +32 -0
  118. novel_downloader/tui/main.py +17 -0
  119. novel_downloader/tui/screens/__init__.py +14 -0
  120. novel_downloader/tui/screens/home.py +191 -0
  121. novel_downloader/tui/screens/login.py +74 -0
  122. novel_downloader/tui/styles/home_layout.tcss +79 -0
  123. novel_downloader/tui/widgets/richlog_handler.py +24 -0
  124. novel_downloader/utils/__init__.py +6 -0
  125. novel_downloader/utils/chapter_storage.py +25 -38
  126. novel_downloader/utils/constants.py +11 -5
  127. novel_downloader/utils/cookies.py +66 -0
  128. novel_downloader/utils/crypto_utils.py +1 -74
  129. novel_downloader/utils/fontocr/ocr_v1.py +2 -1
  130. novel_downloader/utils/fontocr/ocr_v2.py +2 -2
  131. novel_downloader/utils/hash_store.py +10 -18
  132. novel_downloader/utils/hash_utils.py +3 -2
  133. novel_downloader/utils/logger.py +2 -3
  134. novel_downloader/utils/network.py +2 -1
  135. novel_downloader/utils/text_utils/chapter_formatting.py +6 -1
  136. novel_downloader/utils/text_utils/font_mapping.py +1 -1
  137. novel_downloader/utils/text_utils/text_cleaning.py +1 -1
  138. novel_downloader/utils/time_utils/datetime_utils.py +3 -3
  139. novel_downloader/utils/time_utils/sleep_utils.py +1 -1
  140. {novel_downloader-1.3.3.dist-info → novel_downloader-1.4.1.dist-info}/METADATA +69 -35
  141. novel_downloader-1.4.1.dist-info/RECORD +170 -0
  142. {novel_downloader-1.3.3.dist-info → novel_downloader-1.4.1.dist-info}/WHEEL +1 -1
  143. {novel_downloader-1.3.3.dist-info → novel_downloader-1.4.1.dist-info}/entry_points.txt +1 -0
  144. novel_downloader/cli/interactive.py +0 -66
  145. novel_downloader/cli/settings.py +0 -177
  146. novel_downloader/config/models.py +0 -187
  147. novel_downloader/core/downloaders/base/__init__.py +0 -14
  148. novel_downloader/core/downloaders/base/base_async.py +0 -153
  149. novel_downloader/core/downloaders/base/base_sync.py +0 -208
  150. novel_downloader/core/downloaders/biquge/__init__.py +0 -14
  151. novel_downloader/core/downloaders/biquge/biquge_async.py +0 -27
  152. novel_downloader/core/downloaders/biquge/biquge_sync.py +0 -27
  153. novel_downloader/core/downloaders/common/__init__.py +0 -14
  154. novel_downloader/core/downloaders/common/common_async.py +0 -210
  155. novel_downloader/core/downloaders/common/common_sync.py +0 -202
  156. novel_downloader/core/downloaders/esjzone/__init__.py +0 -14
  157. novel_downloader/core/downloaders/esjzone/esjzone_async.py +0 -27
  158. novel_downloader/core/downloaders/esjzone/esjzone_sync.py +0 -27
  159. novel_downloader/core/downloaders/qianbi/__init__.py +0 -14
  160. novel_downloader/core/downloaders/qianbi/qianbi_async.py +0 -27
  161. novel_downloader/core/downloaders/qianbi/qianbi_sync.py +0 -27
  162. novel_downloader/core/downloaders/qidian/__init__.py +0 -10
  163. novel_downloader/core/downloaders/qidian/qidian_sync.py +0 -219
  164. novel_downloader/core/downloaders/sfacg/__init__.py +0 -14
  165. novel_downloader/core/downloaders/sfacg/sfacg_async.py +0 -27
  166. novel_downloader/core/downloaders/sfacg/sfacg_sync.py +0 -27
  167. novel_downloader/core/downloaders/yamibo/__init__.py +0 -14
  168. novel_downloader/core/downloaders/yamibo/yamibo_async.py +0 -27
  169. novel_downloader/core/downloaders/yamibo/yamibo_sync.py +0 -27
  170. novel_downloader/core/factory/requester.py +0 -144
  171. novel_downloader/core/factory/saver.py +0 -56
  172. novel_downloader/core/interfaces/async_downloader.py +0 -36
  173. novel_downloader/core/interfaces/async_requester.py +0 -84
  174. novel_downloader/core/interfaces/sync_downloader.py +0 -36
  175. novel_downloader/core/interfaces/sync_requester.py +0 -82
  176. novel_downloader/core/parsers/qidian/browser/__init__.py +0 -12
  177. novel_downloader/core/parsers/qidian/browser/chapter_normal.py +0 -93
  178. novel_downloader/core/parsers/qidian/browser/chapter_router.py +0 -71
  179. novel_downloader/core/parsers/qidian/session/__init__.py +0 -12
  180. novel_downloader/core/parsers/qidian/session/chapter_encrypted.py +0 -443
  181. novel_downloader/core/parsers/qidian/session/chapter_normal.py +0 -115
  182. novel_downloader/core/parsers/qidian/session/main_parser.py +0 -128
  183. novel_downloader/core/parsers/qidian/shared/__init__.py +0 -37
  184. novel_downloader/core/parsers/qidian/shared/book_info_parser.py +0 -150
  185. novel_downloader/core/requesters/base/async_session.py +0 -410
  186. novel_downloader/core/requesters/base/browser.py +0 -337
  187. novel_downloader/core/requesters/base/session.py +0 -378
  188. novel_downloader/core/requesters/biquge/__init__.py +0 -14
  189. novel_downloader/core/requesters/common/__init__.py +0 -17
  190. novel_downloader/core/requesters/common/session.py +0 -113
  191. novel_downloader/core/requesters/esjzone/__init__.py +0 -13
  192. novel_downloader/core/requesters/esjzone/session.py +0 -235
  193. novel_downloader/core/requesters/qianbi/__init__.py +0 -13
  194. novel_downloader/core/requesters/qidian/__init__.py +0 -21
  195. novel_downloader/core/requesters/qidian/broswer.py +0 -307
  196. novel_downloader/core/requesters/qidian/session.py +0 -290
  197. novel_downloader/core/requesters/sfacg/__init__.py +0 -13
  198. novel_downloader/core/requesters/sfacg/session.py +0 -242
  199. novel_downloader/core/requesters/yamibo/__init__.py +0 -13
  200. novel_downloader/core/requesters/yamibo/session.py +0 -237
  201. novel_downloader/core/savers/__init__.py +0 -34
  202. novel_downloader/core/savers/biquge.py +0 -25
  203. novel_downloader/core/savers/common/__init__.py +0 -12
  204. novel_downloader/core/savers/esjzone.py +0 -25
  205. novel_downloader/core/savers/qianbi.py +0 -25
  206. novel_downloader/core/savers/sfacg.py +0 -25
  207. novel_downloader/core/savers/yamibo.py +0 -25
  208. novel_downloader/resources/config/rules.toml +0 -196
  209. novel_downloader-1.3.3.dist-info/RECORD +0 -166
  210. {novel_downloader-1.3.3.dist-info → novel_downloader-1.4.1.dist-info}/licenses/LICENSE +0 -0
  211. {novel_downloader-1.3.3.dist-info → novel_downloader-1.4.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ novel_downloader.core.exporters.qianbi
4
+ --------------------------------------
5
+
6
+ """
7
+
8
+ from novel_downloader.models import ExporterConfig
9
+
10
+ from .common import CommonExporter
11
+
12
+
13
+ class QianbiExporter(CommonExporter):
14
+ def __init__(
15
+ self,
16
+ config: ExporterConfig,
17
+ ):
18
+ super().__init__(
19
+ config,
20
+ site="qianbi",
21
+ chap_folders=["chapters"],
22
+ )
23
+
24
+
25
+ __all__ = ["QianbiExporter"]
@@ -1,22 +1,22 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- novel_downloader.core.savers.qidian
4
- -----------------------------------
3
+ novel_downloader.core.exporters.qidian
4
+ --------------------------------------
5
5
 
6
- This module provides the `QidianSaver` class for handling the saving process
6
+ This module provides the `QidianExporter` class for handling the saving process
7
7
  of novels sourced from Qidian (起点中文网). It implements the platform-specific
8
8
  logic required to structure and export novel content into desired formats.
9
9
  """
10
10
 
11
- from novel_downloader.config.models import SaverConfig
11
+ from novel_downloader.models import ExporterConfig
12
12
 
13
- from .common import CommonSaver
13
+ from .common import CommonExporter
14
14
 
15
15
 
16
- class QidianSaver(CommonSaver):
16
+ class QidianExporter(CommonExporter):
17
17
  def __init__(
18
18
  self,
19
- config: SaverConfig,
19
+ config: ExporterConfig,
20
20
  ):
21
21
  super().__init__(
22
22
  config,
@@ -25,4 +25,4 @@ class QidianSaver(CommonSaver):
25
25
  )
26
26
 
27
27
 
28
- __all__ = ["QidianSaver"]
28
+ __all__ = ["QidianExporter"]
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ novel_downloader.core.exporters.sfacg
4
+ -------------------------------------
5
+
6
+ """
7
+
8
+ from novel_downloader.models import ExporterConfig
9
+
10
+ from .common import CommonExporter
11
+
12
+
13
+ class SfacgExporter(CommonExporter):
14
+ def __init__(
15
+ self,
16
+ config: ExporterConfig,
17
+ ):
18
+ super().__init__(
19
+ config,
20
+ site="sfacg",
21
+ chap_folders=["chapters"],
22
+ )
23
+
24
+
25
+ __all__ = ["SfacgExporter"]
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ novel_downloader.core.exporters.yamibo
4
+ --------------------------------------
5
+
6
+ """
7
+
8
+ from novel_downloader.models import ExporterConfig
9
+
10
+ from .common import CommonExporter
11
+
12
+
13
+ class YamiboExporter(CommonExporter):
14
+ def __init__(
15
+ self,
16
+ config: ExporterConfig,
17
+ ):
18
+ super().__init__(
19
+ config,
20
+ site="yamibo",
21
+ chap_folders=["chapters"],
22
+ )
23
+
24
+
25
+ __all__ = ["YamiboExporter"]
@@ -7,26 +7,14 @@ This package provides factory methods for dynamically retrieving components
7
7
  based on runtime parameters such as site name or content type.
8
8
  """
9
9
 
10
- from .downloader import (
11
- get_async_downloader,
12
- get_downloader,
13
- get_sync_downloader,
14
- )
10
+ from .downloader import get_downloader
11
+ from .exporter import get_exporter
12
+ from .fetcher import get_fetcher
15
13
  from .parser import get_parser
16
- from .requester import (
17
- get_async_requester,
18
- get_requester,
19
- get_sync_requester,
20
- )
21
- from .saver import get_saver
22
14
 
23
15
  __all__ = [
24
- "get_async_downloader",
25
16
  "get_downloader",
26
- "get_sync_downloader",
17
+ "get_exporter",
18
+ "get_fetcher",
27
19
  "get_parser",
28
- "get_async_requester",
29
- "get_requester",
30
- "get_sync_requester",
31
- "get_saver",
32
20
  ]
@@ -1,60 +1,42 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- novel_downloader.core.factory.downloader_factory
4
- ------------------------------------------------
3
+ novel_downloader.core.factory.downloader
4
+ ----------------------------------------
5
5
 
6
6
  This module implements a factory function for creating downloader instances
7
7
  based on the site name and parser mode specified in the configuration.
8
8
  """
9
9
 
10
10
  from collections.abc import Callable
11
- from typing import cast
12
11
 
13
- from novel_downloader.config import DownloaderConfig, load_site_rules
12
+ from novel_downloader.config import load_site_rules
14
13
  from novel_downloader.core.downloaders import (
15
- BiqugeAsyncDownloader,
16
14
  BiqugeDownloader,
17
- CommonAsyncDownloader,
18
15
  CommonDownloader,
19
- EsjzoneAsyncDownloader,
20
16
  EsjzoneDownloader,
21
- QianbiAsyncDownloader,
17
+ LinovelibDownloader,
22
18
  QianbiDownloader,
23
19
  QidianDownloader,
24
- SfacgAsyncDownloader,
25
20
  SfacgDownloader,
26
- YamiboAsyncDownloader,
27
21
  YamiboDownloader,
28
22
  )
29
23
  from novel_downloader.core.interfaces import (
30
- AsyncDownloaderProtocol,
31
- AsyncRequesterProtocol,
24
+ DownloaderProtocol,
25
+ ExporterProtocol,
26
+ FetcherProtocol,
32
27
  ParserProtocol,
33
- SaverProtocol,
34
- SyncDownloaderProtocol,
35
- SyncRequesterProtocol,
36
28
  )
29
+ from novel_downloader.models import DownloaderConfig
37
30
 
38
- AsyncDownloaderBuilder = Callable[
39
- [AsyncRequesterProtocol, ParserProtocol, SaverProtocol, DownloaderConfig],
40
- AsyncDownloaderProtocol,
31
+ DownloaderBuilder = Callable[
32
+ [FetcherProtocol, ParserProtocol, ExporterProtocol, DownloaderConfig],
33
+ DownloaderProtocol,
41
34
  ]
42
35
 
43
- SyncDownloaderBuilder = Callable[
44
- [SyncRequesterProtocol, ParserProtocol, SaverProtocol, DownloaderConfig],
45
- SyncDownloaderProtocol,
46
- ]
47
-
48
- _async_site_map: dict[str, AsyncDownloaderBuilder] = {
49
- "biquge": BiqugeAsyncDownloader,
50
- "esjzone": EsjzoneAsyncDownloader,
51
- "qianbi": QianbiAsyncDownloader,
52
- "sfacg": SfacgAsyncDownloader,
53
- "yamibo": YamiboAsyncDownloader,
54
- }
55
- _sync_site_map: dict[str, SyncDownloaderBuilder] = {
36
+ _site_map: dict[str, DownloaderBuilder] = {
56
37
  "biquge": BiqugeDownloader,
57
38
  "esjzone": EsjzoneDownloader,
39
+ "linovelib": LinovelibDownloader,
58
40
  "qianbi": QianbiDownloader,
59
41
  "qidian": QidianDownloader,
60
42
  "sfacg": SfacgDownloader,
@@ -62,117 +44,33 @@ _sync_site_map: dict[str, SyncDownloaderBuilder] = {
62
44
  }
63
45
 
64
46
 
65
- def get_async_downloader(
66
- requester: AsyncRequesterProtocol,
67
- parser: ParserProtocol,
68
- saver: SaverProtocol,
69
- site: str,
70
- config: DownloaderConfig,
71
- ) -> AsyncDownloaderProtocol:
72
- """
73
- Returns an AsyncDownloaderProtocol for the given site.
74
-
75
- :param requester: Requester implementation
76
- :param parser: Parser implementation
77
- :param saver: Saver implementation
78
- :param site: Site name (e.g., 'qidian')
79
- :param config: Downloader configuration
80
-
81
- :return: An instance of a downloader class
82
-
83
- :raises ValueError: If a site-specific downloader does not support async mode.
84
- :raises TypeError: If the provided requester does not match the required protocol.
85
- """
86
- site_key = site.lower()
87
-
88
- if not isinstance(requester, AsyncRequesterProtocol):
89
- raise TypeError("Async mode requires an AsyncRequesterProtocol")
90
-
91
- # site-specific
92
- if site_key in _async_site_map:
93
- return _async_site_map[site_key](requester, parser, saver, config)
94
-
95
- # fallback
96
- site_rules = load_site_rules()
97
- site_rule = site_rules.get(site_key)
98
- if site_rule is None:
99
- raise ValueError(f"Unsupported site: {site}")
100
-
101
- return CommonAsyncDownloader(requester, parser, saver, config, site_key)
102
-
103
-
104
- def get_sync_downloader(
105
- requester: SyncRequesterProtocol,
47
+ def get_downloader(
48
+ fetcher: FetcherProtocol,
106
49
  parser: ParserProtocol,
107
- saver: SaverProtocol,
50
+ exporter: ExporterProtocol,
108
51
  site: str,
109
52
  config: DownloaderConfig,
110
- ) -> SyncDownloaderProtocol:
53
+ ) -> DownloaderProtocol:
111
54
  """
112
- Returns a DownloaderProtocol for the given site.
113
- First tries a site-specific downloader (e.g. QidianDownloader),
114
- otherwise falls back to CommonDownloader.
55
+ Returns an DownloaderProtocol for the given site.
115
56
 
116
- :param requester: Requester implementation
57
+ :param fetcher: Fetcher implementation
117
58
  :param parser: Parser implementation
118
- :param saver: Saver implementation
59
+ :param exporter: Exporter implementation
119
60
  :param site: Site name (e.g., 'qidian')
120
61
  :param config: Downloader configuration
121
62
 
122
63
  :return: An instance of a downloader class
123
-
124
- :raises ValueError: If a site-specific downloader does not support async mode.
125
- :raises TypeError: If the provided requester does not match the required protocol.
126
64
  """
127
65
  site_key = site.lower()
128
66
 
129
- if not isinstance(requester, SyncRequesterProtocol):
130
- raise TypeError("Sync mode requires a RequesterProtocol")
131
-
132
67
  # site-specific
133
- if site_key in _sync_site_map:
134
- return _sync_site_map[site_key](requester, parser, saver, config)
68
+ if site_key in _site_map:
69
+ return _site_map[site_key](fetcher, parser, exporter, config)
135
70
 
136
71
  # fallback
137
72
  site_rules = load_site_rules()
138
- site_rule = site_rules.get(site_key)
139
- if site_rule is None:
73
+ if site_key not in site_rules:
140
74
  raise ValueError(f"Unsupported site: {site}")
141
75
 
142
- return CommonDownloader(requester, parser, saver, config, site_key)
143
-
144
-
145
- def get_downloader(
146
- requester: AsyncRequesterProtocol | SyncRequesterProtocol,
147
- parser: ParserProtocol,
148
- saver: SaverProtocol,
149
- site: str,
150
- config: DownloaderConfig,
151
- ) -> AsyncDownloaderProtocol | SyncDownloaderProtocol:
152
- """
153
- Dispatches to get_async_downloader if config.mode == 'async',
154
- otherwise to get_sync_downloader.
155
-
156
- :param requester: Requester implementation
157
- :param parser: Parser implementation
158
- :param saver: Saver implementation
159
- :param site: Site name (e.g., 'qidian')
160
- :param config: Downloader configuration
161
-
162
- :return: An instance of a downloader class
163
-
164
- :raises ValueError: If a site-specific downloader does not support async mode.
165
- :raises TypeError: If the provided requester does not match the required protocol.
166
- """
167
- if requester.is_async():
168
- if config.mode.lower() != "async":
169
- raise TypeError("Requester is async, but config.mode is not 'async'")
170
- async_requester = cast(AsyncRequesterProtocol, requester)
171
- return get_async_downloader(async_requester, parser, saver, site, config)
172
- else:
173
- if config.mode.lower() not in ("browser", "session"):
174
- raise TypeError(
175
- "Requester is sync, but config.mode is not 'browser' or 'session'"
176
- )
177
- sync_requester = cast(SyncRequesterProtocol, requester)
178
- return get_sync_downloader(sync_requester, parser, saver, site, config)
76
+ return CommonDownloader(fetcher, parser, exporter, config, site_key)
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ novel_downloader.core.factory.exporter
4
+ --------------------------------------
5
+
6
+ This module implements a factory function for creating exporter instances
7
+ based on the site name.
8
+ """
9
+
10
+ from collections.abc import Callable
11
+
12
+ from novel_downloader.config import load_site_rules
13
+ from novel_downloader.core.exporters import (
14
+ BiqugeExporter,
15
+ CommonExporter,
16
+ EsjzoneExporter,
17
+ LinovelibExporter,
18
+ QianbiExporter,
19
+ QidianExporter,
20
+ SfacgExporter,
21
+ YamiboExporter,
22
+ )
23
+ from novel_downloader.core.interfaces import ExporterProtocol
24
+ from novel_downloader.models import ExporterConfig
25
+
26
+ ExporterBuilder = Callable[[ExporterConfig], ExporterProtocol]
27
+
28
+ _site_map: dict[str, ExporterBuilder] = {
29
+ "biquge": BiqugeExporter,
30
+ "esjzone": EsjzoneExporter,
31
+ "linovelib": LinovelibExporter,
32
+ "qianbi": QianbiExporter,
33
+ "qidian": QidianExporter,
34
+ "sfacg": SfacgExporter,
35
+ "yamibo": YamiboExporter,
36
+ }
37
+
38
+
39
+ def get_exporter(site: str, config: ExporterConfig) -> ExporterProtocol:
40
+ """
41
+ Returns a site-specific exporter instance.
42
+
43
+ :param site: Site name (e.g., 'qidian')
44
+ :param config: Configuration for the exporter
45
+ :return: An instance of a exporter class
46
+ """
47
+ site_key = site.lower()
48
+
49
+ # site-specific
50
+ if site_key in _site_map:
51
+ return _site_map[site_key](config)
52
+
53
+ # Fallback
54
+ site_rules = load_site_rules()
55
+ if site_key not in site_rules:
56
+ raise ValueError(f"Unsupported site: {site}")
57
+
58
+ return CommonExporter(config, site_key)
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ novel_downloader.core.factory.fetcher
4
+ -------------------------------------
5
+
6
+ This module implements a factory function for retrieving fetcher instances
7
+ based on the target novel platform (site).
8
+ """
9
+
10
+ from collections.abc import Callable
11
+
12
+ from novel_downloader.config import load_site_rules
13
+ from novel_downloader.core.fetchers import (
14
+ BiqugeBrowser,
15
+ BiqugeSession,
16
+ CommonBrowser,
17
+ CommonSession,
18
+ EsjzoneBrowser,
19
+ EsjzoneSession,
20
+ LinovelibBrowser,
21
+ LinovelibSession,
22
+ QianbiBrowser,
23
+ QianbiSession,
24
+ QidianBrowser,
25
+ QidianSession,
26
+ SfacgBrowser,
27
+ SfacgSession,
28
+ YamiboBrowser,
29
+ YamiboSession,
30
+ )
31
+ from novel_downloader.core.interfaces import FetcherProtocol
32
+ from novel_downloader.models import FetcherConfig
33
+
34
+ FetcherBuilder = Callable[[FetcherConfig], FetcherProtocol]
35
+
36
+ _site_map: dict[str, dict[str, FetcherBuilder]] = {
37
+ "biquge": {
38
+ "browser": BiqugeBrowser,
39
+ "session": BiqugeSession,
40
+ },
41
+ "esjzone": {
42
+ "browser": EsjzoneBrowser,
43
+ "session": EsjzoneSession,
44
+ },
45
+ "linovelib": {
46
+ "browser": LinovelibBrowser,
47
+ "session": LinovelibSession,
48
+ },
49
+ "qianbi": {
50
+ "browser": QianbiBrowser,
51
+ "session": QianbiSession,
52
+ },
53
+ "qidian": {
54
+ "browser": QidianBrowser,
55
+ "session": QidianSession,
56
+ },
57
+ "sfacg": {
58
+ "browser": SfacgBrowser,
59
+ "session": SfacgSession,
60
+ },
61
+ "yamibo": {
62
+ "browser": YamiboBrowser,
63
+ "session": YamiboSession,
64
+ },
65
+ }
66
+
67
+
68
+ def get_fetcher(
69
+ site: str,
70
+ config: FetcherConfig,
71
+ ) -> FetcherProtocol:
72
+ """
73
+ Returns an FetcherProtocol for the given site.
74
+
75
+ :param site: Site name (e.g., 'qidian')
76
+ :param config: Configuration for the requester
77
+ :return: An instance of a requester class
78
+ """
79
+ site_key = site.lower()
80
+ mode = config.mode
81
+
82
+ # site-specific
83
+ fetcher_cls = _site_map.get(site_key, {}).get(mode)
84
+ if fetcher_cls is not None:
85
+ return fetcher_cls(config)
86
+
87
+ # fallback: use Common based on mode
88
+ site_rules = load_site_rules()
89
+ site_rule = site_rules.get(site_key)
90
+ if site_rule is None:
91
+ raise ValueError(f"Unsupported site: {site}")
92
+ profile = site_rule["profile"]
93
+
94
+ if mode == "browser":
95
+ return CommonBrowser(site_key, profile, config)
96
+ return CommonSession(site_key, profile, config)
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- novel_downloader.core.factory.parser_factory
4
- --------------------------------------------
3
+ novel_downloader.core.factory.parser
4
+ ------------------------------------
5
5
 
6
6
  This module implements a factory function for creating parser instances
7
7
  based on the site name and parser mode specified in the configuration.
@@ -9,45 +9,50 @@ based on the site name and parser mode specified in the configuration.
9
9
 
10
10
  from collections.abc import Callable
11
11
 
12
- from novel_downloader.config import ParserConfig, load_site_rules
12
+ from novel_downloader.config import load_site_rules
13
13
  from novel_downloader.core.interfaces import ParserProtocol
14
14
  from novel_downloader.core.parsers import (
15
15
  BiqugeParser,
16
16
  CommonParser,
17
17
  EsjzoneParser,
18
+ LinovelibParser,
18
19
  QianbiParser,
19
- QidianBrowserParser,
20
- QidianSessionParser,
20
+ QidianParser,
21
21
  SfacgParser,
22
22
  YamiboParser,
23
23
  )
24
+ from novel_downloader.models import ParserConfig
24
25
 
25
26
  ParserBuilder = Callable[[ParserConfig], ParserProtocol]
26
27
 
27
28
  _site_map: dict[str, dict[str, ParserBuilder]] = {
28
29
  "biquge": {
30
+ "browser": BiqugeParser,
29
31
  "session": BiqugeParser,
30
- "async": BiqugeParser,
31
32
  },
32
33
  "esjzone": {
34
+ "browser": EsjzoneParser,
33
35
  "session": EsjzoneParser,
34
- "async": EsjzoneParser,
36
+ },
37
+ "linovelib": {
38
+ "browser": LinovelibParser,
39
+ "session": LinovelibParser,
35
40
  },
36
41
  "qianbi": {
42
+ "browser": QianbiParser,
37
43
  "session": QianbiParser,
38
- "async": QianbiParser,
39
44
  },
40
45
  "qidian": {
41
- "browser": QidianBrowserParser,
42
- "session": QidianSessionParser,
46
+ "browser": QidianParser,
47
+ "session": QidianParser,
43
48
  },
44
49
  "sfacg": {
50
+ "browser": SfacgParser,
45
51
  "session": SfacgParser,
46
- "async": SfacgParser,
47
52
  },
48
53
  "yamibo": {
54
+ "browser": YamiboParser,
49
55
  "session": YamiboParser,
50
- "async": YamiboParser,
51
56
  },
52
57
  }
53
58
 
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- novel_downloader.core.requesters
4
- --------------------------------
3
+ novel_downloader.core.fetchers
4
+ ------------------------------
5
5
 
6
- This package provides requester implementations for different novel platforms.
6
+ This package provides fetcher implementations for different novel platforms.
7
7
  Each submodule corresponds to a specific site and encapsulates the logic needed
8
8
  to perform network interactions, such as logging in, sending requests,
9
9
  or interacting with browser/session-based sources.
@@ -11,6 +11,7 @@ or interacting with browser/session-based sources.
11
11
  Subpackages:
12
12
  - biquge (笔趣阁)
13
13
  - esjzone (ESJ Zone)
14
+ - linovelib (哔哩轻小说)
14
15
  - qianbi (铅笔小说)
15
16
  - qidian (起点中文网)
16
17
  - sfacg (SF轻小说)
@@ -19,19 +20,23 @@ Subpackages:
19
20
  """
20
21
 
21
22
  from .biquge import (
22
- BiqugeAsyncSession,
23
+ BiqugeBrowser,
23
24
  BiqugeSession,
24
25
  )
25
26
  from .common import (
26
- CommonAsyncSession,
27
+ CommonBrowser,
27
28
  CommonSession,
28
29
  )
29
30
  from .esjzone import (
30
- EsjzoneAsyncSession,
31
+ EsjzoneBrowser,
31
32
  EsjzoneSession,
32
33
  )
34
+ from .linovelib import (
35
+ LinovelibBrowser,
36
+ LinovelibSession,
37
+ )
33
38
  from .qianbi import (
34
- QianbiAsyncSession,
39
+ QianbiBrowser,
35
40
  QianbiSession,
36
41
  )
37
42
  from .qidian import (
@@ -39,27 +44,29 @@ from .qidian import (
39
44
  QidianSession,
40
45
  )
41
46
  from .sfacg import (
42
- SfacgAsyncSession,
47
+ SfacgBrowser,
43
48
  SfacgSession,
44
49
  )
45
50
  from .yamibo import (
46
- YamiboAsyncSession,
51
+ YamiboBrowser,
47
52
  YamiboSession,
48
53
  )
49
54
 
50
55
  __all__ = [
51
- "BiqugeAsyncSession",
56
+ "BiqugeBrowser",
52
57
  "BiqugeSession",
53
- "CommonAsyncSession",
58
+ "CommonBrowser",
54
59
  "CommonSession",
55
- "EsjzoneAsyncSession",
60
+ "EsjzoneBrowser",
56
61
  "EsjzoneSession",
57
- "QianbiAsyncSession",
62
+ "LinovelibBrowser",
63
+ "LinovelibSession",
64
+ "QianbiBrowser",
58
65
  "QianbiSession",
59
66
  "QidianBrowser",
60
67
  "QidianSession",
61
- "SfacgAsyncSession",
68
+ "SfacgBrowser",
62
69
  "SfacgSession",
63
- "YamiboAsyncSession",
70
+ "YamiboBrowser",
64
71
  "YamiboSession",
65
72
  ]
@@ -1,16 +1,14 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- novel_downloader.core.requesters.base
4
- -------------------------------------
3
+ novel_downloader.core.fetchers.base
4
+ -----------------------------------
5
5
 
6
6
  """
7
7
 
8
- from .async_session import BaseAsyncSession
9
8
  from .browser import BaseBrowser
10
9
  from .session import BaseSession
11
10
 
12
11
  __all__ = [
13
- "BaseAsyncSession",
14
12
  "BaseBrowser",
15
13
  "BaseSession",
16
14
  ]