novel-downloader 1.5.0__py3-none-any.whl → 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- novel_downloader/__init__.py +1 -1
- novel_downloader/cli/__init__.py +1 -3
- novel_downloader/cli/clean.py +21 -88
- novel_downloader/cli/config.py +26 -21
- novel_downloader/cli/download.py +77 -64
- novel_downloader/cli/export.py +16 -20
- novel_downloader/cli/main.py +1 -1
- novel_downloader/cli/search.py +62 -65
- novel_downloader/cli/ui.py +156 -0
- novel_downloader/config/__init__.py +8 -5
- novel_downloader/config/adapter.py +65 -105
- novel_downloader/config/{loader.py → file_io.py} +53 -26
- novel_downloader/core/__init__.py +1 -0
- 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/{searchers/qidian.py → archived/qidian/searcher.py} +12 -20
- novel_downloader/core/archived/wanbengo/searcher.py +98 -0
- novel_downloader/core/archived/xshbook/searcher.py +93 -0
- novel_downloader/core/downloaders/__init__.py +3 -24
- novel_downloader/core/downloaders/base.py +49 -23
- novel_downloader/core/downloaders/common.py +191 -137
- novel_downloader/core/downloaders/qianbi.py +187 -146
- novel_downloader/core/downloaders/qidian.py +187 -141
- novel_downloader/core/downloaders/registry.py +4 -2
- novel_downloader/core/downloaders/signals.py +46 -0
- novel_downloader/core/exporters/__init__.py +3 -20
- novel_downloader/core/exporters/base.py +33 -37
- novel_downloader/core/exporters/common/__init__.py +1 -2
- novel_downloader/core/exporters/common/epub.py +15 -10
- novel_downloader/core/exporters/common/main_exporter.py +19 -12
- novel_downloader/core/exporters/common/txt.py +14 -9
- novel_downloader/core/exporters/epub_util.py +59 -29
- novel_downloader/core/exporters/linovelib/__init__.py +1 -0
- novel_downloader/core/exporters/linovelib/epub.py +23 -25
- novel_downloader/core/exporters/linovelib/main_exporter.py +8 -12
- novel_downloader/core/exporters/linovelib/txt.py +17 -11
- novel_downloader/core/exporters/qidian.py +2 -8
- novel_downloader/core/exporters/registry.py +4 -2
- novel_downloader/core/exporters/txt_util.py +7 -7
- novel_downloader/core/fetchers/__init__.py +54 -48
- novel_downloader/core/fetchers/aaatxt.py +83 -0
- novel_downloader/core/fetchers/{biquge/session.py → b520.py} +6 -11
- novel_downloader/core/fetchers/{base/session.py → base.py} +37 -46
- novel_downloader/core/fetchers/{biquge/browser.py → biquyuedu.py} +12 -17
- 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} +19 -12
- 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} +19 -28
- novel_downloader/core/fetchers/ixdzs8.py +113 -0
- novel_downloader/core/fetchers/jpxs123.py +101 -0
- novel_downloader/core/fetchers/lewenn.py +83 -0
- novel_downloader/core/fetchers/{linovelib/session.py → linovelib.py} +12 -13
- 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} +5 -10
- novel_downloader/core/fetchers/{qidian/session.py → qidian.py} +46 -39
- 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 +5 -16
- novel_downloader/core/fetchers/{sfacg/session.py → sfacg.py} +7 -10
- novel_downloader/core/fetchers/shencou.py +106 -0
- novel_downloader/core/fetchers/shuhaige.py +84 -0
- novel_downloader/core/fetchers/tongrenquan.py +84 -0
- novel_downloader/core/fetchers/ttkan.py +95 -0
- novel_downloader/core/fetchers/wanbengo.py +83 -0
- 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} +19 -12
- novel_downloader/core/fetchers/yibige.py +114 -0
- novel_downloader/core/interfaces/__init__.py +1 -9
- 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 +9 -1
- novel_downloader/core/parsers/__init__.py +49 -12
- 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.py +61 -66
- 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.py +48 -64
- novel_downloader/core/parsers/piaotia.py +189 -0
- novel_downloader/core/parsers/qbtr.py +136 -0
- novel_downloader/core/parsers/qianbi.py +48 -50
- novel_downloader/core/parsers/qidian/book_info_parser.py +58 -59
- novel_downloader/core/parsers/qidian/chapter_encrypted.py +272 -330
- novel_downloader/core/parsers/qidian/chapter_normal.py +24 -55
- novel_downloader/core/parsers/qidian/main_parser.py +11 -38
- novel_downloader/core/parsers/qidian/utils/__init__.py +1 -0
- novel_downloader/core/parsers/qidian/utils/decryptor_fetcher.py +1 -1
- 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/quanben5.py +103 -0
- novel_downloader/core/parsers/registry.py +5 -16
- novel_downloader/core/parsers/sfacg.py +38 -45
- 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 +87 -131
- novel_downloader/core/parsers/yibige.py +166 -0
- novel_downloader/core/searchers/__init__.py +34 -3
- novel_downloader/core/searchers/aaatxt.py +107 -0
- novel_downloader/core/searchers/{biquge.py → b520.py} +29 -28
- novel_downloader/core/searchers/base.py +112 -36
- novel_downloader/core/searchers/dxmwx.py +105 -0
- novel_downloader/core/searchers/eightnovel.py +84 -0
- novel_downloader/core/searchers/esjzone.py +43 -25
- 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 +74 -40
- novel_downloader/core/searchers/quanben5.py +144 -0
- novel_downloader/core/searchers/registry.py +24 -8
- 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 +31 -82
- novel_downloader/locales/zh.json +32 -83
- novel_downloader/models/__init__.py +21 -22
- novel_downloader/models/book.py +44 -0
- novel_downloader/models/config.py +4 -37
- novel_downloader/models/login.py +1 -1
- novel_downloader/models/search.py +5 -0
- novel_downloader/resources/config/settings.toml +8 -70
- novel_downloader/resources/json/xiguashuwu.json +718 -0
- novel_downloader/utils/__init__.py +13 -22
- novel_downloader/utils/chapter_storage.py +3 -2
- novel_downloader/utils/constants.py +4 -29
- 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 +1 -1
- novel_downloader/utils/epub/constants.py +57 -16
- novel_downloader/utils/epub/documents.py +88 -194
- novel_downloader/utils/epub/models.py +0 -14
- novel_downloader/utils/epub/utils.py +63 -96
- novel_downloader/utils/file_utils/__init__.py +2 -23
- novel_downloader/utils/file_utils/io.py +3 -113
- novel_downloader/utils/file_utils/sanitize.py +0 -4
- novel_downloader/utils/fontocr.py +207 -0
- novel_downloader/utils/logger.py +8 -16
- novel_downloader/utils/network.py +2 -2
- novel_downloader/utils/state.py +4 -90
- novel_downloader/utils/text_utils/__init__.py +1 -7
- novel_downloader/utils/text_utils/diff_display.py +5 -7
- novel_downloader/utils/time_utils/__init__.py +5 -11
- novel_downloader/utils/time_utils/datetime_utils.py +20 -29
- novel_downloader/utils/time_utils/sleep_utils.py +4 -8
- 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.5.0.dist-info → novel_downloader-2.0.0.dist-info}/entry_points.txt +1 -1
- novel_downloader/core/downloaders/biquge.py +0 -29
- novel_downloader/core/downloaders/esjzone.py +0 -29
- novel_downloader/core/downloaders/linovelib.py +0 -29
- novel_downloader/core/downloaders/sfacg.py +0 -29
- novel_downloader/core/downloaders/yamibo.py +0 -29
- novel_downloader/core/exporters/biquge.py +0 -22
- novel_downloader/core/exporters/esjzone.py +0 -22
- novel_downloader/core/exporters/qianbi.py +0 -22
- novel_downloader/core/exporters/sfacg.py +0 -22
- novel_downloader/core/exporters/yamibo.py +0 -22
- novel_downloader/core/fetchers/base/__init__.py +0 -14
- novel_downloader/core/fetchers/base/browser.py +0 -422
- novel_downloader/core/fetchers/biquge/__init__.py +0 -14
- novel_downloader/core/fetchers/esjzone/__init__.py +0 -14
- novel_downloader/core/fetchers/esjzone/browser.py +0 -209
- novel_downloader/core/fetchers/linovelib/__init__.py +0 -14
- novel_downloader/core/fetchers/linovelib/browser.py +0 -198
- 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 -326
- novel_downloader/core/fetchers/sfacg/__init__.py +0 -14
- novel_downloader/core/fetchers/sfacg/browser.py +0 -194
- novel_downloader/core/fetchers/yamibo/__init__.py +0 -14
- novel_downloader/core/fetchers/yamibo/browser.py +0 -234
- novel_downloader/core/parsers/biquge.py +0 -139
- novel_downloader/models/chapter.py +0 -25
- novel_downloader/models/types.py +0 -13
- 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/hash_store.py +0 -280
- novel_downloader/utils/fontocr/hash_utils.py +0 -103
- novel_downloader/utils/fontocr/model_loader.py +0 -69
- novel_downloader/utils/fontocr/ocr_v1.py +0 -315
- novel_downloader/utils/fontocr/ocr_v2.py +0 -764
- novel_downloader/utils/fontocr/ocr_v3.py +0 -744
- novel_downloader-1.5.0.dist-info/METADATA +0 -196
- novel_downloader-1.5.0.dist-info/RECORD +0 -164
- {novel_downloader-1.5.0.dist-info → novel_downloader-2.0.0.dist-info}/WHEEL +0 -0
- {novel_downloader-1.5.0.dist-info → novel_downloader-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {novel_downloader-1.5.0.dist-info → novel_downloader-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,84 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
novel_downloader.core.fetchers.shuhaige
|
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=["shuhaige"],
|
17
|
+
)
|
18
|
+
class ShuhaigeSession(BaseSession):
|
19
|
+
"""
|
20
|
+
A session class for interacting with the
|
21
|
+
书海阁小说网 (www.shuhaige.net) novel website.
|
22
|
+
"""
|
23
|
+
|
24
|
+
BOOK_INFO_URL = "https://www.shuhaige.net/{book_id}/"
|
25
|
+
CHAPTER_URL = "https://www.shuhaige.net/{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__("shuhaige", 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,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)
|
@@ -0,0 +1,83 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
novel_downloader.core.fetchers.wanbengo
|
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=["wanbengo"],
|
17
|
+
)
|
18
|
+
class WanbengoSession(BaseSession):
|
19
|
+
"""
|
20
|
+
A session class for interacting with the 完本神站 (www.wanbengo.com) novel website.
|
21
|
+
"""
|
22
|
+
|
23
|
+
BOOK_INFO_URL = "https://www.wanbengo.com/{book_id}/"
|
24
|
+
CHAPTER_URL = "https://www.wanbengo.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__("wanbengo", 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
|
+
url = self.book_info_url(book_id=book_id)
|
46
|
+
return [await self.fetch(url, **kwargs)]
|
47
|
+
|
48
|
+
async def get_book_chapter(
|
49
|
+
self,
|
50
|
+
book_id: str,
|
51
|
+
chapter_id: str,
|
52
|
+
**kwargs: Any,
|
53
|
+
) -> list[str]:
|
54
|
+
"""
|
55
|
+
Fetch the raw HTML of a single chapter asynchronously.
|
56
|
+
|
57
|
+
:param book_id: The book identifier.
|
58
|
+
:param chapter_id: The chapter identifier.
|
59
|
+
:return: The page content as string list.
|
60
|
+
"""
|
61
|
+
url = self.chapter_url(book_id=book_id, chapter_id=chapter_id)
|
62
|
+
return [await self.fetch(url, **kwargs)]
|
63
|
+
|
64
|
+
@classmethod
|
65
|
+
def book_info_url(cls, book_id: str) -> str:
|
66
|
+
"""
|
67
|
+
Construct the URL for fetching a book's info page.
|
68
|
+
|
69
|
+
:param book_id: The identifier of the book.
|
70
|
+
:return: Fully qualified URL for the book info page.
|
71
|
+
"""
|
72
|
+
return cls.BOOK_INFO_URL.format(book_id=book_id)
|
73
|
+
|
74
|
+
@classmethod
|
75
|
+
def chapter_url(cls, book_id: str, chapter_id: str) -> str:
|
76
|
+
"""
|
77
|
+
Construct the URL for fetching a specific chapter.
|
78
|
+
|
79
|
+
:param book_id: The identifier of the book.
|
80
|
+
:param chapter_id: The identifier of the chapter.
|
81
|
+
:return: Fully qualified chapter URL.
|
82
|
+
"""
|
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)
|
@@ -0,0 +1,177 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
novel_downloader.core.fetchers.xiguashuwu
|
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
|
+
from novel_downloader.utils import async_jitter_sleep
|
14
|
+
|
15
|
+
|
16
|
+
@register_fetcher(
|
17
|
+
site_keys=["xiguashuwu"],
|
18
|
+
)
|
19
|
+
class XiguashuwuSession(BaseSession):
|
20
|
+
"""
|
21
|
+
A session class for interacting with the 西瓜书屋 (www.xiguashuwu.com) novel.
|
22
|
+
"""
|
23
|
+
|
24
|
+
BASE_URL = "https://www.xiguashuwu.com"
|
25
|
+
BOOK_INFO_URL = "https://www.xiguashuwu.com/book/{book_id}/iszip/0/"
|
26
|
+
BOOK_CATALOG_URL = "https://www.xiguashuwu.com/book/{book_id}/catalog/"
|
27
|
+
CHAPTER_URL = "https://www.xiguashuwu.com/book/{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__("xiguashuwu", 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, catalogs1, ..., catalogsN]
|
46
|
+
|
47
|
+
:param book_id: The book identifier.
|
48
|
+
:return: The page content as string list.
|
49
|
+
"""
|
50
|
+
info_url = self.book_info_url(book_id=book_id)
|
51
|
+
info_html = await self.fetch(info_url, **kwargs)
|
52
|
+
|
53
|
+
catalog_url = self.book_catalog_url(book_id=book_id)
|
54
|
+
catalog_pages: list[str] = []
|
55
|
+
idx = 1
|
56
|
+
while True:
|
57
|
+
suffix = "" if idx == 1 else f"{idx}.html"
|
58
|
+
full_url = catalog_url + suffix
|
59
|
+
|
60
|
+
try:
|
61
|
+
html = await self.fetch(full_url, **kwargs)
|
62
|
+
except Exception as exc:
|
63
|
+
self.logger.warning(
|
64
|
+
"[async] get_book_catalog(%s page %d) failed: %s",
|
65
|
+
book_id,
|
66
|
+
idx,
|
67
|
+
exc,
|
68
|
+
)
|
69
|
+
break
|
70
|
+
|
71
|
+
catalog_pages.append(html)
|
72
|
+
idx += 1
|
73
|
+
next_patterns = [
|
74
|
+
# f"javascript:readbook('{book_id}','{idx}');",
|
75
|
+
# f"javascript:gobook('{book_id}','{idx}');",
|
76
|
+
# f"javascript:runbook('{book_id}','{idx}');",
|
77
|
+
# f"javascript:gotochapter('{book_id}','{idx}');",
|
78
|
+
f"javascript:readbookjump('{book_id}','{idx}');",
|
79
|
+
f"javascript:gobookjump('{book_id}','{idx}');",
|
80
|
+
f"javascript:runbookjump('{book_id}','{idx}');",
|
81
|
+
f"javascript:gotojump('{book_id}','{idx}');",
|
82
|
+
f"javascript:gotochapterjump('{book_id}','{idx}');",
|
83
|
+
f"/book/{book_id}/catalog/{idx}.html",
|
84
|
+
]
|
85
|
+
if not any(pat in html for pat in next_patterns):
|
86
|
+
break
|
87
|
+
|
88
|
+
await async_jitter_sleep(
|
89
|
+
self.request_interval,
|
90
|
+
mul_spread=1.1,
|
91
|
+
max_sleep=self.request_interval + 2,
|
92
|
+
)
|
93
|
+
return [info_html, *catalog_pages]
|
94
|
+
|
95
|
+
async def get_book_chapter(
|
96
|
+
self,
|
97
|
+
book_id: str,
|
98
|
+
chapter_id: str,
|
99
|
+
**kwargs: Any,
|
100
|
+
) -> list[str]:
|
101
|
+
"""
|
102
|
+
Fetch the raw HTML of a single chapter asynchronously.
|
103
|
+
|
104
|
+
Order: [page1, ..., pageN]
|
105
|
+
|
106
|
+
:param book_id: The book identifier.
|
107
|
+
:param chapter_id: The chapter identifier.
|
108
|
+
:return: The page content as string list.
|
109
|
+
"""
|
110
|
+
html_pages: list[str] = []
|
111
|
+
idx = 1
|
112
|
+
|
113
|
+
while True:
|
114
|
+
chapter_suffix = chapter_id if idx == 1 else f"{chapter_id}_{idx}"
|
115
|
+
relative_path = self.relative_chapter_url(book_id, chapter_suffix)
|
116
|
+
if idx > 1 and relative_path not in html_pages[-1]:
|
117
|
+
break
|
118
|
+
full_url = self.BASE_URL + relative_path
|
119
|
+
|
120
|
+
try:
|
121
|
+
html = await self.fetch(full_url, **kwargs)
|
122
|
+
except Exception as exc:
|
123
|
+
self.logger.warning(
|
124
|
+
"[async] get_book_chapter(%s page %d) failed: %s",
|
125
|
+
chapter_id,
|
126
|
+
idx,
|
127
|
+
exc,
|
128
|
+
)
|
129
|
+
break
|
130
|
+
|
131
|
+
html_pages.append(html)
|
132
|
+
idx += 1
|
133
|
+
await async_jitter_sleep(
|
134
|
+
self.request_interval,
|
135
|
+
mul_spread=1.1,
|
136
|
+
max_sleep=self.request_interval + 2,
|
137
|
+
)
|
138
|
+
|
139
|
+
return html_pages
|
140
|
+
|
141
|
+
@classmethod
|
142
|
+
def book_info_url(cls, book_id: str) -> str:
|
143
|
+
"""
|
144
|
+
Construct the URL for fetching a book's info page.
|
145
|
+
|
146
|
+
:param book_id: The identifier of the book.
|
147
|
+
:return: Fully qualified URL for the book info page.
|
148
|
+
"""
|
149
|
+
return cls.BOOK_INFO_URL.format(book_id=book_id)
|
150
|
+
|
151
|
+
@classmethod
|
152
|
+
def book_catalog_url(cls, book_id: str) -> str:
|
153
|
+
"""
|
154
|
+
Construct the URL for fetching a book's catalog page.
|
155
|
+
|
156
|
+
:param book_id: The identifier of the book.
|
157
|
+
:return: Fully qualified catalog page URL.
|
158
|
+
"""
|
159
|
+
return cls.BOOK_CATALOG_URL.format(book_id=book_id)
|
160
|
+
|
161
|
+
@classmethod
|
162
|
+
def chapter_url(cls, book_id: str, chapter_id: str) -> str:
|
163
|
+
"""
|
164
|
+
Construct the URL for fetching a specific chapter.
|
165
|
+
|
166
|
+
:param book_id: The identifier of the book.
|
167
|
+
:param chapter_id: The identifier of the chapter.
|
168
|
+
:return: Fully qualified chapter URL.
|
169
|
+
"""
|
170
|
+
return cls.CHAPTER_URL.format(book_id=book_id, chapter_id=chapter_id)
|
171
|
+
|
172
|
+
@classmethod
|
173
|
+
def relative_chapter_url(cls, book_id: str, chapter_id: str) -> str:
|
174
|
+
"""
|
175
|
+
Return the relative URL path for a given chapter.
|
176
|
+
"""
|
177
|
+
return f"/book/{book_id}/{chapter_id}.html"
|