@zhin.js/adapter-dingtalk 1.1.15 → 1.1.16

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/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @zhin.js/adapter-dingtalk
2
2
 
3
+ ## 1.1.16
4
+
5
+ ### Patch Changes
6
+
7
+ - 53bc6a0: 适配器配置标签、端点命令和工具反馈支持中英文;复用当前操作语言,保持协议值、用户内容、身份、权限与发送链路不变。Sandbox 展示使用项目语言,QQ 绑定与交互状态保留完整会话身份。
8
+ - Updated dependencies [334e92d]
9
+ - Updated dependencies [f3b7c9f]
10
+ - Updated dependencies [658a5fb]
11
+ - Updated dependencies [144a17b]
12
+ - Updated dependencies [18296cf]
13
+ - Updated dependencies [08e64de]
14
+ - Updated dependencies [9cade30]
15
+ - Updated dependencies [53bc6a0]
16
+ - Updated dependencies [7595136]
17
+ - Updated dependencies [66b6404]
18
+ - Updated dependencies [adb4af2]
19
+ - Updated dependencies [69f9ef6]
20
+ - Updated dependencies [17d1301]
21
+ - @zhin.js/adapter@1.1.23
22
+ - @zhin.js/host-http@1.1.10
23
+ - @zhin.js/core@1.1.51
24
+ - @zhin.js/tool@1.1.10
25
+ - @zhin.js/skill@1.1.10
26
+ - @zhin.js/im-contract@1.1.7
27
+ - @zhin.js/agent-feature@1.1.10
28
+ - @zhin.js/feature-kit@1.1.9
29
+
3
30
  ## 1.1.15
4
31
 
5
32
  ### Patch Changes
@@ -0,0 +1,32 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { createMessages } from 'zhin.js/i18n';
3
+ export const messages = createMessages({
4
+ "createFailed": {
5
+ "zh-CN": "创建失败",
6
+ "en": "Creation failed"
7
+ },
8
+ "chatCreated": {
9
+ "zh-CN": "群聊创建成功: {p0}",
10
+ "en": "Group chat created: {p0}"
11
+ },
12
+ "sendFailed": {
13
+ "zh-CN": "发送失败",
14
+ "en": "Delivery failed"
15
+ },
16
+ "workNoticeSent": {
17
+ "zh-CN": "工作通知已发送",
18
+ "en": "Work notification sent"
19
+ },
20
+ "addFailed": {
21
+ "zh-CN": "添加失败",
22
+ "en": "Could not add members"
23
+ },
24
+ "membersAdded": {
25
+ "zh-CN": "成员添加成功",
26
+ "en": "Members added"
27
+ },
28
+ "chatUpdated": {
29
+ "zh-CN": "群聊设置已更新",
30
+ "en": "Group chat settings updated"
31
+ }
32
+ });
@@ -0,0 +1,32 @@
1
+ import { createMessages } from 'zhin.js/i18n';
2
+
3
+ export const messages = createMessages({
4
+ "createFailed": {
5
+ "zh-CN": "创建失败",
6
+ "en": "Creation failed"
7
+ },
8
+ "chatCreated": {
9
+ "zh-CN": "群聊创建成功: {p0}",
10
+ "en": "Group chat created: {p0}"
11
+ },
12
+ "sendFailed": {
13
+ "zh-CN": "发送失败",
14
+ "en": "Delivery failed"
15
+ },
16
+ "workNoticeSent": {
17
+ "zh-CN": "工作通知已发送",
18
+ "en": "Work notification sent"
19
+ },
20
+ "addFailed": {
21
+ "zh-CN": "添加失败",
22
+ "en": "Could not add members"
23
+ },
24
+ "membersAdded": {
25
+ "zh-CN": "成员添加成功",
26
+ "en": "Members added"
27
+ },
28
+ "chatUpdated": {
29
+ "zh-CN": "群聊设置已更新",
30
+ "en": "Group chat settings updated"
31
+ }
32
+ });
@@ -1,4 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { messages as t } from "../../../../messages.js";
2
3
  import { defineAgentTool } from '@zhin.js/tool';
3
4
  import { z } from 'zod';
4
5
  import { platformPermission } from '@zhin.js/permission';
@@ -15,6 +16,6 @@ export default defineAgentTool({
15
16
  async execute({ name, owner, members }, context) {
16
17
  const endpoint = context.$client;
17
18
  const chatId = await endpoint.createChat(name, owner, members.split(','));
18
- return { success: !!chatId, chat_id: chatId, message: chatId ? `群聊创建成功: ${chatId}` : '创建失败' };
19
+ return { success: !!chatId, chat_id: chatId, message: chatId ? t(context.locale, 'chatCreated', { p0: chatId }) : t(context.locale, 'createFailed') };
19
20
  },
20
21
  });
