novel-downloader 1.3.3__py3-none-any.whl → 1.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +193 -0
- novel_downloader/core/fetchers/linovelib/session.py +193 -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.1.dist-info}/METADATA +69 -35
- novel_downloader-1.4.1.dist-info/RECORD +170 -0
- {novel_downloader-1.3.3.dist-info → novel_downloader-1.4.1.dist-info}/WHEEL +1 -1
- {novel_downloader-1.3.3.dist-info → novel_downloader-1.4.1.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.1.dist-info}/licenses/LICENSE +0 -0
- {novel_downloader-1.3.3.dist-info → novel_downloader-1.4.1.dist-info}/top_level.txt +0 -0
@@ -1,14 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
novel_downloader.core.requesters.biquge
|
4
|
-
---------------------------------------
|
5
|
-
|
6
|
-
"""
|
7
|
-
|
8
|
-
from .async_session import BiqugeAsyncSession
|
9
|
-
from .session import BiqugeSession
|
10
|
-
|
11
|
-
__all__ = [
|
12
|
-
"BiqugeAsyncSession",
|
13
|
-
"BiqugeSession",
|
14
|
-
]
|
@@ -1,17 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
novel_downloader.core.requesters.common
|
4
|
-
---------------------------------------
|
5
|
-
|
6
|
-
This module provides the `CommonSession` class wrapper for common HTTP
|
7
|
-
request operations to novel websites. It serves as a unified access
|
8
|
-
point to import `CommonSession` without exposing lower-level modules.
|
9
|
-
"""
|
10
|
-
|
11
|
-
from .async_session import CommonAsyncSession
|
12
|
-
from .session import CommonSession
|
13
|
-
|
14
|
-
__all__ = [
|
15
|
-
"CommonAsyncSession",
|
16
|
-
"CommonSession",
|
17
|
-
]
|
@@ -1,113 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
novel_downloader.core.requesters.common.session
|
4
|
-
-----------------------------------------------
|
5
|
-
|
6
|
-
This module defines a `CommonSession` class for handling HTTP requests
|
7
|
-
to common novel sites. It provides methods to retrieve raw book
|
8
|
-
information pages and chapter contents using a flexible URL templating
|
9
|
-
system defined by a site profile.
|
10
|
-
"""
|
11
|
-
|
12
|
-
from typing import Any
|
13
|
-
|
14
|
-
from novel_downloader.config import RequesterConfig, SiteProfile
|
15
|
-
from novel_downloader.core.requesters.base import BaseSession
|
16
|
-
|
17
|
-
|
18
|
-
class CommonSession(BaseSession):
|
19
|
-
"""
|
20
|
-
A common session for handling site-specific HTTP requests.
|
21
|
-
"""
|
22
|
-
|
23
|
-
def __init__(
|
24
|
-
self,
|
25
|
-
config: RequesterConfig,
|
26
|
-
site: str,
|
27
|
-
profile: SiteProfile,
|
28
|
-
cookies: dict[str, str] | None = None,
|
29
|
-
) -> None:
|
30
|
-
"""
|
31
|
-
Initialize a CommonSession instance.
|
32
|
-
|
33
|
-
:param config: The RequesterConfig instance containing settings.
|
34
|
-
:param site: The identifier or domain of the target site.
|
35
|
-
:param profile: The site's metadata and URL templates.
|
36
|
-
:param cookies: Optional cookies to preload into the session.
|
37
|
-
"""
|
38
|
-
super().__init__(config, cookies)
|
39
|
-
self._site = site
|
40
|
-
self._profile = profile
|
41
|
-
|
42
|
-
def get_book_info(
|
43
|
-
self,
|
44
|
-
book_id: str,
|
45
|
-
**kwargs: Any,
|
46
|
-
) -> list[str]:
|
47
|
-
"""
|
48
|
-
Fetch the raw HTML of the book info page.
|
49
|
-
|
50
|
-
:param book_id: The book identifier.
|
51
|
-
:return: The page content as a string.
|
52
|
-
"""
|
53
|
-
url = self.book_info_url(book_id=book_id)
|
54
|
-
try:
|
55
|
-
resp = self.get(url, **kwargs)
|
56
|
-
resp.raise_for_status()
|
57
|
-
return [resp.text]
|
58
|
-
except Exception as e:
|
59
|
-
self.logger.warning("Failed to fetch book info for %s: %s", book_id, e)
|
60
|
-
return []
|
61
|
-
|
62
|
-
def get_book_chapter(
|
63
|
-
self,
|
64
|
-
book_id: str,
|
65
|
-
chapter_id: str,
|
66
|
-
**kwargs: Any,
|
67
|
-
) -> list[str]:
|
68
|
-
"""
|
69
|
-
Fetch the raw HTML of a single chapter.
|
70
|
-
|
71
|
-
:param book_id: The book identifier.
|
72
|
-
:param chapter_id: The chapter identifier.
|
73
|
-
:return: The chapter content as a string.
|
74
|
-
"""
|
75
|
-
url = self.chapter_url(book_id=book_id, chapter_id=chapter_id)
|
76
|
-
try:
|
77
|
-
resp = self.get(url, **kwargs)
|
78
|
-
resp.raise_for_status()
|
79
|
-
return [resp.text]
|
80
|
-
except Exception as e:
|
81
|
-
self.logger.warning(
|
82
|
-
"Failed to fetch book chapter for %s(%s): %s",
|
83
|
-
book_id,
|
84
|
-
chapter_id,
|
85
|
-
e,
|
86
|
-
)
|
87
|
-
return []
|
88
|
-
|
89
|
-
@property
|
90
|
-
def site(self) -> str:
|
91
|
-
"""Return the site name."""
|
92
|
-
return self._site
|
93
|
-
|
94
|
-
def book_info_url(self, book_id: str) -> str:
|
95
|
-
"""
|
96
|
-
Construct the URL for fetching a book's info page.
|
97
|
-
|
98
|
-
:param book_id: The identifier of the book.
|
99
|
-
:return: Fully qualified URL for the book info page.
|
100
|
-
"""
|
101
|
-
return self._profile["book_info_url"].format(book_id=book_id)
|
102
|
-
|
103
|
-
def chapter_url(self, book_id: str, chapter_id: str) -> str:
|
104
|
-
"""
|
105
|
-
Construct the URL for fetching a specific chapter.
|
106
|
-
|
107
|
-
:param book_id: The identifier of the book.
|
108
|
-
:param chapter_id: The identifier of the chapter.
|
109
|
-
:return: Fully qualified chapter URL.
|
110
|
-
"""
|
111
|
-
return self._profile["chapter_url"].format(
|
112
|
-
book_id=book_id, chapter_id=chapter_id
|
113
|
-
)
|
@@ -1,13 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
novel_downloader.core.requesters.esjzone
|
3
|
-
----------------------------------------
|
4
|
-
|
5
|
-
"""
|
6
|
-
|
7
|
-
from .async_session import EsjzoneAsyncSession
|
8
|
-
from .session import EsjzoneSession
|
9
|
-
|
10
|
-
__all__ = [
|
11
|
-
"EsjzoneAsyncSession",
|
12
|
-
"EsjzoneSession",
|
13
|
-
]
|
@@ -1,235 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
novel_downloader.core.requesters.esjzone.session
|
3
|
-
----------------------------------------------
|
4
|
-
|
5
|
-
"""
|
6
|
-
|
7
|
-
import re
|
8
|
-
from typing import Any
|
9
|
-
|
10
|
-
from novel_downloader.config.models import RequesterConfig
|
11
|
-
from novel_downloader.core.requesters.base import BaseSession
|
12
|
-
from novel_downloader.utils.i18n import t
|
13
|
-
from novel_downloader.utils.state import state_mgr
|
14
|
-
from novel_downloader.utils.time_utils import sleep_with_random_delay
|
15
|
-
|
16
|
-
|
17
|
-
class EsjzoneSession(BaseSession):
|
18
|
-
"""
|
19
|
-
A session class for interacting with the
|
20
|
-
esjzone (www.esjzone.cc) novel website.
|
21
|
-
"""
|
22
|
-
|
23
|
-
BOOKCASE_URL = "https://www.esjzone.cc/my/favorite"
|
24
|
-
BOOK_INFO_URL = "https://www.esjzone.cc/detail/{book_id}.html"
|
25
|
-
CHAPTER_URL = "https://www.esjzone.cc/forum/{book_id}/{chapter_id}.html"
|
26
|
-
|
27
|
-
API_LOGIN_URL_1 = "https://www.esjzone.cc/my/login"
|
28
|
-
API_LOGIN_URL_2 = "https://www.esjzone.cc/inc/mem_login.php"
|
29
|
-
|
30
|
-
def __init__(
|
31
|
-
self,
|
32
|
-
config: RequesterConfig,
|
33
|
-
):
|
34
|
-
super().__init__(config)
|
35
|
-
self._logged_in: bool = False
|
36
|
-
self._request_interval = config.backoff_factor
|
37
|
-
self._retry_times = config.retry_times
|
38
|
-
self._username = config.username
|
39
|
-
self._password = config.password
|
40
|
-
|
41
|
-
def login(
|
42
|
-
self,
|
43
|
-
username: str = "",
|
44
|
-
password: str = "",
|
45
|
-
manual_login: bool = False,
|
46
|
-
**kwargs: Any,
|
47
|
-
) -> bool:
|
48
|
-
"""
|
49
|
-
Restore cookies persisted by the session-based workflow.
|
50
|
-
"""
|
51
|
-
cookies: dict[str, str] = state_mgr.get_cookies("esjzone")
|
52
|
-
username = username or self._username
|
53
|
-
password = password or self._password
|
54
|
-
|
55
|
-
self.update_cookies(cookies)
|
56
|
-
for _ in range(self._retry_times):
|
57
|
-
if self._check_login_status():
|
58
|
-
self.logger.debug("[auth] Already logged in.")
|
59
|
-
self._logged_in = True
|
60
|
-
return True
|
61
|
-
if username and password and not self._api_login(username, password):
|
62
|
-
print(t("session_login_failed", site="esjzone"))
|
63
|
-
sleep_with_random_delay(
|
64
|
-
self._request_interval,
|
65
|
-
mul_spread=1.1,
|
66
|
-
max_sleep=self._request_interval + 2,
|
67
|
-
)
|
68
|
-
|
69
|
-
self._logged_in = self._check_login_status()
|
70
|
-
return self._logged_in
|
71
|
-
|
72
|
-
def get_book_info(
|
73
|
-
self,
|
74
|
-
book_id: str,
|
75
|
-
**kwargs: Any,
|
76
|
-
) -> list[str]:
|
77
|
-
"""
|
78
|
-
Fetch the raw HTML of the book info and catalog pages.
|
79
|
-
|
80
|
-
Order: [info, catalog]
|
81
|
-
|
82
|
-
:param book_id: The book identifier.
|
83
|
-
:return: The page content as a string.
|
84
|
-
"""
|
85
|
-
url = self.book_info_url(book_id=book_id)
|
86
|
-
try:
|
87
|
-
resp = self.get(url, **kwargs)
|
88
|
-
resp.raise_for_status()
|
89
|
-
return [resp.text]
|
90
|
-
except Exception as exc:
|
91
|
-
self.logger.warning(
|
92
|
-
"[session] get_book_info(%s) failed: %s",
|
93
|
-
book_id,
|
94
|
-
exc,
|
95
|
-
)
|
96
|
-
return []
|
97
|
-
|
98
|
-
def get_book_chapter(
|
99
|
-
self,
|
100
|
-
book_id: str,
|
101
|
-
chapter_id: str,
|
102
|
-
**kwargs: Any,
|
103
|
-
) -> list[str]:
|
104
|
-
"""
|
105
|
-
Fetch the HTML of a single chapter.
|
106
|
-
|
107
|
-
:param book_id: The book identifier.
|
108
|
-
:param chapter_id: The chapter identifier.
|
109
|
-
:return: The chapter content as a string.
|
110
|
-
"""
|
111
|
-
url = self.chapter_url(book_id=book_id, chapter_id=chapter_id)
|
112
|
-
try:
|
113
|
-
resp = self.get(url, **kwargs)
|
114
|
-
resp.raise_for_status()
|
115
|
-
return [resp.text]
|
116
|
-
except Exception as exc:
|
117
|
-
self.logger.warning(
|
118
|
-
"[session] get_book_chapter(%s) failed: %s",
|
119
|
-
book_id,
|
120
|
-
exc,
|
121
|
-
)
|
122
|
-
return []
|
123
|
-
|
124
|
-
def get_bookcase(
|
125
|
-
self,
|
126
|
-
page: int = 1,
|
127
|
-
**kwargs: Any,
|
128
|
-
) -> list[str]:
|
129
|
-
"""
|
130
|
-
Retrieve the user's *bookcase* page.
|
131
|
-
|
132
|
-
:return: The HTML markup of the bookcase page.
|
133
|
-
"""
|
134
|
-
url = self.bookcase_url()
|
135
|
-
try:
|
136
|
-
resp = self.get(url, **kwargs)
|
137
|
-
resp.raise_for_status()
|
138
|
-
return [resp.text]
|
139
|
-
except Exception as exc:
|
140
|
-
self.logger.warning(
|
141
|
-
"[session] get_bookcase failed: %s",
|
142
|
-
exc,
|
143
|
-
)
|
144
|
-
return []
|
145
|
-
|
146
|
-
@classmethod
|
147
|
-
def bookcase_url(cls) -> str:
|
148
|
-
"""
|
149
|
-
Construct the URL for the user's bookcase page.
|
150
|
-
|
151
|
-
:return: Fully qualified URL of the bookcase.
|
152
|
-
"""
|
153
|
-
return cls.BOOKCASE_URL
|
154
|
-
|
155
|
-
@classmethod
|
156
|
-
def book_info_url(cls, book_id: str) -> str:
|
157
|
-
"""
|
158
|
-
Construct the URL for fetching a book's info page.
|
159
|
-
|
160
|
-
:param book_id: The identifier of the book.
|
161
|
-
:return: Fully qualified URL for the book info page.
|
162
|
-
"""
|
163
|
-
return cls.BOOK_INFO_URL.format(book_id=book_id)
|
164
|
-
|
165
|
-
@classmethod
|
166
|
-
def chapter_url(cls, book_id: str, chapter_id: str) -> str:
|
167
|
-
"""
|
168
|
-
Construct the URL for fetching a specific chapter.
|
169
|
-
|
170
|
-
:param book_id: The identifier of the book.
|
171
|
-
:param chapter_id: The identifier of the chapter.
|
172
|
-
:return: Fully qualified chapter URL.
|
173
|
-
"""
|
174
|
-
return cls.CHAPTER_URL.format(book_id=book_id, chapter_id=chapter_id)
|
175
|
-
|
176
|
-
def _api_login(self, username: str, password: str) -> bool:
|
177
|
-
"""
|
178
|
-
Login to the API using a 2-step token-based process.
|
179
|
-
|
180
|
-
Step 1: Get auth token.
|
181
|
-
Step 2: Use token and credentials to perform login.
|
182
|
-
Return True if login succeeds, False otherwise.
|
183
|
-
"""
|
184
|
-
data_1 = {
|
185
|
-
"plxf": "getAuthToken",
|
186
|
-
}
|
187
|
-
try:
|
188
|
-
resp_1 = self.post(self.API_LOGIN_URL_1, data=data_1)
|
189
|
-
resp_1.raise_for_status()
|
190
|
-
# Example response: <JinJing>token_here</JinJing>
|
191
|
-
token = self._extract_token(resp_1.text)
|
192
|
-
except Exception as exc:
|
193
|
-
self.logger.warning("[session] _api_login failed at step 1: %s", exc)
|
194
|
-
return False
|
195
|
-
|
196
|
-
data_2 = {
|
197
|
-
"email": username,
|
198
|
-
"pwd": password,
|
199
|
-
"remember_me": "on",
|
200
|
-
}
|
201
|
-
temp_headers = dict(self.headers)
|
202
|
-
temp_headers["Authorization"] = token
|
203
|
-
try:
|
204
|
-
resp_2 = self.post(self.API_LOGIN_URL_2, data=data_2, headers=temp_headers)
|
205
|
-
resp_2.raise_for_status()
|
206
|
-
resp_code: int = resp_2.json().get("status", 301)
|
207
|
-
return resp_code == 200
|
208
|
-
except Exception as exc:
|
209
|
-
self.logger.warning("[session] _api_login failed at step 2: %s", exc)
|
210
|
-
return False
|
211
|
-
|
212
|
-
def _check_login_status(self) -> bool:
|
213
|
-
"""
|
214
|
-
Check whether the user is currently logged in by
|
215
|
-
inspecting the bookcase page content.
|
216
|
-
|
217
|
-
:return: True if the user is logged in, False otherwise.
|
218
|
-
"""
|
219
|
-
keywords = [
|
220
|
-
"window.location.href='/my/login'",
|
221
|
-
]
|
222
|
-
resp_text = self.get_bookcase()
|
223
|
-
if not resp_text:
|
224
|
-
return False
|
225
|
-
return not any(kw in resp_text[0] for kw in keywords)
|
226
|
-
|
227
|
-
def _extract_token(self, text: str) -> str:
|
228
|
-
match = re.search(r"<JinJing>(.+?)</JinJing>", text)
|
229
|
-
return match.group(1) if match else ""
|
230
|
-
|
231
|
-
def _on_close(self) -> None:
|
232
|
-
"""
|
233
|
-
Save cookies to the state manager before closing.
|
234
|
-
"""
|
235
|
-
state_mgr.set_cookies("esjzone", self.cookies)
|
@@ -1,21 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
novel_downloader.core.requesters.qidian
|
4
|
-
---------------------------------------
|
5
|
-
|
6
|
-
This package provides the implementation of the Qidian-specific requester logic.
|
7
|
-
It contains modules for interacting with Qidian's website, including login,
|
8
|
-
page navigation, and data retrieval using a browser-based automation approach.
|
9
|
-
|
10
|
-
Modules:
|
11
|
-
- browser
|
12
|
-
- session
|
13
|
-
"""
|
14
|
-
|
15
|
-
from .broswer import QidianBrowser
|
16
|
-
from .session import QidianSession
|
17
|
-
|
18
|
-
__all__ = [
|
19
|
-
"QidianBrowser",
|
20
|
-
"QidianSession",
|
21
|
-
]
|