@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.
@@ -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
- constructor(root: string);
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
- constructor(root: string);
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, HTTP, Logger, Schema } from "koishi";
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坏掉了" } } } }, _config: [{ $desc: "基础设置", url: "JM域名", sendMethod: "发送方式", fileMethod: "文件获取方式<br>`buffer`: 读取成buffer后发送给bot实现端<br>`file`: 以`file:///` 本地路径形式发送,如docker环境,请在bot实现端同时映射/koishi目录", retryCount: "重试次数限制", password: "密码,留空则不加密", fileName: "文件名定义<br>`{{name}}`:标题<br>`{{id}}`:章节或者本子ID<br>`{{index}}`:多章节本子自动填充`1` 、 `2`" }, { level: "压缩级别,0~9,0为仅存储" }, { $desc: "缓存设置", cache: "缓存文件" }, { autoDelete: "自动删除缓存,**依赖cron服务**" }, { cron: "5位cron表达式", deleteInStart: "启动时检测", keepDays: "缓存保留时间(天)" }, { $desc: "开发者选项", debug: "调试模式,输出更多信息" }] };
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." } } } }, _config: [{ $desc: "Basic settings", url: "JM domain name", 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`" }, { 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" }] };
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
- logger: () => logger,
56
- name: () => name,
57
- retryCount: () => retryCount
53
+ name: () => name
58
54
  });
59
55
  module.exports = __toCommonJS(src_exports);
60
- var import_koishi = require("koishi");
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(url, method, config);
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
- if (isMysqlError || isEmptyBuffer) {
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
- constructor(root) {
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("/album", "POST", {
770
- params: { id },
771
- headers: { token, tokenparam },
772
- responseType: "json"
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("/chapter", "POST", {
796
- params: { id },
797
- headers: { token, tokenparam },
798
- responseType: "json"
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
- 5
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
- 10
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 = import_koishi.Schema.intersect([
1065
- import_koishi.Schema.object({
1066
- retryCount: import_koishi.Schema.number().min(1).max(5).default(5),
1067
- sendMethod: import_koishi.Schema.union(["zip", "pdf"]).default("pdf"),
1068
- fileMethod: import_koishi.Schema.union(["buffer", "file"]).default("buffer"),
1069
- password: import_koishi.Schema.string(),
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
- import_koishi.Schema.union([
1073
- import_koishi.Schema.object({
1074
- sendMethod: import_koishi.Schema.const("zip").required(),
1075
- level: import_koishi.Schema.number().min(0).max(9).default(6).role("slider")
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
- import_koishi.Schema.object({})
1364
+ import_koishi2.Schema.object({})
1078
1365
  ]),
1079
- import_koishi.Schema.object({
1080
- cache: import_koishi.Schema.boolean().default(false)
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
- import_koishi.Schema.union([
1083
- import_koishi.Schema.object({
1084
- cache: import_koishi.Schema.const(true).required(),
1085
- autoDelete: import_koishi.Schema.boolean().default(false)
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
- import_koishi.Schema.object({})
1380
+ import_koishi2.Schema.object({})
1088
1381
  ]),
1089
- import_koishi.Schema.union([
1090
- import_koishi.Schema.object({
1091
- cache: import_koishi.Schema.const(true).required(),
1092
- autoDelete: import_koishi.Schema.const(true).required(),
1093
- cron: import_koishi.Schema.string().default("0 0 * * *"),
1094
- deleteInStart: import_koishi.Schema.boolean().default(false),
1095
- keepDays: import_koishi.Schema.number().min(1).default(7)
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
- import_koishi.Schema.object({})
1390
+ import_koishi2.Schema.object({})
1098
1391
  ]),
1099
- import_koishi.Schema.object({
1100
- debug: import_koishi.Schema.boolean().default(false)
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
- import_koishi.h.quote(messageId),
1143
- import_koishi.h.text("输入的ID不合法,请检查")
1450
+ import_koishi2.h.quote(messageId),
1451
+ import_koishi2.h.text("输入的ID不合法,请检查")
1144
1452
  ]);
1145
1453
  return;
1146
1454
  }
1147
- try {
1148
- const jmClient = new JMAppClient(root);
1149
- const album = await jmClient.getAlbumById(albumId);
1150
- await jmClient.downloadByAlbum(album);
1151
- let filePath;
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
- import_koishi.h.quote(messageId),
1222
- import_koishi.h.text("输入的ID不合法,请检查")
1465
+ import_koishi2.h.quote(messageId),
1466
+ import_koishi2.h.text("输入的ID不合法,请检查")
1223
1467
  ]);
1224
1468
  return;
1225
1469
  }
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
- }
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
- import_koishi.h.quote(messageId),
1277
- import_koishi.h.text("输入的ID不合法,请检查")
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
- import_koishi.h.quote(messageId),
1286
- import_koishi.h.text(`ID:${album.getId()}
1489
+ import_koishi2.h.quote(messageId),
1490
+ import_koishi2.h.text(`ID:${album.getId()}
1287
1491
  `),
1288
- import_koishi.h.text(`名称:${album.getName()}
1492
+ import_koishi2.h.text(`名称:${album.getName()}
1289
1493
  `),
1290
- import_koishi.h.text(`章节数:${album.getPhotos().length}
1494
+ import_koishi2.h.text(`章节数:${album.getPhotos().length}
1291
1495
  `),
1292
- import_koishi.h.text(`作者:${album.getAuthors()?.join("、") ?? ""}
1496
+ import_koishi2.h.text(`作者:${album.getAuthors()?.join("、") ?? ""}
1293
1497
  `),
1294
- import_koishi.h.text(`登场人物:${album.getActors()?.join("、") ?? ""}
1498
+ import_koishi2.h.text(`登场人物:${album.getActors()?.join("、") ?? ""}
1295
1499
  `),
1296
- import_koishi.h.text(`点赞数:${album.getLikes()}
1500
+ import_koishi2.h.text(`点赞数:${album.getLikes()}
1297
1501
  `),
1298
- import_koishi.h.text(`观看数:${album.getTotalViews()}`)
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
- import_koishi.h.quote(messageId),
1304
- import_koishi.h.text(session.text(".notExistError"))
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
- import_koishi.h.quote(messageId),
1309
- import_koishi.h.text(session.text(".mysqlError"))
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
- logger,
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 {};
@@ -1,4 +1,5 @@
1
- import { HTTP } from "koishi";
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?: HTTP.RequestConfig, retryIndex?: number): Promise<T>;
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?: HTTP.RequestConfig, type?: "IMAGE" | "CLIENT", urlIndex?: number): Promise<T>;
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wahaha216/koishi-plugin-jmcomic",
3
3
  "description": "下载JM本子,无需python。支持pdf、zip加密。",
4
- "version": "0.0.6",
4
+ "version": "0.1.0",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
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
  添加了一些错误提示