@wahaha216/koishi-plugin-jmcomic 0.0.6 → 0.1.0
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 +455 -222
- package/lib/processors/jmProcessor.d.ts +20 -0
- package/lib/utils/Queue.d.ts +59 -0
- package/lib/utils/Utils.d.ts +5 -4
- package/package.json +1 -1
- package/readme.md +16 -0
|
@@ -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} 添加到处理队列,请稍候。", notExistError: "找不到该ID对应的本子", mysqlError: "已尝试所有备用地址,但是JM坏掉了" }, info: { examples: "jm album info 本子数字ID", messages: { notExistError: "找不到该ID对应的本子", mysqlError: "已尝试所有备用地址,但是JM坏掉了" } } }, photo: { examples: "jm photo 本子章节数字ID", messages: { addedToQueue: "已将 {id} 添加到处理队列,请稍候。", 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.", 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.", 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,289 @@ 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
|
+
setTimeout(() => this._processQueue(), 0);
|
|
1291
|
+
return task;
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* 获取任务状态
|
|
1295
|
+
*/
|
|
1296
|
+
getTask(id) {
|
|
1297
|
+
return this.tasks.find((t) => t.id === id);
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* 获取所有任务的只读列表
|
|
1301
|
+
*/
|
|
1302
|
+
getAllTasks() {
|
|
1303
|
+
return this.tasks;
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* 检查并处理队列中的任务
|
|
1307
|
+
*/
|
|
1308
|
+
_processQueue() {
|
|
1309
|
+
while (this.activeTasks < this.concurrency) {
|
|
1310
|
+
const task = this.tasks.find((t) => t.status === "pending");
|
|
1311
|
+
if (!task) {
|
|
1312
|
+
break;
|
|
1313
|
+
}
|
|
1314
|
+
this.activeTasks++;
|
|
1315
|
+
task.status = "processing";
|
|
1316
|
+
if (this.config.debug)
|
|
1317
|
+
this.logger.info(
|
|
1318
|
+
`[任务开始] 任务ID: ${task.id} 类型: ${task.payload.type} ID: ${task.payload.id}`
|
|
1319
|
+
);
|
|
1320
|
+
this._runTask(task);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* 执行单个任务
|
|
1325
|
+
*/
|
|
1326
|
+
async _runTask(task) {
|
|
1327
|
+
try {
|
|
1328
|
+
await this.processor(task.payload);
|
|
1329
|
+
task.status = "completed";
|
|
1330
|
+
if (this.config.debug)
|
|
1331
|
+
this.logger.info(
|
|
1332
|
+
`[任务成功] 任务ID: ${task.id} 类型: ${task.payload.type} ID: ${task.payload.id}`
|
|
1333
|
+
);
|
|
1334
|
+
} catch (error) {
|
|
1335
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1336
|
+
if (this.config.debug)
|
|
1337
|
+
this.logger.error(
|
|
1338
|
+
`[任务失败] 任务ID: ${task.id} 类型: ${task.payload.type} ID: ${task.payload.id}, 错误: ${errorMessage}`
|
|
1339
|
+
);
|
|
1340
|
+
task.status = "failed";
|
|
1341
|
+
task.error = errorMessage;
|
|
1342
|
+
} finally {
|
|
1343
|
+
task.processedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1344
|
+
this.activeTasks--;
|
|
1345
|
+
this._processQueue();
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
1349
|
+
|
|
1062
1350
|
// src/index.ts
|
|
1063
1351
|
var name = "jmcomic";
|
|
1064
|
-
var Config =
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
fileName: import_koishi.Schema.string().default("{{name}} ({{id}})_{{index}}")
|
|
1352
|
+
var Config = import_koishi2.Schema.intersect([
|
|
1353
|
+
import_koishi2.Schema.object({
|
|
1354
|
+
fileMethod: import_koishi2.Schema.union(["buffer", "file"]).default("buffer"),
|
|
1355
|
+
password: import_koishi2.Schema.string(),
|
|
1356
|
+
fileName: import_koishi2.Schema.string().default("{{name}} ({{id}})_{{index}}"),
|
|
1357
|
+
sendMethod: import_koishi2.Schema.union(["zip", "pdf"]).default("pdf")
|
|
1071
1358
|
}),
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
sendMethod:
|
|
1075
|
-
level:
|
|
1359
|
+
import_koishi2.Schema.union([
|
|
1360
|
+
import_koishi2.Schema.object({
|
|
1361
|
+
sendMethod: import_koishi2.Schema.const("zip").required(),
|
|
1362
|
+
level: import_koishi2.Schema.number().min(0).max(9).default(6).role("slider")
|
|
1076
1363
|
}),
|
|
1077
|
-
|
|
1364
|
+
import_koishi2.Schema.object({})
|
|
1078
1365
|
]),
|
|
1079
|
-
|
|
1080
|
-
|
|
1366
|
+
import_koishi2.Schema.object({
|
|
1367
|
+
retryCount: import_koishi2.Schema.number().min(1).max(5).default(5),
|
|
1368
|
+
concurrentDownloadLimit: import_koishi2.Schema.number().min(0).max(20).default(10),
|
|
1369
|
+
concurrentDecodeLimit: import_koishi2.Schema.number().min(0).max(20).default(5),
|
|
1370
|
+
concurrentQueueLimit: import_koishi2.Schema.number().min(0).max(10).default(1)
|
|
1371
|
+
}),
|
|
1372
|
+
import_koishi2.Schema.object({
|
|
1373
|
+
cache: import_koishi2.Schema.boolean().default(false)
|
|
1081
1374
|
}),
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
cache:
|
|
1085
|
-
autoDelete:
|
|
1375
|
+
import_koishi2.Schema.union([
|
|
1376
|
+
import_koishi2.Schema.object({
|
|
1377
|
+
cache: import_koishi2.Schema.const(true).required(),
|
|
1378
|
+
autoDelete: import_koishi2.Schema.boolean().default(false)
|
|
1086
1379
|
}),
|
|
1087
|
-
|
|
1380
|
+
import_koishi2.Schema.object({})
|
|
1088
1381
|
]),
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
cache:
|
|
1092
|
-
autoDelete:
|
|
1093
|
-
cron:
|
|
1094
|
-
deleteInStart:
|
|
1095
|
-
keepDays:
|
|
1382
|
+
import_koishi2.Schema.union([
|
|
1383
|
+
import_koishi2.Schema.object({
|
|
1384
|
+
cache: import_koishi2.Schema.const(true).required(),
|
|
1385
|
+
autoDelete: import_koishi2.Schema.const(true).required(),
|
|
1386
|
+
cron: import_koishi2.Schema.string().default("0 0 * * *"),
|
|
1387
|
+
deleteInStart: import_koishi2.Schema.boolean().default(false),
|
|
1388
|
+
keepDays: import_koishi2.Schema.number().min(1).default(7)
|
|
1096
1389
|
}),
|
|
1097
|
-
|
|
1390
|
+
import_koishi2.Schema.object({})
|
|
1098
1391
|
]),
|
|
1099
|
-
|
|
1100
|
-
debug:
|
|
1392
|
+
import_koishi2.Schema.object({
|
|
1393
|
+
debug: import_koishi2.Schema.boolean().default(false)
|
|
1101
1394
|
})
|
|
1102
1395
|
]).i18n({
|
|
1103
1396
|
"zh-CN": require_zh_CN()._config,
|
|
@@ -1107,17 +1400,10 @@ var inject = {
|
|
|
1107
1400
|
required: ["http"],
|
|
1108
1401
|
optional: ["notifier", "cron"]
|
|
1109
1402
|
};
|
|
1110
|
-
var http;
|
|
1111
|
-
var logger;
|
|
1112
|
-
var retryCount;
|
|
1113
|
-
var debug;
|
|
1114
1403
|
async function apply(ctx, config) {
|
|
1115
|
-
http = ctx.http;
|
|
1116
|
-
retryCount = config.retryCount;
|
|
1117
|
-
debug = config.debug;
|
|
1118
1404
|
ctx.i18n.define("en-US", require_en_US());
|
|
1119
1405
|
ctx.i18n.define("zh-CN", require_zh_CN());
|
|
1120
|
-
logger = ctx.logger("jmcomic");
|
|
1406
|
+
const logger = ctx.logger("jmcomic");
|
|
1121
1407
|
const root = (0, import_path3.join)(ctx.baseDir, "data", "jmcomic");
|
|
1122
1408
|
const scheduleFn = /* @__PURE__ */ __name(async () => {
|
|
1123
1409
|
const albumPath = (0, import_path3.join)(ctx.baseDir, "data", "jmcomic", "album");
|
|
@@ -1135,194 +1421,141 @@ async function apply(ctx, config) {
|
|
|
1135
1421
|
content: "据JMComic-Crawler-Python源码可知JM图片还有gif形式,目前尚未支持"
|
|
1136
1422
|
});
|
|
1137
1423
|
}
|
|
1424
|
+
const processorConfig = {
|
|
1425
|
+
root,
|
|
1426
|
+
sendMethod: config.sendMethod,
|
|
1427
|
+
password: config.password,
|
|
1428
|
+
level: config.level,
|
|
1429
|
+
fileName: config.fileName,
|
|
1430
|
+
fileMethod: config.fileMethod,
|
|
1431
|
+
cache: config.cache,
|
|
1432
|
+
debug: config.debug || false
|
|
1433
|
+
};
|
|
1434
|
+
const jmProcessor = createJmProcessor(
|
|
1435
|
+
processorConfig,
|
|
1436
|
+
ctx.http,
|
|
1437
|
+
config,
|
|
1438
|
+
logger
|
|
1439
|
+
);
|
|
1440
|
+
const queue = new Queue(
|
|
1441
|
+
jmProcessor,
|
|
1442
|
+
{ concurrency: config.concurrentQueueLimit || 1 },
|
|
1443
|
+
config,
|
|
1444
|
+
logger
|
|
1445
|
+
);
|
|
1138
1446
|
ctx.command("jm.album <albumId:string>").alias("本子").action(async ({ session, options }, albumId) => {
|
|
1139
1447
|
const messageId = session.messageId;
|
|
1140
1448
|
if (!/^\d+$/.test(albumId)) {
|
|
1141
1449
|
await session.send([
|
|
1142
|
-
|
|
1143
|
-
|
|
1450
|
+
import_koishi2.h.quote(messageId),
|
|
1451
|
+
import_koishi2.h.text("输入的ID不合法,请检查")
|
|
1144
1452
|
]);
|
|
1145
1453
|
return;
|
|
1146
1454
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
if (config.sendMethod === "zip") {
|
|
1153
|
-
filePath = await jmClient.albumToZip(
|
|
1154
|
-
album,
|
|
1155
|
-
config.password,
|
|
1156
|
-
config.level
|
|
1157
|
-
);
|
|
1158
|
-
} else {
|
|
1159
|
-
filePath = await jmClient.albumToPdf(album, config.password);
|
|
1160
|
-
}
|
|
1161
|
-
if (typeof filePath === "string") {
|
|
1162
|
-
const { fileName, ext, dir } = getFileInfo(filePath);
|
|
1163
|
-
const name2 = formatFileName(config.fileName, fileName, albumId);
|
|
1164
|
-
if (debug) logger.info(`文件名:${name2}.${ext}`);
|
|
1165
|
-
if (config.fileMethod === "buffer") {
|
|
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
|
-
}
|
|
1215
|
-
}
|
|
1455
|
+
queue.add({ type: "album", id: albumId, session, messageId });
|
|
1456
|
+
await session.send([
|
|
1457
|
+
import_koishi2.h.quote(messageId),
|
|
1458
|
+
import_koishi2.h.text(session.text(".addedToQueue", { id: albumId }))
|
|
1459
|
+
]);
|
|
1216
1460
|
});
|
|
1217
1461
|
ctx.command("jm.photo <photoId:string>").alias("本子章节").action(async ({ session }, photoId) => {
|
|
1218
1462
|
const messageId = session.messageId;
|
|
1219
1463
|
if (!/^\d+$/.test(photoId)) {
|
|
1220
1464
|
await session.send([
|
|
1221
|
-
|
|
1222
|
-
|
|
1465
|
+
import_koishi2.h.quote(messageId),
|
|
1466
|
+
import_koishi2.h.text("输入的ID不合法,请检查")
|
|
1223
1467
|
]);
|
|
1224
1468
|
return;
|
|
1225
1469
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
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
|
-
}
|
|
1470
|
+
queue.add({ type: "album", id: photoId, session, messageId });
|
|
1471
|
+
await session.send([
|
|
1472
|
+
import_koishi2.h.quote(messageId),
|
|
1473
|
+
import_koishi2.h.text(session.text(".addedToQueue", { id: photoId }))
|
|
1474
|
+
]);
|
|
1271
1475
|
});
|
|
1272
1476
|
ctx.command("jm.album.info <albumId:string>").alias("本子信息").action(async ({ session, options }, albumId) => {
|
|
1273
1477
|
const messageId = session.messageId;
|
|
1274
1478
|
if (!/^\d+$/.test(albumId)) {
|
|
1275
1479
|
await session.send([
|
|
1276
|
-
|
|
1277
|
-
|
|
1480
|
+
import_koishi2.h.quote(messageId),
|
|
1481
|
+
import_koishi2.h.text("输入的ID不合法,请检查")
|
|
1278
1482
|
]);
|
|
1279
1483
|
return;
|
|
1280
1484
|
}
|
|
1281
1485
|
try {
|
|
1282
|
-
const jmClient = new JMAppClient(root);
|
|
1486
|
+
const jmClient = new JMAppClient(root, ctx.http, config, logger);
|
|
1283
1487
|
const album = await jmClient.getAlbumById(albumId);
|
|
1284
1488
|
await session.send([
|
|
1285
|
-
|
|
1286
|
-
|
|
1489
|
+
import_koishi2.h.quote(messageId),
|
|
1490
|
+
import_koishi2.h.text(`ID:${album.getId()}
|
|
1287
1491
|
`),
|
|
1288
|
-
|
|
1492
|
+
import_koishi2.h.text(`名称:${album.getName()}
|
|
1289
1493
|
`),
|
|
1290
|
-
|
|
1494
|
+
import_koishi2.h.text(`章节数:${album.getPhotos().length}
|
|
1291
1495
|
`),
|
|
1292
|
-
|
|
1496
|
+
import_koishi2.h.text(`作者:${album.getAuthors()?.join("、") ?? ""}
|
|
1293
1497
|
`),
|
|
1294
|
-
|
|
1498
|
+
import_koishi2.h.text(`登场人物:${album.getActors()?.join("、") ?? ""}
|
|
1295
1499
|
`),
|
|
1296
|
-
|
|
1500
|
+
import_koishi2.h.text(`点赞数:${album.getLikes()}
|
|
1297
1501
|
`),
|
|
1298
|
-
|
|
1502
|
+
import_koishi2.h.text(`观看数:${album.getTotalViews()}`)
|
|
1299
1503
|
]);
|
|
1300
1504
|
} catch (error) {
|
|
1301
1505
|
if (error instanceof AlbumNotExistError) {
|
|
1302
1506
|
await session.send([
|
|
1303
|
-
|
|
1304
|
-
|
|
1507
|
+
import_koishi2.h.quote(messageId),
|
|
1508
|
+
import_koishi2.h.text(session.text(".notExistError"))
|
|
1305
1509
|
]);
|
|
1306
1510
|
} else if (error instanceof MySqlError) {
|
|
1307
1511
|
await session.send([
|
|
1308
|
-
|
|
1309
|
-
|
|
1512
|
+
import_koishi2.h.quote(messageId),
|
|
1513
|
+
import_koishi2.h.text(session.text(".mysqlError"))
|
|
1310
1514
|
]);
|
|
1311
1515
|
} else {
|
|
1312
1516
|
throw new Error(error);
|
|
1313
1517
|
}
|
|
1314
1518
|
}
|
|
1315
1519
|
});
|
|
1520
|
+
ctx.command("jm.queue").alias("本子队列").action(async ({ session, options }) => {
|
|
1521
|
+
const messageId = session.messageId;
|
|
1522
|
+
const allTasks = queue.getAllTasks();
|
|
1523
|
+
const pendingOrProcessingTasks = allTasks.filter(
|
|
1524
|
+
(task) => task.status !== "completed"
|
|
1525
|
+
);
|
|
1526
|
+
if (pendingOrProcessingTasks.length === 0) {
|
|
1527
|
+
await session.send([
|
|
1528
|
+
import_koishi2.h.quote(messageId),
|
|
1529
|
+
import_koishi2.h.text(session.text(".emptyQueue"))
|
|
1530
|
+
]);
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
const statusMap = {
|
|
1534
|
+
pending: session.text(".task.pending"),
|
|
1535
|
+
processing: session.text(".task.processing"),
|
|
1536
|
+
failed: session.text(".task.failed")
|
|
1537
|
+
};
|
|
1538
|
+
const typeMap = {
|
|
1539
|
+
album: session.text(".type.album"),
|
|
1540
|
+
photo: session.text(".type.photo")
|
|
1541
|
+
};
|
|
1542
|
+
const taskInfos = pendingOrProcessingTasks.map((task) => {
|
|
1543
|
+
return import_koishi2.h.text(
|
|
1544
|
+
session.text(".msgFormat", {
|
|
1545
|
+
id: task.payload.id,
|
|
1546
|
+
type: typeMap[task.payload.type],
|
|
1547
|
+
status: statusMap[task.status]
|
|
1548
|
+
})
|
|
1549
|
+
);
|
|
1550
|
+
});
|
|
1551
|
+
await session.send([import_koishi2.h.quote(messageId), ...taskInfos]);
|
|
1552
|
+
});
|
|
1316
1553
|
}
|
|
1317
1554
|
__name(apply, "apply");
|
|
1318
1555
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1319
1556
|
0 && (module.exports = {
|
|
1320
1557
|
Config,
|
|
1321
1558
|
apply,
|
|
1322
|
-
debug,
|
|
1323
|
-
http,
|
|
1324
1559
|
inject,
|
|
1325
|
-
|
|
1326
|
-
name,
|
|
1327
|
-
retryCount
|
|
1560
|
+
name
|
|
1328
1561
|
});
|
|
@@ -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,59 @@
|
|
|
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
|
+
export declare class Queue {
|
|
25
|
+
private tasks;
|
|
26
|
+
private processor;
|
|
27
|
+
private readonly concurrency;
|
|
28
|
+
private activeTasks;
|
|
29
|
+
/**
|
|
30
|
+
* koishi 配置项
|
|
31
|
+
*/
|
|
32
|
+
private config;
|
|
33
|
+
/**
|
|
34
|
+
* koishi 日志
|
|
35
|
+
*/
|
|
36
|
+
private logger;
|
|
37
|
+
constructor(processor: TaskProcessor, options: QueueOptions, config: Config, logger: Logger);
|
|
38
|
+
/**
|
|
39
|
+
* 向队列添加一个新任务
|
|
40
|
+
*/
|
|
41
|
+
add(payload: JmTaskPayload): Task;
|
|
42
|
+
/**
|
|
43
|
+
* 获取任务状态
|
|
44
|
+
*/
|
|
45
|
+
getTask(id: string): Task | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* 获取所有任务的只读列表
|
|
48
|
+
*/
|
|
49
|
+
getAllTasks(): Readonly<Task[]>;
|
|
50
|
+
/**
|
|
51
|
+
* 检查并处理队列中的任务
|
|
52
|
+
*/
|
|
53
|
+
private _processQueue;
|
|
54
|
+
/**
|
|
55
|
+
* 执行单个任务
|
|
56
|
+
*/
|
|
57
|
+
private _runTask;
|
|
58
|
+
}
|
|
59
|
+
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
|
@@ -16,6 +16,22 @@ jm photo xxxxxx
|
|
|
16
16
|
|
|
17
17
|
## 更新日志
|
|
18
18
|
|
|
19
|
+
<details>
|
|
20
|
+
<summary>0.1.0</summary>
|
|
21
|
+
1.队列系统
|
|
22
|
+
|
|
23
|
+
2.下载并发与解密并发限制
|
|
24
|
+
|
|
25
|
+
3.修改配置页面顺序、分类
|
|
26
|
+
|
|
27
|
+
4.不再直接暴露变量,改为逐级传递
|
|
28
|
+
|
|
29
|
+
5.统一暴露Error类
|
|
30
|
+
|
|
31
|
+
6.添加域名切换条件
|
|
32
|
+
|
|
33
|
+
</details>
|
|
34
|
+
|
|
19
35
|
<details>
|
|
20
36
|
<summary>0.0.6</summary>
|
|
21
37
|
添加了一些错误提示
|