@meet-im/meet 3.0.1 → 3.1.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/dist/src/bot.d.ts +2 -1
- package/dist/src/bot.js +9 -5
- package/dist/src/channel.js +51 -2
- package/dist/src/directory-cache.d.ts +43 -0
- package/dist/src/directory-cache.js +125 -0
- package/dist/src/mentions.d.ts +9 -0
- package/dist/src/mentions.js +44 -0
- package/dist/src/monitor.js +5 -2
- package/dist/src/sdk-bridge.d.ts +6 -1
- package/dist/src/sdk-bridge.js +81 -0
- package/dist/src/send.js +4 -1
- package/dist/src/types.d.ts +5 -0
- package/package.json +2 -2
package/dist/src/bot.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
2
2
|
import type { HistoryEntry } from "openclaw/plugin-sdk/reply-history";
|
|
3
3
|
import type { MeetBot, MsgContent } from "@meet-im/meet-bot-jssdk";
|
|
4
|
-
import type { ResolvedMeetAccount } from "./types.js";
|
|
4
|
+
import type { ResolvedMeetAccount, MeetMessageContext } from "./types.js";
|
|
5
5
|
export declare function handleMeetMessage(params: {
|
|
6
6
|
cfg: ClawdbotConfig;
|
|
7
7
|
msg: MsgContent;
|
|
@@ -12,4 +12,5 @@ export declare function handleMeetMessage(params: {
|
|
|
12
12
|
bot: MeetBot;
|
|
13
13
|
groupHistories: Map<string, HistoryEntry[]>;
|
|
14
14
|
quoteMsgMap?: Record<string, MsgContent>;
|
|
15
|
+
ctx?: MeetMessageContext;
|
|
15
16
|
}): Promise<void>;
|
package/dist/src/bot.js
CHANGED
|
@@ -12,12 +12,12 @@ function formatHistoryEntry(entry) {
|
|
|
12
12
|
return `${entry.sender}: ${entry.body}`;
|
|
13
13
|
}
|
|
14
14
|
export async function handleMeetMessage(params) {
|
|
15
|
-
const { cfg, msg, botUserId, runtime, accountId, account, bot, groupHistories, quoteMsgMap } = params;
|
|
15
|
+
const { cfg, msg, botUserId, runtime, accountId, account, bot, groupHistories, quoteMsgMap, ctx: providedCtx } = params;
|
|
16
16
|
const log = runtime?.log ?? console.log;
|
|
17
17
|
const error = runtime?.error ?? console.error;
|
|
18
18
|
let ctx;
|
|
19
19
|
try {
|
|
20
|
-
ctx = msgContentToContext(msg, botUserId, quoteMsgMap);
|
|
20
|
+
ctx = providedCtx ?? msgContentToContext(msg, botUserId, quoteMsgMap);
|
|
21
21
|
}
|
|
22
22
|
catch (err) {
|
|
23
23
|
error(`[${accountId}]: failed to parse message: ${String(err)}`);
|
|
@@ -235,9 +235,12 @@ export async function handleMeetMessage(params) {
|
|
|
235
235
|
// 构建最终的消息内容
|
|
236
236
|
// Discord 做法:文字优先,无文字时用媒体占位符
|
|
237
237
|
// 媒体路径通过 MediaPaths 传递,mediaContext 仅作为 BodyForAgent 的补充描述
|
|
238
|
+
const mentionsContext = ctx.mentions && ctx.mentions.length > 0
|
|
239
|
+
? `\n\nMentioned users: ${ctx.mentions.map((m) => `${m.name} (${m.userId})`).join(", ")}`
|
|
240
|
+
: "";
|
|
238
241
|
const finalContent = ctx.content.trim()
|
|
239
|
-
? `${ctx.content.trim()}${mediaContext}`
|
|
240
|
-
: (ctx.placeholder || "") + mediaContext;
|
|
242
|
+
? `${ctx.content.trim()}${mentionsContext}${mediaContext}`
|
|
243
|
+
: (ctx.placeholder || "") + mentionsContext + mediaContext;
|
|
241
244
|
// Discord 做法:跳过空内容消息
|
|
242
245
|
if (!finalContent.trim() && mediaPaths.length === 0) {
|
|
243
246
|
log(`[${accountId}]: skip message ${ctx.messageId} (empty content)`);
|
|
@@ -301,7 +304,7 @@ export async function handleMeetMessage(params) {
|
|
|
301
304
|
SessionKey: route.sessionKey,
|
|
302
305
|
AccountId: route.accountId,
|
|
303
306
|
ChatType: isGroup ? "group" : "direct",
|
|
304
|
-
GroupSubject: isGroup ? ctx.chatId : undefined,
|
|
307
|
+
GroupSubject: isGroup ? (groupConfig?.groupConfig?.name ?? ctx.chatId) : undefined,
|
|
305
308
|
GroupSystemPrompt: systemPrompt,
|
|
306
309
|
SenderName: ctx.senderName,
|
|
307
310
|
SenderId: ctx.senderId,
|
|
@@ -313,6 +316,7 @@ export async function handleMeetMessage(params) {
|
|
|
313
316
|
ReplyToId: ctx.replyContext?.messageId,
|
|
314
317
|
ReplyToBody: ctx.replyContext?.content,
|
|
315
318
|
ReplyToSender: ctx.replyContext?.senderId,
|
|
319
|
+
MentionedUsers: ctx.mentions,
|
|
316
320
|
InboundHistory: inboundHistory,
|
|
317
321
|
CommandAuthorized: true,
|
|
318
322
|
OriginatingChannel: "meet",
|
package/dist/src/channel.js
CHANGED
|
@@ -4,6 +4,8 @@ import { resolveMeetAccount, listMeetAccountIds, resolveDefaultMeetAccountId, is
|
|
|
4
4
|
import { meetOutbound } from "./outbound.js";
|
|
5
5
|
import { parseMeetTarget, looksLikeMeetId, formatMeetTarget, } from "./targets.js";
|
|
6
6
|
import { sendMessageMeet } from "./send.js";
|
|
7
|
+
import { getMeetClient } from "./client.js";
|
|
8
|
+
import { getAllCachedUsers, rememberMeetUser } from "./directory-cache.js";
|
|
7
9
|
const meta = {
|
|
8
10
|
id: "meet",
|
|
9
11
|
label: "Meet",
|
|
@@ -339,9 +341,56 @@ export const meetPlugin = {
|
|
|
339
341
|
},
|
|
340
342
|
directory: {
|
|
341
343
|
self: async () => null,
|
|
342
|
-
listPeers: async () =>
|
|
344
|
+
listPeers: async (params) => {
|
|
345
|
+
const { accountId, query, limit } = params;
|
|
346
|
+
if (!accountId)
|
|
347
|
+
return [];
|
|
348
|
+
const normalizedAccountId = normalizeAccountId(accountId);
|
|
349
|
+
const users = getAllCachedUsers({ accountId: normalizedAccountId, query, limit });
|
|
350
|
+
return users.map((u) => ({
|
|
351
|
+
kind: "user",
|
|
352
|
+
id: u.userId,
|
|
353
|
+
name: u.name,
|
|
354
|
+
handle: u.handles[0] ?? u.name,
|
|
355
|
+
}));
|
|
356
|
+
},
|
|
343
357
|
listGroups: async () => [],
|
|
344
|
-
listPeersLive: async () =>
|
|
358
|
+
listPeersLive: async (params) => {
|
|
359
|
+
const { accountId, query, limit, runtime } = params;
|
|
360
|
+
const log = runtime?.log ?? console.log;
|
|
361
|
+
if (!query || !accountId) {
|
|
362
|
+
log(`[meet] listPeersLive: missing query or accountId`);
|
|
363
|
+
return [];
|
|
364
|
+
}
|
|
365
|
+
const normalizedAccountId = normalizeAccountId(accountId);
|
|
366
|
+
const bot = getMeetClient(normalizedAccountId);
|
|
367
|
+
if (!bot) {
|
|
368
|
+
log(`[meet] listPeersLive: no bot client for account ${normalizedAccountId}`);
|
|
369
|
+
return [];
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
const users = await bot.searchUserByName(query);
|
|
373
|
+
log(`[meet] listPeersLive: searchUserByName("${query}") => ${users.length} users`);
|
|
374
|
+
const sliced = limit ? users.slice(0, limit) : users;
|
|
375
|
+
for (const user of sliced) {
|
|
376
|
+
rememberMeetUser({
|
|
377
|
+
accountId: normalizedAccountId,
|
|
378
|
+
userId: user.userID,
|
|
379
|
+
handles: [user.nickName, user.aliasName],
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
return sliced.map((u) => ({
|
|
383
|
+
kind: "user",
|
|
384
|
+
id: String(u.userID),
|
|
385
|
+
name: u.aliasName || u.nickName,
|
|
386
|
+
handle: u.aliasName || u.nickName,
|
|
387
|
+
}));
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
log(`[meet] listPeersLive: searchUserByName error: ${String(err)}`);
|
|
391
|
+
return [];
|
|
392
|
+
}
|
|
393
|
+
},
|
|
345
394
|
listGroupsLive: async () => [],
|
|
346
395
|
},
|
|
347
396
|
outbound: meetOutbound,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meet 用户目录缓存
|
|
3
|
+
* 用于出站消息中 @handle → <@userId> 转换
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* 注册用户到目录缓存
|
|
7
|
+
*/
|
|
8
|
+
export declare function rememberMeetUser(params: {
|
|
9
|
+
accountId: string;
|
|
10
|
+
userId: number;
|
|
11
|
+
handles: (string | null | undefined)[];
|
|
12
|
+
}): void;
|
|
13
|
+
/**
|
|
14
|
+
* 从目录缓存查找 userId
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveMeetUserId(params: {
|
|
17
|
+
accountId?: string | null;
|
|
18
|
+
handle: string;
|
|
19
|
+
}): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* 清除指定账户的缓存
|
|
22
|
+
*/
|
|
23
|
+
export declare function clearMeetDirectoryCache(accountId: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* 清除所有缓存
|
|
26
|
+
*/
|
|
27
|
+
export declare function clearAllMeetDirectoryCache(): void;
|
|
28
|
+
/**
|
|
29
|
+
* 获取缓存大小(用于测试)
|
|
30
|
+
*/
|
|
31
|
+
export declare function getMeetDirectoryCacheSize(accountId: string): number;
|
|
32
|
+
/**
|
|
33
|
+
* 获取指定账户的所有缓存用户(用于 listPeers)
|
|
34
|
+
*/
|
|
35
|
+
export declare function getAllCachedUsers(params: {
|
|
36
|
+
accountId?: string | null;
|
|
37
|
+
query?: string | null;
|
|
38
|
+
limit?: number | null;
|
|
39
|
+
}): {
|
|
40
|
+
userId: string;
|
|
41
|
+
name: string;
|
|
42
|
+
handles: string[];
|
|
43
|
+
}[];
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meet 用户目录缓存
|
|
3
|
+
* 用于出站消息中 @handle → <@userId> 转换
|
|
4
|
+
*/
|
|
5
|
+
const DIRECTORY_CACHE_MAX_ENTRIES = 4000;
|
|
6
|
+
const DIRECTORY_HANDLE_CACHE = new Map();
|
|
7
|
+
const DIRECTORY_USER_CACHE = new Map();
|
|
8
|
+
function getAccountCache(accountId) {
|
|
9
|
+
let cache = DIRECTORY_HANDLE_CACHE.get(accountId);
|
|
10
|
+
if (!cache) {
|
|
11
|
+
cache = new Map();
|
|
12
|
+
DIRECTORY_HANDLE_CACHE.set(accountId, cache);
|
|
13
|
+
}
|
|
14
|
+
return cache;
|
|
15
|
+
}
|
|
16
|
+
function getUserCache(accountId) {
|
|
17
|
+
let cache = DIRECTORY_USER_CACHE.get(accountId);
|
|
18
|
+
if (!cache) {
|
|
19
|
+
cache = new Map();
|
|
20
|
+
DIRECTORY_USER_CACHE.set(accountId, cache);
|
|
21
|
+
}
|
|
22
|
+
return cache;
|
|
23
|
+
}
|
|
24
|
+
function normalizeHandle(raw) {
|
|
25
|
+
const handle = raw?.trim().toLowerCase();
|
|
26
|
+
if (!handle)
|
|
27
|
+
return null;
|
|
28
|
+
return handle;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 注册用户到目录缓存
|
|
32
|
+
*/
|
|
33
|
+
export function rememberMeetUser(params) {
|
|
34
|
+
const { accountId, userId, handles } = params;
|
|
35
|
+
const cache = getAccountCache(accountId);
|
|
36
|
+
const userCache = getUserCache(accountId);
|
|
37
|
+
const userIdStr = String(userId);
|
|
38
|
+
const validHandles = [];
|
|
39
|
+
for (const rawHandle of handles) {
|
|
40
|
+
const handle = normalizeHandle(rawHandle ?? "");
|
|
41
|
+
if (!handle)
|
|
42
|
+
continue;
|
|
43
|
+
validHandles.push(rawHandle ?? handle);
|
|
44
|
+
if (cache.size >= DIRECTORY_CACHE_MAX_ENTRIES && !cache.has(handle)) {
|
|
45
|
+
const firstKey = cache.keys().next().value;
|
|
46
|
+
if (firstKey)
|
|
47
|
+
cache.delete(firstKey);
|
|
48
|
+
}
|
|
49
|
+
cache.set(handle, userIdStr);
|
|
50
|
+
}
|
|
51
|
+
// 存储用户信息(用第一个非空 handle 作为 name)
|
|
52
|
+
if (validHandles.length > 0) {
|
|
53
|
+
const existing = userCache.get(userIdStr);
|
|
54
|
+
const name = validHandles[0];
|
|
55
|
+
if (existing) {
|
|
56
|
+
// 合并 handles
|
|
57
|
+
const mergedHandles = [...new Set([...existing.handles, ...validHandles])];
|
|
58
|
+
userCache.set(userIdStr, { name: existing.name || name, handles: mergedHandles });
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
userCache.set(userIdStr, { name, handles: validHandles });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 从目录缓存查找 userId
|
|
67
|
+
*/
|
|
68
|
+
export function resolveMeetUserId(params) {
|
|
69
|
+
const { accountId, handle } = params;
|
|
70
|
+
if (!accountId)
|
|
71
|
+
return null;
|
|
72
|
+
const normalized = normalizeHandle(handle);
|
|
73
|
+
if (!normalized)
|
|
74
|
+
return null;
|
|
75
|
+
const cache = DIRECTORY_HANDLE_CACHE.get(accountId);
|
|
76
|
+
if (!cache)
|
|
77
|
+
return null;
|
|
78
|
+
return cache.get(normalized) ?? null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 清除指定账户的缓存
|
|
82
|
+
*/
|
|
83
|
+
export function clearMeetDirectoryCache(accountId) {
|
|
84
|
+
DIRECTORY_HANDLE_CACHE.delete(accountId);
|
|
85
|
+
DIRECTORY_USER_CACHE.delete(accountId);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 清除所有缓存
|
|
89
|
+
*/
|
|
90
|
+
export function clearAllMeetDirectoryCache() {
|
|
91
|
+
DIRECTORY_HANDLE_CACHE.clear();
|
|
92
|
+
DIRECTORY_USER_CACHE.clear();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 获取缓存大小(用于测试)
|
|
96
|
+
*/
|
|
97
|
+
export function getMeetDirectoryCacheSize(accountId) {
|
|
98
|
+
return DIRECTORY_HANDLE_CACHE.get(accountId)?.size ?? 0;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 获取指定账户的所有缓存用户(用于 listPeers)
|
|
102
|
+
*/
|
|
103
|
+
export function getAllCachedUsers(params) {
|
|
104
|
+
const { accountId, query, limit } = params;
|
|
105
|
+
if (!accountId)
|
|
106
|
+
return [];
|
|
107
|
+
const userCache = DIRECTORY_USER_CACHE.get(accountId);
|
|
108
|
+
if (!userCache)
|
|
109
|
+
return [];
|
|
110
|
+
const normalizedQuery = query?.trim().toLowerCase() ?? "";
|
|
111
|
+
const results = [];
|
|
112
|
+
for (const [userId, info] of userCache) {
|
|
113
|
+
if (normalizedQuery) {
|
|
114
|
+
const matchesQuery = info.name.toLowerCase().includes(normalizedQuery) ||
|
|
115
|
+
info.handles.some((h) => h.toLowerCase().includes(normalizedQuery)) ||
|
|
116
|
+
userId === normalizedQuery;
|
|
117
|
+
if (!matchesQuery)
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
results.push({ userId, ...info });
|
|
121
|
+
if (limit && results.length >= limit)
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
return results;
|
|
125
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meet @提及 重写
|
|
3
|
+
* 将 @handle 转换为 <@userId> 格式
|
|
4
|
+
*/
|
|
5
|
+
import { resolveMeetUserId } from "./directory-cache.js";
|
|
6
|
+
const MARKDOWN_CODE_PATTERN = /```[\s\S]*?```|`[^`\n]*`/g;
|
|
7
|
+
const MENTION_CANDIDATE_PATTERN = /(^|[\s([{"'.,;:!?])@([^\s`@<>{}\[\],;:!?]+(?:([^)\s`@<>{}\[\],;:!?]+))?)/gu;
|
|
8
|
+
const EXPLICIT_MENTION_WITH_ID_PATTERN = /@([^\s`@<>{}\[\],;:!?]+(?:([^)\s`@<>{}\[\],;:!?]+))?)\s*[((](\d+)[))]/gu;
|
|
9
|
+
function rewriteMentionsInSegment(text, accountId) {
|
|
10
|
+
if (!text.includes("@")) {
|
|
11
|
+
return text;
|
|
12
|
+
}
|
|
13
|
+
return text.replace(MENTION_CANDIDATE_PATTERN, (match, prefix, handle) => {
|
|
14
|
+
if (!handle)
|
|
15
|
+
return match;
|
|
16
|
+
const userId = resolveMeetUserId({ accountId, handle });
|
|
17
|
+
if (!userId)
|
|
18
|
+
return match;
|
|
19
|
+
return `${prefix ?? ""}<@${userId}>`;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 重写消息中的 @handle 为 <@userId> 格式
|
|
24
|
+
* 排除代码块中的内容
|
|
25
|
+
*/
|
|
26
|
+
export function rewriteMeetKnownMentions(text, accountId) {
|
|
27
|
+
if (!text.includes("@")) {
|
|
28
|
+
return text;
|
|
29
|
+
}
|
|
30
|
+
const withExplicitIds = text.replace(EXPLICIT_MENTION_WITH_ID_PATTERN, (_, _name, id) => {
|
|
31
|
+
return `<@${id}>`;
|
|
32
|
+
});
|
|
33
|
+
let rewritten = "";
|
|
34
|
+
let offset = 0;
|
|
35
|
+
MARKDOWN_CODE_PATTERN.lastIndex = 0;
|
|
36
|
+
for (const match of withExplicitIds.matchAll(MARKDOWN_CODE_PATTERN)) {
|
|
37
|
+
const matchIndex = match.index ?? 0;
|
|
38
|
+
rewritten += rewriteMentionsInSegment(withExplicitIds.slice(offset, matchIndex), accountId);
|
|
39
|
+
rewritten += match[0];
|
|
40
|
+
offset = matchIndex + match[0].length;
|
|
41
|
+
}
|
|
42
|
+
rewritten += rewriteMentionsInSegment(withExplicitIds.slice(offset), accountId);
|
|
43
|
+
return rewritten;
|
|
44
|
+
}
|
package/dist/src/monitor.js
CHANGED
|
@@ -2,7 +2,7 @@ import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
|
|
|
2
2
|
import { resolveMeetAccount, listEnabledMeetAccounts } from "./accounts.js";
|
|
3
3
|
import { createMeetClient, closeMeetClient, closeAllMeetClients, getPollingOptions } from "./client.js";
|
|
4
4
|
import { handleMeetMessage } from "./bot.js";
|
|
5
|
-
import { msgContentToContext } from "./sdk-bridge.js";
|
|
5
|
+
import { msgContentToContext, enrichContextWithUserNames } from "./sdk-bridge.js";
|
|
6
6
|
export async function monitorMeetProvider(opts = {}) {
|
|
7
7
|
const cfg = opts.config;
|
|
8
8
|
if (!cfg) {
|
|
@@ -66,8 +66,9 @@ async function monitorSingleAccount(params) {
|
|
|
66
66
|
abortSignal?.addEventListener("abort", handleAbort, { once: true });
|
|
67
67
|
bot.on("message", ({ message, quoteMsgMap }) => {
|
|
68
68
|
let queueKey;
|
|
69
|
+
let ctx;
|
|
69
70
|
try {
|
|
70
|
-
|
|
71
|
+
ctx = msgContentToContext(message, botUserId, quoteMsgMap);
|
|
71
72
|
queueKey = ctx.chatId;
|
|
72
73
|
}
|
|
73
74
|
catch (err) {
|
|
@@ -80,6 +81,7 @@ async function monitorSingleAccount(params) {
|
|
|
80
81
|
log(`[${accountId}]: enqueue message to queue=${queueKey}, queues=${queueSize}, pending=${pendingInQueue}`);
|
|
81
82
|
messageQueue.enqueue(queueKey, async () => {
|
|
82
83
|
try {
|
|
84
|
+
await enrichContextWithUserNames(ctx, bot, accountId);
|
|
83
85
|
await handleMeetMessage({
|
|
84
86
|
cfg,
|
|
85
87
|
msg: message,
|
|
@@ -90,6 +92,7 @@ async function monitorSingleAccount(params) {
|
|
|
90
92
|
bot,
|
|
91
93
|
groupHistories,
|
|
92
94
|
quoteMsgMap,
|
|
95
|
+
ctx,
|
|
93
96
|
});
|
|
94
97
|
}
|
|
95
98
|
catch (err) {
|
package/dist/src/sdk-bridge.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MsgContent, SessionInfo, SessionType } from "@meet-im/meet-bot-jssdk";
|
|
1
|
+
import type { MsgContent, SessionInfo, SessionType, MeetBot } from "@meet-im/meet-bot-jssdk";
|
|
2
2
|
import type { MeetMessageContext, MeetReplyContext, MeetMediaAttachment } from "./types.js";
|
|
3
3
|
export type QuoteMsgMap = Record<string, MsgContent>;
|
|
4
4
|
export declare function mapSessionType(sessionType: SessionType): "direct" | "channel";
|
|
@@ -17,5 +17,10 @@ export declare function resolveQuoteMessage(msg: MsgContent, quoteMsgMap: QuoteM
|
|
|
17
17
|
*/
|
|
18
18
|
export declare function extractQuoteMessageMedia(msg: MsgContent, quoteMsgMap: QuoteMsgMap): MeetMediaAttachment[] | undefined;
|
|
19
19
|
export declare function msgContentToContext(msg: MsgContent, botUserId: string, quoteMsgMap?: QuoteMsgMap): MeetMessageContext;
|
|
20
|
+
/**
|
|
21
|
+
* 从缓存中填充 senderName 和 atIds 对应的用户名
|
|
22
|
+
* 同时将用户名注册到目录缓存,用于出站 @提及 转换
|
|
23
|
+
*/
|
|
24
|
+
export declare function enrichContextWithUserNames(ctx: MeetMessageContext, bot: MeetBot, accountId?: string): Promise<void>;
|
|
20
25
|
export declare function parseTargetToSessionInfo(target: string, botUserId: number): SessionInfo;
|
|
21
26
|
export declare function buildMeetTarget(sessionInfo: SessionInfo, botUserId: number): string;
|
package/dist/src/sdk-bridge.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { rememberMeetUser } from "./directory-cache.js";
|
|
1
2
|
export function mapSessionType(sessionType) {
|
|
2
3
|
return sessionType === 1 ? "direct" : "channel";
|
|
3
4
|
}
|
|
@@ -177,6 +178,86 @@ export function msgContentToContext(msg, botUserId, quoteMsgMap = {}) {
|
|
|
177
178
|
media,
|
|
178
179
|
};
|
|
179
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* 从缓存中填充 senderName 和 atIds 对应的用户名
|
|
183
|
+
* 同时将用户名注册到目录缓存,用于出站 @提及 转换
|
|
184
|
+
*/
|
|
185
|
+
export async function enrichContextWithUserNames(ctx, bot, accountId) {
|
|
186
|
+
const userIds = new Set();
|
|
187
|
+
const senderId = Number(ctx.senderId);
|
|
188
|
+
if (senderId > 0)
|
|
189
|
+
userIds.add(senderId);
|
|
190
|
+
if (ctx.atIds) {
|
|
191
|
+
for (const id of ctx.atIds)
|
|
192
|
+
userIds.add(id);
|
|
193
|
+
}
|
|
194
|
+
if (ctx.replyContext?.senderId) {
|
|
195
|
+
const replySenderId = Number(ctx.replyContext.senderId);
|
|
196
|
+
if (replySenderId > 0)
|
|
197
|
+
userIds.add(replySenderId);
|
|
198
|
+
}
|
|
199
|
+
if (userIds.size === 0)
|
|
200
|
+
return;
|
|
201
|
+
const users = await bot.getUserByIds([...userIds]);
|
|
202
|
+
const sender = users.get(senderId);
|
|
203
|
+
if (sender) {
|
|
204
|
+
ctx.senderName = sender.aliasName || sender.nickName;
|
|
205
|
+
if (accountId) {
|
|
206
|
+
rememberMeetUser({
|
|
207
|
+
accountId,
|
|
208
|
+
userId: senderId,
|
|
209
|
+
handles: [sender.nickName, sender.aliasName],
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (ctx.replyContext?.senderId) {
|
|
214
|
+
const replySender = users.get(Number(ctx.replyContext.senderId));
|
|
215
|
+
if (replySender) {
|
|
216
|
+
ctx.replyContext.senderName = replySender.aliasName || replySender.nickName;
|
|
217
|
+
if (accountId) {
|
|
218
|
+
rememberMeetUser({
|
|
219
|
+
accountId,
|
|
220
|
+
userId: replySender.userID,
|
|
221
|
+
handles: [replySender.nickName, replySender.aliasName],
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (ctx.atIds && ctx.atIds.length > 0) {
|
|
227
|
+
const mentionedNames = [];
|
|
228
|
+
const mentions = new Map();
|
|
229
|
+
for (const atId of ctx.atIds) {
|
|
230
|
+
const user = users.get(atId);
|
|
231
|
+
if (user) {
|
|
232
|
+
const displayName = user.aliasName || user.nickName;
|
|
233
|
+
mentions.set(String(atId), displayName);
|
|
234
|
+
if (accountId) {
|
|
235
|
+
rememberMeetUser({
|
|
236
|
+
accountId,
|
|
237
|
+
userId: atId,
|
|
238
|
+
handles: [user.nickName, user.aliasName],
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
// 替换文本中已有的 <@id> 格式
|
|
242
|
+
const nextContent = ctx.content.replace(new RegExp(`<@${atId}>`, "g"), `@${displayName}`);
|
|
243
|
+
const replaced = nextContent !== ctx.content;
|
|
244
|
+
ctx.content = nextContent;
|
|
245
|
+
// 如果文本中没有对应的 <@id> 被替换,则追加到列表
|
|
246
|
+
if (!replaced) {
|
|
247
|
+
mentionedNames.push(displayName);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
ctx.mentions = [...mentions.entries()].map(([userId, name]) => ({ userId, name }));
|
|
252
|
+
// 如果有未被文本包含的 @提及,追加到内容末尾
|
|
253
|
+
if (mentionedNames.length > 0) {
|
|
254
|
+
const mentionText = mentionedNames.map((n) => `@${n}`).join(" ");
|
|
255
|
+
ctx.content = ctx.content.trim()
|
|
256
|
+
? `${ctx.content} ${mentionText}`
|
|
257
|
+
: mentionText;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
180
261
|
export function parseTargetToSessionInfo(target, botUserId) {
|
|
181
262
|
const userMatch = target.match(/^user:(\d+)$/);
|
|
182
263
|
if (userMatch) {
|
package/dist/src/send.js
CHANGED
|
@@ -2,6 +2,7 @@ import { resolveMeetAccount } from "./accounts.js";
|
|
|
2
2
|
import { getMeetClient, createMeetClient } from "./client.js";
|
|
3
3
|
import { parseTargetToSessionInfo } from "./sdk-bridge.js";
|
|
4
4
|
import { getMeetRuntime } from "./runtime.js";
|
|
5
|
+
import { rewriteMeetKnownMentions } from "./mentions.js";
|
|
5
6
|
const MENTION_PATTERN = /<@(-?\d+)>|@(-?\d+)(?![\d])/g;
|
|
6
7
|
/**
|
|
7
8
|
* 根据文件扩展名推断 MIME 类型
|
|
@@ -177,7 +178,9 @@ export async function sendMessageMeet(opts) {
|
|
|
177
178
|
if (!bot) {
|
|
178
179
|
bot = createMeetClient(account);
|
|
179
180
|
}
|
|
180
|
-
|
|
181
|
+
// 先重写 @handle 为 <@userId>,再提取 atIds
|
|
182
|
+
const textWithMentions = rewriteMeetKnownMentions(text, account.accountId);
|
|
183
|
+
const { text: cleanText, atIds: extractedAtIds } = extractAtIds(textWithMentions);
|
|
181
184
|
const finalAtIds = explicitAtIds
|
|
182
185
|
? [...explicitAtIds, ...extractedAtIds]
|
|
183
186
|
: extractedAtIds;
|
package/dist/src/types.d.ts
CHANGED
|
@@ -23,6 +23,10 @@ export type MeetReplyContext = {
|
|
|
23
23
|
timestamp?: number;
|
|
24
24
|
mediaPaths?: string[];
|
|
25
25
|
};
|
|
26
|
+
export type MeetMention = {
|
|
27
|
+
userId: string;
|
|
28
|
+
name: string;
|
|
29
|
+
};
|
|
26
30
|
export type MeetMessageContext = {
|
|
27
31
|
chatId: string;
|
|
28
32
|
messageId: string;
|
|
@@ -39,6 +43,7 @@ export type MeetMessageContext = {
|
|
|
39
43
|
sessionInfo: SessionInfo;
|
|
40
44
|
timestamp?: number;
|
|
41
45
|
atIds?: number[];
|
|
46
|
+
mentions?: MeetMention[];
|
|
42
47
|
replyContext?: MeetReplyContext;
|
|
43
48
|
media?: MeetMediaAttachment[];
|
|
44
49
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meet-im/meet",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw Meet channel plugin",
|
|
6
6
|
"scripts": {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@meet-im/meet-bot-jssdk": "1.
|
|
58
|
+
"@meet-im/meet-bot-jssdk": "^1.1.0",
|
|
59
59
|
"zod": "^4.4.3"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|