novel-downloader 1.1.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 +14 -0
- novel_downloader/cli/__init__.py +14 -0
- novel_downloader/cli/clean.py +134 -0
- novel_downloader/cli/download.py +98 -0
- novel_downloader/cli/interactive.py +67 -0
- novel_downloader/cli/main.py +45 -0
- novel_downloader/cli/settings.py +177 -0
- novel_downloader/config/__init__.py +52 -0
- novel_downloader/config/adapter.py +150 -0
- novel_downloader/config/loader.py +177 -0
- novel_downloader/config/models.py +170 -0
- novel_downloader/config/site_rules.py +97 -0
- novel_downloader/core/__init__.py +25 -0
- novel_downloader/core/downloaders/__init__.py +20 -0
- novel_downloader/core/downloaders/base_downloader.py +187 -0
- novel_downloader/core/downloaders/common_downloader.py +192 -0
- novel_downloader/core/downloaders/qidian_downloader.py +208 -0
- novel_downloader/core/factory/__init__.py +21 -0
- novel_downloader/core/factory/downloader_factory.py +62 -0
- novel_downloader/core/factory/parser_factory.py +62 -0
- novel_downloader/core/factory/requester_factory.py +62 -0
- novel_downloader/core/factory/saver_factory.py +49 -0
- novel_downloader/core/interfaces/__init__.py +28 -0
- novel_downloader/core/interfaces/downloader_protocol.py +37 -0
- novel_downloader/core/interfaces/parser_protocol.py +40 -0
- novel_downloader/core/interfaces/requester_protocol.py +65 -0
- novel_downloader/core/interfaces/saver_protocol.py +61 -0
- novel_downloader/core/parsers/__init__.py +28 -0
- novel_downloader/core/parsers/base_parser.py +96 -0
- novel_downloader/core/parsers/common_parser/__init__.py +14 -0
- novel_downloader/core/parsers/common_parser/helper.py +321 -0
- novel_downloader/core/parsers/common_parser/main_parser.py +86 -0
- novel_downloader/core/parsers/qidian_parser/__init__.py +20 -0
- novel_downloader/core/parsers/qidian_parser/browser/__init__.py +13 -0
- novel_downloader/core/parsers/qidian_parser/browser/chapter_encrypted.py +498 -0
- novel_downloader/core/parsers/qidian_parser/browser/chapter_normal.py +97 -0
- novel_downloader/core/parsers/qidian_parser/browser/chapter_router.py +70 -0
- novel_downloader/core/parsers/qidian_parser/browser/main_parser.py +110 -0
- novel_downloader/core/parsers/qidian_parser/session/__init__.py +13 -0
- novel_downloader/core/parsers/qidian_parser/session/chapter_encrypted.py +451 -0
- novel_downloader/core/parsers/qidian_parser/session/chapter_normal.py +119 -0
- novel_downloader/core/parsers/qidian_parser/session/chapter_router.py +67 -0
- novel_downloader/core/parsers/qidian_parser/session/main_parser.py +113 -0
- novel_downloader/core/parsers/qidian_parser/session/node_decryptor.py +164 -0
- novel_downloader/core/parsers/qidian_parser/shared/__init__.py +38 -0
- novel_downloader/core/parsers/qidian_parser/shared/book_info_parser.py +95 -0
- novel_downloader/core/parsers/qidian_parser/shared/helpers.py +133 -0
- novel_downloader/core/requesters/__init__.py +27 -0
- novel_downloader/core/requesters/base_browser.py +210 -0
- novel_downloader/core/requesters/base_session.py +243 -0
- novel_downloader/core/requesters/common_requester/__init__.py +14 -0
- novel_downloader/core/requesters/common_requester/common_session.py +126 -0
- novel_downloader/core/requesters/qidian_requester/__init__.py +22 -0
- novel_downloader/core/requesters/qidian_requester/qidian_broswer.py +377 -0
- novel_downloader/core/requesters/qidian_requester/qidian_session.py +202 -0
- novel_downloader/core/savers/__init__.py +20 -0
- novel_downloader/core/savers/base_saver.py +169 -0
- novel_downloader/core/savers/common_saver/__init__.py +13 -0
- novel_downloader/core/savers/common_saver/common_epub.py +232 -0
- novel_downloader/core/savers/common_saver/common_txt.py +176 -0
- novel_downloader/core/savers/common_saver/main_saver.py +86 -0
- novel_downloader/core/savers/epub_utils/__init__.py +27 -0
- novel_downloader/core/savers/epub_utils/css_builder.py +68 -0
- novel_downloader/core/savers/epub_utils/initializer.py +98 -0
- novel_downloader/core/savers/epub_utils/text_to_html.py +132 -0
- novel_downloader/core/savers/epub_utils/volume_intro.py +61 -0
- novel_downloader/core/savers/qidian_saver.py +22 -0
- novel_downloader/locales/en.json +91 -0
- novel_downloader/locales/zh.json +91 -0
- novel_downloader/resources/config/rules.toml +196 -0
- novel_downloader/resources/config/settings.yaml +70 -0
- novel_downloader/resources/css_styles/main.css +104 -0
- novel_downloader/resources/css_styles/volume-intro.css +56 -0
- novel_downloader/resources/images/volume_border.png +0 -0
- novel_downloader/resources/js_scripts/qidian_decrypt_node.js +82 -0
- novel_downloader/resources/json/replace_word_map.json +4 -0
- novel_downloader/resources/text/blacklist.txt +22 -0
- novel_downloader/utils/__init__.py +0 -0
- novel_downloader/utils/cache.py +24 -0
- novel_downloader/utils/constants.py +158 -0
- novel_downloader/utils/crypto_utils.py +144 -0
- novel_downloader/utils/file_utils/__init__.py +43 -0
- novel_downloader/utils/file_utils/io.py +252 -0
- novel_downloader/utils/file_utils/normalize.py +68 -0
- novel_downloader/utils/file_utils/sanitize.py +77 -0
- novel_downloader/utils/fontocr/__init__.py +23 -0
- novel_downloader/utils/fontocr/ocr_v1.py +304 -0
- novel_downloader/utils/fontocr/ocr_v2.py +658 -0
- novel_downloader/utils/hash_store.py +288 -0
- novel_downloader/utils/hash_utils.py +103 -0
- novel_downloader/utils/i18n.py +41 -0
- novel_downloader/utils/logger.py +104 -0
- novel_downloader/utils/model_loader.py +72 -0
- novel_downloader/utils/network.py +287 -0
- novel_downloader/utils/state.py +156 -0
- novel_downloader/utils/text_utils/__init__.py +27 -0
- novel_downloader/utils/text_utils/chapter_formatting.py +46 -0
- novel_downloader/utils/text_utils/diff_display.py +75 -0
- novel_downloader/utils/text_utils/font_mapping.py +31 -0
- novel_downloader/utils/text_utils/text_cleaning.py +57 -0
- novel_downloader/utils/time_utils/__init__.py +22 -0
- novel_downloader/utils/time_utils/datetime_utils.py +146 -0
- novel_downloader/utils/time_utils/sleep_utils.py +49 -0
- novel_downloader-1.1.1.dist-info/METADATA +137 -0
- novel_downloader-1.1.1.dist-info/RECORD +109 -0
- novel_downloader-1.1.1.dist-info/WHEEL +5 -0
- novel_downloader-1.1.1.dist-info/entry_points.txt +2 -0
- novel_downloader-1.1.1.dist-info/licenses/LICENSE +21 -0
- novel_downloader-1.1.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,196 @@
|
|
1
|
+
[qidian.profile] # 起点
|
2
|
+
book_info_url = "https://book.qidian.com/info/{book_id}/"
|
3
|
+
chapter_url = "https://www.qidian.com/chapter/{book_id}/{chapter_id}/"
|
4
|
+
|
5
|
+
[qidian.book_info.book_name]
|
6
|
+
steps = [
|
7
|
+
{ type = "select_one", selector = "em#bookName" },
|
8
|
+
{ type = "text" },
|
9
|
+
{ type = "strip" }
|
10
|
+
]
|
11
|
+
|
12
|
+
[qidian.book_info.author]
|
13
|
+
steps = [
|
14
|
+
{ type = "select_one", selector = "a.writer" },
|
15
|
+
{ type = "text" },
|
16
|
+
{ type = "strip" }
|
17
|
+
]
|
18
|
+
|
19
|
+
[qidian.book_info.cover_url]
|
20
|
+
steps = [
|
21
|
+
{ type = "select_one", selector = "div.book-img img" },
|
22
|
+
{ type = "attr", attr = "src" },
|
23
|
+
{ type = "strip" }
|
24
|
+
]
|
25
|
+
|
26
|
+
[qidian.book_info.update_time]
|
27
|
+
steps = [
|
28
|
+
{ type = "select_one", selector = "span.book-update-time" },
|
29
|
+
{ type = "text" },
|
30
|
+
{ type = "replace", old = "更新时间", new = "" },
|
31
|
+
{ type = "strip" }
|
32
|
+
]
|
33
|
+
|
34
|
+
[qidian.book_info.serial_status]
|
35
|
+
steps = [
|
36
|
+
{ type = "select_one", selector = "span.blue" },
|
37
|
+
{ type = "text" },
|
38
|
+
{ type = "strip" }
|
39
|
+
]
|
40
|
+
|
41
|
+
[qidian.book_info.word_count]
|
42
|
+
steps = [
|
43
|
+
{ type = "regex", pattern = "<em>([\\d.]+)</em>\\s*<cite>(.*?)字</cite>", template = "$1$2字" }
|
44
|
+
]
|
45
|
+
|
46
|
+
[qidian.book_info.summary]
|
47
|
+
steps = [
|
48
|
+
{ type = "select_one", selector = "div.book-intro p" },
|
49
|
+
{ type = "text" },
|
50
|
+
{ type = "strip" }
|
51
|
+
]
|
52
|
+
|
53
|
+
[qidian.book_info.volumes]
|
54
|
+
has_volume = true
|
55
|
+
volume_selector = "div.volume-wrap div.volume"
|
56
|
+
chapter_selector = "li"
|
57
|
+
|
58
|
+
volume_name_steps = [
|
59
|
+
{ type = "select_one", selector = "h3" },
|
60
|
+
{ type = "text" },
|
61
|
+
{ type = "split", sep = "·", index = 0 },
|
62
|
+
{ type = "replace", old = "订阅本卷", new = "" },
|
63
|
+
{ type = "strip" }
|
64
|
+
]
|
65
|
+
|
66
|
+
chapter_steps = [
|
67
|
+
{ key = "title", steps = [
|
68
|
+
{ type = "select_one", selector = "a" },
|
69
|
+
{ type = "text" }
|
70
|
+
] },
|
71
|
+
|
72
|
+
{ key = "url", steps = [
|
73
|
+
{ type = "select_one", selector = "a" },
|
74
|
+
{ type = "attr", attr = "href" }
|
75
|
+
] },
|
76
|
+
|
77
|
+
{ key = "chapterId", steps = [
|
78
|
+
{ type = "select_one", selector = "a" },
|
79
|
+
{ type = "attr", attr = "href" },
|
80
|
+
{ type = "regex", pattern = "/(\\d+)", group = 1 }
|
81
|
+
] }
|
82
|
+
]
|
83
|
+
|
84
|
+
[qidian.chapter.title] # 章节标题
|
85
|
+
steps = [
|
86
|
+
{ type = "select_one", selector = "div#app div.print h1.title" },
|
87
|
+
{ type = "exclude", selector = "span.review" },
|
88
|
+
{ type = "text" },
|
89
|
+
{ type = "strip" }
|
90
|
+
]
|
91
|
+
|
92
|
+
[qidian.chapter.content] # 章节正文
|
93
|
+
steps = [
|
94
|
+
{ type = "select_one", selector = "div#app div#reader-content main" },
|
95
|
+
{ type = "exclude", selector = "span.review" },
|
96
|
+
{ type = "find_all", name = "p" },
|
97
|
+
{ type = "text" },
|
98
|
+
{ type = "strip" },
|
99
|
+
{ type = "join", sep = "\n\n" }
|
100
|
+
]
|
101
|
+
|
102
|
+
[biquge.profile] # 笔趣阁
|
103
|
+
book_info_url = "http://www.b520.cc/{book_id}/"
|
104
|
+
chapter_url = "http://www.b520.cc/{book_id}/{chapter_id}.html"
|
105
|
+
|
106
|
+
[biquge.book_info.book_name]
|
107
|
+
steps = [
|
108
|
+
{ type = "select_one", selector = "div#info > h1" },
|
109
|
+
{ type = "text" },
|
110
|
+
{ type = "strip" }
|
111
|
+
]
|
112
|
+
|
113
|
+
[biquge.book_info.author]
|
114
|
+
steps = [
|
115
|
+
{ type = "select", selector = "div#info > p", index = 0 },
|
116
|
+
{ type = "text" },
|
117
|
+
{ type = "replace", old = "\u00a0", new = "" },
|
118
|
+
{ type = "regex", pattern = "作\\s*者[::]?\\s*(\\S+)", group = 1 },
|
119
|
+
{ type = "strip" }
|
120
|
+
]
|
121
|
+
|
122
|
+
[biquge.book_info.cover_url]
|
123
|
+
steps = [
|
124
|
+
{ type = "select_one", selector = "div#fmimg > img" },
|
125
|
+
{ type = "attr", attr = "src" },
|
126
|
+
{ type = "strip" }
|
127
|
+
]
|
128
|
+
|
129
|
+
[biquge.book_info.update_time]
|
130
|
+
steps = [
|
131
|
+
{ type = "select", selector = "div#info > p", index = 2 },
|
132
|
+
{ type = "text" },
|
133
|
+
{ type = "regex", pattern = "最后更新[::]\\s*(\\S+)", group = 1 },
|
134
|
+
{ type = "strip" }
|
135
|
+
]
|
136
|
+
|
137
|
+
# [biquge.book_info.serial_status] # 没有
|
138
|
+
|
139
|
+
# [biquge.book_info.word_count] # 没有
|
140
|
+
|
141
|
+
[biquge.book_info.summary]
|
142
|
+
steps = [
|
143
|
+
{ type = "select_one", selector = "div#intro" },
|
144
|
+
{ type = "text" },
|
145
|
+
{ type = "strip" }
|
146
|
+
]
|
147
|
+
|
148
|
+
[biquge.book_info.volumes]
|
149
|
+
has_volume = true
|
150
|
+
volume_mode = "mixed"
|
151
|
+
list_selector = "div#list"
|
152
|
+
volume_selector = "dt"
|
153
|
+
chapter_selector = "dd"
|
154
|
+
|
155
|
+
volume_name_steps = [
|
156
|
+
{ type = "select_one", selector = "dt" },
|
157
|
+
{ type = "text" },
|
158
|
+
{ type = "strip" },
|
159
|
+
{ type = "split", sep = "\n", index = 0 },
|
160
|
+
{ type = "strip" }
|
161
|
+
]
|
162
|
+
|
163
|
+
chapter_steps = [
|
164
|
+
{ key = "title", steps = [
|
165
|
+
{ type = "select_one", selector = "a" },
|
166
|
+
{ type = "text" }
|
167
|
+
] },
|
168
|
+
|
169
|
+
{ key = "url", steps = [
|
170
|
+
{ type = "select_one", selector = "a" },
|
171
|
+
{ type = "attr", attr = "href" }
|
172
|
+
] },
|
173
|
+
|
174
|
+
{ key = "chapterId", steps = [
|
175
|
+
{ type = "select_one", selector = "a" },
|
176
|
+
{ type = "attr", attr = "href" },
|
177
|
+
{ type = "replace", old = ".html", new = "" },
|
178
|
+
{ type = "regex", pattern = ".*/(\\d+)$", group = 1 }
|
179
|
+
] }
|
180
|
+
]
|
181
|
+
|
182
|
+
[biquge.chapter.title] # 章节标题
|
183
|
+
steps = [
|
184
|
+
{ type = "select_one", selector = "div.bookname > h1" },
|
185
|
+
{ type = "text" },
|
186
|
+
{ type = "strip" }
|
187
|
+
]
|
188
|
+
|
189
|
+
[biquge.chapter.content] # 章节正文
|
190
|
+
steps = [
|
191
|
+
{ type = "select_one", selector = "div#content" },
|
192
|
+
{ type = "find_all", name = "p" },
|
193
|
+
{ type = "text" },
|
194
|
+
{ type = "strip" },
|
195
|
+
{ type = "join", sep = "\n\n" }
|
196
|
+
]
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# 网络请求层设置
|
2
|
+
requests:
|
3
|
+
wait_time: 5 # 每次请求等待时间 (秒)
|
4
|
+
retry_times: 3 # 请求失败重试次数
|
5
|
+
retry_interval: 5
|
6
|
+
timeout: 30 # 页面加载超时时间 (秒)
|
7
|
+
# DrissionPage 专用设置
|
8
|
+
headless: false # 是否以无头模式启动浏览器
|
9
|
+
user_data_folder: "" # 浏览器用户数据目录: 为空则使用默认目录
|
10
|
+
profile_name: "" # 使用的用户配置名称: 为空则使用默认配置
|
11
|
+
auto_close: true # 页面抓取完是否自动关闭浏览器
|
12
|
+
disable_images: false # 是否禁用图片加载 (加速)
|
13
|
+
mute_audio: true # 是否静音
|
14
|
+
|
15
|
+
# 全局通用设置
|
16
|
+
general:
|
17
|
+
request_interval: 5 # 同一本书各章节请求间隔 (秒)
|
18
|
+
raw_data_dir: "./raw_data" # 原始章节 HTML/JSON 存放目录
|
19
|
+
output_dir: "./downloads" # 最终输出文件存放目录
|
20
|
+
cache_dir: "./novel_cache" # 本地缓存目录 (字体 / 图片等)
|
21
|
+
max_threads: 4 # 最大并发下载线程数 (未实现)
|
22
|
+
skip_existing: true # 是否跳过已存在章节
|
23
|
+
debug:
|
24
|
+
save_html: false # 是否将抓取到的原始 HTML 保留到磁盘
|
25
|
+
log_level: "INFO" # 日志级别: DEBUG, INFO, WARNING, ERROR
|
26
|
+
|
27
|
+
# 各站点的特定配置
|
28
|
+
sites:
|
29
|
+
qidian:
|
30
|
+
# 小说 ID 列表
|
31
|
+
# 例如: 访问 https://www.qidian.com/book/1010868264/
|
32
|
+
# 该小说的 ID 就是 1010868264
|
33
|
+
book_ids:
|
34
|
+
- "0000000000"
|
35
|
+
- "0000000000"
|
36
|
+
mode: "browser" # browser / session
|
37
|
+
login_required: true # 是否需要登录才能访问
|
38
|
+
decode_font: false # 是否尝试本地解码混淆字体
|
39
|
+
use_freq: false # 是否使用频率分析
|
40
|
+
ocr_version: "v2.0" # "v1.0" / "v2.0"
|
41
|
+
use_ocr: true # 是否使用 OCR 辅助识别文本
|
42
|
+
use_vec: false # 是否使用 Vector 辅助识别文本
|
43
|
+
save_font_debug: false # 是否保存字体解码调试数据
|
44
|
+
batch_size: 32
|
45
|
+
ocr_weight: 0.6
|
46
|
+
vec_weight: 0.4
|
47
|
+
#
|
48
|
+
sample_site:
|
49
|
+
book_ids:
|
50
|
+
- "ABCDEF"
|
51
|
+
api_key: "your_api_key"
|
52
|
+
special_flag: 42
|
53
|
+
common:
|
54
|
+
mode: "session" # async / session
|
55
|
+
login_required: false # 是否需要登录才能访问
|
56
|
+
|
57
|
+
# 输出文件格式及相关选项
|
58
|
+
output:
|
59
|
+
clean_text: true # 是否对章节文本做清理
|
60
|
+
formats:
|
61
|
+
make_txt: true # 是否生成完整 TXT 文件
|
62
|
+
make_epub: false # 是否生成 EPUB
|
63
|
+
make_md: false # 是否生成 Markdown (未实现)
|
64
|
+
make_pdf: false # 可能支持 PDF 输出 (未实现)
|
65
|
+
naming:
|
66
|
+
append_timestamp: false # 在文件名中追加时间戳
|
67
|
+
filename_template: "{title}_{author}" # 文件命名规则
|
68
|
+
epub:
|
69
|
+
include_cover: true # 是否在 EPUB 中包含封面
|
70
|
+
include_toc: false # 是否自动生成目录
|
@@ -0,0 +1,104 @@
|
|
1
|
+
html, body {
|
2
|
+
height: 95%;
|
3
|
+
padding: 0.5em;
|
4
|
+
}
|
5
|
+
|
6
|
+
.images img {
|
7
|
+
width:100%; /*自适应*/
|
8
|
+
}
|
9
|
+
.color .images {
|
10
|
+
margin:0 0 1.5em;
|
11
|
+
}
|
12
|
+
.article .images {
|
13
|
+
margin:1.0em 0;
|
14
|
+
border:1px solid #000
|
15
|
+
}
|
16
|
+
|
17
|
+
.new-page-after {
|
18
|
+
page-break-after:always;
|
19
|
+
}
|
20
|
+
|
21
|
+
.cover {
|
22
|
+
width:100%;
|
23
|
+
padding:0px;
|
24
|
+
}
|
25
|
+
.center {
|
26
|
+
text-align: center;
|
27
|
+
margin-left: 0%;
|
28
|
+
margin-right: 0%;
|
29
|
+
}
|
30
|
+
.left {
|
31
|
+
text-align: center;
|
32
|
+
margin-left: 0%;
|
33
|
+
margin-right: 0%;
|
34
|
+
}
|
35
|
+
.right {
|
36
|
+
text-align: right;
|
37
|
+
margin-left: 0%;
|
38
|
+
margin-right: 0%;
|
39
|
+
}
|
40
|
+
|
41
|
+
hr {
|
42
|
+
margin: 0;
|
43
|
+
border-style: dotted;
|
44
|
+
border-width: 1px 0 0 0;
|
45
|
+
margin-top: 0.5em;
|
46
|
+
margin-bottom: 0.5em;
|
47
|
+
}
|
48
|
+
|
49
|
+
h1 {
|
50
|
+
text-indent: 0;
|
51
|
+
duokan-text-indent: 0;
|
52
|
+
line-height: 110%;
|
53
|
+
text-align: center;
|
54
|
+
font-size: 1.2em;
|
55
|
+
margin-top: 0.4em;
|
56
|
+
margin-bottom: 0.4em;
|
57
|
+
}
|
58
|
+
|
59
|
+
h2, h3, h4, h5, h6 {
|
60
|
+
text-indent: 0;
|
61
|
+
duokan-text-indent: 0;
|
62
|
+
line-height: 110%;
|
63
|
+
font-size: 1.1em;
|
64
|
+
margin-top: 0.4em;
|
65
|
+
margin-bottom: 0.4em;
|
66
|
+
}
|
67
|
+
|
68
|
+
p {
|
69
|
+
text-align: justify; /* 让文本两端对齐 */
|
70
|
+
text-indent: 2em;
|
71
|
+
duokan-text-indent: 2em;
|
72
|
+
line-height: 100%;
|
73
|
+
margin-top: 0.4em;
|
74
|
+
margin-bottom: 0.4em;
|
75
|
+
}
|
76
|
+
|
77
|
+
div.list {
|
78
|
+
text-align: left;
|
79
|
+
font-size: 0.85em;
|
80
|
+
line-height: 130%;
|
81
|
+
}
|
82
|
+
|
83
|
+
span {
|
84
|
+
text-indent: 0;
|
85
|
+
duokan-text-indent: 0;
|
86
|
+
text-align: left;
|
87
|
+
font-size: 1em;
|
88
|
+
}
|
89
|
+
|
90
|
+
ol {
|
91
|
+
text-align: left;
|
92
|
+
margin-top: 1.2em;
|
93
|
+
margin-bottom: 1.2em;
|
94
|
+
margin-left: 0;
|
95
|
+
list-style-type: none;
|
96
|
+
}
|
97
|
+
|
98
|
+
li {
|
99
|
+
text-align: left;
|
100
|
+
margin-top: 0.4em;
|
101
|
+
margin-bottom: 0.4em;
|
102
|
+
margin-left: 0;
|
103
|
+
list-style-type: none;
|
104
|
+
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
/*标题页*/
|
2
|
+
|
3
|
+
h1.volume-title-line1 {
|
4
|
+
font-size: 1.25rem; /* 第一行较大 */
|
5
|
+
font-weight: bold;
|
6
|
+
text-align: center;
|
7
|
+
text-indent: 0em;
|
8
|
+
duokan-text-indent: 0em;
|
9
|
+
color: #6e471c;
|
10
|
+
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.8);
|
11
|
+
margin: 0;
|
12
|
+
display: block;
|
13
|
+
}
|
14
|
+
|
15
|
+
p.volume-title-line2 {
|
16
|
+
font-size: 1rem; /* 第二行稍小 */
|
17
|
+
font-weight: bold;
|
18
|
+
text-align: center;
|
19
|
+
text-indent: 0em;
|
20
|
+
duokan-text-indent: 0em;
|
21
|
+
color: #6e471c;
|
22
|
+
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.8);
|
23
|
+
margin: 0;
|
24
|
+
display: block;
|
25
|
+
}
|
26
|
+
|
27
|
+
div.border1 {
|
28
|
+
margin: 35% auto 0 auto;
|
29
|
+
text-align: center;
|
30
|
+
text-indent: 0em;
|
31
|
+
duokan-text-indent: 0em;
|
32
|
+
}
|
33
|
+
|
34
|
+
div.border2 {
|
35
|
+
margin: 0 auto 0 auto;
|
36
|
+
text-align: center;
|
37
|
+
text-indent: 0em;
|
38
|
+
duokan-text-indent: 0em;
|
39
|
+
transform: rotate(180deg);
|
40
|
+
-ms-transform: rotate(180deg);
|
41
|
+
/* IE 9 */
|
42
|
+
-moz-transform: rotate(180deg);
|
43
|
+
/* Firefox */
|
44
|
+
-webkit-transform: rotate(180deg);
|
45
|
+
/* Safari 和 Chrome */
|
46
|
+
-o-transform: rotate(180deg);
|
47
|
+
/* Opera */;
|
48
|
+
}
|
49
|
+
|
50
|
+
img.border1 {
|
51
|
+
width: 100%;
|
52
|
+
}
|
53
|
+
|
54
|
+
img.border2 {
|
55
|
+
width: 80%;
|
56
|
+
}
|
Binary file
|
@@ -0,0 +1,82 @@
|
|
1
|
+
window = global;
|
2
|
+
null_fun = function(){console.log(arguments);}
|
3
|
+
window.outerHeight = 1000
|
4
|
+
window.innerHeight = 100
|
5
|
+
globalThis = window
|
6
|
+
self = window
|
7
|
+
window.location = {}
|
8
|
+
location.protocol = "https:"
|
9
|
+
location.hostname = "vipreader.qidian.com"
|
10
|
+
setTimeout = null_fun
|
11
|
+
setInterval = null_fun
|
12
|
+
document = {createElement: null_fun, documentElement: {}, createEvent: null_fun, currentScript: {src: "https://qdfepccdn.qidian.com/www.qidian.com/fock/116594983210.js"}, domain: 'qidian.com'}
|
13
|
+
navigator = {userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36'}
|
14
|
+
performance = {}
|
15
|
+
performance.navigation = {type: 1}
|
16
|
+
|
17
|
+
// ---- DECRYPT FUNCTION ----
|
18
|
+
const Fock = require('./4819793b.qeooxh.js');
|
19
|
+
|
20
|
+
async function decrypt(enContent, cuChapterId, fkp, fuid) {
|
21
|
+
Fock.initialize();
|
22
|
+
Fock.setupUserKey(fuid);
|
23
|
+
eval(atob(fkp));
|
24
|
+
isFockInit = true;
|
25
|
+
|
26
|
+
return new Promise((resolve, reject) => {
|
27
|
+
Fock.unlock(enContent, cuChapterId, (code, decrypted) => {
|
28
|
+
if (code === 0) {
|
29
|
+
resolve(decrypted);
|
30
|
+
} else {
|
31
|
+
reject(new Error(`Fock.unlock failed, code=${code}`));
|
32
|
+
}
|
33
|
+
});
|
34
|
+
});
|
35
|
+
}
|
36
|
+
|
37
|
+
// ---- MAIN ----
|
38
|
+
const fs = require('fs');
|
39
|
+
|
40
|
+
(async () => {
|
41
|
+
const [inputPath, outputPath] = process.argv.slice(2);
|
42
|
+
if (!inputPath || !outputPath) {
|
43
|
+
console.error(
|
44
|
+
"Usage: node script.js <input.json> <output.txt>"
|
45
|
+
);
|
46
|
+
process.exit(1);
|
47
|
+
}
|
48
|
+
|
49
|
+
try {
|
50
|
+
const inputData = fs.readFileSync(inputPath, "utf-8");
|
51
|
+
const [
|
52
|
+
raw_enContent,
|
53
|
+
raw_cuChapterId,
|
54
|
+
raw_fkp,
|
55
|
+
raw_fuid
|
56
|
+
] = JSON.parse(inputData);
|
57
|
+
|
58
|
+
const decryptPromise = decrypt(
|
59
|
+
String(raw_enContent),
|
60
|
+
String(raw_cuChapterId),
|
61
|
+
String(raw_fkp),
|
62
|
+
String(raw_fuid)
|
63
|
+
);
|
64
|
+
|
65
|
+
const timeoutMs = 5000;
|
66
|
+
const timerPromise = new Promise((_, reject) => {
|
67
|
+
setTimeout(() => {
|
68
|
+
reject(new Error(`decrypt timeout after ${timeoutMs}ms`));
|
69
|
+
}, timeoutMs);
|
70
|
+
});
|
71
|
+
|
72
|
+
const result = await Promise.race([
|
73
|
+
decryptPromise,
|
74
|
+
timerPromise
|
75
|
+
]);
|
76
|
+
|
77
|
+
fs.writeFileSync(outputPath, result, "utf-8");
|
78
|
+
} catch (err) {
|
79
|
+
console.error("Failed to decrypt:", err);
|
80
|
+
process.exit(1);
|
81
|
+
}
|
82
|
+
})();
|
File without changes
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
"""
|
4
|
+
novel_downloader.utils.cache
|
5
|
+
----------------------------
|
6
|
+
|
7
|
+
Provides decorators for caching function results,
|
8
|
+
specifically optimized for configuration loading functions.
|
9
|
+
"""
|
10
|
+
|
11
|
+
from functools import lru_cache, wraps
|
12
|
+
from typing import Any, Callable, TypeVar, cast
|
13
|
+
|
14
|
+
T = TypeVar("T", bound=Callable[..., Any])
|
15
|
+
|
16
|
+
|
17
|
+
def cached_load_config(func: T) -> T:
|
18
|
+
"""
|
19
|
+
A decorator to cache the result of a config-loading function.
|
20
|
+
Uses LRU cache with maxsize=1.
|
21
|
+
"""
|
22
|
+
cached = lru_cache(maxsize=1)(func)
|
23
|
+
wrapped = wraps(func)(cached)
|
24
|
+
return cast(T, wrapped)
|