@dcrays/dcgchat-test 0.1.9 → 0.1.10
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/README.md +83 -83
- package/index.ts +24 -24
- package/openclaw.plugin.json +9 -9
- package/package.json +58 -58
- package/src/api.ts +62 -62
- package/src/bot.ts +272 -272
- package/src/channel.ts +192 -192
- package/src/connection.ts +11 -11
- package/src/log.ts +46 -46
- package/src/monitor.ts +191 -190
- package/src/oss.ts +72 -72
- package/src/request.ts +194 -194
- package/src/runtime.ts +38 -38
- package/src/skill.ts +110 -110
- package/src/tool.ts +74 -64
- package/src/types.ts +103 -103
- package/src/userInfo.ts +97 -97
package/src/monitor.ts
CHANGED
|
@@ -1,190 +1,191 @@
|
|
|
1
|
-
import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
2
|
-
import WebSocket from "ws";
|
|
3
|
-
import { handleDcgchatMessage } from "./bot.js";
|
|
4
|
-
import { resolveAccount } from "./channel.js";
|
|
5
|
-
import { setWsConnection } from "./connection.js";
|
|
6
|
-
import type { InboundMessage, OutboundReply } from "./types.js";
|
|
7
|
-
import { setMsgParams } from "./tool.js";
|
|
8
|
-
import { installSkill, uninstallSkill } from "./skill.js";
|
|
9
|
-
|
|
10
|
-
export type MonitorDcgchatOpts = {
|
|
11
|
-
config?: ClawdbotConfig;
|
|
12
|
-
runtime?: RuntimeEnv;
|
|
13
|
-
abortSignal?: AbortSignal;
|
|
14
|
-
accountId?: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const RECONNECT_DELAY_MS = 3000;
|
|
18
|
-
const HEARTBEAT_INTERVAL_MS = 30_000;
|
|
19
|
-
|
|
20
|
-
function buildConnectUrl(account: Record<string, string>): string {
|
|
21
|
-
const { wsUrl, botToken, userId, domainId, appId } = account;
|
|
22
|
-
const url = new URL(wsUrl);
|
|
23
|
-
if (botToken) url.searchParams.set("bot_token", botToken);
|
|
24
|
-
if (userId) url.searchParams.set("_userId", userId);
|
|
25
|
-
url.searchParams.set("_domainId", domainId || "1000");
|
|
26
|
-
url.searchParams.set("_appId", appId || "100");
|
|
27
|
-
return url.toString();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export async function monitorDcgchatProvider(opts: MonitorDcgchatOpts): Promise<void> {
|
|
31
|
-
const { config, runtime, abortSignal, accountId } = opts;
|
|
32
|
-
// @ts-ignore
|
|
33
|
-
const cfg = config ?? (runtime?.config?.() as ClawdbotConfig | undefined);
|
|
34
|
-
if (!cfg) {
|
|
35
|
-
runtime?.error?.("dcgchat: no config available");
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const account = resolveAccount(cfg, accountId ?? "default");
|
|
40
|
-
const log = runtime?.log ?? console.log;
|
|
41
|
-
const err = runtime?.error ?? console.error;
|
|
42
|
-
|
|
43
|
-
if (!account.wsUrl) {
|
|
44
|
-
err(`dcgchat[${account.accountId}]: wsUrl not configured`);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
let shouldReconnect = true;
|
|
49
|
-
let ws: WebSocket | null = null;
|
|
50
|
-
let heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
|
51
|
-
|
|
52
|
-
const stopHeartbeat = () => {
|
|
53
|
-
if (heartbeatTimer) {
|
|
54
|
-
clearInterval(heartbeatTimer);
|
|
55
|
-
heartbeatTimer = null;
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const startHeartbeat = () => {
|
|
60
|
-
stopHeartbeat();
|
|
61
|
-
heartbeatTimer = setInterval(() => {
|
|
62
|
-
if (ws?.readyState === WebSocket.OPEN) {
|
|
63
|
-
const heartbeat = {
|
|
64
|
-
messageType: "openclaw_bot_heartbeat",
|
|
65
|
-
_userId: Number(account.userId) || 0,
|
|
66
|
-
source: "client",
|
|
67
|
-
content: {
|
|
68
|
-
bot_token: account.botToken,
|
|
69
|
-
status: "1",
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
ws.send(JSON.stringify(heartbeat));
|
|
73
|
-
// log(`dcgchat[${account.accountId}]: heartbeat sent, ${JSON.stringify(heartbeat)}`);
|
|
74
|
-
}
|
|
75
|
-
}, HEARTBEAT_INTERVAL_MS);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const connect = () => {
|
|
79
|
-
if (!shouldReconnect) return;
|
|
80
|
-
|
|
81
|
-
const connectUrl = buildConnectUrl(account as Record<string, any>);
|
|
82
|
-
log(`dcgchat[${account.accountId}]: connecting to ${connectUrl}`);
|
|
83
|
-
ws = new WebSocket(connectUrl);
|
|
84
|
-
|
|
85
|
-
ws.on("open", () => {
|
|
86
|
-
log(`dcgchat[${account.accountId}]: connected`);
|
|
87
|
-
setWsConnection(ws);
|
|
88
|
-
startHeartbeat();
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
ws.on("message", async (data) => {
|
|
92
|
-
log(`dcgchat[${account.accountId}]: on message, ${data.toString()}`);
|
|
93
|
-
let parsed: { messageType?: string; content: any };
|
|
94
|
-
try {
|
|
95
|
-
parsed = JSON.parse(data.toString());
|
|
96
|
-
} catch {
|
|
97
|
-
err(`dcgchat[${account.accountId}]: invalid JSON received`);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (parsed.messageType === "openclaw_bot_heartbeat") {
|
|
102
|
-
log(`dcgchat[${account.accountId}]: heartbeat ack received, ${data.toString()}`);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
try {
|
|
106
|
-
parsed.content = JSON.parse(parsed.content);
|
|
107
|
-
} catch {
|
|
108
|
-
err(`dcgchat[${account.accountId}]: invalid JSON received`);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (parsed.messageType == "openclaw_bot_chat") {
|
|
113
|
-
const msg = parsed as unknown as InboundMessage;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
1
|
+
import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
2
|
+
import WebSocket from "ws";
|
|
3
|
+
import { handleDcgchatMessage } from "./bot.js";
|
|
4
|
+
import { resolveAccount } from "./channel.js";
|
|
5
|
+
import { setWsConnection } from "./connection.js";
|
|
6
|
+
import type { InboundMessage, OutboundReply } from "./types.js";
|
|
7
|
+
import { setMsgParams,setMsgStatus } from "./tool.js";
|
|
8
|
+
import { installSkill, uninstallSkill } from "./skill.js";
|
|
9
|
+
|
|
10
|
+
export type MonitorDcgchatOpts = {
|
|
11
|
+
config?: ClawdbotConfig;
|
|
12
|
+
runtime?: RuntimeEnv;
|
|
13
|
+
abortSignal?: AbortSignal;
|
|
14
|
+
accountId?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const RECONNECT_DELAY_MS = 3000;
|
|
18
|
+
const HEARTBEAT_INTERVAL_MS = 30_000;
|
|
19
|
+
|
|
20
|
+
function buildConnectUrl(account: Record<string, string>): string {
|
|
21
|
+
const { wsUrl, botToken, userId, domainId, appId } = account;
|
|
22
|
+
const url = new URL(wsUrl);
|
|
23
|
+
if (botToken) url.searchParams.set("bot_token", botToken);
|
|
24
|
+
if (userId) url.searchParams.set("_userId", userId);
|
|
25
|
+
url.searchParams.set("_domainId", domainId || "1000");
|
|
26
|
+
url.searchParams.set("_appId", appId || "100");
|
|
27
|
+
return url.toString();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function monitorDcgchatProvider(opts: MonitorDcgchatOpts): Promise<void> {
|
|
31
|
+
const { config, runtime, abortSignal, accountId } = opts;
|
|
32
|
+
// @ts-ignore
|
|
33
|
+
const cfg = config ?? (runtime?.config?.() as ClawdbotConfig | undefined);
|
|
34
|
+
if (!cfg) {
|
|
35
|
+
runtime?.error?.("dcgchat: no config available");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const account = resolveAccount(cfg, accountId ?? "default");
|
|
40
|
+
const log = runtime?.log ?? console.log;
|
|
41
|
+
const err = runtime?.error ?? console.error;
|
|
42
|
+
|
|
43
|
+
if (!account.wsUrl) {
|
|
44
|
+
err(`dcgchat[${account.accountId}]: wsUrl not configured`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let shouldReconnect = true;
|
|
49
|
+
let ws: WebSocket | null = null;
|
|
50
|
+
let heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
|
51
|
+
|
|
52
|
+
const stopHeartbeat = () => {
|
|
53
|
+
if (heartbeatTimer) {
|
|
54
|
+
clearInterval(heartbeatTimer);
|
|
55
|
+
heartbeatTimer = null;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const startHeartbeat = () => {
|
|
60
|
+
stopHeartbeat();
|
|
61
|
+
heartbeatTimer = setInterval(() => {
|
|
62
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
63
|
+
const heartbeat = {
|
|
64
|
+
messageType: "openclaw_bot_heartbeat",
|
|
65
|
+
_userId: Number(account.userId) || 0,
|
|
66
|
+
source: "client",
|
|
67
|
+
content: {
|
|
68
|
+
bot_token: account.botToken,
|
|
69
|
+
status: "1",
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
ws.send(JSON.stringify(heartbeat));
|
|
73
|
+
// log(`dcgchat[${account.accountId}]: heartbeat sent, ${JSON.stringify(heartbeat)}`);
|
|
74
|
+
}
|
|
75
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const connect = () => {
|
|
79
|
+
if (!shouldReconnect) return;
|
|
80
|
+
|
|
81
|
+
const connectUrl = buildConnectUrl(account as Record<string, any>);
|
|
82
|
+
log(`dcgchat[${account.accountId}]: connecting to ${connectUrl}`);
|
|
83
|
+
ws = new WebSocket(connectUrl);
|
|
84
|
+
|
|
85
|
+
ws.on("open", () => {
|
|
86
|
+
log(`dcgchat[${account.accountId}]: connected`);
|
|
87
|
+
setWsConnection(ws);
|
|
88
|
+
startHeartbeat();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
ws.on("message", async (data) => {
|
|
92
|
+
log(`dcgchat[${account.accountId}]: on message, ${data.toString()}`);
|
|
93
|
+
let parsed: { messageType?: string; content: any };
|
|
94
|
+
try {
|
|
95
|
+
parsed = JSON.parse(data.toString());
|
|
96
|
+
} catch {
|
|
97
|
+
err(`dcgchat[${account.accountId}]: invalid JSON received`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (parsed.messageType === "openclaw_bot_heartbeat") {
|
|
102
|
+
log(`dcgchat[${account.accountId}]: heartbeat ack received, ${data.toString()}`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
parsed.content = JSON.parse(parsed.content);
|
|
107
|
+
} catch {
|
|
108
|
+
err(`dcgchat[${account.accountId}]: invalid JSON received`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (parsed.messageType == "openclaw_bot_chat") {
|
|
113
|
+
const msg = parsed as unknown as InboundMessage;
|
|
114
|
+
setMsgStatus('running');
|
|
115
|
+
setMsgParams({
|
|
116
|
+
userId: msg._userId,
|
|
117
|
+
token: msg.content.bot_token,
|
|
118
|
+
sessionId: msg.content.session_id,
|
|
119
|
+
messageId: msg.content.message_id,
|
|
120
|
+
});
|
|
121
|
+
await handleDcgchatMessage({
|
|
122
|
+
cfg,
|
|
123
|
+
msg,
|
|
124
|
+
accountId: account.accountId,
|
|
125
|
+
runtime,
|
|
126
|
+
onChunk: (reply) => {
|
|
127
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
128
|
+
const res = { ...reply, content: JSON.stringify(reply.content) };
|
|
129
|
+
ws.send(JSON.stringify(res));
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
} else if (parsed.messageType == "openclaw_bot_event") {
|
|
134
|
+
const { event_type, operation_type, skill_url, skill_code, skill_id, bot_token, websocket_trace_id } = parsed.content ? parsed.content : {} as Record<string, any>;
|
|
135
|
+
const content = { event_type, operation_type, skill_url, skill_code, skill_id, bot_token, websocket_trace_id };
|
|
136
|
+
if (event_type === "skill") {
|
|
137
|
+
if (operation_type === "install" || operation_type === "enable") {
|
|
138
|
+
installSkill({ path: skill_url, code: skill_code }, content);
|
|
139
|
+
} else if (operation_type === "remove" || operation_type === "disable") {
|
|
140
|
+
uninstallSkill({ code: skill_code }, content);
|
|
141
|
+
} else {
|
|
142
|
+
log(`dcgchat[${account.accountId}]: openclaw_bot_event unknown event_type: ${event_type}, ${data.toString()}`);
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
log(`dcgchat[${account.accountId}]: openclaw_bot_event unknown operation_type: ${operation_type}, ${data.toString()}`);
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
log(`dcgchat[${account.accountId}]: ignoring unknown messageType: ${parsed.messageType}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
ws.on("close", (code, reason) => {
|
|
154
|
+
stopHeartbeat();
|
|
155
|
+
setWsConnection(null);
|
|
156
|
+
log(
|
|
157
|
+
`dcgchat[${account.accountId}]: disconnected (code=${code}, reason=${reason?.toString() || ""})`,
|
|
158
|
+
);
|
|
159
|
+
if (shouldReconnect) {
|
|
160
|
+
log(`dcgchat[${account.accountId}]: reconnecting in ${RECONNECT_DELAY_MS}ms...`);
|
|
161
|
+
setTimeout(connect, RECONNECT_DELAY_MS);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
ws.on("error", (e) => {
|
|
166
|
+
err(`dcgchat[${account.accountId}]: WebSocket error: ${String(e)}`);
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
connect();
|
|
171
|
+
|
|
172
|
+
await new Promise<void>((resolve) => {
|
|
173
|
+
if (abortSignal?.aborted) {
|
|
174
|
+
shouldReconnect = false;
|
|
175
|
+
ws?.close();
|
|
176
|
+
resolve();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
abortSignal?.addEventListener(
|
|
180
|
+
"abort",
|
|
181
|
+
() => {
|
|
182
|
+
log(`dcgchat[${account.accountId}]: stopping`);
|
|
183
|
+
stopHeartbeat();
|
|
184
|
+
shouldReconnect = false;
|
|
185
|
+
ws?.close();
|
|
186
|
+
resolve();
|
|
187
|
+
},
|
|
188
|
+
{ once: true },
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
}
|
package/src/oss.ts
CHANGED
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
import { createReadStream } from "node:fs";
|
|
2
|
-
import OSS from "ali-oss";
|
|
3
|
-
import { getStsToken, getUserToken } from "./api";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
/** 将 File/路径/Buffer 转为 ali-oss put 所需的 Buffer 或 ReadableStream */
|
|
7
|
-
async function toUploadContent(
|
|
8
|
-
input: File | string | Buffer,
|
|
9
|
-
): Promise<{ content: Buffer | ReturnType<typeof createReadStream>; fileName: string }> {
|
|
10
|
-
if (Buffer.isBuffer(input)) {
|
|
11
|
-
return { content: input, fileName: "file" };
|
|
12
|
-
}
|
|
13
|
-
if (typeof input === "string") {
|
|
14
|
-
return {
|
|
15
|
-
content: createReadStream(input),
|
|
16
|
-
fileName: input.split("/").pop() ?? "file",
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
// File: ali-oss 需要 Buffer/Stream,用 arrayBuffer 转 Buffer
|
|
20
|
-
const buf = Buffer.from(await input.arrayBuffer());
|
|
21
|
-
return { content: buf, fileName: input.name };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const ossUpload = async (file: File | string | Buffer, botToken: string) => {
|
|
25
|
-
await getUserToken(botToken);
|
|
26
|
-
|
|
27
|
-
const { content, fileName } = await toUploadContent(file);
|
|
28
|
-
const data = await getStsToken(fileName, botToken);
|
|
29
|
-
|
|
30
|
-
const options: OSS.Options = {
|
|
31
|
-
// 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
|
|
32
|
-
accessKeyId: data.tempAccessKeyId,
|
|
33
|
-
accessKeySecret: data.tempAccessKeySecret,
|
|
34
|
-
// 从STS服务获取的安全令牌(SecurityToken)。
|
|
35
|
-
stsToken: data.tempSecurityToken,
|
|
36
|
-
// 填写Bucket名称。
|
|
37
|
-
bucket: data.bucket,
|
|
38
|
-
endpoint: data.endPoint,
|
|
39
|
-
region: data.region,
|
|
40
|
-
secure: true,
|
|
41
|
-
// refreshSTSToken: async () => {
|
|
42
|
-
// const tokenResponse = await getStsToken(fileName);
|
|
43
|
-
// return {
|
|
44
|
-
// accessKeyId: tokenResponse.tempAccessKeyId,
|
|
45
|
-
// accessKeySecret: tokenResponse.tempAccessKeySecret,
|
|
46
|
-
// stsToken: tokenResponse.tempSecurityToken,
|
|
47
|
-
// }
|
|
48
|
-
// },
|
|
49
|
-
// // 5 seconds
|
|
50
|
-
// refreshSTSTokenInterval: 5 * 1000,
|
|
51
|
-
// // // 5 minutes
|
|
52
|
-
// // refreshSTSTokenInterval: 5 * 60 * 1000,
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const client = new OSS(options);
|
|
56
|
-
|
|
57
|
-
const name = `${data.uploadDir}${data.ossFileKey}`;
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
const objectResult = await client.put(name, content);
|
|
61
|
-
if (objectResult?.res?.status !== 200) {
|
|
62
|
-
throw new Error("OSS 上传失败");
|
|
63
|
-
}
|
|
64
|
-
console.log(objectResult.url);
|
|
65
|
-
// const url = `${data.protocol || 'http'}://${data.bucket}.${data.endPoint}/${data.uploadDir}${data.ossFileKey}`
|
|
66
|
-
return objectResult.url;
|
|
67
|
-
} catch (error) {
|
|
68
|
-
console.error("OSS 上传失败:", error);
|
|
69
|
-
throw error;
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
1
|
+
import { createReadStream } from "node:fs";
|
|
2
|
+
import OSS from "ali-oss";
|
|
3
|
+
import { getStsToken, getUserToken } from "./api";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/** 将 File/路径/Buffer 转为 ali-oss put 所需的 Buffer 或 ReadableStream */
|
|
7
|
+
async function toUploadContent(
|
|
8
|
+
input: File | string | Buffer,
|
|
9
|
+
): Promise<{ content: Buffer | ReturnType<typeof createReadStream>; fileName: string }> {
|
|
10
|
+
if (Buffer.isBuffer(input)) {
|
|
11
|
+
return { content: input, fileName: "file" };
|
|
12
|
+
}
|
|
13
|
+
if (typeof input === "string") {
|
|
14
|
+
return {
|
|
15
|
+
content: createReadStream(input),
|
|
16
|
+
fileName: input.split("/").pop() ?? "file",
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
// File: ali-oss 需要 Buffer/Stream,用 arrayBuffer 转 Buffer
|
|
20
|
+
const buf = Buffer.from(await input.arrayBuffer());
|
|
21
|
+
return { content: buf, fileName: input.name };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const ossUpload = async (file: File | string | Buffer, botToken: string) => {
|
|
25
|
+
await getUserToken(botToken);
|
|
26
|
+
|
|
27
|
+
const { content, fileName } = await toUploadContent(file);
|
|
28
|
+
const data = await getStsToken(fileName, botToken);
|
|
29
|
+
|
|
30
|
+
const options: OSS.Options = {
|
|
31
|
+
// 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
|
|
32
|
+
accessKeyId: data.tempAccessKeyId,
|
|
33
|
+
accessKeySecret: data.tempAccessKeySecret,
|
|
34
|
+
// 从STS服务获取的安全令牌(SecurityToken)。
|
|
35
|
+
stsToken: data.tempSecurityToken,
|
|
36
|
+
// 填写Bucket名称。
|
|
37
|
+
bucket: data.bucket,
|
|
38
|
+
endpoint: data.endPoint,
|
|
39
|
+
region: data.region,
|
|
40
|
+
secure: true,
|
|
41
|
+
// refreshSTSToken: async () => {
|
|
42
|
+
// const tokenResponse = await getStsToken(fileName);
|
|
43
|
+
// return {
|
|
44
|
+
// accessKeyId: tokenResponse.tempAccessKeyId,
|
|
45
|
+
// accessKeySecret: tokenResponse.tempAccessKeySecret,
|
|
46
|
+
// stsToken: tokenResponse.tempSecurityToken,
|
|
47
|
+
// }
|
|
48
|
+
// },
|
|
49
|
+
// // 5 seconds
|
|
50
|
+
// refreshSTSTokenInterval: 5 * 1000,
|
|
51
|
+
// // // 5 minutes
|
|
52
|
+
// // refreshSTSTokenInterval: 5 * 60 * 1000,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const client = new OSS(options);
|
|
56
|
+
|
|
57
|
+
const name = `${data.uploadDir}${data.ossFileKey}`;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const objectResult = await client.put(name, content);
|
|
61
|
+
if (objectResult?.res?.status !== 200) {
|
|
62
|
+
throw new Error("OSS 上传失败");
|
|
63
|
+
}
|
|
64
|
+
console.log(objectResult.url);
|
|
65
|
+
// const url = `${data.protocol || 'http'}://${data.bucket}.${data.endPoint}/${data.uploadDir}${data.ossFileKey}`
|
|
66
|
+
return objectResult.url;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error("OSS 上传失败:", error);
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|