koishi-plugin-custom-image 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +2 -7
- package/lib/index.js +55 -166
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -3,21 +3,16 @@ export declare const name = "custom-image";
|
|
|
3
3
|
export interface Config {
|
|
4
4
|
enable: boolean;
|
|
5
5
|
showWaitingTip: boolean;
|
|
6
|
-
sameImageApiInterval: number;
|
|
7
6
|
timeout: number;
|
|
8
7
|
customImageApis: string[];
|
|
9
8
|
customImageEnabled: boolean;
|
|
10
9
|
hsjpEnabled: boolean;
|
|
11
10
|
dmjpEnabled: boolean;
|
|
12
|
-
ignoreSendError: boolean;
|
|
13
|
-
retryTimes: number;
|
|
14
|
-
retryInterval: number;
|
|
15
11
|
imageSendTimeout: number;
|
|
16
12
|
autoClearCacheInterval: number;
|
|
17
|
-
downloadImageBeforeSend: boolean;
|
|
18
|
-
messageBufferDelay: number;
|
|
19
13
|
tempDir: string;
|
|
20
|
-
|
|
14
|
+
showSuccessTip: boolean;
|
|
15
|
+
showTimeoutTip: boolean;
|
|
21
16
|
}
|
|
22
17
|
export declare const Config: Schema<Config>;
|
|
23
18
|
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
CHANGED
|
@@ -7,7 +7,6 @@ exports.using = exports.inject = exports.Config = exports.name = void 0;
|
|
|
7
7
|
exports.apply = apply;
|
|
8
8
|
const koishi_1 = require("koishi");
|
|
9
9
|
const axios_1 = __importDefault(require("axios"));
|
|
10
|
-
const crypto_1 = __importDefault(require("crypto"));
|
|
11
10
|
const fs_1 = __importDefault(require("fs"));
|
|
12
11
|
const path_1 = __importDefault(require("path"));
|
|
13
12
|
const promises_1 = require("stream/promises");
|
|
@@ -40,10 +39,6 @@ exports.Config = koishi_1.Schema.object({
|
|
|
40
39
|
showWaitingTip: koishi_1.Schema.boolean()
|
|
41
40
|
.default(true)
|
|
42
41
|
.description('【基础设置】请求图片时显示等待提示'),
|
|
43
|
-
sameImageApiInterval: koishi_1.Schema.number()
|
|
44
|
-
.default(60)
|
|
45
|
-
.min(0)
|
|
46
|
-
.description('【基础设置】重复API调用间隔(秒)'),
|
|
47
42
|
timeout: koishi_1.Schema.number()
|
|
48
43
|
.default(10000)
|
|
49
44
|
.min(0)
|
|
@@ -61,38 +56,23 @@ exports.Config = koishi_1.Schema.object({
|
|
|
61
56
|
dmjpEnabled: koishi_1.Schema.boolean()
|
|
62
57
|
.default(true)
|
|
63
58
|
.description('【功能开关】启用动漫举牌功能'),
|
|
64
|
-
ignoreSendError: koishi_1.Schema.boolean()
|
|
65
|
-
.default(true)
|
|
66
|
-
.description('【容错设置】忽略发送错误'),
|
|
67
|
-
retryTimes: koishi_1.Schema.number()
|
|
68
|
-
.default(1)
|
|
69
|
-
.min(0)
|
|
70
|
-
.description('【容错设置】API重试次数'),
|
|
71
|
-
retryInterval: koishi_1.Schema.number()
|
|
72
|
-
.default(500)
|
|
73
|
-
.min(0)
|
|
74
|
-
.description('【容错设置】重试间隔(毫秒)'),
|
|
75
59
|
imageSendTimeout: koishi_1.Schema.number()
|
|
76
60
|
.default(15000)
|
|
77
61
|
.min(0)
|
|
78
|
-
.description('
|
|
62
|
+
.description('【基础设置】图片发送超时(毫秒)'),
|
|
79
63
|
autoClearCacheInterval: koishi_1.Schema.number()
|
|
80
64
|
.default(60)
|
|
81
65
|
.min(0)
|
|
82
|
-
.description('
|
|
83
|
-
downloadImageBeforeSend: koishi_1.Schema.boolean()
|
|
84
|
-
.default(false)
|
|
85
|
-
.description('【展示设置】发送前下载图片(关闭可解决OneBot文件上传问题)'),
|
|
86
|
-
messageBufferDelay: koishi_1.Schema.number()
|
|
87
|
-
.default(0)
|
|
88
|
-
.min(0)
|
|
89
|
-
.description('【性能设置】消息缓冲延迟:合并短时间内的多个图片请求(秒)'),
|
|
66
|
+
.description('【基础设置】自动清理缓存间隔(分钟)'),
|
|
90
67
|
tempDir: koishi_1.Schema.string()
|
|
91
68
|
.default(path_1.default.join(process.cwd(), 'temp_images'))
|
|
92
|
-
.description('
|
|
93
|
-
|
|
69
|
+
.description('【基础设置】临时图片保存目录'),
|
|
70
|
+
showSuccessTip: koishi_1.Schema.boolean()
|
|
71
|
+
.default(true)
|
|
72
|
+
.description('【提示设置】请求图片成功时显示提示'),
|
|
73
|
+
showTimeoutTip: koishi_1.Schema.boolean()
|
|
94
74
|
.default(true)
|
|
95
|
-
.description('
|
|
75
|
+
.description('【提示设置】API请求超时后给用户提示')
|
|
96
76
|
});
|
|
97
77
|
if (!worker_threads_1.isMainThread) {
|
|
98
78
|
const { url, filePath } = worker_threads_1.workerData;
|
|
@@ -137,28 +117,6 @@ async function sendTimeout(session, content, config) {
|
|
|
137
117
|
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), config.imageSendTimeout))
|
|
138
118
|
]).catch(() => null);
|
|
139
119
|
}
|
|
140
|
-
async function downloadImageWithThreads(url, filename, config) {
|
|
141
|
-
return new Promise((resolve, reject) => {
|
|
142
|
-
if (!fs_1.default.existsSync(config.tempDir)) {
|
|
143
|
-
fs_1.default.mkdirSync(config.tempDir, { recursive: true, mode: 0o777 });
|
|
144
|
-
}
|
|
145
|
-
const filePath = path_1.default.join(config.tempDir, `${filename}.jpg`);
|
|
146
|
-
const worker = new worker_threads_1.Worker(currentFilePath, { workerData: { url, filePath } });
|
|
147
|
-
worker.on('message', (result) => {
|
|
148
|
-
if (result.success && result.filePath) {
|
|
149
|
-
resolve(result.filePath);
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
reject(new Error(result.error || '图片获取失败'));
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
worker.on('error', reject);
|
|
156
|
-
worker.on('exit', (code) => {
|
|
157
|
-
if (code !== 0)
|
|
158
|
-
reject(new Error('图片下载线程异常'));
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
120
|
function clearAllCache(config) {
|
|
163
121
|
processedApi.clear();
|
|
164
122
|
imageBuffer.forEach(buf => clearTimeout(buf.timer));
|
|
@@ -190,23 +148,20 @@ async function fetchImage(url, config) {
|
|
|
190
148
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
191
149
|
}
|
|
192
150
|
});
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return { success: true, type: 'url', data: url };
|
|
197
|
-
}
|
|
198
|
-
const res = await http.get(url, { responseType: 'arraybuffer' });
|
|
199
|
-
if (res.status === 200 && res.data) {
|
|
200
|
-
return { success: true, type: 'buffer', data: res.data };
|
|
201
|
-
}
|
|
151
|
+
try {
|
|
152
|
+
if (/^https?:\/\/.+\.(jpg|jpeg|png|gif|webp|bmp)/i.test(url)) {
|
|
153
|
+
return { success: true, type: 'url', data: url, timeout: false };
|
|
202
154
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
await delay(config.retryInterval);
|
|
155
|
+
const res = await http.get(url, { responseType: 'arraybuffer' });
|
|
156
|
+
if (res.status === 200 && res.data) {
|
|
157
|
+
return { success: true, type: 'buffer', data: res.data, timeout: false };
|
|
207
158
|
}
|
|
208
159
|
}
|
|
209
|
-
|
|
160
|
+
catch (error) {
|
|
161
|
+
const isTimeout = error.message.includes('timeout');
|
|
162
|
+
return { success: false, type: '', data: '', timeout: isTimeout };
|
|
163
|
+
}
|
|
164
|
+
return { success: false, type: '', data: '', timeout: false };
|
|
210
165
|
}
|
|
211
166
|
async function fetchHsjpImage(msg, msg1, msg2, config) {
|
|
212
167
|
const encodedMsg = encodeURIComponent(msg);
|
|
@@ -221,46 +176,16 @@ async function fetchDmjpImage(text, config) {
|
|
|
221
176
|
return fetchImage(url, config);
|
|
222
177
|
}
|
|
223
178
|
async function processCustomImage(session, apiUrl, config) {
|
|
224
|
-
const hash = crypto_1.default.createHash('md5').update(apiUrl).digest('hex');
|
|
225
|
-
const now = Date.now();
|
|
226
|
-
if (processedApi.get(hash) && now - processedApi.get(hash) < config.sameImageApiInterval * 1000) {
|
|
227
|
-
return { success: false, msg: getI18nText(session, 'messages.repeatRequest') };
|
|
228
|
-
}
|
|
229
|
-
processedApi.set(hash, now);
|
|
230
179
|
const result = await fetchImage(apiUrl, config);
|
|
180
|
+
if (result.timeout && config.showTimeoutTip) {
|
|
181
|
+
return { success: false, msg: '图片请求超时,请稍后再试' };
|
|
182
|
+
}
|
|
231
183
|
if (!result.success) {
|
|
232
184
|
return { success: false, msg: getI18nText(session, 'messages.fetchFailed') };
|
|
233
185
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
else if (result.type === 'url' && config.downloadImageBeforeSend) {
|
|
239
|
-
try {
|
|
240
|
-
const filename = crypto_1.default.createHash('md5').update(result.data).digest('hex');
|
|
241
|
-
const filePath = await downloadImageWithThreads(result.data, filename, config);
|
|
242
|
-
const absPath = path_1.default.resolve(filePath);
|
|
243
|
-
fs_1.default.chmodSync(absPath, 0o777);
|
|
244
|
-
imageElem = koishi_1.h.file(`file://${absPath.replace(/\\/g, '/')}`);
|
|
245
|
-
}
|
|
246
|
-
catch (error) {
|
|
247
|
-
imageElem = koishi_1.h.image(result.data);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
else if (result.type === 'buffer') {
|
|
251
|
-
if (!fs_1.default.existsSync(config.tempDir)) {
|
|
252
|
-
fs_1.default.mkdirSync(config.tempDir, { recursive: true, mode: 0o777 });
|
|
253
|
-
}
|
|
254
|
-
const filename = crypto_1.default.randomUUID();
|
|
255
|
-
const filePath = path_1.default.join(config.tempDir, `${filename}.jpg`);
|
|
256
|
-
const absPath = path_1.default.resolve(filePath);
|
|
257
|
-
fs_1.default.writeFileSync(absPath, result.data, { mode: 0o777 });
|
|
258
|
-
imageElem = koishi_1.h.file(`file://${absPath.replace(/\\/g, '/')}`);
|
|
259
|
-
}
|
|
260
|
-
else {
|
|
261
|
-
imageElem = koishi_1.h.image(result.data);
|
|
262
|
-
}
|
|
263
|
-
return { success: true, msg: 'ok', data: { text: getI18nText(session, 'messages.fetchSuccess'), image: imageElem } };
|
|
186
|
+
const imageElem = koishi_1.h.image(result.type === 'url' ? result.data : `data:image/jpeg;base64,${Buffer.from(result.data).toString('base64')}`);
|
|
187
|
+
const successText = config.showSuccessTip ? getI18nText(session, 'messages.fetchSuccess') : '';
|
|
188
|
+
return { success: true, msg: 'ok', data: { text: successText, image: imageElem } };
|
|
264
189
|
}
|
|
265
190
|
async function flush(session, config, manualApi) {
|
|
266
191
|
const key = `${session.platform}:${session.userId}:${session.channelId}`;
|
|
@@ -283,21 +208,21 @@ async function flush(session, config, manualApi) {
|
|
|
283
208
|
errs.push(`【${apiUrl.slice(0, 22)}...】:${result.msg}`);
|
|
284
209
|
}
|
|
285
210
|
}
|
|
286
|
-
if (errs.length
|
|
211
|
+
if (errs.length) {
|
|
287
212
|
const errorMsg = `${getI18nText(session, 'messages.partialFailed')}\n${errs.join('\n')}`;
|
|
288
213
|
await sendTimeout(session, errorMsg, config);
|
|
289
214
|
await delay(600);
|
|
290
215
|
}
|
|
291
216
|
if (items.length === 0) {
|
|
292
217
|
const failMsg = `${getI18nText(session, 'messages.allFailed')}\n${errs.join('\n')}`;
|
|
293
|
-
|
|
294
|
-
await sendTimeout(session, failMsg, config);
|
|
295
|
-
}
|
|
218
|
+
await sendTimeout(session, failMsg, config);
|
|
296
219
|
return;
|
|
297
220
|
}
|
|
298
221
|
for (const item of items) {
|
|
299
|
-
|
|
300
|
-
|
|
222
|
+
if (item.text) {
|
|
223
|
+
await sendTimeout(session, item.text, config);
|
|
224
|
+
await delay(300);
|
|
225
|
+
}
|
|
301
226
|
await sendTimeout(session, item.image, config);
|
|
302
227
|
await delay(1000);
|
|
303
228
|
}
|
|
@@ -329,12 +254,12 @@ function apply(ctx, config) {
|
|
|
329
254
|
if (imageBuffer.has(key)) {
|
|
330
255
|
const b = imageBuffer.get(key);
|
|
331
256
|
clearTimeout(b.timer);
|
|
332
|
-
b.timer = setTimeout(() => flush(session, config),
|
|
257
|
+
b.timer = setTimeout(() => flush(session, config), 0);
|
|
333
258
|
}
|
|
334
259
|
else {
|
|
335
260
|
imageBuffer.set(key, {
|
|
336
261
|
apis: config.customImageApis,
|
|
337
|
-
timer: setTimeout(() => flush(session, config),
|
|
262
|
+
timer: setTimeout(() => flush(session, config), 0),
|
|
338
263
|
tipMsgId: tipId
|
|
339
264
|
});
|
|
340
265
|
}
|
|
@@ -352,39 +277,21 @@ function apply(ctx, config) {
|
|
|
352
277
|
await sendTimeout(session, 'messages.fetchWaiting', config);
|
|
353
278
|
}
|
|
354
279
|
const result = await fetchHsjpImage(msg, msg1, msg2, config);
|
|
280
|
+
if (result.timeout && config.showTimeoutTip) {
|
|
281
|
+
await sendTimeout(session, '图片请求超时,请稍后再试', config);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
355
284
|
if (result.success) {
|
|
356
|
-
|
|
357
|
-
if (config.
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
else if (result.type === 'url' && config.downloadImageBeforeSend) {
|
|
361
|
-
try {
|
|
362
|
-
const filename = crypto_1.default.createHash('md5').update(result.data).digest('hex');
|
|
363
|
-
const filePath = await downloadImageWithThreads(result.data, filename, config);
|
|
364
|
-
const absPath = path_1.default.resolve(filePath);
|
|
365
|
-
fs_1.default.chmodSync(absPath, 0o777);
|
|
366
|
-
imageElem = koishi_1.h.file(`file://${absPath.replace(/\\/g, '/')}`);
|
|
367
|
-
}
|
|
368
|
-
catch (error) {
|
|
369
|
-
imageElem = koishi_1.h.image(result.data);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
else if (result.type === 'buffer') {
|
|
373
|
-
if (!fs_1.default.existsSync(config.tempDir)) {
|
|
374
|
-
fs_1.default.mkdirSync(config.tempDir, { recursive: true, mode: 0o777 });
|
|
375
|
-
}
|
|
376
|
-
const filename = crypto_1.default.randomUUID();
|
|
377
|
-
const filePath = path_1.default.join(config.tempDir, `${filename}.jpg`);
|
|
378
|
-
const absPath = path_1.default.resolve(filePath);
|
|
379
|
-
fs_1.default.writeFileSync(absPath, result.data, { mode: 0o777 });
|
|
380
|
-
imageElem = koishi_1.h.file(`file://${absPath.replace(/\\/g, '/')}`);
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
imageElem = koishi_1.h.image(result.data);
|
|
285
|
+
const imageElem = koishi_1.h.image(result.type === 'url' ? result.data : `data:image/jpeg;base64,${Buffer.from(result.data).toString('base64')}`);
|
|
286
|
+
if (config.showSuccessTip) {
|
|
287
|
+
await sendTimeout(session, 'messages.fetchSuccess', config);
|
|
288
|
+
await delay(300);
|
|
384
289
|
}
|
|
385
|
-
await sendTimeout(session, 'messages.fetchSuccess', config);
|
|
386
290
|
await sendTimeout(session, imageElem, config);
|
|
387
291
|
}
|
|
292
|
+
else {
|
|
293
|
+
await sendTimeout(session, 'messages.fetchFailed', config);
|
|
294
|
+
}
|
|
388
295
|
});
|
|
389
296
|
ctx.command('dmjp <text>', locales['zh-CN']['commands.dmjp'])
|
|
390
297
|
.action(async ({ session }, text) => {
|
|
@@ -398,39 +305,21 @@ function apply(ctx, config) {
|
|
|
398
305
|
await sendTimeout(session, 'messages.fetchWaiting', config);
|
|
399
306
|
}
|
|
400
307
|
const result = await fetchDmjpImage(text, config);
|
|
308
|
+
if (result.timeout && config.showTimeoutTip) {
|
|
309
|
+
await sendTimeout(session, '图片请求超时,请稍后再试', config);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
401
312
|
if (result.success) {
|
|
402
|
-
|
|
403
|
-
if (config.
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
else if (result.type === 'url' && config.downloadImageBeforeSend) {
|
|
407
|
-
try {
|
|
408
|
-
const filename = crypto_1.default.createHash('md5').update(result.data).digest('hex');
|
|
409
|
-
const filePath = await downloadImageWithThreads(result.data, filename, config);
|
|
410
|
-
const absPath = path_1.default.resolve(filePath);
|
|
411
|
-
fs_1.default.chmodSync(absPath, 0o777);
|
|
412
|
-
imageElem = koishi_1.h.file(`file://${absPath.replace(/\\/g, '/')}`);
|
|
413
|
-
}
|
|
414
|
-
catch (error) {
|
|
415
|
-
imageElem = koishi_1.h.image(result.data);
|
|
416
|
-
}
|
|
313
|
+
const imageElem = koishi_1.h.image(result.type === 'url' ? result.data : `data:image/jpeg;base64,${Buffer.from(result.data).toString('base64')}`);
|
|
314
|
+
if (config.showSuccessTip) {
|
|
315
|
+
await sendTimeout(session, 'messages.fetchSuccess', config);
|
|
316
|
+
await delay(300);
|
|
417
317
|
}
|
|
418
|
-
else if (result.type === 'buffer') {
|
|
419
|
-
if (!fs_1.default.existsSync(config.tempDir)) {
|
|
420
|
-
fs_1.default.mkdirSync(config.tempDir, { recursive: true, mode: 0o777 });
|
|
421
|
-
}
|
|
422
|
-
const filename = crypto_1.default.randomUUID();
|
|
423
|
-
const filePath = path_1.default.join(config.tempDir, `${filename}.jpg`);
|
|
424
|
-
const absPath = path_1.default.resolve(filePath);
|
|
425
|
-
fs_1.default.writeFileSync(absPath, result.data, { mode: 0o777 });
|
|
426
|
-
imageElem = koishi_1.h.file(`file://${absPath.replace(/\\/g, '/')}`);
|
|
427
|
-
}
|
|
428
|
-
else {
|
|
429
|
-
imageElem = koishi_1.h.image(result.data);
|
|
430
|
-
}
|
|
431
|
-
await sendTimeout(session, 'messages.fetchSuccess', config);
|
|
432
318
|
await sendTimeout(session, imageElem, config);
|
|
433
319
|
}
|
|
320
|
+
else {
|
|
321
|
+
await sendTimeout(session, 'messages.fetchFailed', config);
|
|
322
|
+
}
|
|
434
323
|
});
|
|
435
324
|
ctx.command('clear-image-cache', locales['zh-CN']['commands.clear-image-cache'])
|
|
436
325
|
.action(({ session }) => {
|