novel-downloader 1.5.0__py3-none-any.whl → 2.0.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/__init__.py +1 -3
- novel_downloader/cli/clean.py +21 -88
- novel_downloader/cli/config.py +26 -21
- novel_downloader/cli/download.py +79 -66
- novel_downloader/cli/export.py +17 -21
- 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 +206 -209
- novel_downloader/config/{loader.py → file_io.py} +53 -26
- novel_downloader/core/__init__.py +5 -5
- 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 +17 -12
- 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 +20 -14
- 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} +56 -64
- 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 +6 -19
- novel_downloader/core/interfaces/parser.py +7 -8
- 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 +64 -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 +64 -69
- 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/main_parser.py +756 -48
- novel_downloader/core/parsers/qidian/utils/__init__.py +3 -21
- novel_downloader/core/parsers/qidian/utils/decryptor_fetcher.py +1 -1
- novel_downloader/core/parsers/qidian/utils/node_decryptor.py +4 -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 +429 -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 +34 -85
- novel_downloader/locales/zh.json +35 -86
- 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 -24
- novel_downloader/utils/chapter_storage.py +5 -5
- novel_downloader/utils/constants.py +4 -31
- novel_downloader/utils/cookies.py +38 -35
- novel_downloader/utils/crypto_utils/__init__.py +7 -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/rc4.py +54 -0
- novel_downloader/utils/epub/__init__.py +3 -4
- novel_downloader/utils/epub/builder.py +6 -6
- novel_downloader/utils/epub/constants.py +62 -21
- novel_downloader/utils/epub/documents.py +95 -201
- novel_downloader/utils/epub/models.py +8 -22
- novel_downloader/utils/epub/utils.py +73 -106
- novel_downloader/utils/file_utils/__init__.py +2 -23
- novel_downloader/utils/file_utils/io.py +53 -188
- novel_downloader/utils/file_utils/normalize.py +1 -7
- novel_downloader/utils/file_utils/sanitize.py +4 -15
- novel_downloader/utils/fontocr/__init__.py +5 -14
- novel_downloader/utils/fontocr/core.py +216 -0
- novel_downloader/utils/fontocr/loader.py +50 -0
- novel_downloader/utils/logger.py +81 -65
- novel_downloader/utils/network.py +17 -41
- 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/text_utils/text_cleaner.py +39 -30
- novel_downloader/utils/text_utils/truncate_utils.py +3 -14
- 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 +55 -49
- 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.1.dist-info/METADATA +172 -0
- novel_downloader-2.0.1.dist-info/RECORD +206 -0
- {novel_downloader-1.5.0.dist-info → novel_downloader-2.0.1.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/core/parsers/qidian/book_info_parser.py +0 -90
- novel_downloader/core/parsers/qidian/chapter_encrypted.py +0 -528
- novel_downloader/core/parsers/qidian/chapter_normal.py +0 -157
- novel_downloader/core/parsers/qidian/chapter_router.py +0 -68
- novel_downloader/core/parsers/qidian/utils/helpers.py +0 -114
- 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/crypto_utils.py +0 -71
- 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.1.dist-info}/WHEEL +0 -0
- {novel_downloader-1.5.0.dist-info → novel_downloader-2.0.1.dist-info}/licenses/LICENSE +0 -0
- {novel_downloader-1.5.0.dist-info → novel_downloader-2.0.1.dist-info}/top_level.txt +0 -0
@@ -3,45 +3,34 @@
|
|
3
3
|
novel_downloader.utils
|
4
4
|
----------------------
|
5
5
|
|
6
|
+
A collection of helper functions and classes.
|
6
7
|
"""
|
7
8
|
|
8
9
|
__all__ = [
|
9
10
|
"ChapterStorage",
|
10
11
|
"TextCleaner",
|
11
|
-
"
|
12
|
-
"
|
13
|
-
"find_cookie_value",
|
14
|
-
"rc4_crypt",
|
12
|
+
"parse_cookies",
|
13
|
+
"get_cookie_value",
|
15
14
|
"sanitize_filename",
|
16
|
-
"
|
17
|
-
"save_as_txt",
|
18
|
-
"read_text_file",
|
19
|
-
"read_json_file",
|
20
|
-
"read_binary_file",
|
15
|
+
"write_file",
|
21
16
|
"download",
|
22
17
|
"get_cleaner",
|
23
18
|
"content_prefix",
|
24
19
|
"truncate_half_lines",
|
25
20
|
"diff_inline_display",
|
26
|
-
"
|
27
|
-
"
|
28
|
-
"
|
21
|
+
"time_diff",
|
22
|
+
"async_jitter_sleep",
|
23
|
+
"jitter_sleep",
|
29
24
|
]
|
30
25
|
|
31
26
|
from .chapter_storage import ChapterStorage
|
32
27
|
from .cookies import (
|
33
|
-
|
34
|
-
|
35
|
-
resolve_cookies,
|
28
|
+
get_cookie_value,
|
29
|
+
parse_cookies,
|
36
30
|
)
|
37
|
-
from .crypto_utils import rc4_crypt
|
38
31
|
from .file_utils import (
|
39
|
-
read_binary_file,
|
40
|
-
read_json_file,
|
41
|
-
read_text_file,
|
42
32
|
sanitize_filename,
|
43
|
-
|
44
|
-
save_as_txt,
|
33
|
+
write_file,
|
45
34
|
)
|
46
35
|
from .network import download
|
47
36
|
from .text_utils import (
|
@@ -52,7 +41,7 @@ from .text_utils import (
|
|
52
41
|
truncate_half_lines,
|
53
42
|
)
|
54
43
|
from .time_utils import (
|
55
|
-
|
56
|
-
|
57
|
-
|
44
|
+
async_jitter_sleep,
|
45
|
+
jitter_sleep,
|
46
|
+
time_diff,
|
58
47
|
)
|
@@ -3,16 +3,17 @@
|
|
3
3
|
novel_downloader.utils.chapter_storage
|
4
4
|
--------------------------------------
|
5
5
|
|
6
|
-
Storage module for managing novel chapters in
|
7
|
-
either JSON file form or an SQLite database.
|
6
|
+
Storage module for managing novel chapters in an SQLite database.
|
8
7
|
"""
|
9
8
|
|
9
|
+
__all__ = ["ChapterStorage"]
|
10
|
+
|
10
11
|
import contextlib
|
11
12
|
import json
|
12
13
|
import sqlite3
|
13
14
|
import types
|
14
15
|
from pathlib import Path
|
15
|
-
from typing import Any, Self
|
16
|
+
from typing import Any, Self
|
16
17
|
|
17
18
|
from novel_downloader.models import ChapterDict
|
18
19
|
|
@@ -312,8 +313,7 @@ class ChapterStorage:
|
|
312
313
|
@staticmethod
|
313
314
|
def _load_dict(data: str) -> dict[str, Any]:
|
314
315
|
try:
|
315
|
-
|
316
|
-
return cast(dict[str, Any], parsed)
|
316
|
+
return json.loads(data) or {}
|
317
317
|
except Exception:
|
318
318
|
return {}
|
319
319
|
|
@@ -16,14 +16,12 @@ from platformdirs import user_config_path
|
|
16
16
|
# -----------------------------------------------------------------------------
|
17
17
|
PACKAGE_NAME = "novel_downloader" # Python package name
|
18
18
|
APP_NAME = "NovelDownloader" # Display name
|
19
|
-
APP_DIR_NAME = "novel_downloader" # Directory name for platformdirs
|
20
|
-
LOGGER_NAME = PACKAGE_NAME # Root logger name
|
21
19
|
|
22
20
|
# -----------------------------------------------------------------------------
|
23
21
|
# Base directories
|
24
22
|
# -----------------------------------------------------------------------------
|
25
23
|
# Base config directory (e.g. ~/AppData/Local/novel_downloader/)
|
26
|
-
BASE_CONFIG_DIR =
|
24
|
+
BASE_CONFIG_DIR = user_config_path(PACKAGE_NAME, appauthor=False)
|
27
25
|
WORK_DIR = Path.cwd()
|
28
26
|
PACKAGE_ROOT: Path = Path(__file__).parent.parent
|
29
27
|
LOCALES_DIR: Path = PACKAGE_ROOT / "locales"
|
@@ -33,20 +31,17 @@ LOGGER_DIR = WORK_DIR / "logs"
|
|
33
31
|
JS_SCRIPT_DIR = BASE_CONFIG_DIR / "scripts"
|
34
32
|
DATA_DIR = BASE_CONFIG_DIR / "data"
|
35
33
|
CONFIG_DIR = BASE_CONFIG_DIR / "config"
|
36
|
-
MODEL_CACHE_DIR = BASE_CONFIG_DIR / "models"
|
37
34
|
|
38
35
|
# -----------------------------------------------------------------------------
|
39
36
|
# Default file paths
|
40
37
|
# -----------------------------------------------------------------------------
|
41
38
|
STATE_FILE = DATA_DIR / "state.json"
|
42
39
|
SETTING_FILE = CONFIG_DIR / "settings.json"
|
43
|
-
DEFAULT_USER_DATA_DIR = DATA_DIR / "browser_data"
|
44
40
|
|
45
41
|
|
46
42
|
# -----------------------------------------------------------------------------
|
47
43
|
# Default preferences & headers
|
48
44
|
# -----------------------------------------------------------------------------
|
49
|
-
DEFAULT_USER_PROFILE_NAME = "Profile_1"
|
50
45
|
DEFAULT_IMAGE_SUFFIX = ".jpg"
|
51
46
|
|
52
47
|
DEFAULT_USER_AGENT = (
|
@@ -90,33 +85,11 @@ VOLUME_BORDER_IMAGE_PATH = files("novel_downloader.resources.images").joinpath(
|
|
90
85
|
LINOVELIB_FONT_MAP_PATH = files("novel_downloader.resources.json").joinpath(
|
91
86
|
"linovelib_font_map.json"
|
92
87
|
)
|
88
|
+
XIGUASHUWU_FONT_MAP_PATH = files("novel_downloader.resources.json").joinpath(
|
89
|
+
"xiguashuwu.json"
|
90
|
+
)
|
93
91
|
|
94
92
|
# JavaScript
|
95
93
|
QD_DECRYPT_SCRIPT_PATH = files("novel_downloader.resources.js_scripts").joinpath(
|
96
94
|
"qidian_decrypt_node.js"
|
97
95
|
)
|
98
|
-
|
99
|
-
# ---------------------------------------------------------------------
|
100
|
-
# Pretrained model registry (e.g. used in font recovery or OCR)
|
101
|
-
# ---------------------------------------------------------------------
|
102
|
-
|
103
|
-
# Hugging Face model repo for character recognition
|
104
|
-
REC_CHAR_MODEL_REPO = "saudadez/rec_chinese_char"
|
105
|
-
|
106
|
-
# Required files to be downloaded for the model
|
107
|
-
REC_CHAR_MODEL_FILES = [
|
108
|
-
"inference.pdmodel",
|
109
|
-
"inference.pdiparams",
|
110
|
-
"rec_custom_keys.txt",
|
111
|
-
"char_freq.json",
|
112
|
-
]
|
113
|
-
|
114
|
-
REC_CHAR_VECTOR_FILES = [
|
115
|
-
"char_vectors.npy",
|
116
|
-
"char_vectors.txt",
|
117
|
-
]
|
118
|
-
|
119
|
-
REC_IMAGE_SHAPE_MAP = {
|
120
|
-
"v1.0": "3,32,32",
|
121
|
-
"v2.0": "3,48,48",
|
122
|
-
}
|
@@ -6,61 +6,64 @@ novel_downloader.utils.cookies
|
|
6
6
|
Utility for normalizing cookie input from user configuration.
|
7
7
|
"""
|
8
8
|
|
9
|
+
__all__ = ["parse_cookies", "get_cookie_value"]
|
10
|
+
|
11
|
+
import functools
|
9
12
|
import json
|
10
13
|
from collections.abc import Mapping
|
11
|
-
from email.utils import parsedate_to_datetime
|
12
|
-
from http.cookies import SimpleCookie
|
13
14
|
from pathlib import Path
|
15
|
+
from typing import Any
|
14
16
|
|
15
17
|
|
16
|
-
def
|
18
|
+
def parse_cookies(cookies: str | Mapping[str, str]) -> dict[str, str]:
|
17
19
|
"""
|
18
20
|
Parse cookies from a string or dictionary into a standard dictionary.
|
19
21
|
|
20
22
|
Supports input like:
|
21
|
-
|
22
|
-
|
23
|
+
* `"key1=value1; key2=value2"`
|
24
|
+
* `{"key1": "value1", "key2": "value2"}`
|
23
25
|
|
24
26
|
:param cookies: Cookie string or dict-like object (e.g., from config)
|
25
27
|
:return: A normalized cookie dictionary (key -> value)
|
26
28
|
:raises TypeError: If the input is neither string nor dict-like
|
27
29
|
"""
|
28
30
|
if isinstance(cookies, str):
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
31
|
+
result: dict[str, str] = {}
|
32
|
+
for part in cookies.split(";"):
|
33
|
+
if "=" not in part:
|
34
|
+
continue
|
35
|
+
key, value = part.split("=", 1)
|
36
|
+
key, value = key.strip(), value.strip()
|
37
|
+
if not key:
|
38
|
+
continue
|
39
|
+
result[key] = value
|
40
|
+
return result
|
33
41
|
elif isinstance(cookies, Mapping):
|
34
42
|
return {str(k).strip(): str(v).strip() for k, v in cookies.items()}
|
35
43
|
raise TypeError("Unsupported cookie format: must be str or dict-like")
|
36
44
|
|
37
45
|
|
38
|
-
def
|
39
|
-
if not value:
|
40
|
-
return -1
|
41
|
-
try:
|
42
|
-
return int(value)
|
43
|
-
except (ValueError, TypeError):
|
44
|
-
try:
|
45
|
-
dt = parsedate_to_datetime(value)
|
46
|
-
return int(dt.timestamp())
|
47
|
-
except Exception:
|
48
|
-
return -1
|
49
|
-
|
50
|
-
|
51
|
-
def find_cookie_value(state_files: list[Path], key: str) -> str:
|
46
|
+
def get_cookie_value(state_files: list[Path], key: str) -> str:
|
52
47
|
for state_file in state_files:
|
53
|
-
|
54
|
-
|
55
|
-
data = json.load(f)
|
56
|
-
except Exception:
|
57
|
-
continue
|
58
|
-
|
48
|
+
mtime = state_file.stat().st_mtime
|
49
|
+
data = load_state_file(state_file, mtime)
|
59
50
|
cookies = data.get("cookies", [])
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
51
|
+
value = next(
|
52
|
+
(
|
53
|
+
c.get("value")
|
54
|
+
for c in cookies
|
55
|
+
if c.get("name") == key and isinstance(c.get("value"), str)
|
56
|
+
),
|
57
|
+
None,
|
58
|
+
)
|
59
|
+
if isinstance(value, str):
|
60
|
+
return value
|
66
61
|
return ""
|
62
|
+
|
63
|
+
|
64
|
+
@functools.cache
|
65
|
+
def load_state_file(state_file: Path, mtime: float = 0.0) -> dict[str, Any]:
|
66
|
+
try:
|
67
|
+
return json.loads(state_file.read_text(encoding="utf-8")) or {}
|
68
|
+
except (OSError, json.JSONDecodeError):
|
69
|
+
return {}
|
@@ -0,0 +1,90 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
novel_downloader.utils.crypto_utils.aes_util
|
4
|
+
--------------------------------------------
|
5
|
+
|
6
|
+
AES decrypt functions.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from __future__ import annotations
|
10
|
+
|
11
|
+
__all__ = ["aes_cbc_decrypt"]
|
12
|
+
|
13
|
+
from typing import Any
|
14
|
+
|
15
|
+
_BLOCK = 16
|
16
|
+
_VALID_KEY_SIZES = (16, 24, 32)
|
17
|
+
|
18
|
+
|
19
|
+
def _as_bytes(name: str, b: Any) -> bytes:
|
20
|
+
if isinstance(b, bytes):
|
21
|
+
return b
|
22
|
+
if isinstance(b, bytearray | memoryview):
|
23
|
+
return bytes(b)
|
24
|
+
raise TypeError(f"{name} must be bytes-like, got {type(b).__name__}")
|
25
|
+
|
26
|
+
|
27
|
+
def _validate_inputs(key: bytes, iv: bytes, data: bytes) -> None:
|
28
|
+
if len(iv) != _BLOCK:
|
29
|
+
raise ValueError(f"iv must be {_BLOCK} bytes, got {len(iv)}")
|
30
|
+
if len(key) not in _VALID_KEY_SIZES:
|
31
|
+
raise ValueError(
|
32
|
+
f"key length must be one of {_VALID_KEY_SIZES} bytes, got {len(key)}"
|
33
|
+
)
|
34
|
+
if len(data) % _BLOCK != 0:
|
35
|
+
raise ValueError(
|
36
|
+
f"ciphertext length must be a multiple of {_BLOCK} bytes, got {len(data)}"
|
37
|
+
)
|
38
|
+
|
39
|
+
|
40
|
+
try:
|
41
|
+
from Crypto.Cipher import AES as _PyAES
|
42
|
+
from Crypto.Util.Padding import unpad as _py_unpad
|
43
|
+
|
44
|
+
def aes_cbc_decrypt(
|
45
|
+
key: bytes, iv: bytes, data: bytes, block_size: int = _BLOCK
|
46
|
+
) -> bytes:
|
47
|
+
"""
|
48
|
+
AES-CBC decrypt + PKCS#7 unpad (PyCryptodome).
|
49
|
+
|
50
|
+
:param key: AES key (16/24/32 bytes)
|
51
|
+
:param iv: Initialization vector (16 bytes)
|
52
|
+
:param data: Ciphertext, length multiple of 16
|
53
|
+
:return: Plaintext bytes (unpadded)
|
54
|
+
:raises TypeError, ValueError: on invalid inputs
|
55
|
+
"""
|
56
|
+
key_b = _as_bytes("key", key)
|
57
|
+
iv_b = _as_bytes("iv", iv)
|
58
|
+
data_b = _as_bytes("data", data)
|
59
|
+
if not data_b:
|
60
|
+
return b""
|
61
|
+
_validate_inputs(key_b, iv_b, data_b)
|
62
|
+
pt = _PyAES.new(key_b, _PyAES.MODE_CBC, iv_b).decrypt(data_b)
|
63
|
+
return _py_unpad(pt, block_size, style="pkcs7") # type: ignore[no-any-return]
|
64
|
+
|
65
|
+
except ImportError:
|
66
|
+
print(
|
67
|
+
"[crypto_utils] Falling back to pure-Python AES_CBC.\n"
|
68
|
+
"Tip: pip install pycryptodome for ~800x faster speed."
|
69
|
+
)
|
70
|
+
from novel_downloader.utils.crypto_utils.aes_v2 import AES_CBC
|
71
|
+
|
72
|
+
def aes_cbc_decrypt(
|
73
|
+
key: bytes, iv: bytes, data: bytes, block_size: int = _BLOCK
|
74
|
+
) -> bytes:
|
75
|
+
"""
|
76
|
+
AES-CBC decrypt + PKCS#7 unpad (handled by AES_CBC internally).
|
77
|
+
|
78
|
+
:param key: AES key (16/24/32 bytes)
|
79
|
+
:param iv: Initialization vector (16 bytes)
|
80
|
+
:param data: Ciphertext, length multiple of 16
|
81
|
+
:return: Plaintext bytes (unpadded)
|
82
|
+
:raises TypeError, ValueError: on invalid inputs
|
83
|
+
"""
|
84
|
+
key_b = _as_bytes("key", key)
|
85
|
+
iv_b = _as_bytes("iv", iv)
|
86
|
+
data_b = _as_bytes("data", data)
|
87
|
+
if not data_b:
|
88
|
+
return b""
|
89
|
+
_validate_inputs(key_b, iv_b, data_b)
|
90
|
+
return AES_CBC(key_b, iv_b).decrypt_padded(data_b, block_size)
|