koishi-plugin-message-dedup 0.0.1 → 0.0.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/README.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  Koishi 消息去重插件,检测群内重复的图片、链接、聊天记录。
4
4
 
5
- ![](assets/dup-1.jpg)
5
+ <p align="center">
6
+ <img src="assets/dup-1.jpg" width="200">
7
+ </p>
6
8
 
7
9
  ## 功能
8
10
 
package/assets/dup-1.jpg CHANGED
Binary file
package/lib/config.js CHANGED
@@ -4,8 +4,8 @@ exports.Config = void 0;
4
4
  const koishi_1 = require("koishi");
5
5
  exports.Config = koishi_1.Schema.object({
6
6
  enableImage: koishi_1.Schema.boolean()
7
- .default(false)
8
- .description('启用图片去重(默认关闭,可能误判表情包)'),
7
+ .default(true)
8
+ .description('启用图片去重(自动排除表情包)'),
9
9
  enableLink: koishi_1.Schema.boolean()
10
10
  .default(true)
11
11
  .description('启用链接去重'),
package/lib/index.js CHANGED
@@ -94,10 +94,18 @@ async function processMessage(session, config, ctx, logger) {
94
94
  return null;
95
95
  const username = session.author?.nickname || session.author?.username || session.username || '未知用户';
96
96
  const originalContent = session.content || extractTextFromElements(elements) || '';
97
- // 1. 检查图片
97
+ // 1. 检查图片(排除表情包:subType=1)
98
98
  if (config.enableImage) {
99
99
  for (const elem of elements) {
100
100
  if (elem.type === 'img' && elem.attrs?.src) {
101
+ // 表情包 subType 为 1,跳过
102
+ const subType = elem.attrs['sub-type'] ?? elem.attrs.subType;
103
+ if (subType === 1 || subType === '1') {
104
+ if (config.debug) {
105
+ logger.info('跳过表情包');
106
+ }
107
+ continue;
108
+ }
101
109
  const duplicate = await processImage(elem.attrs.src, session, username, originalContent, config, ctx, logger);
102
110
  if (duplicate)
103
111
  return duplicate;
@@ -116,6 +124,9 @@ async function processMessage(session, config, ctx, logger) {
116
124
  }
117
125
  // 3. 检查转发消息
118
126
  if (config.enableForward) {
127
+ if (config.debug) {
128
+ logger.info(`检查转发消息, elements types: ${elements.map(e => e.type).join(', ')}`);
129
+ }
119
130
  for (const elem of elements) {
120
131
  if (elem.type === 'forward') {
121
132
  const duplicate = await processForward(elem, session, username, originalContent, config, ctx, logger);
@@ -217,14 +228,73 @@ async function processLink(url, session, username, originalContent, config, ctx,
217
228
  }
218
229
  async function processForward(forwardElem, session, username, originalContent, config, ctx, logger) {
219
230
  try {
220
- const content = extractForwardContent(forwardElem);
221
- if (!content || content.length < 10) {
231
+ if (config.debug) {
232
+ logger.info(`处理转发消息, elem: ${JSON.stringify(forwardElem, null, 2)}`);
233
+ }
234
+ // 获取转发消息 ID
235
+ const forwardId = forwardElem.attrs?.id;
236
+ if (!forwardId) {
222
237
  return null;
223
238
  }
224
- const truncated = content.slice(0, config.forwardContentMaxLength);
239
+ // 先尝试通过 OneBot API 获取转发消息内容
240
+ let content = '';
241
+ if (session.platform === 'onebot' && session.bot?.internal) {
242
+ const internal = session.bot.internal;
243
+ // 尝试多种 payload 格式
244
+ const payloads = [
245
+ { message_id: forwardId },
246
+ { id: forwardId },
247
+ ];
248
+ // 尝试多种 API 调用方式
249
+ const callApi = async (action, params) => {
250
+ if (typeof internal._get === 'function') {
251
+ return await internal._get(action, params);
252
+ }
253
+ if (typeof internal.request === 'function') {
254
+ return await internal.request(action, params);
255
+ }
256
+ if (typeof internal.callAction === 'function') {
257
+ return await internal.callAction(action, params);
258
+ }
259
+ return null;
260
+ };
261
+ for (const payload of payloads) {
262
+ try {
263
+ const forwardData = await callApi('get_forward_msg', payload);
264
+ if (forwardData) {
265
+ const messages = extractMessagesArray(forwardData);
266
+ if (messages && Array.isArray(messages) && messages.length > 0) {
267
+ content = messages.map((node) => {
268
+ if (node.message) {
269
+ return node.message
270
+ .filter((m) => m.type === 'text')
271
+ .map((m) => m.data?.text || '')
272
+ .join('');
273
+ }
274
+ return '';
275
+ }).join('\n');
276
+ if (config.debug) {
277
+ logger.info(`get_forward_msg成功获取内容, 长度: ${content.length}`);
278
+ }
279
+ break;
280
+ }
281
+ }
282
+ }
283
+ catch (err) {
284
+ // API 调用失败,继续尝试
285
+ }
286
+ }
287
+ }
288
+ // 如果 API 获取失败,直接用 forwardId 作为去重标识
289
+ // 同一内容的转发消息在不同用户发送时应该有相同的 ID
290
+ const hashSource = content || forwardId;
291
+ if (config.debug) {
292
+ logger.info(`转发消息去重标识: ${hashSource.slice(0, 50)} (来源: ${content ? 'API内容' : 'forwardId'})`);
293
+ }
294
+ const truncated = hashSource.slice(0, config.forwardContentMaxLength);
225
295
  const hash = (0, hash_1.calculateStringHash)(truncated);
226
296
  if (config.debug) {
227
- logger.info(`转发消息哈希: ${hash}, 内容长度: ${content.length}`);
297
+ logger.info(`转发消息哈希: ${hash}`);
228
298
  }
229
299
  const guildId = session.guildId;
230
300
  const duplicate = await (0, database_1.findDuplicate)(ctx, guildId, 'forward', hash);
@@ -243,7 +313,7 @@ async function processForward(forwardElem, session, username, originalContent, c
243
313
  contentHash: hash,
244
314
  originalMessageId: session.messageId,
245
315
  originalContent: truncated.slice(0, 100),
246
- extraInfo: JSON.stringify({ preview: truncated.slice(0, 100) })
316
+ extraInfo: JSON.stringify({ forwardId, preview: truncated.slice(0, 100) })
247
317
  });
248
318
  return null;
249
319
  }
@@ -269,6 +339,28 @@ function extractForwardContent(elem) {
269
339
  }
270
340
  return content;
271
341
  }
342
+ /**
343
+ * 从多种可能的数据结构中提取 messages 数组
344
+ * 参考 chatluna-forward-msg 的实现
345
+ */
346
+ function extractMessagesArray(data) {
347
+ const candidates = [
348
+ data,
349
+ data?.messages,
350
+ data?.data,
351
+ data?.result,
352
+ data?.response,
353
+ data?.data?.messages,
354
+ data?.response?.messages,
355
+ data?.envelope?.result?.messages,
356
+ ];
357
+ for (const item of candidates) {
358
+ if (Array.isArray(item)) {
359
+ return item;
360
+ }
361
+ }
362
+ return null;
363
+ }
272
364
  async function sendDuplicateWarning(session, duplicate, config, ctx) {
273
365
  const date = new Date(duplicate.timestamp);
274
366
  const dateStr = date.toLocaleString('zh-CN', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-message-dedup",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "消息去重插件,检测群内重复的图片、链接、聊天记录",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",