@@ -1,3 +1,4 @@
1
+ import { messages as t } from "../../../../messages.js";
1
2
  import { defineAgentTool } from '@zhin.js/tool';
2
3
  import { z } from 'zod';
3
4
  import { platformPermission } from '@zhin.js/permission';
@@ -15,7 +16,7 @@ export default defineAgentTool<{ name: string; owner: string; members: string }>
15
16
  async execute({ name, owner, members }: { name: string; owner: string; members: string }, context) {
16
17
  const endpoint = context.$client;
17
18
  const chatId = await endpoint.createChat(name, owner, members.split(','));
18
- return { success: !!chatId, chat_id: chatId, message: chatId ? `群聊创建成功: ${chatId}` : '创建失败' };
19
+ return { success: !!chatId, chat_id: chatId, message: chatId ? t(context.locale, 'chatCreated', { p0: chatId }) : t(context.locale, 'createFailed') };
19
20
  },
20
21
  });
21
22
 
@@ -1,4 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { messages as t } from "../../../../messages.js";
2
3
  import { defineAgentTool } from '@zhin.js/tool';
3
4
  import { z } from 'zod';
4
5
  export default defineAgentTool({
@@ -13,6 +14,6 @@ export default defineAgentTool({
13
14
  const endpoint = context.$client;
14
15
  const msgContent = { msgtype: 'text', text: { content } };
15
16
  const success = await endpoint.sendWorkNotice(user_ids.split(','), msgContent);
16
- return { success, message: success ? '工作通知已发送' : '发送失败' };
17
+ return { success, message: success ? t(context.locale, 'workNoticeSent') : t(context.locale, 'sendFailed') };
17
18
  },
18
19
  });
@@ -1,3 +1,4 @@
1
+ import { messages as t } from "../../../../messages.js";
1
2
  import { defineAgentTool } from '@zhin.js/tool';
2
3
  import { z } from 'zod';
3
4
  export default defineAgentTool<{ user_ids: string; content: string }>({
@@ -12,7 +13,7 @@ export default defineAgentTool<{ user_ids: string; content: string }>({
12
13
  const endpoint = context.$client;
13
14
  const msgContent = { msgtype: 'text', text: { content } };
14
15
  const success = await endpoint.sendWorkNotice(user_ids.split(','), msgContent);
15
- return { success, message: success ? '工作通知已发送' : '发送失败' };
16
+ return { success, message: success ? t(context.locale, 'workNoticeSent') : t(context.locale, 'sendFailed') };
16
17
  },
17
18
  });
18
19
 
@@ -1,4 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { messages as t } from "../../../../messages.js";
2
3
  import { defineAgentTool } from '@zhin.js/tool';
3
4
  import { z } from 'zod';
4
5
  import { platformPermission } from '@zhin.js/permission';
@@ -15,6 +16,6 @@ export default defineAgentTool({
15
16
  async execute({ chat_id, user_ids }, context) {
16
17
  const endpoint = context.$client;
17
18
  const success = await endpoint.updateChat(chat_id, { add_useridlist: user_ids.split(',') });
18
- return { success, message: success ? '成员添加成功' : '添加失败' };
19
+ return { success, message: success ? t(context.locale, 'membersAdded') : t(context.locale, 'addFailed') };
19
20
  },
20
21
  });
@@ -1,3 +1,4 @@
1
+ import { messages as t } from "../../../../messages.js";
1
2
  import { defineAgentTool } from '@zhin.js/tool';
2
3
  import { z } from 'zod';
3
4
  import { platformPermission } from '@zhin.js/permission';
@@ -15,7 +16,7 @@ export default defineAgentTool<{ chat_id: string; user_ids: string }>({
15
16
  async execute({ chat_id, user_ids }: { chat_id: string; user_ids: string }, context) {
16
17
  const endpoint = context.$client;
17
18
  const success = await endpoint.updateChat(chat_id, { add_useridlist: user_ids.split(',') });
18
- return { success, message: success ? '成员添加成功' : '添加失败' };
19
+ return { success, message: success ? t(context.locale, 'membersAdded') : t(context.locale, 'addFailed') };
19
20
  },
20
21
  });
21
22
 
@@ -1,4 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { messages as t } from "../../../../messages.js";
2
3
  import { defineAgentTool } from '@zhin.js/tool';
3
4
  import { z } from 'zod';
4
5
  export default defineAgentTool({
@@ -25,6 +26,6 @@ export default defineAgentTool({
25
26
  if (remove_members)
26
27
  options.del_useridlist = remove_members.split(',').map((s) => s.trim());
27
28
  await endpoint.updateChat(chat_id, options);
28
- return { success: true, message: '群聊设置已更新' };
29
+ return { success: true, message: t(context.locale, 'chatUpdated') };
29
30
  },
30
31
  });
@@ -1,3 +1,4 @@
1
+ import { messages as t } from "../../../../messages.js";
1
2
  import { defineAgentTool } from '@zhin.js/tool';
2
3
  import { z } from 'zod';
3
4
  export default defineAgentTool<{ chat_id: string; name?: string; owner?: string; add_members?: string; remove_members?: string }>({
@@ -20,7 +21,7 @@ export default defineAgentTool<{ chat_id: string; name?: string; owner?: string;
20
21
  if (add_members) options.add_useridlist = add_members.split(',').map((s: string) => s.trim());
21
22
  if (remove_members) options.del_useridlist = remove_members.split(',').map((s: string) => s.trim());
22
23
  await endpoint.updateChat(chat_id, options);
23
- return { success: true, message: '群聊设置已更新' };
24
+ return { success: true, message: t(context.locale, 'chatUpdated') };
24
25
  },
25
26
  });
26
27
 
@@ -10,9 +10,9 @@ export const dingtalkEndpointCommands = createEndpointCommands({
10
10
  adapterKey: 'dingtalk',
11
11
  adapterDisplayName: 'DingTalk',
12
12
  fields: [
13
- { key: 'appKey', required: true, env: true, description: '钉钉 AppKey' },
14
- { key: 'appSecret', required: true, env: true, description: '钉钉 AppSecret' },
15
- { key: 'robotCode', required: true, env: true, description: '机器人 Code(出站 senderStaffId)' },
13
+ { key: 'appKey', required: true, env: true, description: '钉钉 AppKey', descriptionI18n: { en: "DingTalk app key" } },
14
+ { key: 'appSecret', required: true, env: true, description: '钉钉 AppSecret', descriptionI18n: { en: "DingTalk app secret" } },
15
+ { key: 'robotCode', required: true, env: true, description: '机器人 Code(出站 senderStaffId)', descriptionI18n: { en: "Robot code (outbound senderStaffId)" } },
16
16
  ],
17
17
  running: (use) => use(dingtalkRuntimeStateToken).endpoints.values(),
18
18
  describeEntry: (entry) => `appKey: ${String(entry.appKey)}`,
@@ -10,9 +10,9 @@ export const dingtalkEndpointCommands = createEndpointCommands({
10
10
  adapterKey: 'dingtalk',
11
11
  adapterDisplayName: 'DingTalk',
12
12
  fields: [
13
- { key: 'appKey', required: true, env: true, description: '钉钉 AppKey' },
14
- { key: 'appSecret', required: true, env: true, description: '钉钉 AppSecret' },
15
- { key: 'robotCode', required: true, env: true, description: '机器人 Code(出站 senderStaffId)' },
13
+ { key: 'appKey', required: true, env: true, description: '钉钉 AppKey', descriptionI18n: { en: "DingTalk app key" } },
14
+ { key: 'appSecret', required: true, env: true, description: '钉钉 AppSecret', descriptionI18n: { en: "DingTalk app secret" } },
15
+ { key: 'robotCode', required: true, env: true, description: '机器人 Code(出站 senderStaffId)', descriptionI18n: { en: "Robot code (outbound senderStaffId)" } },
16
16
  ],
17
17
  running: (use) => use(dingtalkRuntimeStateToken).endpoints.values(),
18
18
  describeEntry: (entry) => `appKey: ${String(entry.appKey)}`,
package/lib/cards.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { Locale } from 'zhin.js/i18n';
1
2
  import { type ConversationRef } from '@zhin.js/im-contract';
2
3
  import type { DingTalkMessage, ResolvedDingTalkConfig } from './protocol.js';
3
4
  export interface DingTalkCardCallback {
@@ -24,7 +25,7 @@ export declare class DingTalkCards {
24
25
  #private;
25
26
  remember(conversation: ConversationRef, event: DingTalkMessage): void;
26
27
  clear(): void;
27
- prepare(config: ResolvedDingTalkConfig, conversation: ConversationRef, payload: unknown): {
28
+ prepare(config: ResolvedDingTalkConfig, conversation: ConversationRef, payload: unknown, locale?: Locale): {
28
29
  id: `${string}-${string}-${string}-${string}-${string}`;
29
30
  record: CardRecord;
30
31
  epoch: number;
package/lib/cards.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { messages as t } from './i18n.js';
1
2
  import { randomUUID } from 'node:crypto';
2
3
  import { EndpointDeliveryError } from '@zhin.js/im-contract';
3
4
  /** Endpoint-owned, bounded correlation. Cards from old generations deliberately cannot dispatch. */
@@ -12,7 +13,7 @@ export class DingTalkCards {
12
13
  this.#contexts.delete(this.#contexts.keys().next().value);
13
14
  }
14
15
  clear() { this.#epoch++; this.#records.clear(); this.#contexts.clear(); }
15
- prepare(config, conversation, payload) {
16
+ prepare(config, conversation, payload, locale) {
16
17
  const segments = Array.isArray(payload) ? payload : [payload];
17
18
  const keyboard = segments.filter(item => item && typeof item === 'object' && item.type === 'keyboard');
18
19
  if (!keyboard.length)
@@ -42,7 +43,7 @@ export class DingTalkCards {
42
43
  return String(item.data?.text ?? item.data?.content ?? '');
43
44
  throw new EndpointDeliveryError('invalid_payload', 'DingTalk interactive cards accept text/markdown and keyboard only', 'not_sent');
44
45
  }).join('');
45
- const cardParamMap = { title: '消息', text };
46
+ const cardParamMap = { title: t(locale, 'message'), text };
46
47
  for (let i = 0; i < count; i++) {
47
48
  cardParamMap[`button${i}_label`] = buttons[i]?.label ?? '';
48
49
  cardParamMap[`button${i}_payload`] = buttons[i]?.payload ?? '';
package/lib/endpoint.d.ts CHANGED
@@ -70,7 +70,7 @@ export declare class DingTalkEndpoint extends Endpoint<DingTalkClient> {
70
70
  open(): void;
71
71
  close(): void;
72
72
  stop(): Promise<void>;
73
- send({ conversation, payload }: EndpointSendRequest): Promise<string>;
73
+ send({ conversation, payload, locale }: EndpointSendRequest): Promise<string>;
74
74
  admitCard(event: DingTalkCardCallback, id: string): Promise<void>;
75
75
  /** Test / internal: admit a parsed event when open (non-webhook path). */
76
76
  admit(event: DingTalkEvent | DingTalkMessage): Promise<void>;
package/lib/endpoint.js CHANGED
@@ -105,9 +105,9 @@ export class DingTalkEndpoint extends Endpoint {
105
105
  this.#started = false;
106
106
  this.#logger.debug(formatCompact({ op: 'disconnect' }));
107
107
  }
108
- async send({ conversation, payload }) {
108
+ async send({ conversation, payload, locale }) {
109
109
  try {
110
- const card = this.#cards.prepare(this.config, conversation, payload);
110
+ const card = this.#cards.prepare(this.config, conversation, payload, locale);
111
111
  if (card) {
112
112
  let stage = 'token';
113
113
  let httpStatus;
@@ -151,7 +151,7 @@ export class DingTalkEndpoint extends Endpoint {
151
151
  throw error;
152
152
  }
153
153
  }
154
- const content = formatOutboundBody(payload);
154
+ const content = formatOutboundBody(payload, locale);
155
155
  const session = this.#sessionWebhooks.get(conversation.id);
156
156
  const sessionWebhook = session && session.expiresAt > Date.now() ? session.url : undefined;
157
157
  if (session && !sessionWebhook)
package/lib/i18n.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare const messages: (locale: import("zhin.js").Locale | undefined, key: "openLink" | "link" | "message", values?: import("zhin.js").MessageValues) => string;
package/lib/i18n.js ADDED
@@ -0,0 +1,6 @@
1
+ import { createMessages } from 'zhin.js/i18n';
2
+ export const messages = createMessages({
3
+ openLink: { 'zh-CN': '打开链接', en: 'Open link' },
4
+ link: { 'zh-CN': '链接', en: 'Link' },
5
+ message: { 'zh-CN': '消息', en: 'Message' }
6
+ });
package/lib/protocol.d.ts CHANGED
@@ -1,7 +1,4 @@
1
- /**
2
- * DingTalk protocol helpers — no legacy Adapter/Endpoint / segment-mapper.
3
- * Canonicalization is owned by gateway/core before endpoint.send.
4
- */
1
+ import type { Locale } from 'zhin.js/i18n';
5
2
  import type { IncomingMessage } from 'node:http';
6
3
  import { type ConversationRef } from '@zhin.js/im-contract';
7
4
  /** One endpoint config after AdapterIndex expands `plugins.<instanceKey>.endpoints`. */
@@ -131,7 +128,7 @@ export declare function verifySignature(appSecret: string, timestamp: string, si
131
128
  * 钉钉机器人图片使用官方 Markdown HTTP(S) 图片链接,保留同载荷文本和图片顺序。
132
129
  * 本地媒体上传、原生引用、音频/视频/文件未接线,在请求前明确拒绝。
133
130
  */
134
- export declare function formatOutboundBody(payload: unknown): DingTalkSendBody;
131
+ export declare function formatOutboundBody(payload: unknown, locale?: Locale): DingTalkSendBody;
135
132
  export declare function headerValue(headers: IncomingMessage['headers'], name: string): string;
136
133
  export declare function readTextBody(request: IncomingMessage, options?: {
137
134
  readonly limit?: number;
package/lib/protocol.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { messages as t } from './i18n.js';
1
2
  /**
2
3
  * DingTalk protocol helpers — no legacy Adapter/Endpoint / segment-mapper.
3
4
  * Canonicalization is owned by gateway/core before endpoint.send.
@@ -143,7 +144,7 @@ export function verifySignature(appSecret, timestamp, sign) {
143
144
  * 钉钉机器人图片使用官方 Markdown HTTP(S) 图片链接,保留同载荷文本和图片顺序。
144
145
  * 本地媒体上传、原生引用、音频/视频/文件未接线,在请求前明确拒绝。
145
146
  */
146
- export function formatOutboundBody(payload) {
147
+ export function formatOutboundBody(payload, locale) {
147
148
  if (typeof payload === 'string') {
148
149
  return { msgtype: 'text', text: { content: payload } };
149
150
  }
@@ -199,7 +200,7 @@ export function formatOutboundBody(payload) {
199
200
  throw new EndpointDeliveryError('invalid_payload', 'DingTalk share requires an HTTP(S) URL', 'not_sent');
200
201
  if (media)
201
202
  throw new EndpointDeliveryError('invalid_payload', 'DingTalk accepts one link card per message', 'not_sent');
202
- media = { msgtype: 'link', link: { title: String(data.title ?? '链接'), text: String(data.description ?? data.content ?? ''), messageUrl: url.toString(), ...(typeof data.image === 'string' ? { picUrl: data.image } : {}) } };
203
+ media = { msgtype: 'link', link: { title: String(data.title ?? t(locale, 'link')), text: String(data.description ?? data.content ?? ''), messageUrl: url.toString(), ...(typeof data.image === 'string' ? { picUrl: data.image } : {}) } };
203
204
  break;
204
205
  }
205
206
  case 'keyboard':
@@ -216,7 +217,7 @@ export function formatOutboundBody(payload) {
216
217
  }
217
218
  if (!['https:', 'http:'].includes(url.protocol))
218
219
  throw new EndpointDeliveryError('invalid_payload', 'DingTalk image requires an HTTP(S) URL', 'not_sent');
219
- markdownTitle ??= '消息';
220
+ markdownTitle ??= t(locale, 'message');
220
221
  textParts.push(`\n![](${ref.value.replace(/[()\s]/g, character => encodeURIComponent(character))})\n`);
221
222
  break;
222
223
  }
@@ -227,7 +228,7 @@ export function formatOutboundBody(payload) {
227
228
  case 'file':
228
229
  throw new EndpointDeliveryError('unsupported_operation', 'DingTalk audio/video/file delivery is not implemented', 'not_sent');
229
230
  case 'markdown':
230
- markdownTitle ??= String(data.title || '消息');
231
+ markdownTitle ??= String(data.title || t(locale, 'message'));
231
232
  textParts.push(String(data.content ?? data.text ?? ''));
232
233
  break;
233
234
  case 'link':
@@ -235,7 +236,7 @@ export function formatOutboundBody(payload) {
235
236
  media = {
236
237
  msgtype: 'link',
237
238
  link: {
238
- title: String(data.title || '链接'),
239
+ title: String(data.title || t(locale, 'link')),
239
240
  text: String(data.text ?? data.content ?? ''),
240
241
  messageUrl: typeof data.url === 'string' ? data.url : undefined,
241
242
  picUrl: typeof data.picUrl === 'string' ? data.picUrl : undefined,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-dingtalk",
3
- "version": "1.1.15",
3
+ "version": "1.1.16",
4
4
  "description": "Zhin.js DingTalk (钉钉) adapter for Plugin Runtime (HTTP webhook)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -33,24 +33,24 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "ws": "^8.18.3",
36
- "@zhin.js/adapter": "1.1.22",
37
- "@zhin.js/agent-feature": "1.1.9",
38
- "@zhin.js/core": "1.1.50",
39
- "@zhin.js/feature-kit": "1.1.8",
40
- "@zhin.js/host-http": "1.1.9",
41
- "@zhin.js/im-contract": "1.1.6",
36
+ "@zhin.js/adapter": "1.1.23",
37
+ "@zhin.js/agent-feature": "1.1.10",
38
+ "@zhin.js/core": "1.1.51",
39
+ "@zhin.js/feature-kit": "1.1.9",
40
+ "@zhin.js/host-http": "1.1.10",
41
+ "@zhin.js/im-contract": "1.1.7",
42
42
  "@zhin.js/logger": "1.1.2",
43
- "@zhin.js/skill": "1.1.9",
44
- "@zhin.js/tool": "1.1.9"
43
+ "@zhin.js/skill": "1.1.10",
44
+ "@zhin.js/tool": "1.1.10"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "zod": "^4.0.0",
48
- "@zhin.js/adapter": "^1.1.22",
49
- "@zhin.js/command": "^1.1.10",
50
- "@zhin.js/core": "^1.1.50",
51
- "@zhin.js/host-http": "^1.1.9",
52
- "@zhin.js/permission": "^1.1.8",
53
- "zhin.js": "^1.1.15"
48
+ "@zhin.js/adapter": "^1.1.23",
49
+ "@zhin.js/command": "^1.1.11",
50
+ "@zhin.js/core": "^1.1.51",
51
+ "@zhin.js/host-http": "^1.1.10",
52
+ "@zhin.js/permission": "^1.1.9",
53
+ "zhin.js": "^1.1.16"
54
54
  },
55
55
  "peerDependenciesMeta": {
56
56
  "@zhin.js/command": {
@@ -69,7 +69,7 @@
69
69
  "vitest": "^4.1.10",
70
70
  "zod": "^4.4.3",
71
71
  "@types/ws": "^8.18.1",
72
- "zhin.js": "1.1.15"
72
+ "zhin.js": "1.1.16"
73
73
  },
74
74
  "files": [
75
75
  "adapters",
package/schema.json CHANGED
@@ -12,7 +12,12 @@
12
12
  "string",
13
13
  "number"
14
14
  ],
15
- "description": "框架 master(DingTalk userid;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
15
+ "description": "框架 master(DingTalk userid;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖",
16
+ "x-i18n": {
17
+ "en": {
18
+ "description": "Framework masters (DingTalk userid; AI/tool permissions and endpoint management). endpoints[i].master can override this value."
19
+ }
20
+ }
16
21
  },
17
22
  "trusted": {
18
23
  "type": "array",
@@ -23,7 +28,12 @@
23
28
  ],
24
29
  "description": "Trusted DingTalk userid"
25
30
  },
26
- "description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
31
+ "description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加",
32
+ "x-i18n": {
33
+ "en": {
34
+ "description": "Framework trusted users, below master authority. endpoints[i].trusted can add users."
35
+ }
36
+ }
27
37
  },
28
38
  "endpoints": {
29
39
  "type": "array",
@@ -38,7 +48,12 @@
38
48
  "string",
39
49
  "number"
40
50
  ],
41
- "description": "本 endpoint 的框架 master(DingTalk userid);覆盖顶层 master"
51
+ "description": "本 endpoint 的框架 master(DingTalk userid);覆盖顶层 master",
52
+ "x-i18n": {
53
+ "en": {
54
+ "description": "Framework masters for this endpoint (DingTalk userid), overriding the top-level master list."
55
+ }
56
+ }
42
57
  },
43
58
  "trusted": {
44
59
  "type": "array",
@@ -49,7 +64,12 @@
49
64
  ],
50
65
  "description": "Trusted DingTalk userid"
51
66
  },
52
- "description": "本 endpoint 的 trusted 列表"
67
+ "description": "本 endpoint 的 trusted 列表",
68
+ "x-i18n": {
69
+ "en": {
70
+ "description": "Trusted users for this endpoint."
71
+ }
72
+ }
53
73
  },
54
74
  "appKey": {
55
75
  "type": "string",
@@ -116,7 +136,12 @@
116
136
  "port",
117
137
  "serverName"
118
138
  ],
119
- "description": "故障验收专用:TCP固定127.0.0.1,保留真实WSS TLS身份"
139
+ "description": "故障验收专用:TCP固定127.0.0.1,保留真实WSS TLS身份",
140
+ "x-i18n": {
141
+ "en": {
142
+ "description": "Fault-injection tests only: pin TCP to 127.0.0.1 while preserving the actual WSS TLS identity."
143
+ }
144
+ }
120
145
  }
121
146
  },
122
147
  "required": [
@@ -124,12 +149,22 @@
124
149
  "appKey",
125
150
  "appSecret"
126
151
  ]
152
+ },
153
+ "x-i18n": {
154
+ "en": {
155
+ "description": "Multiple accounts: mount multiple endpoints in one plugin instance. Each entry uses the top-level fields; id is required and other fields override defaults."
156
+ }
127
157
  }
128
158
  },
129
159
  "commandPrefix": {
130
160
  "type": "string",
131
161
  "default": "",
132
- "description": "命令前缀(默认 '' 无前缀,任意文本按命令匹配;如 '/' 要求 / 开头)。endpoints[i] 可逐项覆盖"
162
+ "description": "命令前缀(默认 '' 无前缀,任意文本按命令匹配;如 '/' 要求 / 开头)。endpoints[i] 可逐项覆盖",
163
+ "x-i18n": {
164
+ "en": {
165
+ "description": "Command prefix. The default empty string allows unprefixed commands; '/' requires a leading slash. Each endpoints[i] can override it."
166
+ }
167
+ }
133
168
  },
134
169
  "mode": {
135
170
  "type": "string",
package/src/cards.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { Locale } from 'zhin.js/i18n';
2
+ import { messages as t } from './i18n.js';
1
3
  import { randomUUID } from 'node:crypto';
2
4
  import { EndpointDeliveryError, type ConversationRef } from '@zhin.js/im-contract';
3
5
  import type { DingTalkMessage, ResolvedDingTalkConfig } from './protocol.js';
@@ -14,7 +16,7 @@ export class DingTalkCards {
14
16
  while (this.#contexts.size > 1024) this.#contexts.delete(this.#contexts.keys().next().value!);
15
17
  }
16
18
  clear(): void { this.#epoch++; this.#records.clear(); this.#contexts.clear(); }
17
- prepare(config: ResolvedDingTalkConfig, conversation: ConversationRef, payload: unknown) {
19
+ prepare(config: ResolvedDingTalkConfig, conversation: ConversationRef, payload: unknown, locale?: Locale) {
18
20
  const segments = Array.isArray(payload) ? payload : [payload];
19
21
  const keyboard = segments.filter(item => item && typeof item === 'object' && item.type === 'keyboard');
20
22
  if (!keyboard.length) return undefined;
@@ -35,7 +37,7 @@ export class DingTalkCards {
35
37
  if (item?.type === 'text' || item?.type === 'markdown') return String(item.data?.text ?? item.data?.content ?? '');
36
38
  throw new EndpointDeliveryError('invalid_payload', 'DingTalk interactive cards accept text/markdown and keyboard only', 'not_sent');
37
39
  }).join('');
38
- const cardParamMap: Record<string, string> = { title: '消息', text };
40
+ const cardParamMap: Record<string, string> = { title: t(locale, 'message'), text };
39
41
  for (let i = 0; i < count; i++) { cardParamMap[`button${i}_label`] = buttons[i]?.label ?? ''; cardParamMap[`button${i}_payload`] = buttons[i]?.payload ?? ''; cardParamMap[`button${i}_visible`] = buttons[i] ? 'true' : 'false'; }
40
42
  const id = randomUUID();
41
43
  const record: CardRecord = { conversation, spaceType, spaceId, staffId: context.senderStaffId, senderId: context.senderId, corpId: context.senderCorpId, payloads: new Set(buttons.map((button: any) => button.payload)), expiresAt: Date.now() + 3_600_000 };
package/src/endpoint.ts CHANGED
@@ -169,9 +169,9 @@ export class DingTalkEndpoint extends Endpoint<DingTalkClient> {
169
169
  this.#logger.debug(formatCompact({ op: 'disconnect' }));
170
170
  }
171
171
 
172
- async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
172
+ async send({ conversation, payload, locale }: EndpointSendRequest): Promise<string> {
173
173
  try {
174
- const card = this.#cards.prepare(this.config, conversation, payload);
174
+ const card = this.#cards.prepare(this.config, conversation, payload, locale);
175
175
  if (card) {
176
176
  let stage = 'token';
177
177
  let httpStatus: number | undefined;
@@ -202,7 +202,7 @@ export class DingTalkEndpoint extends Endpoint<DingTalkClient> {
202
202
  throw error;
203
203
  }
204
204
  }
205
- const content = formatOutboundBody(payload);
205
+ const content = formatOutboundBody(payload, locale);
206
206
  const session = this.#sessionWebhooks.get(conversation.id);
207
207
  const sessionWebhook = session && session.expiresAt > Date.now() ? session.url : undefined;
208
208
  if (session && !sessionWebhook) this.#sessionWebhooks.delete(conversation.id);
package/src/i18n.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { createMessages } from 'zhin.js/i18n';
2
+
3
+ export const messages = createMessages({
4
+ openLink: { 'zh-CN': '打开链接', en: 'Open link' },
5
+ link: { 'zh-CN': '链接', en: 'Link' },
6
+ message: { 'zh-CN': '消息', en: 'Message' }
7
+ });
package/src/protocol.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { messages as t } from './i18n.js';
2
+ import type { Locale } from 'zhin.js/i18n';
1
3
  /**
2
4
  * DingTalk protocol helpers — no legacy Adapter/Endpoint / segment-mapper.
3
5
  * Canonicalization is owned by gateway/core before endpoint.send.
@@ -250,7 +252,7 @@ export function verifySignature(
250
252
  * 钉钉机器人图片使用官方 Markdown HTTP(S) 图片链接,保留同载荷文本和图片顺序。
251
253
  * 本地媒体上传、原生引用、音频/视频/文件未接线,在请求前明确拒绝。
252
254
  */
253
- export function formatOutboundBody(payload: unknown): DingTalkSendBody {
255
+ export function formatOutboundBody(payload: unknown, locale?: Locale): DingTalkSendBody {
254
256
  if (typeof payload === 'string') {
255
257
  return { msgtype: 'text', text: { content: payload } };
256
258
  }
@@ -303,7 +305,7 @@ export function formatOutboundBody(payload: unknown): DingTalkSendBody {
303
305
  try { url = new URL(String(data.url ?? '')); } catch { throw new EndpointDeliveryError('invalid_payload', 'DingTalk share requires an HTTP(S) URL', 'not_sent'); }
304
306
  if (!['https:', 'http:'].includes(url.protocol)) throw new EndpointDeliveryError('invalid_payload', 'DingTalk share requires an HTTP(S) URL', 'not_sent');
305
307
  if (media) throw new EndpointDeliveryError('invalid_payload', 'DingTalk accepts one link card per message', 'not_sent');
306
- media = { msgtype: 'link', link: { title: String(data.title ?? '链接'), text: String(data.description ?? data.content ?? ''), messageUrl: url.toString(), ...(typeof data.image === 'string' ? { picUrl: data.image } : {}) } };
308
+ media = { msgtype: 'link', link: { title: String(data.title ?? t(locale, 'link')), text: String(data.description ?? data.content ?? ''), messageUrl: url.toString(), ...(typeof data.image === 'string' ? { picUrl: data.image } : {}) } };
307
309
  break;
308
310
  }
309
311
  case 'keyboard':
@@ -314,7 +316,7 @@ export function formatOutboundBody(payload: unknown): DingTalkSendBody {
314
316
  let url: URL;
315
317
  try { url = new URL(ref.value); } catch { throw new EndpointDeliveryError('invalid_payload', 'DingTalk image requires an HTTP(S) URL', 'not_sent'); }
316
318
  if (!['https:', 'http:'].includes(url.protocol)) throw new EndpointDeliveryError('invalid_payload', 'DingTalk image requires an HTTP(S) URL', 'not_sent');
317
- markdownTitle ??= '消息';
319
+ markdownTitle ??= t(locale, 'message');
318
320
  textParts.push(`\n![](${ref.value.replace(/[()\s]/g, character => encodeURIComponent(character))})\n`);
319
321
  break;
320
322
  }
@@ -325,7 +327,7 @@ export function formatOutboundBody(payload: unknown): DingTalkSendBody {
325
327
  case 'file':
326
328
  throw new EndpointDeliveryError('unsupported_operation', 'DingTalk audio/video/file delivery is not implemented', 'not_sent');
327
329
  case 'markdown':
328
- markdownTitle ??= String(data.title || '消息');
330
+ markdownTitle ??= String(data.title || t(locale, 'message'));
329
331
  textParts.push(String(data.content ?? data.text ?? ''));
330
332
  break;
331
333
  case 'link':
@@ -333,7 +335,7 @@ export function formatOutboundBody(payload: unknown): DingTalkSendBody {
333
335
  media = {
334
336
  msgtype: 'link',
335
337
  link: {
336
- title: String(data.title || '链接'),
338
+ title: String(data.title || t(locale, 'link')),
337
339
  text: String(data.text ?? data.content ?? ''),
338
340
  messageUrl: typeof data.url === 'string' ? data.url : undefined,
339
341
  picUrl: typeof data.picUrl === 'string' ? data.picUrl : undefined,