@meet-im/meet 2.0.5 → 3.0.0-beta.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.
Files changed (70) hide show
  1. package/dist/account-inspect-api.d.ts +2 -0
  2. package/dist/account-inspect-api.js +4 -0
  3. package/dist/channel-plugin-api.d.ts +1 -0
  4. package/dist/channel-plugin-api.js +3 -0
  5. package/dist/index.d.ts +14 -0
  6. package/dist/index.js +43 -0
  7. package/dist/monitor-api.d.ts +1 -0
  8. package/dist/monitor-api.js +1 -0
  9. package/dist/probe-api.d.ts +1 -0
  10. package/dist/probe-api.js +1 -0
  11. package/dist/runtime-setter-api.d.ts +1 -0
  12. package/dist/runtime-setter-api.js +2 -0
  13. package/dist/send-api.d.ts +1 -0
  14. package/dist/send-api.js +1 -0
  15. package/dist/src/account-inspect.d.ts +5 -0
  16. package/dist/src/account-inspect.js +9 -0
  17. package/dist/src/accounts.d.ts +12 -0
  18. package/dist/src/accounts.js +134 -0
  19. package/dist/src/bot.d.ts +15 -0
  20. package/dist/src/bot.js +355 -0
  21. package/dist/src/channel.d.ts +3 -0
  22. package/dist/src/channel.js +402 -0
  23. package/dist/src/client.d.ts +8 -0
  24. package/dist/src/client.js +49 -0
  25. package/dist/src/config-schema.d.ts +82 -0
  26. package/dist/src/config-schema.js +46 -0
  27. package/dist/src/media.d.ts +57 -0
  28. package/dist/src/media.js +140 -0
  29. package/dist/src/monitor.d.ts +9 -0
  30. package/dist/src/monitor.js +153 -0
  31. package/dist/src/outbound.d.ts +2 -0
  32. package/dist/src/outbound.js +34 -0
  33. package/dist/src/policy.d.ts +30 -0
  34. package/dist/src/policy.js +78 -0
  35. package/dist/src/probe.d.ts +10 -0
  36. package/dist/src/probe.js +56 -0
  37. package/dist/src/reply-dispatcher.d.ts +29 -0
  38. package/dist/src/reply-dispatcher.js +173 -0
  39. package/dist/src/runtime.d.ts +11 -0
  40. package/dist/src/runtime.js +6 -0
  41. package/dist/src/sdk-bridge.d.ts +21 -0
  42. package/dist/src/sdk-bridge.js +214 -0
  43. package/dist/src/send.d.ts +60 -0
  44. package/dist/src/send.js +317 -0
  45. package/dist/src/targets.d.ts +15 -0
  46. package/dist/src/targets.js +63 -0
  47. package/dist/src/types.d.ts +76 -0
  48. package/dist/src/types.js +1 -0
  49. package/dist/vitest.config.d.ts +8 -0
  50. package/dist/vitest.config.js +7 -0
  51. package/openclaw.plugin.json +116 -0
  52. package/package.json +18 -17
  53. package/skills/meet-emojis/SKILL.md +13 -15
  54. package/index.ts +0 -26
  55. package/src/accounts.ts +0 -182
  56. package/src/bot.ts +0 -418
  57. package/src/channel.ts +0 -396
  58. package/src/client.ts +0 -63
  59. package/src/config-schema.ts +0 -50
  60. package/src/media.ts +0 -198
  61. package/src/monitor.ts +0 -195
  62. package/src/outbound.ts +0 -43
  63. package/src/policy.ts +0 -131
  64. package/src/probe.ts +0 -75
  65. package/src/reply-dispatcher.ts +0 -207
  66. package/src/runtime.ts +0 -14
  67. package/src/sdk-bridge.ts +0 -268
  68. package/src/send.ts +0 -363
  69. package/src/targets.ts +0 -101
  70. package/src/types.ts +0 -96
