@wahaha216/koishi-plugin-jmcomic 0.0.5 → 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,3 @@
1
+ export declare class AlbumNotExistError extends Error {
2
+ constructor(message?: string);
3
+ }
@@ -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, };
@@ -0,0 +1,3 @@
1
+ export declare class PhotoNotExistError extends Error {
2
+ constructor(message?: string);
3
+ }
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", messages: { empty: "JMID 不能为空", formatError: "JMID 格式错误", notFound: "找不到该车牌", pleaseWait: "正在获取,请等待亿会" }, album: { examples: "jm album 数字ID", info: { examples: "jm album info 本子数字ID" } }, photo: { examples: "jm photo 本子章节数字ID" } } }, _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", messages: { empty: "JM ID cannot be empty", formatError: "JM ID format error", notFound: "The JM ID cannot be found", pleaseWait: "Getting it, please wait" }, album: { examples: "jm album albumID", info: { examples: "jm album info albumID" } }, photo: { examples: "jm photo photoID" } } }, _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
  );
@@ -702,6 +716,30 @@ __name(archiverImage, "archiverImage");
702
716
  var import_path2 = require("path");
703
717
  var import_sharp2 = __toESM(require("sharp"));
704
718
  var import_muhammara = require("muhammara");
719
+
720
+ // src/error/albumNotExist.error.ts
721
+ var AlbumNotExistError = class extends Error {
722
+ static {
723
+ __name(this, "AlbumNotExistError");
724
+ }
725
+ constructor(message) {
726
+ super(message);
727
+ this.name = "AlbumNotExistError";
728
+ }
729
+ };
730
+
731
+ // src/error/photoNotExist.error.ts
732
+ var PhotoNotExistError = class extends Error {
733
+ static {
734
+ __name(this, "PhotoNotExistError");
735
+ }
736
+ constructor(message) {
737
+ super(message);
738
+ this.name = "PhotoNotExistError";
739
+ }
740
+ };
741
+
742
+ // src/entity/JMAppClient.ts
705
743
  var JMAppClient = class _JMAppClient extends JMClientAbstract {
706
744
  static {
707
745
  __name(this, "JMAppClient");
@@ -710,8 +748,23 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
710
748
  static APP_TOKEN_SECRET = "18comicAPP";
711
749
  static APP_TOKEN_SECRET_2 = "18comicAPPContent";
712
750
  static APP_DATA_SECRET = "185Hcomic3PAPP7R";
713
- 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) {
714
764
  super(root);
765
+ this.config = config;
766
+ this.logger = logger;
767
+ this.http = http;
715
768
  }
716
769
  /**
717
770
  * 登录,未完成
@@ -731,7 +784,7 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
731
784
  const formData = new import_form_data.default();
732
785
  formData.append("username", username);
733
786
  formData.append("password", password);
734
- const res = await http.post(
787
+ const res = await this.http.post(
735
788
  "https://www.cdnmhws.cc/login",
736
789
  formData,
737
790
  { headers, responseType: "json" }
@@ -739,15 +792,23 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
739
792
  return this.decodeBase64(res.data, timestamp);
740
793
  }
741
794
  async getAlbumById(id) {
742
- if (debug) logger.info(`获取本子(${id})信息`);
795
+ if (this.config.debug) this.logger.info(`获取本子(${id})信息`);
743
796
  const timestamp = this.getTimeStamp();
744
797
  const { token, tokenparam } = this.getTokenAndTokenParam(timestamp);
745
- const res = await requestWithUrlSwitch("/album", "POST", {
746
- params: { id },
747
- headers: { token, tokenparam },
748
- responseType: "json"
749
- });
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
+ );
750
810
  const album_json = this.decodeBase64(res.data, timestamp);
811
+ if (!album_json.name) throw new AlbumNotExistError();
751
812
  const album = JMAppAlbum.fromJson(album_json);
752
813
  const series = album.getSeries();
753
814
  const photos = [];
@@ -764,15 +825,23 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
764
825
  return album;
765
826
  }
766
827
  async getPhotoById(id) {
767
- if (debug) logger.info(`获取章节(${id})信息`);
828
+ if (this.config.debug) this.logger.info(`获取章节(${id})信息`);
768
829
  const timestamp = this.getTimeStamp();
769
830
  const { token, tokenparam } = this.getTokenAndTokenParam(timestamp);
770
- const res = await requestWithUrlSwitch("/chapter", "POST", {
771
- params: { id },
772
- headers: { token, tokenparam },
773
- responseType: "json"
774
- });
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
+ );
775
843
  const photo_json = this.decodeBase64(res.data, timestamp);
844
+ if (!photo_json.name) throw new PhotoNotExistError();
776
845
  const photo = JMAppPhoto.fromJson(photo_json);
777
846
  const images = photo.getImages();
778
847
  const image_ids = images.map((image) => image.split(".")[0]);
@@ -792,11 +861,13 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
792
861
  const images = photo.getImages();
793
862
  const id = photo.getId();
794
863
  let path = `${this.root}/${type}/${id}/origin`;
795
- if (debug) {
796
- logger.info(`开始下载: ${id}`);
797
- logger.info(`单章节: ${single ? "" : "否"}`);
798
- logger.info(`子章节: ${albumId ? "是" : "否"}`);
799
- logger.info(`本子ID:${albumId}`);
864
+ if (this.config.debug) {
865
+ this.logger.info(`开始下载: ${id}`);
866
+ if (type === "album") {
867
+ this.logger.info(`单章节: ${single ? "是" : "否"}`);
868
+ this.logger.info(`子章节: ${albumId ? "是" : "否"}`);
869
+ this.logger.info(`本子ID: ${albumId}`);
870
+ }
800
871
  }
801
872
  if (type === "album") {
802
873
  if (single) {
@@ -805,7 +876,7 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
805
876
  path = `${this.root}/${type}/${albumId}/origin/${id}`;
806
877
  }
807
878
  }
808
- if (debug) logger.info(`存储目录:${path}`);
879
+ if (this.config.debug) this.logger.info(`存储目录:${path}`);
809
880
  await (0, import_promises2.mkdir)(path, { recursive: true });
810
881
  await limitPromiseAll(
811
882
  images.filter((image) => {
@@ -815,18 +886,21 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
815
886
  return !fileExists || !fileSize;
816
887
  }).map((image) => async () => {
817
888
  const url = `/media/photos/${id}/${image}`;
818
- if (debug) logger.info(`下载图片:${url}`);
889
+ if (this.config.debug) this.logger.info(`下载图片:${url}`);
819
890
  const res = await requestWithUrlSwitch(
820
891
  url,
821
892
  "GET",
822
893
  { responseType: "arraybuffer" },
894
+ this.http,
895
+ this.config,
896
+ this.logger,
823
897
  "IMAGE"
824
898
  );
825
899
  await saveImage(res, `${path}/${image}`);
826
900
  }),
827
- 5
901
+ this.config.concurrentDownloadLimit
828
902
  );
829
- if (debug) logger.info(`${id} 下载完成,开始解密图片`);
903
+ if (this.config.debug) this.logger.info(`${id} 下载完成,开始解密图片`);
830
904
  await this.decodeByPhoto(photo, type, albumId, single);
831
905
  }
832
906
  async decodeByPhoto(photo, type = "photo", albumId = "", single = false) {
@@ -851,14 +925,14 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
851
925
  return !fileExists || !fileSize;
852
926
  }).map((image, index) => async () => {
853
927
  const imagePath = `${path}/${image}`;
854
- if (debug) logger.info(`解密图片:${imagePath}`);
928
+ if (this.config.debug) this.logger.info(`解密图片:${imagePath}`);
855
929
  const decodedImagePath = `${decodedPath}/${image}`;
856
930
  const imageBuffer = await (0, import_promises2.readFile)(imagePath);
857
931
  await decodeImage(imageBuffer, splitNumbers[index], decodedImagePath);
858
932
  }),
859
- 10
933
+ this.config.concurrentDecodeLimit
860
934
  );
861
- logger.info(`${id} 解密完成`);
935
+ this.logger.info(`${id} 解密完成`);
862
936
  }
863
937
  async albumToPdf(album, password) {
864
938
  const id = album.getId();
@@ -892,7 +966,7 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
892
966
  async photoToPdf(photo, pdfName, type = "photo", albumId = "", single = false, password) {
893
967
  const images = photo.getImages();
894
968
  const id = photo.getId();
895
- if (debug) logger.info(`开始生成PDF ${pdfName}.pdf`);
969
+ if (this.config.debug) this.logger.info(`开始生成PDF ${pdfName}.pdf`);
896
970
  pdfName = sanitizeFileName(pdfName);
897
971
  let path = (0, import_path2.join)(this.root, type, `${id}`);
898
972
  if (type === "album") {
@@ -932,7 +1006,7 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
932
1006
  }
933
1007
  try {
934
1008
  pdfDoc.endPDF(() => {
935
- if (debug) logger.info(`PDF ${pdfName}.pdf 生成完成`);
1009
+ if (this.config.debug) this.logger.info(`PDF ${pdfName}.pdf 生成完成`);
936
1010
  });
937
1011
  } catch (error) {
938
1012
  throw new Error(error);
@@ -958,7 +1032,7 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
958
1032
  directorys.push({ directory: `${path}/decoded`, destpath: false });
959
1033
  }
960
1034
  await archiverImage(directorys, `${path}/${zipName}.zip`, password, level);
961
- if (debug) logger.info(`ZIP ${zipName}.zip 生成完成`);
1035
+ if (this.config.debug) this.logger.info(`ZIP ${zipName}.zip 生成完成`);
962
1036
  return `${path}/${zipName}.zip`;
963
1037
  }
964
1038
  async photoToZip(photo, zipName, password, level = 6) {
@@ -971,7 +1045,7 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
971
1045
  password,
972
1046
  level
973
1047
  );
974
- if (debug) logger.info(`ZIP ${zipName}.zip 生成完成`);
1048
+ if (this.config.debug) this.logger.info(`ZIP ${zipName}.zip 生成完成`);
975
1049
  return `${path}/${zipName}.zip`;
976
1050
  }
977
1051
  /**
@@ -995,7 +1069,10 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
995
1069
  const html = await requestWithUrlSwitch(
996
1070
  "/chapter_view_template",
997
1071
  "POST",
998
- { params: { id }, headers: { token, tokenparam }, responseType: "text" }
1072
+ { params: { id }, headers: { token, tokenparam }, responseType: "text" },
1073
+ this.http,
1074
+ this.config,
1075
+ this.logger
999
1076
  );
1000
1077
  return parseInt(html.match(JM_SCRAMBLE_ID)[1]);
1001
1078
  }
@@ -1031,45 +1108,289 @@ var JMAppClient = class _JMAppClient extends JMClientAbstract {
1031
1108
  }
1032
1109
  };
1033
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
+
1034
1350
  // src/index.ts
1035
1351
  var name = "jmcomic";
1036
- var Config = import_koishi.Schema.intersect([
1037
- import_koishi.Schema.object({
1038
- retryCount: import_koishi.Schema.number().min(1).max(5).default(5),
1039
- sendMethod: import_koishi.Schema.union(["zip", "pdf"]).default("pdf"),
1040
- fileMethod: import_koishi.Schema.union(["buffer", "file"]).default("buffer"),
1041
- password: import_koishi.Schema.string(),
1042
- 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")
1043
1358
  }),
1044
- import_koishi.Schema.union([
1045
- import_koishi.Schema.object({
1046
- sendMethod: import_koishi.Schema.const("zip").required(),
1047
- level: import_koishi.Schema.number().min(0).max(9).default(6).role("slider")
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")
1048
1363
  }),
1049
- import_koishi.Schema.object({})
1364
+ import_koishi2.Schema.object({})
1050
1365
  ]),
1051
- import_koishi.Schema.object({
1052
- 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)
1053
1371
  }),
1054
- import_koishi.Schema.union([
1055
- import_koishi.Schema.object({
1056
- cache: import_koishi.Schema.const(true).required(),
1057
- autoDelete: import_koishi.Schema.boolean().default(false)
1372
+ import_koishi2.Schema.object({
1373
+ cache: import_koishi2.Schema.boolean().default(false)
1374
+ }),
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)
1058
1379
  }),
1059
- import_koishi.Schema.object({})
1380
+ import_koishi2.Schema.object({})
1060
1381
  ]),
1061
- import_koishi.Schema.union([
1062
- import_koishi.Schema.object({
1063
- cache: import_koishi.Schema.const(true).required(),
1064
- autoDelete: import_koishi.Schema.const(true).required(),
1065
- cron: import_koishi.Schema.string().default("0 0 * * *"),
1066
- deleteInStart: import_koishi.Schema.boolean().default(false),
1067
- keepDays: import_koishi.Schema.number().min(1).default(7)
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)
1068
1389
  }),
1069
- import_koishi.Schema.object({})
1390
+ import_koishi2.Schema.object({})
1070
1391
  ]),
1071
- import_koishi.Schema.object({
1072
- debug: import_koishi.Schema.boolean().default(false)
1392
+ import_koishi2.Schema.object({
1393
+ debug: import_koishi2.Schema.boolean().default(false)
1073
1394
  })
1074
1395
  ]).i18n({
1075
1396
  "zh-CN": require_zh_CN()._config,
@@ -1079,17 +1400,10 @@ var inject = {
1079
1400
  required: ["http"],
1080
1401
  optional: ["notifier", "cron"]
1081
1402
  };
1082
- var http;
1083
- var logger;
1084
- var retryCount;
1085
- var debug;
1086
1403
  async function apply(ctx, config) {
1087
- http = ctx.http;
1088
- retryCount = config.retryCount;
1089
- debug = config.debug;
1090
1404
  ctx.i18n.define("en-US", require_en_US());
1091
1405
  ctx.i18n.define("zh-CN", require_zh_CN());
1092
- logger = ctx.logger("jmcomic");
1406
+ const logger = ctx.logger("jmcomic");
1093
1407
  const root = (0, import_path3.join)(ctx.baseDir, "data", "jmcomic");
1094
1408
  const scheduleFn = /* @__PURE__ */ __name(async () => {
1095
1409
  const albumPath = (0, import_path3.join)(ctx.baseDir, "data", "jmcomic", "album");
@@ -1107,185 +1421,141 @@ async function apply(ctx, config) {
1107
1421
  content: "据JMComic-Crawler-Python源码可知JM图片还有gif形式,目前尚未支持"
1108
1422
  });
1109
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
+ );
1110
1446
  ctx.command("jm.album <albumId:string>").alias("本子").action(async ({ session, options }, albumId) => {
1111
1447
  const messageId = session.messageId;
1112
1448
  if (!/^\d+$/.test(albumId)) {
1113
1449
  await session.send([
1114
- import_koishi.h.quote(messageId),
1115
- import_koishi.h.text("输入的ID不合法,请检查")
1450
+ import_koishi2.h.quote(messageId),
1451
+ import_koishi2.h.text("输入的ID不合法,请检查")
1116
1452
  ]);
1117
1453
  return;
1118
1454
  }
1119
- try {
1120
- const jmClient = new JMAppClient(root);
1121
- const album = await jmClient.getAlbumById(albumId);
1122
- await jmClient.downloadByAlbum(album);
1123
- let filePath;
1124
- if (config.sendMethod === "zip") {
1125
- filePath = await jmClient.albumToZip(
1126
- album,
1127
- config.password,
1128
- config.level
1129
- );
1130
- } else {
1131
- filePath = await jmClient.albumToPdf(album, config.password);
1132
- }
1133
- if (typeof filePath === "string") {
1134
- const { fileName, ext, dir } = getFileInfo(filePath);
1135
- const name2 = formatFileName(config.fileName, fileName, albumId);
1136
- if (debug) logger.info(`文件名:${name2}.${ext}`);
1137
- if (config.fileMethod === "buffer") {
1138
- const buffer = await (0, import_promises3.readFile)(filePath);
1139
- await session.send([
1140
- import_koishi.h.file(buffer, ext, { title: `${name2}.${ext}` })
1141
- ]);
1142
- } else {
1143
- await session.send([
1144
- import_koishi.h.file(`file:///${filePath}`, { title: `${name2}.${ext}` })
1145
- ]);
1146
- }
1147
- if (!config.cache) (0, import_promises3.rm)(dir, { recursive: true });
1148
- } else {
1149
- let fileDir;
1150
- for (const [index, p] of filePath.entries()) {
1151
- const { fileName, ext, dir } = getFileInfo(p);
1152
- const name2 = formatFileName(
1153
- config.fileName,
1154
- fileName,
1155
- albumId,
1156
- index + 1
1157
- );
1158
- if (debug) logger.info(`文件名:${name2}.${ext}`);
1159
- if (config.fileMethod === "buffer") {
1160
- const buffer = await (0, import_promises3.readFile)(p);
1161
- await session.send([
1162
- import_koishi.h.file(buffer, ext, { title: `${name2}.${ext}` })
1163
- ]);
1164
- } else {
1165
- await session.send([
1166
- import_koishi.h.file(`file:///${p}`, { title: `${name2}.${ext}` })
1167
- ]);
1168
- }
1169
- fileDir = dir;
1170
- }
1171
- if (!config.cache) (0, import_promises3.rm)(fileDir, { recursive: true });
1172
- }
1173
- } catch (error) {
1174
- if (error instanceof Error) {
1175
- if (error.message.includes("Could not connect to mysql")) {
1176
- await session.send([
1177
- import_koishi.h.quote(messageId),
1178
- import_koishi.h.text("已尝试所有可能,但是JM坏掉了")
1179
- ]);
1180
- }
1181
- } else {
1182
- throw new Error(error);
1183
- }
1184
- }
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
+ ]);
1185
1460
  });
