koishi-plugin-maple-drift-bottle 0.1.0 → 0.1.1
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 +13 -8
- package/lib/index.js +274 -258
- package/package.json +1 -4
package/lib/index.d.ts
CHANGED
|
@@ -7,25 +7,30 @@ interface DriftBottle {
|
|
|
7
7
|
authorName: string;
|
|
8
8
|
anonymous: boolean;
|
|
9
9
|
content: string;
|
|
10
|
-
|
|
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;
|
|
11
20
|
created: Date;
|
|
12
21
|
}
|
|
13
22
|
declare module 'koishi' {
|
|
14
23
|
interface Tables {
|
|
15
24
|
'maple-drift-bottles': DriftBottle;
|
|
25
|
+
'maple-drift-bottle-comments': BottleComment;
|
|
16
26
|
}
|
|
17
27
|
}
|
|
18
28
|
export interface Config {
|
|
19
29
|
anonymousByDefault: boolean;
|
|
20
30
|
maxContentLength: number;
|
|
21
31
|
maxPreviewLength: number;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
imageSavePath: string;
|
|
25
|
-
compressImages: boolean;
|
|
26
|
-
maxImageWidth: number;
|
|
27
|
-
maxImageHeight: number;
|
|
28
|
-
imageQuality: number;
|
|
32
|
+
maxCommentLength: number;
|
|
33
|
+
maxImageSize: number;
|
|
29
34
|
adminQQ: string[];
|
|
30
35
|
}
|
|
31
36
|
export declare const Config: Schema<Config>;
|
package/lib/index.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
var __create = Object.create;
|
|
2
1
|
var __defProp = Object.defineProperty;
|
|
3
2
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
6
4
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
5
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
6
|
var __export = (target, all) => {
|
|
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
|
|
30
20
|
// src/index.ts
|
|
@@ -37,34 +27,24 @@ __export(src_exports, {
|
|
|
37
27
|
});
|
|
38
28
|
module.exports = __toCommonJS(src_exports);
|
|
39
29
|
var import_koishi = require("koishi");
|
|
40
|
-
var import_promises = require("fs/promises");
|
|
41
|
-
var import_path = require("path");
|
|
42
|
-
var import_crypto = require("crypto");
|
|
43
|
-
var import_fs = require("fs");
|
|
44
|
-
var import_sharp = __toESM(require("sharp"));
|
|
45
30
|
var name = "maple-drift-bottle";
|
|
46
31
|
var using = ["database"];
|
|
47
32
|
var Config = import_koishi.Schema.object({
|
|
48
33
|
anonymousByDefault: import_koishi.Schema.boolean().default(false).description("默认是否匿名(true: 匿名,false: 不匿名)"),
|
|
49
|
-
maxContentLength: import_koishi.Schema.number().default(500).min(50).max(2e3).description("
|
|
50
|
-
maxPreviewLength: import_koishi.Schema.number().default(8).min(3).max(50).description('指令"我的漂流瓶"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
imageSavePath: import_koishi.Schema.string().default("./data/maple-drift-bottle/images").description("图片本地保存路径(相对Koishi根目录)"),
|
|
54
|
-
compressImages: import_koishi.Schema.boolean().default(true).description("是否压缩图片以节省空间"),
|
|
55
|
-
maxImageWidth: import_koishi.Schema.number().default(1200).min(100).max(3840).description("图片最大宽度(像素)"),
|
|
56
|
-
maxImageHeight: import_koishi.Schema.number().default(1200).min(100).max(3840).description("图片最大高度(像素)"),
|
|
57
|
-
imageQuality: import_koishi.Schema.number().default(85).min(10).max(100).description("图片质量 (0-100,越高图片质量越好但文件越大)"),
|
|
34
|
+
maxContentLength: import_koishi.Schema.number().default(500).min(50).max(2e3).description("漂流瓶内容最大长度(字)(50-2000)"),
|
|
35
|
+
maxPreviewLength: import_koishi.Schema.number().default(8).min(3).max(50).description('指令"我的漂流瓶"最大预览字数(3-50)'),
|
|
36
|
+
maxCommentLength: import_koishi.Schema.number().default(50).min(10).max(500).description("漂流瓶评论最大长度(字)(10-500)"),
|
|
37
|
+
maxImageSize: import_koishi.Schema.number().default(1e3).min(0).max(1e4).description("漂流瓶图片大小限制(KB)(0-10000,0表示不允许图片)"),
|
|
58
38
|
adminQQ: import_koishi.Schema.array(String).role("table").default([]).description("管理人QQ号(可添加多个)")
|
|
59
39
|
});
|
|
60
40
|
function getSenderDisplay(bottle) {
|
|
61
|
-
|
|
62
|
-
return "匿名";
|
|
63
|
-
} else {
|
|
64
|
-
return `${bottle.authorName} (${bottle.author})`;
|
|
65
|
-
}
|
|
41
|
+
return bottle.anonymous ? "匿名" : bottle.authorName;
|
|
66
42
|
}
|
|
67
43
|
__name(getSenderDisplay, "getSenderDisplay");
|
|
44
|
+
function getCommenterDisplay(comment) {
|
|
45
|
+
return comment.anonymous ? "匿名" : comment.authorName;
|
|
46
|
+
}
|
|
47
|
+
__name(getCommenterDisplay, "getCommenterDisplay");
|
|
68
48
|
function formatTime(date) {
|
|
69
49
|
return date.toLocaleString("zh-CN", {
|
|
70
50
|
year: "numeric",
|
|
@@ -76,91 +56,24 @@ function formatTime(date) {
|
|
|
76
56
|
});
|
|
77
57
|
}
|
|
78
58
|
__name(formatTime, "formatTime");
|
|
79
|
-
function getContentPreview(content, maxLength) {
|
|
80
|
-
|
|
81
|
-
|
|
59
|
+
function getContentPreview(content, hasImage, maxLength) {
|
|
60
|
+
let preview = content;
|
|
61
|
+
if (hasImage) {
|
|
62
|
+
preview += "[图片]";
|
|
82
63
|
}
|
|
83
|
-
if (
|
|
84
|
-
return
|
|
64
|
+
if (preview.length <= maxLength) {
|
|
65
|
+
return preview;
|
|
85
66
|
}
|
|
86
|
-
return
|
|
67
|
+
return preview.substring(0, maxLength) + "...";
|
|
87
68
|
}
|
|
88
69
|
__name(getContentPreview, "getContentPreview");
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
text += element.attrs?.content || "";
|
|
95
|
-
} else if (element.type === "image" || element.type === "img") {
|
|
96
|
-
const url = element.attrs?.url || element.attrs?.src;
|
|
97
|
-
if (url) {
|
|
98
|
-
images.push(url);
|
|
99
|
-
}
|
|
100
|
-
} else if (element.type === "at") {
|
|
101
|
-
continue;
|
|
102
|
-
} else if (element.type === "face") {
|
|
103
|
-
continue;
|
|
104
|
-
} else {
|
|
105
|
-
text += `[${element.type}]`;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return { text: text.trim(), images };
|
|
70
|
+
function getBase64ImageSize(base64) {
|
|
71
|
+
const stringLength = base64.length;
|
|
72
|
+
const sizeInBytes = 4 * Math.ceil(stringLength / 3) * 0.5624896334383812;
|
|
73
|
+
const sizeInKB = sizeInBytes / 1024;
|
|
74
|
+
return sizeInKB;
|
|
109
75
|
}
|
|
110
|
-
__name(
|
|
111
|
-
async function downloadAndSaveImage(ctx, url, savePath, compress, maxWidth, maxHeight, quality) {
|
|
112
|
-
try {
|
|
113
|
-
if (!(0, import_fs.existsSync)(savePath)) {
|
|
114
|
-
await (0, import_promises.mkdir)(savePath, { recursive: true });
|
|
115
|
-
}
|
|
116
|
-
const hash = (0, import_crypto.createHash)("md5").update(url + Date.now()).digest("hex");
|
|
117
|
-
const originalExt = (0, import_path.extname)(new URL(url).pathname) || ".jpg";
|
|
118
|
-
const fileName = `${hash}${originalExt}`;
|
|
119
|
-
const filePath = (0, import_path.join)(savePath, fileName);
|
|
120
|
-
const response = await ctx.http.get(url, { responseType: "arraybuffer" });
|
|
121
|
-
const imageBuffer = Buffer.from(response);
|
|
122
|
-
if (compress) {
|
|
123
|
-
try {
|
|
124
|
-
let sharpInstance = (0, import_sharp.default)(imageBuffer);
|
|
125
|
-
const metadata = await sharpInstance.metadata();
|
|
126
|
-
if (metadata.width > maxWidth || metadata.height > maxHeight) {
|
|
127
|
-
sharpInstance = sharpInstance.resize({
|
|
128
|
-
width: maxWidth,
|
|
129
|
-
height: maxHeight,
|
|
130
|
-
fit: "inside",
|
|
131
|
-
// 保持宽高比,不超过指定尺寸
|
|
132
|
-
withoutEnlargement: true
|
|
133
|
-
// 不放大比指定尺寸小的图片
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
await sharpInstance.jpeg({ quality }).toFile(filePath);
|
|
137
|
-
ctx.logger("maple-drift-bottle").info(`图片压缩保存: ${fileName} (${imageBuffer.length}字节 -> ${(await (0, import_promises.readFile)(filePath)).length}字节)`);
|
|
138
|
-
} catch (sharpError) {
|
|
139
|
-
ctx.logger("maple-drift-bottle").warn("sharp压缩失败,保存原始图片:", sharpError);
|
|
140
|
-
await (0, import_promises.writeFile)(filePath, imageBuffer);
|
|
141
|
-
}
|
|
142
|
-
} else {
|
|
143
|
-
await (0, import_promises.writeFile)(filePath, imageBuffer);
|
|
144
|
-
}
|
|
145
|
-
return fileName;
|
|
146
|
-
} catch (error) {
|
|
147
|
-
ctx.logger("maple-drift-bottle").error("下载或保存图片失败:", error);
|
|
148
|
-
throw new Error(`图片保存失败: ${error.message}`);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
__name(downloadAndSaveImage, "downloadAndSaveImage");
|
|
152
|
-
function getLocalImagePath(fileName, ctx, config) {
|
|
153
|
-
if (!fileName || !config.saveImagesLocally) {
|
|
154
|
-
return fileName;
|
|
155
|
-
}
|
|
156
|
-
const fullPath = (0, import_path.join)(config.imageSavePath, fileName);
|
|
157
|
-
if (!(0, import_fs.existsSync)(fullPath)) {
|
|
158
|
-
ctx.logger("maple-drift-bottle").warn(`图片文件不存在: ${fullPath}`);
|
|
159
|
-
return fileName;
|
|
160
|
-
}
|
|
161
|
-
return fullPath;
|
|
162
|
-
}
|
|
163
|
-
__name(getLocalImagePath, "getLocalImagePath");
|
|
76
|
+
__name(getBase64ImageSize, "getBase64ImageSize");
|
|
164
77
|
function apply(ctx, config) {
|
|
165
78
|
console.log("maple-drift-bottle 漂流瓶插件加载中...");
|
|
166
79
|
ctx.model.extend("maple-drift-bottles", {
|
|
@@ -172,15 +85,36 @@ function apply(ctx, config) {
|
|
|
172
85
|
anonymous: "boolean",
|
|
173
86
|
// 是否匿名
|
|
174
87
|
content: "text",
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
//
|
|
88
|
+
// 漂流瓶内容(text类型,支持长文本)
|
|
89
|
+
image: "text",
|
|
90
|
+
// 图片base64数据
|
|
178
91
|
created: "timestamp"
|
|
179
92
|
// 创建时间
|
|
180
93
|
}, {
|
|
181
94
|
primary: "id",
|
|
182
95
|
autoInc: true
|
|
183
96
|
});
|
|
97
|
+
ctx.model.extend("maple-drift-bottle-comments", {
|
|
98
|
+
id: "unsigned",
|
|
99
|
+
bottleId: "unsigned",
|
|
100
|
+
// 关联的漂流瓶ID
|
|
101
|
+
author: "string",
|
|
102
|
+
// 评论者ID
|
|
103
|
+
authorName: "string",
|
|
104
|
+
// 评论者昵称
|
|
105
|
+
anonymous: "boolean",
|
|
106
|
+
// 是否匿名评论
|
|
107
|
+
content: "text",
|
|
108
|
+
// 评论内容
|
|
109
|
+
created: "timestamp"
|
|
110
|
+
// 评论时间
|
|
111
|
+
}, {
|
|
112
|
+
primary: "id",
|
|
113
|
+
autoInc: true,
|
|
114
|
+
foreign: {
|
|
115
|
+
bottleId: ["maple-drift-bottles", "id"]
|
|
116
|
+
}
|
|
117
|
+
});
|
|
184
118
|
ctx.command("漂流瓶", "漂流瓶相关指令").action(({ session }) => {
|
|
185
119
|
return session.execute("help 漂流瓶");
|
|
186
120
|
});
|
|
@@ -192,56 +126,85 @@ function apply(ctx, config) {
|
|
|
192
126
|
}
|
|
193
127
|
const randomIndex = Math.floor(Math.random() * allBottles.length);
|
|
194
128
|
const bottle = allBottles[randomIndex];
|
|
129
|
+
const comments = await ctx.database.get("maple-drift-bottle-comments", {
|
|
130
|
+
bottleId: bottle.id
|
|
131
|
+
});
|
|
132
|
+
comments.sort((a, b) => b.created.getTime() - a.created.getTime());
|
|
195
133
|
const senderDisplay = getSenderDisplay(bottle);
|
|
196
134
|
const timeDisplay = formatTime(bottle.created);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
135
|
+
let textContent = bottle.content;
|
|
136
|
+
if (bottle.image) {
|
|
137
|
+
textContent += "\n[图片]";
|
|
138
|
+
}
|
|
139
|
+
let output = `【漂流瓶 #${bottle.id}】
|
|
140
|
+
发送者:${senderDisplay}
|
|
141
|
+
时间:${timeDisplay}
|
|
142
|
+
内容:
|
|
143
|
+
${textContent}`;
|
|
144
|
+
if (comments.length > 0) {
|
|
145
|
+
output += "\n──────────\n最新评论:\n";
|
|
146
|
+
const recentComments = comments.slice(0, 3);
|
|
147
|
+
recentComments.forEach((comment, index) => {
|
|
148
|
+
const commenterDisplay = getCommenterDisplay(comment);
|
|
149
|
+
output += `${index + 1}. ${commenterDisplay}:${comment.content}
|
|
150
|
+
`;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
await session.send(output);
|
|
154
|
+
if (bottle.image) {
|
|
155
|
+
try {
|
|
156
|
+
await session.send(`[CQ:image,file=base64://${bottle.image}]`);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
ctx.logger("maple-drift-bottle").error("发送图片时出错:", error);
|
|
159
|
+
await session.send("(图片发送失败)");
|
|
216
160
|
}
|
|
217
|
-
return "";
|
|
218
|
-
} else {
|
|
219
|
-
return message.join("\n");
|
|
220
161
|
}
|
|
162
|
+
return "";
|
|
221
163
|
} catch (error) {
|
|
222
164
|
ctx.logger("maple-drift-bottle").error("捞漂流瓶时出错:", error);
|
|
223
165
|
return "捞取漂流瓶时出错了,请稍后再试。";
|
|
224
166
|
}
|
|
225
167
|
});
|
|
226
|
-
ctx.command("漂流瓶/扔漂流瓶", "
|
|
168
|
+
ctx.command("漂流瓶/扔漂流瓶 <content:text>", "扔一个漂流瓶到海里(可附带一张图片)").alias("扔漂流瓶").alias("丢漂流瓶").option("invisible", "-i 匿名发送(不显示发送者)").option("visible", "-v 公开发送(显示发送者)").example("扔漂流瓶 这是一条漂流瓶内容").example("扔漂流瓶 -i 这是一条匿名漂流瓶内容").action(async ({ session, options }, content) => {
|
|
227
169
|
try {
|
|
228
|
-
const
|
|
229
|
-
if (
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (!text && imageUrls.length === 0) {
|
|
234
|
-
return "漂流瓶内容不能为空!请发送文本或图片。";
|
|
235
|
-
}
|
|
236
|
-
if (text.length > config.maxContentLength) {
|
|
237
|
-
return `漂流瓶文本内容太长了!最多只能输入${config.maxContentLength}个字符(当前: ${text.length})。`;
|
|
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
|
+
}
|
|
238
175
|
}
|
|
239
|
-
|
|
240
|
-
|
|
176
|
+
const trimmedContent = content ? content.trim() : "";
|
|
177
|
+
if (trimmedContent.length > config.maxContentLength) {
|
|
178
|
+
return `漂流瓶内容太长了!最多只能输入${config.maxContentLength}个字(当前: ${trimmedContent.length})。`;
|
|
241
179
|
}
|
|
242
180
|
if (options.invisible && options.visible) {
|
|
243
181
|
return "选项冲突:-i(匿名)和-v(公开)不能同时使用";
|
|
244
182
|
}
|
|
183
|
+
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
|
+
}
|
|
207
|
+
}
|
|
245
208
|
let anonymous = config.anonymousByDefault;
|
|
246
209
|
if (options.invisible) {
|
|
247
210
|
anonymous = true;
|
|
@@ -250,40 +213,19 @@ ${bottle.content}`);
|
|
|
250
213
|
}
|
|
251
214
|
const userInfo = await session.getUser(session.userId);
|
|
252
215
|
const authorName = userInfo?.name || session.username || `用户${session.userId}`;
|
|
253
|
-
const savedImageNames = [];
|
|
254
|
-
if (config.saveImagesLocally && imageUrls.length > 0) {
|
|
255
|
-
for (const imageUrl of imageUrls) {
|
|
256
|
-
try {
|
|
257
|
-
const savedName = await downloadAndSaveImage(
|
|
258
|
-
ctx,
|
|
259
|
-
imageUrl,
|
|
260
|
-
config.imageSavePath,
|
|
261
|
-
config.compressImages,
|
|
262
|
-
config.maxImageWidth,
|
|
263
|
-
config.maxImageHeight,
|
|
264
|
-
config.imageQuality
|
|
265
|
-
);
|
|
266
|
-
savedImageNames.push(savedName);
|
|
267
|
-
} catch (error) {
|
|
268
|
-
ctx.logger("maple-drift-bottle").error("图片处理失败:", error);
|
|
269
|
-
return `图片处理失败: ${error.message}`;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
216
|
const newBottle = await ctx.database.create("maple-drift-bottles", {
|
|
274
217
|
author: session.userId,
|
|
275
218
|
authorName,
|
|
276
219
|
anonymous,
|
|
277
|
-
content:
|
|
278
|
-
|
|
220
|
+
content: trimmedContent,
|
|
221
|
+
image: imageBase64,
|
|
279
222
|
created: /* @__PURE__ */ new Date()
|
|
280
223
|
});
|
|
281
224
|
const anonymousText = anonymous ? "匿名" : "不匿名";
|
|
282
|
-
const imageText =
|
|
283
|
-
const saveText = config.saveImagesLocally ? "(已本地保存)" : "";
|
|
225
|
+
const imageText = imageBase64 ? "(含图片)" : "";
|
|
284
226
|
return `成功扔出一个漂流瓶!
|
|
285
227
|
ID: #${newBottle.id}
|
|
286
|
-
状态: ${anonymousText}${imageText}
|
|
228
|
+
状态: ${anonymousText}${imageText}
|
|
287
229
|
内容已保存到海中,等待有缘人捞取。`;
|
|
288
230
|
} catch (error) {
|
|
289
231
|
ctx.logger("maple-drift-bottle").error("扔漂流瓶时出错:", error);
|
|
@@ -317,10 +259,9 @@ ID: #${newBottle.id}
|
|
|
317
259
|
const displayIndex = startIndex + index + 1;
|
|
318
260
|
const timeDisplay = formatTime(bottle.created);
|
|
319
261
|
const anonymousText = bottle.anonymous ? "(匿名)" : "";
|
|
320
|
-
const contentPreview = getContentPreview(bottle.content, config.maxPreviewLength);
|
|
321
|
-
const imageText = bottle.images && bottle.images.length > 0 ? ` [${bottle.images.length}图]` : "";
|
|
262
|
+
const contentPreview = getContentPreview(bottle.content, !!bottle.image, config.maxPreviewLength);
|
|
322
263
|
output += `${displayIndex}. 漂流瓶 #${bottle.id} ${anonymousText} - ${timeDisplay}
|
|
323
|
-
预览: ${contentPreview}
|
|
264
|
+
预览: ${contentPreview}
|
|
324
265
|
`;
|
|
325
266
|
});
|
|
326
267
|
output += "\n──────────\n";
|
|
@@ -354,33 +295,41 @@ ID: #${newBottle.id}
|
|
|
354
295
|
return `仅可查看由你自己发送的漂流瓶。
|
|
355
296
|
可以发送"我的漂流瓶"查看你发送的所有漂流瓶序号。`;
|
|
356
297
|
}
|
|
298
|
+
const comments = await ctx.database.get("maple-drift-bottle-comments", {
|
|
299
|
+
bottleId: bottle.id
|
|
300
|
+
});
|
|
301
|
+
comments.sort((a, b) => b.created.getTime() - a.created.getTime());
|
|
357
302
|
const senderDisplay = getSenderDisplay(bottle);
|
|
358
303
|
const timeDisplay = formatTime(bottle.created);
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if (
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
304
|
+
let textContent = bottle.content;
|
|
305
|
+
if (bottle.image) {
|
|
306
|
+
textContent += "\n[图片]";
|
|
307
|
+
}
|
|
308
|
+
let output = `【漂流瓶 #${bottle.id}】
|
|
309
|
+
发送者:${senderDisplay}
|
|
310
|
+
时间:${timeDisplay}
|
|
311
|
+
状态:${bottle.anonymous ? "匿名" : "公开"}
|
|
312
|
+
内容:
|
|
313
|
+
${textContent}`;
|
|
314
|
+
if (comments.length > 0) {
|
|
315
|
+
output += "\n──────────\n最新评论:\n";
|
|
316
|
+
const recentComments = comments.slice(0, 3);
|
|
317
|
+
recentComments.forEach((comment, index) => {
|
|
318
|
+
const commenterDisplay = getCommenterDisplay(comment);
|
|
319
|
+
output += `${index + 1}. ${commenterDisplay}:${comment.content}
|
|
320
|
+
`;
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
await session.send(output);
|
|
324
|
+
if (bottle.image) {
|
|
325
|
+
try {
|
|
326
|
+
await session.send(`[CQ:image,file=base64://${bottle.image}]`);
|
|
327
|
+
} catch (error) {
|
|
328
|
+
ctx.logger("maple-drift-bottle").error("发送图片时出错:", error);
|
|
329
|
+
await session.send("(图片发送失败)");
|
|
379
330
|
}
|
|
380
|
-
return "";
|
|
381
|
-
} else {
|
|
382
|
-
return message.join("\n");
|
|
383
331
|
}
|
|
332
|
+
return "";
|
|
384
333
|
} catch (error) {
|
|
385
334
|
ctx.logger("maple-drift-bottle").error("查看漂流瓶时出错:", error);
|
|
386
335
|
return "查看漂流瓶时出错了,请稍后再试。";
|
|
@@ -401,33 +350,130 @@ ${bottle.content}`);
|
|
|
401
350
|
return `仅可删除由你自己发送的漂流瓶。
|
|
402
351
|
可以发送"我的漂流瓶"查看你发送的所有漂流瓶序号。`;
|
|
403
352
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
const imageText = bottle.images && bottle.images.length > 0 ? ` (${bottle.images.length}张图片)` : "";
|
|
408
|
-
if (config.saveImagesLocally && bottle.images && bottle.images.length > 0) {
|
|
409
|
-
for (const imageName of bottle.images) {
|
|
410
|
-
try {
|
|
411
|
-
const imagePath = (0, import_path.join)(config.imageSavePath, imageName);
|
|
412
|
-
if ((0, import_fs.existsSync)(imagePath)) {
|
|
413
|
-
await (0, import_promises.rm)(imagePath);
|
|
414
|
-
ctx.logger("maple-drift-bottle").info(`删除图片文件: ${imagePath}`);
|
|
415
|
-
}
|
|
416
|
-
} catch (error) {
|
|
417
|
-
ctx.logger("maple-drift-bottle").warn(`删除图片文件失败: ${imageName}`, error);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
353
|
+
await ctx.database.remove("maple-drift-bottle-comments", {
|
|
354
|
+
bottleId: id
|
|
355
|
+
});
|
|
421
356
|
await ctx.database.remove("maple-drift-bottles", { id });
|
|
422
|
-
return `已成功删除漂流瓶 #${id}
|
|
423
|
-
发送者:${senderDisplay}
|
|
424
|
-
时间:${timeDisplay}
|
|
425
|
-
预览:${contentPreview}${imageText}`;
|
|
357
|
+
return `已成功删除漂流瓶 #${id},并同时删除了该漂流瓶的所有评论。`;
|
|
426
358
|
} catch (error) {
|
|
427
359
|
ctx.logger("maple-drift-bottle").error("删除漂流瓶时出错:", error);
|
|
428
360
|
return "删除漂流瓶时出错了,请稍后再试。";
|
|
429
361
|
}
|
|
430
362
|
});
|
|
363
|
+
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
|
+
try {
|
|
365
|
+
const bottles = await ctx.database.get("maple-drift-bottles", { id: bottleId });
|
|
366
|
+
if (bottles.length === 0) {
|
|
367
|
+
return `没有找到ID为 #${bottleId} 的漂流瓶。`;
|
|
368
|
+
}
|
|
369
|
+
if (!content || content.trim().length === 0) {
|
|
370
|
+
return "评论内容不能为空!";
|
|
371
|
+
}
|
|
372
|
+
const trimmedContent = content.trim();
|
|
373
|
+
if (trimmedContent.length > config.maxCommentLength) {
|
|
374
|
+
return `评论内容太长了!最多只能输入${config.maxCommentLength}个字(当前: ${trimmedContent.length})。`;
|
|
375
|
+
}
|
|
376
|
+
if (options.invisible && options.visible) {
|
|
377
|
+
return "选项冲突:-i(匿名)和-v(公开)不能同时使用";
|
|
378
|
+
}
|
|
379
|
+
let anonymous = config.anonymousByDefault;
|
|
380
|
+
if (options.invisible) {
|
|
381
|
+
anonymous = true;
|
|
382
|
+
} else if (options.visible) {
|
|
383
|
+
anonymous = false;
|
|
384
|
+
}
|
|
385
|
+
const userInfo = await session.getUser(session.userId);
|
|
386
|
+
const authorName = userInfo?.name || session.username || `用户${session.userId}`;
|
|
387
|
+
await ctx.database.create("maple-drift-bottle-comments", {
|
|
388
|
+
bottleId,
|
|
389
|
+
author: session.userId,
|
|
390
|
+
authorName,
|
|
391
|
+
anonymous,
|
|
392
|
+
content: trimmedContent,
|
|
393
|
+
created: /* @__PURE__ */ new Date()
|
|
394
|
+
});
|
|
395
|
+
const comments = await ctx.database.get("maple-drift-bottle-comments", {
|
|
396
|
+
bottleId
|
|
397
|
+
});
|
|
398
|
+
if (comments.length > 3) {
|
|
399
|
+
comments.sort((a, b) => a.created.getTime() - b.created.getTime());
|
|
400
|
+
const commentsToDelete = comments.slice(0, comments.length - 3);
|
|
401
|
+
for (const comment of commentsToDelete) {
|
|
402
|
+
await ctx.database.remove("maple-drift-bottle-comments", { id: comment.id });
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const anonymousText = anonymous ? "匿名" : "公开";
|
|
406
|
+
return `成功评论漂流瓶 #${bottleId}!
|
|
407
|
+
状态: ${anonymousText}
|
|
408
|
+
评论已保存。`;
|
|
409
|
+
} catch (error) {
|
|
410
|
+
ctx.logger("maple-drift-bottle").error("评论漂流瓶时出错:", error);
|
|
411
|
+
return "评论漂流瓶时出错了,请稍后再试。";
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
ctx.command("漂流瓶/删除漂流瓶评论 <bottleId:number>", "删除指定漂流瓶的所有评论").alias("删除漂流瓶评论").option("all", "-a 无视权限限制删除评论").example("删除漂流瓶评论 1").action(async ({ session, options }, bottleId) => {
|
|
415
|
+
try {
|
|
416
|
+
const bottles = await ctx.database.get("maple-drift-bottles", { id: bottleId });
|
|
417
|
+
if (bottles.length === 0) {
|
|
418
|
+
return `没有找到ID为 #${bottleId} 的漂流瓶。`;
|
|
419
|
+
}
|
|
420
|
+
const bottle = bottles[0];
|
|
421
|
+
const comments = await ctx.database.get("maple-drift-bottle-comments", {
|
|
422
|
+
bottleId
|
|
423
|
+
});
|
|
424
|
+
if (comments.length === 0) {
|
|
425
|
+
return `漂流瓶 #${bottleId} 还没有任何评论。`;
|
|
426
|
+
}
|
|
427
|
+
if (!options.all) {
|
|
428
|
+
const userId = session.userId;
|
|
429
|
+
const isBottleAuthor = bottle.author === userId;
|
|
430
|
+
const userComments = comments.filter((comment) => comment.author === userId);
|
|
431
|
+
const isCommentAuthor = userComments.length > 0;
|
|
432
|
+
if (!isBottleAuthor && !isCommentAuthor) {
|
|
433
|
+
return `仅可由漂流瓶发送者或评论发送者删除该漂流瓶的评论。
|
|
434
|
+
可以发送"我的漂流瓶"查看你发送的漂流瓶序号。`;
|
|
435
|
+
}
|
|
436
|
+
if (!isBottleAuthor && isCommentAuthor) {
|
|
437
|
+
await ctx.database.remove("maple-drift-bottle-comments", {
|
|
438
|
+
bottleId,
|
|
439
|
+
author: userId
|
|
440
|
+
});
|
|
441
|
+
const deletedCount = userComments.length;
|
|
442
|
+
return `已成功删除你在漂流瓶 #${bottleId} 的评论(共 ${deletedCount} 条)。`;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
await ctx.database.remove("maple-drift-bottle-comments", {
|
|
446
|
+
bottleId
|
|
447
|
+
});
|
|
448
|
+
return `已成功删除漂流瓶 #${bottleId} 的所有评论(共 ${comments.length} 条)。`;
|
|
449
|
+
} catch (error) {
|
|
450
|
+
ctx.logger("maple-drift-bottle").error("删除漂流瓶评论时出错:", error);
|
|
451
|
+
return "删除漂流瓶评论时出错了,请稍后再试。";
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
ctx.command("漂流瓶/清空漂流瓶评论 [options]", "清空漂流瓶评论").alias("清空漂流瓶评论").option("all", "-a 清空所有漂流瓶评论(默认)").option("user", "-u <userId:string> 清空指定用户的所有漂流瓶评论").example("清空漂流瓶评论").example("清空漂流瓶评论 -u 123456").action(async ({ session, options }) => {
|
|
455
|
+
try {
|
|
456
|
+
const query = {};
|
|
457
|
+
let description = "";
|
|
458
|
+
if (options.user) {
|
|
459
|
+
query.author = options.user;
|
|
460
|
+
description += `用户 ${options.user} 的`;
|
|
461
|
+
}
|
|
462
|
+
if (!options.user) {
|
|
463
|
+
description = "所有";
|
|
464
|
+
}
|
|
465
|
+
const comments = await ctx.database.get("maple-drift-bottle-comments", query);
|
|
466
|
+
const count = comments.length;
|
|
467
|
+
if (count === 0) {
|
|
468
|
+
return `没有找到${description}漂流瓶评论。`;
|
|
469
|
+
}
|
|
470
|
+
await ctx.database.remove("maple-drift-bottle-comments", query);
|
|
471
|
+
return `已成功清空 ${count} 条${description}漂流瓶评论。`;
|
|
472
|
+
} catch (error) {
|
|
473
|
+
ctx.logger("maple-drift-bottle").error("清空漂流瓶评论时出错:", error);
|
|
474
|
+
return "清空漂流瓶评论时出错了,请稍后再试。";
|
|
475
|
+
}
|
|
476
|
+
});
|
|
431
477
|
ctx.command("漂流瓶/清空漂流瓶 [options]", "清空漂流瓶").alias("清空漂流瓶").alias("清空瓶子").option("all", "-a 清空所有漂流瓶(默认)").option("user", "-u <userId:string> 清空指定用户的所有漂流瓶").option("date", "-d <date:string> 清空指定日期之前的漂流瓶(格式: yyyy-mm-dd)").example("清空漂流瓶").example("清空漂流瓶 -d 2023-12-31").action(async ({ session, options }) => {
|
|
432
478
|
try {
|
|
433
479
|
const query = {};
|
|
@@ -458,29 +504,13 @@ ${bottle.content}`);
|
|
|
458
504
|
if (count === 0) {
|
|
459
505
|
return `没有找到${description}漂流瓶。`;
|
|
460
506
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
for (const imageName of bottle.images) {
|
|
466
|
-
try {
|
|
467
|
-
const imagePath = (0, import_path.join)(config.imageSavePath, imageName);
|
|
468
|
-
if ((0, import_fs.existsSync)(imagePath)) {
|
|
469
|
-
await (0, import_promises.rm)(imagePath);
|
|
470
|
-
deletedImages++;
|
|
471
|
-
}
|
|
472
|
-
} catch (error) {
|
|
473
|
-
ctx.logger("maple-drift-bottle").warn(`删除图片文件失败: ${imageName}`, error);
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
if (deletedImages > 0) {
|
|
479
|
-
ctx.logger("maple-drift-bottle").info(`清空漂流瓶时删除了 ${deletedImages} 张图片文件`);
|
|
480
|
-
}
|
|
507
|
+
for (const bottle of bottles) {
|
|
508
|
+
await ctx.database.remove("maple-drift-bottle-comments", {
|
|
509
|
+
bottleId: bottle.id
|
|
510
|
+
});
|
|
481
511
|
}
|
|
482
512
|
await ctx.database.remove("maple-drift-bottles", query);
|
|
483
|
-
return `已成功清空 ${count} 条${description}
|
|
513
|
+
return `已成功清空 ${count} 条${description}漂流瓶记录,并同时删除了相关评论。`;
|
|
484
514
|
} catch (error) {
|
|
485
515
|
ctx.logger("maple-drift-bottle").error("清空漂流瓶时出错:", error);
|
|
486
516
|
return "清空漂流瓶时出错了,请稍后再试。";
|
|
@@ -533,27 +563,13 @@ ${failDetails.join("\n")}`;
|
|
|
533
563
|
return "联系漂流瓶管理人时出错了,请稍后再试。";
|
|
534
564
|
}
|
|
535
565
|
});
|
|
536
|
-
ctx.on("ready",
|
|
566
|
+
ctx.on("ready", () => {
|
|
537
567
|
console.log("maple-drift-bottle 漂流瓶插件已加载完成");
|
|
538
|
-
if (config.saveImagesLocally) {
|
|
539
|
-
if (!(0, import_fs.existsSync)(config.imageSavePath)) {
|
|
540
|
-
await (0, import_promises.mkdir)(config.imageSavePath, { recursive: true });
|
|
541
|
-
ctx.logger("maple-drift-bottle").info(`创建图片保存目录: ${config.imageSavePath}`);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
568
|
ctx.logger("maple-drift-bottle").info(`默认匿名设置: ${config.anonymousByDefault ? "是" : "否"}`);
|
|
545
|
-
ctx.logger("maple-drift-bottle").info(`内容最大长度: ${config.maxContentLength}
|
|
569
|
+
ctx.logger("maple-drift-bottle").info(`内容最大长度: ${config.maxContentLength} 字`);
|
|
546
570
|
ctx.logger("maple-drift-bottle").info(`我的漂流瓶最大预览字数: ${config.maxPreviewLength} 字`);
|
|
547
|
-
ctx.logger("maple-drift-bottle").info(
|
|
548
|
-
ctx.logger("maple-drift-bottle").info(
|
|
549
|
-
if (config.saveImagesLocally) {
|
|
550
|
-
ctx.logger("maple-drift-bottle").info(`图片保存路径: ${config.imageSavePath}`);
|
|
551
|
-
ctx.logger("maple-drift-bottle").info(`压缩图片: ${config.compressImages ? "是" : "否"}`);
|
|
552
|
-
if (config.compressImages) {
|
|
553
|
-
ctx.logger("maple-drift-bottle").info(`图片最大尺寸: ${config.maxImageWidth}x${config.maxImageHeight} 像素`);
|
|
554
|
-
ctx.logger("maple-drift-bottle").info(`图片质量: ${config.imageQuality}%`);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
571
|
+
ctx.logger("maple-drift-bottle").info(`漂流瓶评论最大长度: ${config.maxCommentLength} 字`);
|
|
572
|
+
ctx.logger("maple-drift-bottle").info(`漂流瓶图片大小限制: ${config.maxImageSize} KB`);
|
|
557
573
|
ctx.logger("maple-drift-bottle").info(`管理人QQ: ${config.adminQQ.length > 0 ? config.adminQQ.join(", ") : "未配置"}`);
|
|
558
574
|
});
|
|
559
575
|
}
|
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.
|
|
4
|
+
"version": "0.1.1",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -14,8 +14,5 @@
|
|
|
14
14
|
],
|
|
15
15
|
"peerDependencies": {
|
|
16
16
|
"koishi": "^4.18.7"
|
|
17
|
-
},
|
|
18
|
-
"dependencies": {
|
|
19
|
-
"sharp": "^0.33.0"
|
|
20
17
|
}
|
|
21
18
|
}
|