koishi-plugin-chat-patch 1.2.0 → 2.0.0
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/client/icons/activity.vue +9 -9
- package/client/icons/index.ts +4 -4
- package/client/index.scss +40 -4
- package/client/index.ts +23 -22
- package/client/vue/chat-logic.ts +217 -3784
- package/client/vue/composables/useChatActions.ts +61 -0
- package/client/vue/composables/useChatData.ts +184 -0
- package/client/vue/composables/useImageCache.ts +140 -0
- package/client/vue/index.vue +434 -362
- package/client/vue/types.ts +71 -0
- package/dist/index.js +5 -27
- package/dist/style.css +1 -1
- package/lib/index.js +86 -37
- package/package.json +49 -40
- package/readme.md +25 -25
- package/src/api-handlers.ts +664 -657
- package/src/config.ts +47 -49
- package/src/file-manager.ts +168 -168
- package/src/index.ts +125 -125
- package/src/message-handler.ts +305 -282
- package/src/types.ts +59 -59
- package/src/utils.ts +143 -99
- package/client/vue/style.css +0 -1973
- package/lib/config.d.ts +0 -14
package/lib/index.js
CHANGED
|
@@ -39,12 +39,17 @@ __export(src_exports, {
|
|
|
39
39
|
usage: () => usage
|
|
40
40
|
});
|
|
41
41
|
module.exports = __toCommonJS(src_exports);
|
|
42
|
-
var
|
|
42
|
+
var import_node_path3 = __toESM(require("node:path"));
|
|
43
43
|
|
|
44
44
|
// src/utils.ts
|
|
45
|
+
var import_node_fs = require("node:fs");
|
|
46
|
+
var import_node_path = require("node:path");
|
|
47
|
+
var import_node_crypto = require("node:crypto");
|
|
48
|
+
var import_node_url = require("node:url");
|
|
45
49
|
var Utils = class {
|
|
46
|
-
constructor(config) {
|
|
50
|
+
constructor(config, ctx) {
|
|
47
51
|
this.config = config;
|
|
52
|
+
this.ctx = ctx;
|
|
48
53
|
}
|
|
49
54
|
static {
|
|
50
55
|
__name(this, "Utils");
|
|
@@ -95,14 +100,41 @@ var Utils = class {
|
|
|
95
100
|
}
|
|
96
101
|
return false;
|
|
97
102
|
}
|
|
98
|
-
//
|
|
103
|
+
// 持久化 base64 图片并返回本地文件 URL
|
|
104
|
+
persistBase64Image(base64Data) {
|
|
105
|
+
if (!this.ctx || !base64Data.startsWith("data:image/")) return base64Data;
|
|
106
|
+
try {
|
|
107
|
+
const dir = (0, import_node_path.join)(this.ctx.baseDir, "data", "chat-patch", "persist-images");
|
|
108
|
+
if (!(0, import_node_fs.existsSync)(dir)) (0, import_node_fs.mkdirSync)(dir, { recursive: true });
|
|
109
|
+
const hash = (0, import_node_crypto.createHash)("md5").update(base64Data).digest("hex");
|
|
110
|
+
const ext = base64Data.split(";")[0].split("/")[1] || "png";
|
|
111
|
+
const filename = `${Date.now()}_${hash}.${ext}`;
|
|
112
|
+
const filePath = (0, import_node_path.join)(dir, filename);
|
|
113
|
+
const base64Content = base64Data.split(",")[1];
|
|
114
|
+
(0, import_node_fs.writeFileSync)(filePath, Buffer.from(base64Content, "base64"));
|
|
115
|
+
this.cleanupPersistImages(dir);
|
|
116
|
+
return (0, import_node_url.pathToFileURL)(filePath).href;
|
|
117
|
+
} catch (e) {
|
|
118
|
+
return base64Data;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
cleanupPersistImages(dir) {
|
|
122
|
+
try {
|
|
123
|
+
const files = (0, import_node_fs.readdirSync)(dir).map((name2) => ({ name: name2, path: (0, import_node_path.join)(dir, name2), mtime: (0, import_node_fs.statSync)((0, import_node_path.join)(dir, name2)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
124
|
+
if (files.length > this.config.maxPersistImages) {
|
|
125
|
+
files.slice(this.config.maxPersistImages).forEach((f) => (0, import_node_fs.unlinkSync)(f.path));
|
|
126
|
+
}
|
|
127
|
+
} catch (e) {
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// 清理对象中的base64内容,改为持久化存储
|
|
99
131
|
cleanBase64Content(obj) {
|
|
100
132
|
if (obj === null || obj === void 0) {
|
|
101
133
|
return obj;
|
|
102
134
|
}
|
|
103
135
|
if (typeof obj === "string") {
|
|
104
136
|
if (this.isBase64(obj)) {
|
|
105
|
-
return
|
|
137
|
+
return this.persistBase64Image(obj);
|
|
106
138
|
}
|
|
107
139
|
return obj;
|
|
108
140
|
}
|
|
@@ -113,7 +145,7 @@ var Utils = class {
|
|
|
113
145
|
const cleaned = {};
|
|
114
146
|
for (const [key, value] of Object.entries(obj)) {
|
|
115
147
|
if (typeof value === "string" && (key === "src" || key === "url" || key === "file" || key === "data" || key === "content") && this.isBase64(value)) {
|
|
116
|
-
cleaned[key] =
|
|
148
|
+
cleaned[key] = this.persistBase64Image(value);
|
|
117
149
|
} else {
|
|
118
150
|
cleaned[key] = this.cleanBase64Content(value);
|
|
119
151
|
}
|
|
@@ -166,14 +198,31 @@ var MessageHandler = class {
|
|
|
166
198
|
// 更新频道信息到JSON文件
|
|
167
199
|
async updateChannelInfoToFile(session) {
|
|
168
200
|
let guildName = session.channelId;
|
|
201
|
+
let directUserName = session.username || session.userId || "未知用户";
|
|
169
202
|
const data = this.fileManager.readChatDataFromFile();
|
|
170
203
|
try {
|
|
171
204
|
if (session.guildId && session.bot.getGuild && typeof session.bot.getGuild === "function") {
|
|
172
205
|
const guild = await session.bot.getGuild(session.guildId);
|
|
173
206
|
guildName = guild?.name || session.channelId;
|
|
174
207
|
}
|
|
175
|
-
|
|
176
|
-
|
|
208
|
+
if (session.userId && session.isDirect && session.bot.getUser && typeof session.bot.getUser === "function") {
|
|
209
|
+
try {
|
|
210
|
+
const user = await session.bot.getUser(session.userId);
|
|
211
|
+
directUserName = user?.name || session.username || session.userId || "未知用户";
|
|
212
|
+
} catch (userError) {
|
|
213
|
+
this.logInfo("获取用户信息失败,使用备用名称:", userError);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (session.guildId && !session.bot.getGuild && session.bot.getChannel && typeof session.bot.getChannel === "function") {
|
|
217
|
+
try {
|
|
218
|
+
const channel = await session.bot.getChannel(session.guildId);
|
|
219
|
+
guildName = channel?.name || session.channelId;
|
|
220
|
+
} catch (channelError) {
|
|
221
|
+
this.logInfo("获取频道信息失败,使用频道ID作为备用:", channelError);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
this.logInfo("获取频道信息失败,使用频道ID作为备用:", error);
|
|
177
226
|
guildName = session.channelId;
|
|
178
227
|
}
|
|
179
228
|
if (!data.channels[session.selfId]) {
|
|
@@ -181,7 +230,7 @@ var MessageHandler = class {
|
|
|
181
230
|
}
|
|
182
231
|
const channelInfo = {
|
|
183
232
|
id: session.channelId,
|
|
184
|
-
name: session.isDirect ?
|
|
233
|
+
name: session.isDirect ? `私聊(${session.username || session.userId || directUserName})` : `${guildName} (${session.channelId})`,
|
|
185
234
|
type: session.type || 0,
|
|
186
235
|
channelId: session.channelId,
|
|
187
236
|
guildName,
|
|
@@ -286,8 +335,8 @@ var MessageHandler = class {
|
|
|
286
335
|
await this.updateBotInfoToFile(session);
|
|
287
336
|
const guildName = await this.updateChannelInfoToFile(session);
|
|
288
337
|
const timestamp = Date.now();
|
|
289
|
-
let content = "";
|
|
290
|
-
if (session.event?.message?.elements) {
|
|
338
|
+
let content = session.content || "";
|
|
339
|
+
if (!content && session.event?.message?.elements) {
|
|
291
340
|
content = this.utils.extractTextContent(session.event.message.elements).trim();
|
|
292
341
|
}
|
|
293
342
|
const messageInfo = {
|
|
@@ -353,13 +402,13 @@ var MessageHandler = class {
|
|
|
353
402
|
};
|
|
354
403
|
|
|
355
404
|
// src/file-manager.ts
|
|
356
|
-
var
|
|
357
|
-
var
|
|
405
|
+
var import_node_path2 = __toESM(require("node:path"));
|
|
406
|
+
var import_node_fs2 = __toESM(require("node:fs"));
|
|
358
407
|
var FileManager = class {
|
|
359
408
|
constructor(ctx, config) {
|
|
360
409
|
this.ctx = ctx;
|
|
361
410
|
this.config = config;
|
|
362
|
-
this.dataFilePath =
|
|
411
|
+
this.dataFilePath = import_node_path2.default.resolve(ctx.baseDir, "data", "chat-patch", "chat-data.json");
|
|
363
412
|
this.logger = ctx.logger("chat-patch");
|
|
364
413
|
this.utils = new Utils(config);
|
|
365
414
|
}
|
|
@@ -372,16 +421,16 @@ var FileManager = class {
|
|
|
372
421
|
utils;
|
|
373
422
|
// 确保目录存在
|
|
374
423
|
ensureDataDir() {
|
|
375
|
-
const dir =
|
|
376
|
-
if (!
|
|
377
|
-
|
|
424
|
+
const dir = import_node_path2.default.dirname(this.dataFilePath);
|
|
425
|
+
if (!import_node_fs2.default.existsSync(dir)) {
|
|
426
|
+
import_node_fs2.default.mkdirSync(dir, { recursive: true });
|
|
378
427
|
}
|
|
379
428
|
}
|
|
380
429
|
// 从JSON文件读取数据
|
|
381
430
|
readChatDataFromFile() {
|
|
382
431
|
try {
|
|
383
|
-
if (
|
|
384
|
-
const jsonData =
|
|
432
|
+
if (import_node_fs2.default.existsSync(this.dataFilePath)) {
|
|
433
|
+
const jsonData = import_node_fs2.default.readFileSync(this.dataFilePath, "utf8");
|
|
385
434
|
const data = JSON.parse(jsonData);
|
|
386
435
|
return {
|
|
387
436
|
bots: data.bots || {},
|
|
@@ -409,7 +458,7 @@ var FileManager = class {
|
|
|
409
458
|
this.ensureDataDir();
|
|
410
459
|
data.lastSaveTime = Date.now();
|
|
411
460
|
const jsonData = JSON.stringify(data, null, 2);
|
|
412
|
-
|
|
461
|
+
import_node_fs2.default.writeFileSync(this.dataFilePath, jsonData, "utf8");
|
|
413
462
|
} catch (error) {
|
|
414
463
|
this.logger.error("写入聊天数据失败:", error);
|
|
415
464
|
}
|
|
@@ -497,7 +546,9 @@ var FileManager = class {
|
|
|
497
546
|
|
|
498
547
|
// src/api-handlers.ts
|
|
499
548
|
var import_koishi = require("koishi");
|
|
500
|
-
var
|
|
549
|
+
var import_node_url2 = require("node:url");
|
|
550
|
+
var import_node_fs3 = require("node:fs");
|
|
551
|
+
var mime = __toESM(require("mime-types"));
|
|
501
552
|
var ApiHandlers = class {
|
|
502
553
|
constructor(ctx, config, fileManager, messageHandler) {
|
|
503
554
|
this.ctx = ctx;
|
|
@@ -689,8 +740,8 @@ var ApiHandlers = class {
|
|
|
689
740
|
messageId,
|
|
690
741
|
content: messageContent,
|
|
691
742
|
userId: data.selfId,
|
|
692
|
-
|
|
693
|
-
|
|
743
|
+
username: bot2?.user?.name || `Bot-${data.selfId}`,
|
|
744
|
+
avatar: bot2?.user?.avatar,
|
|
694
745
|
timestamp: Date.now(),
|
|
695
746
|
guildName: "",
|
|
696
747
|
// 这个信息在前端会补充
|
|
@@ -864,7 +915,6 @@ var ApiHandlers = class {
|
|
|
864
915
|
keepTempImages: this.config.keepTempImages,
|
|
865
916
|
loggerinfo: this.config.loggerinfo,
|
|
866
917
|
blockedPlatforms: this.config.blockedPlatforms || [],
|
|
867
|
-
chatContainerHeight: this.config.chatContainerHeight,
|
|
868
918
|
clearIndexedDBOnStart: this.config.clearIndexedDBOnStart
|
|
869
919
|
}
|
|
870
920
|
};
|
|
@@ -889,7 +939,7 @@ var ApiHandlers = class {
|
|
|
889
939
|
// 检查是否为文件 URL
|
|
890
940
|
isFileUrl(url) {
|
|
891
941
|
try {
|
|
892
|
-
const parsedUrl = new
|
|
942
|
+
const parsedUrl = new import_node_url2.URL(url);
|
|
893
943
|
return parsedUrl.protocol === "file:";
|
|
894
944
|
} catch {
|
|
895
945
|
return false;
|
|
@@ -898,7 +948,7 @@ var ApiHandlers = class {
|
|
|
898
948
|
// 创建文件 URL
|
|
899
949
|
createFileUrl(filePath) {
|
|
900
950
|
try {
|
|
901
|
-
return (0,
|
|
951
|
+
return (0, import_node_url2.pathToFileURL)(filePath).href;
|
|
902
952
|
} catch (error) {
|
|
903
953
|
this.logger.error("创建文件URL失败:", { filePath, error });
|
|
904
954
|
return `file://${filePath}`;
|
|
@@ -907,15 +957,16 @@ var ApiHandlers = class {
|
|
|
907
957
|
// 处理本地文件请求
|
|
908
958
|
async handleLocalFileRequest(fileUrl) {
|
|
909
959
|
try {
|
|
910
|
-
const
|
|
911
|
-
const
|
|
912
|
-
|
|
913
|
-
|
|
960
|
+
const filePath = (0, import_node_url2.fileURLToPath)(fileUrl);
|
|
961
|
+
const buffer = (0, import_node_fs3.readFileSync)(filePath);
|
|
962
|
+
const base64 = buffer.toString("base64");
|
|
963
|
+
const contentType = mime.lookup(filePath) || "application/octet-stream";
|
|
964
|
+
this.logInfo("成功读取本地文件:", { fileUrl, filePath, contentType });
|
|
914
965
|
return {
|
|
915
966
|
success: true,
|
|
916
|
-
base64
|
|
967
|
+
base64,
|
|
917
968
|
contentType,
|
|
918
|
-
dataUrl: `data:${contentType};base64,${
|
|
969
|
+
dataUrl: `data:${contentType};base64,${base64}`
|
|
919
970
|
};
|
|
920
971
|
} catch (error) {
|
|
921
972
|
this.logger.error("读取本地文件失败:", { fileUrl, error: error.message });
|
|
@@ -998,6 +1049,7 @@ var Config = import_koishi2.Schema.intersect([
|
|
|
998
1049
|
maxMessagesPerChannel: import_koishi2.Schema.number().default(500).description("每个群组最大保存消息数量").min(50).max(1500),
|
|
999
1050
|
keepMessagesOnClear: import_koishi2.Schema.number().default(50).description("手动清理历史记录时保留的消息数量").min(0).max(1e3),
|
|
1000
1051
|
keepTempImages: import_koishi2.Schema.number().default(50).description("发送消息保留的临时图片数量(最新的N张)").min(10).max(200),
|
|
1052
|
+
maxPersistImages: import_koishi2.Schema.number().default(100).description("持久化存储的机器人发送图片数量").min(10).max(500),
|
|
1001
1053
|
blockedPlatforms: import_koishi2.Schema.array(import_koishi2.Schema.object({
|
|
1002
1054
|
platformName: import_koishi2.Schema.string().description("平台名称或关键词"),
|
|
1003
1055
|
exactMatch: import_koishi2.Schema.boolean().default(false).description("完全匹配?如果关闭,包含关键词即屏蔽").default(true)
|
|
@@ -1018,9 +1070,6 @@ var Config = import_koishi2.Schema.intersect([
|
|
|
1018
1070
|
]
|
|
1019
1071
|
)
|
|
1020
1072
|
}).description("基础设置"),
|
|
1021
|
-
import_koishi2.Schema.object({
|
|
1022
|
-
chatContainerHeight: import_koishi2.Schema.number().default(80).description("手机端使用的视口高度(防止文本输入框被挡住)").min(50).max(100)
|
|
1023
|
-
}).description("进阶设置"),
|
|
1024
1073
|
import_koishi2.Schema.object({
|
|
1025
1074
|
clearIndexedDBOnStart: import_koishi2.Schema.boolean().default(true).description("启动时强制清空IndexedDB缓存(适用于紧急情况,防止浏览器卡死)"),
|
|
1026
1075
|
loggerinfo: import_koishi2.Schema.boolean().default(false).description("日志调试模式").experimental()
|
|
@@ -1049,7 +1098,7 @@ async function apply(ctx, config) {
|
|
|
1049
1098
|
const fileManager = new FileManager(ctx, config);
|
|
1050
1099
|
const messageHandler = new MessageHandler(ctx, config, fileManager);
|
|
1051
1100
|
const apiHandlers = new ApiHandlers(ctx, config, fileManager, messageHandler);
|
|
1052
|
-
const utils = new Utils(config);
|
|
1101
|
+
const utils = new Utils(config, ctx);
|
|
1053
1102
|
const initialData = fileManager.readChatDataFromFile();
|
|
1054
1103
|
const cleanedData = fileManager.cleanExcessMessages(initialData);
|
|
1055
1104
|
const originalCount = Object.values(initialData.messages).reduce((total, msgs) => total + msgs.length, 0);
|
|
@@ -1098,8 +1147,8 @@ async function apply(ctx, config) {
|
|
|
1098
1147
|
});
|
|
1099
1148
|
apiHandlers.registerApiHandlers();
|
|
1100
1149
|
ctx.console.addEntry({
|
|
1101
|
-
dev:
|
|
1102
|
-
prod:
|
|
1150
|
+
dev: import_node_path3.default.resolve(__dirname, "../client/index.ts"),
|
|
1151
|
+
prod: import_node_path3.default.resolve(__dirname, "../dist")
|
|
1103
1152
|
});
|
|
1104
1153
|
}
|
|
1105
1154
|
__name(apply, "apply");
|
package/package.json
CHANGED
|
@@ -1,40 +1,49 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "koishi-plugin-chat-patch",
|
|
3
|
-
"description": "[<ruby>chat-patch<rp>(</rp><rt>点我预览效果</rt><rp>)</rp></ruby>](https://i0.hdslb.com/bfs/openplatform/71074dfc9e5256fc3333d8bd8478bec1af874046.png) 视奸小插件((bushi( (低性能警告)。手机端适配。灵感来自 chat 插件。",
|
|
4
|
-
"version": "
|
|
5
|
-
"main": "lib/index.js",
|
|
6
|
-
"typings": "lib/index.d.ts",
|
|
7
|
-
"files": [
|
|
8
|
-
"client",
|
|
9
|
-
"dist",
|
|
10
|
-
"lib",
|
|
11
|
-
"src"
|
|
12
|
-
],
|
|
13
|
-
"license": "MIT",
|
|
14
|
-
"homepage": "https://github.com/
|
|
15
|
-
"bugs": {
|
|
16
|
-
"url": "https://github.com/
|
|
17
|
-
},
|
|
18
|
-
"keywords": [
|
|
19
|
-
"koishi",
|
|
20
|
-
"plugin",
|
|
21
|
-
"chat",
|
|
22
|
-
"聊天室",
|
|
23
|
-
"console"
|
|
24
|
-
],
|
|
25
|
-
"
|
|
26
|
-
"service": {
|
|
27
|
-
"required": [
|
|
28
|
-
"console"
|
|
29
|
-
]
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
"peerDependencies": {
|
|
33
|
-
"@koishijs/plugin-console": "^5.11.0",
|
|
34
|
-
"koishi": "^4.16.0"
|
|
35
|
-
},
|
|
36
|
-
"devDependencies": {
|
|
37
|
-
"@koishijs/client": "^5.11.0",
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "koishi-plugin-chat-patch",
|
|
3
|
+
"description": "[<ruby>chat-patch<rp>(</rp><rt>点我预览效果</rt><rp>)</rp></ruby>](https://i0.hdslb.com/bfs/openplatform/71074dfc9e5256fc3333d8bd8478bec1af874046.png) 视奸小插件((bushi( (低性能警告)。手机端适配。灵感来自 chat 插件。",
|
|
4
|
+
"version": "2.0.0",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"typings": "lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"client",
|
|
9
|
+
"dist",
|
|
10
|
+
"lib",
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"homepage": "https://github.com/koishi-shangxue-plugins/koishi-shangxue-apps/",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/koishi-shangxue-plugins/koishi-shangxue-apps/issues"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"koishi",
|
|
20
|
+
"plugin",
|
|
21
|
+
"chat",
|
|
22
|
+
"聊天室",
|
|
23
|
+
"console"
|
|
24
|
+
],
|
|
25
|
+
"koishi": {
|
|
26
|
+
"service": {
|
|
27
|
+
"required": [
|
|
28
|
+
"console"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@koishijs/plugin-console": "^5.11.0",
|
|
34
|
+
"koishi": "^4.16.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@koishijs/client": "^5.11.0",
|
|
38
|
+
"@types/mime-types": "^3.0.1",
|
|
39
|
+
"autoprefixer": "^10.4.23",
|
|
40
|
+
"koishi": "^4.16.0",
|
|
41
|
+
"postcss": "^8.5.6",
|
|
42
|
+
"tailwindcss": "^3.4.17"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@element-plus/icons-vue": "^2.3.2",
|
|
46
|
+
"element-plus": "^2.13.0",
|
|
47
|
+
"mime-types": "^3.0.2"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/readme.md
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
# koishi-plugin-chat-patch
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/koishi-plugin-chat-patch) [](https://www.npmjs.com/package/koishi-plugin-chat-patch)
|
|
4
|
-
|
|
5
|
-
Koishi 控制台聊天插件,允许用户直接在控制台中管理机器人、频道和消息。
|
|
6
|
-
|
|
7
|
-
支持消息的收发、图片缓存、频道置顶等。
|
|
8
|
-
|
|
9
|
-
## ✨ 功能特性
|
|
10
|
-
|
|
11
|
-
- **聊天界面**:在 Koishi 控制台内直接查看机器人的聊天记录。
|
|
12
|
-
- **消息收发**:支持接收文本、图片等多种类型的消息。
|
|
13
|
-
- **图片缓存**:通过 IndexedDB 缓存图片,优化加载速度并减少网络请求。
|
|
14
|
-
- **手机端优化**:针对手机端进行适配,提供滑动返回等手势操作,并可配置聊天容器高度以防止输入框被遮挡。
|
|
15
|
-
- **平台屏蔽**:可配置屏蔽特定平台的消息,避免不必要的干扰。
|
|
16
|
-
|
|
17
|
-
## 🛠️ 开发与贡献
|
|
18
|
-
|
|
19
|
-
欢迎 PR ~
|
|
20
|
-
|
|
21
|
-
PR 方式请参考 -> https://github.com/
|
|
22
|
-
|
|
23
|
-
## 许可证
|
|
24
|
-
|
|
25
|
-
本项目采用 [MIT 许可证](LICENSE) 开源。
|
|
1
|
+
# koishi-plugin-chat-patch
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/koishi-plugin-chat-patch) [](https://www.npmjs.com/package/koishi-plugin-chat-patch)
|
|
4
|
+
|
|
5
|
+
Koishi 控制台聊天插件,允许用户直接在控制台中管理机器人、频道和消息。
|
|
6
|
+
|
|
7
|
+
支持消息的收发、图片缓存、频道置顶等。
|
|
8
|
+
|
|
9
|
+
## ✨ 功能特性
|
|
10
|
+
|
|
11
|
+
- **聊天界面**:在 Koishi 控制台内直接查看机器人的聊天记录。
|
|
12
|
+
- **消息收发**:支持接收文本、图片等多种类型的消息。
|
|
13
|
+
- **图片缓存**:通过 IndexedDB 缓存图片,优化加载速度并减少网络请求。
|
|
14
|
+
- **手机端优化**:针对手机端进行适配,提供滑动返回等手势操作,并可配置聊天容器高度以防止输入框被遮挡。
|
|
15
|
+
- **平台屏蔽**:可配置屏蔽特定平台的消息,避免不必要的干扰。
|
|
16
|
+
|
|
17
|
+
## 🛠️ 开发与贡献
|
|
18
|
+
|
|
19
|
+
欢迎 PR ~
|
|
20
|
+
|
|
21
|
+
PR 方式请参考 -> <https://github.com/koishi-shangxue-plugins/koishi-shangxue-apps/tree/main?tab=readme-ov-file#%E5%A6%82%E4%BD%95%E5%9C%A8%E9%A1%B9%E7%9B%AE%E6%A8%A1%E6%9D%BF%E4%B8%AD%E5%BC%80%E5%8F%91%E6%AD%A4%E4%BB%93%E5%BA%93>
|
|
22
|
+
|
|
23
|
+
## 许可证
|
|
24
|
+
|
|
25
|
+
本项目采用 [MIT 许可证](LICENSE) 开源。
|