dingtalk-manager 1.0.0 → 1.1.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/README.md CHANGED
@@ -15,6 +15,69 @@ yarn add dingtalk-manager
15
15
  bun add dingtalk-manager
16
16
  ```
17
17
 
18
+ ## 命令行工具
19
+
20
+ 本包支持通过 `bunx` 或 `npx` 直接作为命令行工具使用,无需安装。
21
+
22
+ ### 配置凭证
23
+
24
+ 首先设置环境变量:
25
+
26
+ ```bash
27
+ export DINGTALK_APP_KEY="your-app-key"
28
+ export DINGTALK_APP_SECRET="your-app-secret"
29
+ export DINGTALK_AGENT_ID="your-agent-id"
30
+ export DINGTALK_USER_ID="target-user-id"
31
+ ```
32
+
33
+ 或者使用凭证文件(`--credentials-path` 参数):
34
+
35
+ ```bash
36
+ bunx dingtalk-manager@latest sendMarkdown --credentials-path /path/to/credentials.json --title "标题" --content "内容"
37
+ ```
38
+
39
+ ### 发送 Markdown 消息
40
+
41
+ ```bash
42
+ bunx dingtalk-manager@latest sendMarkdown --title "标题" --content "**加粗** 内容\n- 项目 1\n- 项目 2"
43
+ ```
44
+
45
+ ### 发送文本消息
46
+
47
+ ```bash
48
+ bunx dingtalk-manager@latest sendText --content "纯文本消息"
49
+ ```
50
+
51
+ ### 输出格式
52
+
53
+ 命令执行成功时输出 JSON 格式的结果:
54
+
55
+ ```json
56
+ {
57
+ "success": true,
58
+ "taskId": "123456789"
59
+ }
60
+ ```
61
+
62
+ 失败时:
63
+
64
+ ```json
65
+ {
66
+ "success": false,
67
+ "error": "错误信息"
68
+ }
69
+ ```
70
+
71
+ ### 帮助信息
72
+
73
+ ```bash
74
+ bunx dingtalk-manager@latest --help
75
+ bunx dingtalk-manager@latest sendMarkdown --help
76
+ bunx dingtalk-manager@latest sendText --help
77
+ ```
78
+
79
+ ---
80
+
18
81
  ## 快速开始
19
82
 
20
83
  ### 方式一:直接传入凭证
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,355 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { Command } from "commander";
5
+ import { readFileSync as readFileSync2 } from "node:fs";
6
+ import { dirname, join as join2 } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ // src/utils.ts
10
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { tmpdir } from "node:os";
13
+
14
+ // src/errors.ts
15
+ class DingTalkError extends Error {
16
+ constructor(message) {
17
+ super(message);
18
+ this.name = "DingTalkError";
19
+ }
20
+ }
21
+
22
+ class TokenError extends DingTalkError {
23
+ constructor(message) {
24
+ super(message);
25
+ this.name = "TokenError";
26
+ }
27
+ }
28
+
29
+ class MessageError extends DingTalkError {
30
+ constructor(message) {
31
+ super(message);
32
+ this.name = "MessageError";
33
+ }
34
+ }
35
+
36
+ class CredentialError extends DingTalkError {
37
+ constructor(message) {
38
+ super(message);
39
+ this.name = "CredentialError";
40
+ }
41
+ }
42
+
43
+ // src/utils.ts
44
+ var ENV_KEYS = {
45
+ APP_KEY: "DINGTALK_APP_KEY",
46
+ APP_SECRET: "DINGTALK_APP_SECRET",
47
+ AGENT_ID: "DINGTALK_AGENT_ID",
48
+ USER_ID: "DINGTALK_USER_ID"
49
+ };
50
+
51
+ class TokenManager {
52
+ cacheDir;
53
+ cacheFile;
54
+ constructor(cacheDir) {
55
+ this.cacheDir = cacheDir ?? tmpdir();
56
+ this.cacheFile = join(this.cacheDir, "dingtalk-token-cache.json");
57
+ }
58
+ load() {
59
+ try {
60
+ if (!existsSync(this.cacheFile)) {
61
+ return null;
62
+ }
63
+ const content = readFileSync(this.cacheFile, "utf-8");
64
+ return JSON.parse(content);
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+ save(cache) {
70
+ try {
71
+ if (!existsSync(this.cacheDir)) {
72
+ mkdirSync(this.cacheDir, { recursive: true, mode: 448 });
73
+ }
74
+ writeFileSync(this.cacheFile, JSON.stringify(cache, null, 2), { mode: 384 });
75
+ } catch (error) {
76
+ console.error("Failed to cache DingTalk token:", error instanceof Error ? error.message : String(error));
77
+ }
78
+ }
79
+ isValid(cache) {
80
+ return cache !== null && cache.expiresAt > Date.now();
81
+ }
82
+ }
83
+ function loadCredentials(options) {
84
+ if (options?.credentials) {
85
+ validateCredentials(options.credentials);
86
+ return options.credentials;
87
+ }
88
+ if (options?.credentialsPath) {
89
+ const creds = loadFromFile(options.credentialsPath);
90
+ if (creds) {
91
+ validateCredentials(creds);
92
+ return creds;
93
+ }
94
+ }
95
+ const envCreds = loadFromEnv();
96
+ if (envCreds) {
97
+ validateCredentials(envCreds);
98
+ return envCreds;
99
+ }
100
+ throw new CredentialError("DingTalk credentials not found. Provide credentials via options.credentials, options.credentialsPath, or environment variables (DINGTALK_APP_KEY, DINGTALK_APP_SECRET, DINGTALK_AGENT_ID, DINGTALK_USER_ID)");
101
+ }
102
+ function loadFromEnv() {
103
+ const appKey = process.env[ENV_KEYS.APP_KEY];
104
+ const appSecret = process.env[ENV_KEYS.APP_SECRET];
105
+ const agentId = process.env[ENV_KEYS.AGENT_ID];
106
+ const userId = process.env[ENV_KEYS.USER_ID];
107
+ if (appKey && appSecret && agentId && userId) {
108
+ return { appKey, appSecret, agentId, userId };
109
+ }
110
+ return null;
111
+ }
112
+ function loadFromFile(path) {
113
+ try {
114
+ if (!existsSync(path)) {
115
+ return null;
116
+ }
117
+ const content = readFileSync(path, "utf-8");
118
+ const data = JSON.parse(content);
119
+ return {
120
+ appKey: data.appKey,
121
+ appSecret: data.appSecret,
122
+ agentId: data.agentId,
123
+ userId: data.userId ?? data.userid
124
+ };
125
+ } catch {
126
+ return null;
127
+ }
128
+ }
129
+ function validateCredentials(creds) {
130
+ const missing = [];
131
+ if (!creds.appKey)
132
+ missing.push("appKey");
133
+ if (!creds.appSecret)
134
+ missing.push("appSecret");
135
+ if (!creds.agentId)
136
+ missing.push("agentId");
137
+ if (!creds.userId)
138
+ missing.push("userId");
139
+ if (missing.length > 0) {
140
+ throw new CredentialError(`Missing required credentials: ${missing.join(", ")}`);
141
+ }
142
+ }
143
+ var FORBIDDEN_CODES = {
144
+ "143103": {
145
+ description: "企业应用消息发送QPM(每分钟请求数)超限",
146
+ solution: "请稍后重试,或优化发送频率"
147
+ },
148
+ "143104": {
149
+ description: "企业每分钟发送QPM超限",
150
+ solution: "请稍后重试,或优化发送频率"
151
+ },
152
+ "143105": {
153
+ description: "应用发送日限超限(自建应用: 500条/天/用户, ISV应用: 50条/天/用户)",
154
+ solution: "已达到每日发送上限,请明天再试"
155
+ },
156
+ "143106": {
157
+ description: "应用重复消息发送限超限(同一用户每天只能接收一条相同内容的消息)",
158
+ solution: "修改消息内容后重新发送,或等待明天自动重置"
159
+ }
160
+ };
161
+ function getForbiddenCodeInfo(code) {
162
+ return FORBIDDEN_CODES[code] ?? null;
163
+ }
164
+
165
+ // src/client.ts
166
+ var TOKEN_EXPIRY_BUFFER_SECONDS = 300;
167
+
168
+ class DingTalk {
169
+ credentials;
170
+ tokenManager;
171
+ constructor(options) {
172
+ this.credentials = loadCredentials(options);
173
+ this.tokenManager = new TokenManager(options?.tokenCacheDir);
174
+ }
175
+ async getAccessToken() {
176
+ const cached = this.tokenManager.load();
177
+ if (this.tokenManager.isValid(cached)) {
178
+ return cached.accessToken;
179
+ }
180
+ const url = `https://oapi.dingtalk.com/gettoken?appkey=${encodeURIComponent(this.credentials.appKey)}&appsecret=${encodeURIComponent(this.credentials.appSecret)}`;
181
+ const response = await fetch(url, { method: "GET" });
182
+ if (!response.ok) {
183
+ throw new TokenError(`Failed to get access token: HTTP ${response.status}`);
184
+ }
185
+ const data = await response.json();
186
+ if (data.errcode !== 0) {
187
+ throw new TokenError(`Failed to get access token: ${data.errmsg ?? "Unknown error"}`);
188
+ }
189
+ if (!data.access_token) {
190
+ throw new TokenError("Access token missing in response");
191
+ }
192
+ const expiresInSeconds = data.expires_in ?? 7200;
193
+ this.tokenManager.save({
194
+ accessToken: data.access_token,
195
+ expiresAt: Date.now() + (expiresInSeconds - TOKEN_EXPIRY_BUFFER_SECONDS) * 1000
196
+ });
197
+ return data.access_token;
198
+ }
199
+ async sendMarkdown(title, content) {
200
+ try {
201
+ const accessToken = await this.getAccessToken();
202
+ const url = `https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2?access_token=${accessToken}`;
203
+ const body = {
204
+ agent_id: this.credentials.agentId,
205
+ userid_list: this.credentials.userId,
206
+ msg: {
207
+ msgtype: "markdown",
208
+ markdown: {
209
+ title,
210
+ text: content
211
+ }
212
+ }
213
+ };
214
+ const response = await fetch(url, {
215
+ method: "POST",
216
+ headers: { "Content-Type": "application/json" },
217
+ body: JSON.stringify(body)
218
+ });
219
+ if (!response.ok) {
220
+ return { success: false, error: `HTTP ${response.status}` };
221
+ }
222
+ const data = await response.json();
223
+ if (data.errcode !== 0) {
224
+ return { success: false, error: data.errmsg ?? `Error code: ${data.errcode}` };
225
+ }
226
+ return { success: true, taskId: data.task_id?.toString() };
227
+ } catch (error) {
228
+ return {
229
+ success: false,
230
+ error: error instanceof Error ? error.message : String(error)
231
+ };
232
+ }
233
+ }
234
+ async sendText(content) {
235
+ try {
236
+ const accessToken = await this.getAccessToken();
237
+ const url = `https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2?access_token=${accessToken}`;
238
+ const body = {
239
+ agent_id: this.credentials.agentId,
240
+ userid_list: this.credentials.userId,
241
+ msg: {
242
+ msgtype: "text",
243
+ text: { content }
244
+ }
245
+ };
246
+ const response = await fetch(url, {
247
+ method: "POST",
248
+ headers: { "Content-Type": "application/json" },
249
+ body: JSON.stringify(body)
250
+ });
251
+ if (!response.ok) {
252
+ return { success: false, error: `HTTP ${response.status}` };
253
+ }
254
+ const data = await response.json();
255
+ if (data.errcode !== 0) {
256
+ return { success: false, error: data.errmsg ?? `Error code: ${data.errcode}` };
257
+ }
258
+ return { success: true, taskId: data.task_id?.toString() };
259
+ } catch (error) {
260
+ return {
261
+ success: false,
262
+ error: error instanceof Error ? error.message : String(error)
263
+ };
264
+ }
265
+ }
266
+ async getSendResult(taskId) {
267
+ try {
268
+ const accessToken = await this.getAccessToken();
269
+ const url = `https://oapi.dingtalk.com/topapi/message/corpconversation/getsendresult?access_token=${accessToken}`;
270
+ const body = {
271
+ agent_id: this.credentials.agentId,
272
+ task_id: Number(taskId)
273
+ };
274
+ const response = await fetch(url, {
275
+ method: "POST",
276
+ headers: { "Content-Type": "application/json" },
277
+ body: JSON.stringify(body)
278
+ });
279
+ if (!response.ok) {
280
+ return { success: false, error: `HTTP ${response.status}` };
281
+ }
282
+ const data = await response.json();
283
+ if (data.errcode !== 0) {
284
+ return { success: false, error: data.errmsg ?? `Error code: ${data.errcode}` };
285
+ }
286
+ return {
287
+ success: true,
288
+ invalidUserIdList: data.send_result?.invalid_user_id_list,
289
+ forbiddenUserIdList: data.send_result?.forbidden_user_id_list,
290
+ failedUserIdList: data.send_result?.failed_user_id_list,
291
+ readUserIdList: data.send_result?.read_user_id_list,
292
+ unreadUserIdList: data.send_result?.unread_user_id_list,
293
+ forbiddenList: data.send_result?.forbidden_list
294
+ };
295
+ } catch (error) {
296
+ return {
297
+ success: false,
298
+ error: error instanceof Error ? error.message : String(error)
299
+ };
300
+ }
301
+ }
302
+ static getForbiddenCodeInfo(code) {
303
+ return getForbiddenCodeInfo(code);
304
+ }
305
+ }
306
+
307
+ // src/cli.ts
308
+ var __dirname2 = dirname(fileURLToPath(import.meta.url));
309
+ var packageJson = JSON.parse(readFileSync2(join2(__dirname2, "../package.json"), "utf-8"));
310
+ var program = new Command;
311
+ program.name("dingtalk-manager").description("DingTalk work notification CLI - send markdown and text messages").version(packageJson.version).option("--credentials-path <path>", "Path to credentials JSON file");
312
+ program.command("sendMarkdown").description("Send a markdown message to DingTalk").requiredOption("--title <title>", "Message title").requiredOption("--content <content>", "Markdown content").action(async (options, cmd) => {
313
+ const globalOpts = cmd.optsWithGlobals();
314
+ const clientOptions = {};
315
+ if (globalOpts.credentialsPath) {
316
+ clientOptions.credentialsPath = globalOpts.credentialsPath;
317
+ }
318
+ try {
319
+ const client = new DingTalk(clientOptions);
320
+ const result = await client.sendMarkdown(options.title, options.content);
321
+ console.log(JSON.stringify(result, null, 2));
322
+ process.exit(result.success ? 0 : 1);
323
+ } catch (error) {
324
+ const result = {
325
+ success: false,
326
+ error: error instanceof Error ? error.message : String(error)
327
+ };
328
+ console.log(JSON.stringify(result, null, 2));
329
+ process.exit(1);
330
+ }
331
+ });
332
+ program.command("sendText").description("Send a text message to DingTalk").requiredOption("--content <content>", "Text content").action(async (options, cmd) => {
333
+ const globalOpts = cmd.optsWithGlobals();
334
+ const clientOptions = {};
335
+ if (globalOpts.credentialsPath) {
336
+ clientOptions.credentialsPath = globalOpts.credentialsPath;
337
+ }
338
+ try {
339
+ const client = new DingTalk(clientOptions);
340
+ const result = await client.sendText(options.content);
341
+ console.log(JSON.stringify(result, null, 2));
342
+ process.exit(result.success ? 0 : 1);
343
+ } catch (error) {
344
+ const result = {
345
+ success: false,
346
+ error: error instanceof Error ? error.message : String(error)
347
+ };
348
+ console.log(JSON.stringify(result, null, 2));
349
+ process.exit(1);
350
+ }
351
+ });
352
+ program.parse();
353
+
354
+ //# debugId=4B8AA822D0E0923864756E2164756E21
355
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/cli.ts", "../src/utils.ts", "../src/errors.ts", "../src/client.ts"],
4
+ "sourcesContent": [
5
+ "#!/usr/bin/env node\nimport { Command } from 'commander';\nimport { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { DingTalk } from './client.js';\nimport type { DingTalkOptions } from './types.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));\n\nconst program = new Command();\n\nprogram\n .name('dingtalk-manager')\n .description('DingTalk work notification CLI - send markdown and text messages')\n .version(packageJson.version)\n .option('--credentials-path <path>', 'Path to credentials JSON file');\n\nprogram\n .command('sendMarkdown')\n .description('Send a markdown message to DingTalk')\n .requiredOption('--title <title>', 'Message title')\n .requiredOption('--content <content>', 'Markdown content')\n .action(async (options, cmd) => {\n const globalOpts = cmd.optsWithGlobals();\n const clientOptions: DingTalkOptions = {};\n \n if (globalOpts.credentialsPath) {\n clientOptions.credentialsPath = globalOpts.credentialsPath;\n }\n \n try {\n const client = new DingTalk(clientOptions);\n const result = await client.sendMarkdown(options.title, options.content);\n \n console.log(JSON.stringify(result, null, 2));\n process.exit(result.success ? 0 : 1);\n } catch (error) {\n const result = {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n console.log(JSON.stringify(result, null, 2));\n process.exit(1);\n }\n });\n\nprogram\n .command('sendText')\n .description('Send a text message to DingTalk')\n .requiredOption('--content <content>', 'Text content')\n .action(async (options, cmd) => {\n const globalOpts = cmd.optsWithGlobals();\n const clientOptions: DingTalkOptions = {};\n \n if (globalOpts.credentialsPath) {\n clientOptions.credentialsPath = globalOpts.credentialsPath;\n }\n \n try {\n const client = new DingTalk(clientOptions);\n const result = await client.sendText(options.content);\n \n console.log(JSON.stringify(result, null, 2));\n process.exit(result.success ? 0 : 1);\n } catch (error) {\n const result = {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n console.log(JSON.stringify(result, null, 2));\n process.exit(1);\n }\n });\n\nprogram.parse();\n",
6
+ "import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { tmpdir } from 'node:os';\nimport type { DingTalkCredentials, DingTalkOptions, TokenCache } from './types.js';\nimport { CredentialError } from './errors.js';\n\nconst ENV_KEYS = {\n APP_KEY: 'DINGTALK_APP_KEY',\n APP_SECRET: 'DINGTALK_APP_SECRET',\n AGENT_ID: 'DINGTALK_AGENT_ID',\n USER_ID: 'DINGTALK_USER_ID',\n} as const;\n\nexport class TokenManager {\n private cacheDir: string;\n private cacheFile: string;\n\n constructor(cacheDir?: string) {\n this.cacheDir = cacheDir ?? tmpdir();\n this.cacheFile = join(this.cacheDir, 'dingtalk-token-cache.json');\n }\n\n load(): TokenCache | null {\n try {\n if (!existsSync(this.cacheFile)) {\n return null;\n }\n const content = readFileSync(this.cacheFile, 'utf-8');\n return JSON.parse(content) as TokenCache;\n } catch {\n return null;\n }\n }\n\n save(cache: TokenCache): void {\n try {\n if (!existsSync(this.cacheDir)) {\n mkdirSync(this.cacheDir, { recursive: true, mode: 0o700 });\n }\n writeFileSync(this.cacheFile, JSON.stringify(cache, null, 2), { mode: 0o600 });\n } catch (error) {\n console.error('Failed to cache DingTalk token:', error instanceof Error ? error.message : String(error));\n }\n }\n\n isValid(cache: TokenCache | null): cache is TokenCache {\n return cache !== null && cache.expiresAt > Date.now();\n }\n}\n\nexport function loadCredentials(options?: DingTalkOptions): DingTalkCredentials {\n if (options?.credentials) {\n validateCredentials(options.credentials);\n return options.credentials;\n }\n\n if (options?.credentialsPath) {\n const creds = loadFromFile(options.credentialsPath);\n if (creds) {\n validateCredentials(creds);\n return creds;\n }\n }\n\n const envCreds = loadFromEnv();\n if (envCreds) {\n validateCredentials(envCreds);\n return envCreds;\n }\n\n throw new CredentialError(\n 'DingTalk credentials not found. Provide credentials via options.credentials, options.credentialsPath, or environment variables (DINGTALK_APP_KEY, DINGTALK_APP_SECRET, DINGTALK_AGENT_ID, DINGTALK_USER_ID)'\n );\n}\n\nfunction loadFromEnv(): DingTalkCredentials | null {\n const appKey = process.env[ENV_KEYS.APP_KEY];\n const appSecret = process.env[ENV_KEYS.APP_SECRET];\n const agentId = process.env[ENV_KEYS.AGENT_ID];\n const userId = process.env[ENV_KEYS.USER_ID];\n\n if (appKey && appSecret && agentId && userId) {\n return { appKey, appSecret, agentId, userId };\n }\n return null;\n}\n\nfunction loadFromFile(path: string): DingTalkCredentials | null {\n try {\n if (!existsSync(path)) {\n return null;\n }\n const content = readFileSync(path, 'utf-8');\n const data = JSON.parse(content);\n return {\n appKey: data.appKey,\n appSecret: data.appSecret,\n agentId: data.agentId,\n userId: data.userId ?? data.userid,\n };\n } catch {\n return null;\n }\n}\n\nfunction validateCredentials(creds: DingTalkCredentials): void {\n const missing: string[] = [];\n if (!creds.appKey) missing.push('appKey');\n if (!creds.appSecret) missing.push('appSecret');\n if (!creds.agentId) missing.push('agentId');\n if (!creds.userId) missing.push('userId');\n\n if (missing.length > 0) {\n throw new CredentialError(`Missing required credentials: ${missing.join(', ')}`);\n }\n}\n\nexport const FORBIDDEN_CODES: Record<string, { description: string; solution: string }> = {\n '143103': {\n description: '企业应用消息发送QPM(每分钟请求数)超限',\n solution: '请稍后重试,或优化发送频率',\n },\n '143104': {\n description: '企业每分钟发送QPM超限',\n solution: '请稍后重试,或优化发送频率',\n },\n '143105': {\n description: '应用发送日限超限(自建应用: 500条/天/用户, ISV应用: 50条/天/用户)',\n solution: '已达到每日发送上限,请明天再试',\n },\n '143106': {\n description: '应用重复消息发送限超限(同一用户每天只能接收一条相同内容的消息)',\n solution: '修改消息内容后重新发送,或等待明天自动重置',\n },\n};\n\nexport function getForbiddenCodeInfo(code: string): { description: string; solution: string } | null {\n return FORBIDDEN_CODES[code] ?? null;\n}\n",
7
+ "export class DingTalkError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'DingTalkError';\n }\n}\n\nexport class TokenError extends DingTalkError {\n constructor(message: string) {\n super(message);\n this.name = 'TokenError';\n }\n}\n\nexport class MessageError extends DingTalkError {\n constructor(message: string) {\n super(message);\n this.name = 'MessageError';\n }\n}\n\nexport class CredentialError extends DingTalkError {\n constructor(message: string) {\n super(message);\n this.name = 'CredentialError';\n }\n}\n",
8
+ "import type {\n DingTalkOptions,\n DingTalkCredentials,\n SendMessageResult,\n SendResult,\n TokenResponse,\n SendResponse,\n SendResultResponse,\n} from './types.js';\nimport { TokenManager, loadCredentials, getForbiddenCodeInfo } from './utils.js';\nimport { TokenError, MessageError } from './errors.js';\n\nconst TOKEN_EXPIRY_BUFFER_SECONDS = 300;\n\nexport class DingTalk {\n private credentials: DingTalkCredentials;\n private tokenManager: TokenManager;\n\n constructor(options?: DingTalkOptions) {\n this.credentials = loadCredentials(options);\n this.tokenManager = new TokenManager(options?.tokenCacheDir);\n }\n\n async getAccessToken(): Promise<string> {\n const cached = this.tokenManager.load();\n if (this.tokenManager.isValid(cached)) {\n return cached.accessToken;\n }\n\n const url = `https://oapi.dingtalk.com/gettoken?appkey=${encodeURIComponent(this.credentials.appKey)}&appsecret=${encodeURIComponent(this.credentials.appSecret)}`;\n\n const response = await fetch(url, { method: 'GET' });\n\n if (!response.ok) {\n throw new TokenError(`Failed to get access token: HTTP ${response.status}`);\n }\n\n const data = (await response.json()) as TokenResponse;\n\n if (data.errcode !== 0) {\n throw new TokenError(`Failed to get access token: ${data.errmsg ?? 'Unknown error'}`);\n }\n\n if (!data.access_token) {\n throw new TokenError('Access token missing in response');\n }\n\n const expiresInSeconds = data.expires_in ?? 7200;\n this.tokenManager.save({\n accessToken: data.access_token,\n expiresAt: Date.now() + (expiresInSeconds - TOKEN_EXPIRY_BUFFER_SECONDS) * 1000,\n });\n\n return data.access_token;\n }\n\n async sendMarkdown(title: string, content: string): Promise<SendMessageResult> {\n try {\n const accessToken = await this.getAccessToken();\n const url = `https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2?access_token=${accessToken}`;\n\n const body = {\n agent_id: this.credentials.agentId,\n userid_list: this.credentials.userId,\n msg: {\n msgtype: 'markdown',\n markdown: {\n title,\n text: content,\n },\n },\n };\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n return { success: false, error: `HTTP ${response.status}` };\n }\n\n const data = (await response.json()) as SendResponse;\n\n if (data.errcode !== 0) {\n return { success: false, error: data.errmsg ?? `Error code: ${data.errcode}` };\n }\n\n return { success: true, taskId: data.task_id?.toString() };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n\n async sendText(content: string): Promise<SendMessageResult> {\n try {\n const accessToken = await this.getAccessToken();\n const url = `https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2?access_token=${accessToken}`;\n\n const body = {\n agent_id: this.credentials.agentId,\n userid_list: this.credentials.userId,\n msg: {\n msgtype: 'text',\n text: { content },\n },\n };\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n return { success: false, error: `HTTP ${response.status}` };\n }\n\n const data = (await response.json()) as SendResponse;\n\n if (data.errcode !== 0) {\n return { success: false, error: data.errmsg ?? `Error code: ${data.errcode}` };\n }\n\n return { success: true, taskId: data.task_id?.toString() };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n\n async getSendResult(taskId: string): Promise<SendResult> {\n try {\n const accessToken = await this.getAccessToken();\n const url = `https://oapi.dingtalk.com/topapi/message/corpconversation/getsendresult?access_token=${accessToken}`;\n\n const body = {\n agent_id: this.credentials.agentId,\n task_id: Number(taskId),\n };\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n return { success: false, error: `HTTP ${response.status}` };\n }\n\n const data = (await response.json()) as SendResultResponse;\n\n if (data.errcode !== 0) {\n return { success: false, error: data.errmsg ?? `Error code: ${data.errcode}` };\n }\n\n return {\n success: true,\n invalidUserIdList: data.send_result?.invalid_user_id_list,\n forbiddenUserIdList: data.send_result?.forbidden_user_id_list,\n failedUserIdList: data.send_result?.failed_user_id_list,\n readUserIdList: data.send_result?.read_user_id_list,\n unreadUserIdList: data.send_result?.unread_user_id_list,\n forbiddenList: data.send_result?.forbidden_list,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n\n static getForbiddenCodeInfo(code: string): { description: string; solution: string } | null {\n return getForbiddenCodeInfo(code);\n }\n}\n"
9
+ ],
10
+ "mappings": "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",
11
+ "debugId": "4B8AA822D0E0923864756E2164756E21",
12
+ "names": []
13
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dingtalk-manager",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "DingTalk work notification API client for Node.js/Bun - send markdown and text messages",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -12,6 +12,9 @@
12
12
  "types": "./dist/index.d.ts"
13
13
  }
14
14
  },
15
+ "bin": {
16
+ "dingtalk-manager": "./dist/cli.js"
17
+ },
15
18
  "files": [
16
19
  "dist",
17
20
  "README.md",
@@ -20,7 +23,7 @@
20
23
  "scripts": {
21
24
  "build": "bun run build:clean && bun run build:js && bun run build:types",
22
25
  "build:clean": "rm -rf dist",
23
- "build:js": "bun build ./src/index.ts --outdir ./dist --target node --format esm --sourcemap --external commander",
26
+ "build:js": "bun build ./src/index.ts ./src/cli.ts --outdir ./dist --target node --format esm --sourcemap --external commander",
24
27
  "build:types": "tsc --emitDeclarationOnly --declaration --outDir dist",
25
28
  "test": "bun test",
26
29
  "test:watch": "bun test --watch",
@@ -49,6 +52,9 @@
49
52
  "engines": {
50
53
  "node": ">=18.0.0"
51
54
  },
55
+ "dependencies": {
56
+ "commander": "^12.0.0"
57
+ },
52
58
  "devDependencies": {
53
59
  "@types/bun": "latest",
54
60
  "typescript": "^5.3.0"