koishi-plugin-maple-drift-bottle 0.1.1 → 0.1.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 +105 -38
- package/package.json +1 -1
- package/lib/index.d.ts +0 -38
package/lib/index.js
CHANGED
|
@@ -27,8 +27,9 @@ __export(src_exports, {
|
|
|
27
27
|
});
|
|
28
28
|
module.exports = __toCommonJS(src_exports);
|
|
29
29
|
var import_koishi = require("koishi");
|
|
30
|
+
var import_koishi2 = require("koishi");
|
|
30
31
|
var name = "maple-drift-bottle";
|
|
31
|
-
var using = ["database"];
|
|
32
|
+
var using = ["database", "http"];
|
|
32
33
|
var Config = import_koishi.Schema.object({
|
|
33
34
|
anonymousByDefault: import_koishi.Schema.boolean().default(false).description("默认是否匿名(true: 匿名,false: 不匿名)"),
|
|
34
35
|
maxContentLength: import_koishi.Schema.number().default(500).min(50).max(2e3).description("漂流瓶内容最大长度(字)(50-2000)"),
|
|
@@ -74,6 +75,91 @@ function getBase64ImageSize(base64) {
|
|
|
74
75
|
return sizeInKB;
|
|
75
76
|
}
|
|
76
77
|
__name(getBase64ImageSize, "getBase64ImageSize");
|
|
78
|
+
function extractImageUrlFromCQCode(cqCode) {
|
|
79
|
+
const srcMatch = cqCode.match(/src="([^"]+)"/);
|
|
80
|
+
if (srcMatch && srcMatch[1]) {
|
|
81
|
+
const url = srcMatch[1];
|
|
82
|
+
if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
83
|
+
return url;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
__name(extractImageUrlFromCQCode, "extractImageUrlFromCQCode");
|
|
89
|
+
async function downloadImageToBase64(url, maxSizeKB, ctx) {
|
|
90
|
+
try {
|
|
91
|
+
const response = await ctx.http.get(url, { responseType: "arraybuffer" });
|
|
92
|
+
const buffer = Buffer.from(response);
|
|
93
|
+
const sizeKB = buffer.length / 1024;
|
|
94
|
+
if (sizeKB > maxSizeKB) {
|
|
95
|
+
throw new Error(`图片太大: ${sizeKB.toFixed(2)}KB (最大允许: ${maxSizeKB}KB)`);
|
|
96
|
+
}
|
|
97
|
+
const base64 = buffer.toString("base64");
|
|
98
|
+
const base64SizeKB = base64.length * 3 / (4 * 1024);
|
|
99
|
+
if (base64SizeKB > maxSizeKB) {
|
|
100
|
+
throw new Error(`转换后图片太大: ${base64SizeKB.toFixed(2)}KB (最大允许: ${maxSizeKB}KB)`);
|
|
101
|
+
}
|
|
102
|
+
return base64;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
ctx.logger("maple-drift-bottle").error("下载或转换图片失败:", error);
|
|
105
|
+
throw new Error(`图片处理失败: ${error.message}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
__name(downloadImageToBase64, "downloadImageToBase64");
|
|
109
|
+
async function processImages(session, content, config, ctx) {
|
|
110
|
+
let processedContent = content || "";
|
|
111
|
+
let imageBase64 = null;
|
|
112
|
+
if (content && content.includes("<img")) {
|
|
113
|
+
const imgMatches = content.match(/<img[^>]+>/g);
|
|
114
|
+
if (imgMatches && imgMatches.length > 0) {
|
|
115
|
+
if (imgMatches.length > 1) {
|
|
116
|
+
throw new Error("最多只能发送一张图片!");
|
|
117
|
+
}
|
|
118
|
+
if (config.maxImageSize === 0) {
|
|
119
|
+
throw new Error("当前配置不允许发送图片!");
|
|
120
|
+
}
|
|
121
|
+
const imgTag = imgMatches[0];
|
|
122
|
+
const imageUrl = extractImageUrlFromCQCode(imgTag);
|
|
123
|
+
if (imageUrl) {
|
|
124
|
+
try {
|
|
125
|
+
imageBase64 = await downloadImageToBase64(imageUrl, config.maxImageSize, ctx);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
ctx.logger("maple-drift-bottle").error("处理CQ码图片失败:", error);
|
|
128
|
+
throw new Error(`图片处理失败: ${error.message}`);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
throw new Error("无法从图片消息中提取有效的图片URL,请尝试直接发送图片而不是复制图片链接。");
|
|
132
|
+
}
|
|
133
|
+
processedContent = content.replace(/<img[^>]*>/g, "").trim();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const hasImageInElements = session.elements?.some((el) => el.type === "image");
|
|
137
|
+
if (hasImageInElements) {
|
|
138
|
+
const images = session.elements.filter((el) => el.type === "image");
|
|
139
|
+
if (images.length > 1) {
|
|
140
|
+
throw new Error("最多只能发送一张图片!");
|
|
141
|
+
}
|
|
142
|
+
if (config.maxImageSize === 0) {
|
|
143
|
+
throw new Error("当前配置不允许发送图片!");
|
|
144
|
+
}
|
|
145
|
+
const imageElement = images[0];
|
|
146
|
+
let imageUrl = imageElement.attrs.url || imageElement.attrs.file;
|
|
147
|
+
if (imageUrl && imageUrl.startsWith("base64://")) {
|
|
148
|
+
const base64Data = imageUrl.substring(9);
|
|
149
|
+
const imageSize = getBase64ImageSize(base64Data);
|
|
150
|
+
if (imageSize > config.maxImageSize) {
|
|
151
|
+
throw new Error(`图片太大了!最大允许 ${config.maxImageSize}KB(当前: ${imageSize.toFixed(2)}KB)。`);
|
|
152
|
+
}
|
|
153
|
+
imageBase64 = base64Data;
|
|
154
|
+
} else if (imageUrl) {
|
|
155
|
+
imageBase64 = await downloadImageToBase64(imageUrl, config.maxImageSize, ctx);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (hasImageInElements && processedContent !== content) {
|
|
159
|
+
}
|
|
160
|
+
return { content: processedContent, imageBase64 };
|
|
161
|
+
}
|
|
162
|
+
__name(processImages, "processImages");
|
|
77
163
|
function apply(ctx, config) {
|
|
78
164
|
console.log("maple-drift-bottle 漂流瓶插件加载中...");
|
|
79
165
|
ctx.model.extend("maple-drift-bottles", {
|
|
@@ -134,7 +220,7 @@ function apply(ctx, config) {
|
|
|
134
220
|
const timeDisplay = formatTime(bottle.created);
|
|
135
221
|
let textContent = bottle.content;
|
|
136
222
|
if (bottle.image) {
|
|
137
|
-
textContent += "\n[图片]";
|
|
223
|
+
textContent += textContent ? "\n[图片]" : "[图片]";
|
|
138
224
|
}
|
|
139
225
|
let output = `【漂流瓶 #${bottle.id}】
|
|
140
226
|
发送者:${senderDisplay}
|
|
@@ -153,7 +239,7 @@ ${textContent}`;
|
|
|
153
239
|
await session.send(output);
|
|
154
240
|
if (bottle.image) {
|
|
155
241
|
try {
|
|
156
|
-
await session.send(`
|
|
242
|
+
await session.send(import_koishi2.segment.image(`base64://${bottle.image}`));
|
|
157
243
|
} catch (error) {
|
|
158
244
|
ctx.logger("maple-drift-bottle").error("发送图片时出错:", error);
|
|
159
245
|
await session.send("(图片发送失败)");
|
|
@@ -167,43 +253,24 @@ ${textContent}`;
|
|
|
167
253
|
});
|
|
168
254
|
ctx.command("漂流瓶/扔漂流瓶 <content:text>", "扔一个漂流瓶到海里(可附带一张图片)").alias("扔漂流瓶").alias("丢漂流瓶").option("invisible", "-i 匿名发送(不显示发送者)").option("visible", "-v 公开发送(显示发送者)").example("扔漂流瓶 这是一条漂流瓶内容").example("扔漂流瓶 -i 这是一条匿名漂流瓶内容").action(async ({ session, options }, content) => {
|
|
169
255
|
try {
|
|
170
|
-
const hasImage = session.elements?.some((el) => el.type === "image");
|
|
171
|
-
if (!content || content.trim().length === 0) {
|
|
172
|
-
if (!hasImage) {
|
|
173
|
-
return "漂流瓶内容不能为空!\n格式:扔漂流瓶 [-i|-v] 内容\n示例:扔漂流瓶 -i 这是一条匿名漂流瓶";
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
const trimmedContent = content ? content.trim() : "";
|
|
177
|
-
if (trimmedContent.length > config.maxContentLength) {
|
|
178
|
-
return `漂流瓶内容太长了!最多只能输入${config.maxContentLength}个字(当前: ${trimmedContent.length})。`;
|
|
179
|
-
}
|
|
180
256
|
if (options.invisible && options.visible) {
|
|
181
257
|
return "选项冲突:-i(匿名)和-v(公开)不能同时使用";
|
|
182
258
|
}
|
|
259
|
+
let processedContent = content || "";
|
|
183
260
|
let imageBase64 = null;
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (imageSize > config.maxImageSize) {
|
|
198
|
-
return `图片太大了!最大允许 ${config.maxImageSize}KB(当前: ${imageSize.toFixed(2)}KB)。`;
|
|
199
|
-
}
|
|
200
|
-
} else if (imageUrl) {
|
|
201
|
-
try {
|
|
202
|
-
await session.send("正在处理图片,请稍候...");
|
|
203
|
-
} catch (error) {
|
|
204
|
-
return "图片处理失败,请稍后再试。";
|
|
205
|
-
}
|
|
206
|
-
}
|
|
261
|
+
try {
|
|
262
|
+
const result = await processImages(session, content || "", config, ctx);
|
|
263
|
+
processedContent = result.content;
|
|
264
|
+
imageBase64 = result.imageBase64;
|
|
265
|
+
} catch (error) {
|
|
266
|
+
return error.message;
|
|
267
|
+
}
|
|
268
|
+
if (!imageBase64 && (!processedContent || processedContent.trim().length === 0)) {
|
|
269
|
+
return "漂流瓶内容不能为空!\n格式:扔漂流瓶 [-i|-v] 内容\n示例:扔漂流瓶 -i 这是一条匿名漂流瓶";
|
|
270
|
+
}
|
|
271
|
+
const trimmedContent = processedContent.trim();
|
|
272
|
+
if (trimmedContent.length > config.maxContentLength) {
|
|
273
|
+
return `漂流瓶内容太长了!最多只能输入${config.maxContentLength}个字(当前: ${trimmedContent.length})。`;
|
|
207
274
|
}
|
|
208
275
|
let anonymous = config.anonymousByDefault;
|
|
209
276
|
if (options.invisible) {
|
|
@@ -303,7 +370,7 @@ ID: #${newBottle.id}
|
|
|
303
370
|
const timeDisplay = formatTime(bottle.created);
|
|
304
371
|
let textContent = bottle.content;
|
|
305
372
|
if (bottle.image) {
|
|
306
|
-
textContent += "\n[图片]";
|
|
373
|
+
textContent += textContent ? "\n[图片]" : "[图片]";
|
|
307
374
|
}
|
|
308
375
|
let output = `【漂流瓶 #${bottle.id}】
|
|
309
376
|
发送者:${senderDisplay}
|
|
@@ -323,7 +390,7 @@ ${textContent}`;
|
|
|
323
390
|
await session.send(output);
|
|
324
391
|
if (bottle.image) {
|
|
325
392
|
try {
|
|
326
|
-
await session.send(`
|
|
393
|
+
await session.send(import_koishi2.segment.image(`base64://${bottle.image}`));
|
|
327
394
|
} catch (error) {
|
|
328
395
|
ctx.logger("maple-drift-bottle").error("发送图片时出错:", error);
|
|
329
396
|
await session.send("(图片发送失败)");
|
package/package.json
CHANGED
package/lib/index.d.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { Context, Schema } from 'koishi';
|
|
2
|
-
export declare const name = "maple-drift-bottle";
|
|
3
|
-
export declare const using: readonly ["database"];
|
|
4
|
-
interface DriftBottle {
|
|
5
|
-
id: number;
|
|
6
|
-
author: string;
|
|
7
|
-
authorName: string;
|
|
8
|
-
anonymous: boolean;
|
|
9
|
-
content: string;
|
|
10
|
-
image: string | null;
|
|
11
|
-
created: Date;
|
|
12
|
-
}
|
|
13
|
-
interface BottleComment {
|
|
14
|
-
id: number;
|
|
15
|
-
bottleId: number;
|
|
16
|
-
author: string;
|
|
17
|
-
authorName: string;
|
|
18
|
-
anonymous: boolean;
|
|
19
|
-
content: string;
|
|
20
|
-
created: Date;
|
|
21
|
-
}
|
|
22
|
-
declare module 'koishi' {
|
|
23
|
-
interface Tables {
|
|
24
|
-
'maple-drift-bottles': DriftBottle;
|
|
25
|
-
'maple-drift-bottle-comments': BottleComment;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
export interface Config {
|
|
29
|
-
anonymousByDefault: boolean;
|
|
30
|
-
maxContentLength: number;
|
|
31
|
-
maxPreviewLength: number;
|
|
32
|
-
maxCommentLength: number;
|
|
33
|
-
maxImageSize: number;
|
|
34
|
-
adminQQ: string[];
|
|
35
|
-
}
|
|
36
|
-
export declare const Config: Schema<Config>;
|
|
37
|
-
export declare function apply(ctx: Context, config: Config): void;
|
|
38
|
-
export {};
|