novel-downloader 1.4.5__py3-none-any.whl → 1.5.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 -2
- novel_downloader/cli/config.py +1 -83
- novel_downloader/cli/download.py +4 -5
- novel_downloader/cli/export.py +4 -1
- novel_downloader/cli/main.py +2 -0
- novel_downloader/cli/search.py +123 -0
- novel_downloader/config/__init__.py +3 -10
- novel_downloader/config/adapter.py +190 -54
- novel_downloader/config/loader.py +2 -3
- novel_downloader/core/__init__.py +13 -13
- novel_downloader/core/downloaders/__init__.py +10 -11
- novel_downloader/core/downloaders/base.py +152 -26
- novel_downloader/core/downloaders/biquge.py +5 -1
- novel_downloader/core/downloaders/common.py +157 -378
- novel_downloader/core/downloaders/esjzone.py +5 -1
- novel_downloader/core/downloaders/linovelib.py +5 -1
- novel_downloader/core/downloaders/qianbi.py +291 -4
- novel_downloader/core/downloaders/qidian.py +199 -285
- novel_downloader/core/downloaders/registry.py +67 -0
- novel_downloader/core/downloaders/sfacg.py +5 -1
- novel_downloader/core/downloaders/yamibo.py +5 -1
- novel_downloader/core/exporters/__init__.py +10 -11
- novel_downloader/core/exporters/base.py +87 -7
- novel_downloader/core/exporters/biquge.py +5 -8
- novel_downloader/core/exporters/common/__init__.py +2 -2
- novel_downloader/core/exporters/common/epub.py +82 -166
- novel_downloader/core/exporters/common/main_exporter.py +0 -60
- novel_downloader/core/exporters/common/txt.py +82 -83
- novel_downloader/core/exporters/epub_util.py +157 -1330
- novel_downloader/core/exporters/esjzone.py +5 -8
- novel_downloader/core/exporters/linovelib/__init__.py +2 -2
- novel_downloader/core/exporters/linovelib/epub.py +157 -212
- novel_downloader/core/exporters/linovelib/main_exporter.py +2 -59
- novel_downloader/core/exporters/linovelib/txt.py +67 -63
- novel_downloader/core/exporters/qianbi.py +5 -8
- novel_downloader/core/exporters/qidian.py +14 -4
- novel_downloader/core/exporters/registry.py +53 -0
- novel_downloader/core/exporters/sfacg.py +5 -8
- novel_downloader/core/exporters/txt_util.py +67 -0
- novel_downloader/core/exporters/yamibo.py +5 -8
- novel_downloader/core/fetchers/__init__.py +19 -24
- novel_downloader/core/fetchers/base/__init__.py +3 -3
- novel_downloader/core/fetchers/base/browser.py +23 -4
- novel_downloader/core/fetchers/base/session.py +30 -5
- novel_downloader/core/fetchers/biquge/__init__.py +3 -3
- novel_downloader/core/fetchers/biquge/browser.py +5 -0
- novel_downloader/core/fetchers/biquge/session.py +6 -1
- novel_downloader/core/fetchers/esjzone/__init__.py +3 -3
- novel_downloader/core/fetchers/esjzone/browser.py +5 -0
- novel_downloader/core/fetchers/esjzone/session.py +6 -1
- novel_downloader/core/fetchers/linovelib/__init__.py +3 -3
- novel_downloader/core/fetchers/linovelib/browser.py +6 -1
- novel_downloader/core/fetchers/linovelib/session.py +6 -1
- novel_downloader/core/fetchers/qianbi/__init__.py +3 -3
- novel_downloader/core/fetchers/qianbi/browser.py +5 -0
- novel_downloader/core/fetchers/qianbi/session.py +5 -0
- novel_downloader/core/fetchers/qidian/__init__.py +3 -3
- novel_downloader/core/fetchers/qidian/browser.py +12 -4
- novel_downloader/core/fetchers/qidian/session.py +11 -3
- novel_downloader/core/fetchers/registry.py +71 -0
- novel_downloader/core/fetchers/sfacg/__init__.py +3 -3
- novel_downloader/core/fetchers/sfacg/browser.py +5 -0
- novel_downloader/core/fetchers/sfacg/session.py +5 -0
- novel_downloader/core/fetchers/yamibo/__init__.py +3 -3
- novel_downloader/core/fetchers/yamibo/browser.py +5 -0
- novel_downloader/core/fetchers/yamibo/session.py +6 -1
- novel_downloader/core/interfaces/__init__.py +7 -5
- novel_downloader/core/interfaces/searcher.py +18 -0
- novel_downloader/core/parsers/__init__.py +10 -11
- novel_downloader/core/parsers/{biquge/main_parser.py → biquge.py} +7 -2
- novel_downloader/core/parsers/{esjzone/main_parser.py → esjzone.py} +7 -2
- novel_downloader/core/parsers/{linovelib/main_parser.py → linovelib.py} +7 -2
- novel_downloader/core/parsers/{qianbi/main_parser.py → qianbi.py} +7 -2
- novel_downloader/core/parsers/qidian/__init__.py +2 -2
- novel_downloader/core/parsers/qidian/chapter_encrypted.py +23 -21
- novel_downloader/core/parsers/qidian/chapter_normal.py +1 -1
- novel_downloader/core/parsers/qidian/main_parser.py +10 -21
- novel_downloader/core/parsers/qidian/utils/__init__.py +11 -11
- novel_downloader/core/parsers/qidian/utils/decryptor_fetcher.py +5 -6
- novel_downloader/core/parsers/qidian/utils/node_decryptor.py +2 -2
- novel_downloader/core/parsers/registry.py +68 -0
- novel_downloader/core/parsers/{sfacg/main_parser.py → sfacg.py} +7 -2
- novel_downloader/core/parsers/{yamibo/main_parser.py → yamibo.py} +7 -2
- novel_downloader/core/searchers/__init__.py +20 -0
- novel_downloader/core/searchers/base.py +92 -0
- novel_downloader/core/searchers/biquge.py +83 -0
- novel_downloader/core/searchers/esjzone.py +84 -0
- novel_downloader/core/searchers/qianbi.py +131 -0
- novel_downloader/core/searchers/qidian.py +87 -0
- novel_downloader/core/searchers/registry.py +63 -0
- novel_downloader/locales/en.json +12 -4
- novel_downloader/locales/zh.json +12 -4
- novel_downloader/models/__init__.py +4 -30
- novel_downloader/models/config.py +12 -6
- novel_downloader/models/search.py +16 -0
- novel_downloader/models/types.py +0 -2
- novel_downloader/resources/config/settings.toml +31 -4
- novel_downloader/resources/css_styles/intro.css +83 -0
- novel_downloader/resources/css_styles/main.css +30 -89
- novel_downloader/utils/__init__.py +52 -0
- novel_downloader/utils/chapter_storage.py +244 -224
- novel_downloader/utils/constants.py +1 -21
- novel_downloader/utils/epub/__init__.py +34 -0
- novel_downloader/utils/epub/builder.py +377 -0
- novel_downloader/utils/epub/constants.py +77 -0
- novel_downloader/utils/epub/documents.py +403 -0
- novel_downloader/utils/epub/models.py +134 -0
- novel_downloader/utils/epub/utils.py +212 -0
- novel_downloader/utils/file_utils/__init__.py +10 -14
- novel_downloader/utils/file_utils/io.py +20 -51
- novel_downloader/utils/file_utils/normalize.py +2 -2
- novel_downloader/utils/file_utils/sanitize.py +2 -3
- novel_downloader/utils/fontocr/__init__.py +5 -5
- novel_downloader/utils/{hash_store.py → fontocr/hash_store.py} +4 -3
- novel_downloader/utils/{hash_utils.py → fontocr/hash_utils.py} +2 -2
- novel_downloader/utils/fontocr/ocr_v1.py +13 -1
- novel_downloader/utils/fontocr/ocr_v2.py +13 -1
- novel_downloader/utils/fontocr/ocr_v3.py +744 -0
- novel_downloader/utils/i18n.py +2 -0
- novel_downloader/utils/logger.py +2 -0
- novel_downloader/utils/network.py +110 -251
- novel_downloader/utils/state.py +1 -0
- novel_downloader/utils/text_utils/__init__.py +18 -17
- novel_downloader/utils/text_utils/diff_display.py +4 -5
- 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 +3 -3
- novel_downloader/utils/time_utils/datetime_utils.py +4 -5
- novel_downloader/utils/time_utils/sleep_utils.py +2 -3
- {novel_downloader-1.4.5.dist-info → novel_downloader-1.5.0.dist-info}/METADATA +2 -2
- novel_downloader-1.5.0.dist-info/RECORD +164 -0
- novel_downloader/config/site_rules.py +0 -94
- 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/common/__init__.py +0 -14
- novel_downloader/core/fetchers/common/browser.py +0 -79
- novel_downloader/core/fetchers/common/session.py +0 -79
- novel_downloader/core/parsers/biquge/__init__.py +0 -10
- 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/models/browser.py +0 -21
- novel_downloader/models/site_rules.py +0 -99
- novel_downloader/models/tasks.py +0 -33
- 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/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/RECORD +0 -165
- {novel_downloader-1.4.5.dist-info → novel_downloader-1.5.0.dist-info}/WHEEL +0 -0
- {novel_downloader-1.4.5.dist-info → novel_downloader-1.5.0.dist-info}/entry_points.txt +0 -0
- {novel_downloader-1.4.5.dist-info → novel_downloader-1.5.0.dist-info}/licenses/LICENSE +0 -0
- {novel_downloader-1.4.5.dist-info → novel_downloader-1.5.0.dist-info}/top_level.txt +0 -0
@@ -8,9 +8,14 @@ novel_downloader.core.fetchers.biquge.browser
|
|
8
8
|
from typing import Any
|
9
9
|
|
10
10
|
from novel_downloader.core.fetchers.base import BaseBrowser
|
11
|
+
from novel_downloader.core.fetchers.registry import register_fetcher
|
11
12
|
from novel_downloader.models import FetcherConfig
|
12
13
|
|
13
14
|
|
15
|
+
@register_fetcher(
|
16
|
+
site_keys=["biquge", "bqg"],
|
17
|
+
backends=["browser"],
|
18
|
+
)
|
14
19
|
class BiqugeBrowser(BaseBrowser):
|
15
20
|
"""
|
16
21
|
A browser class for interacting with the Biquge (www.b520.cc) novel website.
|
@@ -8,9 +8,14 @@ novel_downloader.core.fetchers.biquge.session
|
|
8
8
|
from typing import Any
|
9
9
|
|
10
10
|
from novel_downloader.core.fetchers.base import BaseSession
|
11
|
+
from novel_downloader.core.fetchers.registry import register_fetcher
|
11
12
|
from novel_downloader.models import FetcherConfig
|
12
13
|
|
13
14
|
|
15
|
+
@register_fetcher(
|
16
|
+
site_keys=["biquge", "bqg"],
|
17
|
+
backends=["session"],
|
18
|
+
)
|
14
19
|
class BiqugeSession(BaseSession):
|
15
20
|
"""
|
16
21
|
A session class for interacting with the Biquge (www.b520.cc) novel website.
|
@@ -55,7 +60,7 @@ class BiqugeSession(BaseSession):
|
|
55
60
|
:return: The chapter content as a string.
|
56
61
|
"""
|
57
62
|
url = self.chapter_url(book_id=book_id, chapter_id=chapter_id)
|
58
|
-
return [await self.fetch(url, **kwargs)]
|
63
|
+
return [await self.fetch(url, encoding="gbk", **kwargs)]
|
59
64
|
|
60
65
|
@classmethod
|
61
66
|
def book_info_url(cls, book_id: str) -> str:
|
@@ -5,10 +5,10 @@ novel_downloader.core.fetchers.esjzone
|
|
5
5
|
|
6
6
|
"""
|
7
7
|
|
8
|
-
from .browser import EsjzoneBrowser
|
9
|
-
from .session import EsjzoneSession
|
10
|
-
|
11
8
|
__all__ = [
|
12
9
|
"EsjzoneBrowser",
|
13
10
|
"EsjzoneSession",
|
14
11
|
]
|
12
|
+
|
13
|
+
from .browser import EsjzoneBrowser
|
14
|
+
from .session import EsjzoneSession
|
@@ -8,9 +8,14 @@ novel_downloader.core.fetchers.esjzone.browser
|
|
8
8
|
from typing import Any
|
9
9
|
|
10
10
|
from novel_downloader.core.fetchers.base import BaseBrowser
|
11
|
+
from novel_downloader.core.fetchers.registry import register_fetcher
|
11
12
|
from novel_downloader.models import FetcherConfig, LoginField
|
12
13
|
|
13
14
|
|
15
|
+
@register_fetcher(
|
16
|
+
site_keys=["esjzone"],
|
17
|
+
backends=["browser"],
|
18
|
+
)
|
14
19
|
class EsjzoneBrowser(BaseBrowser):
|
15
20
|
"""
|
16
21
|
A browser class for interacting with the Esjzone (www.esjzone.cc) novel website.
|
@@ -9,10 +9,15 @@ import re
|
|
9
9
|
from typing import Any
|
10
10
|
|
11
11
|
from novel_downloader.core.fetchers.base import BaseSession
|
12
|
+
from novel_downloader.core.fetchers.registry import register_fetcher
|
12
13
|
from novel_downloader.models import FetcherConfig, LoginField
|
13
|
-
from novel_downloader.utils
|
14
|
+
from novel_downloader.utils import async_sleep_with_random_delay
|
14
15
|
|
15
16
|
|
17
|
+
@register_fetcher(
|
18
|
+
site_keys=["esjzone"],
|
19
|
+
backends=["session"],
|
20
|
+
)
|
16
21
|
class EsjzoneSession(BaseSession):
|
17
22
|
"""
|
18
23
|
A session class for interacting with the esjzone (www.esjzone.cc) novel website.
|
@@ -5,10 +5,10 @@ novel_downloader.core.fetchers.linovelib
|
|
5
5
|
|
6
6
|
"""
|
7
7
|
|
8
|
-
from .browser import LinovelibBrowser
|
9
|
-
from .session import LinovelibSession
|
10
|
-
|
11
8
|
__all__ = [
|
12
9
|
"LinovelibBrowser",
|
13
10
|
"LinovelibSession",
|
14
11
|
]
|
12
|
+
|
13
|
+
from .browser import LinovelibBrowser
|
14
|
+
from .session import LinovelibSession
|
@@ -9,10 +9,15 @@ import re
|
|
9
9
|
from typing import Any
|
10
10
|
|
11
11
|
from novel_downloader.core.fetchers.base import BaseBrowser
|
12
|
+
from novel_downloader.core.fetchers.registry import register_fetcher
|
12
13
|
from novel_downloader.models import FetcherConfig
|
13
|
-
from novel_downloader.utils
|
14
|
+
from novel_downloader.utils import async_sleep_with_random_delay
|
14
15
|
|
15
16
|
|
17
|
+
@register_fetcher(
|
18
|
+
site_keys=["linovelib"],
|
19
|
+
backends=["browser"],
|
20
|
+
)
|
16
21
|
class LinovelibBrowser(BaseBrowser):
|
17
22
|
"""
|
18
23
|
A browser class for interacting with Linovelib (www.linovelib.com) novel website.
|
@@ -9,10 +9,15 @@ import re
|
|
9
9
|
from typing import Any
|
10
10
|
|
11
11
|
from novel_downloader.core.fetchers.base import BaseSession
|
12
|
+
from novel_downloader.core.fetchers.registry import register_fetcher
|
12
13
|
from novel_downloader.models import FetcherConfig
|
13
|
-
from novel_downloader.utils
|
14
|
+
from novel_downloader.utils import async_sleep_with_random_delay
|
14
15
|
|
15
16
|
|
17
|
+
@register_fetcher(
|
18
|
+
site_keys=["linovelib"],
|
19
|
+
backends=["session"],
|
20
|
+
)
|
16
21
|
class LinovelibSession(BaseSession):
|
17
22
|
"""
|
18
23
|
A session class for interacting with Linovelib (www.linovelib.com) novel website.
|
@@ -5,10 +5,10 @@ novel_downloader.core.fetchers.qianbi
|
|
5
5
|
|
6
6
|
"""
|
7
7
|
|
8
|
-
from .browser import QianbiBrowser
|
9
|
-
from .session import QianbiSession
|
10
|
-
|
11
8
|
__all__ = [
|
12
9
|
"QianbiBrowser",
|
13
10
|
"QianbiSession",
|
14
11
|
]
|
12
|
+
|
13
|
+
from .browser import QianbiBrowser
|
14
|
+
from .session import QianbiSession
|
@@ -8,9 +8,14 @@ novel_downloader.core.fetchers.qianbi.browser
|
|
8
8
|
from typing import Any
|
9
9
|
|
10
10
|
from novel_downloader.core.fetchers.base import BaseBrowser
|
11
|
+
from novel_downloader.core.fetchers.registry import register_fetcher
|
11
12
|
from novel_downloader.models import FetcherConfig
|
12
13
|
|
13
14
|
|
15
|
+
@register_fetcher(
|
16
|
+
site_keys=["qianbi"],
|
17
|
+
backends=["browser"],
|
18
|
+
)
|
14
19
|
class QianbiBrowser(BaseBrowser):
|
15
20
|
"""
|
16
21
|
A browser class for interacting with the Qianbi (www.23qb.com) novel website.
|
@@ -9,9 +9,14 @@ import asyncio
|
|
9
9
|
from typing import Any
|
10
10
|
|
11
11
|
from novel_downloader.core.fetchers.base import BaseSession
|
12
|
+
from novel_downloader.core.fetchers.registry import register_fetcher
|
12
13
|
from novel_downloader.models import FetcherConfig
|
13
14
|
|
14
15
|
|
16
|
+
@register_fetcher(
|
17
|
+
site_keys=["qianbi"],
|
18
|
+
backends=["session"],
|
19
|
+
)
|
15
20
|
class QianbiSession(BaseSession):
|
16
21
|
"""
|
17
22
|
A session class for interacting with the Qianbi (www.23qb.com) novel website.
|
@@ -5,10 +5,10 @@ novel_downloader.core.fetchers.qidian
|
|
5
5
|
|
6
6
|
"""
|
7
7
|
|
8
|
-
from .browser import QidianBrowser
|
9
|
-
from .session import QidianSession
|
10
|
-
|
11
8
|
__all__ = [
|
12
9
|
"QidianBrowser",
|
13
10
|
"QidianSession",
|
14
11
|
]
|
12
|
+
|
13
|
+
from .browser import QidianBrowser
|
14
|
+
from .session import QidianSession
|
@@ -11,15 +11,21 @@ from typing import Any
|
|
11
11
|
from playwright.async_api import Page
|
12
12
|
|
13
13
|
from novel_downloader.core.fetchers.base import BaseBrowser
|
14
|
+
from novel_downloader.core.fetchers.registry import register_fetcher
|
14
15
|
from novel_downloader.models import FetcherConfig, LoginField
|
15
16
|
from novel_downloader.utils.i18n import t
|
16
17
|
|
17
18
|
|
19
|
+
@register_fetcher(
|
20
|
+
site_keys=["qidian", "qd"],
|
21
|
+
backends=["browser"],
|
22
|
+
)
|
18
23
|
class QidianBrowser(BaseBrowser):
|
19
24
|
"""
|
20
25
|
A browser class for interacting with the Qidian (www.qidian.com) novel website.
|
21
26
|
"""
|
22
27
|
|
28
|
+
WAIT_TIME = 2.0
|
23
29
|
HOMEPAGE_URL = "https://www.qidian.com/"
|
24
30
|
BOOKCASE_URL = "https://my.qidian.com/bookcase/"
|
25
31
|
# BOOK_INFO_URL = "https://book.qidian.com/info/{book_id}/"
|
@@ -59,7 +65,7 @@ class QidianBrowser(BaseBrowser):
|
|
59
65
|
:return: The page content as a string.
|
60
66
|
"""
|
61
67
|
url = self.book_info_url(book_id=book_id)
|
62
|
-
return [await self.fetch(url, **kwargs)]
|
68
|
+
return [await self.fetch(url, delay=self.WAIT_TIME, **kwargs)]
|
63
69
|
|
64
70
|
async def get_book_chapter(
|
65
71
|
self,
|
@@ -76,7 +82,9 @@ class QidianBrowser(BaseBrowser):
|
|
76
82
|
"""
|
77
83
|
catalog_url = self.book_info_url(book_id=book_id)
|
78
84
|
url = self.chapter_url(book_id=book_id, chapter_id=chapter_id)
|
79
|
-
return [
|
85
|
+
return [
|
86
|
+
await self.fetch(url, referer=catalog_url, delay=self.WAIT_TIME, **kwargs)
|
87
|
+
]
|
80
88
|
|
81
89
|
async def get_bookcase(
|
82
90
|
self,
|
@@ -88,7 +96,7 @@ class QidianBrowser(BaseBrowser):
|
|
88
96
|
:return: The HTML markup of the bookcase page.
|
89
97
|
"""
|
90
98
|
url = self.bookcase_url()
|
91
|
-
return [await self.fetch(url, **kwargs)]
|
99
|
+
return [await self.fetch(url, delay=self.WAIT_TIME, **kwargs)]
|
92
100
|
|
93
101
|
async def get_homepage(
|
94
102
|
self,
|
@@ -100,7 +108,7 @@ class QidianBrowser(BaseBrowser):
|
|
100
108
|
:return: The HTML markup of the home page.
|
101
109
|
"""
|
102
110
|
url = self.homepage_url()
|
103
|
-
return [await self.fetch(url, **kwargs)]
|
111
|
+
return [await self.fetch(url, delay=self.WAIT_TIME, **kwargs)]
|
104
112
|
|
105
113
|
async def set_interactive_mode(self, enable: bool) -> bool:
|
106
114
|
"""
|
@@ -15,11 +15,18 @@ from typing import Any, ClassVar
|
|
15
15
|
import aiohttp
|
16
16
|
|
17
17
|
from novel_downloader.core.fetchers.base import BaseSession
|
18
|
+
from novel_downloader.core.fetchers.registry import register_fetcher
|
18
19
|
from novel_downloader.models import FetcherConfig, LoginField
|
19
|
-
from novel_downloader.utils
|
20
|
-
|
20
|
+
from novel_downloader.utils import (
|
21
|
+
async_sleep_with_random_delay,
|
22
|
+
rc4_crypt,
|
23
|
+
)
|
21
24
|
|
22
25
|
|
26
|
+
@register_fetcher(
|
27
|
+
site_keys=["qidian", "qd"],
|
28
|
+
backends=["session"],
|
29
|
+
)
|
23
30
|
class QidianSession(BaseSession):
|
24
31
|
"""
|
25
32
|
A session class for interacting with the Qidian (www.qidian.com) novel website.
|
@@ -144,6 +151,7 @@ class QidianSession(BaseSession):
|
|
144
151
|
async def fetch(
|
145
152
|
self,
|
146
153
|
url: str,
|
154
|
+
encoding: str | None = None,
|
147
155
|
**kwargs: Any,
|
148
156
|
) -> str:
|
149
157
|
"""
|
@@ -167,7 +175,7 @@ class QidianSession(BaseSession):
|
|
167
175
|
|
168
176
|
async with self.session.get(url, **kwargs) as resp:
|
169
177
|
resp.raise_for_status()
|
170
|
-
text: str = await resp.text()
|
178
|
+
text: str = await resp.text(encoding=encoding)
|
171
179
|
return text
|
172
180
|
except aiohttp.ClientError:
|
173
181
|
if attempt < self.retry_times:
|
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
novel_downloader.core.fetchers.registry
|
4
|
+
---------------------------------------
|
5
|
+
|
6
|
+
"""
|
7
|
+
|
8
|
+
__all__ = ["register_fetcher", "get_fetcher"]
|
9
|
+
|
10
|
+
from collections.abc import Callable, Sequence
|
11
|
+
from typing import TypeVar
|
12
|
+
|
13
|
+
from novel_downloader.core.interfaces import FetcherProtocol
|
14
|
+
from novel_downloader.models import FetcherConfig
|
15
|
+
|
16
|
+
FetcherBuilder = Callable[[FetcherConfig], FetcherProtocol]
|
17
|
+
|
18
|
+
F = TypeVar("F", bound=FetcherProtocol)
|
19
|
+
_FETCHER_MAP: dict[str, dict[str, FetcherBuilder]] = {}
|
20
|
+
|
21
|
+
|
22
|
+
def register_fetcher(
|
23
|
+
site_keys: Sequence[str],
|
24
|
+
backends: Sequence[str],
|
25
|
+
) -> Callable[[type[F]], type[F]]:
|
26
|
+
"""
|
27
|
+
Decorator to register a fetcher class under given keys.
|
28
|
+
|
29
|
+
:param site_keys: Sequence of site identifiers
|
30
|
+
:param backends: Sequence of backend types
|
31
|
+
:return: A class decorator that populates _FETCHER_MAP.
|
32
|
+
"""
|
33
|
+
|
34
|
+
def decorator(cls: type[F]) -> type[F]:
|
35
|
+
for site in site_keys:
|
36
|
+
site_lower = site.lower()
|
37
|
+
bucket = _FETCHER_MAP.setdefault(site_lower, {})
|
38
|
+
for backend in backends:
|
39
|
+
bucket[backend] = cls
|
40
|
+
return cls
|
41
|
+
|
42
|
+
return decorator
|
43
|
+
|
44
|
+
|
45
|
+
def get_fetcher(
|
46
|
+
site: str,
|
47
|
+
config: FetcherConfig,
|
48
|
+
) -> FetcherProtocol:
|
49
|
+
"""
|
50
|
+
Returns an FetcherProtocol for the given site.
|
51
|
+
|
52
|
+
:param site: Site name (e.g., 'qidian')
|
53
|
+
:param config: Configuration for the requester
|
54
|
+
:return: An instance of a requester class
|
55
|
+
"""
|
56
|
+
site_key = site.lower()
|
57
|
+
try:
|
58
|
+
backend_map = _FETCHER_MAP[site_key]
|
59
|
+
except KeyError as err:
|
60
|
+
raise ValueError(f"Unsupported site: {site!r}") from err
|
61
|
+
|
62
|
+
mode = config.mode
|
63
|
+
try:
|
64
|
+
fetcher_cls = backend_map[mode]
|
65
|
+
except KeyError as err:
|
66
|
+
raise ValueError(
|
67
|
+
f"Unsupported fetcher mode {mode!r} for site {site!r}. "
|
68
|
+
f"Available modes: {list(backend_map)}"
|
69
|
+
) from err
|
70
|
+
|
71
|
+
return fetcher_cls(config)
|
@@ -5,10 +5,10 @@ novel_downloader.core.fetchers.sfacg
|
|
5
5
|
|
6
6
|
"""
|
7
7
|
|
8
|
-
from .browser import SfacgBrowser
|
9
|
-
from .session import SfacgSession
|
10
|
-
|
11
8
|
__all__ = [
|
12
9
|
"SfacgBrowser",
|
13
10
|
"SfacgSession",
|
14
11
|
]
|
12
|
+
|
13
|
+
from .browser import SfacgBrowser
|
14
|
+
from .session import SfacgSession
|
@@ -8,10 +8,15 @@ novel_downloader.core.fetchers.sfacg.browser
|
|
8
8
|
from typing import Any
|
9
9
|
|
10
10
|
from novel_downloader.core.fetchers.base import BaseBrowser
|
11
|
+
from novel_downloader.core.fetchers.registry import register_fetcher
|
11
12
|
from novel_downloader.models import FetcherConfig, LoginField
|
12
13
|
from novel_downloader.utils.i18n import t
|
13
14
|
|
14
15
|
|
16
|
+
@register_fetcher(
|
17
|
+
site_keys=["sfacg"],
|
18
|
+
backends=["browser"],
|
19
|
+
)
|
15
20
|
class SfacgBrowser(BaseBrowser):
|
16
21
|
"""
|
17
22
|
A browser class for interacting with the Sfacg (m.sfacg.com) novel website.
|
@@ -8,9 +8,14 @@ novel_downloader.core.fetchers.sfacg.session
|
|
8
8
|
from typing import Any
|
9
9
|
|
10
10
|
from novel_downloader.core.fetchers.base import BaseSession
|
11
|
+
from novel_downloader.core.fetchers.registry import register_fetcher
|
11
12
|
from novel_downloader.models import FetcherConfig, LoginField
|
12
13
|
|
13
14
|
|
15
|
+
@register_fetcher(
|
16
|
+
site_keys=["sfacg"],
|
17
|
+
backends=["session"],
|
18
|
+
)
|
14
19
|
class SfacgSession(BaseSession):
|
15
20
|
"""
|
16
21
|
A session class for interacting with the Sfacg (m.sfacg.com) novel website.
|
@@ -5,10 +5,10 @@ novel_downloader.core.fetchers.yamibo
|
|
5
5
|
|
6
6
|
"""
|
7
7
|
|
8
|
-
from .browser import YamiboBrowser
|
9
|
-
from .session import YamiboSession
|
10
|
-
|
11
8
|
__all__ = [
|
12
9
|
"YamiboBrowser",
|
13
10
|
"YamiboSession",
|
14
11
|
]
|
12
|
+
|
13
|
+
from .browser import YamiboBrowser
|
14
|
+
from .session import YamiboSession
|
@@ -8,9 +8,14 @@ novel_downloader.core.fetchers.yamibo.browser
|
|
8
8
|
from typing import Any
|
9
9
|
|
10
10
|
from novel_downloader.core.fetchers.base import BaseBrowser
|
11
|
+
from novel_downloader.core.fetchers.registry import register_fetcher
|
11
12
|
from novel_downloader.models import FetcherConfig, LoginField
|
12
13
|
|
13
14
|
|
15
|
+
@register_fetcher(
|
16
|
+
site_keys=["yamibo"],
|
17
|
+
backends=["browser"],
|
18
|
+
)
|
14
19
|
class YamiboBrowser(BaseBrowser):
|
15
20
|
"""
|
16
21
|
A browser class for interacting with the Yamibo (www.yamibo.com) novel website.
|
@@ -10,10 +10,15 @@ from typing import Any
|
|
10
10
|
from lxml import html
|
11
11
|
|
12
12
|
from novel_downloader.core.fetchers.base import BaseSession
|
13
|
+
from novel_downloader.core.fetchers.registry import register_fetcher
|
13
14
|
from novel_downloader.models import FetcherConfig, LoginField
|
14
|
-
from novel_downloader.utils
|
15
|
+
from novel_downloader.utils import async_sleep_with_random_delay
|
15
16
|
|
16
17
|
|
18
|
+
@register_fetcher(
|
19
|
+
site_keys=["yamibo"],
|
20
|
+
backends=["session"],
|
21
|
+
)
|
17
22
|
class YamiboSession(BaseSession):
|
18
23
|
"""
|
19
24
|
A session class for interacting with the Yamibo (www.yamibo.com) novel website.
|
@@ -14,14 +14,16 @@ Included protocols:
|
|
14
14
|
- ExporterProtocol
|
15
15
|
"""
|
16
16
|
|
17
|
-
from .downloader import DownloaderProtocol
|
18
|
-
from .exporter import ExporterProtocol
|
19
|
-
from .fetcher import FetcherProtocol
|
20
|
-
from .parser import ParserProtocol
|
21
|
-
|
22
17
|
__all__ = [
|
23
18
|
"DownloaderProtocol",
|
24
19
|
"ExporterProtocol",
|
25
20
|
"FetcherProtocol",
|
26
21
|
"ParserProtocol",
|
22
|
+
"SearcherProtocol",
|
27
23
|
]
|
24
|
+
|
25
|
+
from .downloader import DownloaderProtocol
|
26
|
+
from .exporter import ExporterProtocol
|
27
|
+
from .fetcher import FetcherProtocol
|
28
|
+
from .parser import ParserProtocol
|
29
|
+
from .searcher import SearcherProtocol
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
novel_downloader.core.interfaces.searcher
|
4
|
+
-----------------------------------------
|
5
|
+
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Protocol
|
9
|
+
|
10
|
+
from novel_downloader.models import SearchResult
|
11
|
+
|
12
|
+
|
13
|
+
class SearcherProtocol(Protocol):
|
14
|
+
site_name: str
|
15
|
+
|
16
|
+
@classmethod
|
17
|
+
def search(cls, keyword: str, limit: int | None = None) -> list[SearchResult]:
|
18
|
+
...
|
@@ -14,21 +14,11 @@ Modules:
|
|
14
14
|
- qidian (起点中文网)
|
15
15
|
- sfacg (SF轻小说)
|
16
16
|
- yamibo (百合会)
|
17
|
-
- common (通用架构)
|
18
17
|
"""
|
19
18
|
|
20
|
-
from .biquge import BiqugeParser
|
21
|
-
from .common import CommonParser
|
22
|
-
from .esjzone import EsjzoneParser
|
23
|
-
from .linovelib import LinovelibParser
|
24
|
-
from .qianbi import QianbiParser
|
25
|
-
from .qidian import QidianParser
|
26
|
-
from .sfacg import SfacgParser
|
27
|
-
from .yamibo import YamiboParser
|
28
|
-
|
29
19
|
__all__ = [
|
20
|
+
"get_parser",
|
30
21
|
"BiqugeParser",
|
31
|
-
"CommonParser",
|
32
22
|
"EsjzoneParser",
|
33
23
|
"LinovelibParser",
|
34
24
|
"QianbiParser",
|
@@ -36,3 +26,12 @@ __all__ = [
|
|
36
26
|
"SfacgParser",
|
37
27
|
"YamiboParser",
|
38
28
|
]
|
29
|
+
|
30
|
+
from .biquge import BiqugeParser
|
31
|
+
from .esjzone import EsjzoneParser
|
32
|
+
from .linovelib import LinovelibParser
|
33
|
+
from .qianbi import QianbiParser
|
34
|
+
from .qidian import QidianParser
|
35
|
+
from .registry import get_parser
|
36
|
+
from .sfacg import SfacgParser
|
37
|
+
from .yamibo import YamiboParser
|
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
"""
|
3
|
-
novel_downloader.core.parsers.biquge
|
4
|
-
|
3
|
+
novel_downloader.core.parsers.biquge
|
4
|
+
------------------------------------
|
5
5
|
|
6
6
|
"""
|
7
7
|
|
@@ -11,9 +11,14 @@ from typing import Any
|
|
11
11
|
from lxml import html
|
12
12
|
|
13
13
|
from novel_downloader.core.parsers.base import BaseParser
|
14
|
+
from novel_downloader.core.parsers.registry import register_parser
|
14
15
|
from novel_downloader.models import ChapterDict
|
15
16
|
|
16
17
|
|
18
|
+
@register_parser(
|
19
|
+
site_keys=["biquge", "bqg"],
|
20
|
+
backends=["session", "browser"],
|
21
|
+
)
|
17
22
|
class BiqugeParser(BaseParser):
|
18
23
|
""" """
|
19
24
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
"""
|
3
|
-
novel_downloader.core.parsers.esjzone
|
4
|
-
|
3
|
+
novel_downloader.core.parsers.esjzone
|
4
|
+
-------------------------------------
|
5
5
|
|
6
6
|
"""
|
7
7
|
|
@@ -11,9 +11,14 @@ from typing import Any
|
|
11
11
|
from lxml import html
|
12
12
|
|
13
13
|
from novel_downloader.core.parsers.base import BaseParser
|
14
|
+
from novel_downloader.core.parsers.registry import register_parser
|
14
15
|
from novel_downloader.models import ChapterDict
|
15
16
|
|
16
17
|
|
18
|
+
@register_parser(
|
19
|
+
site_keys=["esjzone"],
|
20
|
+
backends=["session", "browser"],
|
21
|
+
)
|
17
22
|
class EsjzoneParser(BaseParser):
|
18
23
|
""" """
|
19
24
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
"""
|
3
|
-
novel_downloader.core.parsers.linovelib
|
4
|
-
|
3
|
+
novel_downloader.core.parsers.linovelib
|
4
|
+
---------------------------------------
|
5
5
|
|
6
6
|
"""
|
7
7
|
|
@@ -13,10 +13,15 @@ from typing import Any
|
|
13
13
|
from lxml import html
|
14
14
|
|
15
15
|
from novel_downloader.core.parsers.base import BaseParser
|
16
|
+
from novel_downloader.core.parsers.registry import register_parser
|
16
17
|
from novel_downloader.models import ChapterDict
|
17
18
|
from novel_downloader.utils.constants import LINOVELIB_FONT_MAP_PATH
|
18
19
|
|
19
20
|
|
21
|
+
@register_parser(
|
22
|
+
site_keys=["linovelib"],
|
23
|
+
backends=["session", "browser"],
|
24
|
+
)
|
20
25
|
class LinovelibParser(BaseParser):
|
21
26
|
""" """
|
22
27
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
"""
|
3
|
-
novel_downloader.core.parsers.qianbi
|
4
|
-
|
3
|
+
novel_downloader.core.parsers.qianbi
|
4
|
+
------------------------------------
|
5
5
|
|
6
6
|
"""
|
7
7
|
|
@@ -11,9 +11,14 @@ from typing import Any
|
|
11
11
|
from lxml import html
|
12
12
|
|
13
13
|
from novel_downloader.core.parsers.base import BaseParser
|
14
|
+
from novel_downloader.core.parsers.registry import register_parser
|
14
15
|
from novel_downloader.models import ChapterDict
|
15
16
|
|
16
17
|
|
18
|
+
@register_parser(
|
19
|
+
site_keys=["qianbi"],
|
20
|
+
backends=["session", "browser"],
|
21
|
+
)
|
17
22
|
class QianbiParser(BaseParser):
|
18
23
|
""" """
|
19
24
|
|