1186
1461
  ctx.command("jm.photo <photoId:string>").alias("本子章节").action(async ({ session }, photoId) => {
1187
1462
  const messageId = session.messageId;
1188
1463
  if (!/^\d+$/.test(photoId)) {
1189
1464
  await session.send([
1190
- import_koishi.h.quote(messageId),
1191
- import_koishi.h.text("输入的ID不合法,请检查")
1465
+ import_koishi2.h.quote(messageId),
1466
+ import_koishi2.h.text("输入的ID不合法,请检查")
1192
1467
  ]);
1193
1468
  return;
1194
1469
  }
1195
- try {
1196
- const jmClient = new JMAppClient(root);
1197
- const photo = await jmClient.getPhotoById(photoId);
1198
- await jmClient.downloadByPhoto(photo);
1199
- const photoName = photo.getName();
1200
- let filePath;
1201
- if (config.sendMethod === "zip") {
1202
- filePath = await jmClient.photoToZip(
1203
- photo,
1204
- photoName,
1205
- config.password,
1206
- config.level
1207
- );
1208
- } else {
1209
- filePath = await jmClient.photoToPdf(photo, photoName);
1210
- }
1211
- const { fileName, ext, dir } = getFileInfo(filePath);
1212
- const name2 = formatFileName(config.fileName, fileName, photoId);
1213
- if (debug) logger.info(`文件名:${name2}.${ext}`);
1214
- if (config.fileMethod === "buffer") {
1215
- const buffer = await (0, import_promises3.readFile)(filePath);
1216
- await session.send([
1217
- import_koishi.h.file(buffer, ext, { title: `${name2} (${photoId}).${ext}` })
1218
- ]);
1219
- } else {
1220
- await session.send([
1221
- import_koishi.h.file(`file:///${filePath}`, { title: `${name2}.${ext}` })
1222
- ]);
1223
- }
1224
- if (!config.cache) (0, import_promises3.rm)(dir, { recursive: true });
1225
- } catch (error) {
1226
- if (error instanceof Error) {
1227
- if (error.message.includes("Could not connect to mysql")) {
1228
- await session.send([
1229
- import_koishi.h.quote(messageId),
1230
- import_koishi.h.text("已尝试所有可能,但是JM坏掉了")
1231
- ]);
1232
- }
1233
- } else {
1234
- throw new Error(error);
1235
- }
1236
- }
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
+ ]);
1237
1475
  });
