@dcrays/dcgchat-test 0.4.27 → 0.4.28
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 +1 -1
- package/src/request/oss.ts +81 -13
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -222,7 +222,7 @@ export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<v
|
|
|
222
222
|
const notMessageId = `${msgCtx?.messageId}`?.length === 13 || !msgCtx?.messageId
|
|
223
223
|
try {
|
|
224
224
|
const botToken = msgCtx.botToken ?? getOpenClawConfig()?.channels?.["dcgchat-test"]?.botToken ?? ''
|
|
225
|
-
const url =
|
|
225
|
+
const url = mediaUrl ? await ossUpload(mediaUrl, botToken, 1) : ''
|
|
226
226
|
if (!msgCtx.agentId) {
|
|
227
227
|
msgCtx.agentId = agentId
|
|
228
228
|
}
|
package/src/request/oss.ts
CHANGED
|
@@ -1,32 +1,89 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fileURLToPath } from 'node:url'
|
|
2
2
|
// @ts-ignore
|
|
3
3
|
import OSS from 'ali-oss'
|
|
4
4
|
import { getStsToken, getUserToken } from './api.js'
|
|
5
5
|
import { dcgLogger } from '../utils/log.js'
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
/** 分片大小:OSS 要求每片 ≥100 KB(最后一片可更小) */
|
|
8
|
+
const MULTIPART_PART_SIZE = 1024 * 1024
|
|
9
|
+
|
|
10
|
+
/** ali-oss 默认 timeout 为 60s,大文件单 PUT 或慢网易触发 ResponseTimeoutError */
|
|
11
|
+
const OSS_HTTP_TIMEOUT_MS = 10 * 60 * 1000
|
|
12
|
+
|
|
13
|
+
/** 归一化入参,避免 file://、包装对象、TypedArray 等导致 SDK 识别失败 */
|
|
14
|
+
function coerceOssFileInput(input: File | string | Buffer): File | string | Buffer {
|
|
15
|
+
if (typeof input === 'string') {
|
|
16
|
+
const t = input.trim()
|
|
17
|
+
if (t.startsWith('file:')) {
|
|
18
|
+
try {
|
|
19
|
+
return fileURLToPath(t)
|
|
20
|
+
} catch {
|
|
21
|
+
return input
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return input
|
|
25
|
+
}
|
|
26
|
+
if (Buffer.isBuffer(input)) {
|
|
27
|
+
return input
|
|
28
|
+
}
|
|
29
|
+
if (input && typeof input === 'object') {
|
|
30
|
+
if (ArrayBuffer.isView(input) && !(input instanceof DataView) && !Buffer.isBuffer(input)) {
|
|
31
|
+
const v = input as ArrayBufferView
|
|
32
|
+
return Buffer.from(v.buffer, v.byteOffset, v.byteLength)
|
|
33
|
+
}
|
|
34
|
+
const o = input as unknown as Record<string, unknown>
|
|
35
|
+
const p = o.path ?? o.filePath
|
|
36
|
+
if (typeof p === 'string' && p.trim()) return p.trim()
|
|
37
|
+
}
|
|
38
|
+
return input
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function resolveMime(input: File | string | Buffer): string {
|
|
42
|
+
if (typeof input !== 'string' && !Buffer.isBuffer(input) && input.type) {
|
|
43
|
+
return input.type
|
|
44
|
+
}
|
|
45
|
+
return 'application/octet-stream'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 将 File/路径/Buffer 转为 ali-oss 接受的类型。
|
|
50
|
+
* 本地路径保持为字符串:put 内部用 contentLength + ReadStream,大文件也稳定。
|
|
51
|
+
*/
|
|
52
|
+
async function toUploadContent(input: File | string | Buffer): Promise<{ content: Buffer | string; fileName: string }> {
|
|
11
53
|
if (Buffer.isBuffer(input)) {
|
|
12
54
|
return { content: input, fileName: 'file' }
|
|
13
55
|
}
|
|
14
56
|
if (typeof input === 'string') {
|
|
15
57
|
return {
|
|
16
|
-
content:
|
|
17
|
-
fileName: input.split(
|
|
58
|
+
content: input,
|
|
59
|
+
fileName: input.split(/[/\\]/).pop() ?? 'file'
|
|
18
60
|
}
|
|
19
61
|
}
|
|
20
|
-
// File: ali-oss 需要 Buffer/Stream,用 arrayBuffer 转 Buffer
|
|
21
62
|
const buf = Buffer.from(await input.arrayBuffer())
|
|
22
|
-
|
|
63
|
+
const n = (input as { name?: string }).name
|
|
64
|
+
return { content: buf, fileName: typeof n === 'string' && n ? n : 'file' }
|
|
23
65
|
}
|
|
24
66
|
|
|
25
|
-
export
|
|
67
|
+
export type OssUploadOptions = {
|
|
68
|
+
/** 分片上传进度,p 为 0~1(仅大 Buffer 分片时触发) */
|
|
69
|
+
onProgress?: (p: number) => void
|
|
70
|
+
/** HTTP 超时(毫秒),覆盖默认 15 分钟;可传 `30 * 60 * 1000` 等 */
|
|
71
|
+
timeoutMs?: number
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const ossUpload = async (
|
|
75
|
+
rawFile: File | string | Buffer,
|
|
76
|
+
botToken: string,
|
|
77
|
+
isPrivate: 0 | 1 = 1,
|
|
78
|
+
uploadOptions?: OssUploadOptions
|
|
79
|
+
) => {
|
|
26
80
|
await getUserToken(botToken)
|
|
27
81
|
|
|
82
|
+
const file = coerceOssFileInput(rawFile)
|
|
28
83
|
const { content, fileName } = await toUploadContent(file)
|
|
29
84
|
const data = await getStsToken(fileName, botToken, isPrivate)
|
|
85
|
+
const mime = resolveMime(file)
|
|
86
|
+
const onProgress = uploadOptions?.onProgress
|
|
30
87
|
|
|
31
88
|
const options: OSS.Options = {
|
|
32
89
|
// 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
|
|
@@ -40,7 +97,8 @@ export const ossUpload = async (file: File | string | Buffer, botToken: string,
|
|
|
40
97
|
region: data.region,
|
|
41
98
|
secure: true,
|
|
42
99
|
cname: true,
|
|
43
|
-
authorizationV4: true
|
|
100
|
+
authorizationV4: true,
|
|
101
|
+
timeout: uploadOptions?.timeoutMs ?? OSS_HTTP_TIMEOUT_MS
|
|
44
102
|
}
|
|
45
103
|
|
|
46
104
|
const client = new OSS(options)
|
|
@@ -48,12 +106,22 @@ export const ossUpload = async (file: File | string | Buffer, botToken: string,
|
|
|
48
106
|
const name = `${data.uploadDir}${data.ossFileKey}`
|
|
49
107
|
|
|
50
108
|
try {
|
|
51
|
-
|
|
109
|
+
let objectResult: OSS.PutObjectResult | OSS.CompleteMultipartUploadResult
|
|
110
|
+
|
|
111
|
+
const multipartUploadOptions: OSS.MultipartUploadOptions = {
|
|
112
|
+
progress: (p: number) => {
|
|
113
|
+
onProgress?.(p)
|
|
114
|
+
},
|
|
115
|
+
parallel: 4,
|
|
116
|
+
partSize: MULTIPART_PART_SIZE,
|
|
117
|
+
mime
|
|
118
|
+
}
|
|
119
|
+
objectResult = await client.multipartUpload(name, content, multipartUploadOptions)
|
|
120
|
+
|
|
52
121
|
if (objectResult?.res?.status !== 200) {
|
|
53
122
|
dcgLogger(`OSS 上传失败, ${objectResult?.res?.status}`)
|
|
54
123
|
}
|
|
55
124
|
dcgLogger(`OSS 上传成功, ${objectResult.name || objectResult.url}`)
|
|
56
|
-
// const url = `${data.protocol || 'http'}://${data.bucket}.${data.endPoint}/${data.uploadDir}${data.ossFileKey}`
|
|
57
125
|
return isPrivate === 1 ? objectResult.name || objectResult.url : objectResult.url
|
|
58
126
|
} catch (error) {
|
|
59
127
|
dcgLogger(`OSS 上传失败: ${error}`, 'error')
|