package/src/sdk-bridge.ts DELETED
@@ -1,268 +0,0 @@
1
- import type { MsgContent, SessionInfo, SessionType } from "@meet-im/meet-bot-jssdk"
2
- import type { MeetMessageContext, MeetReplyContext, MeetMediaAttachment } from "./types.js"
3
-
4
- // TODO: 从 SDK 导入,等待 SDK 发布
5
- export type QuoteMsgMap = Record<string, MsgContent>
6
-
7
- export function mapSessionType(sessionType: SessionType): "direct" | "channel" {
8
- return sessionType === 1 ? "direct" : "channel"
9
- }
10
-
11
- /**
12
- * 生成与 Discord 一致的媒体占位符
13
- * 格式:<media:image> (2 images) 或 <media:document> (3 files)
14
- */
15
- function buildMediaPlaceholder(media: MeetMediaAttachment[]): string {
16
- const images = media.filter((m) => m.mimeType?.startsWith("image/"))
17
- const videos = media.filter((m) => m.mimeType?.startsWith("video/"))
18
- const audios = media.filter((m) => m.mimeType?.startsWith("audio/"))
19
- const documents = media.filter(
20
- (m) => !m.mimeType?.startsWith("image/") &&
21
- !m.mimeType?.startsWith("video/") &&
22
- !m.mimeType?.startsWith("audio/")
23
- )
24
-
25
- const parts: string[] = []
26
-
27
- if (images.length > 0) {
28
- const label = images.length === 1 ? "image" : "images"
29
- parts.push(`<media:image> (${images.length} ${label})`)
30
- }
31
- if (videos.length > 0) {
32
- const label = videos.length === 1 ? "video" : "videos"
33
- parts.push(`<media:video> (${videos.length} ${label})`)
34
- }
35
- if (audios.length > 0) {
36
- const label = audios.length === 1 ? "audio" : "audios"
37
- parts.push(`<media:audio> (${audios.length} ${label})`)
38
- }
39
- if (documents.length > 0) {
40
- const label = documents.length === 1 ? "document" : "documents"
41
- parts.push(`<media:document> (${documents.length} ${label})`)
42
- }
43
-
44
- return parts.join("\n")
45
- }
46
-
47
- /**
48
- * 构建引用消息查找 Key
49
- * 群聊: firstID+secondID:quoteSeqID
50
- * 私聊: min(firstID,secondID):max(firstID,secondID):quoteSeqID
51
- */
52
- export function buildQuoteMsgKey(sessionInfo: SessionInfo, quoteSeqID: number): string {
53
- const { firstID, secondID, sessionType } = sessionInfo
54
- if (sessionType === 3) {
55
- return `${firstID}+${secondID}:${quoteSeqID}`
56
- } else {
57
- const [minId, maxId] = firstID < secondID ? [firstID, secondID] : [secondID, firstID]
58
- return `${minId}:${maxId}:${quoteSeqID}`
59
- }
60
- }
61
-
62
- /**
63
- * 解析引用消息
64
- */
65
- export function resolveQuoteMessage(
66
- msg: MsgContent,
67
- quoteMsgMap: QuoteMsgMap
68
- ): MeetReplyContext | undefined {
69
- // 使用 quoteSeqID 字段(SDK V2)
70
- const quoteSeqID = (msg as { quoteSeqID?: number }).quoteSeqID
71
- if (!quoteSeqID || !msg.sessionInfo) {
72
- return undefined
73
- }
74
-
75
- const key = buildQuoteMsgKey(msg.sessionInfo, quoteSeqID)
76
- const quoteMsg = quoteMsgMap[key]
77
-
78
- if (!quoteMsg) {
79
- return undefined
80
- }
81
-
82
- // 对齐 Discord: 引用消息的 body 包含媒体占位符
83
- const body = buildQuoteMessageBody(quoteMsg)
84
-
85
- return {
86
- messageId: String(quoteMsg.seqId ?? 0),
87
- senderId: quoteMsg.fromUid ? String(quoteMsg.fromUid) : undefined,
88
- content: body,
89
- timestamp: quoteMsg.timestamp,
90
- }
91
- }
92
-
93
- /**
94
- * 从引用消息中提取媒体附件信息(用于下载)
95
- */
96
- export function extractQuoteMessageMedia(
97
- msg: MsgContent,
98
- quoteMsgMap: QuoteMsgMap
99
- ): MeetMediaAttachment[] | undefined {
100
- const quoteSeqID = (msg as { quoteSeqID?: number }).quoteSeqID
101
- if (!quoteSeqID || !msg.sessionInfo) {
102
- return undefined
103
- }
104
-
105
- const key = buildQuoteMsgKey(msg.sessionInfo, quoteSeqID)
106
- const quoteMsg = quoteMsgMap[key]
107
-
108
- if (!quoteMsg) {
109
- return undefined
110
- }
111
-
112
- return extractMediaAttachments(quoteMsg)
113
- }
114
-
115
- /**
116
- * 从消息中提取媒体附件信息
117
- */
118
- function extractMediaAttachments(msg: MsgContent): MeetMediaAttachment[] | undefined {
119
- const extraInfo = msg.extraInfo
120
- if (!extraInfo) {
121
- return undefined
122
- }
123
-
124
- const attachments: MeetMediaAttachment[] = []
125
-
126
- // 单附件
127
- if (extraInfo.attechmentInfo) {
128
- const att = extraInfo.attechmentInfo
129
- attachments.push({
130
- fileId: att.fileID,
131
- fileName: att.fileName,
132
- fileSize: att.fileSize,
133
- mimeType: att.mimeType,
134
- fileUrl: att.fileUrl,
135
- })
136
- }
137
-
138
- // 多附件
139
- if (extraInfo.attechmentInfos && Array.isArray(extraInfo.attechmentInfos)) {
140
- for (const att of extraInfo.attechmentInfos) {
141
- attachments.push({
142
- fileId: att.fileID,
143
- fileName: att.fileName,
144
- fileSize: att.fileSize,
145
- mimeType: att.mimeType,
146
- fileUrl: att.fileUrl,
147
- })
148
- }
149
- }
150
-
151
- return attachments.length > 0 ? attachments : undefined
152
- }
153
-
154
- /**
155
- * 构建引用消息的 body(对齐 Discord)
156
- * 包含文本内容 + 媒体占位符
157
- */
158
- function buildQuoteMessageBody(quoteMsg: MsgContent): string {
159
- const parts: string[] = []
160
-
161
- // 添加文本内容
162
- const text = quoteMsg.content?.trim()
163
- if (text) {
164
- parts.push(text)
165
- }
166
-
167
- // 添加媒体占位符
168
- const media = extractMediaAttachments(quoteMsg)
169
- if (media && media.length > 0) {
170
- const placeholder = buildMediaPlaceholder(media)
171
- if (placeholder) {
172
- parts.push(placeholder)
173
- }
174
- }
175
-
176
- return parts.join("\n")
177
- }
178
-
179
- export function msgContentToContext(
180
- msg: MsgContent,
181
- botUserId: string,
182
- quoteMsgMap: QuoteMsgMap = {},
183
- ): MeetMessageContext {
184
- if (!msg.sessionInfo) {
185
- throw new Error("MsgContent missing sessionInfo")
186
- }
187
-
188
- const chatType = mapSessionType(msg.sessionInfo.sessionType)
189
- const chatId =
190
- chatType === "direct"
191
- ? `user:${msg.fromUid}`
192
- : `channel:${msg.sessionInfo.secondID}`
193
-
194
- const mentionedBot = msg.atIds?.includes(Number(botUserId)) ?? false
195
- const replyContext = resolveQuoteMessage(msg, quoteMsgMap)
196
- const media = extractMediaAttachments(msg)
197
-
198
- // 检测消息类型,生成与 Discord 一致的占位符
199
- let contentType = "text"
200
- let placeholder: string | undefined
201
- if (media && media.length > 0) {
202
- contentType = "media"
203
- placeholder = buildMediaPlaceholder(media)
204
- }
205
-
206
- return {
207
- chatId,
208
- messageId: String(msg.seqId ?? 0),
209
- senderId: String(msg.fromUid ?? 0),
210
- senderOpenId: String(msg.fromUid ?? 0),
211
- chatType,
212
- mentionedBot,
213
- content: msg.content ?? "",
214
- contentType,
215
- placeholder,
216
- sessionInfo: msg.sessionInfo,
217
- timestamp: msg.timestamp,
218
- atIds: msg.atIds,
219
- replyContext,
220
- media,
221
- }
222
- }
223
-
224
- export function parseTargetToSessionInfo(
225
- target: string,
226
- botUserId: number,
227
- ): SessionInfo {
228
- const userMatch = target.match(/^user:(\d+)$/)
229
- if (userMatch) {
230
- return {
231
- firstID: botUserId,
232
- secondID: Number(userMatch[1]),
233
- sessionType: 1,
234
- }
235
- }
236
-
237
- const channelMatch = target.match(/^channel:(\d+)$/)
238
- if (channelMatch) {
239
- return {
240
- firstID: 1,
241
- secondID: Number(channelMatch[1]),
242
- sessionType: 3,
243
- }
244
- }
245
-
246
- const numericMatch = target.match(/^(\d+)$/)
247
- if (numericMatch) {
248
- return {
249
- firstID: botUserId,
250
- secondID: Number(numericMatch[1]),
251
- sessionType: 1,
252
- }
253
- }
254
-
255
- throw new Error(`Invalid Meet target: ${target}`)
256
- }
257
-
258
- export function buildMeetTarget(
259
- sessionInfo: SessionInfo,
260
- botUserId: number,
261
- ): string {
262
- if (sessionInfo.sessionType === 1) {
263
- return sessionInfo.secondID === botUserId
264
- ? `user:${sessionInfo.firstID}`
265
- : `user:${sessionInfo.secondID}`
266
- }
267
- return `channel:${sessionInfo.secondID}`
268
- }
package/src/send.ts DELETED
@@ -1,363 +0,0 @@
1
- import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
2
- import type { MeetMediaInfo } from "./types.js";
3
- import type { UploadProgress as SdkUploadProgress } from "@meet-im/meet-bot-jssdk";
4
- import { resolveMeetAccount } from "./accounts.js";
5
- import { getMeetClient, createMeetClient } from "./client.js";
6
- import { parseTargetToSessionInfo } from "./sdk-bridge.js";
7
- import { getMeetRuntime } from "./runtime.js";
8
-
9
- const MENTION_PATTERN = /<@(-?\d+)>|@(-?\d+)(?![\d])/g;
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
- * 根据 MIME 类型获取文件扩展名
57
- */
58
- function getExtensionFromContentType(contentType: string): string | undefined {
59
- const mimeToExt: Record<string, string> = {
60
- "image/jpeg": ".jpg",
61
- "image/png": ".png",
62
- "image/gif": ".gif",
63
- "image/webp": ".webp",
64
- "image/avif": ".avif",
65
- "image/heic": ".heic",
66
- "image/heif": ".heif",
67
- "image/bmp": ".bmp",
68
- "image/x-icon": ".ico",
69
- "image/svg+xml": ".svg",
70
- "image/tiff": ".tiff",
71
- "video/mp4": ".mp4",
72
- "video/webm": ".webm",
73
- "video/quicktime": ".mov",
74
- "video/x-msvideo": ".avi",
75
- "video/x-matroska": ".mkv",
76
- "audio/mpeg": ".mp3",
77
- "audio/wav": ".wav",
78
- "audio/ogg": ".ogg",
79
- "audio/flac": ".flac",
80
- "audio/mp4": ".m4a",
81
- "application/pdf": ".pdf",
82
- "application/json": ".json",
83
- "application/xml": ".xml",
84
- };
85
- return mimeToExt[contentType];
86
- }
87
-
88
- /**
89
- * 确保文件名有正确的扩展名
90
- * 如果文件名没有扩展名,根据 contentType 添加
91
- */
92
- function ensureFileNameExtension(fileName: string, contentType: string): string {
93
- // 检查是否已有扩展名
94
- const hasExtension = /\.[a-zA-Z0-9]+$/.test(fileName);
95
- if (hasExtension) {
96
- return fileName;
97
- }
98
- // 根据 contentType 添加扩展名
99
- const ext = getExtensionFromContentType(contentType);
100
- if (ext) {
101
- return fileName + ext;
102
- }
103
- return fileName;
104
- }
105
-
106
- /**
107
- * 获取最终的 contentType
108
- * 如果原始 contentType 缺失或为通用二进制流,则根据文件名推断
109
- */
110
- export function resolveContentType(
111
- fileName: string,
112
- originalContentType?: string,
113
- ): string {
114
- // 如果有明确的 MIME 类型(非通用二进制流),直接使用
115
- if (originalContentType && originalContentType !== "application/octet-stream") {
116
- return originalContentType;
117
- }
118
- // 根据文件名推断
119
- const inferred = inferContentTypeFromFileName(fileName);
120
- return inferred || originalContentType || "application/octet-stream";
121
- }
122
-
123
- let _logger: RuntimeEnv | null = null;
124
-
125
- export function setSendMessageLogger(logger: RuntimeEnv): void {
126
- _logger = logger;
127
- }
128
-
129
- function log(message: string): void {
130
- if (_logger) {
131
- _logger.log(message);
132
- return;
133
- }
134
- console.log(message);
135
- }
136
-
137
- function logError(message: string): void {
138
- if (_logger) {
139
- _logger.error(message);
140
- } else {
141
- console.error(message);
142
- }
143
- }
144
-
145
- export function extractAtIds(text: string): { text: string; atIds: number[] } {
146
- const atIds: number[] = [];
147
- text.replace(MENTION_PATTERN, (_, id1, id2) => {
148
- const id = id1 ?? id2;
149
- if (id) {
150
- atIds.push(Number(id));
151
- }
152
- return _;
153
- });
154
- return { text: text.trim(), atIds };
155
- }
156
-
157
- export type SendMessageMeetOpts = {
158
- cfg: ClawdbotConfig;
159
- to: string;
160
- text: string;
161
- accountId?: string;
162
- replyToMessageId?: string;
163
- atIds?: number[];
164
- };
165
-
166
- export async function sendMessageMeet(
167
- opts: SendMessageMeetOpts,
168
- ): Promise<{ messageId: string; chatId: string }> {
169
- const { cfg, to, text, accountId, atIds: explicitAtIds } = opts;
170
- const account = resolveMeetAccount({ cfg, accountId });
171
- if (!account.configured) {
172
- throw new Error(`Meet account not configured: ${accountId ?? "default"}`);
173
- }
174
- // logLevel: info 时记录 AI 输出的原始内容,方便调试 mention 格式问题
175
- // 默认为 silent,只有显式设置为 info 时才输出
176
- if (account.config.logLevel === "info") {
177
- console.log(`[${account.accountId}] AI output raw text: ${JSON.stringify(text)}`);
178
- }
179
- const token = account.apiToken;
180
- if (!token) {
181
- throw new Error("Meet API token not configured");
182
- }
183
- // 检查 token 是否未解析(仍是 secret ref 格式)
184
- if (token.startsWith("${secret:")) {
185
- throw new Error(
186
- `Meet API token not resolved (still a secret reference). ` +
187
- `Ensure Gateway is running or token is configured directly. ` +
188
- `AccountId: ${account.accountId}`
189
- );
190
- }
191
- const botUserId = token.split(":")[0];
192
- if (!botUserId) {
193
- throw new Error("Invalid Meet API token format");
194
- }
195
- // 如果 client 不存在则自动创建(支持 message tool 直接发送消息)
196
- let bot = getMeetClient(account.accountId);
197
- if (!bot) {
198
- bot = createMeetClient(account);
199
- }
200
- const { text: cleanText, atIds: extractedAtIds } = extractAtIds(text);
201
- const finalAtIds = explicitAtIds
202
- ? [...explicitAtIds, ...extractedAtIds]
203
- : extractedAtIds;
204
- log(`send message to=${to} atIds=${finalAtIds.join(",") || "none"}`);
205
- const sessionInfo = parseTargetToSessionInfo(to, Number(botUserId));
206
- try {
207
- const result = await bot.sendMessage(sessionInfo, {
208
- content: cleanText,
209
- atIds: finalAtIds,
210
- });
211
- return {
212
- messageId: String(result.msgContent?.seqId ?? 0),
213
- chatId: to,
214
- };
215
- } catch (error) {
216
- logError(`send message error: ${String(error)}`);
217
- throw error;
218
- }
219
- }
220
-
221
- export type SendMediaMeetOpts = {
222
- cfg: ClawdbotConfig;
223
- to: string;
224
- text?: string;
225
- mediaUrl: string;
226
- mediaLocalRoots?: readonly string[];
227
- accountId?: string;
228
- /** 上传进度回调 */
229
- onProgress?: (progress: { percent: string; loaded: number; total: number; speedPerSecond: string }) => void;
230
- };
231
-
232
- export async function sendMediaMeet(
233
- opts: SendMediaMeetOpts,
234
- ): Promise<{ messageId: string; chatId: string }> {
235
- const { cfg, to, text, mediaUrl, mediaLocalRoots, accountId, onProgress } = opts;
236
- const account = resolveMeetAccount({ cfg, accountId });
237
- if (!account.configured) {
238
- throw new Error(`Meet account not configured: ${accountId ?? "default"}`);
239
- }
240
- const token = account.apiToken;
241
- if (!token) {
242
- throw new Error("Meet API token not configured");
243
- }
244
- // 检查 token 是否未解析(仍是 secret ref 格式)
245
- if (token.startsWith("${secret:")) {
246
- throw new Error(
247
- `Meet API token not resolved (still a secret reference). ` +
248
- `Ensure Gateway is running or token is configured directly. ` +
249
- `AccountId: ${account.accountId}`
250
- );
251
- }
252
- const botUserId = token.split(":")[0];
253
- if (!botUserId) {
254
- throw new Error("Invalid Meet API token format");
255
- }
256
- // 如果 client 不存在则自动创建(支持 message tool 直接发送消息)
257
- let bot = getMeetClient(account.accountId);
258
- if (!bot) {
259
- bot = createMeetClient(account);
260
- }
261
-
262
- const runtime = getMeetRuntime();
263
- const maxBytes = account.config.mediaMaxMb
264
- ? account.config.mediaMaxMb * 1024 * 1024
265
- : undefined;
266
-
267
- log(`loading media: ${mediaUrl}`);
268
- const media = await runtime.media.loadWebMedia(mediaUrl, {
269
- maxBytes,
270
- localRoots: mediaLocalRoots,
271
- });
272
-
273
- // 检查媒体大小限制
274
- if (maxBytes && media.buffer.length > maxBytes) {
275
- throw new Error(`Media file too large: ${media.buffer.length} bytes (max: ${maxBytes})`);
276
- }
277
-
278
- const sessionInfo = parseTargetToSessionInfo(to, Number(botUserId));
279
- const rawFileName = media.fileName || "file";
280
- const contentType = resolveContentType(rawFileName, media.contentType);
281
- // 确保文件名有正确的扩展名
282
- const fileName = ensureFileNameExtension(rawFileName, contentType);
283
-
284
- log(
285
- `sending media to=${to} fileName=${fileName} size=${media.buffer.length} contentType=${contentType}`,
286
- );
287
-
288
- // 包装进度回调
289
- const progressCallback = onProgress
290
- ? (progress: SdkUploadProgress) => {
291
- onProgress({
292
- percent: progress.percent,
293
- loaded: progress.loaded,
294
- total: progress.total,
295
- speedPerSecond: progress.speedPerSecond,
296
- });
297
- }
298
- : undefined;
299
-
300
- try {
301
- const result = await bot.sendMedia(sessionInfo, {
302
- buffer: media.buffer,
303
- fileName,
304
- contentType,
305
- content: text || "",
306
- onProgress: progressCallback,
307
- });
308
- return {
309
- messageId: String(result.msgContent?.seqId ?? 0),
310
- chatId: to,
311
- };
312
- } catch (error) {
313
- logError(`send media error: ${String(error)}`);
314
- throw error;
315
- }
316
- }
317
-
318
- export async function resolveMeetMedia(
319
- mediaUrl: string,
320
- opts?: {
321
- cfg?: ClawdbotConfig;
322
- accountId?: string;
323
- mediaLocalRoots?: readonly string[];
324
- },
325
- ): Promise<MeetMediaInfo> {
326
- const runtime = getMeetRuntime();
327
- let maxBytes: number | undefined;
328
- if (opts?.cfg) {
329
- const account = resolveMeetAccount({
330
- cfg: opts.cfg,
331
- accountId: opts.accountId,
332
- });
333
- if (account.config.mediaMaxMb) {
334
- maxBytes = account.config.mediaMaxMb * 1024 * 1024;
335
- }
336
- }
337
- const media = await runtime.media.loadWebMedia(mediaUrl, {
338
- maxBytes,
339
- localRoots: opts?.mediaLocalRoots,
340
- });
341
- return {
342
- path: media.fileName || "file",
343
- contentType: media.contentType,
344
- placeholder: `[Meet file: ${media.fileName || "file"}]`,
345
- };
346
- }
347
-
348
- export async function getMessageMeet(opts: {
349
- cfg: ClawdbotConfig;
350
- messageId: string;
351
- accountId?: string;
352
- }): Promise<{ content: string } | null> {
353
- const { cfg, accountId } = opts;
354
- const account = resolveMeetAccount({ cfg, accountId });
355
- if (!account.configured) {
356
- return null;
357
- }
358
- const bot = getMeetClient(account.accountId);
359
- if (!bot) {
360
- return null;
361
- }
362
- return null;
363
- }