@dcrays/dcgchat-test 0.1.11 → 0.1.13

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcrays/dcgchat-test",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "description": "OpenClaw channel plugin for DCG Chat (WebSocket)",
6
6
  "main": "index.ts",
@@ -20,10 +20,8 @@
20
20
  "typecheck": "tsc --noEmit"
21
21
  },
22
22
  "dependencies": {
23
- "ali-oss": "file:src/libs/ali-oss-6.23.0.tgz",
24
23
  "ws": "file:src/libs/ws-8.19.0.tgz",
25
- "axios": "file:src/libs/axios-1.13.6.tgz",
26
- "md5": "file:src/libs/md5-2.3.0.tgz",
24
+ "mime-types": "file:src/libs/mime-types-3.0.2.tgz",
27
25
  "unzipper": "file:src/libs/unzipper-0.12.3.tgz"
28
26
  },
29
27
  "devDependencies": {
package/src/bot.ts CHANGED
@@ -1,10 +1,14 @@
1
1
  import path from "node:path";
2
+ import os from "node:os";
2
3
  import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
3
4
  import { createReplyPrefixContext } from "openclaw/plugin-sdk";
4
5
  import type { InboundMessage, OutboundReply } from "./types.js";
5
6
  import { getDcgchatRuntime } from "./runtime.js";
6
7
  import { resolveAccount } from "./channel.js";
7
8
  import { setMsgStatus } from "./tool.js";
9
+ import mime from "mime-types"
10
+
11
+ const targetPath = path.join(os.homedir(), '.openclaw');
8
12
 
9
13
  type MediaInfo = {
10
14
  path: string;
@@ -13,63 +17,26 @@ type MediaInfo = {
13
17
  };
14
18
 
15
19
  async function resolveMediaFromUrls(
16
- fileUrls: string[],
17
- maxBytes: number,
20
+ files: { url: string, name: string }[],
18
21
  log?: (msg: string) => void,
19
22
  ): Promise<MediaInfo[]> {
20
- const core = getDcgchatRuntime();
21
23
  const out: MediaInfo[] = [];
22
-
23
- log?.(`dcgchat media: starting resolve for ${fileUrls.length} file(s): ${JSON.stringify(fileUrls)}`);
24
-
25
- for (let i = 0; i < fileUrls.length; i++) {
26
- const url = fileUrls[i];
27
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] fetching ${url}`);
24
+ for (let i = 0; i < files.length; i++) {
25
+ const url = path.join(targetPath, files[i]?.url);
28
26
  try {
29
27
  const response = await fetch(url);
30
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] fetch response status=${response.status}, content-type=${response.headers.get("content-type")}, content-length=${response.headers.get("content-length")}`);
31
- if (!response.ok) {
32
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] fetch failed with HTTP ${response.status}, skipping`);
33
- continue;
34
- }
35
- const buffer = Buffer.from(await response.arrayBuffer());
36
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] downloaded buffer size=${buffer.length} bytes`);
37
-
38
- let contentType = response.headers.get("content-type") || "";
39
- if (!contentType) {
40
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] no content-type header, detecting mime...`);
41
- // @ts-ignore
42
- contentType = await core.media.detectMime({ buffer });
43
- }
44
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] resolved contentType=${contentType}`);
45
-
46
- const fileName = path.basename(new URL(url).pathname) || "file";
47
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] fileName=${fileName}, saving to disk (maxBytes=${maxBytes})...`);
48
-
49
- const saved = await core.channel.media.saveMediaBuffer(
50
- buffer,
51
- contentType,
52
- "inbound",
53
- maxBytes,
54
- fileName,
55
- );
56
-
28
+ const contentType = response.headers.get("content-type") || "";
57
29
  const isImage = contentType.startsWith("image/");
58
30
  out.push({
59
- path: saved.path,
31
+ path: url,
60
32
  // @ts-ignore
61
33
  contentType: saved.contentType,
62
34
  placeholder: isImage ? "<media:image>" : "<media:file>",
63
35
  });
64
-
65
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] saved to ${saved.path} (contentType=${saved.contentType}, isImage=${isImage})`);
66
36
  } catch (err) {
67
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] FAILED to process ${url}: ${String(err)}`);
37
+ log?.(`dcgchat media: [${i + 1}/${files.length}] FAILED to process ${url}: ${String(err)}`);
68
38
  }
69
39
  }
70
-
71
- log?.(`dcgchat media: resolve complete, ${out.length}/${fileUrls.length} file(s) succeeded`);
72
-
73
40
  return out;
74
41
  }
75
42
 
@@ -141,15 +108,20 @@ export async function handleDcgchatMessage(params: {
141
108
  });
142
109
 
143
110
  // 处理用户上传的文件
144
- const fileUrls = msg.content.file_urls ?? [];
145
- log(`dcgchat[${accountId}]: incoming message from user=${userId}, text="${text?.slice(0, 80)}", file_urls count=${fileUrls.length}`);
111
+ const files = msg.content.files ?? [];
146
112
  let mediaPayload: Record<string, unknown> = {};
147
- if (fileUrls.length > 0) {
148
- log(`dcgchat[${accountId}]: processing ${fileUrls.length} file(s): ${JSON.stringify(fileUrls)}`);
149
- const mediaMaxBytes = 30 * 1024 * 1024;
150
- const mediaList = await resolveMediaFromUrls(fileUrls, mediaMaxBytes, log);
113
+ if (files.length > 0) {
114
+ const mediaList = files?.map(item => {
115
+ const contentType = mime.lookup(item.name) || "application/octet-stream";
116
+ const isImage = contentType.startsWith("image/");
117
+ return {
118
+ path: path.join(targetPath, item?.url),
119
+ contentType: contentType,
120
+ placeholder: isImage ? "<media:image>" : "<media:file>",
121
+ }
122
+ });
151
123
  mediaPayload = buildMediaPayload(mediaList);
152
- log(`dcgchat[${accountId}]: media resolved ${mediaList.length}/${fileUrls.length} file(s), payload=${JSON.stringify(mediaPayload)}`);
124
+ log(`dcgchat[${accountId}]: media resolved ${mediaList.length}/${files.length} file(s), payload=${JSON.stringify(mediaList)}`);
153
125
  }
154
126
 
155
127
  const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
package/src/channel.ts CHANGED
@@ -1,11 +1,56 @@
1
+ import { copyFile, mkdir, rename, unlink } from "node:fs/promises";
2
+ import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
1
4
  import type { ChannelPlugin, OpenClawConfig } from "openclaw/plugin-sdk";
2
5
  import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk";
3
6
  import type { ResolvedDcgchatAccount, DcgchatConfig } from "./types.js";
4
7
  import { logDcgchat } from "./log.js";
5
8
  import { getWsConnection } from "./connection.js";
6
- import { ossUpload } from "./oss.js";
9
+ // import { ossUpload } from "./oss.js";
7
10
  import { getMsgParams } from "./tool.js";
8
11
 
12
+ const uploadRoot = resolve('/', "upload");
13
+
14
+ function isPathInside(parentPath: string, targetPath: string): boolean {
15
+ const relativePath = relative(parentPath, targetPath);
16
+ return relativePath === "" || (!relativePath.startsWith("..") && !isAbsolute(relativePath));
17
+ }
18
+
19
+ async function ensureMediaInUploadDir(url: string): Promise<string> {
20
+ if (!url || /^([a-z][a-z\d+\-.]*):\/\//i.test(url) || !isAbsolute(url)) {
21
+ return url;
22
+ }
23
+
24
+ const sourcePath = resolve(url);
25
+ if (isPathInside(uploadRoot, sourcePath)) {
26
+ return sourcePath;
27
+ }
28
+
29
+ const fileName = basename(sourcePath);
30
+ if (!fileName) {
31
+ return sourcePath;
32
+ }
33
+
34
+ const targetPath = resolve(uploadRoot, fileName);
35
+ if (targetPath === sourcePath) {
36
+ return targetPath;
37
+ }
38
+
39
+ await mkdir(uploadRoot, { recursive: true });
40
+
41
+ try {
42
+ await rename(sourcePath, targetPath);
43
+ } catch (error) {
44
+ if ((error as NodeJS.ErrnoException).code !== "EXDEV") {
45
+ throw error;
46
+ }
47
+ await copyFile(sourcePath, targetPath);
48
+ await unlink(sourcePath);
49
+ }
50
+
51
+ return targetPath;
52
+ }
53
+
9
54
  export function resolveAccount(cfg: OpenClawConfig, accountId?: string | null): ResolvedDcgchatAccount {
10
55
  const id = accountId ?? DEFAULT_ACCOUNT_ID;
11
56
  const raw = (cfg.channels?.["dcgchat"] as DcgchatConfig | undefined) ?? {};
@@ -98,14 +143,13 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
98
143
  // textChunkLimit: 25,
99
144
  textChunkLimit: 4000,
100
145
  sendText: async (ctx) => {
101
- const target = ctx.to || "(implicit)";
102
146
  const ws = getWsConnection()
103
147
  const params = getMsgParams();
104
148
  if (ws?.readyState === WebSocket.OPEN) {
105
149
  const {botToken} = resolveAccount(ctx.cfg, ctx.accountId);
106
150
  const content = {
107
151
  messageType: "openclaw_bot_chat",
108
- _userId: target,
152
+ _userId: params.userId,
109
153
  source: "client",
110
154
  content: {
111
155
  bot_token: botToken,
@@ -115,60 +159,65 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
115
159
  },
116
160
  };
117
161
  ws.send(JSON.stringify(content));
118
- logDcgchat.info(`dcgchat[${ctx.accountId}]: sendText to ${target}, ${JSON.stringify(content)}`);
162
+ logDcgchat.info(`dcgchat[${ctx.accountId}]: sendText to ${params.userId}, ${JSON.stringify(content)}`);
119
163
  } else {
120
164
  logDcgchat.warn(`[dcgchat][${ctx.accountId ?? DEFAULT_ACCOUNT_ID}] outbound -> ${ws?.readyState}: ${ctx.text}`);
121
165
  }
122
166
  return {
123
167
  channel: "dcgchat",
124
168
  messageId: `dcg-${Date.now()}`,
125
- chatId: target,
169
+ chatId: params.userId.toString(),
126
170
  };
127
171
  },
128
172
  sendMedia: async (ctx) => {
129
- const target = ctx.to || "(implicit)";
130
173
  const ws = getWsConnection()
131
174
  const params = getMsgParams();
175
+
132
176
  if (ws?.readyState === WebSocket.OPEN) {
133
177
  const {botToken} = resolveAccount(ctx.cfg, ctx.accountId);
134
- try {
135
- const url = ctx.mediaUrl ? await ossUpload(ctx.mediaUrl, botToken) : '';
136
- const fileName = ctx.mediaUrl?.split(/[\\/]/).pop() || ''
178
+
179
+ // try {
180
+ const url = ctx.mediaUrl;
181
+ const fileName = url?.split(/[\\/]/).pop() || ''
137
182
  const content = {
138
183
  messageType: "openclaw_bot_chat",
139
- _userId: target,
184
+ _userId: params.userId,
140
185
  source: "client",
141
186
  content: {
142
187
  bot_token: botToken,
143
- response: ctx.text + '\n' + `[${fileName}](${url})`,
188
+ response: ctx.text,
189
+ files: [{
190
+ url: url,
191
+ name: fileName,
192
+ }],
144
193
  session_id: params.sessionId,
145
- message_id: params.messageId ||Date.now().toString(),
146
- },
147
- };
148
- ws.send(JSON.stringify(content));
149
- logDcgchat.info(`dcgchat[${ctx.accountId}]: sendMedia to ${target}, ${JSON.stringify(content)}`);
150
- } catch (error) {
151
- const content = {
152
- messageType: "openclaw_bot_chat",
153
- _userId: target,
154
- source: "client",
155
- content: {
156
- bot_token: botToken,
157
- response: ctx.text + '\n' + ctx.mediaUrl,
158
- session_id: params.sessionId || Date.now().toString(),
159
- message_id: params.messageId ||Date.now().toString(),
194
+ message_id: params.messageId || Date.now().toString(),
160
195
  },
161
196
  };
162
197
  ws.send(JSON.stringify(content));
163
- logDcgchat.info(`dcgchat[${ctx.accountId}]: sendMedia to ${target}, ${JSON.stringify(content)}`);
164
- }
198
+ logDcgchat.info(`dcgchat[${ctx.accountId}]: agent sendMedia to ${params.userId}, ${JSON.stringify(content)}`);
199
+ // } catch (error) {
200
+ // const content = {
201
+ // messageType: "openclaw_bot_chat",
202
+ // _userId: target,
203
+ // source: "client",
204
+ // content: {
205
+ // bot_token: botToken,
206
+ // response: ctx.text + '\n' + ctx.mediaUrl,
207
+ // session_id: params.sessionId || Date.now().toString(),
208
+ // message_id: params.messageId ||Date.now().toString(),
209
+ // },
210
+ // };
211
+ // ws.send(JSON.stringify(content));
212
+ // logDcgchat.info(`dcgchat[${ctx.accountId}]: sendMedia to ${target}, ${JSON.stringify(content)}`);
213
+ // }
165
214
  } else {
166
215
  logDcgchat.warn(`[dcgchat][${ctx.accountId ?? DEFAULT_ACCOUNT_ID}] outbound -> ${ws?.readyState}: ${ctx.text}`);
167
216
  }
168
217
  return {
169
218
  channel: "dcgchat",
170
219
  messageId: `dcg-${Date.now()}`,
171
- chatId: target,
220
+ chatId: params.userId.toString(),
172
221
  };
173
222
  },
174
223
  },
Binary file
package/src/types.ts CHANGED
@@ -42,7 +42,10 @@ export type InboundMessage = {
42
42
  session_id: string;
43
43
  message_id: string;
44
44
  text: string;
45
- file_urls?: string[];
45
+ files?: {
46
+ url: string;
47
+ name: string;
48
+ }[];
46
49
  };
47
50
  };
48
51