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.
- novel_downloader/__init__.py +1 -1
- novel_downloader/cli/__init__.py +2 -4
- novel_downloader/cli/clean.py +21 -88
- novel_downloader/cli/config.py +27 -104
- novel_downloader/cli/download.py +78 -66
- novel_downloader/cli/export.py +20 -21
- novel_downloader/cli/main.py +3 -1
- novel_downloader/cli/search.py +120 -0
- novel_downloader/cli/ui.py +156 -0
- novel_downloader/config/__init__.py +10 -14
- novel_downloader/config/adapter.py +195 -99
- novel_downloader/config/{loader.py → file_io.py} +53 -27
- novel_downloader/core/__init__.py +14 -13
- novel_downloader/core/archived/deqixs/fetcher.py +115 -0
- novel_downloader/core/archived/deqixs/parser.py +132 -0
- novel_downloader/core/archived/deqixs/searcher.py +89 -0
- novel_downloader/core/archived/qidian/searcher.py +79 -0
- novel_downloader/core/archived/wanbengo/searcher.py +98 -0
- novel_downloader/core/archived/xshbook/searcher.py +93 -0
- novel_downloader/core/downloaders/__init__.py +8 -30
- novel_downloader/core/downloaders/base.py +182 -30
- novel_downloader/core/downloaders/common.py +217 -384
- novel_downloader/core/downloaders/qianbi.py +332 -4
- novel_downloader/core/downloaders/qidian.py +250 -290
- novel_downloader/core/downloaders/registry.py +69 -0
- novel_downloader/core/downloaders/signals.py +46 -0
- novel_downloader/core/exporters/__init__.py +8 -26
- novel_downloader/core/exporters/base.py +107 -31
- novel_downloader/core/exporters/common/__init__.py +3 -4
- novel_downloader/core/exporters/common/epub.py +92 -171
- novel_downloader/core/exporters/common/main_exporter.py +14 -67
- novel_downloader/core/exporters/common/txt.py +90 -86
- novel_downloader/core/exporters/epub_util.py +184 -1327
- novel_downloader/core/exporters/linovelib/__init__.py +3 -2
- novel_downloader/core/exporters/linovelib/epub.py +165 -222
- novel_downloader/core/exporters/linovelib/main_exporter.py +10 -71
- novel_downloader/core/exporters/linovelib/txt.py +76 -66
- novel_downloader/core/exporters/qidian.py +15 -11
- novel_downloader/core/exporters/registry.py +55 -0
- novel_downloader/core/exporters/txt_util.py +67 -0
- novel_downloader/core/fetchers/__init__.py +57 -56
- novel_downloader/core/fetchers/aaatxt.py +83 -0
- novel_downloader/core/fetchers/{biquge/session.py → b520.py} +10 -10
- novel_downloader/core/fetchers/{base/session.py → base.py} +63 -47
- novel_downloader/core/fetchers/biquyuedu.py +83 -0
- novel_downloader/core/fetchers/dxmwx.py +110 -0
- novel_downloader/core/fetchers/eightnovel.py +139 -0
- novel_downloader/core/fetchers/{esjzone/session.py → esjzone.py} +23 -11
- novel_downloader/core/fetchers/guidaye.py +85 -0
- novel_downloader/core/fetchers/hetushu.py +92 -0
- novel_downloader/core/fetchers/{qianbi/browser.py → i25zw.py} +22 -26
- novel_downloader/core/fetchers/ixdzs8.py +113 -0
- novel_downloader/core/fetchers/jpxs123.py +101 -0
- novel_downloader/core/fetchers/{biquge/browser.py → lewenn.py} +15 -15
- novel_downloader/core/fetchers/{linovelib/session.py → linovelib.py} +16 -12
- novel_downloader/core/fetchers/piaotia.py +105 -0
- novel_downloader/core/fetchers/qbtr.py +101 -0
- novel_downloader/core/fetchers/{qianbi/session.py → qianbi.py} +9 -9
- novel_downloader/core/fetchers/{qidian/session.py → qidian.py} +55 -40
- novel_downloader/core/fetchers/quanben5.py +92 -0
- novel_downloader/core/fetchers/{base/rate_limiter.py → rate_limiter.py} +2 -2
- novel_downloader/core/fetchers/registry.py +60 -0
- novel_downloader/core/fetchers/{sfacg/session.py → sfacg.py} +11 -9
- novel_downloader/core/fetchers/shencou.py +106 -0
- novel_downloader/core/fetchers/{common/browser.py → shuhaige.py} +24 -19
- novel_downloader/core/fetchers/tongrenquan.py +84 -0
- novel_downloader/core/fetchers/ttkan.py +95 -0
- novel_downloader/core/fetchers/{common/session.py → wanbengo.py} +21 -17
- novel_downloader/core/fetchers/xiaoshuowu.py +106 -0
- novel_downloader/core/fetchers/xiguashuwu.py +177 -0
- novel_downloader/core/fetchers/xs63b.py +171 -0
- novel_downloader/core/fetchers/xshbook.py +85 -0
- novel_downloader/core/fetchers/{yamibo/session.py → yamibo.py} +23 -11
- novel_downloader/core/fetchers/yibige.py +114 -0
- novel_downloader/core/interfaces/__init__.py +8 -14
- novel_downloader/core/interfaces/downloader.py +6 -2
- novel_downloader/core/interfaces/exporter.py +7 -7
- novel_downloader/core/interfaces/fetcher.py +4 -17
- novel_downloader/core/interfaces/parser.py +5 -6
- novel_downloader/core/interfaces/searcher.py +26 -0
- novel_downloader/core/parsers/__init__.py +58 -22
- novel_downloader/core/parsers/aaatxt.py +132 -0
- novel_downloader/core/parsers/b520.py +116 -0
- novel_downloader/core/parsers/base.py +63 -12
- novel_downloader/core/parsers/biquyuedu.py +133 -0
- novel_downloader/core/parsers/dxmwx.py +162 -0
- novel_downloader/core/parsers/eightnovel.py +224 -0
- novel_downloader/core/parsers/{esjzone/main_parser.py → esjzone.py} +67 -67
- novel_downloader/core/parsers/guidaye.py +128 -0
- novel_downloader/core/parsers/hetushu.py +139 -0
- novel_downloader/core/parsers/i25zw.py +137 -0
- novel_downloader/core/parsers/ixdzs8.py +186 -0
- novel_downloader/core/parsers/jpxs123.py +137 -0
- novel_downloader/core/parsers/lewenn.py +142 -0
- novel_downloader/core/parsers/{linovelib/main_parser.py → linovelib.py} +54 -65
- novel_downloader/core/parsers/piaotia.py +189 -0
- novel_downloader/core/parsers/qbtr.py +136 -0
- novel_downloader/core/parsers/{qianbi/main_parser.py → qianbi.py} +54 -51
- novel_downloader/core/parsers/qidian/__init__.py +2 -2
- novel_downloader/core/parsers/qidian/book_info_parser.py +58 -59
- novel_downloader/core/parsers/qidian/chapter_encrypted.py +290 -346
- novel_downloader/core/parsers/qidian/chapter_normal.py +25 -56
- novel_downloader/core/parsers/qidian/main_parser.py +19 -57
- novel_downloader/core/parsers/qidian/utils/__init__.py +12 -11
- novel_downloader/core/parsers/qidian/utils/decryptor_fetcher.py +6 -7
- novel_downloader/core/parsers/qidian/utils/fontmap_recover.py +143 -0
- novel_downloader/core/parsers/qidian/utils/helpers.py +0 -4
- novel_downloader/core/parsers/qidian/utils/node_decryptor.py +2 -2
- novel_downloader/core/parsers/quanben5.py +103 -0
- novel_downloader/core/parsers/registry.py +57 -0
- novel_downloader/core/parsers/{sfacg/main_parser.py → sfacg.py} +46 -48
- novel_downloader/core/parsers/shencou.py +215 -0
- novel_downloader/core/parsers/shuhaige.py +111 -0
- novel_downloader/core/parsers/tongrenquan.py +116 -0
- novel_downloader/core/parsers/ttkan.py +132 -0
- novel_downloader/core/parsers/wanbengo.py +191 -0
- novel_downloader/core/parsers/xiaoshuowu.py +173 -0
- novel_downloader/core/parsers/xiguashuwu.py +435 -0
- novel_downloader/core/parsers/xs63b.py +161 -0
- novel_downloader/core/parsers/xshbook.py +134 -0
- novel_downloader/core/parsers/yamibo.py +155 -0
- novel_downloader/core/parsers/yibige.py +166 -0
- novel_downloader/core/searchers/__init__.py +51 -0
- novel_downloader/core/searchers/aaatxt.py +107 -0
- novel_downloader/core/searchers/b520.py +84 -0
- novel_downloader/core/searchers/base.py +168 -0
- novel_downloader/core/searchers/dxmwx.py +105 -0
- novel_downloader/core/searchers/eightnovel.py +84 -0
- novel_downloader/core/searchers/esjzone.py +102 -0
- novel_downloader/core/searchers/hetushu.py +92 -0
- novel_downloader/core/searchers/i25zw.py +93 -0
- novel_downloader/core/searchers/ixdzs8.py +107 -0
- novel_downloader/core/searchers/jpxs123.py +107 -0
- novel_downloader/core/searchers/piaotia.py +100 -0
- novel_downloader/core/searchers/qbtr.py +106 -0
- novel_downloader/core/searchers/qianbi.py +165 -0
- novel_downloader/core/searchers/quanben5.py +144 -0
- novel_downloader/core/searchers/registry.py +79 -0
- novel_downloader/core/searchers/shuhaige.py +124 -0
- novel_downloader/core/searchers/tongrenquan.py +110 -0
- novel_downloader/core/searchers/ttkan.py +92 -0
- novel_downloader/core/searchers/xiaoshuowu.py +122 -0
- novel_downloader/core/searchers/xiguashuwu.py +95 -0
- novel_downloader/core/searchers/xs63b.py +104 -0
- novel_downloader/locales/en.json +36 -79
- novel_downloader/locales/zh.json +37 -80
- novel_downloader/models/__init__.py +23 -50
- novel_downloader/models/book.py +44 -0
- novel_downloader/models/config.py +16 -43
- novel_downloader/models/login.py +1 -1
- novel_downloader/models/search.py +21 -0
- novel_downloader/resources/config/settings.toml +39 -74
- novel_downloader/resources/css_styles/intro.css +83 -0
- novel_downloader/resources/css_styles/main.css +30 -89
- novel_downloader/resources/json/xiguashuwu.json +718 -0
- novel_downloader/utils/__init__.py +43 -0
- novel_downloader/utils/chapter_storage.py +247 -226
- novel_downloader/utils/constants.py +5 -50
- novel_downloader/utils/cookies.py +6 -18
- novel_downloader/utils/crypto_utils/__init__.py +13 -0
- novel_downloader/utils/crypto_utils/aes_util.py +90 -0
- novel_downloader/utils/crypto_utils/aes_v1.py +619 -0
- novel_downloader/utils/crypto_utils/aes_v2.py +1143 -0
- novel_downloader/utils/{crypto_utils.py → crypto_utils/rc4.py} +3 -10
- novel_downloader/utils/epub/__init__.py +34 -0
- novel_downloader/utils/epub/builder.py +377 -0
- novel_downloader/utils/epub/constants.py +118 -0
- novel_downloader/utils/epub/documents.py +297 -0
- novel_downloader/utils/epub/models.py +120 -0
- novel_downloader/utils/epub/utils.py +179 -0
- novel_downloader/utils/file_utils/__init__.py +5 -30
- novel_downloader/utils/file_utils/io.py +9 -150
- novel_downloader/utils/file_utils/normalize.py +2 -2
- novel_downloader/utils/file_utils/sanitize.py +2 -7
- novel_downloader/utils/fontocr.py +207 -0
- novel_downloader/utils/i18n.py +2 -0
- novel_downloader/utils/logger.py +10 -16
- novel_downloader/utils/network.py +111 -252
- novel_downloader/utils/state.py +5 -90
- novel_downloader/utils/text_utils/__init__.py +16 -21
- novel_downloader/utils/text_utils/diff_display.py +6 -9
- novel_downloader/utils/text_utils/numeric_conversion.py +253 -0
- novel_downloader/utils/text_utils/text_cleaner.py +179 -0
- novel_downloader/utils/text_utils/truncate_utils.py +62 -0
- novel_downloader/utils/time_utils/__init__.py +6 -12
- novel_downloader/utils/time_utils/datetime_utils.py +23 -33
- novel_downloader/utils/time_utils/sleep_utils.py +5 -10
- novel_downloader/web/__init__.py +13 -0
- novel_downloader/web/components/__init__.py +11 -0
- novel_downloader/web/components/navigation.py +35 -0
- novel_downloader/web/main.py +66 -0
- novel_downloader/web/pages/__init__.py +17 -0
- novel_downloader/web/pages/download.py +78 -0
- novel_downloader/web/pages/progress.py +147 -0
- novel_downloader/web/pages/search.py +329 -0
- novel_downloader/web/services/__init__.py +17 -0
- novel_downloader/web/services/client_dialog.py +164 -0
- novel_downloader/web/services/cred_broker.py +113 -0
- novel_downloader/web/services/cred_models.py +35 -0
- novel_downloader/web/services/task_manager.py +264 -0
- novel_downloader-2.0.0.dist-info/METADATA +171 -0
- novel_downloader-2.0.0.dist-info/RECORD +210 -0
- {novel_downloader-1.4.5.dist-info → novel_downloader-2.0.0.dist-info}/entry_points.txt +1 -1
- novel_downloader/config/site_rules.py +0 -94
- novel_downloader/core/downloaders/biquge.py +0 -25
- novel_downloader/core/downloaders/esjzone.py +0 -25
- novel_downloader/core/downloaders/linovelib.py +0 -25
- novel_downloader/core/downloaders/sfacg.py +0 -25
- novel_downloader/core/downloaders/yamibo.py +0 -25
- novel_downloader/core/exporters/biquge.py +0 -25
- novel_downloader/core/exporters/esjzone.py +0 -25
- novel_downloader/core/exporters/qianbi.py +0 -25
- novel_downloader/core/exporters/sfacg.py +0 -25
- novel_downloader/core/exporters/yamibo.py +0 -25
- novel_downloader/core/factory/__init__.py +0 -20
- novel_downloader/core/factory/downloader.py +0 -73
- novel_downloader/core/factory/exporter.py +0 -58
- novel_downloader/core/factory/fetcher.py +0 -96
- novel_downloader/core/factory/parser.py +0 -86
- novel_downloader/core/fetchers/base/__init__.py +0 -14
- novel_downloader/core/fetchers/base/browser.py +0 -403
- novel_downloader/core/fetchers/biquge/__init__.py +0 -14
- novel_downloader/core/fetchers/common/__init__.py +0 -14
- novel_downloader/core/fetchers/esjzone/__init__.py +0 -14
- novel_downloader/core/fetchers/esjzone/browser.py +0 -204
- novel_downloader/core/fetchers/linovelib/__init__.py +0 -14
- novel_downloader/core/fetchers/linovelib/browser.py +0 -193
- novel_downloader/core/fetchers/qianbi/__init__.py +0 -14
- novel_downloader/core/fetchers/qidian/__init__.py +0 -14
- novel_downloader/core/fetchers/qidian/browser.py +0 -318
- novel_downloader/core/fetchers/sfacg/__init__.py +0 -14
- novel_downloader/core/fetchers/sfacg/browser.py +0 -189
- novel_downloader/core/fetchers/yamibo/__init__.py +0 -14
- novel_downloader/core/fetchers/yamibo/browser.py +0 -229
- novel_downloader/core/parsers/biquge/__init__.py +0 -10
- novel_downloader/core/parsers/biquge/main_parser.py +0 -134
- novel_downloader/core/parsers/common/__init__.py +0 -13
- novel_downloader/core/parsers/common/helper.py +0 -323
- novel_downloader/core/parsers/common/main_parser.py +0 -106
- novel_downloader/core/parsers/esjzone/__init__.py +0 -10
- novel_downloader/core/parsers/linovelib/__init__.py +0 -10
- novel_downloader/core/parsers/qianbi/__init__.py +0 -10
- novel_downloader/core/parsers/sfacg/__init__.py +0 -10
- novel_downloader/core/parsers/yamibo/__init__.py +0 -10
- novel_downloader/core/parsers/yamibo/main_parser.py +0 -194
- novel_downloader/models/browser.py +0 -21
- novel_downloader/models/chapter.py +0 -25
- novel_downloader/models/site_rules.py +0 -99
- novel_downloader/models/tasks.py +0 -33
- novel_downloader/models/types.py +0 -15
- novel_downloader/resources/css_styles/volume-intro.css +0 -56
- novel_downloader/resources/json/replace_word_map.json +0 -4
- novel_downloader/resources/text/blacklist.txt +0 -22
- novel_downloader/tui/__init__.py +0 -7
- novel_downloader/tui/app.py +0 -32
- novel_downloader/tui/main.py +0 -17
- novel_downloader/tui/screens/__init__.py +0 -14
- novel_downloader/tui/screens/home.py +0 -198
- novel_downloader/tui/screens/login.py +0 -74
- novel_downloader/tui/styles/home_layout.tcss +0 -79
- novel_downloader/tui/widgets/richlog_handler.py +0 -24
- novel_downloader/utils/cache.py +0 -24
- novel_downloader/utils/fontocr/__init__.py +0 -22
- novel_downloader/utils/fontocr/model_loader.py +0 -69
- novel_downloader/utils/fontocr/ocr_v1.py +0 -303
- novel_downloader/utils/fontocr/ocr_v2.py +0 -752
- novel_downloader/utils/hash_store.py +0 -279
- novel_downloader/utils/hash_utils.py +0 -103
- novel_downloader/utils/text_utils/chapter_formatting.py +0 -46
- novel_downloader/utils/text_utils/font_mapping.py +0 -28
- novel_downloader/utils/text_utils/text_cleaning.py +0 -107
- novel_downloader-1.4.5.dist-info/METADATA +0 -196
- novel_downloader-1.4.5.dist-info/RECORD +0 -165
- {novel_downloader-1.4.5.dist-info → novel_downloader-2.0.0.dist-info}/WHEEL +0 -0
- {novel_downloader-1.4.5.dist-info → novel_downloader-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {novel_downloader-1.4.5.dist-info → novel_downloader-2.0.0.dist-info}/top_level.txt +0 -0
@@ -1,21 +1,26 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
"""
|
3
|
-
novel_downloader.core.fetchers.esjzone
|
4
|
-
|
3
|
+
novel_downloader.core.fetchers.esjzone
|
4
|
+
--------------------------------------
|
5
5
|
|
6
6
|
"""
|
7
7
|
|
8
8
|
import re
|
9
|
+
from collections.abc import Mapping
|
9
10
|
from typing import Any
|
10
11
|
|
11
12
|
from novel_downloader.core.fetchers.base import BaseSession
|
13
|
+
from novel_downloader.core.fetchers.registry import register_fetcher
|
12
14
|
from novel_downloader.models import FetcherConfig, LoginField
|
13
|
-
from novel_downloader.utils
|
15
|
+
from novel_downloader.utils import async_jitter_sleep
|
14
16
|
|
15
17
|
|
18
|
+
@register_fetcher(
|
19
|
+
site_keys=["esjzone"],
|
20
|
+
)
|
16
21
|
class EsjzoneSession(BaseSession):
|
17
22
|
"""
|
18
|
-
A session class for interacting with the
|
23
|
+
A session class for interacting with the ESJ Zone (www.esjzone.cc) novel website.
|
19
24
|
"""
|
20
25
|
|
21
26
|
BOOKCASE_URL = "https://www.esjzone.cc/my/favorite"
|
@@ -63,7 +68,7 @@ class EsjzoneSession(BaseSession):
|
|
63
68
|
):
|
64
69
|
self._is_logged_in = True
|
65
70
|
return True
|
66
|
-
await
|
71
|
+
await async_jitter_sleep(
|
67
72
|
self.backoff_factor,
|
68
73
|
mul_spread=1.1,
|
69
74
|
max_sleep=self.backoff_factor + 2,
|
@@ -81,7 +86,7 @@ class EsjzoneSession(BaseSession):
|
|
81
86
|
Fetch the raw HTML of the book info page asynchronously.
|
82
87
|
|
83
88
|
:param book_id: The book identifier.
|
84
|
-
:return: The page content as
|
89
|
+
:return: The page content as string list.
|
85
90
|
"""
|
86
91
|
url = self.book_info_url(book_id=book_id)
|
87
92
|
return [await self.fetch(url, **kwargs)]
|
@@ -97,7 +102,7 @@ class EsjzoneSession(BaseSession):
|
|
97
102
|
|
98
103
|
:param book_id: The book identifier.
|
99
104
|
:param chapter_id: The chapter identifier.
|
100
|
-
:return: The
|
105
|
+
:return: The page content as string list.
|
101
106
|
"""
|
102
107
|
url = self.chapter_url(book_id=book_id, chapter_id=chapter_id)
|
103
108
|
return [await self.fetch(url, **kwargs)]
|
@@ -165,10 +170,6 @@ class EsjzoneSession(BaseSession):
|
|
165
170
|
"""
|
166
171
|
return cls.CHAPTER_URL.format(book_id=book_id, chapter_id=chapter_id)
|
167
172
|
|
168
|
-
@property
|
169
|
-
def hostname(self) -> str:
|
170
|
-
return "www.esjzone.cc"
|
171
|
-
|
172
173
|
async def _api_login(self, username: str, password: str) -> bool:
|
173
174
|
"""
|
174
175
|
Login to the API using a 2-step token-based process.
|
@@ -229,3 +230,14 @@ class EsjzoneSession(BaseSession):
|
|
229
230
|
def _extract_token(self, text: str) -> str:
|
230
231
|
match = re.search(r"<JinJing>(.+?)</JinJing>", text)
|
231
232
|
return match.group(1) if match else ""
|
233
|
+
|
234
|
+
@staticmethod
|
235
|
+
def _filter_cookies(
|
236
|
+
raw_cookies: list[Mapping[str, Any]],
|
237
|
+
) -> dict[str, str]:
|
238
|
+
ALLOWED_DOMAINS = {".www.esjzone.cc", "www.esjzone.cc", ".esjzone.cc", ""}
|
239
|
+
return {
|
240
|
+
c["name"]: c["value"]
|
241
|
+
for c in raw_cookies
|
242
|
+
if c.get("domain", "") in ALLOWED_DOMAINS
|
243
|
+
}
|
@@ -0,0 +1,85 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
novel_downloader.core.fetchers.guidaye
|
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=["guidaye"],
|
17
|
+
)
|
18
|
+
class GuidayeSession(BaseSession):
|
19
|
+
"""
|
20
|
+
A session class for interacting with the 名著阅读 (b.guidaye.com) novel website.
|
21
|
+
"""
|
22
|
+
|
23
|
+
BOOK_INFO_URL = "https://b.guidaye.com/{book_id}/"
|
24
|
+
CHAPTER_URL = "https://b.guidaye.com/{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__("guidaye", config, cookies, **kwargs)
|
33
|
+
|
34
|
+
async def get_book_info(
|
35
|
+
self,
|
36
|
+
book_id: str,
|
37
|
+
**kwargs: Any,
|
38
|
+
) -> list[str]:
|
39
|
+
"""
|
40
|
+
Fetch the raw HTML of the book info page asynchronously.
|
41
|
+
|
42
|
+
:param book_id: The book identifier.
|
43
|
+
:return: The page content as string list.
|
44
|
+
"""
|
45
|
+
book_id = book_id.replace("-", "/")
|
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
|
+
book_id = book_id.replace("-", "/")
|
63
|
+
url = self.chapter_url(book_id=book_id, chapter_id=chapter_id)
|
64
|
+
return [await self.fetch(url, **kwargs)]
|
65
|
+
|
66
|
+
@classmethod
|
67
|
+
def book_info_url(cls, book_id: str) -> str:
|
68
|
+
"""
|
69
|
+
Construct the URL for fetching a book's info page.
|
70
|
+
|
71
|
+
:param book_id: The identifier of the book.
|
72
|
+
:return: Fully qualified URL for the book info page.
|
73
|
+
"""
|
74
|
+
return cls.BOOK_INFO_URL.format(book_id=book_id)
|
75
|
+
|
76
|
+
@classmethod
|
77
|
+
def chapter_url(cls, book_id: str, chapter_id: str) -> str:
|
78
|
+
"""
|
79
|
+
Construct the URL for fetching a specific chapter.
|
80
|
+
|
81
|
+
:param book_id: The identifier of the book.
|
82
|
+
:param chapter_id: The identifier of the chapter.
|
83
|
+
:return: Fully qualified chapter URL.
|
84
|
+
"""
|
85
|
+
return cls.CHAPTER_URL.format(book_id=book_id, chapter_id=chapter_id)
|
@@ -0,0 +1,92 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
novel_downloader.core.fetchers.hetushu
|
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=["hetushu"],
|
17
|
+
)
|
18
|
+
class HetushuSession(BaseSession):
|
19
|
+
"""
|
20
|
+
A session class for interacting with the 和图书 (www.hetushu.com) novel website.
|
21
|
+
"""
|
22
|
+
|
23
|
+
BOOK_INFO_URL = "https://{base_url}/book/{book_id}/index.html"
|
24
|
+
CHAPTER_URL = "https://{base_url}/book/{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__("hetushu", config, cookies, **kwargs)
|
33
|
+
self.base_url = (
|
34
|
+
"www.hetushu.com"
|
35
|
+
if config.locale_style == "simplified"
|
36
|
+
else "www.hetubook.com"
|
37
|
+
)
|
38
|
+
|
39
|
+
async def get_book_info(
|
40
|
+
self,
|
41
|
+
book_id: str,
|
42
|
+
**kwargs: Any,
|
43
|
+
) -> list[str]:
|
44
|
+
"""
|
45
|
+
Fetch the raw HTML of the book info page asynchronously.
|
46
|
+
|
47
|
+
:param book_id: The book identifier.
|
48
|
+
:return: The page content as string list.
|
49
|
+
"""
|
50
|
+
url = self.book_info_url(base_url=self.base_url, book_id=book_id)
|
51
|
+
return [await self.fetch(url, **kwargs)]
|
52
|
+
|
53
|
+
async def get_book_chapter(
|
54
|
+
self,
|
55
|
+
book_id: str,
|
56
|
+
chapter_id: str,
|
57
|
+
**kwargs: Any,
|
58
|
+
) -> list[str]:
|
59
|
+
"""
|
60
|
+
Fetch the raw HTML of a single chapter asynchronously.
|
61
|
+
|
62
|
+
:param book_id: The book identifier.
|
63
|
+
:param chapter_id: The chapter identifier.
|
64
|
+
:return: The page content as string list.
|
65
|
+
"""
|
66
|
+
url = self.chapter_url(
|
67
|
+
base_url=self.base_url, book_id=book_id, chapter_id=chapter_id
|
68
|
+
)
|
69
|
+
return [await self.fetch(url, **kwargs)]
|
70
|
+
|
71
|
+
@classmethod
|
72
|
+
def book_info_url(cls, base_url: str, book_id: str) -> str:
|
73
|
+
"""
|
74
|
+
Construct the URL for fetching a book's info page.
|
75
|
+
|
76
|
+
:param book_id: The identifier of the book.
|
77
|
+
:return: Fully qualified URL for the book info page.
|
78
|
+
"""
|
79
|
+
return cls.BOOK_INFO_URL.format(base_url=base_url, book_id=book_id)
|
80
|
+
|
81
|
+
@classmethod
|
82
|
+
def chapter_url(cls, base_url: str, book_id: str, chapter_id: str) -> str:
|
83
|
+
"""
|
84
|
+
Construct the URL for fetching a specific chapter.
|
85
|
+
|
86
|
+
:param book_id: The identifier of the book.
|
87
|
+
:param chapter_id: The identifier of the chapter.
|
88
|
+
:return: Fully qualified chapter URL.
|
89
|
+
"""
|
90
|
+
return cls.CHAPTER_URL.format(
|
91
|
+
base_url=base_url, book_id=book_id, chapter_id=chapter_id
|
92
|
+
)
|
@@ -1,37 +1,37 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
"""
|
3
|
-
novel_downloader.core.fetchers.
|
4
|
-
|
3
|
+
novel_downloader.core.fetchers.i25zw
|
4
|
+
------------------------------------
|
5
5
|
|
6
6
|
"""
|
7
7
|
|
8
|
+
import asyncio
|
8
9
|
from typing import Any
|
9
10
|
|
10
|
-
from novel_downloader.core.fetchers.base import
|
11
|
+
from novel_downloader.core.fetchers.base import BaseSession
|
12
|
+
from novel_downloader.core.fetchers.registry import register_fetcher
|
11
13
|
from novel_downloader.models import FetcherConfig
|
12
14
|
|
13
15
|
|
14
|
-
|
16
|
+
@register_fetcher(
|
17
|
+
site_keys=["i25zw"],
|
18
|
+
)
|
19
|
+
class I25zwSession(BaseSession):
|
15
20
|
"""
|
16
|
-
A
|
21
|
+
A session class for interacting with the 25中文网 (www.i25zw.com) novel website.
|
17
22
|
"""
|
18
23
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
]
|
23
|
-
|
24
|
-
BOOK_INFO_URL = "https://www.23qb.com/book/{book_id}/"
|
25
|
-
BOOK_CATALOG_URL = "https://www.23qb.com/book/{book_id}/catalog"
|
26
|
-
CHAPTER_URL = "https://www.23qb.com/book/{book_id}/{chapter_id}.html"
|
24
|
+
BOOK_INFO_URL = "https://www.i25zw.com/book/{book_id}.html"
|
25
|
+
BOOK_CATALOG_URL = "https://www.i25zw.com/{book_id}/"
|
26
|
+
CHAPTER_URL = "https://www.i25zw.com/{book_id}/{chapter_id}.html"
|
27
27
|
|
28
28
|
def __init__(
|
29
29
|
self,
|
30
30
|
config: FetcherConfig,
|
31
|
-
|
31
|
+
cookies: dict[str, str] | None = None,
|
32
32
|
**kwargs: Any,
|
33
33
|
) -> None:
|
34
|
-
super().__init__("
|
34
|
+
super().__init__("i25zw", config, cookies, **kwargs)
|
35
35
|
|
36
36
|
async def get_book_info(
|
37
37
|
self,
|
@@ -44,14 +44,15 @@ class QianbiBrowser(BaseBrowser):
|
|
44
44
|
Order: [info, catalog]
|
45
45
|
|
46
46
|
:param book_id: The book identifier.
|
47
|
-
:return: The page content as
|
47
|
+
:return: The page content as string list.
|
48
48
|
"""
|
49
49
|
info_url = self.book_info_url(book_id=book_id)
|
50
50
|
catalog_url = self.book_catalog_url(book_id=book_id)
|
51
51
|
|
52
|
-
info_html = await
|
53
|
-
|
54
|
-
|
52
|
+
info_html, catalog_html = await asyncio.gather(
|
53
|
+
self.fetch(info_url, **kwargs),
|
54
|
+
self.fetch(catalog_url, **kwargs),
|
55
|
+
)
|
55
56
|
return [info_html, catalog_html]
|
56
57
|
|
57
58
|
async def get_book_chapter(
|
@@ -65,11 +66,10 @@ class QianbiBrowser(BaseBrowser):
|
|
65
66
|
|
66
67
|
:param book_id: The book identifier.
|
67
68
|
:param chapter_id: The chapter identifier.
|
68
|
-
:return: The
|
69
|
+
:return: The page content as string list.
|
69
70
|
"""
|
70
|
-
catalog_url = self.book_catalog_url(book_id=book_id)
|
71
71
|
url = self.chapter_url(book_id=book_id, chapter_id=chapter_id)
|
72
|
-
return [await self.fetch(url,
|
72
|
+
return [await self.fetch(url, **kwargs)]
|
73
73
|
|
74
74
|
@classmethod
|
75
75
|
def book_info_url(cls, book_id: str) -> str:
|
@@ -101,7 +101,3 @@ class QianbiBrowser(BaseBrowser):
|
|
101
101
|
:return: Fully qualified chapter URL.
|
102
102
|
"""
|
103
103
|
return cls.CHAPTER_URL.format(book_id=book_id, chapter_id=chapter_id)
|
104
|
-
|
105
|
-
@property
|
106
|
-
def hostname(self) -> str:
|
107
|
-
return "www.23qb.com"
|
@@ -0,0 +1,113 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
novel_downloader.core.fetchers.ixdzs8
|
4
|
+
-------------------------------------
|
5
|
+
|
6
|
+
"""
|
7
|
+
|
8
|
+
import asyncio
|
9
|
+
import re
|
10
|
+
from typing import Any
|
11
|
+
|
12
|
+
from novel_downloader.core.fetchers.base import BaseSession
|
13
|
+
from novel_downloader.core.fetchers.registry import register_fetcher
|
14
|
+
from novel_downloader.models import FetcherConfig
|
15
|
+
|
16
|
+
|
17
|
+
@register_fetcher(
|
18
|
+
site_keys=["ixdzs8"],
|
19
|
+
)
|
20
|
+
class Ixdzs8Session(BaseSession):
|
21
|
+
"""
|
22
|
+
A session class for interacting with the 爱下电子书 (ixdzs8.com) novel website.
|
23
|
+
"""
|
24
|
+
|
25
|
+
BOOK_INFO_URL = "https://ixdzs8.com/read/{book_id}/"
|
26
|
+
BOOK_CATALOG_URL = "https://ixdzs8.com/novel/clist/"
|
27
|
+
CHAPTER_URL = "https://ixdzs8.com/read/{book_id}/{chapter_id}.html"
|
28
|
+
_TOKEN_PATTERN = re.compile(r'let\s+token\s*=\s*"([^"]+)"')
|
29
|
+
|
30
|
+
def __init__(
|
31
|
+
self,
|
32
|
+
config: FetcherConfig,
|
33
|
+
cookies: dict[str, str] | None = None,
|
34
|
+
**kwargs: Any,
|
35
|
+
) -> None:
|
36
|
+
super().__init__("ixdzs8", config, cookies, **kwargs)
|
37
|
+
|
38
|
+
async def get_book_info(
|
39
|
+
self,
|
40
|
+
book_id: str,
|
41
|
+
**kwargs: Any,
|
42
|
+
) -> list[str]:
|
43
|
+
"""
|
44
|
+
Fetch the raw HTML of the book info page asynchronously.
|
45
|
+
|
46
|
+
Order: [info, catalog]
|
47
|
+
|
48
|
+
:param book_id: The book identifier.
|
49
|
+
:return: The page content as string list.
|
50
|
+
"""
|
51
|
+
url = self.book_info_url(book_id=book_id)
|
52
|
+
data = {"bid": book_id}
|
53
|
+
info_html, clist_response = await asyncio.gather(
|
54
|
+
self.fetch_verified_html(url, **kwargs),
|
55
|
+
self.post(self.BOOK_CATALOG_URL, data),
|
56
|
+
)
|
57
|
+
catalog_html = await clist_response.text()
|
58
|
+
return [info_html, catalog_html]
|
59
|
+
|
60
|
+
async def get_book_chapter(
|
61
|
+
self,
|
62
|
+
book_id: str,
|
63
|
+
chapter_id: str,
|
64
|
+
**kwargs: Any,
|
65
|
+
) -> list[str]:
|
66
|
+
"""
|
67
|
+
Fetch the raw HTML of a single chapter asynchronously.
|
68
|
+
|
69
|
+
:param book_id: The book identifier.
|
70
|
+
:param chapter_id: The chapter identifier.
|
71
|
+
:return: The page content as string list.
|
72
|
+
"""
|
73
|
+
url = self.chapter_url(book_id=book_id, chapter_id=chapter_id)
|
74
|
+
return [await self.fetch_verified_html(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
|
+
return cls.BOOK_INFO_URL.format(book_id=book_id)
|
85
|
+
|
86
|
+
@classmethod
|
87
|
+
def chapter_url(cls, book_id: str, chapter_id: str) -> str:
|
88
|
+
"""
|
89
|
+
Construct the URL for fetching a specific chapter.
|
90
|
+
|
91
|
+
:param book_id: The identifier of the book.
|
92
|
+
:param chapter_id: The identifier of the chapter.
|
93
|
+
:return: Fully qualified chapter URL.
|
94
|
+
"""
|
95
|
+
return cls.CHAPTER_URL.format(book_id=book_id, chapter_id=chapter_id)
|
96
|
+
|
97
|
+
async def fetch_verified_html(self, url: str, **kwargs: Any) -> str:
|
98
|
+
"""
|
99
|
+
Automatically solving the browser verification challenge if required.
|
100
|
+
"""
|
101
|
+
resp = await self.fetch(url, **kwargs)
|
102
|
+
|
103
|
+
if "正在验证浏览器" not in resp:
|
104
|
+
return resp
|
105
|
+
|
106
|
+
token_match = self._TOKEN_PATTERN.search(resp)
|
107
|
+
if not token_match:
|
108
|
+
raise ValueError("Token not found in page HTML.")
|
109
|
+
token_value = token_match.group(1)
|
110
|
+
|
111
|
+
challenge_url = f"{url}?challenge={token_value}"
|
112
|
+
_ = await self.fetch(challenge_url, **kwargs)
|
113
|
+
return await self.fetch(url, **kwargs)
|
@@ -0,0 +1,101 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
novel_downloader.core.fetchers.jpxs123
|
4
|
+
--------------------------------------
|
5
|
+
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Any
|
9
|
+
|
10
|
+
from lxml import html
|
11
|
+
|
12
|
+
from novel_downloader.core.fetchers.base import BaseSession
|
13
|
+
from novel_downloader.core.fetchers.registry import register_fetcher
|
14
|
+
from novel_downloader.models import FetcherConfig
|
15
|
+
|
16
|
+
|
17
|
+
@register_fetcher(
|
18
|
+
site_keys=["jpxs123"],
|
19
|
+
)
|
20
|
+
class Jpxs123Session(BaseSession):
|
21
|
+
"""
|
22
|
+
A session class for interacting with the 精品小说网 (www.jpxs123.com) novel website.
|
23
|
+
"""
|
24
|
+
|
25
|
+
BASE_URL = "https://www.jpxs123.com"
|
26
|
+
BOOK_INFO_URL = "https://www.jpxs123.com/{book_id}.html"
|
27
|
+
CHAPTER_URL = "https://www.jpxs123.com/{book_id}/{chapter_id}.html"
|
28
|
+
|
29
|
+
def __init__(
|
30
|
+
self,
|
31
|
+
config: FetcherConfig,
|
32
|
+
cookies: dict[str, str] | None = None,
|
33
|
+
**kwargs: Any,
|
34
|
+
) -> None:
|
35
|
+
super().__init__("jpxs123", config, cookies, **kwargs)
|
36
|
+
|
37
|
+
async def get_book_info(
|
38
|
+
self,
|
39
|
+
book_id: str,
|
40
|
+
**kwargs: Any,
|
41
|
+
) -> list[str]:
|
42
|
+
"""
|
43
|
+
Fetch the raw HTML of the book info page asynchronously.
|
44
|
+
|
45
|
+
Order: [info, download]
|
46
|
+
|
47
|
+
:param book_id: The book identifier.
|
48
|
+
:return: The page content as string list.
|
49
|
+
"""
|
50
|
+
book_id = book_id.replace("-", "/")
|
51
|
+
url = self.book_info_url(book_id=book_id)
|
52
|
+
info_html = await self.fetch(url, **kwargs)
|
53
|
+
try:
|
54
|
+
info_tree = html.fromstring(info_html)
|
55
|
+
txt_link = info_tree.xpath(
|
56
|
+
'//div[@class="booktips"]//a[contains(text(), "txt下载")]/@href'
|
57
|
+
)
|
58
|
+
download_url = f"{self.BASE_URL}{txt_link[0]}" if txt_link else None
|
59
|
+
except Exception:
|
60
|
+
download_url = None
|
61
|
+
|
62
|
+
download_html = await self.fetch(download_url, **kwargs) if download_url else ""
|
63
|
+
return [info_html, download_html]
|
64
|
+
|
65
|
+
async def get_book_chapter(
|
66
|
+
self,
|
67
|
+
book_id: str,
|
68
|
+
chapter_id: str,
|
69
|
+
**kwargs: Any,
|
70
|
+
) -> list[str]:
|
71
|
+
"""
|
72
|
+
Fetch the raw HTML of a single chapter asynchronously.
|
73
|
+
|
74
|
+
:param book_id: The book identifier.
|
75
|
+
:param chapter_id: The chapter identifier.
|
76
|
+
:return: The page content as string list.
|
77
|
+
"""
|
78
|
+
book_id = book_id.replace("-", "/")
|
79
|
+
url = self.chapter_url(book_id=book_id, chapter_id=chapter_id)
|
80
|
+
return [await self.fetch(url, **kwargs)]
|
81
|
+
|
82
|
+
@classmethod
|
83
|
+
def book_info_url(cls, book_id: str) -> str:
|
84
|
+
"""
|
85
|
+
Construct the URL for fetching a book's info page.
|
86
|
+
|
87
|
+
:param book_id: The identifier of the book.
|
88
|
+
:return: Fully qualified URL for the book info page.
|
89
|
+
"""
|
90
|
+
return cls.BOOK_INFO_URL.format(book_id=book_id)
|
91
|
+
|
92
|
+
@classmethod
|
93
|
+
def chapter_url(cls, book_id: str, chapter_id: str) -> str:
|
94
|
+
"""
|
95
|
+
Construct the URL for fetching a specific chapter.
|
96
|
+
|
97
|
+
:param book_id: The identifier of the book.
|
98
|
+
:param chapter_id: The identifier of the chapter.
|
99
|
+
:return: Fully qualified chapter URL.
|
100
|
+
"""
|
101
|
+
return cls.CHAPTER_URL.format(book_id=book_id, chapter_id=chapter_id)
|
@@ -1,31 +1,35 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
"""
|
3
|
-
novel_downloader.core.fetchers.
|
4
|
-
|
3
|
+
novel_downloader.core.fetchers.lewenn
|
4
|
+
-------------------------------------
|
5
5
|
|
6
6
|
"""
|
7
7
|
|
8
8
|
from typing import Any
|
9
9
|
|
10
|
-
from novel_downloader.core.fetchers.base import
|
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
|
12
13
|
|
13
14
|
|
14
|
-
|
15
|
+
@register_fetcher(
|
16
|
+
site_keys=["lewenn", "lewen"],
|
17
|
+
)
|
18
|
+
class LewennSession(BaseSession):
|
15
19
|
"""
|
16
|
-
A
|
20
|
+
A session class for interacting with the 乐文小说网 (www.lewenn.net) novel website.
|
17
21
|
"""
|
18
22
|
|
19
|
-
BOOK_INFO_URL = "
|
20
|
-
CHAPTER_URL = "
|
23
|
+
BOOK_INFO_URL = "https://www.lewenn.net/{book_id}/"
|
24
|
+
CHAPTER_URL = "https://www.lewenn.net/{book_id}/{chapter_id}.html"
|
21
25
|
|
22
26
|
def __init__(
|
23
27
|
self,
|
24
28
|
config: FetcherConfig,
|
25
|
-
|
29
|
+
cookies: dict[str, str] | None = None,
|
26
30
|
**kwargs: Any,
|
27
31
|
) -> None:
|
28
|
-
super().__init__("
|
32
|
+
super().__init__("lewenn", config, cookies, **kwargs)
|
29
33
|
|
30
34
|
async def get_book_info(
|
31
35
|
self,
|
@@ -36,7 +40,7 @@ class BiqugeBrowser(BaseBrowser):
|
|
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
|
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,7 +56,7 @@ class BiqugeBrowser(BaseBrowser):
|
|
52
56
|
|
53
57
|
:param book_id: The book identifier.
|
54
58
|
:param chapter_id: The chapter identifier.
|
55
|
-
:return: The
|
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)]
|
@@ -77,7 +81,3 @@ class BiqugeBrowser(BaseBrowser):
|
|
77
81
|
:return: Fully qualified chapter URL.
|
78
82
|
"""
|
79
83
|
return cls.CHAPTER_URL.format(book_id=book_id, chapter_id=chapter_id)
|
80
|
-
|
81
|
-
@property
|
82
|
-
def hostname(self) -> str:
|
83
|
-
return "www.b520.cc"
|