clawdbot-dingtalk 0.1.21 → 0.2.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/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/src/accounts.d.ts +1 -1
- package/dist/src/api/index.d.ts +3 -1
- package/dist/src/api/index.d.ts.map +1 -1
- package/dist/src/api/index.js +3 -1
- package/dist/src/api/index.js.map +1 -1
- package/dist/src/api/media-upload.d.ts +71 -0
- package/dist/src/api/media-upload.d.ts.map +1 -0
- package/dist/src/api/media-upload.js +223 -0
- package/dist/src/api/media-upload.js.map +1 -0
- package/dist/src/api/send-message.d.ts +31 -0
- package/dist/src/api/send-message.d.ts.map +1 -1
- package/dist/src/api/send-message.js +214 -0
- package/dist/src/api/send-message.js.map +1 -1
- package/dist/src/channel.d.ts +1 -1
- package/dist/src/channel.d.ts.map +1 -1
- package/dist/src/channel.js +25 -11
- package/dist/src/channel.js.map +1 -1
- package/dist/src/dashscope/service.d.ts +1 -1
- package/dist/src/monitor.d.ts +1 -1
- package/dist/src/monitor.d.ts.map +1 -1
- package/dist/src/monitor.js +44 -4
- package/dist/src/monitor.js.map +1 -1
- package/dist/src/runtime.d.ts +1 -1
- package/dist/src/send/media-processor.d.ts +72 -0
- package/dist/src/send/media-processor.d.ts.map +1 -0
- package/dist/src/send/media-processor.js +183 -0
- package/dist/src/send/media-processor.js.map +1 -0
- package/dist/src/send/reply.d.ts +8 -0
- package/dist/src/send/reply.d.ts.map +1 -1
- package/dist/src/send/reply.js +42 -0
- package/dist/src/send/reply.js.map +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +5 -11
- package/clawdbot.plugin.json +0 -11
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
2
|
declare const plugin: {
|
|
3
3
|
id: string;
|
|
4
4
|
name: string;
|
|
5
5
|
description: string;
|
|
6
6
|
configSchema: any;
|
|
7
|
-
register(api:
|
|
7
|
+
register(api: OpenClawPluginApi): void;
|
|
8
8
|
};
|
|
9
9
|
export default plugin;
|
|
10
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { emptyPluginConfigSchema } from "
|
|
1
|
+
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
|
2
2
|
import { dingtalkPlugin } from "./src/channel.js";
|
|
3
3
|
import { DINGTALK_PLUGIN_ID } from "./src/config-schema.js";
|
|
4
4
|
import { setDingTalkRuntime } from "./src/runtime.js";
|
package/dist/src/accounts.d.ts
CHANGED
package/dist/src/api/index.d.ts
CHANGED
|
@@ -3,5 +3,7 @@
|
|
|
3
3
|
* Provides access token management and proactive message sending.
|
|
4
4
|
*/
|
|
5
5
|
export { createTokenManager, createTokenManagerFromAccount, clearAllTokens, invalidateToken, type TokenManager, type TokenManagerOptions, } from "./token-manager.js";
|
|
6
|
-
export { sendProactiveMessage, sendBatchDirectMessage, parseTarget, type MessageTarget, type SendMessageOptions, type SendMessageResult, } from "./send-message.js";
|
|
6
|
+
export { sendProactiveMessage, sendBatchDirectMessage, sendImageMessage, sendImageMessageWithMediaId, sendFileMessage, sendActionCardMessage, sendMediaByPath, parseTarget, type MessageTarget, type SendMessageOptions, type SendMessageResult, type SendImageOptions, type SendFileOptions, type SendMediaByPathOptions, } from "./send-message.js";
|
|
7
|
+
export { uploadMedia, downloadMedia, type UploadMediaResult, type DownloadMediaResult, } from "./media.js";
|
|
8
|
+
export { uploadMediaToOAPI, uploadLocalFile, isLocalPath, normalizeLocalPath, isImageUrl, detectMediaType, type MediaType, type UploadMediaResult as UploadMediaToOAPIResult, } from "./media-upload.js";
|
|
7
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,kBAAkB,EAClB,6BAA6B,EAC7B,cAAc,EACd,eAAe,EACf,KAAK,YAAY,EACjB,KAAK,mBAAmB,GACzB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,kBAAkB,EAClB,6BAA6B,EAC7B,cAAc,EACd,eAAe,EACf,KAAK,YAAY,EACjB,KAAK,mBAAmB,GACzB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,gBAAgB,EAChB,2BAA2B,EAC3B,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,sBAAsB,GAC5B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,WAAW,EACX,aAAa,EACb,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACzB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,kBAAkB,EAClB,UAAU,EACV,eAAe,EACf,KAAK,SAAS,EACd,KAAK,iBAAiB,IAAI,uBAAuB,GAClD,MAAM,mBAAmB,CAAC"}
|
package/dist/src/api/index.js
CHANGED
|
@@ -3,5 +3,7 @@
|
|
|
3
3
|
* Provides access token management and proactive message sending.
|
|
4
4
|
*/
|
|
5
5
|
export { createTokenManager, createTokenManagerFromAccount, clearAllTokens, invalidateToken, } from "./token-manager.js";
|
|
6
|
-
export { sendProactiveMessage, sendBatchDirectMessage, parseTarget, } from "./send-message.js";
|
|
6
|
+
export { sendProactiveMessage, sendBatchDirectMessage, sendImageMessage, sendImageMessageWithMediaId, sendFileMessage, sendActionCardMessage, sendMediaByPath, parseTarget, } from "./send-message.js";
|
|
7
|
+
export { uploadMedia, downloadMedia, } from "./media.js";
|
|
8
|
+
export { uploadMediaToOAPI, uploadLocalFile, isLocalPath, normalizeLocalPath, isImageUrl, detectMediaType, } from "./media-upload.js";
|
|
7
9
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,kBAAkB,EAClB,6BAA6B,EAC7B,cAAc,EACd,eAAe,GAGhB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,WAAW,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,kBAAkB,EAClB,6BAA6B,EAC7B,cAAc,EACd,eAAe,GAGhB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,gBAAgB,EAChB,2BAA2B,EAC3B,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,WAAW,GAOZ,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,WAAW,EACX,aAAa,GAGd,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,kBAAkB,EAClB,UAAU,EACV,eAAe,GAGhB,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DingTalk Media Upload API for local files.
|
|
3
|
+
* Uses the OAPI media upload endpoint to get mediaId for local files.
|
|
4
|
+
*/
|
|
5
|
+
import type { ResolvedDingTalkAccount } from "../accounts.js";
|
|
6
|
+
import type { StreamLogger } from "../stream/types.js";
|
|
7
|
+
import { type TokenManager } from "./token-manager.js";
|
|
8
|
+
/**
|
|
9
|
+
* Media type for DingTalk upload API.
|
|
10
|
+
*/
|
|
11
|
+
export type MediaType = "image" | "voice" | "video" | "file";
|
|
12
|
+
/**
|
|
13
|
+
* Result of uploading media to OAPI.
|
|
14
|
+
*/
|
|
15
|
+
export interface UploadMediaResult {
|
|
16
|
+
ok: boolean;
|
|
17
|
+
mediaId?: string;
|
|
18
|
+
type?: MediaType;
|
|
19
|
+
error?: Error;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Detect media type based on file extension.
|
|
23
|
+
*/
|
|
24
|
+
export declare function detectMediaType(fileName: string): MediaType;
|
|
25
|
+
/**
|
|
26
|
+
* Check if a string is a local file path (not a URL).
|
|
27
|
+
*
|
|
28
|
+
* Local path patterns:
|
|
29
|
+
* - Absolute paths: /tmp/foo.png, /Users/xxx/image.jpg
|
|
30
|
+
* - Windows paths: C:\Users\xxx\image.jpg
|
|
31
|
+
* - Home paths: ~/Downloads/image.png
|
|
32
|
+
* - file:// protocol: file:///tmp/foo.png
|
|
33
|
+
* - MEDIA: prefix: MEDIA:/tmp/foo.png
|
|
34
|
+
* - attachment:// prefix: attachment:///tmp/foo.png
|
|
35
|
+
*/
|
|
36
|
+
export declare function isLocalPath(urlOrPath: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Normalize a local path by removing special prefixes.
|
|
39
|
+
*/
|
|
40
|
+
export declare function normalizeLocalPath(rawPath: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Upload media to DingTalk OAPI and get mediaId.
|
|
43
|
+
*
|
|
44
|
+
* This uses the old OAPI endpoint (oapi.dingtalk.com/media/upload) which
|
|
45
|
+
* returns a media_id that can be used with sessionWebhook image messages.
|
|
46
|
+
*
|
|
47
|
+
* API: POST https://oapi.dingtalk.com/media/upload?access_token=xxx&type=image
|
|
48
|
+
*/
|
|
49
|
+
export declare function uploadMediaToOAPI(opts: {
|
|
50
|
+
account: ResolvedDingTalkAccount;
|
|
51
|
+
media: Buffer;
|
|
52
|
+
fileName: string;
|
|
53
|
+
mediaType?: MediaType;
|
|
54
|
+
tokenManager?: TokenManager;
|
|
55
|
+
logger?: StreamLogger;
|
|
56
|
+
}): Promise<UploadMediaResult>;
|
|
57
|
+
/**
|
|
58
|
+
* Upload a local file to DingTalk OAPI.
|
|
59
|
+
* Reads the file from disk and uploads it.
|
|
60
|
+
*/
|
|
61
|
+
export declare function uploadLocalFile(opts: {
|
|
62
|
+
account: ResolvedDingTalkAccount;
|
|
63
|
+
filePath: string;
|
|
64
|
+
tokenManager?: TokenManager;
|
|
65
|
+
logger?: StreamLogger;
|
|
66
|
+
}): Promise<UploadMediaResult>;
|
|
67
|
+
/**
|
|
68
|
+
* Check if a URL points to an image based on extension.
|
|
69
|
+
*/
|
|
70
|
+
export declare function isImageUrl(url: string): boolean;
|
|
71
|
+
//# sourceMappingURL=media-upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-upload.d.ts","sourceRoot":"","sources":["../../../src/api/media-upload.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAiC,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAQtF;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAiBD;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAM3D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAuCtD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA0B1D;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,OAAO,EAAE,uBAAuB,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAkG7B;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,OAAO,EAAE,uBAAuB,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA0B7B;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAY/C"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DingTalk Media Upload API for local files.
|
|
3
|
+
* Uses the OAPI media upload endpoint to get mediaId for local files.
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
import { createTokenManagerFromAccount } from "./token-manager.js";
|
|
8
|
+
/**
|
|
9
|
+
* DingTalk OAPI base URL for media upload.
|
|
10
|
+
* This is different from the new API (api.dingtalk.com) used for other operations.
|
|
11
|
+
*/
|
|
12
|
+
const DINGTALK_OAPI_BASE = "https://oapi.dingtalk.com";
|
|
13
|
+
/**
|
|
14
|
+
* Image file extensions.
|
|
15
|
+
*/
|
|
16
|
+
const IMAGE_EXTENSIONS = new Set([".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"]);
|
|
17
|
+
/**
|
|
18
|
+
* Voice file extensions.
|
|
19
|
+
*/
|
|
20
|
+
const VOICE_EXTENSIONS = new Set([".mp3", ".wav", ".amr", ".opus", ".ogg"]);
|
|
21
|
+
/**
|
|
22
|
+
* Video file extensions.
|
|
23
|
+
*/
|
|
24
|
+
const VIDEO_EXTENSIONS = new Set([".mp4", ".mov", ".avi", ".mkv", ".webm"]);
|
|
25
|
+
/**
|
|
26
|
+
* Detect media type based on file extension.
|
|
27
|
+
*/
|
|
28
|
+
export function detectMediaType(fileName) {
|
|
29
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
30
|
+
if (IMAGE_EXTENSIONS.has(ext))
|
|
31
|
+
return "image";
|
|
32
|
+
if (VOICE_EXTENSIONS.has(ext))
|
|
33
|
+
return "voice";
|
|
34
|
+
if (VIDEO_EXTENSIONS.has(ext))
|
|
35
|
+
return "video";
|
|
36
|
+
return "file";
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Check if a string is a local file path (not a URL).
|
|
40
|
+
*
|
|
41
|
+
* Local path patterns:
|
|
42
|
+
* - Absolute paths: /tmp/foo.png, /Users/xxx/image.jpg
|
|
43
|
+
* - Windows paths: C:\Users\xxx\image.jpg
|
|
44
|
+
* - Home paths: ~/Downloads/image.png
|
|
45
|
+
* - file:// protocol: file:///tmp/foo.png
|
|
46
|
+
* - MEDIA: prefix: MEDIA:/tmp/foo.png
|
|
47
|
+
* - attachment:// prefix: attachment:///tmp/foo.png
|
|
48
|
+
*/
|
|
49
|
+
export function isLocalPath(urlOrPath) {
|
|
50
|
+
if (!urlOrPath || typeof urlOrPath !== "string")
|
|
51
|
+
return false;
|
|
52
|
+
const trimmed = urlOrPath.trim();
|
|
53
|
+
// Check for special local path prefixes
|
|
54
|
+
if (trimmed.startsWith("file://") ||
|
|
55
|
+
trimmed.startsWith("MEDIA:") ||
|
|
56
|
+
trimmed.startsWith("attachment://")) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
// Check for Unix absolute paths
|
|
60
|
+
if (trimmed.startsWith("/")) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
// Check for home directory paths
|
|
64
|
+
if (trimmed.startsWith("~")) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
// Check for Windows absolute paths (C:\ or D:\)
|
|
68
|
+
if (/^[a-zA-Z]:[\\/]/.test(trimmed)) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
// Check if it's a valid URL (http://, https://, etc.)
|
|
72
|
+
try {
|
|
73
|
+
const url = new URL(trimmed);
|
|
74
|
+
// If it parses as a URL with http/https, it's not a local path
|
|
75
|
+
return url.protocol === "file:";
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// If it doesn't parse as a URL, it might be a relative path
|
|
79
|
+
// For safety, we treat unrecognized patterns as non-local
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Normalize a local path by removing special prefixes.
|
|
85
|
+
*/
|
|
86
|
+
export function normalizeLocalPath(rawPath) {
|
|
87
|
+
let p = rawPath.trim();
|
|
88
|
+
// Remove special prefixes
|
|
89
|
+
if (p.startsWith("file://")) {
|
|
90
|
+
p = p.slice(7);
|
|
91
|
+
}
|
|
92
|
+
else if (p.startsWith("MEDIA:")) {
|
|
93
|
+
p = p.slice(6);
|
|
94
|
+
}
|
|
95
|
+
else if (p.startsWith("attachment://")) {
|
|
96
|
+
p = p.slice(13);
|
|
97
|
+
}
|
|
98
|
+
// Handle URL encoding
|
|
99
|
+
try {
|
|
100
|
+
p = decodeURIComponent(p);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Ignore decoding errors
|
|
104
|
+
}
|
|
105
|
+
// Expand home directory
|
|
106
|
+
if (p.startsWith("~")) {
|
|
107
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
108
|
+
p = path.join(home, p.slice(1));
|
|
109
|
+
}
|
|
110
|
+
return p;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Upload media to DingTalk OAPI and get mediaId.
|
|
114
|
+
*
|
|
115
|
+
* This uses the old OAPI endpoint (oapi.dingtalk.com/media/upload) which
|
|
116
|
+
* returns a media_id that can be used with sessionWebhook image messages.
|
|
117
|
+
*
|
|
118
|
+
* API: POST https://oapi.dingtalk.com/media/upload?access_token=xxx&type=image
|
|
119
|
+
*/
|
|
120
|
+
export async function uploadMediaToOAPI(opts) {
|
|
121
|
+
const { account, media, fileName, mediaType = detectMediaType(fileName), tokenManager: providedTokenManager, logger, } = opts;
|
|
122
|
+
const tokenManager = providedTokenManager ?? createTokenManagerFromAccount(account, logger);
|
|
123
|
+
let accessToken;
|
|
124
|
+
try {
|
|
125
|
+
accessToken = await tokenManager.getToken();
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
logger?.error?.({ err: { message: err?.message } }, "Failed to get access token for OAPI media upload");
|
|
129
|
+
return { ok: false, error: err };
|
|
130
|
+
}
|
|
131
|
+
const url = `${DINGTALK_OAPI_BASE}/media/upload?access_token=${accessToken}&type=${mediaType}`;
|
|
132
|
+
try {
|
|
133
|
+
// Create FormData for file upload
|
|
134
|
+
const formData = new FormData();
|
|
135
|
+
const blob = new Blob([media], { type: "application/octet-stream" });
|
|
136
|
+
formData.append("media", blob, fileName);
|
|
137
|
+
logger?.debug?.({ fileName, mediaType, size: media.length }, "Uploading media to OAPI");
|
|
138
|
+
const resp = await fetch(url, {
|
|
139
|
+
method: "POST",
|
|
140
|
+
body: formData,
|
|
141
|
+
signal: AbortSignal.timeout(60_000), // 60s timeout for file upload
|
|
142
|
+
});
|
|
143
|
+
if (!resp.ok) {
|
|
144
|
+
const errorText = await resp.text().catch(() => "");
|
|
145
|
+
logger?.error?.({ status: resp.status, error: errorText.slice(0, 200), fileName }, "OAPI media upload failed (HTTP error)");
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
error: new Error(`HTTP ${resp.status}: ${errorText.slice(0, 200)}`),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const data = (await resp.json());
|
|
152
|
+
// Check for API-level errors
|
|
153
|
+
if (data.errcode && data.errcode !== 0) {
|
|
154
|
+
logger?.error?.({ errcode: data.errcode, errmsg: data.errmsg, fileName }, "OAPI media upload failed (API error)");
|
|
155
|
+
return {
|
|
156
|
+
ok: false,
|
|
157
|
+
error: new Error(`DingTalk API error: ${data.errmsg} (code: ${data.errcode})`),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (!data.media_id) {
|
|
161
|
+
logger?.error?.({ data, fileName }, "OAPI media upload failed (no media_id)");
|
|
162
|
+
return {
|
|
163
|
+
ok: false,
|
|
164
|
+
error: new Error("DingTalk API returned no media_id"),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
logger?.debug?.({ mediaId: data.media_id, type: data.type, fileName }, "OAPI media uploaded successfully");
|
|
168
|
+
return {
|
|
169
|
+
ok: true,
|
|
170
|
+
mediaId: data.media_id,
|
|
171
|
+
type: data.type ?? mediaType,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
logger?.error?.({ err: { message: err?.message }, fileName }, "OAPI media upload error");
|
|
176
|
+
return { ok: false, error: err };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Upload a local file to DingTalk OAPI.
|
|
181
|
+
* Reads the file from disk and uploads it.
|
|
182
|
+
*/
|
|
183
|
+
export async function uploadLocalFile(opts) {
|
|
184
|
+
const { account, filePath, tokenManager, logger } = opts;
|
|
185
|
+
// Normalize the path
|
|
186
|
+
const normalizedPath = normalizeLocalPath(filePath);
|
|
187
|
+
// Check if file exists
|
|
188
|
+
if (!fs.existsSync(normalizedPath)) {
|
|
189
|
+
logger?.error?.({ filePath: normalizedPath }, "Local file not found");
|
|
190
|
+
return {
|
|
191
|
+
ok: false,
|
|
192
|
+
error: new Error(`File not found: ${normalizedPath}`),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
// Read the file
|
|
196
|
+
const fileBuffer = fs.readFileSync(normalizedPath);
|
|
197
|
+
const fileName = path.basename(normalizedPath);
|
|
198
|
+
return uploadMediaToOAPI({
|
|
199
|
+
account,
|
|
200
|
+
media: fileBuffer,
|
|
201
|
+
fileName,
|
|
202
|
+
tokenManager,
|
|
203
|
+
logger,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Check if a URL points to an image based on extension.
|
|
208
|
+
*/
|
|
209
|
+
export function isImageUrl(url) {
|
|
210
|
+
if (!url || typeof url !== "string")
|
|
211
|
+
return false;
|
|
212
|
+
try {
|
|
213
|
+
const pathname = new URL(url).pathname;
|
|
214
|
+
const ext = path.extname(pathname).toLowerCase();
|
|
215
|
+
return IMAGE_EXTENSIONS.has(ext);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// If URL parsing fails, try direct extension check
|
|
219
|
+
const ext = path.extname(url.split("?")[0] ?? "").toLowerCase();
|
|
220
|
+
return IMAGE_EXTENSIONS.has(ext);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=media-upload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-upload.js","sourceRoot":"","sources":["../../../src/api/media-upload.ts"],"names":[],"mappings":"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"}
|
|
@@ -112,4 +112,35 @@ export interface SendFileOptions {
|
|
|
112
112
|
* Uses msgKey: sampleFile
|
|
113
113
|
*/
|
|
114
114
|
export declare function sendFileMessage(opts: SendFileOptions): Promise<SendMessageResult>;
|
|
115
|
+
/**
|
|
116
|
+
* Options for sending media by path (local or remote).
|
|
117
|
+
*/
|
|
118
|
+
export interface SendMediaByPathOptions {
|
|
119
|
+
account: ResolvedDingTalkAccount;
|
|
120
|
+
to: string;
|
|
121
|
+
mediaUrl: string;
|
|
122
|
+
text?: string;
|
|
123
|
+
tokenManager?: TokenManager;
|
|
124
|
+
logger?: StreamLogger;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Send an image message using mediaId.
|
|
128
|
+
* Used when you have already uploaded a file and have the mediaId.
|
|
129
|
+
*/
|
|
130
|
+
export declare function sendImageMessageWithMediaId(opts: Omit<SendImageOptions, "picUrl"> & {
|
|
131
|
+
mediaId: string;
|
|
132
|
+
}): Promise<SendMessageResult>;
|
|
133
|
+
/**
|
|
134
|
+
* Send media by path (supports both local files and remote URLs).
|
|
135
|
+
*
|
|
136
|
+
* For local files:
|
|
137
|
+
* - Reads the file from disk
|
|
138
|
+
* - Uploads to DingTalk to get mediaId
|
|
139
|
+
* - Sends as image or file message
|
|
140
|
+
*
|
|
141
|
+
* For remote URLs:
|
|
142
|
+
* - Images: Sends directly using picUrl
|
|
143
|
+
* - Files: Downloads, uploads to DingTalk, then sends
|
|
144
|
+
*/
|
|
145
|
+
export declare function sendMediaByPath(opts: SendMediaByPathOptions): Promise<SendMessageResult>;
|
|
115
146
|
//# sourceMappingURL=send-message.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send-message.d.ts","sourceRoot":"","sources":["../../../src/api/send-message.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"send-message.d.ts","sourceRoot":"","sources":["../../../src/api/send-message.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
* DingTalk Proactive Message Sending API.
|
|
3
3
|
* Supports sending messages to users (1:1) and groups without sessionWebhook.
|
|
4
4
|
*/
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as path from "node:path";
|
|
5
7
|
import { createTokenManagerFromAccount } from "./token-manager.js";
|
|
6
8
|
import { chunkText, chunkMarkdownText, normalizeForTextMessage } from "../send/chunker.js";
|
|
7
9
|
import { convertMarkdownForDingTalk } from "../send/markdown.js";
|
|
10
|
+
import { isLocalPath, normalizeLocalPath, isImageUrl, uploadMediaToOAPI, uploadLocalFile, detectMediaType, } from "./media-upload.js";
|
|
8
11
|
/**
|
|
9
12
|
* Parse target string to determine message type.
|
|
10
13
|
*
|
|
@@ -472,4 +475,215 @@ export async function sendFileMessage(opts) {
|
|
|
472
475
|
return { ok: false, error: err };
|
|
473
476
|
}
|
|
474
477
|
}
|
|
478
|
+
/**
|
|
479
|
+
* Send an image message using mediaId.
|
|
480
|
+
* Used when you have already uploaded a file and have the mediaId.
|
|
481
|
+
*/
|
|
482
|
+
export async function sendImageMessageWithMediaId(opts) {
|
|
483
|
+
const { account, to, mediaId, text, logger, tokenManager: providedTokenManager, } = opts;
|
|
484
|
+
const target = parseTarget(to);
|
|
485
|
+
const tokenManager = providedTokenManager ?? createTokenManagerFromAccount(account, logger);
|
|
486
|
+
let accessToken;
|
|
487
|
+
try {
|
|
488
|
+
accessToken = await tokenManager.getToken();
|
|
489
|
+
}
|
|
490
|
+
catch (err) {
|
|
491
|
+
logger?.error?.({ err: { message: err?.message } }, "Failed to get access token for image message (mediaId)");
|
|
492
|
+
return { ok: false, error: err };
|
|
493
|
+
}
|
|
494
|
+
// For image messages with mediaId, use sampleImageMsg with the mediaId as photoURL
|
|
495
|
+
// DingTalk's sampleImageMsg supports both URL and mediaId in the photoURL field
|
|
496
|
+
const msgKey = "sampleImageMsg";
|
|
497
|
+
const msgParam = JSON.stringify({ photoURL: mediaId });
|
|
498
|
+
const url = target.type === "group"
|
|
499
|
+
? `${account.apiBase}/v1.0/robot/groupMessages/send`
|
|
500
|
+
: `${account.apiBase}/v1.0/robot/oToMessages/batchSend`;
|
|
501
|
+
try {
|
|
502
|
+
const body = target.type === "group"
|
|
503
|
+
? {
|
|
504
|
+
robotCode: account.clientId,
|
|
505
|
+
openConversationId: target.id,
|
|
506
|
+
msgKey,
|
|
507
|
+
msgParam,
|
|
508
|
+
}
|
|
509
|
+
: {
|
|
510
|
+
robotCode: account.clientId,
|
|
511
|
+
userIds: [target.id],
|
|
512
|
+
msgKey,
|
|
513
|
+
msgParam,
|
|
514
|
+
};
|
|
515
|
+
const resp = await fetch(url, {
|
|
516
|
+
method: "POST",
|
|
517
|
+
headers: {
|
|
518
|
+
"Content-Type": "application/json",
|
|
519
|
+
"x-acs-dingtalk-access-token": accessToken,
|
|
520
|
+
},
|
|
521
|
+
body: JSON.stringify(body),
|
|
522
|
+
signal: AbortSignal.timeout(30_000),
|
|
523
|
+
});
|
|
524
|
+
if (!resp.ok) {
|
|
525
|
+
const errorText = await resp.text().catch(() => "");
|
|
526
|
+
logger?.error?.({ status: resp.status, error: errorText.slice(0, 200), mediaId }, "Image message (mediaId) failed");
|
|
527
|
+
return {
|
|
528
|
+
ok: false,
|
|
529
|
+
error: new Error(`HTTP ${resp.status}: ${errorText.slice(0, 200)}`),
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
const data = (await resp.json());
|
|
533
|
+
logger?.debug?.({ processQueryKey: data.processQueryKey, mediaId }, "Image message (mediaId) sent");
|
|
534
|
+
// If there's accompanying text, send it separately
|
|
535
|
+
if (text?.trim()) {
|
|
536
|
+
await sendProactiveMessage({
|
|
537
|
+
account,
|
|
538
|
+
to,
|
|
539
|
+
text,
|
|
540
|
+
replyMode: account.replyMode,
|
|
541
|
+
logger,
|
|
542
|
+
tokenManager,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
return {
|
|
546
|
+
ok: true,
|
|
547
|
+
processQueryKey: data.processQueryKey,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
catch (err) {
|
|
551
|
+
logger?.error?.({ err: { message: err?.message }, mediaId }, "Image message (mediaId) error");
|
|
552
|
+
return { ok: false, error: err };
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Send media by path (supports both local files and remote URLs).
|
|
557
|
+
*
|
|
558
|
+
* For local files:
|
|
559
|
+
* - Reads the file from disk
|
|
560
|
+
* - Uploads to DingTalk to get mediaId
|
|
561
|
+
* - Sends as image or file message
|
|
562
|
+
*
|
|
563
|
+
* For remote URLs:
|
|
564
|
+
* - Images: Sends directly using picUrl
|
|
565
|
+
* - Files: Downloads, uploads to DingTalk, then sends
|
|
566
|
+
*/
|
|
567
|
+
export async function sendMediaByPath(opts) {
|
|
568
|
+
const { account, to, mediaUrl, text, tokenManager: providedTokenManager, logger } = opts;
|
|
569
|
+
const tokenManager = providedTokenManager ?? createTokenManagerFromAccount(account, logger);
|
|
570
|
+
// Check if it's a local path
|
|
571
|
+
if (isLocalPath(mediaUrl)) {
|
|
572
|
+
const localPath = normalizeLocalPath(mediaUrl);
|
|
573
|
+
const fileName = path.basename(localPath);
|
|
574
|
+
const mediaType = detectMediaType(fileName);
|
|
575
|
+
logger?.debug?.({ localPath, mediaType }, "Sending local media");
|
|
576
|
+
// Check if file exists
|
|
577
|
+
if (!fs.existsSync(localPath)) {
|
|
578
|
+
logger?.error?.({ localPath }, "Local file not found");
|
|
579
|
+
return {
|
|
580
|
+
ok: false,
|
|
581
|
+
error: new Error(`File not found: ${localPath}`),
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
// Upload the file
|
|
585
|
+
const uploadResult = await uploadLocalFile({
|
|
586
|
+
account,
|
|
587
|
+
filePath: localPath,
|
|
588
|
+
tokenManager,
|
|
589
|
+
logger,
|
|
590
|
+
});
|
|
591
|
+
if (!uploadResult.ok || !uploadResult.mediaId) {
|
|
592
|
+
return {
|
|
593
|
+
ok: false,
|
|
594
|
+
error: uploadResult.error ?? new Error("Failed to upload local file"),
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
// Send based on media type
|
|
598
|
+
if (mediaType === "image") {
|
|
599
|
+
return sendImageMessageWithMediaId({
|
|
600
|
+
account,
|
|
601
|
+
to,
|
|
602
|
+
mediaId: uploadResult.mediaId,
|
|
603
|
+
text,
|
|
604
|
+
tokenManager,
|
|
605
|
+
logger,
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
return sendFileMessage({
|
|
610
|
+
account,
|
|
611
|
+
to,
|
|
612
|
+
mediaId: uploadResult.mediaId,
|
|
613
|
+
fileName,
|
|
614
|
+
fileType: mediaType,
|
|
615
|
+
tokenManager,
|
|
616
|
+
logger,
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
// Remote URL handling
|
|
621
|
+
const isImage = isImageUrl(mediaUrl);
|
|
622
|
+
if (isImage) {
|
|
623
|
+
// For remote images, try sending directly with URL
|
|
624
|
+
logger?.debug?.({ mediaUrl }, "Sending remote image via URL");
|
|
625
|
+
return sendImageMessage({
|
|
626
|
+
account,
|
|
627
|
+
to,
|
|
628
|
+
picUrl: mediaUrl,
|
|
629
|
+
text,
|
|
630
|
+
tokenManager,
|
|
631
|
+
logger,
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
// For non-image remote files, download first, then upload and send
|
|
635
|
+
logger?.debug?.({ mediaUrl }, "Downloading remote file for upload");
|
|
636
|
+
try {
|
|
637
|
+
const response = await fetch(mediaUrl, {
|
|
638
|
+
signal: AbortSignal.timeout(60_000),
|
|
639
|
+
});
|
|
640
|
+
if (!response.ok) {
|
|
641
|
+
return {
|
|
642
|
+
ok: false,
|
|
643
|
+
error: new Error(`Failed to download file: HTTP ${response.status}`),
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
647
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
648
|
+
// Extract filename from URL
|
|
649
|
+
let fileName;
|
|
650
|
+
try {
|
|
651
|
+
const pathname = new URL(mediaUrl).pathname;
|
|
652
|
+
fileName = path.basename(pathname) || "file";
|
|
653
|
+
}
|
|
654
|
+
catch {
|
|
655
|
+
fileName = "file";
|
|
656
|
+
}
|
|
657
|
+
const mediaType = detectMediaType(fileName);
|
|
658
|
+
// Upload to DingTalk
|
|
659
|
+
const uploadResult = await uploadMediaToOAPI({
|
|
660
|
+
account,
|
|
661
|
+
media: buffer,
|
|
662
|
+
fileName,
|
|
663
|
+
mediaType,
|
|
664
|
+
tokenManager,
|
|
665
|
+
logger,
|
|
666
|
+
});
|
|
667
|
+
if (!uploadResult.ok || !uploadResult.mediaId) {
|
|
668
|
+
return {
|
|
669
|
+
ok: false,
|
|
670
|
+
error: uploadResult.error ?? new Error("Failed to upload remote file"),
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
// Send as file
|
|
674
|
+
return sendFileMessage({
|
|
675
|
+
account,
|
|
676
|
+
to,
|
|
677
|
+
mediaId: uploadResult.mediaId,
|
|
678
|
+
fileName,
|
|
679
|
+
fileType: mediaType,
|
|
680
|
+
tokenManager,
|
|
681
|
+
logger,
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
catch (err) {
|
|
685
|
+
logger?.error?.({ err: { message: err?.message }, mediaUrl }, "Failed to download and send remote file");
|
|
686
|
+
return { ok: false, error: err };
|
|
687
|
+
}
|
|
688
|
+
}
|
|
475
689
|
//# sourceMappingURL=send-message.js.map
|