koishi-plugin-custom-image 0.2.8 → 0.2.9
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 +183 -85
- package/package.json +1 -1
- package/readme.md +70 -70
package/lib/index.js
CHANGED
|
@@ -20,7 +20,7 @@ const locales = {
|
|
|
20
20
|
'messages.fetchSuccess': '图片获取成功!',
|
|
21
21
|
'messages.fetchWaiting': '正在获取图片,请稍候...',
|
|
22
22
|
'messages.inputError': '输入内容不能为空!',
|
|
23
|
-
'messages.cacheCleared': '
|
|
23
|
+
'messages.cacheCleared': '图片缓存已清空',
|
|
24
24
|
'commands.random-image': '随机获取图片',
|
|
25
25
|
'commands.random-image.options.api': '指定图片API地址',
|
|
26
26
|
'commands.hsjp': '生成黑丝举牌图片',
|
|
@@ -28,29 +28,21 @@ const locales = {
|
|
|
28
28
|
'commands.clear-image-cache': '清空图片缓存'
|
|
29
29
|
}
|
|
30
30
|
};
|
|
31
|
-
const currentFilePath = worker_threads_1.isMainThread
|
|
32
|
-
? __filename
|
|
33
|
-
: path_1.default.join(process.cwd(), worker_threads_1.isMainThread ? 'src/index.ts' : 'lib/index.js');
|
|
34
|
-
exports.name = 'custom-image';
|
|
35
|
-
exports.Config = koishi_1.Schema.object({
|
|
36
|
-
enable: koishi_1.Schema.boolean().default(true).description('启用插件'),
|
|
37
|
-
showWaitingTip: koishi_1.Schema.boolean().default(true).description('请求图片时显示等待提示'),
|
|
38
|
-
timeout: koishi_1.Schema.number().default(10000).min(0).description('API请求超时时间(毫秒)'),
|
|
39
|
-
customImageApis: koishi_1.Schema.array(koishi_1.Schema.string()).default([]).role('textarea').description('自定义图片API列表'),
|
|
40
|
-
customImageEnabled: koishi_1.Schema.boolean().default(true).description('启用自定义图片API功能'),
|
|
41
|
-
hsjpEnabled: koishi_1.Schema.boolean().default(true).description('启用黑丝举牌功能'),
|
|
42
|
-
dmjpEnabled: koishi_1.Schema.boolean().default(true).description('启用动漫举牌功能'),
|
|
43
|
-
imageSendTimeout: koishi_1.Schema.number().default(15000).min(0).description('图片发送超时(毫秒)'),
|
|
44
|
-
autoClearCacheInterval: koishi_1.Schema.number().default(60).min(0).description('自动清理缓存间隔(分钟)'),
|
|
45
|
-
tempDir: koishi_1.Schema.string().default(path_1.default.join(process.cwd(), 'temp_images')).description('临时图片保存目录'),
|
|
46
|
-
showSuccessTip: koishi_1.Schema.boolean().default(true).description('请求图片成功时显示提示'),
|
|
47
|
-
showTimeoutTip: koishi_1.Schema.boolean().default(true).description('API请求超时后给用户提示')
|
|
48
|
-
});
|
|
49
31
|
if (!worker_threads_1.isMainThread) {
|
|
50
32
|
const { url, filePath } = worker_threads_1.workerData;
|
|
51
33
|
(async () => {
|
|
52
34
|
try {
|
|
53
|
-
const response = await (0, axios_1.default)({
|
|
35
|
+
const response = await (0, axios_1.default)({
|
|
36
|
+
url,
|
|
37
|
+
method: 'GET',
|
|
38
|
+
responseType: 'stream',
|
|
39
|
+
timeout: 60000,
|
|
40
|
+
maxRedirects: 5,
|
|
41
|
+
maxContentLength: 50 * 1024 * 1024,
|
|
42
|
+
headers: {
|
|
43
|
+
'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'
|
|
44
|
+
}
|
|
45
|
+
});
|
|
54
46
|
await (0, promises_1.pipeline)(response.data, fs_1.default.createWriteStream(filePath));
|
|
55
47
|
worker_threads_1.parentPort?.postMessage({ success: true, filePath });
|
|
56
48
|
}
|
|
@@ -59,51 +51,117 @@ if (!worker_threads_1.isMainThread) {
|
|
|
59
51
|
}
|
|
60
52
|
})();
|
|
61
53
|
}
|
|
54
|
+
const httpClient = axios_1.default.create({
|
|
55
|
+
timeout: 30000,
|
|
56
|
+
maxRedirects: 5,
|
|
57
|
+
maxContentLength: 50 * 1024 * 1024,
|
|
58
|
+
headers: {
|
|
59
|
+
'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'
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const rateLimitMap = new Map();
|
|
63
|
+
const RATE_LIMIT_MS = 3000;
|
|
64
|
+
function checkRateLimit(userId) {
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
const last = rateLimitMap.get(userId);
|
|
67
|
+
if (last && now - last < RATE_LIMIT_MS)
|
|
68
|
+
return false;
|
|
69
|
+
rateLimitMap.set(userId, now);
|
|
70
|
+
if (rateLimitMap.size > 10000) {
|
|
71
|
+
for (const [key, time] of rateLimitMap) {
|
|
72
|
+
if (now - time > RATE_LIMIT_MS * 2)
|
|
73
|
+
rateLimitMap.delete(key);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
function isValidUrl(url) {
|
|
79
|
+
try {
|
|
80
|
+
const parsed = new URL(url);
|
|
81
|
+
if (!['http:', 'https:'].includes(parsed.protocol))
|
|
82
|
+
return false;
|
|
83
|
+
if (parsed.username || parsed.password)
|
|
84
|
+
return false;
|
|
85
|
+
if (!parsed.hostname)
|
|
86
|
+
return false;
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
62
93
|
const delay = (ms) => new Promise(r => setTimeout(r, ms));
|
|
63
94
|
function getI18nText(session, key) {
|
|
64
95
|
if (session?.text)
|
|
65
96
|
return session.text(key);
|
|
66
97
|
const lang = 'zh-CN';
|
|
67
|
-
return locales[lang][key]
|
|
98
|
+
return locales[lang]?.[key] ?? key;
|
|
68
99
|
}
|
|
69
100
|
async function sendTimeout(session, content, config) {
|
|
70
101
|
const text = typeof content === 'string' ? getI18nText(session, content) : content;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
102
|
+
const timeoutMs = config.imageSendTimeout;
|
|
103
|
+
if (timeoutMs <= 0) {
|
|
104
|
+
await session.send(text).catch(() => { });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
let timerId;
|
|
108
|
+
const timeout = new Promise(resolve => { timerId = setTimeout(() => resolve('timeout'), timeoutMs); });
|
|
109
|
+
await Promise.race([session.send(text).then(() => 'sent', () => 'error'), timeout]);
|
|
110
|
+
clearTimeout(timerId);
|
|
74
111
|
}
|
|
75
|
-
function clearAllCache(config) {
|
|
76
|
-
|
|
77
|
-
|
|
112
|
+
async function clearAllCache(config) {
|
|
113
|
+
let count = 0;
|
|
114
|
+
try {
|
|
115
|
+
const resolvedDir = path_1.default.resolve(config.tempDir);
|
|
116
|
+
const entries = await fs_1.default.promises.readdir(resolvedDir);
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
for (const entry of entries) {
|
|
78
119
|
try {
|
|
79
|
-
const filePath = path_1.default.join(
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
82
|
-
|
|
120
|
+
const filePath = path_1.default.join(resolvedDir, entry);
|
|
121
|
+
const realPath = await fs_1.default.promises.realpath(filePath);
|
|
122
|
+
if (!realPath.startsWith(resolvedDir + path_1.default.sep))
|
|
123
|
+
continue;
|
|
124
|
+
const stat = await fs_1.default.promises.stat(filePath);
|
|
125
|
+
if (!stat.isFile())
|
|
126
|
+
continue;
|
|
127
|
+
if (now - stat.mtimeMs > 3600000) {
|
|
128
|
+
await fs_1.default.promises.unlink(filePath);
|
|
129
|
+
count++;
|
|
130
|
+
}
|
|
83
131
|
}
|
|
84
|
-
catch
|
|
85
|
-
}
|
|
132
|
+
catch { }
|
|
133
|
+
}
|
|
86
134
|
}
|
|
87
|
-
|
|
135
|
+
catch { }
|
|
136
|
+
return count;
|
|
88
137
|
}
|
|
89
138
|
function randomSelectApi(customImageApis) {
|
|
90
|
-
const validApis = customImageApis.filter(api => api.trim());
|
|
139
|
+
const validApis = customImageApis.filter(api => api && api.trim());
|
|
91
140
|
if (validApis.length === 0)
|
|
92
141
|
return null;
|
|
93
142
|
return validApis[Math.floor(Math.random() * validApis.length)];
|
|
94
143
|
}
|
|
95
144
|
async function fetchImage(url, config) {
|
|
96
|
-
|
|
145
|
+
if (!isValidUrl(url)) {
|
|
146
|
+
return { success: false, type: '', data: '', timeout: false };
|
|
147
|
+
}
|
|
148
|
+
const imageUrlPattern = /^https?:\/\/.+\.(jpg|jpeg|png|gif|webp|bmp)(\?.*)?$/i;
|
|
149
|
+
if (imageUrlPattern.test(url)) {
|
|
150
|
+
return { success: true, type: 'url', data: url, timeout: false };
|
|
151
|
+
}
|
|
97
152
|
try {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
153
|
+
const res = await httpClient.get(url, {
|
|
154
|
+
responseType: 'arraybuffer',
|
|
155
|
+
timeout: config.timeout
|
|
156
|
+
});
|
|
157
|
+
if (res.status >= 200 && res.status < 300 && res.data) {
|
|
158
|
+
const contentType = res.headers['content-type'];
|
|
159
|
+
return { success: true, type: 'buffer', data: res.data, timeout: false, contentType };
|
|
160
|
+
}
|
|
103
161
|
}
|
|
104
162
|
catch (error) {
|
|
105
|
-
const isTimeout = error.message.includes('timeout');
|
|
106
|
-
return { success: false, type: '', data: '', timeout: isTimeout };
|
|
163
|
+
const isTimeout = error.message?.toLowerCase().includes('timeout');
|
|
164
|
+
return { success: false, type: '', data: '', timeout: !!isTimeout };
|
|
107
165
|
}
|
|
108
166
|
return { success: false, type: '', data: '', timeout: false };
|
|
109
167
|
}
|
|
@@ -119,27 +177,76 @@ async function fetchDmjpImage(text, config) {
|
|
|
119
177
|
const url = `https://api.suyanw.cn/api/dmjp.php?text=${encodedText}`;
|
|
120
178
|
return fetchImage(url, config);
|
|
121
179
|
}
|
|
180
|
+
function buildImageElement(result) {
|
|
181
|
+
if (result.type === 'url') {
|
|
182
|
+
return koishi_1.h.image(result.data);
|
|
183
|
+
}
|
|
184
|
+
const mime = result.contentType || 'image/jpeg';
|
|
185
|
+
return koishi_1.h.image(`data:${mime};base64,${Buffer.from(result.data).toString('base64')}`);
|
|
186
|
+
}
|
|
122
187
|
async function processCustomImage(session, apiUrl, config) {
|
|
123
188
|
const result = await fetchImage(apiUrl, config);
|
|
124
189
|
if (result.timeout && config.showTimeoutTip)
|
|
125
190
|
return { success: false, msg: '图片请求超时,请稍后再试' };
|
|
126
191
|
if (!result.success)
|
|
127
192
|
return { success: false, msg: getI18nText(session, 'messages.fetchFailed') };
|
|
128
|
-
const imageElem =
|
|
193
|
+
const imageElem = buildImageElement(result);
|
|
129
194
|
const successText = config.showSuccessTip ? getI18nText(session, 'messages.fetchSuccess') : '';
|
|
130
195
|
return { success: true, msg: 'ok', data: { text: successText, image: imageElem } };
|
|
131
196
|
}
|
|
197
|
+
async function processFunImage(session, config, fetchFn) {
|
|
198
|
+
const result = await fetchFn();
|
|
199
|
+
if (result.timeout && config.showTimeoutTip) {
|
|
200
|
+
await sendTimeout(session, '图片请求超时,请稍后再试', config);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (result.success) {
|
|
204
|
+
const imageElem = buildImageElement(result);
|
|
205
|
+
if (config.showSuccessTip) {
|
|
206
|
+
await sendTimeout(session, 'messages.fetchSuccess', config);
|
|
207
|
+
await delay(300);
|
|
208
|
+
}
|
|
209
|
+
await sendTimeout(session, imageElem, config);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
await sendTimeout(session, 'messages.fetchFailed', config);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
exports.name = 'custom-image';
|
|
216
|
+
exports.Config = koishi_1.Schema.object({
|
|
217
|
+
enable: koishi_1.Schema.boolean().default(true).description('启用插件'),
|
|
218
|
+
showWaitingTip: koishi_1.Schema.boolean().default(true).description('请求图片时显示等待提示'),
|
|
219
|
+
timeout: koishi_1.Schema.number().default(10000).min(0).description('API请求超时时间(毫秒)'),
|
|
220
|
+
customImageApis: koishi_1.Schema.array(koishi_1.Schema.string()).default([]).role('textarea').description('自定义图片API列表'),
|
|
221
|
+
customImageEnabled: koishi_1.Schema.boolean().default(true).description('启用自定义图片API功能'),
|
|
222
|
+
hsjpEnabled: koishi_1.Schema.boolean().default(true).description('启用黑丝举牌功能'),
|
|
223
|
+
dmjpEnabled: koishi_1.Schema.boolean().default(true).description('启用动漫举牌功能'),
|
|
224
|
+
imageSendTimeout: koishi_1.Schema.number().default(15000).min(0).description('图片发送超时(毫秒)'),
|
|
225
|
+
autoClearCacheInterval: koishi_1.Schema.number().default(60).min(0).description('自动清理缓存间隔(分钟)'),
|
|
226
|
+
tempDir: koishi_1.Schema.string().default(path_1.default.join(process.cwd(), 'temp_images')).description('临时图片保存目录'),
|
|
227
|
+
showSuccessTip: koishi_1.Schema.boolean().default(true).description('请求图片成功时显示提示'),
|
|
228
|
+
showTimeoutTip: koishi_1.Schema.boolean().default(true).description('API请求超时后给用户提示')
|
|
229
|
+
});
|
|
132
230
|
function apply(ctx, config) {
|
|
133
231
|
if (!worker_threads_1.isMainThread)
|
|
134
232
|
return;
|
|
135
|
-
|
|
136
|
-
|
|
233
|
+
if (ctx.i18n) {
|
|
234
|
+
Object.keys(locales).forEach(lang => {
|
|
235
|
+
ctx.i18n.define(lang, locales[lang]);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
clearAllCache(config).catch(() => { });
|
|
137
239
|
ctx.logger.info('[custom-image] 插件已加载');
|
|
138
240
|
ctx.command('random-image [apis...]', locales['zh-CN']['commands.random-image'])
|
|
139
241
|
.option('api', `-a <api> ${locales['zh-CN']['commands.random-image.options.api']}`)
|
|
140
242
|
.action(async ({ session, options = {} }, ...apis) => {
|
|
141
243
|
if (!config.enable || !config.customImageEnabled || !session)
|
|
142
244
|
return;
|
|
245
|
+
const userId = session.userId || session.uid || 'unknown';
|
|
246
|
+
if (!checkRateLimit(userId)) {
|
|
247
|
+
await sendTimeout(session, 'messages.repeatRequest', config);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
143
250
|
const targetApi = options.api || (apis.length ? apis.join(' ') : null);
|
|
144
251
|
if (config.showWaitingTip)
|
|
145
252
|
await sendTimeout(session, 'messages.fetchWaiting', config);
|
|
@@ -162,65 +269,56 @@ function apply(ctx, config) {
|
|
|
162
269
|
.action(async ({ session }, msg, msg1 = '', msg2 = '') => {
|
|
163
270
|
if (!config.enable || !config.hsjpEnabled || !session)
|
|
164
271
|
return;
|
|
165
|
-
if (!msg || msg.trim()
|
|
272
|
+
if (!msg || !msg.trim()) {
|
|
166
273
|
await sendTimeout(session, 'messages.inputError', config);
|
|
167
274
|
return;
|
|
168
275
|
}
|
|
169
|
-
if (
|
|
170
|
-
await sendTimeout(session, 'messages.
|
|
171
|
-
const result = await fetchHsjpImage(msg, msg1, msg2, config);
|
|
172
|
-
if (result.timeout && config.showTimeoutTip) {
|
|
173
|
-
await sendTimeout(session, '图片请求超时,请稍后再试', config);
|
|
276
|
+
if (!checkRateLimit(session.userId || session.uid || 'unknown')) {
|
|
277
|
+
await sendTimeout(session, 'messages.repeatRequest', config);
|
|
174
278
|
return;
|
|
175
279
|
}
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
await sendTimeout(session, 'messages.fetchSuccess', config);
|
|
180
|
-
await delay(300);
|
|
181
|
-
}
|
|
182
|
-
await sendTimeout(session, imageElem, config);
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
await sendTimeout(session, 'messages.fetchFailed', config);
|
|
186
|
-
}
|
|
280
|
+
if (config.showWaitingTip)
|
|
281
|
+
await sendTimeout(session, 'messages.fetchWaiting', config);
|
|
282
|
+
await processFunImage(session, config, () => fetchHsjpImage(msg, msg1, msg2, config));
|
|
187
283
|
});
|
|
188
284
|
ctx.command('dmjp <text>', locales['zh-CN']['commands.dmjp'])
|
|
189
285
|
.action(async ({ session }, text) => {
|
|
190
286
|
if (!config.enable || !config.dmjpEnabled || !session)
|
|
191
287
|
return;
|
|
192
|
-
if (!text || text.trim()
|
|
288
|
+
if (!text || !text.trim()) {
|
|
193
289
|
await sendTimeout(session, 'messages.inputError', config);
|
|
194
290
|
return;
|
|
195
291
|
}
|
|
196
|
-
if (
|
|
197
|
-
await sendTimeout(session, 'messages.
|
|
198
|
-
const result = await fetchDmjpImage(text, config);
|
|
199
|
-
if (result.timeout && config.showTimeoutTip) {
|
|
200
|
-
await sendTimeout(session, '图片请求超时,请稍后再试', config);
|
|
292
|
+
if (!checkRateLimit(session.userId || session.uid || 'unknown')) {
|
|
293
|
+
await sendTimeout(session, 'messages.repeatRequest', config);
|
|
201
294
|
return;
|
|
202
295
|
}
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
await sendTimeout(session, 'messages.fetchSuccess', config);
|
|
207
|
-
await delay(300);
|
|
208
|
-
}
|
|
209
|
-
await sendTimeout(session, imageElem, config);
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
await sendTimeout(session, 'messages.fetchFailed', config);
|
|
213
|
-
}
|
|
296
|
+
if (config.showWaitingTip)
|
|
297
|
+
await sendTimeout(session, 'messages.fetchWaiting', config);
|
|
298
|
+
await processFunImage(session, config, () => fetchDmjpImage(text, config));
|
|
214
299
|
});
|
|
215
300
|
ctx.command('clear-image-cache', locales['zh-CN']['commands.clear-image-cache'])
|
|
216
|
-
.action(({ session }) => {
|
|
217
|
-
clearAllCache(config);
|
|
218
|
-
|
|
301
|
+
.action(async ({ session }) => {
|
|
302
|
+
const count = await clearAllCache(config);
|
|
303
|
+
const base = session ? getI18nText(session, 'messages.cacheCleared') : '图片缓存已清空';
|
|
304
|
+
return `${base}(已清理 ${count} 个文件)`;
|
|
219
305
|
});
|
|
220
306
|
if (config.autoClearCacheInterval > 0) {
|
|
221
|
-
setInterval(() => {
|
|
307
|
+
setInterval(() => {
|
|
308
|
+
clearAllCache(config).then(() => {
|
|
309
|
+
ctx.logger.info('[custom-image] 缓存已自动清理');
|
|
310
|
+
}).catch(() => { });
|
|
311
|
+
}, config.autoClearCacheInterval * 60000);
|
|
222
312
|
}
|
|
223
|
-
process.on('exit', () => {
|
|
313
|
+
process.on('exit', () => {
|
|
314
|
+
fs_1.default.existsSync(config.tempDir) && fs_1.default.readdirSync(config.tempDir).forEach(file => {
|
|
315
|
+
try {
|
|
316
|
+
fs_1.default.unlinkSync(path_1.default.join(config.tempDir, file));
|
|
317
|
+
}
|
|
318
|
+
catch { }
|
|
319
|
+
});
|
|
320
|
+
ctx.logger.info('[custom-image] 插件缓存已清理');
|
|
321
|
+
});
|
|
224
322
|
}
|
|
225
323
|
exports.inject = { optional: ['i18n'] };
|
|
226
324
|
exports.using = [];
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
# koishi-plugin-custom-image
|
|
2
|
-
|
|
3
|
-
## 项目介绍 (Project Introduction)
|
|
4
|
-
|
|
5
|
-
### 中文
|
|
6
|
-
这是一个为 Koishi 机器人框架开发的**随机图片获取插件**,支持从自定义 API 列表中随机选取接口获取图片,同时内置黑丝举牌、动漫举牌等趣味图片功能。核心特性:
|
|
7
|
-
- 🚀 从自定义 API 列表中**随机单张选取**,不批量、不轮询
|
|
8
|
-
- 🖼️ 强制使用图片 URL 直连发送
|
|
9
|
-
- 💡 可配置等待提示、成功提示、超时提示
|
|
10
|
-
- ⚙️ 简单易用的指令与配置项,开箱即用
|
|
11
|
-
- 🧹 支持自动清理缓存与手动清理指令
|
|
12
|
-
- 🎭 内置 `hsjp`、`dmjp` 等趣味图片接口
|
|
13
|
-
|
|
14
|
-
### English
|
|
15
|
-
A random image plugin for the Koishi bot framework. It randomly selects one API from your custom API list to fetch images, with built-in fun image APIs such as haisi and dongman. Core features:
|
|
16
|
-
- 🚀 Randomly select **one API** from the custom list, no batch / loop requests
|
|
17
|
-
- 🖼️ Force the use of image URLs for direct connection sending
|
|
18
|
-
- 💡 Configurable waiting, success, and timeout tips
|
|
19
|
-
- ⚙️ Simple commands and configurations, ready to use
|
|
20
|
-
- 🧹 Auto cache cleanup & manual clear command
|
|
21
|
-
- 🎭 Built-in fun image APIs: `hsjp`, `dmjp`
|
|
22
|
-
|
|
23
|
-
## 项目仓库 (Repository)
|
|
24
|
-
- GitHub: https://github.com/Minecraft-1314/koishi-plugin-custom-image
|
|
25
|
-
- Issues: https://github.com/Minecraft-1314/koishi-plugin-custom-image/issues
|
|
26
|
-
|
|
27
|
-
## 核心指令 (Core Commands)
|
|
28
|
-
|
|
29
|
-
| 指令 (Command) | 说明 (Description) | 示例 (Example) |
|
|
30
|
-
|----------------|--------------------|----------------|
|
|
31
|
-
| `random-image` | 从自定义 API 列表随机获取一张图片 | `random-image` |
|
|
32
|
-
| `hsjp <msg> [msg1] [msg2]` | 生成黑丝举牌图片 | `hsjp 我喜欢你` |
|
|
33
|
-
| `dmjp <text>` | 生成动漫举牌图片 | `dmjp Koishi 真棒` |
|
|
34
|
-
| `clear-image-cache` | 清空图片缓存 | `clear-image-cache` |
|
|
35
|
-
|
|
36
|
-
## 配置项说明 (Configuration)
|
|
37
|
-
|
|
38
|
-
| 配置项 (Config Item) | 类型 (Type) | 默认值 (Default) | 说明 (Description) |
|
|
39
|
-
|----------------------|-------------|------------------|--------------------|
|
|
40
|
-
| `enable` | boolean | true | 是否启用插件 |
|
|
41
|
-
| `showWaitingTip` | boolean | true | 请求图片时是否显示等待提示 |
|
|
42
|
-
| `timeout` | number | 10000 | API 请求超时时间(毫秒) |
|
|
43
|
-
| `customImageApis` | string[] | [] | 自定义图片 API 列表 |
|
|
44
|
-
| `customImageEnabled` | boolean | true | 是否启用自定义图片功能 |
|
|
45
|
-
| `hsjpEnabled` | boolean | true | 是否启用黑丝举牌功能 |
|
|
46
|
-
| `dmjpEnabled` | boolean | true | 是否启用动漫举牌功能 |
|
|
47
|
-
| `imageSendTimeout` | number | 15000 | 图片发送超时(毫秒) |
|
|
48
|
-
| `autoClearCacheInterval` | number | 60 | 自动清理缓存间隔(分钟) |
|
|
49
|
-
| `tempDir` | string | ./temp_images | 临时图片保存目录 |
|
|
50
|
-
| `showSuccessTip` | boolean | true | 请求图片成功时是否显示提示 |
|
|
51
|
-
| `showTimeoutTip` | boolean | true | API 请求超时后是否给用户提示 |
|
|
52
|
-
|
|
53
|
-
## 项目贡献者 (Contributors)
|
|
54
|
-
|
|
55
|
-
| 贡献者 (Contributor) | 贡献内容 (Contribution) |
|
|
56
|
-
|----------------------|-------------------------|
|
|
57
|
-
| Minecraft-1314 | 插件完整开发 (Complete plugin development) |
|
|
58
|
-
| 素颜API | 提供 API 接口支持 |
|
|
59
|
-
| (欢迎提交 PR 加入贡献者列表) | (Welcome to submit PR to join the contributor list) |
|
|
60
|
-
|
|
61
|
-
## 许可协议 (License)
|
|
62
|
-
|
|
63
|
-
本项目采用 MIT 许可证,详情参见 [LICENSE](LICENSE) 文件。
|
|
64
|
-
|
|
65
|
-
This project is licensed under the MIT License, see the [LICENSE](LICENSE) file for details.
|
|
66
|
-
|
|
67
|
-
## 支持我们 (Support Us)
|
|
68
|
-
|
|
69
|
-
如果这个项目对您有帮助,欢迎点亮右上角的 Star ⭐ 支持我们,这将是对所有贡献者最大的鼓励!
|
|
70
|
-
|
|
1
|
+
# koishi-plugin-custom-image
|
|
2
|
+
|
|
3
|
+
## 项目介绍 (Project Introduction)
|
|
4
|
+
|
|
5
|
+
### 中文
|
|
6
|
+
这是一个为 Koishi 机器人框架开发的**随机图片获取插件**,支持从自定义 API 列表中随机选取接口获取图片,同时内置黑丝举牌、动漫举牌等趣味图片功能。核心特性:
|
|
7
|
+
- 🚀 从自定义 API 列表中**随机单张选取**,不批量、不轮询
|
|
8
|
+
- 🖼️ 强制使用图片 URL 直连发送
|
|
9
|
+
- 💡 可配置等待提示、成功提示、超时提示
|
|
10
|
+
- ⚙️ 简单易用的指令与配置项,开箱即用
|
|
11
|
+
- 🧹 支持自动清理缓存与手动清理指令
|
|
12
|
+
- 🎭 内置 `hsjp`、`dmjp` 等趣味图片接口
|
|
13
|
+
|
|
14
|
+
### English
|
|
15
|
+
A random image plugin for the Koishi bot framework. It randomly selects one API from your custom API list to fetch images, with built-in fun image APIs such as haisi and dongman. Core features:
|
|
16
|
+
- 🚀 Randomly select **one API** from the custom list, no batch / loop requests
|
|
17
|
+
- 🖼️ Force the use of image URLs for direct connection sending
|
|
18
|
+
- 💡 Configurable waiting, success, and timeout tips
|
|
19
|
+
- ⚙️ Simple commands and configurations, ready to use
|
|
20
|
+
- 🧹 Auto cache cleanup & manual clear command
|
|
21
|
+
- 🎭 Built-in fun image APIs: `hsjp`, `dmjp`
|
|
22
|
+
|
|
23
|
+
## 项目仓库 (Repository)
|
|
24
|
+
- GitHub: https://github.com/Minecraft-1314/koishi-plugin-custom-image
|
|
25
|
+
- Issues: https://github.com/Minecraft-1314/koishi-plugin-custom-image/issues
|
|
26
|
+
|
|
27
|
+
## 核心指令 (Core Commands)
|
|
28
|
+
|
|
29
|
+
| 指令 (Command) | 说明 (Description) | 示例 (Example) |
|
|
30
|
+
|----------------|--------------------|----------------|
|
|
31
|
+
| `random-image` | 从自定义 API 列表随机获取一张图片 | `random-image` |
|
|
32
|
+
| `hsjp <msg> [msg1] [msg2]` | 生成黑丝举牌图片 | `hsjp 我喜欢你` |
|
|
33
|
+
| `dmjp <text>` | 生成动漫举牌图片 | `dmjp Koishi 真棒` |
|
|
34
|
+
| `clear-image-cache` | 清空图片缓存 | `clear-image-cache` |
|
|
35
|
+
|
|
36
|
+
## 配置项说明 (Configuration)
|
|
37
|
+
|
|
38
|
+
| 配置项 (Config Item) | 类型 (Type) | 默认值 (Default) | 说明 (Description) |
|
|
39
|
+
|----------------------|-------------|------------------|--------------------|
|
|
40
|
+
| `enable` | boolean | true | 是否启用插件 |
|
|
41
|
+
| `showWaitingTip` | boolean | true | 请求图片时是否显示等待提示 |
|
|
42
|
+
| `timeout` | number | 10000 | API 请求超时时间(毫秒) |
|
|
43
|
+
| `customImageApis` | string[] | [] | 自定义图片 API 列表 |
|
|
44
|
+
| `customImageEnabled` | boolean | true | 是否启用自定义图片功能 |
|
|
45
|
+
| `hsjpEnabled` | boolean | true | 是否启用黑丝举牌功能 |
|
|
46
|
+
| `dmjpEnabled` | boolean | true | 是否启用动漫举牌功能 |
|
|
47
|
+
| `imageSendTimeout` | number | 15000 | 图片发送超时(毫秒) |
|
|
48
|
+
| `autoClearCacheInterval` | number | 60 | 自动清理缓存间隔(分钟) |
|
|
49
|
+
| `tempDir` | string | ./temp_images | 临时图片保存目录 |
|
|
50
|
+
| `showSuccessTip` | boolean | true | 请求图片成功时是否显示提示 |
|
|
51
|
+
| `showTimeoutTip` | boolean | true | API 请求超时后是否给用户提示 |
|
|
52
|
+
|
|
53
|
+
## 项目贡献者 (Contributors)
|
|
54
|
+
|
|
55
|
+
| 贡献者 (Contributor) | 贡献内容 (Contribution) |
|
|
56
|
+
|----------------------|-------------------------|
|
|
57
|
+
| Minecraft-1314 | 插件完整开发 (Complete plugin development) |
|
|
58
|
+
| 素颜API | 提供 API 接口支持 |
|
|
59
|
+
| (欢迎提交 PR 加入贡献者列表) | (Welcome to submit PR to join the contributor list) |
|
|
60
|
+
|
|
61
|
+
## 许可协议 (License)
|
|
62
|
+
|
|
63
|
+
本项目采用 MIT 许可证,详情参见 [LICENSE](LICENSE) 文件。
|
|
64
|
+
|
|
65
|
+
This project is licensed under the MIT License, see the [LICENSE](LICENSE) file for details.
|
|
66
|
+
|
|
67
|
+
## 支持我们 (Support Us)
|
|
68
|
+
|
|
69
|
+
如果这个项目对您有帮助,欢迎点亮右上角的 Star ⭐ 支持我们,这将是对所有贡献者最大的鼓励!
|
|
70
|
+
|
|
71
71
|
If this project is helpful to you, please feel free to star it in the upper right corner ⭐ to support us, which will be the greatest encouragement to all contributors!
|