koishi-plugin-custom-image 0.2.1 → 0.2.2

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.js CHANGED
@@ -12,8 +12,26 @@ const fs_1 = __importDefault(require("fs"));
12
12
  const path_1 = __importDefault(require("path"));
13
13
  const promises_1 = require("stream/promises");
14
14
  const worker_threads_1 = require("worker_threads");
15
- const zh_CN_yml_1 = __importDefault(require("./locales/zh-CN.yml"));
16
- const currentFilePath = path_1.default.join(process.cwd(), 'src', 'index.ts');
15
+ const locales = {
16
+ 'zh-CN': {
17
+ 'messages.repeatRequest': '请求过于频繁,请稍后再试',
18
+ 'messages.fetchFailed': '图片获取失败',
19
+ 'messages.partialFailed': '部分图片获取失败:',
20
+ 'messages.allFailed': '所有图片获取失败:',
21
+ 'messages.fetchSuccess': '图片获取成功!',
22
+ 'messages.fetchWaiting': '正在获取图片,请稍候...',
23
+ 'messages.inputError': '输入内容不能为空!',
24
+ 'messages.cacheCleared': '✅ 图片缓存已清空',
25
+ 'commands.random-image': '随机获取图片',
26
+ 'commands.random-image.options.api': '指定图片API地址',
27
+ 'commands.hsjp': '生成黑丝举牌图片',
28
+ 'commands.dmjp': '生成动漫举牌图片',
29
+ 'commands.clear-image-cache': '清空图片缓存'
30
+ }
31
+ };
32
+ const currentFilePath = worker_threads_1.isMainThread
33
+ ? __filename
34
+ : path_1.default.join(process.cwd(), worker_threads_1.isMainThread ? 'src/index.ts' : 'lib/index.js');
17
35
  exports.name = 'custom-image';