1238
1476
  ctx.command("jm.album.info <albumId:string>").alias("本子信息").action(async ({ session, options }, albumId) => {
1239
1477
  const messageId = session.messageId;
1240
1478
  if (!/^\d+$/.test(albumId)) {
1241
1479
  await session.send([
1242
- import_koishi.h.quote(messageId),
1243
- import_koishi.h.text("输入的ID不合法,请检查")
1480
+ import_koishi2.h.quote(messageId),
1481
+ import_koishi2.h.text("输入的ID不合法,请检查")
1244
1482
  ]);
1245
1483
  return;
1246
1484
  }
1247
1485
  try {
1248
- const jmClient = new JMAppClient(root);
1486
+ const jmClient = new JMAppClient(root, ctx.http, config, logger);
1249
1487
  const album = await jmClient.getAlbumById(albumId);
1250
1488
  await session.send([
1251
- import_koishi.h.quote(messageId),
1252
- import_koishi.h.text(`ID:${album.getId()}
1489
+ import_koishi2.h.quote(messageId),
1490
+ import_koishi2.h.text(`ID:${album.getId()}
1253
1491
  `),
1254
- import_koishi.h.text(`名称:${album.getName()}
1492
+ import_koishi2.h.text(`名称:${album.getName()}
1255
1493
  `),
1256
- import_koishi.h.text(`章节数:${album.getPhotos().length}
1494
+ import_koishi2.h.text(`章节数:${album.getPhotos().length}
1257
1495
  `),
1258
- import_koishi.h.text(`作者:${album.getAuthors()?.join("、") ?? ""}
1496
+ import_koishi2.h.text(`作者:${album.getAuthors()?.join("、") ?? ""}
1259
1497
  `),
1260
- import_koishi.h.text(`登场人物:${album.getActors()?.join("、") ?? ""}
1498
+ import_koishi2.h.text(`登场人物:${album.getActors()?.join("、") ?? ""}
1261
1499
  `),
1262
- import_koishi.h.text(`点赞数:${album.getLikes()}
1500
+ import_koishi2.h.text(`点赞数:${album.getLikes()}
1263
1501
  `),
1264
- import_koishi.h.text(`观看数:${album.getTotalViews()}`)
1502
+ import_koishi2.h.text(`观看数:${album.getTotalViews()}`)
1265
1503
  ]);
1266
1504
  } catch (error) {
1267
- if (error instanceof Error) {
1268
- if (error.message.includes("Could not connect to mysql")) {
1269
- await session.send([
1270
- import_koishi.h.quote(messageId),
1271
- import_koishi.h.text("已尝试所有可能,但是JM坏掉了")
1272
- ]);
1273
- }
1505
+ if (error instanceof AlbumNotExistError) {
1506
+ await session.send([
1507
+ import_koishi2.h.quote(messageId),
1508
+ import_koishi2.h.text(session.text(".notExistError"))
1509
+ ]);
1510
+ } else if (error instanceof MySqlError) {
1511
+ await session.send([
1512
+ import_koishi2.h.quote(messageId),
1513
+ import_koishi2.h.text(session.text(".mysqlError"))
1514
+ ]);
1274
1515
  } else {
1275
1516
  throw new Error(error);
1276
1517
  }
1277
1518
  }
1278
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
+ });
1279
1553
  }
1280
1554
  __name(apply, "apply");
1281
1555
  // Annotate the CommonJS export names for ESM import in node:
1282
1556
  0 && (module.exports = {
1283
1557
  Config,
1284
1558
  apply,
1285
- debug,
1286
- http,
1287
1559
  inject,
1288
- logger,
1289
- name,
1290
- retryCount
1560
+ name
1291
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.5",
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,32 @@ 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
+
35
+ <details>
36
+ <summary>0.0.6</summary>
37
+ 添加了一些错误提示
38
+ </details>
39
+
40
+ <details>
41
+ <summary>0.0.5</summary>
42
+ 修改使用示例
43
+ </details>
44
+
19
45
  <details>
20
46
  <summary>0.0.4</summary>
21
47
  1.文件名移除前后空格