@wahaha216/koishi-plugin-jmcomic 0.2.4 → 0.2.6
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/lib/entity/JMAppClient.d.ts +2 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +81 -2
- package/package.json +1 -1
- package/readme.md +19 -5
|
@@ -35,6 +35,8 @@ export declare class JMAppClient extends JMClientAbstract {
|
|
|
35
35
|
getPhotoById(id: string): Promise<JMAppPhoto>;
|
|
36
36
|
downloadByAlbum(album: JMAppAlbum): Promise<void>;
|
|
37
37
|
downloadByPhoto(photo: JMAppPhoto, type?: "photo" | "album", albumId?: string, single?: boolean): Promise<void>;
|
|
38
|
+
downloadFirstImageByAlbum(album: JMAppAlbum): Promise<string>;
|
|
39
|
+
downloadAndDecodeFirstImageByPhoto(photo: JMAppPhoto, type?: "photo" | "album", albumId?: string, single?: boolean): Promise<string>;
|
|
38
40
|
decodeByPhoto(photo: JMPhotoAbstract, type?: "photo" | "album", albumId?: string, single?: boolean): Promise<void>;
|
|
39
41
|
albumToPdf(album: JMAppAlbum, password?: string): Promise<string | string[]>;
|
|
40
42
|
photoToPdf(photo: JMAppPhoto, pdfName: string, type?: "photo" | "album", albumId?: string, single?: boolean, password?: string): Promise<string>;
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -33,14 +33,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
33
33
|
// src/locales/zh-CN.yml
|
|
34
34
|
var require_zh_CN = __commonJS({
|
|
35
35
|
"src/locales/zh-CN.yml"(exports2, module2) {
|
|
36
|
-
module2.exports = { commands: { jm: { description: "下载JM漫画,无需python!", examples: "jm album 本子数字ID\njm album info 本子数字ID\njm photo 本子章节数字ID", album: { examples: "jm album 数字ID", messages: { addedToQueue: "已将 {id} 添加到处理队列,请稍候。", queueFirst: "已将 {id} 添加到处理队列,即将开始处理", queuePosition: "已将 {id} 添加到处理队列\n前面还有 {ahead} 个任务等待或正在处理", queueProcessing: "已将 {id} 添加到处理队列\n当前任务状态:{status}", notExistError: "找不到该ID对应的本子", mysqlError: "已尝试所有备用地址,但是JM坏掉了" }, info: { examples: "jm album info 本子数字ID", messages: { notExistError: "找不到该ID对应的本子", mysqlError: "已尝试所有备用地址,但是JM坏掉了" } } }, photo: { examples: "jm photo 本子章节数字ID", messages: { addedToQueue: "已将 {id} 添加到处理队列,请稍候。", queueFirst: "已将 {id} 添加到处理队列,即将开始处理", queuePosition: "已将 {id} 添加到处理队列\n前面还有 {ahead} 个任务等待或正在处理", queueProcessing: "已将 {id} 添加到处理队列\n当前任务状态:{status}", notExistError: "找不到该ID对应的章节", mysqlError: "已尝试所有备用地址,但是JM坏掉了" } }, queue: { examples: "jm queue", messages: { emptyQueue: "当前没有正在处理或者等待处理的任务", msgFormat: "ID: {id}, 类型: {type}, 状态: {status}\n", task: { pending: "等待中...", processing: "处理中...", failed: "发生未知错误", completed: "已完成", unknown: "未定义状态" }, type: { album: "本子", photo: "章节" } } }, search: { example: "jm search <关键词>", messages: { emptyKeywordError: "请输入搜索关键词", id: "JMID", name: "名称", author: "作者", category: "分类", description: "描述", pagination: "共 {total} 条, 当前第 {page} 页, 每页 {pageSize} 条" } } } }, _config: [{ $desc: "基础设置", sendMethod: "发送方式", fileMethod: "文件获取方式<br>`buffer`: 读取成buffer后发送给bot实现端<br>`file`: 以`file:///` 本地路径形式发送,如docker环境,请在bot实现端同时映射/koishi目录", password: "密码,留空则不加密", fileName: "文件名定义<br>`{{name}}`:标题<br>`{{id}}`:章节或者本子ID<br>`{{index}}`:多章节本子自动填充`1` 、 `2`" }, { level: "压缩级别,0~9,0为仅存储" }, { $desc: "限制相关设置", retryCount: "重试次数限制", concurrentDownloadLimit: "同时下载数量限制", concurrentDecodeLimit: "同时解密数量限制", concurrentQueueLimit: "同时处理数量限制" }, { $desc: "缓存设置", cache: "缓存文件" }, { autoDelete: "自动删除缓存,**依赖cron服务**" }, { cron: "5位cron表达式", deleteInStart: "启动时检测", keepDays: "缓存保留时间(天)" }, { $desc: "开发者选项", debug: "调试模式,输出更多信息" }] };
|
|
36
|
+
module2.exports = { commands: { jm: { description: "下载JM漫画,无需python!", examples: "jm album 本子数字ID\njm album info 本子数字ID\njm photo 本子章节数字ID", album: { examples: "jm album 数字ID", messages: { addedToQueue: "已将 {id} 添加到处理队列,请稍候。", queueFirst: "已将 {id} 添加到处理队列,即将开始处理", queuePosition: "已将 {id} 添加到处理队列\n前面还有 {ahead} 个任务等待或正在处理", queueProcessing: "已将 {id} 添加到处理队列\n当前任务状态:{status}", notExistError: "找不到该ID对应的本子", mysqlError: "已尝试所有备用地址,但是JM坏掉了" }, info: { examples: "jm album info 本子数字ID", messages: { notExistError: "找不到该ID对应的本子", mysqlError: "已尝试所有备用地址,但是JM坏掉了" } } }, photo: { examples: "jm photo 本子章节数字ID", messages: { addedToQueue: "已将 {id} 添加到处理队列,请稍候。", queueFirst: "已将 {id} 添加到处理队列,即将开始处理", queuePosition: "已将 {id} 添加到处理队列\n前面还有 {ahead} 个任务等待或正在处理", queueProcessing: "已将 {id} 添加到处理队列\n当前任务状态:{status}", notExistError: "找不到该ID对应的章节", mysqlError: "已尝试所有备用地址,但是JM坏掉了" } }, queue: { examples: "jm queue", messages: { emptyQueue: "当前没有正在处理或者等待处理的任务", msgFormat: "ID: {id}, 类型: {type}, 状态: {status}\n", task: { pending: "等待中...", processing: "处理中...", failed: "发生未知错误", completed: "已完成", unknown: "未定义状态" }, type: { album: "本子", photo: "章节" } } }, search: { example: "jm search <关键词>", messages: { emptyKeywordError: "请输入搜索关键词", id: "JMID", name: "名称", author: "作者", category: "分类", description: "描述", pagination: "共 {total} 条, 当前第 {page} 页, 每页 {pageSize} 条" } } } }, _config: [{ $desc: "基础设置", sendMethod: "发送方式", fileMethod: "文件获取方式<br>`buffer`: 读取成buffer后发送给bot实现端<br>`file`: 以`file:///` 本地路径形式发送,如docker环境,请在bot实现端同时映射/koishi目录", password: "密码,留空则不加密", fileName: "文件名定义<br>`{{name}}`:标题<br>`{{id}}`:章节或者本子ID<br>`{{index}}`:多章节本子自动填充`1` 、 `2`" }, { level: "压缩级别,0~9,0为仅存储" }, { listeningJMId: "监听 `JM ID` 关键词,当出现JMxxxxx的关键词时,发送第一张图片" }, { $desc: "限制相关设置", retryCount: "重试次数限制", concurrentDownloadLimit: "同时下载数量限制", concurrentDecodeLimit: "同时解密数量限制", concurrentQueueLimit: "同时处理数量限制" }, { $desc: "缓存设置", cache: "缓存文件" }, { autoDelete: "自动删除缓存,**依赖cron服务**" }, { cron: "5位cron表达式", deleteInStart: "启动时检测", keepDays: "缓存保留时间(天)" }, { $desc: "开发者选项", debug: "调试模式,输出更多信息" }] };
|
|
37
37
|
}
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
// src/locales/en-US.yml
|
|
41
41
|
var require_en_US = __commonJS({
|
|
42
42
|
"src/locales/en-US.yml"(exports2, module2) {
|
|
43
|
-
module2.exports = { commands: { jm: { description: "Download JM comics without python!", examples: "jm album albumID\njm album info albumID\njm photo photoID", album: { examples: "jm album albumID", messages: { addedToQueue: "Album {id} has been added to the processing queue. Please wait.", queueFirst: "Added {id} to the processing queue, starting shortly.", queuePosition: "Added {id} to the processing queue.\nThere are {ahead} tasks ahead or currently processing.", queueProcessing: "Added {id} to the processing queue.\nCurrent task status: {status}", notExistError: "albumID not found", mysqlError: "All alternate addresses have been tried. But no response." }, info: { examples: "jm album info albumID", messages: { notExistError: "albumID not found", mysqlError: "All alternate addresses have been tried. But no response." } } }, photo: { examples: "jm photo photoID", messages: { addedToQueue: "Photo {id} has been added to the processing queue. Please wait.", queueFirst: "Added {id} to the processing queue, starting shortly.", queuePosition: "Added {id} to the processing queue.\nThere are {ahead} tasks ahead or currently processing.", queueProcessing: "Added {id} to the processing queue.\nCurrent task status: {status}", notExistError: "photoID not found", mysqlError: "All alternate addresses have been tried. But no response." } }, queue: { examples: "jm queue", messages: { emptyQueue: "There are currently no tasks being processed or waiting.", msgFormat: "ID: {id}, type: {type}, status: {status}\n", task: { Pending: "Pending...", Processing: "Processing...", failed: "Failed (Unknown Error)", completed: "Completed", unknown: "Unknown Status" }, type: { album: "Album", photo: "Photo" } } }, search: { example: "jm search <keyword>", messages: { emptyKeywordError: "Please enter search keywords", id: "JMID", name: "name", author: "author", category: "category", description: "description", pagination: "Total {total}, current {page} page, each page {pageSize}" } } } }, _config: [{ $desc: "Basic settings", sendMethod: "Send method", fileMethod: "File acquisition method<br>`buffer`: Read as buffer and send it to the bot implementation.<br>`file`: Send it in the local path of `file:///`. For example, if in the docker environment, please map the `/koishi` directory at the bot implementation.",
|
|
43
|
+
module2.exports = { commands: { jm: { description: "Download JM comics without python!", examples: "jm album albumID\njm album info albumID\njm photo photoID", album: { examples: "jm album albumID", messages: { addedToQueue: "Album {id} has been added to the processing queue. Please wait.", queueFirst: "Added {id} to the processing queue, starting shortly.", queuePosition: "Added {id} to the processing queue.\nThere are {ahead} tasks ahead or currently processing.", queueProcessing: "Added {id} to the processing queue.\nCurrent task status: {status}", notExistError: "albumID not found", mysqlError: "All alternate addresses have been tried. But no response." }, info: { examples: "jm album info albumID", messages: { notExistError: "albumID not found", mysqlError: "All alternate addresses have been tried. But no response." } } }, photo: { examples: "jm photo photoID", messages: { addedToQueue: "Photo {id} has been added to the processing queue. Please wait.", queueFirst: "Added {id} to the processing queue, starting shortly.", queuePosition: "Added {id} to the processing queue.\nThere are {ahead} tasks ahead or currently processing.", queueProcessing: "Added {id} to the processing queue.\nCurrent task status: {status}", notExistError: "photoID not found", mysqlError: "All alternate addresses have been tried. But no response." } }, queue: { examples: "jm queue", messages: { emptyQueue: "There are currently no tasks being processed or waiting.", msgFormat: "ID: {id}, type: {type}, status: {status}\n", task: { Pending: "Pending...", Processing: "Processing...", failed: "Failed (Unknown Error)", completed: "Completed", unknown: "Unknown Status" }, type: { album: "Album", photo: "Photo" } } }, search: { example: "jm search <keyword>", messages: { emptyKeywordError: "Please enter search keywords", id: "JMID", name: "name", author: "author", category: "category", description: "description", pagination: "Total {total}, current {page} page, each page {pageSize}" } } } }, _config: [{ $desc: "Basic settings", sendMethod: "Send method", fileMethod: "File acquisition method<br>`buffer`: Read as buffer and send it to the bot implementation.<br>`file`: Send it in the local path of `file:///`. For example, if in the docker environment, please map the `/koishi` directory at the bot implementation.", 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" }, { listeningJMId: "Monitors for the `JM ID` keyword. When the 'JMxxxxx' keyword is detected, the first image will be sent." }, { $desc: "Limit settings", retryCount: "Retry limit", concurrentDownloadLimit: "Concurrent Download Limit", concurrentDecodeLimit: "Concurrent Decode Limit", concurrentQueueLimit: "Concurrent Queue Limit" }, { $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
44
|
}
|
|
45
45
|
});
|
|
46
46
|
|
|
@@ -140,6 +140,12 @@ function sanitizeFileName(fileName) {
|
|
|
140
140
|
sanitizedFileName = sanitizedFileName.replace(/\s+/g, "_");
|
|
141
141
|
sanitizedFileName = sanitizedFileName.replace(/_+/g, "_");
|
|
142
142
|
sanitizedFileName = sanitizedFileName.replace(/^[._]+|[._]+$/g, "");
|
|
143
|
+
let byteLength = Buffer.byteLength(sanitizedFileName, "utf-8");
|
|
144
|
+
while (byteLength > 200) {
|
|
145
|
+
const length = sanitizedFileName.length;
|
|
146
|
+
sanitizedFileName = sanitizedFileName.substring(0, length - 1);
|
|
147
|
+
byteLength = Buffer.byteLength(sanitizedFileName, "utf8");
|
|
148
|
+
}
|
|
143
149
|
if (sanitizedFileName.trim() === "") {
|
|
144
150
|
return "untitled_document";
|
|
145
151
|
}
|
|
@@ -929,6 +935,60 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
|
|
|
929
935
|
if (this.config.debug) this.logger.info(`${id} 下载完成,开始解密图片`);
|
|
930
936
|
await this.decodeByPhoto(photo, type, albumId, single);
|
|
931
937
|
}
|
|
938
|
+
async downloadFirstImageByAlbum(album) {
|
|
939
|
+
const id = album.getId();
|
|
940
|
+
const path = `${this.root}/album/${id}`;
|
|
941
|
+
await (0, import_promises2.mkdir)(path, { recursive: true });
|
|
942
|
+
const photos = album.getPhotos();
|
|
943
|
+
return await this.downloadAndDecodeFirstImageByPhoto(
|
|
944
|
+
photos[0],
|
|
945
|
+
"album",
|
|
946
|
+
id,
|
|
947
|
+
photos.length === 1
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
async downloadAndDecodeFirstImageByPhoto(photo, type = "photo", albumId = "", single = false) {
|
|
951
|
+
const image = photo.getImages()[0];
|
|
952
|
+
const id = photo.getId();
|
|
953
|
+
let path = `${this.root}/${type}/${id}/origin`;
|
|
954
|
+
if (type === "album") {
|
|
955
|
+
if (single) {
|
|
956
|
+
path = `${this.root}/${type}/${albumId}/origin`;
|
|
957
|
+
} else {
|
|
958
|
+
path = `${this.root}/${type}/${albumId}/origin/${id}`;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if (this.config.debug) this.logger.info(`存储目录:${path}`);
|
|
962
|
+
await (0, import_promises2.mkdir)(path, { recursive: true });
|
|
963
|
+
const url = `/media/photos/${id}/${image}`;
|
|
964
|
+
if (this.config.debug) this.logger.info(`下载图片:${url}`);
|
|
965
|
+
const res = await requestWithUrlSwitch(
|
|
966
|
+
url,
|
|
967
|
+
"GET",
|
|
968
|
+
{ responseType: "arraybuffer" },
|
|
969
|
+
this.http,
|
|
970
|
+
this.config,
|
|
971
|
+
this.logger,
|
|
972
|
+
"IMAGE"
|
|
973
|
+
);
|
|
974
|
+
await saveImage(res, `${path}/${image}`);
|
|
975
|
+
if (this.config.debug) this.logger.info(`${id} 下载完成,开始解密图片`);
|
|
976
|
+
let decodedPath = `${this.root}/${type}/${id}/decoded`;
|
|
977
|
+
if (type === "album" && !single) {
|
|
978
|
+
path = `${this.root}/${type}/${albumId}/origin/${id}`;
|
|
979
|
+
decodedPath = `${this.root}/${type}/${albumId}/decoded/${id}`;
|
|
980
|
+
}
|
|
981
|
+
await (0, import_promises2.mkdir)(decodedPath, { recursive: true });
|
|
982
|
+
const scramble_id = await this.requestScrambleId(id);
|
|
983
|
+
photo.generateSplitNumbers(scramble_id);
|
|
984
|
+
const splitNumber = photo.getSplitNumbers()[0];
|
|
985
|
+
const imagePath = `${path}/${image}`;
|
|
986
|
+
if (this.config.debug) this.logger.info(`解密图片:${imagePath}`);
|
|
987
|
+
const decodedImagePath = `${decodedPath}/${image}`;
|
|
988
|
+
const imageBuffer = await (0, import_promises2.readFile)(imagePath);
|
|
989
|
+
await decodeImage(imageBuffer, splitNumber, decodedImagePath);
|
|
990
|
+
return decodedImagePath;
|
|
991
|
+
}
|
|
932
992
|
async decodeByPhoto(photo, type = "photo", albumId = "", single = false) {
|
|
933
993
|
const images = photo.getImages();
|
|
934
994
|
const id = photo.getId();
|
|
@@ -1406,6 +1466,7 @@ var Queue = class {
|
|
|
1406
1466
|
};
|
|
1407
1467
|
|
|
1408
1468
|
// src/index.ts
|
|
1469
|
+
var import_promises4 = require("fs/promises");
|
|
1409
1470
|
var name = "jmcomic";
|
|
1410
1471
|
var Config = import_koishi2.Schema.intersect([
|
|
1411
1472
|
import_koishi2.Schema.object({
|
|
@@ -1421,6 +1482,9 @@ var Config = import_koishi2.Schema.intersect([
|
|
|
1421
1482
|
}),
|
|
1422
1483
|
import_koishi2.Schema.object({})
|
|
1423
1484
|
]),
|
|
1485
|
+
import_koishi2.Schema.object({
|
|
1486
|
+
listeningJMId: import_koishi2.Schema.boolean().default(false)
|
|
1487
|
+
}),
|
|
1424
1488
|
import_koishi2.Schema.object({
|
|
1425
1489
|
retryCount: import_koishi2.Schema.number().min(1).max(5).default(5),
|
|
1426
1490
|
concurrentDownloadLimit: import_koishi2.Schema.number().min(0).max(20).default(10),
|
|
@@ -1675,6 +1739,21 @@ async function apply(ctx, config) {
|
|
|
1675
1739
|
});
|
|
1676
1740
|
await session.send([import_koishi2.h.quote(messageId), ...taskInfos]);
|
|
1677
1741
|
});
|
|
1742
|
+
if (config.listeningJMId) {
|
|
1743
|
+
ctx.on("message", async (session) => {
|
|
1744
|
+
const text = session.content;
|
|
1745
|
+
const regexp = /JM\d+/gi;
|
|
1746
|
+
const numbers = text.match(regexp);
|
|
1747
|
+
if (!numbers?.length) return;
|
|
1748
|
+
const id = numbers[0];
|
|
1749
|
+
const jmClient = new JMAppClient(root, ctx.http, config, logger);
|
|
1750
|
+
const album = await jmClient.getAlbumById(id.replace(/JM/i, ""));
|
|
1751
|
+
const imagePath = await jmClient.downloadFirstImageByAlbum(album);
|
|
1752
|
+
const messageId = session.messageId;
|
|
1753
|
+
const imageBuffer = await (0, import_promises4.readFile)(imagePath);
|
|
1754
|
+
session.send([import_koishi2.h.quote(messageId), import_koishi2.h.image(imageBuffer, "webp")]);
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1678
1757
|
}
|
|
1679
1758
|
__name(apply, "apply");
|
|
1680
1759
|
// Annotate the CommonJS export names for ESM import in node:
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -17,10 +17,24 @@ jm queue
|
|
|
17
17
|
|
|
18
18
|
## 更新日志
|
|
19
19
|
|
|
20
|
+
<details>
|
|
21
|
+
<summary>0.2.6</summary>
|
|
22
|
+
|
|
23
|
+
添加监听聊天内容,检测到 `JMxxx` 时发送第一张图片(不是封面)
|
|
24
|
+
|
|
25
|
+
</details>
|
|
26
|
+
|
|
27
|
+
<details>
|
|
28
|
+
<summary>0.2.5</summary>
|
|
29
|
+
|
|
30
|
+
文件名过长时截断至 200 字节
|
|
31
|
+
|
|
32
|
+
</details>
|
|
33
|
+
|
|
20
34
|
<details>
|
|
21
35
|
<summary>0.2.4</summary>
|
|
22
36
|
|
|
23
|
-
1. 调整debug日志
|
|
37
|
+
1. 调整 debug 日志
|
|
24
38
|
2. 修正文化名返回值
|
|
25
39
|
|
|
26
40
|
</details>
|
|
@@ -28,7 +42,7 @@ jm queue
|
|
|
28
42
|
<details>
|
|
29
43
|
<summary>0.2.3</summary>
|
|
30
44
|
|
|
31
|
-
1. 将手动路径拼接替换为join
|
|
45
|
+
1. 将手动路径拼接替换为 join
|
|
32
46
|
2. 健壮文件名序列化,尝试修复部分文件名导致无法创建文件
|
|
33
47
|
|
|
34
48
|
</details>
|
|
@@ -36,7 +50,7 @@ jm queue
|
|
|
36
50
|
<details>
|
|
37
51
|
<summary>0.2.2</summary>
|
|
38
52
|
|
|
39
|
-
高度不足图片分割数时输出原图,尝试规避提取图片高度为0的情况
|
|
53
|
+
高度不足图片分割数时输出原图,尝试规避提取图片高度为 0 的情况
|
|
40
54
|
|
|
41
55
|
</details>
|
|
42
56
|
|
|
@@ -52,7 +66,7 @@ jm queue
|
|
|
52
66
|
<summary>0.2.0</summary>
|
|
53
67
|
|
|
54
68
|
1. 简易搜索功能
|
|
55
|
-
2. 修复队列丢失i18n key的问题
|
|
69
|
+
2. 修复队列丢失 i18n key 的问题
|
|
56
70
|
|
|
57
71
|
</details>
|
|
58
72
|
|
|
@@ -71,7 +85,7 @@ jm queue
|
|
|
71
85
|
2. 下载并发与解密并发限制
|
|
72
86
|
3. 修改配置页面顺序、分类
|
|
73
87
|
4. 不再直接暴露变量,改为逐级传递
|
|
74
|
-
5. 统一暴露Error类
|
|
88
|
+
5. 统一暴露 Error 类
|
|
75
89
|
6. 添加域名切换条件
|
|
76
90
|
|
|
77
91
|
</details>
|