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