@wahaha216/koishi-plugin-jmcomic 0.0.6 → 0.1.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/lib/entity/JMAppClient.d.ts +15 -1
- package/lib/entity/JMHtmlClient.d.ts +15 -1
- package/lib/error/index.d.ts +6 -0
- package/lib/index.d.ts +4 -5
- package/lib/index.js +497 -229
- package/lib/processors/jmProcessor.d.ts +20 -0
- package/lib/utils/Queue.d.ts +75 -0
- package/lib/utils/Utils.d.ts +5 -4
- package/package.json +1 -1
- package/readme.md +23 -2
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Config } from "..";
|
|
2
|
+
import { Logger, HTTP } from "koishi";
|
|
1
3
|
import { IJMUser } from "../types/JMClient";
|
|
2
4
|
import { JMClientAbstract } from "../abstract/JMClientAbstract";
|
|
3
5
|
import { JMAppPhoto } from "./JMAppPhoto";
|
|
@@ -8,7 +10,19 @@ export declare class JMAppClient extends JMClientAbstract {
|
|
|
8
10
|
static APP_TOKEN_SECRET: string;
|
|
9
11
|
static APP_TOKEN_SECRET_2: string;
|
|
10
12
|
static APP_DATA_SECRET: string;
|
|
11
|
-
|
|
13
|
+
/**
|
|
14
|
+
* koishi 配置项
|
|
15
|
+
*/
|
|
16
|
+
private config;
|
|
17
|
+
/**
|
|
18
|
+
* koishi 日志
|
|
19
|
+
*/
|
|
20
|
+
private logger;
|
|
21
|
+
/**
|
|
22
|
+
* koishi http
|
|
23
|
+
*/
|
|
24
|
+
private http;
|
|
25
|
+
constructor(root: string, http: HTTP, config: Config, logger: Logger);
|
|
12
26
|
/**
|
|
13
27
|
* 登录,未完成
|
|
14
28
|
* @param username 用户名
|
|
@@ -1,9 +1,23 @@
|
|
|
1
|
+
import { Config } from "..";
|
|
2
|
+
import { Logger, HTTP } from "koishi";
|
|
1
3
|
import { JMClientAbstract } from "../abstract/JMClientAbstract";
|
|
2
4
|
import { IJMUser } from "../types/JMClient";
|
|
3
5
|
import { JMHtmlAlbum } from "./JMHtmlAlbum";
|
|
4
6
|
import { JMHtmlPhoto } from "./JMHtmlPhoto";
|
|
5
7
|
export declare class JMHtmlClient extends JMClientAbstract {
|
|
6
|
-
|
|
8
|
+
/**
|
|
9
|
+
* koishi 配置项
|
|
10
|
+
*/
|
|
11
|
+
private config;
|
|
12
|
+
/**
|
|
13
|
+
* koishi 日志
|
|
14
|
+
*/
|
|
15
|
+
private logger;
|
|
16
|
+
/**
|
|
17
|
+
* koishi http
|
|
18
|
+
*/
|
|
19
|
+
private http;
|
|
20
|
+
constructor(root: string, http: HTTP, config: Config, logger: Logger);
|
|
7
21
|
login(username: string, password: string): Promise<IJMUser>;
|
|
8
22
|
getAlbumById(id: string): Promise<JMHtmlAlbum>;
|
|
9
23
|
getPhotoById(id: string): Promise<JMHtmlPhoto>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { AlbumNotExistError } from "../error/albumNotExist.error";
|
|
2
|
+
import { PhotoNotExistError } from "../error/photoNotExist.error";
|
|
3
|
+
import { MySqlError } from "../error/mysql.error";
|
|
4
|
+
import { EmptyBufferError } from "../error/emptybuffer.error";
|
|
5
|
+
import { OverRetryError } from "../error/overRetry.error";
|
|
6
|
+
export { AlbumNotExistError, PhotoNotExistError, MySqlError, EmptyBufferError, OverRetryError, };
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context,
|
|
1
|
+
import { Context, Schema } from "koishi";
|
|
2
2
|
export declare const name = "jmcomic";
|
|
3
3
|
export interface Config {
|
|
4
4
|
sendMethod?: "zip" | "pdf";
|
|
@@ -6,6 +6,9 @@ export interface Config {
|
|
|
6
6
|
retryCount?: number;
|
|
7
7
|
password?: string;
|
|
8
8
|
fileName?: string;
|
|
9
|
+
concurrentDownloadLimit?: number;
|
|
10
|
+
concurrentDecodeLimit?: number;
|
|
11
|
+
concurrentQueueLimit?: number;
|
|
9
12
|
level?: number;
|
|
10
13
|
cache?: boolean;
|
|
11
14
|
autoDelete?: boolean;
|
|
@@ -19,8 +22,4 @@ export declare const inject: {
|
|
|
19
22
|
required: string[];
|
|
20
23
|
optional: string[];
|
|
21
24
|
};
|
|
22
|
-
export declare let http: HTTP;
|
|
23
|
-
export declare let logger: Logger;
|
|
24
|
-
export declare let retryCount: number;
|
|
25
|
-
export declare let debug: boolean;
|
|
26
25
|
export declare function apply(ctx: Context, config: Config): Promise<void>;
|
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: { notExistError: "找不到该ID对应的本子", mysqlError: "已尝试所有备用地址,但是JM坏掉了" }, info: { examples: "jm album info 本子数字ID", messages: { notExistError: "找不到该ID对应的本子", mysqlError: "已尝试所有备用地址,但是JM坏掉了" } } }, photo: { examples: "jm photo 本子章节数字ID", messages: { notExistError: "找不到该ID对应的章节", mysqlError: "已尝试所有备用地址,但是JM坏掉了" } } } },
|
|
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: "章节" } } } } }, _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: "调试模式,输出更多信息" }] };
|
|
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: { 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: { notExistError: "photoID not found", mysqlError: "All alternate addresses have been tried. But no response." } } } },
|
|
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" } } } } }, _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.", 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`" }, { $desc: "Limit settings", retryCount: "Retry limit", concurrentDownloadLimit: "Concurrent Download Limit", concurrentDecodeLimit: "Concurrent Decode Limit", concurrentQueueLimit: "Concurrent Queue Limit" }, { 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
44
|
}
|
|
45
45
|
});
|
|
46
46
|
|
|
@@ -49,17 +49,12 @@ var src_exports = {};
|
|
|
49
49
|
__export(src_exports, {
|
|
50
50
|
Config: () => Config,
|
|
51
51
|
apply: () => apply,
|
|
52
|
-
debug: () => debug,
|
|
53
|
-
http: () => http,
|
|
54
52
|
inject: () => inject,
|
|
55
|
-
|
|
56
|
-
name: () => name,
|
|
57
|
-
retryCount: () => retryCount
|
|
53
|
+
name: () => name
|
|
58
54
|
});
|
|
59
55
|
module.exports = __toCommonJS(src_exports);
|
|
60
|
-
var
|
|
56
|
+
var import_koishi2 = require("koishi");
|
|
61
57
|
var import_path3 = require("path");
|
|
62
|
-
var import_promises3 = require("fs/promises");
|
|
63
58
|
|
|
64
59
|
// src/utils/Utils.ts
|
|
65
60
|
var import_fs = require("fs");
|
|
@@ -169,7 +164,7 @@ async function limitPromiseAll(promises, limit) {
|
|
|
169
164
|
return results;
|
|
170
165
|
}
|
|
171
166
|
__name(limitPromiseAll, "limitPromiseAll");
|
|
172
|
-
async function requestWithRetry(url, method, config = {}, retryIndex = 0) {
|
|
167
|
+
async function requestWithRetry(url, method, config = {}, http, pluginsConfig, logger, retryIndex = 0) {
|
|
173
168
|
try {
|
|
174
169
|
const res = await http(method, url, config);
|
|
175
170
|
if (typeof res.data === "string" && res.data.includes("Could not connect to mysql")) {
|
|
@@ -179,18 +174,26 @@ async function requestWithRetry(url, method, config = {}, retryIndex = 0) {
|
|
|
179
174
|
} catch (error) {
|
|
180
175
|
if (error instanceof MySqlError) {
|
|
181
176
|
throw new MySqlError();
|
|
182
|
-
} else if (retryIndex < retryCount) {
|
|
177
|
+
} else if (retryIndex < pluginsConfig.retryCount) {
|
|
183
178
|
logger.info(
|
|
184
|
-
`${url} 请求失败,正在重试... ${retryIndex + 1}/${retryCount}`
|
|
179
|
+
`${url} 请求失败,正在重试... ${retryIndex + 1}/${pluginsConfig.retryCount}`
|
|
180
|
+
);
|
|
181
|
+
return await requestWithRetry(
|
|
182
|
+
url,
|
|
183
|
+
method,
|
|
184
|
+
config,
|
|
185
|
+
http,
|
|
186
|
+
pluginsConfig,
|
|
187
|
+
logger,
|
|
188
|
+
retryIndex + 1
|
|
185
189
|
);
|
|
186
|
-
return await requestWithRetry(url, method, config, retryIndex + 1);
|
|
187
190
|
} else {
|
|
188
191
|
throw new OverRetryError(`请求失败,超过最大重试次数: ${url}`);
|
|
189
192
|
}
|
|
190
193
|
}
|
|
191
194
|
}
|
|
192
195
|
__name(requestWithRetry, "requestWithRetry");
|
|
193
|
-
async function requestWithUrlSwitch(url, method, config = {}, type = "CLIENT", urlIndex = 0) {
|
|
196
|
+
async function requestWithUrlSwitch(url, method, config = {}, http, pluginsConfig, logger, type = "CLIENT", urlIndex = 0) {
|
|
194
197
|
const list = type === "CLIENT" ? JM_CLIENT_URL_LIST : JM_IMAGE_URL_LIST;
|
|
195
198
|
const urlCount = list.length;
|
|
196
199
|
const url_bak = url;
|
|
@@ -199,7 +202,14 @@ async function requestWithUrlSwitch(url, method, config = {}, type = "CLIENT", u
|
|
|
199
202
|
}
|
|
200
203
|
try {
|
|
201
204
|
if (urlIndex < urlCount) {
|
|
202
|
-
const res = await requestWithRetry(
|
|
205
|
+
const res = await requestWithRetry(
|
|
206
|
+
url,
|
|
207
|
+
method,
|
|
208
|
+
config,
|
|
209
|
+
http,
|
|
210
|
+
pluginsConfig,
|
|
211
|
+
logger
|
|
212
|
+
);
|
|
203
213
|
if (res instanceof ArrayBuffer && res.byteLength === 0) {
|
|
204
214
|
throw new EmptyBufferError();
|
|
205
215
|
}
|
|
@@ -210,12 +220,16 @@ async function requestWithUrlSwitch(url, method, config = {}, type = "CLIENT", u
|
|
|
210
220
|
} catch (error) {
|
|
211
221
|
const isMysqlError = error instanceof MySqlError;
|
|
212
222
|
const isEmptyBuffer = error instanceof EmptyBufferError;
|
|
213
|
-
|
|
223
|
+
const isOverRetryError = error instanceof OverRetryError;
|
|
224
|
+
if (isMysqlError || isEmptyBuffer || isOverRetryError) {
|
|
214
225
|
logger.info(`请求失败,尝试切换域名... ${urlIndex + 1}/${urlCount}`);
|
|
215
226
|
return await requestWithUrlSwitch(
|
|
216
227
|
url_bak,
|
|
217
228
|
method,
|
|
218
229
|
config,
|
|
230
|
+
http,
|
|
231
|
+
pluginsConfig,
|
|
232
|
+
logger,
|
|
219
233
|
type,
|
|
220
234
|
urlIndex + 1
|
|
221
235
|
);
|
|
@@ -734,8 +748,23 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
|
|
|
734
748
|
static APP_TOKEN_SECRET = "18comicAPP";
|
|
735
749
|
static APP_TOKEN_SECRET_2 = "18comicAPPContent";
|
|
736
750
|
static APP_DATA_SECRET = "185Hcomic3PAPP7R";
|
|
737
|
-
|
|
751
|
+
/**
|
|
752
|
+
* koishi 配置项
|
|
753
|
+
*/
|
|
754
|
+
config;
|
|
755
|
+
/**
|
|
756
|
+
* koishi 日志
|
|
757
|
+
*/
|
|
758
|
+
logger;
|
|
759
|
+
/**
|
|
760
|
+
* koishi http
|
|
761
|
+
*/
|
|
762
|
+
http;
|
|
763
|
+
constructor(root, http, config, logger) {
|
|
738
764
|
super(root);
|
|
765
|
+
this.config = config;
|
|
766
|
+
this.logger = logger;
|
|
767
|
+
this.http = http;
|
|
739
768
|
}
|
|
740
769
|
/**
|
|
741
770
|
* 登录,未完成
|
|
@@ -755,7 +784,7 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
|
|
|
755
784
|
const formData = new import_form_data.default();
|
|
756
785
|
formData.append("username", username);
|
|
757
786
|
formData.append("password", password);
|
|
758
|
-
const res = await http.post(
|
|
787
|
+
const res = await this.http.post(
|
|
759
788
|
"https://www.cdnmhws.cc/login",
|
|
760
789
|
formData,
|
|
761
790
|
{ headers, responseType: "json" }
|
|
@@ -763,14 +792,21 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
|
|
|
763
792
|
return this.decodeBase64(res.data, timestamp);
|
|
764
793
|
}
|
|
765
794
|
async getAlbumById(id) {
|
|
766
|
-
if (debug) logger.info(`获取本子(${id})信息`);
|
|
795
|
+
if (this.config.debug) this.logger.info(`获取本子(${id})信息`);
|
|
767
796
|
const timestamp = this.getTimeStamp();
|
|
768
797
|
const { token, tokenparam } = this.getTokenAndTokenParam(timestamp);
|
|
769
|
-
const res = await requestWithUrlSwitch(
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
798
|
+
const res = await requestWithUrlSwitch(
|
|
799
|
+
"/album",
|
|
800
|
+
"POST",
|
|
801
|
+
{
|
|
802
|
+
params: { id },
|
|
803
|
+
headers: { token, tokenparam },
|
|
804
|
+
responseType: "json"
|
|
805
|
+
},
|
|
806
|
+
this.http,
|
|
807
|
+
this.config,
|
|
808
|
+
this.logger
|
|
809
|
+
);
|
|
774
810
|
const album_json = this.decodeBase64(res.data, timestamp);
|
|
775
811
|
if (!album_json.name) throw new AlbumNotExistError();
|
|
776
812
|
const album = JMAppAlbum.fromJson(album_json);
|
|
@@ -789,14 +825,21 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
|
|
|
789
825
|
return album;
|
|
790
826
|
}
|
|
791
827
|
async getPhotoById(id) {
|
|
792
|
-
if (debug) logger.info(`获取章节(${id})信息`);
|
|
828
|
+
if (this.config.debug) this.logger.info(`获取章节(${id})信息`);
|
|
793
829
|
const timestamp = this.getTimeStamp();
|
|
794
830
|
const { token, tokenparam } = this.getTokenAndTokenParam(timestamp);
|
|
795
|
-
const res = await requestWithUrlSwitch(
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
831
|
+
const res = await requestWithUrlSwitch(
|
|
832
|
+
"/chapter",
|
|
833
|
+
"POST",
|
|
834
|
+
{
|
|
835
|
+
params: { id },
|
|
836
|
+
headers: { token, tokenparam },
|
|
837
|
+
responseType: "json"
|
|
838
|
+
},
|
|
839
|
+
this.http,
|
|
840
|
+
this.config,
|
|
841
|
+
this.logger
|
|
842
|
+
);
|
|
800
843
|
const photo_json = this.decodeBase64(res.data, timestamp);
|
|
801
844
|
if (!photo_json.name) throw new PhotoNotExistError();
|
|
802
845
|
const photo = JMAppPhoto.fromJson(photo_json);
|
|
@@ -818,12 +861,12 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
|
|
|
818
861
|
const images = photo.getImages();
|
|
819
862
|
const id = photo.getId();
|
|
820
863
|
let path = `${this.root}/${type}/${id}/origin`;
|
|
821
|
-
if (debug) {
|
|
822
|
-
logger.info(`开始下载: ${id}`);
|
|
864
|
+
if (this.config.debug) {
|
|
865
|
+
this.logger.info(`开始下载: ${id}`);
|
|
823
866
|
if (type === "album") {
|
|
824
|
-
logger.info(`单章节: ${single ? "是" : "否"}`);
|
|
825
|
-
logger.info(`子章节: ${albumId ? "是" : "否"}`);
|
|
826
|
-
logger.info(`本子ID: ${albumId}`);
|
|
867
|
+
this.logger.info(`单章节: ${single ? "是" : "否"}`);
|
|
868
|
+
this.logger.info(`子章节: ${albumId ? "是" : "否"}`);
|
|
869
|
+
this.logger.info(`本子ID: ${albumId}`);
|
|
827
870
|
}
|
|
828
871
|
}
|
|
829
872
|
if (type === "album") {
|
|
@@ -833,7 +876,7 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
|
|
|
833
876
|
path = `${this.root}/${type}/${albumId}/origin/${id}`;
|
|
834
877
|
}
|
|
835
878
|
}
|
|
836
|
-
if (debug) logger.info(`存储目录:${path}`);
|
|
879
|
+
if (this.config.debug) this.logger.info(`存储目录:${path}`);
|
|
837
880
|
await (0, import_promises2.mkdir)(path, { recursive: true });
|
|
838
881
|
await limitPromiseAll(
|
|
839
882
|
images.filter((image) => {
|
|
@@ -843,18 +886,21 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
|
|
|
843
886
|
return !fileExists || !fileSize;
|
|
844
887
|
}).map((image) => async () => {
|
|
845
888
|
const url = `/media/photos/${id}/${image}`;
|
|
846
|
-
if (debug) logger.info(`下载图片:${url}`);
|
|
889
|
+
if (this.config.debug) this.logger.info(`下载图片:${url}`);
|
|
847
890
|
const res = await requestWithUrlSwitch(
|
|
848
891
|
url,
|
|
849
892
|
"GET",
|
|
850
893
|
{ responseType: "arraybuffer" },
|
|
894
|
+
this.http,
|
|
895
|
+
this.config,
|
|
896
|
+
this.logger,
|
|
851
897
|
"IMAGE"
|
|
852
898
|
);
|
|
853
899
|
await saveImage(res, `${path}/${image}`);
|
|
854
900
|
}),
|
|
855
|
-
|
|
901
|
+
this.config.concurrentDownloadLimit
|
|
856
902
|
);
|
|
857
|
-
if (debug) logger.info(`${id} 下载完成,开始解密图片`);
|
|
903
|
+
if (this.config.debug) this.logger.info(`${id} 下载完成,开始解密图片`);
|
|
858
904
|
await this.decodeByPhoto(photo, type, albumId, single);
|
|
859
905
|
}
|
|
860
906
|
async decodeByPhoto(photo, type = "photo", albumId = "", single = false) {
|
|
@@ -879,14 +925,14 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
|
|
|
879
925
|
return !fileExists || !fileSize;
|
|
880
926
|
}).map((image, index) => async () => {
|
|
881
927
|
const imagePath = `${path}/${image}`;
|
|
882
|
-
if (debug) logger.info(`解密图片:${imagePath}`);
|
|
928
|
+
if (this.config.debug) this.logger.info(`解密图片:${imagePath}`);
|
|
883
929
|
const decodedImagePath = `${decodedPath}/${image}`;
|
|
884
930
|
const imageBuffer = await (0, import_promises2.readFile)(imagePath);
|
|
885
931
|
await decodeImage(imageBuffer, splitNumbers[index], decodedImagePath);
|
|
886
932
|
}),
|
|
887
|
-
|
|
933
|
+
this.config.concurrentDecodeLimit
|
|
888
934
|
);
|
|
889
|
-
logger.info(`${id} 解密完成`);
|
|
935
|
+
this.logger.info(`${id} 解密完成`);
|
|
890
936
|
}
|
|
891
937
|
async albumToPdf(album, password) {
|
|
892
938
|
const id = album.getId();
|
|
@@ -920,7 +966,7 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
|
|
|
920
966
|
async photoToPdf(photo, pdfName, type = "photo", albumId = "", single = false, password) {
|
|
921
967
|
const images = photo.getImages();
|
|
922
968
|
const id = photo.getId();
|
|
923
|
-
if (debug) logger.info(`开始生成PDF ${pdfName}.pdf`);
|
|
969
|
+
if (this.config.debug) this.logger.info(`开始生成PDF ${pdfName}.pdf`);
|
|
924
970
|
pdfName = sanitizeFileName(pdfName);
|
|
925
971
|
let path = (0, import_path2.join)(this.root, type, `${id}`);
|
|
926
972
|
if (type === "album") {
|
|
@@ -960,7 +1006,7 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
|
|
|
960
1006
|
}
|
|
961
1007
|
try {
|
|
962
1008
|
pdfDoc.endPDF(() => {
|
|
963
|
-
if (debug) logger.info(`PDF ${pdfName}.pdf 生成完成`);
|
|
1009
|
+
if (this.config.debug) this.logger.info(`PDF ${pdfName}.pdf 生成完成`);
|
|
964
1010
|
});
|
|
965
1011
|
} catch (error) {
|
|
966
1012
|
throw new Error(error);
|
|
@@ -986,7 +1032,7 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
|
|
|
986
1032
|
directorys.push({ directory: `${path}/decoded`, destpath: false });
|
|
987
1033
|
}
|
|
988
1034
|
await archiverImage(directorys, `${path}/${zipName}.zip`, password, level);
|
|
989
|
-
if (debug) logger.info(`ZIP ${zipName}.zip 生成完成`);
|
|
1035
|
+
if (this.config.debug) this.logger.info(`ZIP ${zipName}.zip 生成完成`);
|
|
990
1036
|
return `${path}/${zipName}.zip`;
|
|
991
1037
|
}
|
|
992
1038
|
async photoToZip(photo, zipName, password, level = 6) {
|
|
@@ -999,7 +1045,7 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
|
|
|
999
1045
|
password,
|
|
1000
1046
|
level
|
|
1001
1047
|
);
|
|
1002
|
-
if (debug) logger.info(`ZIP ${zipName}.zip 生成完成`);
|
|
1048
|
+
if (this.config.debug) this.logger.info(`ZIP ${zipName}.zip 生成完成`);
|
|
1003
1049
|
return `${path}/${zipName}.zip`;
|
|
1004
1050
|
}
|
|
1005
1051
|
/**
|
|
@@ -1023,7 +1069,10 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
|
|
|
1023
1069
|
const html = await requestWithUrlSwitch(
|
|
1024
1070
|
"/chapter_view_template",
|
|
1025
1071
|
"POST",
|
|
1026
|
-
{ params: { id }, headers: { token, tokenparam }, responseType: "text" }
|
|
1072
|
+
{ params: { id }, headers: { token, tokenparam }, responseType: "text" },
|
|
1073
|
+
this.http,
|
|
1074
|
+
this.config,
|
|
1075
|
+
this.logger
|
|
1027
1076
|
);
|
|
1028
1077
|
return parseInt(html.match(JM_SCRAMBLE_ID)[1]);
|
|
1029
1078
|
}
|
|
@@ -1059,45 +1108,317 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
|
|
|
1059
1108
|
}
|
|
1060
1109
|
};
|
|
1061
1110
|
|
|
1111
|
+
// src/processors/jmProcessor.ts
|
|
1112
|
+
var import_koishi = require("koishi");
|
|
1113
|
+
var import_promises3 = require("node:fs/promises");
|
|
1114
|
+
var createJmProcessor = /* @__PURE__ */ __name((processorConfig, http, config, logger) => {
|
|
1115
|
+
const {
|
|
1116
|
+
root,
|
|
1117
|
+
sendMethod,
|
|
1118
|
+
password,
|
|
1119
|
+
level,
|
|
1120
|
+
fileName,
|
|
1121
|
+
fileMethod,
|
|
1122
|
+
cache,
|
|
1123
|
+
debug
|
|
1124
|
+
} = processorConfig;
|
|
1125
|
+
return async (payload) => {
|
|
1126
|
+
const { id, session, messageId } = payload;
|
|
1127
|
+
try {
|
|
1128
|
+
const jmClient = new JMAppClient(root, http, config, logger);
|
|
1129
|
+
let filePath;
|
|
1130
|
+
let name2;
|
|
1131
|
+
let ext;
|
|
1132
|
+
let dir = "";
|
|
1133
|
+
if (payload.type === "album") {
|
|
1134
|
+
const album = await jmClient.getAlbumById(id);
|
|
1135
|
+
await jmClient.downloadByAlbum(album);
|
|
1136
|
+
if (sendMethod === "zip") {
|
|
1137
|
+
filePath = await jmClient.albumToZip(album, password, level);
|
|
1138
|
+
} else {
|
|
1139
|
+
filePath = await jmClient.albumToPdf(album, password);
|
|
1140
|
+
}
|
|
1141
|
+
if (typeof filePath === "string") {
|
|
1142
|
+
const fileInfo = getFileInfo(filePath);
|
|
1143
|
+
name2 = formatFileName(fileName, fileInfo.fileName, id);
|
|
1144
|
+
ext = fileInfo.ext;
|
|
1145
|
+
dir = fileInfo.dir;
|
|
1146
|
+
} else {
|
|
1147
|
+
const firstPath = filePath[0];
|
|
1148
|
+
const fileInfo = getFileInfo(firstPath);
|
|
1149
|
+
name2 = formatFileName(fileName, fileInfo.fileName, id);
|
|
1150
|
+
ext = fileInfo.ext;
|
|
1151
|
+
dir = fileInfo.dir;
|
|
1152
|
+
}
|
|
1153
|
+
} else if (payload.type === "photo") {
|
|
1154
|
+
const photo = await jmClient.getPhotoById(id);
|
|
1155
|
+
await jmClient.downloadByPhoto(photo);
|
|
1156
|
+
const photoName = photo.getName();
|
|
1157
|
+
if (sendMethod === "zip") {
|
|
1158
|
+
filePath = await jmClient.photoToZip(
|
|
1159
|
+
photo,
|
|
1160
|
+
photoName,
|
|
1161
|
+
password,
|
|
1162
|
+
level
|
|
1163
|
+
);
|
|
1164
|
+
} else {
|
|
1165
|
+
filePath = await jmClient.photoToPdf(photo, photoName);
|
|
1166
|
+
}
|
|
1167
|
+
const fileInfo = getFileInfo(filePath);
|
|
1168
|
+
name2 = formatFileName(fileName, fileInfo.fileName, id);
|
|
1169
|
+
ext = fileInfo.ext;
|
|
1170
|
+
dir = fileInfo.dir;
|
|
1171
|
+
} else {
|
|
1172
|
+
throw new Error(`未知任务类型: ${payload.type}`);
|
|
1173
|
+
}
|
|
1174
|
+
if (typeof filePath === "string") {
|
|
1175
|
+
const {
|
|
1176
|
+
fileName: baseFileName,
|
|
1177
|
+
ext: fileExt,
|
|
1178
|
+
dir: fileDir
|
|
1179
|
+
} = getFileInfo(filePath);
|
|
1180
|
+
const finalName = formatFileName(fileName, baseFileName, id);
|
|
1181
|
+
if (debug) logger.info(`文件名:${finalName}.${fileExt}`);
|
|
1182
|
+
if (fileMethod === "buffer") {
|
|
1183
|
+
const buffer = await (0, import_promises3.readFile)(filePath);
|
|
1184
|
+
await session.send([
|
|
1185
|
+
import_koishi.h.file(buffer, fileExt, { title: `${finalName}.${fileExt}` })
|
|
1186
|
+
]);
|
|
1187
|
+
} else {
|
|
1188
|
+
await session.send([
|
|
1189
|
+
import_koishi.h.file(`file:///${filePath}`, { title: `${finalName}.${fileExt}` })
|
|
1190
|
+
]);
|
|
1191
|
+
}
|
|
1192
|
+
if (!cache) (0, import_promises3.rm)(fileDir, { recursive: true });
|
|
1193
|
+
} else {
|
|
1194
|
+
let currentFileDir = "";
|
|
1195
|
+
for (const [index, p] of filePath.entries()) {
|
|
1196
|
+
const {
|
|
1197
|
+
fileName: baseFileName,
|
|
1198
|
+
ext: fileExt,
|
|
1199
|
+
dir: fileDir
|
|
1200
|
+
} = getFileInfo(p);
|
|
1201
|
+
const finalName = formatFileName(
|
|
1202
|
+
fileName,
|
|
1203
|
+
baseFileName,
|
|
1204
|
+
id,
|
|
1205
|
+
index + 1
|
|
1206
|
+
);
|
|
1207
|
+
if (debug) logger.info(`文件名:${finalName}.${fileExt}`);
|
|
1208
|
+
if (fileMethod === "buffer") {
|
|
1209
|
+
const buffer = await (0, import_promises3.readFile)(p);
|
|
1210
|
+
await session.send([
|
|
1211
|
+
import_koishi.h.file(buffer, fileExt, { title: `${finalName}.${fileExt}` })
|
|
1212
|
+
]);
|
|
1213
|
+
} else {
|
|
1214
|
+
await session.send([
|
|
1215
|
+
import_koishi.h.file(`file:///${p}`, { title: `${finalName}.${fileExt}` })
|
|
1216
|
+
]);
|
|
1217
|
+
}
|
|
1218
|
+
currentFileDir = fileDir;
|
|
1219
|
+
}
|
|
1220
|
+
if (!cache) {
|
|
1221
|
+
if (currentFileDir) {
|
|
1222
|
+
(0, import_promises3.rm)(currentFileDir, { recursive: true });
|
|
1223
|
+
} else {
|
|
1224
|
+
logger.warn(`无法删除目录,fileDir 未定义。任务 ID: ${id}`);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
} catch (error) {
|
|
1229
|
+
if (error instanceof AlbumNotExistError || error instanceof PhotoNotExistError) {
|
|
1230
|
+
await session.send([
|
|
1231
|
+
import_koishi.h.quote(messageId),
|
|
1232
|
+
import_koishi.h.text(session.text(".notExistError"))
|
|
1233
|
+
// 假设 .notExistError 可以通用
|
|
1234
|
+
]);
|
|
1235
|
+
} else if (error instanceof MySqlError) {
|
|
1236
|
+
await session.send([
|
|
1237
|
+
import_koishi.h.quote(messageId),
|
|
1238
|
+
import_koishi.h.text(session.text(".mysqlError"))
|
|
1239
|
+
]);
|
|
1240
|
+
} else {
|
|
1241
|
+
await session.send([
|
|
1242
|
+
import_koishi.h.quote(messageId),
|
|
1243
|
+
import_koishi.h.text(`处理 ID 为 ${id} 的本子/章节时发生错误`)
|
|
1244
|
+
]);
|
|
1245
|
+
throw error;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
}, "createJmProcessor");
|
|
1250
|
+
|
|
1251
|
+
// src/utils/Queue.ts
|
|
1252
|
+
var import_crypto4 = require("crypto");
|
|
1253
|
+
var Queue = class {
|
|
1254
|
+
static {
|
|
1255
|
+
__name(this, "Queue");
|
|
1256
|
+
}
|
|
1257
|
+
tasks = [];
|
|
1258
|
+
processor;
|
|
1259
|
+
concurrency;
|
|
1260
|
+
activeTasks = 0;
|
|
1261
|
+
/**
|
|
1262
|
+
* koishi 配置项
|
|
1263
|
+
*/
|
|
1264
|
+
config;
|
|
1265
|
+
/**
|
|
1266
|
+
* koishi 日志
|
|
1267
|
+
*/
|
|
1268
|
+
logger;
|
|
1269
|
+
constructor(processor, options = {}, config, logger) {
|
|
1270
|
+
this.config = config;
|
|
1271
|
+
this.logger = logger;
|
|
1272
|
+
this.processor = processor;
|
|
1273
|
+
this.concurrency = options.concurrency || 1;
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* 向队列添加一个新任务
|
|
1277
|
+
*/
|
|
1278
|
+
add(payload) {
|
|
1279
|
+
const task = {
|
|
1280
|
+
id: (0, import_crypto4.randomUUID)(),
|
|
1281
|
+
payload,
|
|
1282
|
+
status: "pending",
|
|
1283
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1284
|
+
};
|
|
1285
|
+
this.tasks.push(task);
|
|
1286
|
+
if (this.config.debug)
|
|
1287
|
+
this.logger.info(
|
|
1288
|
+
`[任务添加] 任务ID: ${payload} 类型: ${payload.type} ID: ${payload.id}`
|
|
1289
|
+
);
|
|
1290
|
+
const { pendingAhead, queuePosition } = this.getTaskQueuePosition(task.id);
|
|
1291
|
+
setTimeout(() => this._processQueue(), 0);
|
|
1292
|
+
return { task, pendingAhead, queuePosition };
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* 获取任务状态
|
|
1296
|
+
*/
|
|
1297
|
+
getTask(id) {
|
|
1298
|
+
return this.tasks.find((t) => t.id === id);
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* 获取所有任务的只读列表
|
|
1302
|
+
*/
|
|
1303
|
+
getAllTasks() {
|
|
1304
|
+
return this.tasks;
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* 获取指定任务在队列中的位置信息
|
|
1308
|
+
* @param taskId 任务ID
|
|
1309
|
+
* @returns {pendingAhead: number, queuePosition: number}
|
|
1310
|
+
* pendingAhead: 在此任务之前有多少个待处理任务
|
|
1311
|
+
* queuePosition: 此任务在所有待处理任务中的位置(从1开始)
|
|
1312
|
+
*/
|
|
1313
|
+
getTaskQueuePosition(taskId) {
|
|
1314
|
+
let pendingAhead = 0;
|
|
1315
|
+
let queuePosition = 0;
|
|
1316
|
+
let found = false;
|
|
1317
|
+
for (let i = 0; i < this.tasks.length; i++) {
|
|
1318
|
+
const currentTask = this.tasks[i];
|
|
1319
|
+
if (currentTask.id === taskId) {
|
|
1320
|
+
found = true;
|
|
1321
|
+
if (currentTask.status === "pending" || currentTask.status === "processing") {
|
|
1322
|
+
queuePosition = pendingAhead + 1;
|
|
1323
|
+
}
|
|
1324
|
+
break;
|
|
1325
|
+
}
|
|
1326
|
+
if (currentTask.status === "pending" || currentTask.status === "processing") {
|
|
1327
|
+
pendingAhead++;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
if (!found) {
|
|
1331
|
+
return { pendingAhead: -1, queuePosition: -1 };
|
|
1332
|
+
}
|
|
1333
|
+
return { pendingAhead, queuePosition };
|
|
1334
|
+
}
|
|
1335
|
+
/**
|
|
1336
|
+
* 检查并处理队列中的任务
|
|
1337
|
+
*/
|
|
1338
|
+
_processQueue() {
|
|
1339
|
+
while (this.activeTasks < this.concurrency) {
|
|
1340
|
+
const task = this.tasks.find((t) => t.status === "pending");
|
|
1341
|
+
if (!task) break;
|
|
1342
|
+
this.activeTasks++;
|
|
1343
|
+
task.status = "processing";
|
|
1344
|
+
if (this.config.debug)
|
|
1345
|
+
this.logger.info(
|
|
1346
|
+
`[任务开始] 任务ID: ${task.id} 类型: ${task.payload.type} ID: ${task.payload.id}`
|
|
1347
|
+
);
|
|
1348
|
+
this._runTask(task);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* 执行单个任务
|
|
1353
|
+
*/
|
|
1354
|
+
async _runTask(task) {
|
|
1355
|
+
try {
|
|
1356
|
+
await this.processor(task.payload);
|
|
1357
|
+
task.status = "completed";
|
|
1358
|
+
if (this.config.debug)
|
|
1359
|
+
this.logger.info(
|
|
1360
|
+
`[任务成功] 任务ID: ${task.id} 类型: ${task.payload.type} ID: ${task.payload.id}`
|
|
1361
|
+
);
|
|
1362
|
+
} catch (error) {
|
|
1363
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1364
|
+
if (this.config.debug)
|
|
1365
|
+
this.logger.error(
|
|
1366
|
+
`[任务失败] 任务ID: ${task.id} 类型: ${task.payload.type} ID: ${task.payload.id}, 错误: ${errorMessage}`
|
|
1367
|
+
);
|
|
1368
|
+
task.status = "failed";
|
|
1369
|
+
task.error = errorMessage;
|
|
1370
|
+
} finally {
|
|
1371
|
+
task.processedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1372
|
+
this.activeTasks--;
|
|
1373
|
+
this._processQueue();
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
|
|
1062
1378
|
// src/index.ts
|
|
1063
1379
|
var name = "jmcomic";
|
|
1064
|
-
var Config =
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
fileName: import_koishi.Schema.string().default("{{name}} ({{id}})_{{index}}")
|
|
1380
|
+
var Config = import_koishi2.Schema.intersect([
|
|
1381
|
+
import_koishi2.Schema.object({
|
|
1382
|
+
fileMethod: import_koishi2.Schema.union(["buffer", "file"]).default("buffer"),
|
|
1383
|
+
password: import_koishi2.Schema.string(),
|
|
1384
|
+
fileName: import_koishi2.Schema.string().default("{{name}} ({{id}})_{{index}}"),
|
|
1385
|
+
sendMethod: import_koishi2.Schema.union(["zip", "pdf"]).default("pdf")
|
|
1071
1386
|
}),
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
sendMethod:
|
|
1075
|
-
level:
|
|
1387
|
+
import_koishi2.Schema.union([
|
|
1388
|
+
import_koishi2.Schema.object({
|
|
1389
|
+
sendMethod: import_koishi2.Schema.const("zip").required(),
|
|
1390
|
+
level: import_koishi2.Schema.number().min(0).max(9).default(6).role("slider")
|
|
1076
1391
|
}),
|
|
1077
|
-
|
|
1392
|
+
import_koishi2.Schema.object({})
|
|
1078
1393
|
]),
|
|
1079
|
-
|
|
1080
|
-
|
|
1394
|
+
import_koishi2.Schema.object({
|
|
1395
|
+
retryCount: import_koishi2.Schema.number().min(1).max(5).default(5),
|
|
1396
|
+
concurrentDownloadLimit: import_koishi2.Schema.number().min(0).max(20).default(10),
|
|
1397
|
+
concurrentDecodeLimit: import_koishi2.Schema.number().min(0).max(20).default(5),
|
|
1398
|
+
concurrentQueueLimit: import_koishi2.Schema.number().min(0).max(10).default(1)
|
|
1399
|
+
}),
|
|
1400
|
+
import_koishi2.Schema.object({
|
|
1401
|
+
cache: import_koishi2.Schema.boolean().default(false)
|
|
1081
1402
|
}),
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
cache:
|
|
1085
|
-
autoDelete:
|
|
1403
|
+
import_koishi2.Schema.union([
|
|
1404
|
+
import_koishi2.Schema.object({
|
|
1405
|
+
cache: import_koishi2.Schema.const(true).required(),
|
|
1406
|
+
autoDelete: import_koishi2.Schema.boolean().default(false)
|
|
1086
1407
|
}),
|
|
1087
|
-
|
|
1408
|
+
import_koishi2.Schema.object({})
|
|
1088
1409
|
]),
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
cache:
|
|
1092
|
-
autoDelete:
|
|
1093
|
-
cron:
|
|
1094
|
-
deleteInStart:
|
|
1095
|
-
keepDays:
|
|
1410
|
+
import_koishi2.Schema.union([
|
|
1411
|
+
import_koishi2.Schema.object({
|
|
1412
|
+
cache: import_koishi2.Schema.const(true).required(),
|
|
1413
|
+
autoDelete: import_koishi2.Schema.const(true).required(),
|
|
1414
|
+
cron: import_koishi2.Schema.string().default("0 0 * * *"),
|
|
1415
|
+
deleteInStart: import_koishi2.Schema.boolean().default(false),
|
|
1416
|
+
keepDays: import_koishi2.Schema.number().min(1).default(7)
|
|
1096
1417
|
}),
|
|
1097
|
-
|
|
1418
|
+
import_koishi2.Schema.object({})
|
|
1098
1419
|
]),
|
|
1099
|
-
|
|
1100
|
-
debug:
|
|
1420
|
+
import_koishi2.Schema.object({
|
|
1421
|
+
debug: import_koishi2.Schema.boolean().default(false)
|
|
1101
1422
|
})
|
|
1102
1423
|
]).i18n({
|
|
1103
1424
|
"zh-CN": require_zh_CN()._config,
|
|
@@ -1107,17 +1428,10 @@ var inject = {
|
|
|
1107
1428
|
required: ["http"],
|
|
1108
1429
|
optional: ["notifier", "cron"]
|
|
1109
1430
|
};
|
|
1110
|
-
var http;
|
|
1111
|
-
var logger;
|
|
1112
|
-
var retryCount;
|
|
1113
|
-
var debug;
|
|
1114
1431
|
async function apply(ctx, config) {
|
|
1115
|
-
http = ctx.http;
|
|
1116
|
-
retryCount = config.retryCount;
|
|
1117
|
-
debug = config.debug;
|
|
1118
1432
|
ctx.i18n.define("en-US", require_en_US());
|
|
1119
1433
|
ctx.i18n.define("zh-CN", require_zh_CN());
|
|
1120
|
-
logger = ctx.logger("jmcomic");
|
|
1434
|
+
const logger = ctx.logger("jmcomic");
|
|
1121
1435
|
const root = (0, import_path3.join)(ctx.baseDir, "data", "jmcomic");
|
|
1122
1436
|
const scheduleFn = /* @__PURE__ */ __name(async () => {
|
|
1123
1437
|
const albumPath = (0, import_path3.join)(ctx.baseDir, "data", "jmcomic", "album");
|
|
@@ -1135,194 +1449,148 @@ async function apply(ctx, config) {
|
|
|
1135
1449
|
content: "据JMComic-Crawler-Python源码可知JM图片还有gif形式,目前尚未支持"
|
|
1136
1450
|
});
|
|
1137
1451
|
}
|
|
1138
|
-
|
|
1452
|
+
const processorConfig = {
|
|
1453
|
+
root,
|
|
1454
|
+
sendMethod: config.sendMethod,
|
|
1455
|
+
password: config.password,
|
|
1456
|
+
level: config.level,
|
|
1457
|
+
fileName: config.fileName,
|
|
1458
|
+
fileMethod: config.fileMethod,
|
|
1459
|
+
cache: config.cache,
|
|
1460
|
+
debug: config.debug || false
|
|
1461
|
+
};
|
|
1462
|
+
const jmProcessor = createJmProcessor(
|
|
1463
|
+
processorConfig,
|
|
1464
|
+
ctx.http,
|
|
1465
|
+
config,
|
|
1466
|
+
logger
|
|
1467
|
+
);
|
|
1468
|
+
const queue = new Queue(
|
|
1469
|
+
jmProcessor,
|
|
1470
|
+
{ concurrency: config.concurrentQueueLimit || 1 },
|
|
1471
|
+
config,
|
|
1472
|
+
logger
|
|
1473
|
+
);
|
|
1474
|
+
const handleAlbumOrPhoto = /* @__PURE__ */ __name(async (session, id) => {
|
|
1139
1475
|
const messageId = session.messageId;
|
|
1140
|
-
if (!/^\d+$/.test(
|
|
1476
|
+
if (!/^\d+$/.test(id)) {
|
|
1141
1477
|
await session.send([
|
|
1142
|
-
|
|
1143
|
-
|
|
1478
|
+
import_koishi2.h.quote(messageId),
|
|
1479
|
+
import_koishi2.h.text("输入的ID不合法,请检查")
|
|
1144
1480
|
]);
|
|
1145
1481
|
return;
|
|
1146
1482
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
const buffer = await (0, import_promises3.readFile)(filePath);
|
|
1167
|
-
await session.send([
|
|
1168
|
-
import_koishi.h.file(buffer, ext, { title: `${name2}.${ext}` })
|
|
1169
|
-
]);
|
|
1170
|
-
} else {
|
|
1171
|
-
await session.send([
|
|
1172
|
-
import_koishi.h.file(`file:///${filePath}`, { title: `${name2}.${ext}` })
|
|
1173
|
-
]);
|
|
1174
|
-
}
|
|
1175
|
-
if (!config.cache) (0, import_promises3.rm)(dir, { recursive: true });
|
|
1176
|
-
} else {
|
|
1177
|
-
let fileDir;
|
|
1178
|
-
for (const [index, p] of filePath.entries()) {
|
|
1179
|
-
const { fileName, ext, dir } = getFileInfo(p);
|
|
1180
|
-
const name2 = formatFileName(
|
|
1181
|
-
config.fileName,
|
|
1182
|
-
fileName,
|
|
1183
|
-
albumId,
|
|
1184
|
-
index + 1
|
|
1185
|
-
);
|
|
1186
|
-
if (debug) logger.info(`文件名:${name2}.${ext}`);
|
|
1187
|
-
if (config.fileMethod === "buffer") {
|
|
1188
|
-
const buffer = await (0, import_promises3.readFile)(p);
|
|
1189
|
-
await session.send([
|
|
1190
|
-
import_koishi.h.file(buffer, ext, { title: `${name2}.${ext}` })
|
|
1191
|
-
]);
|
|
1192
|
-
} else {
|
|
1193
|
-
await session.send([
|
|
1194
|
-
import_koishi.h.file(`file:///${p}`, { title: `${name2}.${ext}` })
|
|
1195
|
-
]);
|
|
1196
|
-
}
|
|
1197
|
-
fileDir = dir;
|
|
1198
|
-
}
|
|
1199
|
-
if (!config.cache) (0, import_promises3.rm)(fileDir, { recursive: true });
|
|
1200
|
-
}
|
|
1201
|
-
} catch (error) {
|
|
1202
|
-
if (error instanceof AlbumNotExistError) {
|
|
1203
|
-
await session.send([
|
|
1204
|
-
import_koishi.h.quote(messageId),
|
|
1205
|
-
import_koishi.h.text(session.text(".notExistError"))
|
|
1206
|
-
]);
|
|
1207
|
-
} else if (error instanceof MySqlError) {
|
|
1208
|
-
await session.send([
|
|
1209
|
-
import_koishi.h.quote(messageId),
|
|
1210
|
-
import_koishi.h.text(session.text(".mysqlError"))
|
|
1211
|
-
]);
|
|
1212
|
-
} else {
|
|
1213
|
-
throw new Error(error);
|
|
1214
|
-
}
|
|
1483
|
+
const { task, pendingAhead, queuePosition } = queue.add({
|
|
1484
|
+
type: "album",
|
|
1485
|
+
id,
|
|
1486
|
+
session,
|
|
1487
|
+
messageId
|
|
1488
|
+
});
|
|
1489
|
+
const params = {
|
|
1490
|
+
id,
|
|
1491
|
+
ahead: pendingAhead,
|
|
1492
|
+
pos: queuePosition,
|
|
1493
|
+
status: task.status
|
|
1494
|
+
};
|
|
1495
|
+
const msg = [import_koishi2.h.quote(messageId)];
|
|
1496
|
+
if (pendingAhead === 0 && queuePosition === 1) {
|
|
1497
|
+
msg.push(import_koishi2.h.text(session.text(".queueFirst", params)));
|
|
1498
|
+
} else if (pendingAhead > 0) {
|
|
1499
|
+
msg.push(import_koishi2.h.text(session.text(".queuePosition", params)));
|
|
1500
|
+
} else {
|
|
1501
|
+
msg.push(import_koishi2.h.text(session.text(".queueProcessing", params)));
|
|
1215
1502
|
}
|
|
1503
|
+
await session.send(msg);
|
|
1504
|
+
}, "handleAlbumOrPhoto");
|
|
1505
|
+
ctx.command("jm.album <albumId:string>").alias("本子").action(async ({ session, options }, albumId) => {
|
|
1506
|
+
await handleAlbumOrPhoto(session, albumId);
|
|
1216
1507
|
});
|
|
1217
1508
|
ctx.command("jm.photo <photoId:string>").alias("本子章节").action(async ({ session }, photoId) => {
|
|
1218
|
-
|
|
1219
|
-
if (!/^\d+$/.test(photoId)) {
|
|
1220
|
-
await session.send([
|
|
1221
|
-
import_koishi.h.quote(messageId),
|
|
1222
|
-
import_koishi.h.text("输入的ID不合法,请检查")
|
|
1223
|
-
]);
|
|
1224
|
-
return;
|
|
1225
|
-
}
|
|
1226
|
-
try {
|
|
1227
|
-
const jmClient = new JMAppClient(root);
|
|
1228
|
-
const photo = await jmClient.getPhotoById(photoId);
|
|
1229
|
-
await jmClient.downloadByPhoto(photo);
|
|
1230
|
-
const photoName = photo.getName();
|
|
1231
|
-
let filePath;
|
|
1232
|
-
if (config.sendMethod === "zip") {
|
|
1233
|
-
filePath = await jmClient.photoToZip(
|
|
1234
|
-
photo,
|
|
1235
|
-
photoName,
|
|
1236
|
-
config.password,
|
|
1237
|
-
config.level
|
|
1238
|
-
);
|
|
1239
|
-
} else {
|
|
1240
|
-
filePath = await jmClient.photoToPdf(photo, photoName);
|
|
1241
|
-
}
|
|
1242
|
-
const { fileName, ext, dir } = getFileInfo(filePath);
|
|
1243
|
-
const name2 = formatFileName(config.fileName, fileName, photoId);
|
|
1244
|
-
if (debug) logger.info(`文件名:${name2}.${ext}`);
|
|
1245
|
-
if (config.fileMethod === "buffer") {
|
|
1246
|
-
const buffer = await (0, import_promises3.readFile)(filePath);
|
|
1247
|
-
await session.send([
|
|
1248
|
-
import_koishi.h.file(buffer, ext, { title: `${name2} (${photoId}).${ext}` })
|
|
1249
|
-
]);
|
|
1250
|
-
} else {
|
|
1251
|
-
await session.send([
|
|
1252
|
-
import_koishi.h.file(`file:///${filePath}`, { title: `${name2}.${ext}` })
|
|
1253
|
-
]);
|
|
1254
|
-
}
|
|
1255
|
-
if (!config.cache) (0, import_promises3.rm)(dir, { recursive: true });
|
|
1256
|
-
} catch (error) {
|
|
1257
|
-
if (error instanceof PhotoNotExistError) {
|
|
1258
|
-
await session.send([
|
|
1259
|
-
import_koishi.h.quote(messageId),
|
|
1260
|
-
import_koishi.h.text(session.text(".notExistError"))
|
|
1261
|
-
]);
|
|
1262
|
-
} else if (error instanceof MySqlError) {
|
|
1263
|
-
await session.send([
|
|
1264
|
-
import_koishi.h.quote(messageId),
|
|
1265
|
-
import_koishi.h.text(session.text(".mysqlError"))
|
|
1266
|
-
]);
|
|
1267
|
-
} else {
|
|
1268
|
-
throw new Error(error);
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1509
|
+
await handleAlbumOrPhoto(session, photoId);
|
|
1271
1510
|
});
|
|
1272
1511
|
ctx.command("jm.album.info <albumId:string>").alias("本子信息").action(async ({ session, options }, albumId) => {
|
|
1273
1512
|
const messageId = session.messageId;
|
|
1274
1513
|
if (!/^\d+$/.test(albumId)) {
|
|
1275
1514
|
await session.send([
|
|
1276
|
-
|
|
1277
|
-
|
|
1515
|
+
import_koishi2.h.quote(messageId),
|
|
1516
|
+
import_koishi2.h.text("输入的ID不合法,请检查")
|
|
1278
1517
|
]);
|
|
1279
1518
|
return;
|
|
1280
1519
|
}
|
|
1281
1520
|
try {
|
|
1282
|
-
const jmClient = new JMAppClient(root);
|
|
1521
|
+
const jmClient = new JMAppClient(root, ctx.http, config, logger);
|
|
1283
1522
|
const album = await jmClient.getAlbumById(albumId);
|
|
1284
1523
|
await session.send([
|
|
1285
|
-
|
|
1286
|
-
|
|
1524
|
+
import_koishi2.h.quote(messageId),
|
|
1525
|
+
import_koishi2.h.text(`ID:${album.getId()}
|
|
1287
1526
|
`),
|
|
1288
|
-
|
|
1527
|
+
import_koishi2.h.text(`名称:${album.getName()}
|
|
1289
1528
|
`),
|
|
1290
|
-
|
|
1529
|
+
import_koishi2.h.text(`章节数:${album.getPhotos().length}
|
|
1291
1530
|
`),
|
|
1292
|
-
|
|
1531
|
+
import_koishi2.h.text(`作者:${album.getAuthors()?.join("、") ?? ""}
|
|
1293
1532
|
`),
|
|
1294
|
-
|
|
1533
|
+
import_koishi2.h.text(`登场人物:${album.getActors()?.join("、") ?? ""}
|
|
1295
1534
|
`),
|
|
1296
|
-
|
|
1535
|
+
import_koishi2.h.text(`点赞数:${album.getLikes()}
|
|
1297
1536
|
`),
|
|
1298
|
-
|
|
1537
|
+
import_koishi2.h.text(`观看数:${album.getTotalViews()}`)
|
|
1299
1538
|
]);
|
|
1300
1539
|
} catch (error) {
|
|
1301
1540
|
if (error instanceof AlbumNotExistError) {
|
|
1302
1541
|
await session.send([
|
|
1303
|
-
|
|
1304
|
-
|
|
1542
|
+
import_koishi2.h.quote(messageId),
|
|
1543
|
+
import_koishi2.h.text(session.text(".notExistError"))
|
|
1305
1544
|
]);
|
|
1306
1545
|
} else if (error instanceof MySqlError) {
|
|
1307
1546
|
await session.send([
|
|
1308
|
-
|
|
1309
|
-
|
|
1547
|
+
import_koishi2.h.quote(messageId),
|
|
1548
|
+
import_koishi2.h.text(session.text(".mysqlError"))
|
|
1310
1549
|
]);
|
|
1311
1550
|
} else {
|
|
1312
1551
|
throw new Error(error);
|
|
1313
1552
|
}
|
|
1314
1553
|
}
|
|
1315
1554
|
});
|
|
1555
|
+
ctx.command("jm.queue").alias("本子队列").action(async ({ session, options }) => {
|
|
1556
|
+
const messageId = session.messageId;
|
|
1557
|
+
const allTasks = queue.getAllTasks();
|
|
1558
|
+
const pendingOrProcessingTasks = allTasks.filter(
|
|
1559
|
+
(task) => task.status !== "completed"
|
|
1560
|
+
);
|
|
1561
|
+
if (pendingOrProcessingTasks.length === 0) {
|
|
1562
|
+
await session.send([
|
|
1563
|
+
import_koishi2.h.quote(messageId),
|
|
1564
|
+
import_koishi2.h.text(session.text(".emptyQueue"))
|
|
1565
|
+
]);
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
const statusMap = {
|
|
1569
|
+
pending: session.text(".task.pending"),
|
|
1570
|
+
processing: session.text(".task.processing"),
|
|
1571
|
+
failed: session.text(".task.failed")
|
|
1572
|
+
};
|
|
1573
|
+
const typeMap = {
|
|
1574
|
+
album: session.text(".type.album"),
|
|
1575
|
+
photo: session.text(".type.photo")
|
|
1576
|
+
};
|
|
1577
|
+
const taskInfos = pendingOrProcessingTasks.map((task) => {
|
|
1578
|
+
return import_koishi2.h.text(
|
|
1579
|
+
session.text(".msgFormat", {
|
|
1580
|
+
id: task.payload.id,
|
|
1581
|
+
type: typeMap[task.payload.type],
|
|
1582
|
+
status: statusMap[task.status]
|
|
1583
|
+
})
|
|
1584
|
+
);
|
|
1585
|
+
});
|
|
1586
|
+
await session.send([import_koishi2.h.quote(messageId), ...taskInfos]);
|
|
1587
|
+
});
|
|
1316
1588
|
}
|
|
1317
1589
|
__name(apply, "apply");
|
|
1318
1590
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1319
1591
|
0 && (module.exports = {
|
|
1320
1592
|
Config,
|
|
1321
1593
|
apply,
|
|
1322
|
-
debug,
|
|
1323
|
-
http,
|
|
1324
1594
|
inject,
|
|
1325
|
-
|
|
1326
|
-
name,
|
|
1327
|
-
retryCount
|
|
1595
|
+
name
|
|
1328
1596
|
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Config } from "..";
|
|
2
|
+
import { Logger, Session, HTTP } from "koishi";
|
|
3
|
+
export type JmTaskPayload = {
|
|
4
|
+
type: "album" | "photo";
|
|
5
|
+
id: string;
|
|
6
|
+
session: Session;
|
|
7
|
+
messageId: string;
|
|
8
|
+
};
|
|
9
|
+
interface ProcessorConfig {
|
|
10
|
+
root: string;
|
|
11
|
+
sendMethod: "zip" | "pdf";
|
|
12
|
+
password?: string;
|
|
13
|
+
level?: number;
|
|
14
|
+
fileName: string;
|
|
15
|
+
fileMethod: "buffer" | "file";
|
|
16
|
+
cache: boolean;
|
|
17
|
+
debug: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare const createJmProcessor: (processorConfig: ProcessorConfig, http: HTTP, config: Config, logger: Logger) => (payload: JmTaskPayload) => Promise<void>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Config } from "..";
|
|
2
|
+
import { Logger } from "koishi";
|
|
3
|
+
import { JmTaskPayload } from "../processors/jmProcessor";
|
|
4
|
+
/**
|
|
5
|
+
* 任务可能的状态
|
|
6
|
+
* pending: 等待处理
|
|
7
|
+
* processing: 正在处理
|
|
8
|
+
* completed: 处理完成
|
|
9
|
+
* failed: 处理失败
|
|
10
|
+
*/
|
|
11
|
+
type TaskStatus = "pending" | "processing" | "completed" | "failed";
|
|
12
|
+
interface Task {
|
|
13
|
+
id: string;
|
|
14
|
+
payload: JmTaskPayload;
|
|
15
|
+
status: TaskStatus;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
processedAt?: string;
|
|
18
|
+
error?: string;
|
|
19
|
+
}
|
|
20
|
+
type TaskProcessor = (payload: JmTaskPayload) => Promise<void>;
|
|
21
|
+
interface QueueOptions {
|
|
22
|
+
concurrency?: number;
|
|
23
|
+
}
|
|
24
|
+
interface AddTaskResult {
|
|
25
|
+
task: Task;
|
|
26
|
+
pendingAhead: number;
|
|
27
|
+
queuePosition: number;
|
|
28
|
+
}
|
|
29
|
+
export declare class Queue {
|
|
30
|
+
private tasks;
|
|
31
|
+
private processor;
|
|
32
|
+
private readonly concurrency;
|
|
33
|
+
private activeTasks;
|
|
34
|
+
/**
|
|
35
|
+
* koishi 配置项
|
|
36
|
+
*/
|
|
37
|
+
private config;
|
|
38
|
+
/**
|
|
39
|
+
* koishi 日志
|
|
40
|
+
*/
|
|
41
|
+
private logger;
|
|
42
|
+
constructor(processor: TaskProcessor, options: QueueOptions, config: Config, logger: Logger);
|
|
43
|
+
/**
|
|
44
|
+
* 向队列添加一个新任务
|
|
45
|
+
*/
|
|
46
|
+
add(payload: JmTaskPayload): AddTaskResult;
|
|
47
|
+
/**
|
|
48
|
+
* 获取任务状态
|
|
49
|
+
*/
|
|
50
|
+
getTask(id: string): Task | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* 获取所有任务的只读列表
|
|
53
|
+
*/
|
|
54
|
+
getAllTasks(): Readonly<Task[]>;
|
|
55
|
+
/**
|
|
56
|
+
* 获取指定任务在队列中的位置信息
|
|
57
|
+
* @param taskId 任务ID
|
|
58
|
+
* @returns {pendingAhead: number, queuePosition: number}
|
|
59
|
+
* pendingAhead: 在此任务之前有多少个待处理任务
|
|
60
|
+
* queuePosition: 此任务在所有待处理任务中的位置(从1开始)
|
|
61
|
+
*/
|
|
62
|
+
getTaskQueuePosition(taskId: string): {
|
|
63
|
+
pendingAhead: number;
|
|
64
|
+
queuePosition: number;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* 检查并处理队列中的任务
|
|
68
|
+
*/
|
|
69
|
+
private _processQueue;
|
|
70
|
+
/**
|
|
71
|
+
* 执行单个任务
|
|
72
|
+
*/
|
|
73
|
+
private _runTask;
|
|
74
|
+
}
|
|
75
|
+
export {};
|
package/lib/utils/Utils.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Config } from "..";
|
|
2
|
+
import { HTTP, Logger } from "koishi";
|
|
2
3
|
import { IJMResponse } from "../types/JMClient";
|
|
3
4
|
/**
|
|
4
5
|
* 文件是否存在
|
|
@@ -22,7 +23,7 @@ export declare function sanitizeFileName(fileName: string): string;
|
|
|
22
23
|
* 从github获取最新的JM域名
|
|
23
24
|
* @returns 域名列表
|
|
24
25
|
*/
|
|
25
|
-
export declare function getDomainFromGithub(): Promise<any[]>;
|
|
26
|
+
export declare function getDomainFromGithub(http: HTTP): Promise<any[]>;
|
|
26
27
|
/**
|
|
27
28
|
* 限制Promise并发
|
|
28
29
|
* @param promises Promise方法列表
|
|
@@ -38,7 +39,7 @@ export declare function limitPromiseAll<T>(promises: (() => Promise<T>)[], limit
|
|
|
38
39
|
* @param retryIndex 尝试次数
|
|
39
40
|
* @returns 请求结果
|
|
40
41
|
*/
|
|
41
|
-
export declare function requestWithRetry<T = IJMResponse>(url: string, method: "GET" | "POST", config
|
|
42
|
+
export declare function requestWithRetry<T = IJMResponse>(url: string, method: "GET" | "POST", config: HTTP.RequestConfig, http: HTTP, pluginsConfig: Config, logger: Logger, retryIndex?: number): Promise<T>;
|
|
42
43
|
/**
|
|
43
44
|
* 依次使用定义的地址尝试进行请求,遇到特定错误尝试切换下一个
|
|
44
45
|
* @param url 请求地址
|
|
@@ -47,7 +48,7 @@ export declare function requestWithRetry<T = IJMResponse>(url: string, method: "
|
|
|
47
48
|
* @param urlIndex 地址下标,默认从0开始尝试
|
|
48
49
|
* @returns 请求结果
|
|
49
50
|
*/
|
|
50
|
-
export declare function requestWithUrlSwitch<T = IJMResponse>(url: string, method: "GET" | "POST", config
|
|
51
|
+
export declare function requestWithUrlSwitch<T = IJMResponse>(url: string, method: "GET" | "POST", config: HTTP.RequestConfig, http: HTTP, pluginsConfig: Config, logger: Logger, type?: "IMAGE" | "CLIENT", urlIndex?: number): Promise<T>;
|
|
51
52
|
/**
|
|
52
53
|
* 获取文件名和扩展名
|
|
53
54
|
* @param filePath 文件路径
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -10,12 +10,33 @@
|
|
|
10
10
|
jm album xxxxxx
|
|
11
11
|
jm album info xxxxxx
|
|
12
12
|
jm photo xxxxxx
|
|
13
|
+
jm queue
|
|
13
14
|
```
|
|
14
15
|
|
|
15
16
|
可在配置中配置是发送 PDF 还是 ZIP 压缩包,支持加密。
|
|
16
17
|
|
|
17
18
|
## 更新日志
|
|
18
19
|
|
|
20
|
+
<details>
|
|
21
|
+
<summary>0.1.1</summary>
|
|
22
|
+
|
|
23
|
+
1. 添加队列时返回队列信息
|
|
24
|
+
2. 提取代码
|
|
25
|
+
|
|
26
|
+
</details>
|
|
27
|
+
|
|
28
|
+
<details>
|
|
29
|
+
<summary>0.1.0</summary>
|
|
30
|
+
|
|
31
|
+
1. 队列系统
|
|
32
|
+
2. 下载并发与解密并发限制
|
|
33
|
+
3. 修改配置页面顺序、分类
|
|
34
|
+
4. 不再直接暴露变量,改为逐级传递
|
|
35
|
+
5. 统一暴露Error类
|
|
36
|
+
6. 添加域名切换条件
|
|
37
|
+
|
|
38
|
+
</details>
|
|
39
|
+
|
|
19
40
|
<details>
|
|
20
41
|
<summary>0.0.6</summary>
|
|
21
42
|
添加了一些错误提示
|
|
@@ -28,9 +49,9 @@ jm photo xxxxxx
|
|
|
28
49
|
|
|
29
50
|
<details>
|
|
30
51
|
<summary>0.0.4</summary>
|
|
31
|
-
1.文件名移除前后空格
|
|
32
52
|
|
|
33
|
-
|
|
53
|
+
1. 文件名移除前后空格
|
|
54
|
+
2. 新增文件发送配置,用于配置文件是以 buffer 读取后发送还是以本地地址的形式发送。docker 中使用 file 形式需要在 bot 实现端同时映射/koishi 目录
|
|
34
55
|
|
|
35
56
|
</details>
|
|
36
57
|
|