koishi-plugin-chat-patch 1.0.7 → 1.0.8

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.
@@ -0,0 +1,17 @@
1
+ import { Context } from 'koishi';
2
+ import { Config } from './config';
3
+ import { FileManager } from './file-manager';
4
+ export declare class ApiHandlers {
5
+ private ctx;
6
+ private config;
7
+ private fileManager;
8
+ private logger;
9
+ constructor(ctx: Context, config: Config, fileManager: FileManager);
10
+ registerApiHandlers(): void;
11
+ private isFileUrl;
12
+ private createFileUrl;
13
+ private handleLocalFileRequest;
14
+ private setupTempFileCleanup;
15
+ private cleanupTempImagesByCount;
16
+ private logInfo;
17
+ }
@@ -0,0 +1,13 @@
1
+ import { Schema } from 'koishi';
2
+ export interface Config {
3
+ loggerinfo: boolean;
4
+ maxMessagesPerChannel: number;
5
+ keepMessagesOnClear: number;
6
+ keepTempImages: number;
7
+ blockedPlatforms: Array<{
8
+ platformName: string;
9
+ exactMatch: boolean;
10
+ }>;
11
+ chatContainerHeight: number;
12
+ }
13
+ export declare const Config: Schema<Config>;
@@ -0,0 +1,18 @@
1
+ import { Context } from 'koishi';
2
+ import { ChatData, MessageInfo } from './types';
3
+ import { Config } from './config';
4
+ export declare class FileManager {
5
+ private ctx;
6
+ private config;
7
+ private dataFilePath;
8
+ private fileOperationLock;
9
+ private logger;
10
+ private utils;
11
+ constructor(ctx: Context, config: Config);
12
+ private ensureDataDir;
13
+ readChatDataFromFile(): ChatData;
14
+ writeChatDataToFile(data: ChatData): void;
15
+ cleanExcessMessages(data: ChatData): ChatData;
16
+ addMessageToFile(messageInfo: MessageInfo): Promise<void>;
17
+ private logInfo;
18
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { Context } from 'koishi';
2
+ import { Config } from './config';
3
+ export declare const name = "chat-patch";
4
+ export declare const reusable = false;
5
+ export declare const filter = false;
6
+ export declare const inject: {
7
+ required: string[];
8
+ };
9
+ export declare const usage = "\n\n---\n\n\u5F00\u542F\u540E\uFF0C\u5373\u53EF\u5728koishi\u63A7\u5236\u53F0\u64CD\u4F5C\u673A\u5668\u4EBA\u6536\u53D1\u6D88\u606F\u5566\n\n\u6682\u65F6\u53EA\u652F\u6301\u63A5\u53D7\u56FE\u6587\u6D88\u606F / \u53D1\u9001\u6587\u5B57\u6D88\u606F\n\n---\n";
10
+ export { Config } from './config';
11
+ export declare function apply(ctx: Context, config: Config): Promise<void>;
package/lib/index.js CHANGED
@@ -44,12 +44,98 @@ var import_node_path2 = __toESM(require("node:path"));
44
44
  // src/file-manager.ts
45
45
  var import_node_path = __toESM(require("node:path"));
46
46
  var import_node_fs = __toESM(require("node:fs"));
47
+
48
+ // src/utils.ts
49
+ var Utils = class {
50
+ constructor(config) {
51
+ this.config = config;
52
+ }
53
+ static {
54
+ __name(this, "Utils");
55
+ }
56
+ // 检查平台是否被屏蔽
57
+ isPlatformBlocked(platform) {
58
+ if (!this.config.blockedPlatforms || this.config.blockedPlatforms.length === 0) {
59
+ return false;
60
+ }
61
+ for (const blockedPlatform of this.config.blockedPlatforms) {
62
+ if (blockedPlatform.exactMatch) {
63
+ if (platform === blockedPlatform.platformName) {
64
+ return true;
65
+ }
66
+ } else {
67
+ if (platform.includes(blockedPlatform.platformName)) {
68
+ return true;
69
+ }
70
+ }
71
+ }
72
+ return false;
73
+ }
74
+ // 递归提取所有文本内容的函数
75
+ extractTextContent(elements) {
76
+ let text = "";
77
+ for (const element of elements) {
78
+ if (element.type === "text") {
79
+ text += element.attrs?.content || "";
80
+ } else if (element.type === "p") {
81
+ if (element.children && element.children.length > 0) {
82
+ text += this.extractTextContent(element.children) + "\n";
83
+ }
84
+ } else if (element.children && element.children.length > 0) {
85
+ text += this.extractTextContent(element.children);
86
+ }
87
+ }
88
+ return text;
89
+ }
90
+ // 检查字符串是否为base64格式
91
+ isBase64(str) {
92
+ if (!str || typeof str !== "string") return false;
93
+ if (str.startsWith("data:")) {
94
+ return str.includes("base64,");
95
+ }
96
+ if (str.length > 100) {
97
+ const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
98
+ return base64Regex.test(str);
99
+ }
100
+ return false;
101
+ }
102
+ // 清理对象中的base64内容
103
+ cleanBase64Content(obj) {
104
+ if (obj === null || obj === void 0) {
105
+ return obj;
106
+ }
107
+ if (typeof obj === "string") {
108
+ if (this.isBase64(obj)) {
109
+ return "暂不支持记录base64内容";
110
+ }
111
+ return obj;
112
+ }
113
+ if (Array.isArray(obj)) {
114
+ return obj.map((item) => this.cleanBase64Content(item));
115
+ }
116
+ if (typeof obj === "object") {
117
+ const cleaned = {};
118
+ for (const [key, value] of Object.entries(obj)) {
119
+ if (typeof value === "string" && (key === "src" || key === "url" || key === "file" || key === "data" || key === "content") && this.isBase64(value)) {
120
+ cleaned[key] = "暂不支持记录base64内容";
121
+ } else {
122
+ cleaned[key] = this.cleanBase64Content(value);
123
+ }
124
+ }
125
+ return cleaned;
126
+ }
127
+ return obj;
128
+ }
129
+ };
130
+
131
+ // src/file-manager.ts
47
132
  var FileManager = class {
48
133
  constructor(ctx, config) {
49
134
  this.ctx = ctx;
50
135
  this.config = config;
51
136
  this.dataFilePath = import_node_path.default.resolve(ctx.baseDir, "data", "chat-patch", "chat-data.json");
52
137
  this.logger = ctx.logger("chat-patch");
138
+ this.utils = new Utils(config);
53
139
  }
54
140
  static {
55
141
  __name(this, "FileManager");
@@ -57,6 +143,7 @@ var FileManager = class {
57
143
  dataFilePath;
58
144
  fileOperationLock = Promise.resolve();
59
145
  logger;
146
+ utils;
60
147
  // 确保目录存在
61
148
  ensureDataDir() {
62
149
  const dir = import_node_path.default.dirname(this.dataFilePath);
@@ -147,8 +234,9 @@ var FileManager = class {
147
234
  if (!messageInfo.timestamp) {
148
235
  messageInfo.timestamp = Date.now();
149
236
  }
237
+ const cleanedMessageInfo = this.utils.cleanBase64Content(messageInfo);
150
238
  const beforeCount = data.messages[channelKey].length;
151
- data.messages[channelKey].push(messageInfo);
239
+ data.messages[channelKey].push(cleanedMessageInfo);
152
240
  const afterCount = data.messages[channelKey].length;
153
241
  if (data.messages[channelKey].length > this.config.maxMessagesPerChannel) {
154
242
  data.messages[channelKey].sort((a, b) => a.timestamp - b.timestamp);
@@ -181,50 +269,6 @@ var FileManager = class {
181
269
  }
182
270
  };
183
271
 
184
- // src/utils.ts
185
- var Utils = class {
186
- constructor(config) {
187
- this.config = config;
188
- }
189
- static {
190
- __name(this, "Utils");
191
- }
192
- // 检查平台是否被屏蔽
193
- isPlatformBlocked(platform) {
194
- if (!this.config.blockedPlatforms || this.config.blockedPlatforms.length === 0) {
195
- return false;
196
- }
197
- for (const blockedPlatform of this.config.blockedPlatforms) {
198
- if (blockedPlatform.exactMatch) {
199
- if (platform === blockedPlatform.platformName) {
200
- return true;
201
- }
202
- } else {
203
- if (platform.includes(blockedPlatform.platformName)) {
204
- return true;
205
- }
206
- }
207
- }
208
- return false;
209
- }
210
- // 递归提取所有文本内容的函数
211
- extractTextContent(elements) {
212
- let text = "";
213
- for (const element of elements) {
214
- if (element.type === "text") {
215
- text += element.attrs?.content || "";
216
- } else if (element.type === "p") {
217
- if (element.children && element.children.length > 0) {
218
- text += this.extractTextContent(element.children) + "\n";
219
- }
220
- } else if (element.children && element.children.length > 0) {
221
- text += this.extractTextContent(element.children);
222
- }
223
- }
224
- return text;
225
- }
226
- };
227
-
228
272
  // src/message-handler.ts
229
273
  var MessageHandler = class {
230
274
  constructor(ctx, config, fileManager) {
@@ -325,12 +369,12 @@ var MessageHandler = class {
325
369
  timestamp,
326
370
  channelId: session.channelId,
327
371
  selfId: session.selfId,
328
- elements,
372
+ elements: this.utils.cleanBase64Content(elements),
329
373
  type: "user",
330
374
  guildId: session.guildId,
331
375
  guildName,
332
376
  platform: session.platform || "unknown",
333
- quote: quoteInfo
377
+ quote: quoteInfo ? this.utils.cleanBase64Content(quoteInfo) : void 0
334
378
  };
335
379
  await this.fileManager.addMessageToFile(messageInfo);
336
380
  const messageEvent = {
@@ -347,8 +391,8 @@ var MessageHandler = class {
347
391
  guildId: session.guildId,
348
392
  guildName,
349
393
  channelType: session.type || 0,
350
- elements,
351
- quote: quoteInfo,
394
+ elements: this.utils.cleanBase64Content(elements),
395
+ quote: quoteInfo ? this.utils.cleanBase64Content(quoteInfo) : void 0,
352
396
  bot: {
353
397
  avatar: session.bot.user?.avatar,
354
398
  name: session.bot.user?.name
@@ -378,7 +422,7 @@ var MessageHandler = class {
378
422
  timestamp,
379
423
  channelId: session.event?.channel?.id || session.channelId,
380
424
  selfId: session.selfId,
381
- elements: session.event?.message?.elements,
425
+ elements: this.utils.cleanBase64Content(session.event?.message?.elements),
382
426
  type: "bot",
383
427
  guildId: session.event?.guild?.id || session.guildId,
384
428
  guildName,
@@ -399,7 +443,7 @@ var MessageHandler = class {
399
443
  guildId: session.event?.guild?.id || session.guildId,
400
444
  guildName,
401
445
  channelType: session.event?.channel?.type || session.type || 0,
402
- elements: session.event?.message?.elements,
446
+ elements: this.utils.cleanBase64Content(session.event?.message?.elements),
403
447
  bot: {
404
448
  avatar: session.bot.user?.avatar,
405
449
  name: session.bot.user?.name
@@ -0,0 +1,16 @@
1
+ import { Context, Session } from 'koishi';
2
+ import { Config } from './config';
3
+ import { FileManager } from './file-manager';
4
+ export declare class MessageHandler {
5
+ private ctx;
6
+ private config;
7
+ private fileManager;
8
+ private logger;
9
+ private utils;
10
+ constructor(ctx: Context, config: Config, fileManager: FileManager);
11
+ updateBotInfoToFile(session: Session): Promise<void>;
12
+ updateChannelInfoToFile(session: Session): Promise<string>;
13
+ broadcastMessageEvent(session: Session): Promise<void>;
14
+ broadcastBotMessageEvent(session: Session): Promise<void>;
15
+ private logInfo;
16
+ }
package/lib/types.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { h } from 'koishi';
2
+ export interface BotInfo {
3
+ selfId: string;
4
+ platform: string;
5
+ username: string;
6
+ avatar?: string;
7
+ status: 'online' | 'offline';
8
+ }
9
+ export interface ChannelInfo {
10
+ id: string;
11
+ name: string;
12
+ type: number | string;
13
+ guildId?: string;
14
+ guildName?: string;
15
+ }
16
+ export interface QuoteInfo {
17
+ messageId: string;
18
+ id: string;
19
+ content: string;
20
+ elements?: h[];
21
+ user: {
22
+ id: string;
23
+ name: string;
24
+ userId: string;
25
+ avatar?: string;
26
+ username: string;
27
+ };
28
+ timestamp: number;
29
+ }
30
+ export interface MessageInfo {
31
+ id: string;
32
+ content: string;
33
+ userId: string;
34
+ username: string;
35
+ avatar?: string;
36
+ timestamp: number;
37
+ channelId: string;
38
+ selfId: string;
39
+ elements?: h[];
40
+ type: 'user' | 'bot';
41
+ guildId?: string;
42
+ guildName?: string;
43
+ platform: string;
44
+ quote?: QuoteInfo;
45
+ }
46
+ export interface ChatData {
47
+ bots: Record<string, BotInfo>;
48
+ channels: Record<string, Record<string, ChannelInfo>>;
49
+ messages: Record<string, MessageInfo[]>;
50
+ pinnedBots: string[];
51
+ pinnedChannels: string[];
52
+ lastSaveTime?: number;
53
+ }
package/lib/utils.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { Config } from './config';
2
+ export declare class Utils {
3
+ private config;
4
+ constructor(config: Config);
5
+ isPlatformBlocked(platform: string): boolean;
6
+ extractTextContent(elements: any[]): string;
7
+ private isBase64;
8
+ cleanBase64Content(obj: any): any;
9
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-chat-patch",
3
3
  "description": "[<ruby>chat-patch<rp>(</rp><rt>点我预览效果</rt><rp>)</rp></ruby>](https://i0.hdslb.com/bfs/openplatform/71074dfc9e5256fc3333d8bd8478bec1af874046.png) 视奸小插件((bushi( (低性能警告)。手机端适配。灵感来自 chat 插件。",
4
- "version": "1.0.7",
4
+ "version": "1.0.8",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -3,11 +3,13 @@ import fs from 'node:fs'
3
3
  import { Context, Logger } from 'koishi'
4
4
  import { ChatData, MessageInfo } from './types'
5
5
  import { Config } from './config'
6
+ import { Utils } from './utils'
6
7
 
7
8
  export class FileManager {
8
9
  private dataFilePath: string
9
10
  private fileOperationLock = Promise.resolve()
10
11
  private logger: Logger
12
+ private utils: Utils
11
13
 
12
14
  constructor(
13
15
  private ctx: Context,
@@ -15,6 +17,7 @@ export class FileManager {
15
17
  ) {
16
18
  this.dataFilePath = path.resolve(ctx.baseDir, 'data', 'chat-patch', 'chat-data.json')
17
19
  this.logger = ctx.logger('chat-patch')
20
+ this.utils = new Utils(config)
18
21
  }
19
22
 
20
23
  // 确保目录存在
@@ -119,8 +122,11 @@ export class FileManager {
119
122
  messageInfo.timestamp = Date.now()
120
123
  }
121
124
 
125
+ // 清理消息中的base64内容
126
+ const cleanedMessageInfo = this.utils.cleanBase64Content(messageInfo) as MessageInfo
127
+
122
128
  const beforeCount = data.messages[channelKey].length
123
- data.messages[channelKey].push(messageInfo)
129
+ data.messages[channelKey].push(cleanedMessageInfo)
124
130
  const afterCount = data.messages[channelKey].length
125
131
 
126
132
  // 限制消息数量 - 保留最新的消息
@@ -127,12 +127,12 @@ export class MessageHandler {
127
127
  timestamp: timestamp,
128
128
  channelId: session.channelId,
129
129
  selfId: session.selfId,
130
- elements: elements,
130
+ elements: this.utils.cleanBase64Content(elements),
131
131
  type: 'user',
132
132
  guildId: session.guildId,
133
133
  guildName: guildName,
134
134
  platform: session.platform || 'unknown',
135
- quote: quoteInfo
135
+ quote: quoteInfo ? this.utils.cleanBase64Content(quoteInfo) : undefined
136
136
  }
137
137
 
138
138
  await this.fileManager.addMessageToFile(messageInfo)
@@ -151,8 +151,8 @@ export class MessageHandler {
151
151
  guildId: session.guildId,
152
152
  guildName: guildName,
153
153
  channelType: session.type || 0,
154
- elements: elements,
155
- quote: quoteInfo,
154
+ elements: this.utils.cleanBase64Content(elements),
155
+ quote: quoteInfo ? this.utils.cleanBase64Content(quoteInfo) : undefined,
156
156
  bot: {
157
157
  avatar: session.bot.user?.avatar,
158
158
  name: session.bot.user?.name,
@@ -189,7 +189,7 @@ export class MessageHandler {
189
189
  timestamp: timestamp,
190
190
  channelId: session.event?.channel?.id || session.channelId,
191
191
  selfId: session.selfId,
192
- elements: session.event?.message?.elements,
192
+ elements: this.utils.cleanBase64Content(session.event?.message?.elements),
193
193
  type: 'bot',
194
194
  guildId: session.event?.guild?.id || session.guildId,
195
195
  guildName: guildName,
@@ -212,7 +212,7 @@ export class MessageHandler {
212
212
  guildId: session.event?.guild?.id || session.guildId,
213
213
  guildName: guildName,
214
214
  channelType: session.event?.channel?.type || session.type || 0,
215
- elements: session.event?.message?.elements,
215
+ elements: this.utils.cleanBase64Content(session.event?.message?.elements),
216
216
  bot: {
217
217
  avatar: session.bot.user?.avatar,
218
218
  name: session.bot.user?.name,
package/src/utils.ts CHANGED
@@ -40,4 +40,61 @@ export class Utils {
40
40
  }
41
41
  return text
42
42
  }
43
+
44
+ // 检查字符串是否为base64格式
45
+ private isBase64(str: string): boolean {
46
+ if (!str || typeof str !== 'string') return false
47
+
48
+ // 检查是否以data:开头的base64格式
49
+ if (str.startsWith('data:')) {
50
+ return str.includes('base64,')
51
+ }
52
+
53
+ // 检查纯base64字符串(长度大于100且符合base64格式)
54
+ if (str.length > 100) {
55
+ const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/
56
+ return base64Regex.test(str)
57
+ }
58
+
59
+ return false
60
+ }
61
+
62
+ // 清理对象中的base64内容
63
+ cleanBase64Content(obj: any): any {
64
+ if (obj === null || obj === undefined) {
65
+ return obj
66
+ }
67
+
68
+ if (typeof obj === 'string') {
69
+ if (this.isBase64(obj)) {
70
+ return '暂不支持记录base64内容'
71
+ }
72
+ return obj
73
+ }
74
+
75
+ if (Array.isArray(obj)) {
76
+ return obj.map(item => this.cleanBase64Content(item))
77
+ }
78
+
79
+ if (typeof obj === 'object') {
80
+ const cleaned: any = {}
81
+ for (const [key, value] of Object.entries(obj)) {
82
+ // 特别处理常见的base64字段
83
+ if (typeof value === 'string' && (
84
+ key === 'src' ||
85
+ key === 'url' ||
86
+ key === 'file' ||
87
+ key === 'data' ||
88
+ key === 'content'
89
+ ) && this.isBase64(value)) {
90
+ cleaned[key] = '暂不支持记录base64内容'
91
+ } else {
92
+ cleaned[key] = this.cleanBase64Content(value)
93
+ }
94
+ }
95
+ return cleaned
96
+ }
97
+
98
+ return obj
99
+ }
43
100
  }