@meet-im/meet 1.1.0 → 1.1.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/package.json +1 -1
- package/skills/meet-at/SKILL.md +11 -8
- package/src/channel.ts +3 -12
- package/src/reply-dispatcher.ts +6 -2
- package/src/send.ts +71 -2
package/package.json
CHANGED
package/skills/meet-at/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: meet-at
|
|
3
|
-
description: "Meet IM messaging.
|
|
3
|
+
description: "Meet IM messaging. CRITICAL: Use EXACT format <@USER_ID> with BOTH angle brackets. <@553> is CORRECT. <@553 or @553> is WRONG - missing bracket. Always include opening < and closing > around the user ID."
|
|
4
4
|
metadata: { "openclaw": { "emoji": "💬", "requires": { "config": ["channels.meet"] } } }
|
|
5
5
|
allowed-tools: ["message"]
|
|
6
6
|
---
|
|
@@ -9,16 +9,19 @@ allowed-tools: ["message"]
|
|
|
9
9
|
|
|
10
10
|
Use the `message` tool for all Meet messaging operations.
|
|
11
11
|
|
|
12
|
-
## CRITICAL: Mention Format
|
|
12
|
+
## CRITICAL: Mention Format - MUST HAVE BOTH BRACKETS
|
|
13
13
|
|
|
14
|
-
**
|
|
14
|
+
**Correct format: `<@USER_ID>` with opening `<` and closing `>`**
|
|
15
15
|
|
|
16
|
-
| Format |
|
|
17
|
-
|
|
18
|
-
| <@USER_ID> |
|
|
19
|
-
| <@-1> |
|
|
16
|
+
| Format | Status | Example |
|
|
17
|
+
|--------|--------|---------|
|
|
18
|
+
| <@USER_ID> | ✅ CORRECT | <@553> |
|
|
19
|
+
| <@-1> | ✅ CORRECT | <@-1> |
|
|
20
|
+
| <@553 | ❌ WRONG - missing `>` | Will not trigger mention |
|
|
21
|
+
| @553> | ❌ WRONG - missing `<` | Will not trigger mention |
|
|
22
|
+
| @553 | ❌ WRONG - no brackets | Will not trigger mention |
|
|
20
23
|
|
|
21
|
-
**
|
|
24
|
+
**The format MUST be complete: `<@` + number + `>`**
|
|
22
25
|
|
|
23
26
|
## Examples
|
|
24
27
|
|
package/src/channel.ts
CHANGED
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
} from "./accounts.js";
|
|
19
19
|
import { meetOutbound } from "./outbound.js";
|
|
20
20
|
import { probeMeet } from "./probe.js";
|
|
21
|
-
import { resolveMeetGroupPolicy } from "./policy.js";
|
|
22
21
|
import {
|
|
23
22
|
parseMeetTarget,
|
|
24
23
|
looksLikeMeetId,
|
|
@@ -74,17 +73,9 @@ export const meetPlugin: ChannelPlugin<ResolvedMeetAccount> = {
|
|
|
74
73
|
accountId,
|
|
75
74
|
groupId,
|
|
76
75
|
}): GroupToolPolicyConfig | undefined => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
groupAllowFrom: account.config.groupAllowFrom ?? [],
|
|
81
|
-
chatId: groupId ?? "",
|
|
82
|
-
groups: account.config.groups,
|
|
83
|
-
});
|
|
84
|
-
if (!groupPolicy.allowed) {
|
|
85
|
-
return { deny: ["*"] };
|
|
86
|
-
}
|
|
87
|
-
return undefined;
|
|
76
|
+
// 群组访问控制在 bot.ts 中处理,这里只返回工具策略配置
|
|
77
|
+
// 不使用 { deny: ["*"] } 拒绝所有工具,否则会导致 tools: []
|
|
78
|
+
return undefined
|
|
88
79
|
},
|
|
89
80
|
},
|
|
90
81
|
reload: { configPrefixes: ["channels.meet"] },
|
package/src/reply-dispatcher.ts
CHANGED
|
@@ -50,8 +50,12 @@ export function protectMentionsInChunks(chunks: string[]): string[] {
|
|
|
50
50
|
if (endMatch) {
|
|
51
51
|
// 将不完整的部分移到下一个 chunk 前面
|
|
52
52
|
const splitIndex = chunk.lastIndexOf("<@")
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
// splitIndex >= 0 时处理(包括 chunk 只有 "<@" 的情况)
|
|
54
|
+
if (splitIndex >= 0) {
|
|
55
|
+
// 如果 splitIndex 之前有内容,保留到 result
|
|
56
|
+
if (splitIndex > 0) {
|
|
57
|
+
result.push(chunk.slice(0, splitIndex))
|
|
58
|
+
}
|
|
55
59
|
pendingSuffix = chunk.slice(splitIndex)
|
|
56
60
|
continue
|
|
57
61
|
}
|
package/src/send.ts
CHANGED
|
@@ -8,6 +8,67 @@ import { getMeetRuntime } from "./runtime.js";
|
|
|
8
8
|
|
|
9
9
|
const MENTION_PATTERN = /<@(-?\d+)>|@(-?\d+)(?![\d])/g;
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* 根据文件扩展名推断 MIME 类型
|
|
13
|
+
* 支持常见图片格式,包括现代格式如 avif, webp, heic
|
|
14
|
+
*/
|
|
15
|
+
export function inferContentTypeFromFileName(fileName: string): string | undefined {
|
|
16
|
+
const ext = fileName.toLowerCase().split(".").pop();
|
|
17
|
+
if (!ext) return undefined;
|
|
18
|
+
|
|
19
|
+
const mimeMap: Record<string, string> = {
|
|
20
|
+
// 常见图片格式
|
|
21
|
+
jpg: "image/jpeg",
|
|
22
|
+
jpeg: "image/jpeg",
|
|
23
|
+
png: "image/png",
|
|
24
|
+
gif: "image/gif",
|
|
25
|
+
webp: "image/webp",
|
|
26
|
+
avif: "image/avif",
|
|
27
|
+
heic: "image/heic",
|
|
28
|
+
heif: "image/heif",
|
|
29
|
+
bmp: "image/bmp",
|
|
30
|
+
ico: "image/x-icon",
|
|
31
|
+
svg: "image/svg+xml",
|
|
32
|
+
tiff: "image/tiff",
|
|
33
|
+
tif: "image/tiff",
|
|
34
|
+
// 视频格式
|
|
35
|
+
mp4: "video/mp4",
|
|
36
|
+
webm: "video/webm",
|
|
37
|
+
mov: "video/quicktime",
|
|
38
|
+
avi: "video/x-msvideo",
|
|
39
|
+
mkv: "video/x-matroska",
|
|
40
|
+
// 音频格式
|
|
41
|
+
mp3: "audio/mpeg",
|
|
42
|
+
wav: "audio/wav",
|
|
43
|
+
ogg: "audio/ogg",
|
|
44
|
+
flac: "audio/flac",
|
|
45
|
+
m4a: "audio/mp4",
|
|
46
|
+
// 文档格式
|
|
47
|
+
pdf: "application/pdf",
|
|
48
|
+
json: "application/json",
|
|
49
|
+
xml: "application/xml",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return mimeMap[ext];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 获取最终的 contentType
|
|
57
|
+
* 如果原始 contentType 缺失或为通用二进制流,则根据文件名推断
|
|
58
|
+
*/
|
|
59
|
+
export function resolveContentType(
|
|
60
|
+
fileName: string,
|
|
61
|
+
originalContentType?: string,
|
|
62
|
+
): string {
|
|
63
|
+
// 如果有明确的 MIME 类型(非通用二进制流),直接使用
|
|
64
|
+
if (originalContentType && originalContentType !== "application/octet-stream") {
|
|
65
|
+
return originalContentType;
|
|
66
|
+
}
|
|
67
|
+
// 根据文件名推断
|
|
68
|
+
const inferred = inferContentTypeFromFileName(fileName);
|
|
69
|
+
return inferred || originalContentType || "application/octet-stream";
|
|
70
|
+
}
|
|
71
|
+
|
|
11
72
|
let _logger: RuntimeEnv | null = null;
|
|
12
73
|
|
|
13
74
|
export function setSendMessageLogger(logger: RuntimeEnv): void {
|
|
@@ -17,7 +78,9 @@ export function setSendMessageLogger(logger: RuntimeEnv): void {
|
|
|
17
78
|
function log(message: string): void {
|
|
18
79
|
if (_logger) {
|
|
19
80
|
_logger.log(message);
|
|
81
|
+
return;
|
|
20
82
|
}
|
|
83
|
+
console.log(message);
|
|
21
84
|
}
|
|
22
85
|
|
|
23
86
|
function logError(message: string): void {
|
|
@@ -57,6 +120,11 @@ export async function sendMessageMeet(
|
|
|
57
120
|
if (!account.configured) {
|
|
58
121
|
throw new Error(`Meet account not configured: ${accountId ?? "default"}`);
|
|
59
122
|
}
|
|
123
|
+
// logLevel: info 时记录 AI 输出的原始内容,方便调试 mention 格式问题
|
|
124
|
+
// 默认为 silent,只有显式设置为 info 时才输出
|
|
125
|
+
if (account.config.logLevel === "info") {
|
|
126
|
+
console.log(`[${account.accountId}] AI output raw text: ${JSON.stringify(text)}`);
|
|
127
|
+
}
|
|
60
128
|
const token = account.apiToken;
|
|
61
129
|
if (!token) {
|
|
62
130
|
throw new Error("Meet API token not configured");
|
|
@@ -140,9 +208,10 @@ export async function sendMediaMeet(
|
|
|
140
208
|
|
|
141
209
|
const sessionInfo = parseTargetToSessionInfo(to, Number(botUserId));
|
|
142
210
|
const fileName = media.fileName || "file";
|
|
211
|
+
const contentType = resolveContentType(fileName, media.contentType);
|
|
143
212
|
|
|
144
213
|
log(
|
|
145
|
-
`sending media to=${to} fileName=${fileName} size=${media.buffer.length}`,
|
|
214
|
+
`sending media to=${to} fileName=${fileName} size=${media.buffer.length} contentType=${contentType}`,
|
|
146
215
|
);
|
|
147
216
|
|
|
148
217
|
// 包装进度回调
|
|
@@ -161,7 +230,7 @@ export async function sendMediaMeet(
|
|
|
161
230
|
const result = await bot.sendMedia(sessionInfo, {
|
|
162
231
|
buffer: media.buffer,
|
|
163
232
|
fileName,
|
|
164
|
-
contentType
|
|
233
|
+
contentType,
|
|
165
234
|
content: text || "",
|
|
166
235
|
onProgress: progressCallback,
|
|
167
236
|
});
|