@yaoyuanchao/dingtalk 1.4.11 → 1.4.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": "@yaoyuanchao/dingtalk",
3
- "version": "1.4.11",
3
+ "version": "1.4.13",
4
4
  "type": "module",
5
5
  "description": "DingTalk channel plugin for Clawdbot with Stream Mode support",
6
6
  "license": "MIT",
package/src/api.ts CHANGED
@@ -497,7 +497,7 @@ export const cleanupOldPictures = cleanupOldMedia;
497
497
 
498
498
  /**
499
499
  * Upload a file to DingTalk and get media_id
500
- * Uses the robot messageFiles upload API
500
+ * Uses the legacy oapi media upload endpoint (the new API doesn't exist)
501
501
  */
502
502
  export async function uploadMediaFile(params: {
503
503
  clientId: string;
@@ -514,16 +514,9 @@ export async function uploadMediaFile(params: {
514
514
  const boundary = `----DingTalkBoundary${Date.now()}`;
515
515
  const fileType = params.fileType || 'file';
516
516
 
517
- // Construct multipart body
517
+ // Construct multipart body - oapi uses "media" as the file field name
518
518
  const parts: Buffer[] = [];
519
519
 
520
- // Add robotCode field
521
- parts.push(Buffer.from(
522
- `--${boundary}\r\n` +
523
- `Content-Disposition: form-data; name="robotCode"\r\n\r\n` +
524
- `${params.robotCode}\r\n`
525
- ));
526
-
527
520
  // Add file field
528
521
  parts.push(Buffer.from(
529
522
  `--${boundary}\r\n` +
@@ -538,15 +531,14 @@ export async function uploadMediaFile(params: {
538
531
 
539
532
  const body = Buffer.concat(parts);
540
533
 
541
- // Make request
542
- const url = `${DINGTALK_API_BASE}/robot/messageFiles/upload?type=${fileType}`;
534
+ // Use legacy oapi endpoint - the new v1.0 API doesn't have this endpoint
535
+ const url = `https://oapi.dingtalk.com/media/upload?access_token=${token}&type=${fileType}`;
543
536
 
544
537
  return new Promise((resolve) => {
545
538
  const urlObj = new URL(url);
546
539
  const req = https.request(urlObj, {
547
540
  method: 'POST',
548
541
  headers: {
549
- 'x-acs-dingtalk-access-token': token,
550
542
  'Content-Type': `multipart/form-data; boundary=${boundary}`,
551
543
  'Content-Length': body.length,
552
544
  },
@@ -558,12 +550,16 @@ export async function uploadMediaFile(params: {
558
550
  res.on('end', () => {
559
551
  try {
560
552
  const json = JSON.parse(buf);
561
- if (json.mediaId) {
553
+ // oapi returns media_id (with underscore), not mediaId
554
+ if (json.media_id) {
555
+ console.log(`[dingtalk] File uploaded successfully: media_id=${json.media_id}`);
556
+ resolve({ mediaId: json.media_id });
557
+ } else if (json.mediaId) {
562
558
  console.log(`[dingtalk] File uploaded successfully: mediaId=${json.mediaId}`);
563
559
  resolve({ mediaId: json.mediaId });
564
560
  } else {
565
561
  console.warn(`[dingtalk] File upload failed:`, json);
566
- resolve({ error: json.message || json.errmsg || 'Upload failed' });
562
+ resolve({ error: json.errmsg || json.message || 'Upload failed' });
567
563
  }
568
564
  } catch {
569
565
  resolve({ error: `Invalid response: ${buf}` });
package/src/channel.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { getDingTalkRuntime } from './runtime.js';
2
2
  import { resolveDingTalkAccount } from './accounts.js';
3
3
  import { startDingTalkMonitor } from './monitor.js';
4
- import { sendDingTalkRestMessage } from './api.js';
4
+ import { sendDingTalkRestMessage, uploadMediaFile, sendFileMessage, textToMarkdownFile } from './api.js';
5
5
  import { probeDingTalk } from './probe.js';
6
6
 
7
7
  /**
@@ -187,6 +187,49 @@ export const dingtalkPlugin = {
187
187
  const account = resolveDingTalkAccount({ cfg, accountId });
188
188
  const { type, id } = parseOutboundTo(to);
189
189
 
190
+ // Check longTextMode config
191
+ const longTextMode = account.config?.longTextMode ?? 'chunk';
192
+ const longTextThreshold = account.config?.longTextThreshold ?? 4000;
193
+
194
+ // If longTextMode is 'file' and text exceeds threshold, send as file
195
+ if (longTextMode === 'file' && text.length > longTextThreshold) {
196
+ console.log(`[dingtalk] Outbound text exceeds threshold (${text.length} > ${longTextThreshold}), sending as file`);
197
+
198
+ const { buffer, fileName } = textToMarkdownFile(text, 'AI Response');
199
+
200
+ // Upload file
201
+ const uploadResult = await uploadMediaFile({
202
+ clientId: account.clientId,
203
+ clientSecret: account.clientSecret,
204
+ robotCode: account.robotCode || account.clientId,
205
+ fileBuffer: buffer,
206
+ fileName,
207
+ fileType: 'file',
208
+ });
209
+
210
+ if (uploadResult.mediaId) {
211
+ // Send file message
212
+ const sendResult = await sendFileMessage({
213
+ clientId: account.clientId,
214
+ clientSecret: account.clientSecret,
215
+ robotCode: account.robotCode || account.clientId,
216
+ userId: type === 'dm' ? id : undefined,
217
+ conversationId: type === 'group' ? id : undefined,
218
+ mediaId: uploadResult.mediaId,
219
+ fileName,
220
+ });
221
+
222
+ if (sendResult.ok) {
223
+ console.log(`[dingtalk] File sent successfully via outbound: ${fileName}`);
224
+ return { channel: 'dingtalk', ok: true };
225
+ }
226
+ console.log(`[dingtalk] File send failed, falling back to text: ${sendResult.error}`);
227
+ } else {
228
+ console.log(`[dingtalk] File upload failed, falling back to text: ${uploadResult.error}`);
229
+ }
230
+ // Fall through to text sending if file send fails
231
+ }
232
+
190
233
  if (type === 'dm') {
191
234
  await sendDingTalkRestMessage({
192
235
  clientId: account.clientId,