@wahaha216/koishi-plugin-jmcomic 0.0.1
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.
- package/LICENSE.txt +21 -0
- package/lib/abstract/JMAlbumAbstract.d.ts +70 -0
- package/lib/abstract/JMClientAbstract.d.ts +87 -0
- package/lib/abstract/JMPhotoAbstract.d.ts +20 -0
- package/lib/entity/Album.d.ts +80 -0
- package/lib/entity/JMAppAlbum.d.ts +38 -0
- package/lib/entity/JMAppClient.d.ts +54 -0
- package/lib/entity/JMAppPhoto.d.ts +32 -0
- package/lib/entity/JMHtmlAlbum.d.ts +43 -0
- package/lib/entity/JMHtmlClient.d.ts +17 -0
- package/lib/entity/JMHtmlPhoto.d.ts +22 -0
- package/lib/entity/JMUser.d.ts +30 -0
- package/lib/entity/Photo.d.ts +32 -0
- package/lib/error/emptybuffer.error.d.ts +3 -0
- package/lib/error/mysql.error.d.ts +3 -0
- package/lib/error/overRetry.error.d.ts +3 -0
- package/lib/index.d.ts +25 -0
- package/lib/index.js +1274 -0
- package/lib/types/database.d.ts +27 -0
- package/lib/utils/Const.d.ts +3 -0
- package/lib/utils/Image.d.ts +11 -0
- package/lib/utils/Regexp.d.ts +22 -0
- package/lib/utils/Utils.d.ts +67 -0
- package/package.json +57 -0
- package/readme.md +22 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,1274 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name2 in all)
|
|
13
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// src/locales/zh-CN.yml
|
|
34
|
+
var require_zh_CN = __commonJS({
|
|
35
|
+
"src/locales/zh-CN.yml"(exports2, module2) {
|
|
36
|
+
module2.exports = { commands: { jm: { description: "下载JM漫画,无需python!", examples: "jm 114514", messages: { empty: "JMID 不能为空", formatError: "JMID 格式错误", notFound: "找不到该车牌", pleaseWait: "正在获取,请等待亿会" } } }, _config: [{ $desc: "基础设置", url: "JM域名", sendMethod: "发送方式", retryCount: "重试次数限制", password: "密码,留空则不加密", fileName: "文件名定义<br>`{{name}}`:标题<br>`{{id}}`:章节或者本子ID<br>`{{index}}`:多章节本子自动填充`1` 、 `2`" }, { level: "压缩级别,0~9,0为仅存储" }, { $desc: "缓存设置", cache: "缓存文件" }, { autoDelete: "自动删除缓存,**依赖cron服务**" }, { cron: "5位cron表达式", deleteInStart: "启动时检测", keepDays: "缓存保留时间(天)" }, { $desc: "开发者选项", debug: "调试模式,输出更多信息" }] };
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// src/locales/en-US.yml
|
|
41
|
+
var require_en_US = __commonJS({
|
|
42
|
+
"src/locales/en-US.yml"(exports2, module2) {
|
|
43
|
+
module2.exports = { commands: { jm: { description: "Download JM comics without python!", examples: "jm 114514", messages: { empty: "JM ID cannot be empty", formatError: "JM ID format error", notFound: "The JM ID cannot be found", pleaseWait: "Getting it, please wait" } } }, _config: [{ $desc: "Basic settings", url: "JM domain name", sendMethod: "Send method", retryCount: "Retry limit", password: "Password, leave blank without encryption", fileName: "File name definition<br>`{{name}}`: Title<br>`{{id}}`: Chapter or Book ID<br>`{{index}}`: Multi-chapter book auto-filling `_1` `_2`" }, { level: "Compression level, 0~9, 0 is only stores" }, { $desc: "Cache settings", cache: "Cache files" }, { autoDelete: "Automatically delete cache, **need cron services**" }, { cron: "5-bit cron expression", deleteInStart: "Detection on startup", keepDays: "Cache retention time (days)" }, { $desc: "Developer Options", debug: "Debug mode, output more information" }] };
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// src/index.ts
|
|
48
|
+
var src_exports = {};
|
|
49
|
+
__export(src_exports, {
|
|
50
|
+
Config: () => Config,
|
|
51
|
+
apply: () => apply,
|
|
52
|
+
debug: () => debug,
|
|
53
|
+
http: () => http,
|
|
54
|
+
inject: () => inject,
|
|
55
|
+
logger: () => logger,
|
|
56
|
+
name: () => name,
|
|
57
|
+
retryCount: () => retryCount
|
|
58
|
+
});
|
|
59
|
+
module.exports = __toCommonJS(src_exports);
|
|
60
|
+
var import_koishi = require("koishi");
|
|
61
|
+
var import_path3 = require("path");
|
|
62
|
+
var import_promises3 = require("fs/promises");
|
|
63
|
+
|
|
64
|
+
// src/utils/Utils.ts
|
|
65
|
+
var import_fs = require("fs");
|
|
66
|
+
var import_promises = require("fs/promises");
|
|
67
|
+
|
|
68
|
+
// src/utils/Regexp.ts
|
|
69
|
+
var JM_SCRAMBLE_ID = /var scramble_id = (\d+);/;
|
|
70
|
+
|
|
71
|
+
// src/utils/Const.ts
|
|
72
|
+
var JM_CLIENT_URL_LIST = [
|
|
73
|
+
"www.cdnmhwscc.vip",
|
|
74
|
+
"www.cdnblackmyth.club",
|
|
75
|
+
"www.cdnmhws.cc",
|
|
76
|
+
"www.cdnuc.vip"
|
|
77
|
+
];
|
|
78
|
+
var JM_IMAGE_URL_LIST = [
|
|
79
|
+
"cdn-msp.jmapiproxy1.cc",
|
|
80
|
+
"cdn-msp.jmapiproxy2.cc",
|
|
81
|
+
"cdn-msp2.jmapiproxy2.cc",
|
|
82
|
+
"cdn-msp3.jmapiproxy2.cc",
|
|
83
|
+
"cdn-msp.jmapinodeudzn.net",
|
|
84
|
+
"cdn-msp3.jmapinodeudzn.net"
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
// src/utils/Utils.ts
|
|
88
|
+
var import_path = require("path");
|
|
89
|
+
|
|
90
|
+
// src/error/mysql.error.ts
|
|
91
|
+
var MySqlError = class extends Error {
|
|
92
|
+
static {
|
|
93
|
+
__name(this, "MySqlError");
|
|
94
|
+
}
|
|
95
|
+
constructor(message) {
|
|
96
|
+
super(message);
|
|
97
|
+
this.name = "MySqlError";
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// src/error/overRetry.error.ts
|
|
102
|
+
var OverRetryError = class extends Error {
|
|
103
|
+
static {
|
|
104
|
+
__name(this, "OverRetryError");
|
|
105
|
+
}
|
|
106
|
+
constructor(message) {
|
|
107
|
+
super(message);
|
|
108
|
+
this.name = "OverRetryError";
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// src/error/emptybuffer.error.ts
|
|
113
|
+
var EmptyBufferError = class extends Error {
|
|
114
|
+
static {
|
|
115
|
+
__name(this, "EmptyBufferError");
|
|
116
|
+
}
|
|
117
|
+
constructor(message) {
|
|
118
|
+
super(message);
|
|
119
|
+
this.name = "EmptyBufferError";
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// src/utils/Utils.ts
|
|
124
|
+
function fileExistsAsync(path) {
|
|
125
|
+
try {
|
|
126
|
+
(0, import_fs.accessSync)(path, import_fs.constants.F_OK);
|
|
127
|
+
return true;
|
|
128
|
+
} catch (err) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
__name(fileExistsAsync, "fileExistsAsync");
|
|
133
|
+
function fileSizeAsync(path) {
|
|
134
|
+
try {
|
|
135
|
+
const stats = (0, import_fs.statSync)(path);
|
|
136
|
+
return stats.size;
|
|
137
|
+
} catch (err) {
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
__name(fileSizeAsync, "fileSizeAsync");
|
|
142
|
+
function sanitizeFileName(fileName) {
|
|
143
|
+
const forbiddenChars = /[<>:"/\\|?*]/g;
|
|
144
|
+
fileName = fileName.replace(forbiddenChars, "_");
|
|
145
|
+
fileName = fileName.replace(/\s+/g, "_");
|
|
146
|
+
fileName = fileName.replace(/[. ]+$/, "");
|
|
147
|
+
return fileName;
|
|
148
|
+
}
|
|
149
|
+
__name(sanitizeFileName, "sanitizeFileName");
|
|
150
|
+
async function limitPromiseAll(promises, limit) {
|
|
151
|
+
const results = new Array(promises.length);
|
|
152
|
+
const executing = [];
|
|
153
|
+
for (let i = 0; i < promises.length; i++) {
|
|
154
|
+
const promiseFn = promises[i];
|
|
155
|
+
while (executing.length >= limit) {
|
|
156
|
+
await Promise.race(executing);
|
|
157
|
+
}
|
|
158
|
+
const p = promiseFn().then((res) => {
|
|
159
|
+
results[i] = res;
|
|
160
|
+
}).finally(() => {
|
|
161
|
+
const index = executing.indexOf(p);
|
|
162
|
+
if (index !== -1) {
|
|
163
|
+
executing.splice(index, 1);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
executing.push(p);
|
|
167
|
+
}
|
|
168
|
+
await Promise.all(executing);
|
|
169
|
+
return results;
|
|
170
|
+
}
|
|
171
|
+
__name(limitPromiseAll, "limitPromiseAll");
|
|
172
|
+
async function requestWithRetry(url, method, config = {}, retryIndex = 0) {
|
|
173
|
+
try {
|
|
174
|
+
const res = await http(method, url, config);
|
|
175
|
+
if (typeof res.data === "string" && res.data.includes("Could not connect to mysql")) {
|
|
176
|
+
throw new MySqlError();
|
|
177
|
+
}
|
|
178
|
+
return res.data;
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (error instanceof MySqlError) {
|
|
181
|
+
throw new MySqlError();
|
|
182
|
+
} else if (retryIndex < retryCount) {
|
|
183
|
+
logger.info(
|
|
184
|
+
`${url} 请求失败,正在重试... ${retryIndex + 1}/${retryCount}`
|
|
185
|
+
);
|
|
186
|
+
return await requestWithRetry(url, method, config, retryIndex + 1);
|
|
187
|
+
} else {
|
|
188
|
+
throw new OverRetryError(`请求失败,超过最大重试次数: ${url}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
__name(requestWithRetry, "requestWithRetry");
|
|
193
|
+
async function requestWithUrlSwitch(url, method, config = {}, type = "CLIENT", urlIndex = 0) {
|
|
194
|
+
const list = type === "CLIENT" ? JM_CLIENT_URL_LIST : JM_IMAGE_URL_LIST;
|
|
195
|
+
const urlCount = list.length;
|
|
196
|
+
const url_bak = url;
|
|
197
|
+
if (url.startsWith("/")) {
|
|
198
|
+
url = `https://${list[urlIndex]}${url}`;
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
if (urlIndex < urlCount) {
|
|
202
|
+
const res = await requestWithRetry(url, method, config);
|
|
203
|
+
if (res instanceof ArrayBuffer && res.byteLength === 0) {
|
|
204
|
+
throw new EmptyBufferError();
|
|
205
|
+
}
|
|
206
|
+
return res;
|
|
207
|
+
} else {
|
|
208
|
+
throw new Error("所有域名请求失败");
|
|
209
|
+
}
|
|
210
|
+
} catch (error) {
|
|
211
|
+
const isMysqlError = error instanceof MySqlError;
|
|
212
|
+
const isEmptyBuffer = error instanceof EmptyBufferError;
|
|
213
|
+
if (isMysqlError || isEmptyBuffer) {
|
|
214
|
+
logger.info(`请求失败,尝试切换域名... ${urlIndex + 1}/${urlCount}`);
|
|
215
|
+
return await requestWithUrlSwitch(
|
|
216
|
+
url_bak,
|
|
217
|
+
method,
|
|
218
|
+
config,
|
|
219
|
+
type,
|
|
220
|
+
urlIndex + 1
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
throw new Error(error);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
__name(requestWithUrlSwitch, "requestWithUrlSwitch");
|
|
227
|
+
function getFileInfo(filePath) {
|
|
228
|
+
const normalizedPath = (0, import_path.normalize)(filePath);
|
|
229
|
+
const parsePath = (0, import_path.parse)(normalizedPath);
|
|
230
|
+
const ext = parsePath.ext.slice(1);
|
|
231
|
+
const fileName = parsePath.name;
|
|
232
|
+
const dir = parsePath.dir;
|
|
233
|
+
return { fileName, ext, dir };
|
|
234
|
+
}
|
|
235
|
+
__name(getFileInfo, "getFileInfo");
|
|
236
|
+
async function deleteFewDaysAgoFolders(path, days) {
|
|
237
|
+
const dirEntries = await (0, import_promises.readdir)(path, {
|
|
238
|
+
withFileTypes: true
|
|
239
|
+
});
|
|
240
|
+
const subfolderNames = dirEntries.filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
|
|
241
|
+
for (const folder of subfolderNames) {
|
|
242
|
+
const s = await (0, import_promises.stat)(`${path}/${folder}`);
|
|
243
|
+
const creationTime = s.birthtime || s.ctime;
|
|
244
|
+
const now = /* @__PURE__ */ new Date();
|
|
245
|
+
const diffTime = Math.abs(now.getTime() - creationTime.getTime());
|
|
246
|
+
const diffDays = Math.floor(diffTime / (1e3 * 3600 * 24));
|
|
247
|
+
if (diffDays >= days) {
|
|
248
|
+
(0, import_promises.rm)(`${path}/${folder}`, { recursive: true });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
__name(deleteFewDaysAgoFolders, "deleteFewDaysAgoFolders");
|
|
253
|
+
function formatFileName(originName, name2, id, index) {
|
|
254
|
+
return originName.replaceAll("{{name}}", name2).replaceAll("{{id}}", id).replaceAll("{{index}}", index ? `${index}` : "");
|
|
255
|
+
}
|
|
256
|
+
__name(formatFileName, "formatFileName");
|
|
257
|
+
|
|
258
|
+
// src/entity/JMAppClient.ts
|
|
259
|
+
var import_crypto3 = require("crypto");
|
|
260
|
+
var import_form_data = __toESM(require("form-data"));
|
|
261
|
+
|
|
262
|
+
// src/abstract/JMClientAbstract.ts
|
|
263
|
+
var import_crypto = __toESM(require("crypto"));
|
|
264
|
+
var JMClientAbstract = class {
|
|
265
|
+
static {
|
|
266
|
+
__name(this, "JMClientAbstract");
|
|
267
|
+
}
|
|
268
|
+
root;
|
|
269
|
+
constructor(root) {
|
|
270
|
+
this.root = root;
|
|
271
|
+
}
|
|
272
|
+
setRoot(root) {
|
|
273
|
+
this.root = root;
|
|
274
|
+
}
|
|
275
|
+
getRoot() {
|
|
276
|
+
return this.root;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* 使用MD5将字符串加密成十六进制
|
|
280
|
+
* @param key 要计算MD5的字符串
|
|
281
|
+
* @returns 十六进制MD5
|
|
282
|
+
*/
|
|
283
|
+
md5Hex(key, inputEncoding = "utf-8") {
|
|
284
|
+
return import_crypto.default.createHash("md5").update(key, inputEncoding).digest("hex");
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
// src/abstract/JMPhotoAbstract.ts
|
|
289
|
+
var import_crypto2 = __toESM(require("crypto"));
|
|
290
|
+
var JMPhotoAbstract = class _JMPhotoAbstract {
|
|
291
|
+
static {
|
|
292
|
+
__name(this, "JMPhotoAbstract");
|
|
293
|
+
}
|
|
294
|
+
static SCRAMBLE_268850 = 268850;
|
|
295
|
+
static SCRAMBLE_421926 = 421926;
|
|
296
|
+
id;
|
|
297
|
+
images;
|
|
298
|
+
image_names;
|
|
299
|
+
splitNumbers;
|
|
300
|
+
setId(id) {
|
|
301
|
+
this.id = id;
|
|
302
|
+
}
|
|
303
|
+
getId() {
|
|
304
|
+
return this.id;
|
|
305
|
+
}
|
|
306
|
+
setImages(images) {
|
|
307
|
+
this.images = images;
|
|
308
|
+
}
|
|
309
|
+
getImages() {
|
|
310
|
+
return this.images;
|
|
311
|
+
}
|
|
312
|
+
setImageNames(imageNames) {
|
|
313
|
+
this.image_names = imageNames;
|
|
314
|
+
}
|
|
315
|
+
getImageNames() {
|
|
316
|
+
return this.image_names;
|
|
317
|
+
}
|
|
318
|
+
setSplitNumbers(splitNumbers) {
|
|
319
|
+
this.splitNumbers = splitNumbers;
|
|
320
|
+
}
|
|
321
|
+
getSplitNumbers() {
|
|
322
|
+
return this.splitNumbers;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* 生成图片分割数
|
|
326
|
+
*/
|
|
327
|
+
generateSplitNumbers(scramble_id) {
|
|
328
|
+
const splitNumbers = this.image_names.map((name2) => {
|
|
329
|
+
if (this.id < scramble_id) {
|
|
330
|
+
return 0;
|
|
331
|
+
} else if (this.id < _JMPhotoAbstract.SCRAMBLE_268850) {
|
|
332
|
+
return 10;
|
|
333
|
+
} else {
|
|
334
|
+
const x = this.id < _JMPhotoAbstract.SCRAMBLE_421926 ? 10 : 8;
|
|
335
|
+
const s = `${this.id}${name2}`;
|
|
336
|
+
const md5 = import_crypto2.default.createHash("md5");
|
|
337
|
+
const hash = md5.update(s).digest("hex");
|
|
338
|
+
const lastChar = hash[hash.length - 1];
|
|
339
|
+
let num = lastChar.charCodeAt(0) % x;
|
|
340
|
+
num = num * 2 + 2;
|
|
341
|
+
return num;
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
this.splitNumbers = splitNumbers;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// src/entity/JMAppPhoto.ts
|
|
349
|
+
var JMAppPhoto = class _JMAppPhoto extends JMPhotoAbstract {
|
|
350
|
+
static {
|
|
351
|
+
__name(this, "JMAppPhoto");
|
|
352
|
+
}
|
|
353
|
+
series;
|
|
354
|
+
tags;
|
|
355
|
+
name;
|
|
356
|
+
addtime;
|
|
357
|
+
series_id;
|
|
358
|
+
is_favorite;
|
|
359
|
+
liked;
|
|
360
|
+
constructor(json) {
|
|
361
|
+
super();
|
|
362
|
+
for (const key in json) {
|
|
363
|
+
if (Object.prototype.hasOwnProperty.call(this, key)) {
|
|
364
|
+
this[key] = json[key];
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
setSeries(series) {
|
|
369
|
+
this.series = series;
|
|
370
|
+
}
|
|
371
|
+
getSeries() {
|
|
372
|
+
return this.series;
|
|
373
|
+
}
|
|
374
|
+
setTags(tags) {
|
|
375
|
+
this.tags = tags;
|
|
376
|
+
}
|
|
377
|
+
getTags() {
|
|
378
|
+
return this.tags;
|
|
379
|
+
}
|
|
380
|
+
setName(name2) {
|
|
381
|
+
this.name = name2;
|
|
382
|
+
}
|
|
383
|
+
getName() {
|
|
384
|
+
return this.name;
|
|
385
|
+
}
|
|
386
|
+
setAddtime(addtime) {
|
|
387
|
+
this.addtime = addtime;
|
|
388
|
+
}
|
|
389
|
+
getAddtime() {
|
|
390
|
+
return this.addtime;
|
|
391
|
+
}
|
|
392
|
+
setSeriesId(seriesId) {
|
|
393
|
+
this.series_id = seriesId;
|
|
394
|
+
}
|
|
395
|
+
getSeriesId() {
|
|
396
|
+
return this.series_id;
|
|
397
|
+
}
|
|
398
|
+
setIsFavorite(isFavorite) {
|
|
399
|
+
this.is_favorite = isFavorite;
|
|
400
|
+
}
|
|
401
|
+
getIsFavorite() {
|
|
402
|
+
return this.is_favorite;
|
|
403
|
+
}
|
|
404
|
+
setLiked(liked) {
|
|
405
|
+
this.liked = liked;
|
|
406
|
+
}
|
|
407
|
+
getLiked() {
|
|
408
|
+
return this.liked;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* 从JSON数据返回JMPhoto实体类
|
|
412
|
+
* @param json JMPhoto JSON数据
|
|
413
|
+
* @returns
|
|
414
|
+
*/
|
|
415
|
+
static fromJson(json) {
|
|
416
|
+
return new _JMAppPhoto(json);
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// src/abstract/JMAlbumAbstract.ts
|
|
421
|
+
var JMAlbumAbstract = class {
|
|
422
|
+
static {
|
|
423
|
+
__name(this, "JMAlbumAbstract");
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* 本子ID
|
|
427
|
+
*/
|
|
428
|
+
id;
|
|
429
|
+
/**
|
|
430
|
+
* 名称
|
|
431
|
+
*/
|
|
432
|
+
name;
|
|
433
|
+
/**
|
|
434
|
+
* 章节列表
|
|
435
|
+
*/
|
|
436
|
+
series = [];
|
|
437
|
+
/**
|
|
438
|
+
* 作品
|
|
439
|
+
*/
|
|
440
|
+
works;
|
|
441
|
+
/**
|
|
442
|
+
* 登场人物
|
|
443
|
+
*/
|
|
444
|
+
actors;
|
|
445
|
+
/**
|
|
446
|
+
* 标签
|
|
447
|
+
*/
|
|
448
|
+
tags;
|
|
449
|
+
/**
|
|
450
|
+
* 作者
|
|
451
|
+
*/
|
|
452
|
+
authors;
|
|
453
|
+
/**
|
|
454
|
+
* 描述
|
|
455
|
+
*/
|
|
456
|
+
description;
|
|
457
|
+
/**
|
|
458
|
+
* 点赞数
|
|
459
|
+
*/
|
|
460
|
+
likes;
|
|
461
|
+
/**
|
|
462
|
+
* 观看次数
|
|
463
|
+
*/
|
|
464
|
+
total_views;
|
|
465
|
+
/**
|
|
466
|
+
* 章节信息
|
|
467
|
+
*/
|
|
468
|
+
photos;
|
|
469
|
+
setId(id) {
|
|
470
|
+
this.id = id;
|
|
471
|
+
}
|
|
472
|
+
getId() {
|
|
473
|
+
return this.id;
|
|
474
|
+
}
|
|
475
|
+
setName(name2) {
|
|
476
|
+
this.name = name2;
|
|
477
|
+
}
|
|
478
|
+
getName() {
|
|
479
|
+
return this.name;
|
|
480
|
+
}
|
|
481
|
+
setSeries(series) {
|
|
482
|
+
this.series = series;
|
|
483
|
+
}
|
|
484
|
+
getSeries() {
|
|
485
|
+
return this.series;
|
|
486
|
+
}
|
|
487
|
+
setWorks(works) {
|
|
488
|
+
this.works = works;
|
|
489
|
+
}
|
|
490
|
+
getWorks() {
|
|
491
|
+
return this.works;
|
|
492
|
+
}
|
|
493
|
+
setActors(actors) {
|
|
494
|
+
this.actors = actors;
|
|
495
|
+
}
|
|
496
|
+
getActors() {
|
|
497
|
+
return this.actors;
|
|
498
|
+
}
|
|
499
|
+
setTags(tags) {
|
|
500
|
+
this.tags = tags;
|
|
501
|
+
}
|
|
502
|
+
getTags() {
|
|
503
|
+
return this.tags;
|
|
504
|
+
}
|
|
505
|
+
setAuthors(authors) {
|
|
506
|
+
this.authors = authors;
|
|
507
|
+
}
|
|
508
|
+
getAuthors() {
|
|
509
|
+
return this.authors;
|
|
510
|
+
}
|
|
511
|
+
setDescription(description) {
|
|
512
|
+
this.description = description;
|
|
513
|
+
}
|
|
514
|
+
getDescription() {
|
|
515
|
+
return this.description;
|
|
516
|
+
}
|
|
517
|
+
setLikes(likes) {
|
|
518
|
+
this.likes = likes;
|
|
519
|
+
}
|
|
520
|
+
getLikes() {
|
|
521
|
+
return this.likes;
|
|
522
|
+
}
|
|
523
|
+
setTotalViews(totalViews) {
|
|
524
|
+
this.total_views = totalViews;
|
|
525
|
+
}
|
|
526
|
+
getTotalViews() {
|
|
527
|
+
return this.total_views;
|
|
528
|
+
}
|
|
529
|
+
setPhotos(photos) {
|
|
530
|
+
this.photos = photos;
|
|
531
|
+
}
|
|
532
|
+
getPhotos() {
|
|
533
|
+
return this.photos;
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
// src/entity/JMAppAlbum.ts
|
|
538
|
+
var JMAppAlbum = class _JMAppAlbum extends JMAlbumAbstract {
|
|
539
|
+
static {
|
|
540
|
+
__name(this, "JMAppAlbum");
|
|
541
|
+
}
|
|
542
|
+
images;
|
|
543
|
+
addtime;
|
|
544
|
+
series_id;
|
|
545
|
+
comment_total;
|
|
546
|
+
related_list;
|
|
547
|
+
liked;
|
|
548
|
+
is_favorite;
|
|
549
|
+
is_aids;
|
|
550
|
+
price;
|
|
551
|
+
purchased;
|
|
552
|
+
constructor(json) {
|
|
553
|
+
super();
|
|
554
|
+
for (const key in json) {
|
|
555
|
+
if (Object.prototype.hasOwnProperty.call(this, key)) {
|
|
556
|
+
this[key] = json[key];
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
setImages(images) {
|
|
561
|
+
this.images = images;
|
|
562
|
+
}
|
|
563
|
+
getImages() {
|
|
564
|
+
return this.images;
|
|
565
|
+
}
|
|
566
|
+
setAddtime(addtime) {
|
|
567
|
+
this.addtime = addtime;
|
|
568
|
+
}
|
|
569
|
+
getAddtime() {
|
|
570
|
+
return this.addtime;
|
|
571
|
+
}
|
|
572
|
+
setSeriesId(seriesId) {
|
|
573
|
+
this.series_id = seriesId;
|
|
574
|
+
}
|
|
575
|
+
getSeriesId() {
|
|
576
|
+
return this.series_id;
|
|
577
|
+
}
|
|
578
|
+
setCommentTotal(commentTotal) {
|
|
579
|
+
this.comment_total = commentTotal;
|
|
580
|
+
}
|
|
581
|
+
getCommentTotal() {
|
|
582
|
+
return this.comment_total;
|
|
583
|
+
}
|
|
584
|
+
setRelatedList(relatedList) {
|
|
585
|
+
this.related_list = relatedList;
|
|
586
|
+
}
|
|
587
|
+
getRelatedList() {
|
|
588
|
+
return this.related_list;
|
|
589
|
+
}
|
|
590
|
+
setLiked(liked) {
|
|
591
|
+
this.liked = liked;
|
|
592
|
+
}
|
|
593
|
+
getLiked() {
|
|
594
|
+
return this.liked;
|
|
595
|
+
}
|
|
596
|
+
setIsFavorite(isFavorite) {
|
|
597
|
+
this.is_favorite = isFavorite;
|
|
598
|
+
}
|
|
599
|
+
getIsFavorite() {
|
|
600
|
+
return this.is_favorite;
|
|
601
|
+
}
|
|
602
|
+
setIsAids(isAids) {
|
|
603
|
+
this.is_aids = isAids;
|
|
604
|
+
}
|
|
605
|
+
getIsAids() {
|
|
606
|
+
return this.is_aids;
|
|
607
|
+
}
|
|
608
|
+
setPrice(price) {
|
|
609
|
+
this.price = price;
|
|
610
|
+
}
|
|
611
|
+
getPrice() {
|
|
612
|
+
return this.price;
|
|
613
|
+
}
|
|
614
|
+
setPurchased(purchased) {
|
|
615
|
+
this.purchased = purchased;
|
|
616
|
+
}
|
|
617
|
+
getPurchased() {
|
|
618
|
+
return this.purchased;
|
|
619
|
+
}
|
|
620
|
+
getPhotos() {
|
|
621
|
+
return super.getPhotos();
|
|
622
|
+
}
|
|
623
|
+
static fromJson(json) {
|
|
624
|
+
return new _JMAppAlbum(json);
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
// src/entity/JMAppClient.ts
|
|
629
|
+
var import_promises2 = require("fs/promises");
|
|
630
|
+
|
|
631
|
+
// src/utils/Image.ts
|
|
632
|
+
var import_fs2 = __toESM(require("fs"));
|
|
633
|
+
var import_sharp = __toESM(require("sharp"));
|
|
634
|
+
var import_archiver = __toESM(require("archiver"));
|
|
635
|
+
var import_archiver_zip_encrypted = __toESM(require("archiver-zip-encrypted"));
|
|
636
|
+
async function decodeImage(imageBuffer, num, path) {
|
|
637
|
+
if (num > 0) {
|
|
638
|
+
const metadata = await (0, import_sharp.default)(Buffer.from(imageBuffer)).metadata();
|
|
639
|
+
const height = metadata.height || 0;
|
|
640
|
+
const width = metadata.width || 0;
|
|
641
|
+
const over = height % num;
|
|
642
|
+
const move = Math.floor(height / num);
|
|
643
|
+
let decodedImageInstance = (0, import_sharp.default)({
|
|
644
|
+
create: {
|
|
645
|
+
width,
|
|
646
|
+
height,
|
|
647
|
+
channels: 3,
|
|
648
|
+
background: { r: 0, g: 0, b: 0 }
|
|
649
|
+
}
|
|
650
|
+
}).webp();
|
|
651
|
+
const croppeds = [];
|
|
652
|
+
for (let i = 0; i < num; i++) {
|
|
653
|
+
let currentMove = move;
|
|
654
|
+
let ySrc = height - move * (i + 1) - over;
|
|
655
|
+
let yDst = move * i;
|
|
656
|
+
if (i === 0) {
|
|
657
|
+
currentMove += over;
|
|
658
|
+
} else {
|
|
659
|
+
yDst += over;
|
|
660
|
+
}
|
|
661
|
+
const cropped = await (0, import_sharp.default)(imageBuffer).extract({ left: 0, top: ySrc, width, height: currentMove }).toBuffer();
|
|
662
|
+
croppeds.push({ top: yDst, cropped });
|
|
663
|
+
}
|
|
664
|
+
decodedImageInstance = decodedImageInstance.composite(
|
|
665
|
+
croppeds.map((c) => ({ input: c.cropped, top: c.top, left: 0 }))
|
|
666
|
+
);
|
|
667
|
+
await decodedImageInstance.toFile(path);
|
|
668
|
+
} else {
|
|
669
|
+
await (0, import_sharp.default)(Buffer.from(imageBuffer)).toFile(path);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
__name(decodeImage, "decodeImage");
|
|
673
|
+
async function saveImage(imageBuffer, path) {
|
|
674
|
+
if (!imageBuffer.byteLength) return;
|
|
675
|
+
const buffer = Buffer.from(imageBuffer);
|
|
676
|
+
await (0, import_sharp.default)(buffer).toFile(path);
|
|
677
|
+
}
|
|
678
|
+
__name(saveImage, "saveImage");
|
|
679
|
+
async function archiverImage(directorys, outputPath, password, level = 9) {
|
|
680
|
+
if (!import_archiver.default.isRegisteredFormat("zip-encrypted")) {
|
|
681
|
+
import_archiver.default.registerFormat("zip-encrypted", import_archiver_zip_encrypted.default);
|
|
682
|
+
}
|
|
683
|
+
const output = import_fs2.default.createWriteStream(outputPath);
|
|
684
|
+
const options = {
|
|
685
|
+
zlib: { level }
|
|
686
|
+
// 压缩级别
|
|
687
|
+
};
|
|
688
|
+
if (password) {
|
|
689
|
+
options["encryptionMethod"] = "aes256";
|
|
690
|
+
options["password"] = password;
|
|
691
|
+
}
|
|
692
|
+
const archive = import_archiver.default.create(password ? "zip-encrypted" : "zip", options);
|
|
693
|
+
archive.pipe(output);
|
|
694
|
+
directorys.forEach(({ directory, destpath }) => {
|
|
695
|
+
archive.directory(directory, destpath);
|
|
696
|
+
});
|
|
697
|
+
await archive.finalize();
|
|
698
|
+
}
|
|
699
|
+
__name(archiverImage, "archiverImage");
|
|
700
|
+
|
|
701
|
+
// src/entity/JMAppClient.ts
|
|
702
|
+
var import_path2 = require("path");
|
|
703
|
+
var import_sharp2 = __toESM(require("sharp"));
|
|
704
|
+
var import_muhammara = require("muhammara");
|
|
705
|
+
var JMAppClient = class _JMAppClient extends JMClientAbstract {
|
|
706
|
+
static {
|
|
707
|
+
__name(this, "JMAppClient");
|
|
708
|
+
}
|
|
709
|
+
static APP_VERSION = "1.7.9";
|
|
710
|
+
static APP_TOKEN_SECRET = "18comicAPP";
|
|
711
|
+
static APP_TOKEN_SECRET_2 = "18comicAPPContent";
|
|
712
|
+
static APP_DATA_SECRET = "185Hcomic3PAPP7R";
|
|
713
|
+
constructor(root) {
|
|
714
|
+
super(root);
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* 登录,未完成
|
|
718
|
+
* @param username 用户名
|
|
719
|
+
* @param password 密码
|
|
720
|
+
* @returns 用户信息
|
|
721
|
+
*/
|
|
722
|
+
async login(username, password) {
|
|
723
|
+
const timestamp = this.getTimeStamp();
|
|
724
|
+
const { token, tokenparam } = this.getTokenAndTokenParam(timestamp);
|
|
725
|
+
const headers = {
|
|
726
|
+
"Accept-Encoding": "gzip, deflate",
|
|
727
|
+
"User-Agent": "Mozilla/5.0 (Linux; Android 9; V1938CT Build/PQ3A.190705.11211812; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Safari/537.36",
|
|
728
|
+
token,
|
|
729
|
+
tokenparam
|
|
730
|
+
};
|
|
731
|
+
const formData = new import_form_data.default();
|
|
732
|
+
formData.append("username", username);
|
|
733
|
+
formData.append("password", password);
|
|
734
|
+
const res = await http.post(
|
|
735
|
+
"https://www.cdnmhws.cc/login",
|
|
736
|
+
formData,
|
|
737
|
+
{ headers, responseType: "json" }
|
|
738
|
+
);
|
|
739
|
+
return this.decodeBase64(res.data, timestamp);
|
|
740
|
+
}
|
|
741
|
+
async getAlbumById(id) {
|
|
742
|
+
if (debug) logger.info(`获取本子(${id})信息`);
|
|
743
|
+
const timestamp = this.getTimeStamp();
|
|
744
|
+
const { token, tokenparam } = this.getTokenAndTokenParam(timestamp);
|
|
745
|
+
const res = await requestWithUrlSwitch("/album", "POST", {
|
|
746
|
+
params: { id },
|
|
747
|
+
headers: { token, tokenparam },
|
|
748
|
+
responseType: "json"
|
|
749
|
+
});
|
|
750
|
+
const album_json = this.decodeBase64(res.data, timestamp);
|
|
751
|
+
const album = JMAppAlbum.fromJson(album_json);
|
|
752
|
+
const series = album.getSeries();
|
|
753
|
+
const photos = [];
|
|
754
|
+
if (series.length) {
|
|
755
|
+
for (const s of series) {
|
|
756
|
+
const photo = await this.getPhotoById(s.id);
|
|
757
|
+
photos.push(photo);
|
|
758
|
+
}
|
|
759
|
+
} else {
|
|
760
|
+
const photo = await this.getPhotoById(id);
|
|
761
|
+
photos.push(photo);
|
|
762
|
+
}
|
|
763
|
+
album.setPhotos(photos);
|
|
764
|
+
return album;
|
|
765
|
+
}
|
|
766
|
+
async getPhotoById(id) {
|
|
767
|
+
if (debug) logger.info(`获取章节(${id})信息`);
|
|
768
|
+
const timestamp = this.getTimeStamp();
|
|
769
|
+
const { token, tokenparam } = this.getTokenAndTokenParam(timestamp);
|
|
770
|
+
const res = await requestWithUrlSwitch("/chapter", "POST", {
|
|
771
|
+
params: { id },
|
|
772
|
+
headers: { token, tokenparam },
|
|
773
|
+
responseType: "json"
|
|
774
|
+
});
|
|
775
|
+
const photo_json = this.decodeBase64(res.data, timestamp);
|
|
776
|
+
const photo = JMAppPhoto.fromJson(photo_json);
|
|
777
|
+
const images = photo.getImages();
|
|
778
|
+
const image_ids = images.map((image) => image.split(".")[0]);
|
|
779
|
+
photo.setImageNames(image_ids);
|
|
780
|
+
return photo;
|
|
781
|
+
}
|
|
782
|
+
async downloadByAlbum(album) {
|
|
783
|
+
const id = album.getId();
|
|
784
|
+
const path = `${this.root}/album/${id}`;
|
|
785
|
+
await (0, import_promises2.mkdir)(path, { recursive: true });
|
|
786
|
+
const photos = album.getPhotos();
|
|
787
|
+
for (const photo of photos) {
|
|
788
|
+
await this.downloadByPhoto(photo, "album", id, photos.length === 1);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
async downloadByPhoto(photo, type = "photo", albumId = "", single = false) {
|
|
792
|
+
const images = photo.getImages();
|
|
793
|
+
const id = photo.getId();
|
|
794
|
+
let path = `${this.root}/${type}/${id}/origin`;
|
|
795
|
+
if (debug) {
|
|
796
|
+
logger.info(`开始下载: ${id}`);
|
|
797
|
+
logger.info(`单章节: ${single ? "是" : "否"}`);
|
|
798
|
+
logger.info(`子章节: ${albumId ? "是" : "否"}`);
|
|
799
|
+
logger.info(`本子ID:${albumId}`);
|
|
800
|
+
}
|
|
801
|
+
if (type === "album") {
|
|
802
|
+
if (single) {
|
|
803
|
+
path = `${this.root}/${type}/${albumId}/origin`;
|
|
804
|
+
} else {
|
|
805
|
+
path = `${this.root}/${type}/${albumId}/origin/${id}`;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
if (debug) logger.info(`存储目录:${path}`);
|
|
809
|
+
await (0, import_promises2.mkdir)(path, { recursive: true });
|
|
810
|
+
await limitPromiseAll(
|
|
811
|
+
images.filter((image) => {
|
|
812
|
+
const imagePath = `${path}/${image}`;
|
|
813
|
+
const fileExists = fileExistsAsync(imagePath);
|
|
814
|
+
const fileSize = fileSizeAsync(imagePath);
|
|
815
|
+
return !fileExists || !fileSize;
|
|
816
|
+
}).map((image) => async () => {
|
|
817
|
+
const url = `/media/photos/${id}/${image}`;
|
|
818
|
+
if (debug) logger.info(`下载图片:${url}`);
|
|
819
|
+
const res = await requestWithUrlSwitch(
|
|
820
|
+
url,
|
|
821
|
+
"GET",
|
|
822
|
+
{ responseType: "arraybuffer" },
|
|
823
|
+
"IMAGE"
|
|
824
|
+
);
|
|
825
|
+
await saveImage(res, `${path}/${image}`);
|
|
826
|
+
}),
|
|
827
|
+
5
|
|
828
|
+
);
|
|
829
|
+
if (debug) logger.info(`${id} 下载完成,开始解密图片`);
|
|
830
|
+
await this.decodeByPhoto(photo, type, albumId, single);
|
|
831
|
+
}
|
|
832
|
+
async decodeByPhoto(photo, type = "photo", albumId = "", single = false) {
|
|
833
|
+
const images = photo.getImages();
|
|
834
|
+
const id = photo.getId();
|
|
835
|
+
const scramble_id = await this.requestScrambleId(id);
|
|
836
|
+
photo.generateSplitNumbers(scramble_id);
|
|
837
|
+
const splitNumbers = photo.getSplitNumbers();
|
|
838
|
+
let path = `${this.root}/${type}/${id}/origin`;
|
|
839
|
+
let decodedPath = `${this.root}/${type}/${id}/decoded`;
|
|
840
|
+
if (type === "album" && !single) {
|
|
841
|
+
path = `${this.root}/${type}/${albumId}/origin/${id}`;
|
|
842
|
+
decodedPath = `${this.root}/${type}/${albumId}/decoded/${id}`;
|
|
843
|
+
}
|
|
844
|
+
await (0, import_promises2.mkdir)(path, { recursive: true });
|
|
845
|
+
await (0, import_promises2.mkdir)(decodedPath, { recursive: true });
|
|
846
|
+
await limitPromiseAll(
|
|
847
|
+
images.filter((image) => {
|
|
848
|
+
const imagePath = `${decodedPath}/${image}`;
|
|
849
|
+
const fileExists = fileExistsAsync(imagePath);
|
|
850
|
+
const fileSize = fileSizeAsync(imagePath);
|
|
851
|
+
return !fileExists || !fileSize;
|
|
852
|
+
}).map((image, index) => async () => {
|
|
853
|
+
const imagePath = `${path}/${image}`;
|
|
854
|
+
if (debug) logger.info(`解密图片:${imagePath}`);
|
|
855
|
+
const decodedImagePath = `${decodedPath}/${image}`;
|
|
856
|
+
const imageBuffer = await (0, import_promises2.readFile)(imagePath);
|
|
857
|
+
await decodeImage(imageBuffer, splitNumbers[index], decodedImagePath);
|
|
858
|
+
}),
|
|
859
|
+
10
|
|
860
|
+
);
|
|
861
|
+
logger.info(`${id} 解密完成`);
|
|
862
|
+
}
|
|
863
|
+
async albumToPdf(album, password) {
|
|
864
|
+
const id = album.getId();
|
|
865
|
+
const photos = album.getPhotos();
|
|
866
|
+
if (photos.length === 1) {
|
|
867
|
+
const photo = photos[0];
|
|
868
|
+
return await this.photoToPdf(
|
|
869
|
+
photo,
|
|
870
|
+
`${photo.getName()}`,
|
|
871
|
+
"album",
|
|
872
|
+
id,
|
|
873
|
+
true,
|
|
874
|
+
password
|
|
875
|
+
);
|
|
876
|
+
} else {
|
|
877
|
+
let paths = [];
|
|
878
|
+
for (const [i, photo] of photos.entries()) {
|
|
879
|
+
const path = await this.photoToPdf(
|
|
880
|
+
photo,
|
|
881
|
+
`${photo.getName()}_${i + 1}`,
|
|
882
|
+
"album",
|
|
883
|
+
id,
|
|
884
|
+
false,
|
|
885
|
+
password
|
|
886
|
+
);
|
|
887
|
+
paths.push(path);
|
|
888
|
+
}
|
|
889
|
+
return paths;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
async photoToPdf(photo, pdfName, type = "photo", albumId = "", single = false, password) {
|
|
893
|
+
const images = photo.getImages();
|
|
894
|
+
const id = photo.getId();
|
|
895
|
+
if (debug) logger.info(`开始生成PDF ${pdfName}.pdf`);
|
|
896
|
+
pdfName = sanitizeFileName(pdfName);
|
|
897
|
+
let path = (0, import_path2.join)(this.root, type, `${id}`);
|
|
898
|
+
if (type === "album") {
|
|
899
|
+
path = (0, import_path2.join)(this.root, type, `${albumId}`);
|
|
900
|
+
}
|
|
901
|
+
const pdfPath = (0, import_path2.join)(path, `${pdfName}.pdf`);
|
|
902
|
+
let pdfDoc;
|
|
903
|
+
try {
|
|
904
|
+
pdfDoc = new import_muhammara.Recipe("new", pdfPath, {
|
|
905
|
+
version: 1.6
|
|
906
|
+
});
|
|
907
|
+
} catch (error) {
|
|
908
|
+
throw new Error(error);
|
|
909
|
+
}
|
|
910
|
+
const tempPath = (0, import_path2.join)(path, `temp_${id}`);
|
|
911
|
+
await (0, import_promises2.mkdir)(tempPath);
|
|
912
|
+
for (const image of images) {
|
|
913
|
+
let imagePath = (0, import_path2.join)(path, "decoded", image);
|
|
914
|
+
if (type === "album" && !single) {
|
|
915
|
+
imagePath = (0, import_path2.join)(path, "decoded", `${id}`, image);
|
|
916
|
+
}
|
|
917
|
+
const buffer = await (0, import_promises2.readFile)(imagePath);
|
|
918
|
+
const ext = (0, import_path2.extname)(imagePath);
|
|
919
|
+
const jpgName = image.replace(ext, ".jpg");
|
|
920
|
+
const jpgPath = (0, import_path2.join)(tempPath, jpgName);
|
|
921
|
+
const sharpInstance = (0, import_sharp2.default)(buffer);
|
|
922
|
+
await sharpInstance.jpeg().toFile(jpgPath);
|
|
923
|
+
const metadata = await sharpInstance.metadata();
|
|
924
|
+
pdfDoc.createPage(metadata.width, metadata.height).image(jpgPath, 0, 0).endPage();
|
|
925
|
+
}
|
|
926
|
+
if (password) {
|
|
927
|
+
pdfDoc.encrypt({
|
|
928
|
+
userPassword: password,
|
|
929
|
+
ownerPassword: password,
|
|
930
|
+
userProtectionFlag: 4
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
try {
|
|
934
|
+
pdfDoc.endPDF(() => {
|
|
935
|
+
if (debug) logger.info(`PDF ${pdfName}.pdf 生成完成`);
|
|
936
|
+
});
|
|
937
|
+
} catch (error) {
|
|
938
|
+
throw new Error(error);
|
|
939
|
+
} finally {
|
|
940
|
+
await (0, import_promises2.rm)(tempPath, { recursive: true });
|
|
941
|
+
}
|
|
942
|
+
return pdfPath;
|
|
943
|
+
}
|
|
944
|
+
async albumToZip(album, password, level = 6) {
|
|
945
|
+
const id = album.getId();
|
|
946
|
+
const series = album.getSeries();
|
|
947
|
+
const zipName = sanitizeFileName(album.getName());
|
|
948
|
+
const path = `${this.root}/album/${id}`;
|
|
949
|
+
const directorys = [];
|
|
950
|
+
if (series.length > 1) {
|
|
951
|
+
for (const s of series) {
|
|
952
|
+
directorys.push({
|
|
953
|
+
directory: `${path}/decoded/${s.id}`,
|
|
954
|
+
destpath: `第${s.sort}章`
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
} else {
|
|
958
|
+
directorys.push({ directory: `${path}/decoded`, destpath: false });
|
|
959
|
+
}
|
|
960
|
+
await archiverImage(directorys, `${path}/${zipName}.zip`, password, level);
|
|
961
|
+
if (debug) logger.info(`ZIP ${zipName}.zip 生成完成`);
|
|
962
|
+
return `${path}/${zipName}.zip`;
|
|
963
|
+
}
|
|
964
|
+
async photoToZip(photo, zipName, password, level = 6) {
|
|
965
|
+
const id = photo.getId();
|
|
966
|
+
zipName = sanitizeFileName(zipName);
|
|
967
|
+
const path = `${this.root}/photo/${id}`;
|
|
968
|
+
await archiverImage(
|
|
969
|
+
[{ directory: `${path}/decoded`, destpath: false }],
|
|
970
|
+
`${path}/${zipName}.zip`,
|
|
971
|
+
password,
|
|
972
|
+
level
|
|
973
|
+
);
|
|
974
|
+
if (debug) logger.info(`ZIP ${zipName}.zip 生成完成`);
|
|
975
|
+
return `${path}/${zipName}.zip`;
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* 获取时间戳
|
|
979
|
+
* @returns 时间戳
|
|
980
|
+
*/
|
|
981
|
+
getTimeStamp() {
|
|
982
|
+
const date = /* @__PURE__ */ new Date();
|
|
983
|
+
return date.getTime();
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* 获取Scramble ID
|
|
987
|
+
* @param id JM本子ID
|
|
988
|
+
*/
|
|
989
|
+
async requestScrambleId(id) {
|
|
990
|
+
const timestamp = this.getTimeStamp();
|
|
991
|
+
const { token, tokenparam } = this.getTokenAndTokenParam(
|
|
992
|
+
timestamp,
|
|
993
|
+
_JMAppClient.APP_TOKEN_SECRET_2
|
|
994
|
+
);
|
|
995
|
+
const html = await requestWithUrlSwitch(
|
|
996
|
+
"/chapter_view_template",
|
|
997
|
+
"POST",
|
|
998
|
+
{ params: { id }, headers: { token, tokenparam }, responseType: "text" }
|
|
999
|
+
);
|
|
1000
|
+
return parseInt(html.match(JM_SCRAMBLE_ID)[1]);
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* 获取请求时所需的token和tokenparam
|
|
1004
|
+
* @param timestamp 时间戳
|
|
1005
|
+
* @param version APP版本
|
|
1006
|
+
* @param secret 密钥
|
|
1007
|
+
* @returns 请求时所需的token和tokenparam
|
|
1008
|
+
*/
|
|
1009
|
+
getTokenAndTokenParam(timestamp, secret = _JMAppClient.APP_TOKEN_SECRET, version = _JMAppClient.APP_VERSION) {
|
|
1010
|
+
const key = `${timestamp}${secret}`;
|
|
1011
|
+
const token = (0, import_crypto3.createHash)("md5").update(key).digest("hex");
|
|
1012
|
+
const tokenparam = `${timestamp},${version}`;
|
|
1013
|
+
return { token, tokenparam };
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* 解密加密字符串
|
|
1017
|
+
* @param timestamp 请求时传递的时间戳
|
|
1018
|
+
* @param base64 待解密的字符串
|
|
1019
|
+
* @param secret
|
|
1020
|
+
* @returns 解密结果,JSON
|
|
1021
|
+
*/
|
|
1022
|
+
decodeBase64(base64, timestamp, secret = _JMAppClient.APP_DATA_SECRET) {
|
|
1023
|
+
const dataB64 = Buffer.from(base64, "base64");
|
|
1024
|
+
const md5 = this.md5Hex(`${timestamp}${secret}`);
|
|
1025
|
+
const key = Buffer.from(md5);
|
|
1026
|
+
const decipher = (0, import_crypto3.createDecipheriv)("aes-256-ecb", key, null);
|
|
1027
|
+
let dataAES = decipher.update(dataB64);
|
|
1028
|
+
let decrypted = Buffer.concat([dataAES, decipher.final()]);
|
|
1029
|
+
const decodedString = decrypted.toString("utf-8");
|
|
1030
|
+
return JSON.parse(decodedString);
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
|
|
1034
|
+
// src/index.ts
|
|
1035
|
+
var name = "jmcomic";
|
|
1036
|
+
var Config = import_koishi.Schema.intersect([
|
|
1037
|
+
import_koishi.Schema.object({
|
|
1038
|
+
// url: Schema.string().required().default("18comic-mygo.vip"),
|
|
1039
|
+
retryCount: import_koishi.Schema.number().min(1).max(5).default(5),
|
|
1040
|
+
sendMethod: import_koishi.Schema.union(["zip", "pdf"]).default("pdf"),
|
|
1041
|
+
password: import_koishi.Schema.string(),
|
|
1042
|
+
fileName: import_koishi.Schema.string().default("{{name}} ({{id}})_{{index}}")
|
|
1043
|
+
}),
|
|
1044
|
+
import_koishi.Schema.union([
|
|
1045
|
+
import_koishi.Schema.object({
|
|
1046
|
+
sendMethod: import_koishi.Schema.const("zip").required(),
|
|
1047
|
+
level: import_koishi.Schema.number().min(0).max(9).default(6).role("slider")
|
|
1048
|
+
}),
|
|
1049
|
+
import_koishi.Schema.object({})
|
|
1050
|
+
]),
|
|
1051
|
+
import_koishi.Schema.object({
|
|
1052
|
+
cache: import_koishi.Schema.boolean().default(false)
|
|
1053
|
+
}),
|
|
1054
|
+
import_koishi.Schema.union([
|
|
1055
|
+
import_koishi.Schema.object({
|
|
1056
|
+
cache: import_koishi.Schema.const(true).required(),
|
|
1057
|
+
autoDelete: import_koishi.Schema.boolean().default(false)
|
|
1058
|
+
}),
|
|
1059
|
+
import_koishi.Schema.object({})
|
|
1060
|
+
]),
|
|
1061
|
+
import_koishi.Schema.union([
|
|
1062
|
+
import_koishi.Schema.object({
|
|
1063
|
+
cache: import_koishi.Schema.const(true).required(),
|
|
1064
|
+
autoDelete: import_koishi.Schema.const(true).required(),
|
|
1065
|
+
cron: import_koishi.Schema.string().default("0 0 * * *"),
|
|
1066
|
+
deleteInStart: import_koishi.Schema.boolean().default(false),
|
|
1067
|
+
keepDays: import_koishi.Schema.number().min(1).default(7)
|
|
1068
|
+
}),
|
|
1069
|
+
import_koishi.Schema.object({})
|
|
1070
|
+
]),
|
|
1071
|
+
import_koishi.Schema.object({
|
|
1072
|
+
debug: import_koishi.Schema.boolean().default(false)
|
|
1073
|
+
})
|
|
1074
|
+
]).i18n({
|
|
1075
|
+
"zh-CN": require_zh_CN()._config,
|
|
1076
|
+
"en-US": require_en_US()._config
|
|
1077
|
+
});
|
|
1078
|
+
var inject = {
|
|
1079
|
+
required: ["http"],
|
|
1080
|
+
optional: ["notifier", "cron"]
|
|
1081
|
+
};
|
|
1082
|
+
var http;
|
|
1083
|
+
var logger;
|
|
1084
|
+
var retryCount;
|
|
1085
|
+
var debug;
|
|
1086
|
+
async function apply(ctx, config) {
|
|
1087
|
+
http = ctx.http;
|
|
1088
|
+
retryCount = config.retryCount;
|
|
1089
|
+
debug = config.debug;
|
|
1090
|
+
ctx.i18n.define("en-US", require_en_US());
|
|
1091
|
+
ctx.i18n.define("zh-CN", require_zh_CN());
|
|
1092
|
+
logger = ctx.logger("jmcomic");
|
|
1093
|
+
const root = (0, import_path3.join)(ctx.baseDir, "data", "jmcomic");
|
|
1094
|
+
const scheduleFn = /* @__PURE__ */ __name(async () => {
|
|
1095
|
+
const albumPath = (0, import_path3.join)(ctx.baseDir, "data", "jmcomic", "album");
|
|
1096
|
+
await deleteFewDaysAgoFolders(albumPath, config.keepDays);
|
|
1097
|
+
const photoPath = (0, import_path3.join)(ctx.baseDir, "data", "jmcomic", "photo");
|
|
1098
|
+
await deleteFewDaysAgoFolders(photoPath, config.keepDays);
|
|
1099
|
+
}, "scheduleFn");
|
|
1100
|
+
if (ctx.cron) {
|
|
1101
|
+
ctx.cron(config.cron, scheduleFn);
|
|
1102
|
+
}
|
|
1103
|
+
if (config.autoDelete && config.deleteInStart) scheduleFn();
|
|
1104
|
+
if (ctx.notifier) {
|
|
1105
|
+
ctx.notifier.create({
|
|
1106
|
+
type: "warning",
|
|
1107
|
+
content: "据JMComic-Crawler-Python源码可知JM图片还有gif形式,目前尚未支持"
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
ctx.command("jm.album <albumId:string>").alias("本子").action(async ({ session, options }, albumId) => {
|
|
1111
|
+
const messageId = session.messageId;
|
|
1112
|
+
if (!/^\d+$/.test(albumId)) {
|
|
1113
|
+
await session.send([
|
|
1114
|
+
import_koishi.h.quote(messageId),
|
|
1115
|
+
import_koishi.h.text("输入的ID不合法,请检查")
|
|
1116
|
+
]);
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
try {
|
|
1120
|
+
const jmClient = new JMAppClient(root);
|
|
1121
|
+
const album = await jmClient.getAlbumById(albumId);
|
|
1122
|
+
await jmClient.downloadByAlbum(album);
|
|
1123
|
+
let filePath;
|
|
1124
|
+
if (config.sendMethod === "zip") {
|
|
1125
|
+
filePath = await jmClient.albumToZip(
|
|
1126
|
+
album,
|
|
1127
|
+
config.password,
|
|
1128
|
+
config.level
|
|
1129
|
+
);
|
|
1130
|
+
} else {
|
|
1131
|
+
filePath = await jmClient.albumToPdf(album, config.password);
|
|
1132
|
+
}
|
|
1133
|
+
if (typeof filePath === "string") {
|
|
1134
|
+
const buffer = await (0, import_promises3.readFile)(filePath);
|
|
1135
|
+
const { fileName, ext, dir } = getFileInfo(filePath);
|
|
1136
|
+
const name2 = formatFileName(config.fileName, fileName, albumId);
|
|
1137
|
+
if (debug) logger.info(`文件名:${name2}.${ext}`);
|
|
1138
|
+
await session.send([
|
|
1139
|
+
import_koishi.h.file(buffer, ext, { title: `${name2}.${ext}` })
|
|
1140
|
+
]);
|
|
1141
|
+
if (!config.cache) (0, import_promises3.rm)(dir, { recursive: true });
|
|
1142
|
+
} else {
|
|
1143
|
+
let fileDir;
|
|
1144
|
+
for (const [index, p] of filePath.entries()) {
|
|
1145
|
+
const buffer = await (0, import_promises3.readFile)(p);
|
|
1146
|
+
const { fileName, ext, dir } = getFileInfo(p);
|
|
1147
|
+
const name2 = formatFileName(
|
|
1148
|
+
config.fileName,
|
|
1149
|
+
fileName,
|
|
1150
|
+
albumId,
|
|
1151
|
+
index + 1
|
|
1152
|
+
);
|
|
1153
|
+
if (debug) logger.info(`文件名:${name2}.${ext}`);
|
|
1154
|
+
await session.send([
|
|
1155
|
+
import_koishi.h.file(buffer, ext, { title: `${name2}.${ext}` })
|
|
1156
|
+
]);
|
|
1157
|
+
fileDir = dir;
|
|
1158
|
+
}
|
|
1159
|
+
if (!config.cache) (0, import_promises3.rm)(fileDir, { recursive: true });
|
|
1160
|
+
}
|
|
1161
|
+
} catch (error) {
|
|
1162
|
+
if (error instanceof Error) {
|
|
1163
|
+
if (error.message.includes("Could not connect to mysql")) {
|
|
1164
|
+
await session.send([
|
|
1165
|
+
import_koishi.h.quote(messageId),
|
|
1166
|
+
import_koishi.h.text("已尝试所有可能,但是JM坏掉了")
|
|
1167
|
+
]);
|
|
1168
|
+
}
|
|
1169
|
+
} else {
|
|
1170
|
+
throw new Error(error);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
ctx.command("jm.photo <photoId:string>").alias("本子章节").action(async ({ session }, photoId) => {
|
|
1175
|
+
const messageId = session.messageId;
|
|
1176
|
+
if (!/^\d+$/.test(photoId)) {
|
|
1177
|
+
await session.send([
|
|
1178
|
+
import_koishi.h.quote(messageId),
|
|
1179
|
+
import_koishi.h.text("输入的ID不合法,请检查")
|
|
1180
|
+
]);
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
try {
|
|
1184
|
+
const jmClient = new JMAppClient(root);
|
|
1185
|
+
const photo = await jmClient.getPhotoById(photoId);
|
|
1186
|
+
await jmClient.downloadByPhoto(photo);
|
|
1187
|
+
const photoName = photo.getName();
|
|
1188
|
+
let filePath;
|
|
1189
|
+
if (config.sendMethod === "zip") {
|
|
1190
|
+
filePath = await jmClient.photoToZip(
|
|
1191
|
+
photo,
|
|
1192
|
+
photoName,
|
|
1193
|
+
config.password,
|
|
1194
|
+
config.level
|
|
1195
|
+
);
|
|
1196
|
+
} else {
|
|
1197
|
+
filePath = await jmClient.photoToPdf(photo, photoName);
|
|
1198
|
+
}
|
|
1199
|
+
const buffer = await (0, import_promises3.readFile)(filePath);
|
|
1200
|
+
const { fileName, ext, dir } = getFileInfo(filePath);
|
|
1201
|
+
const name2 = formatFileName(config.fileName, fileName, photoId);
|
|
1202
|
+
if (debug) logger.info(`文件名:${filePath}`);
|
|
1203
|
+
if (debug) logger.info(`文件名:${name2}.${ext}`);
|
|
1204
|
+
await session.send([
|
|
1205
|
+
import_koishi.h.file(buffer, ext, { title: `${name2} (${photoId}).${ext}` })
|
|
1206
|
+
]);
|
|
1207
|
+
if (!config.cache) (0, import_promises3.rm)(dir, { recursive: true });
|
|
1208
|
+
} catch (error) {
|
|
1209
|
+
if (error instanceof Error) {
|
|
1210
|
+
if (error.message.includes("Could not connect to mysql")) {
|
|
1211
|
+
await session.send([
|
|
1212
|
+
import_koishi.h.quote(messageId),
|
|
1213
|
+
import_koishi.h.text("已尝试所有可能,但是JM坏掉了")
|
|
1214
|
+
]);
|
|
1215
|
+
}
|
|
1216
|
+
} else {
|
|
1217
|
+
throw new Error(error);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
ctx.command("jm.album.info <albumId:string>").alias("本子信息").action(async ({ session, options }, albumId) => {
|
|
1222
|
+
const messageId = session.messageId;
|
|
1223
|
+
if (!/^\d+$/.test(albumId)) {
|
|
1224
|
+
await session.send([
|
|
1225
|
+
import_koishi.h.quote(messageId),
|
|
1226
|
+
import_koishi.h.text("输入的ID不合法,请检查")
|
|
1227
|
+
]);
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
try {
|
|
1231
|
+
const jmClient = new JMAppClient(root);
|
|
1232
|
+
const album = await jmClient.getAlbumById(albumId);
|
|
1233
|
+
await session.send([
|
|
1234
|
+
import_koishi.h.quote(messageId),
|
|
1235
|
+
import_koishi.h.text(`ID:${album.getId()}
|
|
1236
|
+
`),
|
|
1237
|
+
import_koishi.h.text(`名称:${album.getName()}
|
|
1238
|
+
`),
|
|
1239
|
+
import_koishi.h.text(`章节数:${album.getPhotos().length}
|
|
1240
|
+
`),
|
|
1241
|
+
import_koishi.h.text(`作者:${album.getAuthors()?.join("、") ?? ""}
|
|
1242
|
+
`),
|
|
1243
|
+
import_koishi.h.text(`登场人物:${album.getActors()?.join("、") ?? ""}
|
|
1244
|
+
`),
|
|
1245
|
+
import_koishi.h.text(`点赞数:${album.getLikes()}
|
|
1246
|
+
`),
|
|
1247
|
+
import_koishi.h.text(`观看数:${album.getTotalViews()}`)
|
|
1248
|
+
]);
|
|
1249
|
+
} catch (error) {
|
|
1250
|
+
if (error instanceof Error) {
|
|
1251
|
+
if (error.message.includes("Could not connect to mysql")) {
|
|
1252
|
+
await session.send([
|
|
1253
|
+
import_koishi.h.quote(messageId),
|
|
1254
|
+
import_koishi.h.text("已尝试所有可能,但是JM坏掉了")
|
|
1255
|
+
]);
|
|
1256
|
+
}
|
|
1257
|
+
} else {
|
|
1258
|
+
throw new Error(error);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
__name(apply, "apply");
|
|
1264
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1265
|
+
0 && (module.exports = {
|
|
1266
|
+
Config,
|
|
1267
|
+
apply,
|
|
1268
|
+
debug,
|
|
1269
|
+
http,
|
|
1270
|
+
inject,
|
|
1271
|
+
logger,
|
|
1272
|
+
name,
|
|
1273
|
+
retryCount
|
|
1274
|
+
});
|