@qwen-code/channel-dingtalk 0.21.0-preview.0
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/dist/DingtalkAdapter.d.ts +102 -0
- package/dist/DingtalkAdapter.js +1001 -0
- package/dist/DingtalkAdapter.js.map +1 -0
- package/dist/DingtalkConnectionManager.d.ts +54 -0
- package/dist/DingtalkConnectionManager.js +317 -0
- package/dist/DingtalkConnectionManager.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown.d.ts +12 -0
- package/dist/markdown.js +93 -0
- package/dist/markdown.js.map +1 -0
- package/dist/media.d.ts +20 -0
- package/dist/media.js +93 -0
- package/dist/media.js.map +1 -0
- package/package.json +29 -0
package/dist/media.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DingTalk media download helpers.
|
|
3
|
+
*
|
|
4
|
+
* Two-step flow:
|
|
5
|
+
* 1. POST downloadCode to DingTalk API → get a temporary downloadUrl
|
|
6
|
+
* 2. GET the downloadUrl → arraybuffer
|
|
7
|
+
*/
|
|
8
|
+
const DOWNLOAD_API = 'https://api.dingtalk.com/v1.0/robot/messageFiles/download';
|
|
9
|
+
/**
|
|
10
|
+
* Download a media file from DingTalk using a downloadCode.
|
|
11
|
+
*
|
|
12
|
+
* @param downloadCode - The code from incoming message richText/content
|
|
13
|
+
* @param robotCode - The bot's clientId (appKey)
|
|
14
|
+
* @param accessToken - A valid DingTalk access token
|
|
15
|
+
* @returns MediaFile with buffer and mimeType, or null on failure
|
|
16
|
+
*/
|
|
17
|
+
export async function downloadMedia(downloadCode, robotCode, accessToken) {
|
|
18
|
+
if (!downloadCode || !robotCode || !accessToken) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
// Step 1: Get downloadUrl from DingTalk API
|
|
23
|
+
const apiResp = await fetch(DOWNLOAD_API, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: {
|
|
26
|
+
'x-acs-dingtalk-access-token': accessToken,
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
},
|
|
29
|
+
body: JSON.stringify({ downloadCode, robotCode }),
|
|
30
|
+
});
|
|
31
|
+
if (!apiResp.ok) {
|
|
32
|
+
const detail = await apiResp.text().catch(() => '');
|
|
33
|
+
process.stderr.write(`[DingTalk] downloadMedia API failed: HTTP ${apiResp.status} ${detail}\n`);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const payload = (await apiResp.json());
|
|
37
|
+
const downloadUrl = payload['downloadUrl'] ??
|
|
38
|
+
payload['data']?.['downloadUrl'];
|
|
39
|
+
if (!downloadUrl) {
|
|
40
|
+
process.stderr.write(`[DingTalk] downloadMedia: no downloadUrl in response\n`);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
// Step 2: Download the actual file. Bound the request so a stalled CDN
|
|
44
|
+
// connection can't hang the streaming loop below indefinitely (matches
|
|
45
|
+
// the Feishu adapter).
|
|
46
|
+
const fileResp = await fetch(downloadUrl, {
|
|
47
|
+
signal: AbortSignal.timeout(30_000),
|
|
48
|
+
});
|
|
49
|
+
if (!fileResp.ok) {
|
|
50
|
+
process.stderr.write(`[DingTalk] downloadMedia file fetch failed: HTTP ${fileResp.status}\n`);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const MAX_DOWNLOAD_BYTES = 50 * 1024 * 1024; // 50 MB
|
|
54
|
+
const contentLength = fileResp.headers.get('content-length');
|
|
55
|
+
if (contentLength && parseInt(contentLength, 10) > MAX_DOWNLOAD_BYTES) {
|
|
56
|
+
// Release the connection instead of letting the unread body pin it
|
|
57
|
+
// until GC.
|
|
58
|
+
await fileResp.body?.cancel();
|
|
59
|
+
process.stderr.write(`[DingTalk] downloadMedia rejected: size ${contentLength} exceeds ${MAX_DOWNLOAD_BYTES} byte limit\n`);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const mimeType = fileResp.headers.get('content-type') || 'application/octet-stream';
|
|
63
|
+
// Stream-read with size enforcement (handles chunked transfer without Content-Length)
|
|
64
|
+
const reader = fileResp.body?.getReader();
|
|
65
|
+
if (!reader) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const chunks = [];
|
|
69
|
+
let totalSize = 0;
|
|
70
|
+
while (true) {
|
|
71
|
+
const { done, value } = await reader.read();
|
|
72
|
+
if (done)
|
|
73
|
+
break;
|
|
74
|
+
totalSize += value.byteLength;
|
|
75
|
+
if (totalSize > MAX_DOWNLOAD_BYTES) {
|
|
76
|
+
// Await the cancel so a stream error during teardown surfaces here
|
|
77
|
+
// instead of becoming an unhandled rejection (matches the body.cancel
|
|
78
|
+
// above).
|
|
79
|
+
await reader.cancel();
|
|
80
|
+
process.stderr.write(`[DingTalk] downloadMedia rejected: actual size exceeds ${MAX_DOWNLOAD_BYTES} byte limit\n`);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
chunks.push(Buffer.from(value));
|
|
84
|
+
}
|
|
85
|
+
const buffer = Buffer.concat(chunks);
|
|
86
|
+
return { buffer, mimeType };
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
process.stderr.write(`[DingTalk] downloadMedia error: ${err instanceof Error ? err.message : err}\n`);
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=media.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.js","sourceRoot":"","sources":["../src/media.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qwen-code/channel-dingtalk",
|
|
3
|
+
"version": "0.21.0-preview.0",
|
|
4
|
+
"description": "DingTalk channel adapter for Qwen Code",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc --build",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:ci": "vitest run"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@qwen-code/channel-base": "^0.21.0",
|
|
24
|
+
"dingtalk-stream-sdk-nodejs": "^2.0.4"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"typescript": "^5.0.0"
|
|
28
|
+
}
|
|
29
|
+
}
|