@ascegu/teamily 1.0.23 → 1.0.25
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/src/channel.ts +25 -7
- package/src/monitor.ts +69 -12
- package/src/upload.ts +1 -1
- package/src/send.ts +0 -271
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -30,7 +30,12 @@ import {
|
|
|
30
30
|
} from "./accounts.js";
|
|
31
31
|
import { TeamilyConfigSchema } from "./config-schema.js";
|
|
32
32
|
import type { CoreConfig } from "./config-schema.js";
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
getTeamilyMonitor,
|
|
35
|
+
startTeamilyMonitoring,
|
|
36
|
+
stopTeamilyMonitoring,
|
|
37
|
+
type TeamilyMonitor,
|
|
38
|
+
} from "./monitor.js";
|
|
34
39
|
import { normalizeTeamilyTarget, normalizeTeamilyAllowEntry } from "./normalize.js";
|
|
35
40
|
import { probeTeamily } from "./probe.js";
|
|
36
41
|
import { getTeamilyRuntime } from "./runtime.js";
|
|
@@ -65,6 +70,11 @@ export const teamilyPlugin: ChannelPlugin<ResolvedTeamilyAccount> = {
|
|
|
65
70
|
threads: false,
|
|
66
71
|
polls: false,
|
|
67
72
|
},
|
|
73
|
+
agentPrompt: {
|
|
74
|
+
messageToolHints: () => [
|
|
75
|
+
"- To send a local file or image to the user, use MEDIA:./relative-path in your reply (e.g. MEDIA:./image.png). The path must be relative to the workspace. You can also use the message tool with media/path/filePath. Avoid absolute paths and ~ paths — they are blocked for security.",
|
|
76
|
+
],
|
|
77
|
+
},
|
|
68
78
|
reload: { configPrefixes: ["channels.teamily"] },
|
|
69
79
|
setup: {
|
|
70
80
|
resolveAccountId: ({ accountId, input }) => {
|
|
@@ -192,7 +202,12 @@ export const teamilyPlugin: ChannelPlugin<ResolvedTeamilyAccount> = {
|
|
|
192
202
|
const fileName = media.fileName || path.basename(mediaUrl) || "media";
|
|
193
203
|
const contentType = media.contentType || guessContentType(mediaUrl);
|
|
194
204
|
const messageId = await sendMediaBuffer(
|
|
195
|
-
monitor,
|
|
205
|
+
monitor,
|
|
206
|
+
target,
|
|
207
|
+
media.buffer,
|
|
208
|
+
fileName,
|
|
209
|
+
contentType,
|
|
210
|
+
mediaUrl,
|
|
196
211
|
);
|
|
197
212
|
return { channel: "teamily" as const, messageId };
|
|
198
213
|
}
|
|
@@ -373,12 +388,14 @@ export const teamilyPlugin: ChannelPlugin<ResolvedTeamilyAccount> = {
|
|
|
373
388
|
if (!monitor)
|
|
374
389
|
throw new Error(`Teamily monitor not running for account ${accountId}`);
|
|
375
390
|
const target = normalizeTeamilyTarget(replyTarget);
|
|
376
|
-
const replyText =
|
|
377
|
-
|
|
378
|
-
??
|
|
391
|
+
const replyText =
|
|
392
|
+
((payload as Record<string, unknown>)?.text as string | undefined) ??
|
|
393
|
+
((payload as Record<string, unknown>)?.body as string | undefined) ??
|
|
394
|
+
"";
|
|
379
395
|
|
|
380
396
|
// Send media attachments (first with caption, rest without).
|
|
381
397
|
const mediaUrls = resolveOutboundMediaUrls(payload as Record<string, unknown>);
|
|
398
|
+
let captionSent = false;
|
|
382
399
|
const sentMedia = await sendMediaWithLeadingCaption({
|
|
383
400
|
mediaUrls,
|
|
384
401
|
caption: replyText,
|
|
@@ -387,6 +404,7 @@ export const teamilyPlugin: ChannelPlugin<ResolvedTeamilyAccount> = {
|
|
|
387
404
|
// OpenIM doesn't support inline captions on media; send separately.
|
|
388
405
|
if (caption) {
|
|
389
406
|
await monitor.sendText(target, caption);
|
|
407
|
+
captionSent = true;
|
|
390
408
|
}
|
|
391
409
|
},
|
|
392
410
|
onError: (error) => {
|
|
@@ -394,8 +412,8 @@ export const teamilyPlugin: ChannelPlugin<ResolvedTeamilyAccount> = {
|
|
|
394
412
|
},
|
|
395
413
|
});
|
|
396
414
|
|
|
397
|
-
// If no media was sent, send text
|
|
398
|
-
if (!sentMedia && replyText) {
|
|
415
|
+
// If no media was sent (or media failed), send text as a plain message.
|
|
416
|
+
if ((!sentMedia || !captionSent) && replyText) {
|
|
399
417
|
log?.info?.(
|
|
400
418
|
`[${accountId}] Sending reply to: ${replyTarget} (isGroup=${isGroup})`,
|
|
401
419
|
);
|
package/src/monitor.ts
CHANGED
|
@@ -20,6 +20,36 @@ export interface TeamilyMonitorOptions {
|
|
|
20
20
|
type SdkModule = typeof import("@openim/client-sdk");
|
|
21
21
|
type SdkInstance = ReturnType<SdkModule["getSDK"]>;
|
|
22
22
|
|
|
23
|
+
// The OpenIM SDK uses FileReader internally for file upload (MD5 hashing of
|
|
24
|
+
// chunks). FileReader is a browser-only API not available in Node.js, so we
|
|
25
|
+
// provide a minimal polyfill that delegates to Blob.prototype.arrayBuffer().
|
|
26
|
+
if (typeof globalThis.FileReader === "undefined") {
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
+
(globalThis as any).FileReader = class FileReader {
|
|
29
|
+
result: ArrayBuffer | null = null;
|
|
30
|
+
error: unknown = null;
|
|
31
|
+
readyState = 0; // 0=EMPTY, 1=LOADING, 2=DONE
|
|
32
|
+
onload: ((ev: { target: FileReader }) => void) | null = null;
|
|
33
|
+
onerror: ((ev: unknown) => void) | null = null;
|
|
34
|
+
|
|
35
|
+
readAsArrayBuffer(blob: Blob): void {
|
|
36
|
+
this.readyState = 1;
|
|
37
|
+
blob
|
|
38
|
+
.arrayBuffer()
|
|
39
|
+
.then((buffer) => {
|
|
40
|
+
this.result = buffer;
|
|
41
|
+
this.readyState = 2;
|
|
42
|
+
this.onload?.({ target: this });
|
|
43
|
+
})
|
|
44
|
+
.catch((err) => {
|
|
45
|
+
this.error = err;
|
|
46
|
+
this.readyState = 2;
|
|
47
|
+
this.onerror?.(err);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
23
53
|
// Lazy-loaded SDK to avoid top-level dynamic import issues
|
|
24
54
|
let sdkModule: SdkModule | null = null;
|
|
25
55
|
async function loadSDK() {
|
|
@@ -209,10 +239,23 @@ export class TeamilyMonitor {
|
|
|
209
239
|
// Like Telegram's InputFile(buffer), pass a buffer and let the SDK upload.
|
|
210
240
|
|
|
211
241
|
/** Send an image from a local buffer. SDK handles the upload to object storage. */
|
|
212
|
-
async sendImageBuffer(
|
|
242
|
+
async sendImageBuffer(
|
|
243
|
+
target: TeamilyMessageTarget,
|
|
244
|
+
buffer: Buffer,
|
|
245
|
+
fileName: string,
|
|
246
|
+
contentType: string,
|
|
247
|
+
): Promise<string> {
|
|
213
248
|
const sdk = this.requireSdk();
|
|
214
249
|
const file = new File([new Uint8Array(buffer)], fileName, { type: contentType });
|
|
215
|
-
const
|
|
250
|
+
const uuid = crypto.randomUUID();
|
|
251
|
+
const picInfo = {
|
|
252
|
+
uuid,
|
|
253
|
+
type: contentType,
|
|
254
|
+
width: 0,
|
|
255
|
+
height: 0,
|
|
256
|
+
size: buffer.length,
|
|
257
|
+
url: "",
|
|
258
|
+
};
|
|
216
259
|
const created = await sdk.createImageMessageByFile({
|
|
217
260
|
sourcePicture: picInfo,
|
|
218
261
|
bigPicture: picInfo,
|
|
@@ -229,7 +272,12 @@ export class TeamilyMonitor {
|
|
|
229
272
|
}
|
|
230
273
|
|
|
231
274
|
/** Send a video from a local buffer. SDK handles the upload to object storage. */
|
|
232
|
-
async sendVideoBuffer(
|
|
275
|
+
async sendVideoBuffer(
|
|
276
|
+
target: TeamilyMessageTarget,
|
|
277
|
+
buffer: Buffer,
|
|
278
|
+
fileName: string,
|
|
279
|
+
contentType: string,
|
|
280
|
+
): Promise<string> {
|
|
233
281
|
const sdk = this.requireSdk();
|
|
234
282
|
const videoFile = new File([new Uint8Array(buffer)], fileName, { type: contentType });
|
|
235
283
|
// snapshotFile is required by the SDK type but we have no thumbnail; use an empty file.
|
|
@@ -239,10 +287,10 @@ export class TeamilyMonitor {
|
|
|
239
287
|
duration: 0,
|
|
240
288
|
videoType: contentType,
|
|
241
289
|
snapshotPath: "",
|
|
242
|
-
videoUUID:
|
|
290
|
+
videoUUID: crypto.randomUUID(),
|
|
243
291
|
videoUrl: "",
|
|
244
292
|
videoSize: buffer.length,
|
|
245
|
-
snapshotUUID:
|
|
293
|
+
snapshotUUID: crypto.randomUUID(),
|
|
246
294
|
snapshotSize: 0,
|
|
247
295
|
snapshotUrl: "",
|
|
248
296
|
snapshotWidth: 0,
|
|
@@ -259,11 +307,16 @@ export class TeamilyMonitor {
|
|
|
259
307
|
}
|
|
260
308
|
|
|
261
309
|
/** Send an audio/sound from a local buffer. SDK handles the upload to object storage. */
|
|
262
|
-
async sendAudioBuffer(
|
|
310
|
+
async sendAudioBuffer(
|
|
311
|
+
target: TeamilyMessageTarget,
|
|
312
|
+
buffer: Buffer,
|
|
313
|
+
fileName: string,
|
|
314
|
+
contentType: string,
|
|
315
|
+
): Promise<string> {
|
|
263
316
|
const sdk = this.requireSdk();
|
|
264
317
|
const file = new File([new Uint8Array(buffer)], fileName, { type: contentType });
|
|
265
318
|
const created = await sdk.createSoundMessageByFile({
|
|
266
|
-
uuid:
|
|
319
|
+
uuid: crypto.randomUUID(),
|
|
267
320
|
soundPath: "",
|
|
268
321
|
sourceUrl: "",
|
|
269
322
|
dataSize: buffer.length,
|
|
@@ -279,13 +332,18 @@ export class TeamilyMonitor {
|
|
|
279
332
|
}
|
|
280
333
|
|
|
281
334
|
/** Send a document/file from a local buffer. SDK handles the upload to object storage. */
|
|
282
|
-
async sendFileBuffer(
|
|
335
|
+
async sendFileBuffer(
|
|
336
|
+
target: TeamilyMessageTarget,
|
|
337
|
+
buffer: Buffer,
|
|
338
|
+
fileName: string,
|
|
339
|
+
contentType: string,
|
|
340
|
+
): Promise<string> {
|
|
283
341
|
const sdk = this.requireSdk();
|
|
284
342
|
const file = new File([new Uint8Array(buffer)], fileName, { type: contentType });
|
|
285
343
|
const created = await sdk.createFileMessageByFile({
|
|
286
344
|
filePath: "",
|
|
287
345
|
fileName,
|
|
288
|
-
uuid:
|
|
346
|
+
uuid: crypto.randomUUID(),
|
|
289
347
|
sourceUrl: "",
|
|
290
348
|
fileSize: buffer.length,
|
|
291
349
|
file,
|
|
@@ -328,13 +386,12 @@ function convertSdkMessage(msg: MessageItem, selfUserID: string): TeamilyMessage
|
|
|
328
386
|
// Determine whether this message @-mentions the bot
|
|
329
387
|
const isAtSelf =
|
|
330
388
|
contentType === CONTENT_TYPES.AT_TEXT &&
|
|
331
|
-
(msg.atTextElem?.isAtSelf === true ||
|
|
332
|
-
(msg.atTextElem?.atUserList ?? []).includes(selfUserID));
|
|
389
|
+
(msg.atTextElem?.isAtSelf === true || (msg.atTextElem?.atUserList ?? []).includes(selfUserID));
|
|
333
390
|
|
|
334
391
|
return {
|
|
335
392
|
serverMsgID: msg.serverMsgID || msg.clientMsgID || `${Date.now()}_${Math.random()}`,
|
|
336
393
|
sendID: msg.sendID || "unknown",
|
|
337
|
-
recvID: isGroupSession(sessionType) ?
|
|
394
|
+
recvID: isGroupSession(sessionType) ? msg.groupID || "" : msg.recvID || selfUserID,
|
|
338
395
|
content,
|
|
339
396
|
contentType,
|
|
340
397
|
sessionType,
|
package/src/upload.ts
CHANGED
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
|
|
3
3
|
/** Returns true if the media URL is a local file path rather than a remote URL. */
|
|
4
4
|
export function isLocalMediaPath(mediaUrl: string): boolean {
|
|
5
|
-
return
|
|
5
|
+
return !/^https?:\/\//i.test(mediaUrl);
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
/** Detect the media category from a file extension. */
|
package/src/send.ts
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
import { generateOperationID } from "./probe.js";
|
|
2
|
-
import type { ResolvedTeamilyAccount, TeamilyMessageTarget } from "./types.js";
|
|
3
|
-
import { CONTENT_TYPES, SESSION_TYPES } from "./types.js";
|
|
4
|
-
|
|
5
|
-
export interface SendTeamilyMessageParams {
|
|
6
|
-
account: ResolvedTeamilyAccount;
|
|
7
|
-
target: TeamilyMessageTarget;
|
|
8
|
-
text: string;
|
|
9
|
-
replyToId?: string;
|
|
10
|
-
fetchImpl?: typeof fetch;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface SendTeamilyMediaParams {
|
|
14
|
-
account: ResolvedTeamilyAccount;
|
|
15
|
-
target: TeamilyMessageTarget;
|
|
16
|
-
mediaUrl: string;
|
|
17
|
-
mediaType: "image" | "video" | "audio" | "file";
|
|
18
|
-
caption?: string;
|
|
19
|
-
fetchImpl?: typeof fetch;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface TeamilySendResult {
|
|
23
|
-
success: boolean;
|
|
24
|
-
messageId?: string;
|
|
25
|
-
serverMsgID?: string;
|
|
26
|
-
clientMsgID?: string;
|
|
27
|
-
error?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Send a text message via Teamily REST API.
|
|
32
|
-
*
|
|
33
|
-
* @param params - Send parameters
|
|
34
|
-
* @returns Send result with message ID or error
|
|
35
|
-
*/
|
|
36
|
-
export async function sendMessageTeamily(
|
|
37
|
-
params: SendTeamilyMessageParams,
|
|
38
|
-
): Promise<TeamilySendResult> {
|
|
39
|
-
const { account, target, text, replyToId, fetchImpl = fetch } = params;
|
|
40
|
-
|
|
41
|
-
const url = `${account.apiURL}/msg/send_msg`;
|
|
42
|
-
|
|
43
|
-
const payload = buildSendMessagePayload({
|
|
44
|
-
sendID: account.userID,
|
|
45
|
-
target,
|
|
46
|
-
content: { content: text },
|
|
47
|
-
contentType: CONTENT_TYPES.TEXT,
|
|
48
|
-
replyToId,
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
const response = await fetchImpl(url, {
|
|
53
|
-
method: "POST",
|
|
54
|
-
headers: {
|
|
55
|
-
"Content-Type": "application/json",
|
|
56
|
-
operationID: generateOperationID(),
|
|
57
|
-
token: account.token,
|
|
58
|
-
},
|
|
59
|
-
body: JSON.stringify(payload),
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
if (!response.ok) {
|
|
63
|
-
const errorText = await response.text();
|
|
64
|
-
return {
|
|
65
|
-
success: false,
|
|
66
|
-
error: `HTTP ${response.status}: ${errorText}`,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const data = (await response.json()) as {
|
|
71
|
-
errCode: number;
|
|
72
|
-
errMsg: string;
|
|
73
|
-
data?: {
|
|
74
|
-
serverMsgID: string;
|
|
75
|
-
clientMsgID: string;
|
|
76
|
-
sendTime: number;
|
|
77
|
-
};
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
if (data.errCode !== 0) {
|
|
81
|
-
return {
|
|
82
|
-
success: false,
|
|
83
|
-
error: data.errMsg || "Send failed",
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
success: true,
|
|
89
|
-
messageId: data.data?.serverMsgID,
|
|
90
|
-
serverMsgID: data.data?.serverMsgID,
|
|
91
|
-
clientMsgID: data.data?.clientMsgID,
|
|
92
|
-
};
|
|
93
|
-
} catch (error) {
|
|
94
|
-
return {
|
|
95
|
-
success: false,
|
|
96
|
-
error: error instanceof Error ? error.message : String(error),
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Send a media message via Teamily REST API.
|
|
103
|
-
*
|
|
104
|
-
* @param params - Send parameters
|
|
105
|
-
* @returns Send result with message ID or error
|
|
106
|
-
*/
|
|
107
|
-
export async function sendMediaTeamily(params: SendTeamilyMediaParams): Promise<TeamilySendResult> {
|
|
108
|
-
const { account, target, mediaUrl, mediaType, caption, fetchImpl = fetch } = params;
|
|
109
|
-
|
|
110
|
-
const url = `${account.apiURL}/msg/send_msg`;
|
|
111
|
-
|
|
112
|
-
let content: Record<string, unknown>;
|
|
113
|
-
let contentType: number;
|
|
114
|
-
|
|
115
|
-
switch (mediaType) {
|
|
116
|
-
case "image":
|
|
117
|
-
contentType = CONTENT_TYPES.PICTURE;
|
|
118
|
-
content = {
|
|
119
|
-
sourcePicture: {
|
|
120
|
-
uuid: generateOperationID(),
|
|
121
|
-
type: "public",
|
|
122
|
-
width: 0,
|
|
123
|
-
height: 0,
|
|
124
|
-
url: mediaUrl,
|
|
125
|
-
},
|
|
126
|
-
};
|
|
127
|
-
break;
|
|
128
|
-
|
|
129
|
-
case "video":
|
|
130
|
-
contentType = CONTENT_TYPES.VIDEO;
|
|
131
|
-
content = {
|
|
132
|
-
videoPath: mediaUrl,
|
|
133
|
-
videoUUID: generateOperationID(),
|
|
134
|
-
videoUrl: mediaUrl,
|
|
135
|
-
videoType: "mp4",
|
|
136
|
-
videoSize: 0,
|
|
137
|
-
duration: 0,
|
|
138
|
-
snapshotUUID: `${generateOperationID()}_snap`,
|
|
139
|
-
snapshotUrl: "",
|
|
140
|
-
snapshotSize: 0,
|
|
141
|
-
snapshotWidth: 0,
|
|
142
|
-
snapshotHeight: 0,
|
|
143
|
-
};
|
|
144
|
-
break;
|
|
145
|
-
|
|
146
|
-
case "audio":
|
|
147
|
-
contentType = CONTENT_TYPES.VOICE;
|
|
148
|
-
content = {
|
|
149
|
-
uuid: generateOperationID(),
|
|
150
|
-
soundPath: mediaUrl,
|
|
151
|
-
sourceUrl: mediaUrl,
|
|
152
|
-
dataSize: 0,
|
|
153
|
-
duration: 0,
|
|
154
|
-
soundType: "mp3",
|
|
155
|
-
};
|
|
156
|
-
break;
|
|
157
|
-
|
|
158
|
-
case "file":
|
|
159
|
-
contentType = CONTENT_TYPES.FILE;
|
|
160
|
-
content = {
|
|
161
|
-
uuid: generateOperationID(),
|
|
162
|
-
fileName: mediaUrl.split("/").pop() || "file",
|
|
163
|
-
fileSize: 0,
|
|
164
|
-
sourceUrl: mediaUrl,
|
|
165
|
-
fileType: "application/octet-stream",
|
|
166
|
-
};
|
|
167
|
-
break;
|
|
168
|
-
|
|
169
|
-
default:
|
|
170
|
-
return {
|
|
171
|
-
success: false,
|
|
172
|
-
error: `Unsupported media type: ${mediaType}`,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Add caption to text content for media messages
|
|
177
|
-
if (caption) {
|
|
178
|
-
content.text = caption;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const payload = buildSendMessagePayload({
|
|
182
|
-
sendID: account.userID,
|
|
183
|
-
target,
|
|
184
|
-
content,
|
|
185
|
-
contentType,
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
try {
|
|
189
|
-
const response = await fetchImpl(url, {
|
|
190
|
-
method: "POST",
|
|
191
|
-
headers: {
|
|
192
|
-
"Content-Type": "application/json",
|
|
193
|
-
operationID: generateOperationID(),
|
|
194
|
-
token: account.token,
|
|
195
|
-
},
|
|
196
|
-
body: JSON.stringify(payload),
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
if (!response.ok) {
|
|
200
|
-
const errorText = await response.text();
|
|
201
|
-
return {
|
|
202
|
-
success: false,
|
|
203
|
-
error: `HTTP ${response.status}: ${errorText}`,
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const data = (await response.json()) as {
|
|
208
|
-
errCode: number;
|
|
209
|
-
errMsg: string;
|
|
210
|
-
data?: {
|
|
211
|
-
serverMsgID: string;
|
|
212
|
-
clientMsgID: string;
|
|
213
|
-
sendTime: number;
|
|
214
|
-
};
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
if (data.errCode !== 0) {
|
|
218
|
-
return {
|
|
219
|
-
success: false,
|
|
220
|
-
error: data.errMsg || "Send failed",
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return {
|
|
225
|
-
success: true,
|
|
226
|
-
messageId: data.data?.serverMsgID,
|
|
227
|
-
serverMsgID: data.data?.serverMsgID,
|
|
228
|
-
clientMsgID: data.data?.clientMsgID,
|
|
229
|
-
};
|
|
230
|
-
} catch (error) {
|
|
231
|
-
return {
|
|
232
|
-
success: false,
|
|
233
|
-
error: error instanceof Error ? error.message : String(error),
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Build the send message payload for Teamily API.
|
|
240
|
-
*/
|
|
241
|
-
function buildSendMessagePayload(params: {
|
|
242
|
-
sendID: string;
|
|
243
|
-
target: TeamilyMessageTarget;
|
|
244
|
-
content: Record<string, unknown>;
|
|
245
|
-
contentType: number;
|
|
246
|
-
replyToId?: string;
|
|
247
|
-
}): Record<string, unknown> {
|
|
248
|
-
const { sendID, target, content, contentType, replyToId } = params;
|
|
249
|
-
|
|
250
|
-
const payload: Record<string, unknown> = {
|
|
251
|
-
sendID,
|
|
252
|
-
recvID: target.type === "user" ? target.id : "",
|
|
253
|
-
groupID: target.type === "group" ? target.id : "",
|
|
254
|
-
content,
|
|
255
|
-
contentType,
|
|
256
|
-
sessionType: target.type === "group" ? SESSION_TYPES.GROUP : SESSION_TYPES.SINGLE,
|
|
257
|
-
isOnlineOnly: false,
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
if (replyToId) {
|
|
261
|
-
payload.quote = {
|
|
262
|
-
text: "",
|
|
263
|
-
content: {},
|
|
264
|
-
isReact: false,
|
|
265
|
-
userID: "",
|
|
266
|
-
msgID: replyToId,
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return payload;
|
|
271
|
-
}
|