@ascegu/teamily 1.0.24 → 1.0.26
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 +5 -3
- package/src/monitor.ts +36 -5
- package/src/upload.ts +42 -1
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -72,7 +72,7 @@ export const teamilyPlugin: ChannelPlugin<ResolvedTeamilyAccount> = {
|
|
|
72
72
|
},
|
|
73
73
|
agentPrompt: {
|
|
74
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.
|
|
75
|
+
"- To send a local file or image to the user, use MEDIA:./relative-path in your reply (e.g. MEDIA:./image.png or MEDIA:./data.json). The path must be relative to the workspace. Avoid absolute paths and ~ paths — they are blocked for security. If the user asks you to send a file outside the workspace (e.g. ~/.openclaw/openclaw.json), first copy it into the workspace (e.g. cp ~/.openclaw/openclaw.json ./openclaw.json), then use MEDIA:./openclaw.json to send it.",
|
|
76
76
|
],
|
|
77
77
|
},
|
|
78
78
|
reload: { configPrefixes: ["channels.teamily"] },
|
|
@@ -395,6 +395,7 @@ export const teamilyPlugin: ChannelPlugin<ResolvedTeamilyAccount> = {
|
|
|
395
395
|
|
|
396
396
|
// Send media attachments (first with caption, rest without).
|
|
397
397
|
const mediaUrls = resolveOutboundMediaUrls(payload as Record<string, unknown>);
|
|
398
|
+
let captionSent = false;
|
|
398
399
|
const sentMedia = await sendMediaWithLeadingCaption({
|
|
399
400
|
mediaUrls,
|
|
400
401
|
caption: replyText,
|
|
@@ -403,6 +404,7 @@ export const teamilyPlugin: ChannelPlugin<ResolvedTeamilyAccount> = {
|
|
|
403
404
|
// OpenIM doesn't support inline captions on media; send separately.
|
|
404
405
|
if (caption) {
|
|
405
406
|
await monitor.sendText(target, caption);
|
|
407
|
+
captionSent = true;
|
|
406
408
|
}
|
|
407
409
|
},
|
|
408
410
|
onError: (error) => {
|
|
@@ -410,8 +412,8 @@ export const teamilyPlugin: ChannelPlugin<ResolvedTeamilyAccount> = {
|
|
|
410
412
|
},
|
|
411
413
|
});
|
|
412
414
|
|
|
413
|
-
// If no media was sent, send text
|
|
414
|
-
if (!sentMedia && replyText) {
|
|
415
|
+
// If no media was sent (or media failed), send text as a plain message.
|
|
416
|
+
if ((!sentMedia || !captionSent) && replyText) {
|
|
415
417
|
log?.info?.(
|
|
416
418
|
`[${accountId}] Sending reply to: ${replyTarget} (isGroup=${isGroup})`,
|
|
417
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() {
|
|
@@ -217,8 +247,9 @@ export class TeamilyMonitor {
|
|
|
217
247
|
): Promise<string> {
|
|
218
248
|
const sdk = this.requireSdk();
|
|
219
249
|
const file = new File([new Uint8Array(buffer)], fileName, { type: contentType });
|
|
250
|
+
const uuid = crypto.randomUUID();
|
|
220
251
|
const picInfo = {
|
|
221
|
-
uuid
|
|
252
|
+
uuid,
|
|
222
253
|
type: contentType,
|
|
223
254
|
width: 0,
|
|
224
255
|
height: 0,
|
|
@@ -256,10 +287,10 @@ export class TeamilyMonitor {
|
|
|
256
287
|
duration: 0,
|
|
257
288
|
videoType: contentType,
|
|
258
289
|
snapshotPath: "",
|
|
259
|
-
videoUUID:
|
|
290
|
+
videoUUID: crypto.randomUUID(),
|
|
260
291
|
videoUrl: "",
|
|
261
292
|
videoSize: buffer.length,
|
|
262
|
-
snapshotUUID:
|
|
293
|
+
snapshotUUID: crypto.randomUUID(),
|
|
263
294
|
snapshotSize: 0,
|
|
264
295
|
snapshotUrl: "",
|
|
265
296
|
snapshotWidth: 0,
|
|
@@ -285,7 +316,7 @@ export class TeamilyMonitor {
|
|
|
285
316
|
const sdk = this.requireSdk();
|
|
286
317
|
const file = new File([new Uint8Array(buffer)], fileName, { type: contentType });
|
|
287
318
|
const created = await sdk.createSoundMessageByFile({
|
|
288
|
-
uuid:
|
|
319
|
+
uuid: crypto.randomUUID(),
|
|
289
320
|
soundPath: "",
|
|
290
321
|
sourceUrl: "",
|
|
291
322
|
dataSize: buffer.length,
|
|
@@ -312,7 +343,7 @@ export class TeamilyMonitor {
|
|
|
312
343
|
const created = await sdk.createFileMessageByFile({
|
|
313
344
|
filePath: "",
|
|
314
345
|
fileName,
|
|
315
|
-
uuid:
|
|
346
|
+
uuid: crypto.randomUUID(),
|
|
316
347
|
sourceUrl: "",
|
|
317
348
|
fileSize: buffer.length,
|
|
318
349
|
file,
|
package/src/upload.ts
CHANGED
|
@@ -12,7 +12,17 @@ export function detectMediaCategory(filePath: string): MediaCategory {
|
|
|
12
12
|
const ext = path.extname(filePath).toLowerCase();
|
|
13
13
|
if ([".mp4", ".mov", ".webm"].includes(ext)) return "video";
|
|
14
14
|
if ([".mp3", ".m4a", ".wav", ".ogg"].includes(ext)) return "audio";
|
|
15
|
-
if (
|
|
15
|
+
if (
|
|
16
|
+
[
|
|
17
|
+
".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx",
|
|
18
|
+
".zip", ".rar", ".7z", ".tar", ".gz", ".tgz",
|
|
19
|
+
".txt", ".csv", ".json", ".xml", ".yaml", ".yml",
|
|
20
|
+
".log", ".md", ".html", ".css", ".js", ".ts",
|
|
21
|
+
".py", ".go", ".rs", ".java", ".c", ".cpp", ".h",
|
|
22
|
+
".sh", ".bat", ".sql", ".toml", ".ini", ".cfg",
|
|
23
|
+
].includes(ext)
|
|
24
|
+
)
|
|
25
|
+
return "file";
|
|
16
26
|
return "image";
|
|
17
27
|
}
|
|
18
28
|
|
|
@@ -41,6 +51,37 @@ export function guessContentType(filePath: string): string {
|
|
|
41
51
|
".doc": "application/msword",
|
|
42
52
|
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
43
53
|
".zip": "application/zip",
|
|
54
|
+
".rar": "application/vnd.rar",
|
|
55
|
+
".7z": "application/x-7z-compressed",
|
|
56
|
+
".tar": "application/x-tar",
|
|
57
|
+
".gz": "application/gzip",
|
|
58
|
+
".tgz": "application/gzip",
|
|
59
|
+
".txt": "text/plain",
|
|
60
|
+
".csv": "text/csv",
|
|
61
|
+
".json": "application/json",
|
|
62
|
+
".xml": "application/xml",
|
|
63
|
+
".yaml": "text/yaml",
|
|
64
|
+
".yml": "text/yaml",
|
|
65
|
+
".log": "text/plain",
|
|
66
|
+
".md": "text/markdown",
|
|
67
|
+
".html": "text/html",
|
|
68
|
+
".css": "text/css",
|
|
69
|
+
".js": "application/javascript",
|
|
70
|
+
".ts": "application/typescript",
|
|
71
|
+
".py": "text/x-python",
|
|
72
|
+
".go": "text/x-go",
|
|
73
|
+
".rs": "text/x-rust",
|
|
74
|
+
".java": "text/x-java",
|
|
75
|
+
".c": "text/x-c",
|
|
76
|
+
".cpp": "text/x-c++",
|
|
77
|
+
".h": "text/x-c",
|
|
78
|
+
".sh": "text/x-shellscript",
|
|
79
|
+
".sql": "application/sql",
|
|
80
|
+
".toml": "application/toml",
|
|
81
|
+
".xls": "application/vnd.ms-excel",
|
|
82
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
83
|
+
".ppt": "application/vnd.ms-powerpoint",
|
|
84
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
44
85
|
};
|
|
45
86
|
return map[ext] ?? "application/octet-stream";
|
|
46
87
|
}
|