@meet-im/meet 1.0.3

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,268 @@
1
+ import type { MsgContent, SessionInfo, SessionType, AttachmentInfo } 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 ADDED
@@ -0,0 +1,223 @@
1
+ import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
2
+ import type { ResolvedMeetAccount, 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 } 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
+ let _logger: RuntimeEnv | null = null;
12
+
13
+ export function setSendMessageLogger(logger: RuntimeEnv): void {
14
+ _logger = logger;
15
+ }
16
+
17
+ function log(message: string): void {
18
+ if (_logger) {
19
+ _logger.log(message);
20
+ }
21
+ }
22
+
23
+ function logError(message: string): void {
24
+ if (_logger) {
25
+ _logger.error(message);
26
+ } else {
27
+ console.error(message);
28
+ }
29
+ }
30
+
31
+ export function extractAtIds(text: string): { text: string; atIds: number[] } {
32
+ const atIds: number[] = [];
33
+ text.replace(MENTION_PATTERN, (_, id1, id2) => {
34
+ const id = id1 ?? id2;
35
+ if (id) {
36
+ atIds.push(Number(id));
37
+ }
38
+ return _;
39
+ });
40
+ return { text: text.trim(), atIds };
41
+ }
42
+
43
+ export type SendMessageMeetOpts = {
44
+ cfg: ClawdbotConfig;
45
+ to: string;
46
+ text: string;
47
+ accountId?: string;
48
+ replyToMessageId?: string;
49
+ atIds?: number[];
50
+ };
51
+
52
+ export async function sendMessageMeet(
53
+ opts: SendMessageMeetOpts,
54
+ ): Promise<{ messageId: string; chatId: string }> {
55
+ const { cfg, to, text, accountId, atIds: explicitAtIds } = opts;
56
+ const account = resolveMeetAccount({ cfg, accountId });
57
+ if (!account.configured) {
58
+ throw new Error(`Meet account not configured: ${accountId ?? "default"}`);
59
+ }
60
+ const token = account.apiToken;
61
+ if (!token) {
62
+ throw new Error("Meet API token not configured");
63
+ }
64
+ const botUserId = token.split(":")[0];
65
+ if (!botUserId) {
66
+ throw new Error("Invalid Meet API token format");
67
+ }
68
+ const bot = getMeetClient(account.accountId);
69
+ if (!bot) {
70
+ throw new Error(`Meet client not found for account: ${account.accountId}`);
71
+ }
72
+ const { text: cleanText, atIds: extractedAtIds } = extractAtIds(text);
73
+ const finalAtIds = explicitAtIds
74
+ ? [...explicitAtIds, ...extractedAtIds]
75
+ : extractedAtIds;
76
+ log(`send message to=${to} atIds=${finalAtIds.join(",") || "none"}`);
77
+ const sessionInfo = parseTargetToSessionInfo(to, Number(botUserId));
78
+ try {
79
+ const result = await bot.sendMessage(sessionInfo, {
80
+ content: cleanText,
81
+ atIds: finalAtIds,
82
+ });
83
+ return {
84
+ messageId: String(result.msgContent?.seqId ?? 0),
85
+ chatId: to,
86
+ };
87
+ } catch (error) {
88
+ logError(`send message error: ${String(error)}`);
89
+ throw error;
90
+ }
91
+ }
92
+
93
+ export type SendMediaMeetOpts = {
94
+ cfg: ClawdbotConfig;
95
+ to: string;
96
+ text?: string;
97
+ mediaUrl: string;
98
+ mediaLocalRoots?: readonly string[];
99
+ accountId?: string;
100
+ /** 上传进度回调 */
101
+ onProgress?: (progress: { percent: string; loaded: number; total: number; speedPerSecond: string }) => void;
102
+ };
103
+
104
+ export async function sendMediaMeet(
105
+ opts: SendMediaMeetOpts,
106
+ ): Promise<{ messageId: string; chatId: string }> {
107
+ const { cfg, to, text, mediaUrl, mediaLocalRoots, accountId, onProgress } = opts;
108
+ const account = resolveMeetAccount({ cfg, accountId });
109
+ if (!account.configured) {
110
+ throw new Error(`Meet account not configured: ${accountId ?? "default"}`);
111
+ }
112
+ const token = account.apiToken;
113
+ if (!token) {
114
+ throw new Error("Meet API token not configured");
115
+ }
116
+ const botUserId = token.split(":")[0];
117
+ if (!botUserId) {
118
+ throw new Error("Invalid Meet API token format");
119
+ }
120
+ const bot = getMeetClient(account.accountId);
121
+ if (!bot) {
122
+ throw new Error(`Meet client not found for account: ${account.accountId}`);
123
+ }
124
+
125
+ const runtime = getMeetRuntime();
126
+ const maxBytes = account.config.mediaMaxMb
127
+ ? account.config.mediaMaxMb * 1024 * 1024
128
+ : undefined;
129
+
130
+ log(`loading media: ${mediaUrl}`);
131
+ const media = await runtime.media.loadWebMedia(mediaUrl, {
132
+ maxBytes,
133
+ localRoots: mediaLocalRoots,
134
+ });
135
+
136
+ // 检查媒体大小限制
137
+ if (maxBytes && media.buffer.length > maxBytes) {
138
+ throw new Error(`Media file too large: ${media.buffer.length} bytes (max: ${maxBytes})`);
139
+ }
140
+
141
+ const sessionInfo = parseTargetToSessionInfo(to, Number(botUserId));
142
+ const fileName = media.fileName || "file";
143
+
144
+ log(
145
+ `sending media to=${to} fileName=${fileName} size=${media.buffer.length}`,
146
+ );
147
+
148
+ // 包装进度回调
149
+ const progressCallback = onProgress
150
+ ? (progress: SdkUploadProgress) => {
151
+ onProgress({
152
+ percent: progress.percent,
153
+ loaded: progress.loaded,
154
+ total: progress.total,
155
+ speedPerSecond: progress.speedPerSecond,
156
+ });
157
+ }
158
+ : undefined;
159
+
160
+ try {
161
+ const result = await bot.sendMedia(sessionInfo, {
162
+ buffer: media.buffer,
163
+ fileName,
164
+ contentType: media.contentType || "application/octet-stream",
165
+ content: text || "",
166
+ onProgress: progressCallback,
167
+ });
168
+ return {
169
+ messageId: String(result.msgContent?.seqId ?? 0),
170
+ chatId: to,
171
+ };
172
+ } catch (error) {
173
+ logError(`send media error: ${String(error)}`);
174
+ throw error;
175
+ }
176
+ }
177
+
178
+ export async function resolveMeetMedia(
179
+ mediaUrl: string,
180
+ opts?: {
181
+ cfg?: ClawdbotConfig;
182
+ accountId?: string;
183
+ mediaLocalRoots?: readonly string[];
184
+ },
185
+ ): Promise<MeetMediaInfo> {
186
+ const runtime = getMeetRuntime();
187
+ let maxBytes: number | undefined;
188
+ if (opts?.cfg) {
189
+ const account = resolveMeetAccount({
190
+ cfg: opts.cfg,
191
+ accountId: opts.accountId,
192
+ });
193
+ if (account.config.mediaMaxMb) {
194
+ maxBytes = account.config.mediaMaxMb * 1024 * 1024;
195
+ }
196
+ }
197
+ const media = await runtime.media.loadWebMedia(mediaUrl, {
198
+ maxBytes,
199
+ localRoots: opts?.mediaLocalRoots,
200
+ });
201
+ return {
202
+ path: media.fileName || "file",
203
+ contentType: media.contentType,
204
+ placeholder: `[Meet file: ${media.fileName || "file"}]`,
205
+ };
206
+ }
207
+
208
+ export async function getMessageMeet(opts: {
209
+ cfg: ClawdbotConfig;
210
+ messageId: string;
211
+ accountId?: string;
212
+ }): Promise<{ content: string } | null> {
213
+ const { cfg, messageId, accountId } = opts;
214
+ const account = resolveMeetAccount({ cfg, accountId });
215
+ if (!account.configured) {
216
+ return null;
217
+ }
218
+ const bot = getMeetClient(account.accountId);
219
+ if (!bot) {
220
+ return null;
221
+ }
222
+ return null;
223
+ }
package/src/targets.ts ADDED
@@ -0,0 +1,101 @@
1
+ export type MeetTargetKind = "user" | "channel"
2
+
3
+ export type MeetTarget = {
4
+ kind: MeetTargetKind
5
+ id: string
6
+ raw: string
7
+ normalized: string
8
+ }
9
+
10
+ export type MeetTargetParseOptions = {
11
+ defaultKind?: MeetTargetKind
12
+ ambiguousMessage?: string
13
+ }
14
+
15
+ function buildMessagingTarget(kind: MeetTargetKind, id: string, raw: string): MeetTarget {
16
+ return { kind, id, raw, normalized: `${kind}:${id}` }
17
+ }
18
+
19
+ function parseMentionPrefixOrAtUserTarget(params: {
20
+ raw: string
21
+ mentionPattern: RegExp
22
+ prefixes: Array<{ prefix: string; kind: MeetTargetKind }>
23
+ atUserPattern: RegExp
24
+ atUserErrorMessage: string
25
+ }): MeetTarget | undefined {
26
+ const { raw, mentionPattern, prefixes, atUserPattern } = params
27
+
28
+ const mentionMatch = raw.match(mentionPattern)
29
+ if (mentionMatch) {
30
+ return buildMessagingTarget("user", mentionMatch[2], raw)
31
+ }
32
+
33
+ for (const { prefix, kind } of prefixes) {
34
+ if (raw.startsWith(prefix)) {
35
+ const id = raw.slice(prefix.length)
36
+ if (id) {
37
+ return buildMessagingTarget(kind, id, raw)
38
+ }
39
+ }
40
+ }
41
+
42
+ if (atUserPattern.test(raw)) {
43
+ return buildMessagingTarget("user", raw, raw)
44
+ }
45
+
46
+ return undefined
47
+ }
48
+
49
+ export function parseMeetTarget(
50
+ raw: string,
51
+ options: MeetTargetParseOptions = {},
52
+ ): MeetTarget | undefined {
53
+ const trimmed = raw.trim()
54
+ if (!trimmed) {
55
+ return undefined
56
+ }
57
+
58
+ const userTarget = parseMentionPrefixOrAtUserTarget({
59
+ raw: trimmed,
60
+ mentionPattern: /^@\[([^\]]+)\]\((\d+)\)$/,
61
+ prefixes: [
62
+ { prefix: "user:", kind: "user" },
63
+ { prefix: "channel:", kind: "channel" },
64
+ { prefix: "meet:", kind: "user" },
65
+ ],
66
+ atUserPattern: /^\d+$/,
67
+ atUserErrorMessage: "Meet DMs require a user id (use user:<id>)",
68
+ })
69
+
70
+ if (userTarget) {
71
+ return userTarget
72
+ }
73
+
74
+ if (/^\d+$/.test(trimmed)) {
75
+ if (options.defaultKind) {
76
+ return buildMessagingTarget(options.defaultKind, trimmed, trimmed)
77
+ }
78
+ throw new Error(
79
+ options.ambiguousMessage ??
80
+ `Ambiguous Meet recipient "${trimmed}". Use "user:${trimmed}" for DMs or "channel:${trimmed}" for channel messages.`,
81
+ )
82
+ }
83
+
84
+ return buildMessagingTarget("channel", trimmed, trimmed)
85
+ }
86
+
87
+ export function resolveMeetChannelId(raw: string): string {
88
+ const target = parseMeetTarget(raw, { defaultKind: "channel" })
89
+ if (!target) {
90
+ throw new Error(`Invalid Meet channel: ${raw}`)
91
+ }
92
+ return target.id
93
+ }
94
+
95
+ export function formatMeetTarget(target: MeetTarget): string {
96
+ return `${target.kind}:${target.id}`
97
+ }
98
+
99
+ export function looksLikeMeetId(input: string): boolean {
100
+ return /^\d+$/.test(input) || /^(user:|channel:|meet:)/.test(input)
101
+ }
package/src/types.ts ADDED
@@ -0,0 +1,96 @@
1
+ import type {
2
+ SessionInfo,
3
+ AttachmentInfo,
4
+ UploadProgress,
5
+ } from "@meet-im/meet-bot-jssdk"
6
+ import type { z } from "zod"
7
+ import {
8
+ MeetConfigSchema,
9
+ MeetAccountConfigSchema,
10
+ MeetChannelConfigSchema,
11
+ MeetGroupConfigSchema,
12
+ } from "./config-schema.js"
13
+
14
+ export type MeetConfig = z.infer<typeof MeetConfigSchema>
15
+ export type MeetAccountConfig = z.infer<typeof MeetAccountConfigSchema>
16
+ export type MeetChannelConfig = z.infer<typeof MeetChannelConfigSchema>
17
+ export type MeetGroupConfig = z.infer<typeof MeetGroupConfigSchema>
18
+
19
+ // 重新导出 SDK 类型
20
+ export type { AttachmentInfo, UploadProgress }
21
+
22
+ export type ResolvedMeetAccount = {
23
+ accountId: string
24
+ enabled: boolean
25
+ configured: boolean
26
+ name?: string
27
+ apiEndpoint?: string
28
+ apiToken?: string
29
+ config: MeetConfig
30
+ }
31
+
32
+ export type MeetReplyContext = {
33
+ messageId: string
34
+ senderId?: string
35
+ senderName?: string
36
+ content?: string
37
+ timestamp?: number
38
+ mediaPaths?: string[]
39
+ }
40
+
41
+ export type MeetMessageContext = {
42
+ chatId: string
43
+ messageId: string
44
+ senderId: string
45
+ senderOpenId: string
46
+ senderName?: string
47
+ chatType: "direct" | "channel"
48
+ mentionedBot: boolean
49
+ hasAnyMention?: boolean
50
+ content: string
51
+ contentType: string
52
+ placeholder?: string
53
+ rawPayload?: string
54
+ sessionInfo: SessionInfo
55
+ timestamp?: number
56
+ atIds?: number[]
57
+ replyContext?: MeetReplyContext
58
+ media?: MeetMediaAttachment[]
59
+ }
60
+
61
+ export type MeetSendResult = {
62
+ messageId: string
63
+ chatId: string
64
+ }
65
+
66
+ export type MeetProbeResult = {
67
+ ok: boolean
68
+ error?: string
69
+ botId?: string
70
+ }
71
+
72
+ export type MeetMediaInfo = {
73
+ path: string
74
+ contentType?: string
75
+ placeholder: string
76
+ }
77
+
78
+ /**
79
+ * 入站消息中的媒体附件信息
80
+ */
81
+ export type MeetMediaAttachment = {
82
+ fileId: string | number
83
+ fileName?: string
84
+ fileSize?: number
85
+ mimeType?: string
86
+ fileUrl?: string
87
+ }
88
+
89
+ /**
90
+ * 文件上传结果
91
+ */
92
+ export type MeetUploadResult = {
93
+ fileID: number
94
+ path: string
95
+ size: number
96
+ }