18
36
  exports.Config = koishi_1.Schema.object({
19
37
  enable: koishi_1.Schema.boolean()
@@ -97,8 +115,17 @@ if (!worker_threads_1.isMainThread) {
97
115
  const processedApi = new Map();
98
116
  const imageBuffer = new Map();
99
117
  const delay = (ms) => new Promise(r => setTimeout(r, ms));
118
+ function getI18nText(session, key) {
119
+ if (session?.text) {
120
+ return session.text(key);
121
+ }
122
+ const lang = 'zh-CN';
123
+ return locales[lang][key] || key;
124
+ }
100
125
  async function sendTimeout(session, content, config) {
101
- const text = typeof content === 'string' ? session.text(content) : content;
126
+ const text = typeof content === 'string'
127
+ ? getI18nText(session, content)
128
+ : content;
102
129
  if (config.imageSendTimeout <= 0) {
103
130
  return session.send(text).catch(() => null);
104
131
  }
@@ -194,12 +221,12 @@ async function processCustomImage(session, apiUrl, config) {
194
221
  const hash = crypto_1.default.createHash('md5').update(apiUrl).digest('hex');
195
222
  const now = Date.now();
196
223
  if (processedApi.get(hash) && now - processedApi.get(hash) < config.sameImageApiInterval * 1000) {
197
- return { success: false, msg: session.text('messages.repeatRequest') };
224
+ return { success: false, msg: getI18nText(session, 'messages.repeatRequest') };
198
225
  }
199
226
  processedApi.set(hash, now);
200
227
  const result = await fetchImage(apiUrl, config);
201
228
  if (!result.success) {
202
- return { success: false, msg: session.text('messages.fetchFailed') };
229
+ return { success: false, msg: getI18nText(session, 'messages.fetchFailed') };
203
230
  }
204
231
  let imageElem;
205
232
  if (result.type === 'url' && config.downloadImageBeforeSend) {
@@ -226,7 +253,7 @@ async function processCustomImage(session, apiUrl, config) {
226
253
  else {
227
254
  imageElem = koishi_1.h.image(result.data);
228
255
  }
229
- return { success: true, msg: 'ok', data: { text: session.text('messages.fetchSuccess'), image: imageElem } };
256
+ return { success: true, msg: 'ok', data: { text: getI18nText(session, 'messages.fetchSuccess'), image: imageElem } };
230
257
  }
231
258
  async function flush(session, config, manualApi) {
232
259
  const key = `${session.platform}:${session.userId}:${session.channelId}`;
@@ -250,12 +277,12 @@ async function flush(session, config, manualApi) {
250
277
  }
251
278
  }
252
279
  if (errs.length && !config.ignoreSendError) {
253
- const errorMsg = `${session.text('messages.partialFailed')}\n${errs.join('\n')}`;
280
+ const errorMsg = `${getI18nText(session, 'messages.partialFailed')}\n${errs.join('\n')}`;
254
281
  await sendTimeout(session, errorMsg, config);
255
282
  await delay(600);
256
283
  }
257
284
  if (items.length === 0) {
258
- const failMsg = `${session.text('messages.allFailed')}\n${errs.join('\n')}`;
285
+ const failMsg = `${getI18nText(session, 'messages.allFailed')}\n${errs.join('\n')}`;
259
286
  if (!config.ignoreSendError) {
260
287
  await sendTimeout(session, failMsg, config);
261
288
  }
@@ -271,11 +298,13 @@ async function flush(session, config, manualApi) {
271
298
  function apply(ctx, config) {
272
299
  if (!worker_threads_1.isMainThread)
273
300
  return;
274
- ctx.i18n.define('zh-CN', zh_CN_yml_1.default);
301
+ Object.keys(locales).forEach(lang => {
302
+ ctx.i18n.define(lang, locales[lang]);
303
+ });
275
304
  clearAllCache(config);
276
305
  ctx.logger.info('[custom-image] 插件已加载');
277
- ctx.command('random-image [apis...]', '#commands.random-image')
278
- .option('api', '-a <api> #commands.random-image.options.api')
306
+ ctx.command('random-image [apis...]', locales['zh-CN']['commands.random-image'])
307
+ .option('api', `-a <api> ${locales['zh-CN']['commands.random-image.options.api']}`)
279
308
  .action(async ({ session, options = {} }, ...apis) => {
280
309
  if (!config.enable || !config.customImageEnabled || !session)
281
310
  return;
@@ -304,7 +333,7 @@ function apply(ctx, config) {
304
333
  }
305
334
  }
306
335
  });
307
- ctx.command('hsjp <msg> [msg1] [msg2]', '#commands.hsjp')
336
+ ctx.command('hsjp <msg> [msg1] [msg2]', locales['zh-CN']['commands.hsjp'])
308
337
  .action(async ({ session }, msg, msg1 = '', msg2 = '') => {
309
338
  if (!config.enable || !config.hsjpEnabled || !session)
310
339
  return;
@@ -346,7 +375,7 @@ function apply(ctx, config) {
346
375
  await sendTimeout(session, imageElem, config);
347
376
  }
348
377
  });
349
- ctx.command('dmjp <text>', '#commands.dmjp')
378
+ ctx.command('dmjp <text>', locales['zh-CN']['commands.dmjp'])
350
379
  .action(async ({ session }, text) => {
351
380
  if (!config.enable || !config.dmjpEnabled || !session)
352
381
  return;
@@ -388,10 +417,10 @@ function apply(ctx, config) {
388
417
  await sendTimeout(session, imageElem, config);
389
418
  }
390
419
  });
391
- ctx.command('clear-image-cache', '#commands.clear-image-cache')
420
+ ctx.command('clear-image-cache', locales['zh-CN']['commands.clear-image-cache'])
392
421
  .action(({ session }) => {
393
422
  clearAllCache(config);
394
- return session ? session.text('messages.cacheCleared') : '✅ 图片缓存已清空';
423
+ return session ? getI18nText(session, 'messages.cacheCleared') : '✅ 图片缓存已清空';
395
424
  });
396
425
  setInterval(() => {
397
426
  const now = Date.now();
package/package.json CHANGED
@@ -1,13 +1,11 @@
1
1
  {
2
2
  "name": "koishi-plugin-custom-image",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Koishi自定义图片API插件(內置支持黑丝/动漫举牌)",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
8
- "lib",
9
- "src",
10
- "src/locales"
8
+ "lib"
11
9
  ],
12
10
  "scripts": {
13
11
  "build": "tsc",
@@ -18,22 +16,16 @@
18
16
  "koishi": "^4.0.0"
19
17
  },
20
18
  "dependencies": {
21
- "axios": "^1.6.8",
22
- "yaml": "^2.4.1"
19
+ "axios": "^1.6.8"
23
20
  },
24
21
  "devDependencies": {
25
22
  "typescript": "^5.3.3",
26
- "@types/node": "^20.11.17",
27
- "@koishijs/typings": "^4.0.0",
28
- "@types/yaml": "^1.9.7"
23
+ "@types/node": "^20.11.17"
29
24
  },
30
25
  "koishi": {
31
26
  "description": {
32
27
  "zh-CN": "自定义图片API调用 + 內置黑丝/动漫举牌功能"
33
- },
34
- "locales": [
35
- "zh-CN"
36
- ]
28
+ }
37
29
  },
38
30
  "keywords": [
39
31
  "koishi",
package/src/index.ts DELETED
@@ -1,471 +0,0 @@
1
- import { Context, Schema, h, Session } from 'koishi'
2
- import axios from 'axios'
3
- import crypto from 'crypto'
4
- import fs from 'fs'
5
- import path from 'path'
6
- import { pipeline } from 'stream/promises'
7
- import { isMainThread, Worker, workerData, parentPort } from 'worker_threads'
8
-
9
- import zhCN from './locales/zh-CN.yml'
10
-
11
- const currentFilePath = path.join(process.cwd(), 'src', 'index.ts');
12
-
13
- export const name = 'custom-image'
14
-
15
- export interface Config {
16
- enable: boolean
17
- showWaitingTip: boolean
18
- sameImageApiInterval: number
19
- timeout: number
20
- customImageApis: string[]
21
- customImageEnabled: boolean
22
- hsjpEnabled: boolean
23
- dmjpEnabled: boolean
24
- ignoreSendError: boolean
25
- retryTimes: number
26
- retryInterval: number
27
- imageSendTimeout: number
28
- autoClearCacheInterval: number
29
- downloadImageBeforeSend: boolean
30
- messageBufferDelay: number
31
- tempDir: string
32
- }
33
-
34
- export const Config: Schema<Config> = Schema.object({
35
- enable: Schema.boolean()
36
- .default(true)
37
- .description('【基础设置】启用插件'),
38
- showWaitingTip: Schema.boolean()
39
- .default(true)
40
- .description('【基础设置】请求图片时显示等待提示'),
41
- sameImageApiInterval: Schema.number()
42
- .default(60)
43
- .min(0)
44
- .description('【基础设置】重复API调用间隔(秒)'),
45
- timeout: Schema.number()
46
- .default(10000)
47
- .min(0)
48
- .description('【基础设置】API请求超时时间(毫秒)'),
49
- customImageApis: Schema.array(Schema.string())
50
- .default([])
51
- .role('textarea')
52
- .description('【基础设置】自定义图片API列表(每行一个地址)'),
53
- customImageEnabled: Schema.boolean()
54
- .default(true)
55
- .description('【功能开关】启用自定义图片API功能'),
56
- hsjpEnabled: Schema.boolean()
57
- .default(true)
58
- .description('【功能开关】启用黑丝举牌功能'),
59
- dmjpEnabled: Schema.boolean()
60
- .default(true)
61
- .description('【功能开关】启用动漫举牌功能'),
62
- ignoreSendError: Schema.boolean()
63
- .default(true)
64
- .description('【容错设置】忽略发送错误'),
65
- retryTimes: Schema.number()
66
- .default(1)
67
- .min(0)
68
- .description('【容错设置】API重试次数'),
69
- retryInterval: Schema.number()
70
- .default(500)
71
- .min(0)
72
- .description('【容错设置】重试间隔(毫秒)'),
73
- imageSendTimeout: Schema.number()
74
- .default(15000)
75
- .min(0)
76
- .description('【容错设置】图片发送超时(毫秒)'),
77
- autoClearCacheInterval: Schema.number()
78
- .default(60)
79
- .min(0)
80
- .description('【缓存设置】自动清理缓存间隔(分钟)'),
81
- downloadImageBeforeSend: Schema.boolean()
82
- .default(true)
83
- .description('【展示设置】发送前下载图片:发送前先下载图片到本地,再发送文件(仅OneBot)'),
84
- messageBufferDelay: Schema.number()
85
- .default(0)
86
- .min(0)
87
- .description('【性能设置】消息缓冲延迟:合并短时间内的多个图片请求(秒)'),
88
- tempDir: Schema.string()
89
- .default(path.join(process.cwd(), 'temp_images'))
90
- .description('【文件设置】临时图片保存目录')
91
- })
92
-
93
- if (!isMainThread) {
94
- const { url, filePath } = workerData;
95
- (async () => {
96
- try {
97
- const response = await axios({
98
- url,
99
- method: 'GET',
100
- responseType: 'stream',
101
- timeout: 60000,
102
- headers: {
103
- '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'
104
- }
105
- });
106
-
107
- await pipeline(response.data, fs.createWriteStream(filePath));
108
- parentPort?.postMessage({ success: true, filePath });
109
- } catch (error) {
110
- parentPort?.postMessage({ success: false, error: (error as Error).message });
111
- }
112
- })();
113
- }
114
-
115
- const processedApi = new Map<string, number>()
116
- const imageBuffer = new Map<string, { apis: string[], timer: NodeJS.Timeout, tipMsgId?: string | number }>()
117
- const delay = (ms: number) => new Promise(r => setTimeout(r, ms))
118
-
119
- async function sendTimeout(session: Session, content: string | any, config: Config) {
120
- const text = typeof content === 'string' ? session.text(content) : content;
121
- if (config.imageSendTimeout <= 0) {
122
- return session.send(text).catch(() => null);
123
- }
124
- return Promise.race([
125
- session.send(text),
126
- new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), config.imageSendTimeout))
127
- ]).catch(() => null);
128
- }
129
-
130
- async function downloadImageWithThreads(url: string, filename: string, config: Config): Promise<string> {
131
- return new Promise((resolve, reject) => {
132
- if (!fs.existsSync(config.tempDir)) {
133
- fs.mkdirSync(config.tempDir, { recursive: true });
134
- }
135
- const filePath = path.join(config.tempDir, `${filename}.jpg`);
136
-
137
- const worker = new Worker(currentFilePath, { workerData: { url, filePath } });
138
- worker.on('message', (result: { success: boolean, filePath?: string, error?: string }) => {
139
- if (result.success && result.filePath) {
140
- resolve(result.filePath);
141
- } else {
142
- reject(new Error(result.error || '图片获取失败'));
143
- }
144
- });
145
- worker.on('error', reject);
146
- worker.on('exit', (code: number) => {
147
- if (code !== 0) reject(new Error('图片下载线程异常'));
148
- });
149
- });
150
- }
151
-
152
- function clearAllCache(config: Config) {
153
- processedApi.clear();
154
- imageBuffer.forEach(buf => clearTimeout(buf.timer));
155
- imageBuffer.clear();
156
-
157
- if (fs.existsSync(config.tempDir)) {
158
- fs.readdirSync(config.tempDir).forEach(file => {
159
- try {
160
- const filePath = path.join(config.tempDir, file);
161
- const stat = fs.statSync(filePath);
162
- if (Date.now() - stat.mtimeMs > 3600000) {
163
- fs.unlinkSync(filePath);
164
- }
165
- } catch (error) {}
166
- });
167
- }
168
- return true;
169
- }
170
-
171
- function randomSelectApi(customImageApis: string[]): string | null {
172
- const validApis = customImageApis.filter(api => api.trim());
173
- if (validApis.length === 0) return null;
174
- return validApis[Math.floor(Math.random() * validApis.length)];
175
- }
176
-
177
- async function fetchImage(url: string, config: Config) {
178
- const http = axios.create({
179
- timeout: config.timeout,
180
- headers: {
181
- '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'
182
- }
183
- });
184
-
185
- for (let retry = 0; retry <= config.retryTimes; retry++) {
186
- try {
187
- if (/^https?:\/\/.+\.(jpg|jpeg|png|gif|webp|bmp)/i.test(url)) {
188
- return { success: true, type: 'url', data: url };
189
- }
190
-
191
- const res = await http.get(url, { responseType: 'arraybuffer' });
192
- if (res.status === 200 && res.data) {
193
- return { success: true, type: 'buffer', data: res.data };
194
- }
195
- } catch (error) {
196
- if (retry === config.retryTimes) return { success: false, type: '', data: '' };
197
- await delay(config.retryInterval);
198
- }
199
- }
200
-
201
- return { success: false, type: '', data: '' };
202
- }
203
-
204
- async function fetchHsjpImage(msg: string, msg1: string, msg2: string, config: Config) {
205
- const encodedMsg = encodeURIComponent(msg);
206
- const encodedMsg1 = encodeURIComponent(msg1);
207
- const encodedMsg2 = encodeURIComponent(msg2);
208
- const url = `https://api.suyanw.cn/api/hsjp/?msg=${encodedMsg}&msg1=${encodedMsg1}&msg2=${encodedMsg2}`;
209
- return fetchImage(url, config);
210
- }
211
-
212
- async function fetchDmjpImage(text: string, config: Config) {
213
- const encodedText = encodeURIComponent(text);
214
- const url = `https://api.suyanw.cn/api/dmjp.php?text=${encodedText}`;
215
- return fetchImage(url, config);
216
- }
217
-
218
- async function processCustomImage(session: Session, apiUrl: string, config: Config) {
219
- const hash = crypto.createHash('md5').update(apiUrl).digest('hex');
220
- const now = Date.now();
221
-
222
- if (processedApi.get(hash) && now - processedApi.get(hash)! < config.sameImageApiInterval * 1000) {
223
- return { success: false, msg: session.text('messages.repeatRequest') };
224
- }
225
- processedApi.set(hash, now);
226
-
227
- const result = await fetchImage(apiUrl, config);
228
- if (!result.success) {
229
- return { success: false, msg: session.text('messages.fetchFailed') };
230
- }
231
-
232
- let imageElem: any;
233
- if (result.type === 'url' && config.downloadImageBeforeSend) {
234
- try {
235
- const filename = crypto.createHash('md5').update(result.data as string).digest('hex');
236
- const filePath = await downloadImageWithThreads(result.data as string, filename, config);
237
- const absPath = path.resolve(filePath);
238
- imageElem = h.file(`file:///${absPath.replace(/\\/g, '/')}`);
239
- } catch (error) {
240
- imageElem = h.image(result.data as string);
241
- }
242
- } else if (result.type === 'buffer') {
243
- if (!fs.existsSync(config.tempDir)) {
244
- fs.mkdirSync(config.tempDir, { recursive: true });
245
- }
246
- const filename = crypto.randomUUID();
247
- const filePath = path.join(config.tempDir, `${filename}.jpg`);
248
- const absPath = path.resolve(filePath);
249
- fs.writeFileSync(absPath, result.data as Buffer);
250
- imageElem = h.file(`file:///${absPath.replace(/\\/g, '/')}`);
251
- } else {
252
- imageElem = h.image(result.data as string);
253
- }
254
-
255
- return { success: true, msg: 'ok', data: { text: session.text('messages.fetchSuccess'), image: imageElem } };
256
- }
257
-
258
- async function flush(session: Session, config: Config, manualApi?: string) {
259
- const key = `${session.platform}:${session.userId}:${session.channelId}`;
260
- const buf = imageBuffer.get(key);
261
- const apis = manualApi ? [manualApi] : buf?.apis || [];
262
-
263
- if (buf) {
264
- clearTimeout(buf.timer);
265
- imageBuffer.delete(key);
266
- }
267
-
268
- if (!config.customImageEnabled || !apis.length) return;
269
-
270
- const items: any[] = [];
271
- const errs: string[] = [];
272
-
273
- for (const apiUrl of apis) {
274
- const result = await processCustomImage(session, apiUrl, config);
275
- if (result.success && result.data) {
276
- items.push(result.data);
277
- } else {
278
- errs.push(`【${apiUrl.slice(0, 22)}...】:${result.msg}`);
279
- }
280
- }
281
-
282
- if (errs.length && !config.ignoreSendError) {
283
- const errorMsg = `${session.text('messages.partialFailed')}\n${errs.join('\n')}`;
284
- await sendTimeout(session, errorMsg, config);
285
- await delay(600);
286
- }
287
-
288
- if (items.length === 0) {
289
- const failMsg = `${session.text('messages.allFailed')}\n${errs.join('\n')}`;
290
- if (!config.ignoreSendError) {
291
- await sendTimeout(session, failMsg, config);
292
- }
293
- return;
294
- }
295
-
296
- for (const item of items) {
297
- await sendTimeout(session, item.text, config);
298
- await delay(300);
299
- await sendTimeout(session, item.image, config);
300
- await delay(1000);
301
- }
302
- }
303
-
304
- export function apply(ctx: Context, config: Config) {
305
- if (!isMainThread) return;
306
-
307
- ctx.i18n.define('zh-CN', zhCN);
308
-
309
- clearAllCache(config);
310
- ctx.logger.info('[custom-image] 插件已加载');
311
-
312
- ctx.command('random-image [apis...]', '#commands.random-image')
313
- .option('api', '-a <api> #commands.random-image.options.api')
314
- .action(async ({ session, options = {} }, ...apis) => {
315
- if (!config.enable || !config.customImageEnabled || !session) return;
316
-
317
- const targetApi = options.api || (apis.length ? apis.join(' ') : null);
318
- if (targetApi) {
319
- await flush(session, config, targetApi);
320
- } else {
321
- const key = `${session.platform}:${session.userId}:${session.channelId}`;
322
-
323
- let tipId;
324
- if (config.showWaitingTip) {
325
- const tip = await sendTimeout(session, 'messages.fetchWaiting', config);
326
- tipId = (tip as any)?.messageId || (tip as any)?.id || tip;
327
- }
328
-
329
- if (imageBuffer.has(key)) {
330
- const b = imageBuffer.get(key)!;
331
- clearTimeout(b.timer);
332
- b.timer = setTimeout(() => flush(session, config), config.messageBufferDelay * 1000);
333
- } else {
334
- imageBuffer.set(key, {
335
- apis: config.customImageApis,
336
- timer: setTimeout(() => flush(session, config), config.messageBufferDelay * 1000),
337
- tipMsgId: tipId
338
- });
339
- }
340
- }
341
- });
342
-
343
- ctx.command('hsjp <msg> [msg1] [msg2]', '#commands.hsjp')
344
- .action(async ({ session }, msg, msg1 = '', msg2 = '') => {
345
- if (!config.enable || !config.hsjpEnabled || !session) return;
346
-
347
- if (!msg || msg.trim().length === 0) {
348
- await sendTimeout(session, 'messages.inputError', config);
349
- return;
350
- }
351
-
352
- if (config.showWaitingTip) {
353
- await sendTimeout(session, 'messages.fetchWaiting', config);
354
- }
355
-
356
- const result = await fetchHsjpImage(msg, msg1, msg2, config);
357
- if (result.success) {
358
- let imageElem: any;
359
- if (result.type === 'url' && config.downloadImageBeforeSend) {
360
- try {
361
- const filename = crypto.createHash('md5').update(result.data as string).digest('hex');
362
- const filePath = await downloadImageWithThreads(result.data as string, filename, config);
363
- const absPath = path.resolve(filePath);
364
- imageElem = h.file(`file:///${absPath.replace(/\\/g, '/')}`);
365
- } catch (error) {
366
- imageElem = h.image(result.data as string);
367
- }
368
- } else if (result.type === 'buffer') {
369
- if (!fs.existsSync(config.tempDir)) {
370
- fs.mkdirSync(config.tempDir, { recursive: true });
371
- }
372
- const filename = crypto.randomUUID();
373
- const filePath = path.join(config.tempDir, `${filename}.jpg`);
374
- const absPath = path.resolve(filePath);
375
- fs.writeFileSync(absPath, result.data as Buffer);
376
- imageElem = h.file(`file:///${absPath.replace(/\\/g, '/')}`);
377
- } else {
378
- imageElem = h.image(result.data as string);
379
- }
380
- await sendTimeout(session, 'messages.fetchSuccess', config);
381
- await sendTimeout(session, imageElem, config);
382
- }
383
- });
384
-
385
- ctx.command('dmjp <text>', '#commands.dmjp')
386
- .action(async ({ session }, text) => {
387
- if (!config.enable || !config.dmjpEnabled || !session) return;
388
-
389
- if (!text || text.trim().length === 0) {
390
- await sendTimeout(session, 'messages.inputError', config);
391
- return;
392
- }
393
-
394
- if (config.showWaitingTip) {
395
- await sendTimeout(session, 'messages.fetchWaiting', config);
396
- }
397
-
398
- const result = await fetchDmjpImage(text, config);
399
- if (result.success) {
400
- let imageElem: any;
401
- if (result.type === 'url' && config.downloadImageBeforeSend) {
402
- try {
403
- const filename = crypto.createHash('md5').update(result.data as string).digest('hex');
404
- const filePath = await downloadImageWithThreads(result.data as string, filename, config);
405
- const absPath = path.resolve(filePath);
406
- imageElem = h.file(`file:///${absPath.replace(/\\/g, '/')}`);
407
- } catch (error) {
408
- imageElem = h.image(result.data as string);
409
- }
410
- } else if (result.type === 'buffer') {
411
- if (!fs.existsSync(config.tempDir)) {
412
- fs.mkdirSync(config.tempDir, { recursive: true });
413
- }
414
- const filename = crypto.randomUUID();
415
- const filePath = path.join(config.tempDir, `${filename}.jpg`);
416
- const absPath = path.resolve(filePath);
417
- fs.writeFileSync(absPath, result.data as Buffer);
418
- imageElem = h.file(`file:///${absPath.replace(/\\/g, '/')}`);
419
- } else {
420
- imageElem = h.image(result.data as string);
421
- }
422
- await sendTimeout(session, 'messages.fetchSuccess', config);
423
- await sendTimeout(session, imageElem, config);
424
- }
425
- });
426
-
427
- ctx.command('clear-image-cache', '#commands.clear-image-cache')
428
- .action(({ session }) => {
429
- clearAllCache(config);
430
- return session ? session.text('messages.cacheCleared') : '✅ 图片缓存已清空';
431
- });
432
-
433
- setInterval(() => {
434
- const now = Date.now();
435
- processedApi.forEach((timestamp, hash) => {
436
- if (now - timestamp > 86400000) processedApi.delete(hash);
437
- });
438
- }, 3600000);
439
-
440
- setInterval(() => {
441
- if (!fs.existsSync(config.tempDir)) return;
442
-
443
- const now = Date.now();
444
- fs.readdirSync(config.tempDir).forEach(file => {
445
- try {
446
- const stat = fs.statSync(path.join(config.tempDir, file));
447
- if (now - stat.mtimeMs > 3600000) {
448
- fs.unlinkSync(path.join(config.tempDir, file));
449
- }
450
- } catch (error) {}
451
- });
452
- }, 1800000);
453
-
454
- if (config.autoClearCacheInterval > 0) {
455
- setInterval(() => {
456
- clearAllCache(config);
457
- ctx.logger.info('[custom-image] 缓存已自动清理');
458
- }, config.autoClearCacheInterval * 60000);
459
- }
460
-
461
- process.on('exit', () => {
462
- clearAllCache(config);
463
- ctx.logger.info('[custom-image] 插件缓存已清理');
464
- });
465
- }
466
-
467
- export const inject = {
468
- optional: ['i18n']
469
- }
470
-
471
- export const using = [] as const
@@ -1,16 +0,0 @@
1
- commands:
2
- random-image: 随机获取自定义图片
3
- random-image.options.api: 指定单个API地址
4
- hsjp: 生成黑丝举牌图片
5
- dmjp: 生成动漫举牌图片
6
- clear-image-cache: 清空图片缓存
7
-
8
- messages:
9
- inputError: ❌ 指令输入错误:请输入举牌文字
10
- cacheCleared: ✅ 图片缓存已清空
11
- fetchSuccess: ✅ 图片获取成功
12
- fetchWaiting: 正在获取图片,请稍候...
13
- repeatRequest: 请勿重复请求
14
- fetchFailed: 图片获取失败
15
- partialFailed: ⚠️ 部分图片获取失败
16
- allFailed: ❌ 所有图片获取失败
package/src/shims.d.ts DELETED
@@ -1,9 +0,0 @@
1
- declare module '*.yml' {
2
- const content: Record<string, any>;
3
- export default content;
4
- }
5
-
6
- declare module '*.yaml' {
7
- const content: Record<string, any>;
8
- export default content;
9
- }