koishi-plugin-maple-drift-bottle 0.1.1 → 0.1.3

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
@@ -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(`[CQ:image,file=base64://${bottle.image}]`);
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,27 @@ ${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
+ if (!content && !session.elements?.some((el) => el.type === "image")) {
260
+ return "格式错误!请使用:\n扔漂流瓶 [-i|-v] 内容\n例如:\n扔漂流瓶 你好世界\n扔漂流瓶 -i 匿名消息";
261
+ }
262
+ let processedContent = content || "";
183
263
  let imageBase64 = null;
184
- if (hasImage) {
185
- const images = session.elements.filter((el) => el.type === "image");
186
- if (images.length > 1) {
187
- return "最多只能发送一张图片!";
188
- }
189
- if (config.maxImageSize === 0) {
190
- return "当前配置不允许发送图片!";
191
- }
192
- const imageElement = images[0];
193
- let imageUrl = imageElement.attrs.url || imageElement.attrs.file;
194
- if (imageUrl && imageUrl.startsWith("base64://")) {
195
- imageBase64 = imageUrl.substring(9);
196
- const imageSize = getBase64ImageSize(imageBase64);
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
- }
264
+ try {
265
+ const result = await processImages(session, content || "", config, ctx);
266
+ processedContent = result.content;
267
+ imageBase64 = result.imageBase64;
268
+ } catch (error) {
269
+ return error.message;
270
+ }
271
+ if (!imageBase64 && (!processedContent || processedContent.trim().length === 0)) {
272
+ return "漂流瓶内容不能为空!\n格式:扔漂流瓶 [-i|-v] 内容\n示例:扔漂流瓶 -i 这是一条匿名漂流瓶";
273
+ }
274
+ const trimmedContent = processedContent.trim();
275
+ if (trimmedContent.length > config.maxContentLength) {
276
+ return `漂流瓶内容太长了!最多只能输入${config.maxContentLength}个字(当前: ${trimmedContent.length})。`;
207
277
  }
208
278
  let anonymous = config.anonymousByDefault;
209
279
  if (options.invisible) {
@@ -258,9 +328,9 @@ ID: #${newBottle.id}
258
328
  currentPageBottles.forEach((bottle, index) => {
259
329
  const displayIndex = startIndex + index + 1;
260
330
  const timeDisplay = formatTime(bottle.created);
261
- const anonymousText = bottle.anonymous ? "(匿名)" : "";
331
+ const anonymousText = bottle.anonymous ? "匿名" : "";
262
332
  const contentPreview = getContentPreview(bottle.content, !!bottle.image, config.maxPreviewLength);
263
- output += `${displayIndex}. 漂流瓶 #${bottle.id} ${anonymousText} - ${timeDisplay}
333
+ output += `${displayIndex}. #${bottle.id} ${anonymousText} - ${timeDisplay}
264
334
  预览: ${contentPreview}
265
335
  `;
266
336
  });
@@ -303,12 +373,11 @@ ID: #${newBottle.id}
303
373
  const timeDisplay = formatTime(bottle.created);
304
374
  let textContent = bottle.content;
305
375
  if (bottle.image) {
306
- textContent += "\n[图片]";
376
+ textContent += textContent ? "\n[图片]" : "[图片]";
307
377
  }
308
378
  let output = `【漂流瓶 #${bottle.id}】
309
379
  发送者:${senderDisplay}
310
380
  时间:${timeDisplay}
311
- 状态:${bottle.anonymous ? "匿名" : "公开"}
312
381
  内容:
313
382
  ${textContent}`;
314
383
  if (comments.length > 0) {
@@ -323,7 +392,7 @@ ${textContent}`;
323
392
  await session.send(output);
324
393
  if (bottle.image) {
325
394
  try {
326
- await session.send(`[CQ:image,file=base64://${bottle.image}]`);
395
+ await session.send(import_koishi2.segment.image(`base64://${bottle.image}`));
327
396
  } catch (error) {
328
397
  ctx.logger("maple-drift-bottle").error("发送图片时出错:", error);
329
398
  await session.send("(图片发送失败)");
@@ -362,13 +431,13 @@ ${textContent}`;
362
431
  });
363
432
  ctx.command("漂流瓶/评论漂流瓶 <bottleId:number> <content:text>", "对指定的漂流瓶进行评论").alias("评论漂流瓶").option("invisible", "-i 匿名评论(不显示评论者)").option("visible", "-v 公开评论(显示评论者)").example("评论漂流瓶 1 这是一条评论").example("评论漂流瓶 2 -i 这是一条匿名评论").action(async ({ session, options }, bottleId, content) => {
364
433
  try {
434
+ if (!bottleId || !content) {
435
+ return "格式错误!请使用:\n评论漂流瓶 序号 评论内容\n例如:\n评论漂流瓶 1 写得真好!\n匿名评论:\n评论漂流瓶 1 -i 匿名评论";
436
+ }
365
437
  const bottles = await ctx.database.get("maple-drift-bottles", { id: bottleId });
366
438
  if (bottles.length === 0) {
367
439
  return `没有找到ID为 #${bottleId} 的漂流瓶。`;
368
440
  }
369
- if (!content || content.trim().length === 0) {
370
- return "评论内容不能为空!";
371
- }
372
441
  const trimmedContent = content.trim();
373
442
  if (trimmedContent.length > config.maxCommentLength) {
374
443
  return `评论内容太长了!最多只能输入${config.maxCommentLength}个字(当前: ${trimmedContent.length})。`;
@@ -439,13 +508,13 @@ ${textContent}`;
439
508
  author: userId
440
509
  });
441
510
  const deletedCount = userComments.length;
442
- return `已成功删除你在漂流瓶 #${bottleId} 的评论(共 ${deletedCount} 条)。`;
511
+ return `已成功删除你在漂流瓶 #${bottleId} 的评论。`;
443
512
  }
444
513
  }
445
514
  await ctx.database.remove("maple-drift-bottle-comments", {
446
515
  bottleId
447
516
  });
448
- return `已成功删除漂流瓶 #${bottleId} 的所有评论(共 ${comments.length} 条)。`;
517
+ return `已成功删除漂流瓶 #${bottleId} 的所有评论。`;
449
518
  } catch (error) {
450
519
  ctx.logger("maple-drift-bottle").error("删除漂流瓶评论时出错:", error);
451
520
  return "删除漂流瓶评论时出错了,请稍后再试。";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-maple-drift-bottle",
3
3
  "description": "-",
4
- "version": "0.1.1",
4
+ "version": "0.1.3",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
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 {};