openclaw-channel-dmwork 0.3.0 → 0.3.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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/inbound.ts +55 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-channel-dmwork",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "DMWork channel plugin for OpenClaw via WuKongIM WebSocket",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -49,4 +49,4 @@
49
49
  "curve25519-js",
50
50
  "md5-typescript"
51
51
  ]
52
- }
52
+ }
package/src/inbound.ts CHANGED
@@ -138,22 +138,36 @@ function resolveContent(payload: BotMessage["payload"], apiUrl?: string): Resolv
138
138
  const makeFullUrl = (relUrl?: string) => {
139
139
  if (!relUrl) return undefined;
140
140
  if (relUrl.startsWith("http")) return relUrl;
141
- return `${apiUrl?.replace(/\/+$/, "")}/v1/bot/file/${relUrl}`;
141
+ // Build public file URL: strip /api suffix from apiUrl, strip "preview/" from path
142
+ // e.g. "file/preview/chat/xxx/img.jpg" → "https://host/file/chat/xxx/img.jpg"
143
+ const baseUrl = apiUrl?.replace(/\/+$/, "").replace(/\/api$/i, "") ?? "";
144
+ const cleanPath = relUrl.replace(/^file\/preview\//, "file/");
145
+ return `${baseUrl}/${cleanPath}`;
142
146
  };
143
147
 
144
148
  switch (payload.type) {
145
149
  case MessageType.Text:
146
150
  return { text: payload.content ?? "" };
147
- case MessageType.Image:
148
- return { text: "[图片]", mediaUrl: makeFullUrl(payload.url), mediaType: "image" };
149
- case MessageType.GIF:
150
- return { text: "[GIF]", mediaUrl: makeFullUrl(payload.url), mediaType: "image" };
151
- case MessageType.Voice:
152
- return { text: "[语音消息]", mediaUrl: makeFullUrl(payload.url), mediaType: "audio" };
153
- case MessageType.Video:
154
- return { text: "[视频]", mediaUrl: makeFullUrl(payload.url), mediaType: "video" };
155
- case MessageType.File:
156
- return { text: `[文件: ${payload.name ?? "未知文件"}]`, mediaUrl: makeFullUrl(payload.url), mediaType: "file" };
151
+ case MessageType.Image: {
152
+ const imgUrl = makeFullUrl(payload.url);
153
+ return { text: `[图片]\n${imgUrl ?? ""}`.trim(), mediaUrl: imgUrl, mediaType: "image" };
154
+ }
155
+ case MessageType.GIF: {
156
+ const gifUrl = makeFullUrl(payload.url);
157
+ return { text: `[GIF]\n${gifUrl ?? ""}`.trim(), mediaUrl: gifUrl, mediaType: "image" };
158
+ }
159
+ case MessageType.Voice: {
160
+ const voiceUrl = makeFullUrl(payload.url);
161
+ return { text: `[语音消息]\n${voiceUrl ?? ""}`.trim(), mediaUrl: voiceUrl, mediaType: "audio" };
162
+ }
163
+ case MessageType.Video: {
164
+ const videoUrl = makeFullUrl(payload.url);
165
+ return { text: `[视频]\n${videoUrl ?? ""}`.trim(), mediaUrl: videoUrl, mediaType: "video" };
166
+ }
167
+ case MessageType.File: {
168
+ const fileUrl = makeFullUrl(payload.url);
169
+ return { text: `[文件: ${payload.name ?? "未知文件"}]\n${fileUrl ?? ""}`.trim(), mediaUrl: fileUrl, mediaType: "file" };
170
+ }
157
171
  case MessageType.Location: {
158
172
  const lat = payload.latitude ?? payload.lat;
159
173
  const lng = payload.longitude ?? payload.lng ?? payload.lon;
@@ -181,6 +195,25 @@ const TEXT_FILE_EXTENSIONS = new Set([
181
195
  "log", "py", "js", "ts", "go", "java",
182
196
  ]);
183
197
 
198
+ /** Fetch an authenticated URL and return a base64 data URL */
199
+ async function fetchAsDataUrl(
200
+ url: string,
201
+ botToken: string,
202
+ ): Promise<string | null> {
203
+ try {
204
+ const resp = await fetch(url, {
205
+ headers: { Authorization: `Bearer ${botToken}` },
206
+ signal: AbortSignal.timeout(30_000),
207
+ });
208
+ if (!resp.ok) return null;
209
+ const contentType = resp.headers.get("content-type") || "application/octet-stream";
210
+ const buffer = Buffer.from(await resp.arrayBuffer());
211
+ return `data:${contentType};base64,${buffer.toString("base64")}`;
212
+ } catch {
213
+ return null;
214
+ }
215
+ }
216
+
184
217
  async function resolveFileContent(
185
218
  url: string,
186
219
  botToken: string,
@@ -385,6 +418,17 @@ export async function handleInboundMessage(params: {
385
418
  }
386
419
  }
387
420
 
421
+ // Convert authenticated media URLs to base64 data URLs so the Agent can access them
422
+ if (inboundMediaUrl && !inboundMediaUrl.startsWith("data:")) {
423
+ const dataUrl = await fetchAsDataUrl(inboundMediaUrl, account.config.botToken ?? "");
424
+ if (dataUrl) {
425
+ log?.info?.(`dmwork: converted media URL to base64 data URL (${resolved.mediaType})`);
426
+ inboundMediaUrl = dataUrl;
427
+ } else {
428
+ log?.warn?.(`dmwork: failed to convert media URL to base64, keeping original`);
429
+ }
430
+ }
431
+
388
432
  if (!rawBody) {
389
433
  log?.info?.(
390
434
  `dmwork: inbound dropped session=${sessionId} reason=empty-content`,