diff --git a/src/components/cards/NotificationChannelCard.vue b/src/components/cards/NotificationChannelCard.vue
index 9469026a..43c3bde5 100644
--- a/src/components/cards/NotificationChannelCard.vue
+++ b/src/components/cards/NotificationChannelCard.vue
@@ -5,7 +5,9 @@ import { useI18n } from 'vue-i18n'
import { openSharedDialog } from '@/composables/useSharedDialog'
import { useCardAccentColor } from '@/composables/useCardAccentColor'
-const NotificationChannelInfoDialog = defineAsyncComponent(() => import('@/components/dialog/NotificationChannelInfoDialog.vue'))
+const NotificationChannelInfoDialog = defineAsyncComponent(
+ () => import('@/components/dialog/NotificationChannelInfoDialog.vue'),
+)
const { t } = useI18n()
const { accentRgb, imageRef, updateAccentColor } = useCardAccentColor()
@@ -38,6 +40,7 @@ const notificationTypeNames: { [key: string]: string } = {
synologychat: t('notification.synologychat.name'),
slack: t('notification.slack.name'),
discord: t('notification.discord.name'),
+ dingtalk: t('notification.dingtalk.name'),
webpush: t('notification.webpush.name'),
custom: t('setting.notification.custom'),
}
@@ -79,6 +82,8 @@ const getIcon = computed(() => {
return getLogoUrl('slack')
case 'discord':
return getLogoUrl('discord')
+ case 'dingtalk':
+ return getLogoUrl('notification')
case 'webpush':
return getLogoUrl('chrome')
default:
diff --git a/src/components/dialog/NotificationChannelInfoDialog.vue b/src/components/dialog/NotificationChannelInfoDialog.vue
index 6cee6597..1b9f16ef 100644
--- a/src/components/dialog/NotificationChannelInfoDialog.vue
+++ b/src/components/dialog/NotificationChannelInfoDialog.vue
@@ -363,12 +363,7 @@ async function logoutWechatClawBot() {
}
wechatClawBotActionLoading.value = true
try {
- await manageNotificationChannel(
- 'WechatClawBot',
- 'logout',
- getWechatClawBotRequestParams(),
- { feedback: 'silent' },
- )
+ await manageNotificationChannel('WechatClawBot', 'logout', getWechatClawBotRequestParams(), { feedback: 'silent' })
$toast.success(t('notification.wechatclawbot.logoutSuccess'))
await fetchWechatClawBotStatus({
autoGenerateQrcode: true,
@@ -1008,6 +1003,37 @@ onMounted(() => {
/>
+
+
+
+
+
+
+
+
+
+
+
{
['telegram', {}, 'notification.telegram.tokenRequired'],
['slack', {}, 'notification.slack.oauthTokenRequired'],
['synologychat', {}, 'notification.synologychat.webhookRequired'],
+ ['dingtalk', {}, 'notification.dingtalk.webhookRequired'],
['vocechat', {}, 'notification.vocechat.hostRequired'],
['webpush', {}, 'notification.webpush.usernameRequired'],
['qqbot', {}, 'notification.qqbot.appIdRequired'],
@@ -399,6 +400,7 @@ describe('useSetupWizard defaults, selection and validation', () => {
['telegram', { TELEGRAM_CHAT_ID: 'chat', TELEGRAM_TOKEN: 'token' }],
['slack', { SLACK_CHANNEL: 'channel', SLACK_OAUTH_TOKEN: 'token' }],
['synologychat', { SYNOLOGYCHAT_WEBHOOK: 'https://chat.example.com' }],
+ ['dingtalk', { DINGTALK_WEBHOOK: 'https://oapi.dingtalk.com/robot/send?access_token=test' }],
['vocechat', { VOCECHAT_API_KEY: 'key', VOCECHAT_HOST: 'https://voce.example.com' }],
['webpush', { WEBPUSH_USERNAME: 'admin' }],
['qqbot', { QQ_APP_ID: 'app', QQ_APP_SECRET: 'secret' }],
diff --git a/src/composables/useSetupWizard.ts b/src/composables/useSetupWizard.ts
index b8f6eebe..5d0ea0e1 100644
--- a/src/composables/useSetupWizard.ts
+++ b/src/composables/useSetupWizard.ts
@@ -358,6 +358,7 @@ export function useSetupWizard() {
'wechat': 'WechatModule',
'wechatclawbot': 'WechatClawBotModule',
'slack': 'SlackModule',
+ 'dingtalk': 'DingTalkModule',
'synologychat': 'SynologyChatModule',
'qqbot': 'QQBotModule',
'vocechat': 'VoceChatModule',
@@ -457,6 +458,7 @@ export function useSetupWizard() {
wechatclawbot: '微信 ClawBot',
telegram: 'Telegram',
slack: 'Slack',
+ dingtalk: '钉钉',
synologychat: 'SynologyChat',
qqbot: 'QQ',
vocechat: 'VoceChat',
@@ -730,6 +732,12 @@ export function useSetupWizard() {
validationErrors.value.notification.SYNOLOGYCHAT_WEBHOOK = true
}
break
+ case 'dingtalk':
+ if (!config.DINGTALK_WEBHOOK?.trim()) {
+ errors.push(t('notification.dingtalk.webhookRequired'))
+ validationErrors.value.notification.DINGTALK_WEBHOOK = true
+ }
+ break
case 'vocechat':
if (!config.VOCECHAT_HOST?.trim()) {
errors.push(t('notification.vocechat.hostRequired'))
diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts
index 47a0651c..ad294b56 100644
--- a/src/locales/en-US.ts
+++ b/src/locales/en-US.ts
@@ -545,7 +545,7 @@ export default {
notification: {
title: 'Notifications',
description:
- 'Notification channels (WeChat Work, WeChat ClawBot, Telegram, Slack, SynologyChat, VoceChat, WebPush), message scope',
+ 'Notification channels (WeChat Work, WeChat ClawBot, Telegram, Slack, DingTalk, SynologyChat, VoceChat, WebPush), message scope',
},
about: {
title: 'About',
@@ -785,6 +785,15 @@ export default {
adminsHint: 'User IDs that can use admin menu and commands, separated by commas',
adminsPlaceholder: 'User IDs list, separated by commas',
},
+ dingtalk: {
+ name: 'DingTalk',
+ webhook: 'Custom Robot Webhook',
+ webhookHint: 'Full webhook URL of the DingTalk group custom robot',
+ webhookRequired: 'Webhook URL cannot be empty',
+ secret: 'Signing Secret',
+ secretHint:
+ 'Enter the SEC-prefixed secret when signature security is enabled; leave empty for keyword or IP allowlist security',
+ },
synologychat: {
name: 'Synology Chat',
webhook: 'Webhook URL',
@@ -2482,6 +2491,7 @@ export default {
other: 'Other',
telegram: 'Telegram',
slack: 'Slack',
+ dingTalk: 'DingTalk',
synologyChat: 'SynologyChat',
voceChat: 'VoceChat',
webPush: 'WebPush',
diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts
index d200d0d6..28334487 100644
--- a/src/locales/zh-CN.ts
+++ b/src/locales/zh-CN.ts
@@ -536,7 +536,8 @@ export default {
},
notification: {
title: '通知',
- description: '通知渠道(企业微信、微信 ClawBot、Telegram、Slack、SynologyChat、VoceChat、WebPush)、消息发送范围',
+ description:
+ '通知渠道(企业微信、微信 ClawBot、Telegram、Slack、钉钉、SynologyChat、VoceChat、WebPush)、消息发送范围',
},
about: {
title: '关于',
@@ -774,6 +775,14 @@ export default {
adminsHint: '可使用管理菜单及命令的用户ID列表,多个ID使用,分隔',
adminsPlaceholder: '用户ID列表,多个ID使用,分隔',
},
+ dingtalk: {
+ name: '钉钉',
+ webhook: '自定义机器人 Webhook',
+ webhookHint: '钉钉群自定义机器人的完整 Webhook 地址',
+ webhookRequired: 'Webhook 地址不能为空',
+ secret: '加签密钥',
+ secretHint: '机器人安全设置启用“加签”时填写,以 SEC 开头;使用关键词或 IP 白名单时可留空',
+ },
synologychat: {
name: 'Synology Chat',
webhook: '机器人传入URL',
@@ -2437,6 +2446,7 @@ export default {
other: '其它',
telegram: 'Telegram',
slack: 'Slack',
+ dingTalk: '钉钉',
synologyChat: 'SynologyChat',
voceChat: 'VoceChat',
webPush: 'WebPush',
diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts
index 46af9dcf..ab6c3c28 100644
--- a/src/locales/zh-TW.ts
+++ b/src/locales/zh-TW.ts
@@ -536,7 +536,8 @@ export default {
},
notification: {
title: '通知',
- description: '通知渠道(企業微信、微信 ClawBot、Telegram、Slack、SynologyChat、VoceChat、WebPush)、消息發送範圍',
+ description:
+ '通知渠道(企業微信、微信 ClawBot、Telegram、Slack、釘釘、SynologyChat、VoceChat、WebPush)、消息發送範圍',
},
about: {
title: '關於',
@@ -774,6 +775,14 @@ export default {
adminsHint: '可使用管理菜單及命令的用戶ID列表,多個ID使用,分隔',
adminsPlaceholder: '用戶ID列表,多個ID使用,分隔',
},
+ dingtalk: {
+ name: '釘釘',
+ webhook: '自定義機器人 Webhook',
+ webhookHint: '釘釘群自定義機器人的完整 Webhook 地址',
+ webhookRequired: 'Webhook 地址不能為空',
+ secret: '加簽密鑰',
+ secretHint: '機器人安全設置啟用“加簽”時填寫,以 SEC 開頭;使用關鍵詞或 IP 白名單時可留空',
+ },
synologychat: {
name: 'Synology Chat',
webhook: '機器人傳入URL',
@@ -2436,6 +2445,7 @@ export default {
other: '其它',
telegram: 'Telegram',
slack: 'Slack',
+ dingTalk: '釘釘',
synologyChat: 'SynologyChat',
voceChat: 'VoceChat',
webPush: 'WebPush',
diff --git a/src/views/setting/AccountSettingNotification.vue b/src/views/setting/AccountSettingNotification.vue
index 591a3fe2..8acde490 100644
--- a/src/views/setting/AccountSettingNotification.vue
+++ b/src/views/setting/AccountSettingNotification.vue
@@ -455,6 +455,9 @@ useSilentSettingRefresh(loadPageData, {
Discord
+
+ {{ t('setting.notification.dingTalk') }}
+
{{ t('setting.notification.synologyChat') }}
diff --git a/src/views/setting/__tests__/AccountSettingNotification.spec.ts b/src/views/setting/__tests__/AccountSettingNotification.spec.ts
index 9dfe7881..037cea2b 100644
--- a/src/views/setting/__tests__/AccountSettingNotification.spec.ts
+++ b/src/views/setting/__tests__/AccountSettingNotification.spec.ts
@@ -180,6 +180,18 @@ describe('AccountSettingNotification', () => {
expect(mocks.toastSuccess).toHaveBeenCalledWith('通知设置保存成功')
})
+ it('offers DingTalk as a native notification channel', async () => {
+ const user = userEvent.setup()
+ await renderNotificationSettings()
+ await screen.findByText('Alpha / wechatclawbot')
+
+ const channelCard = getCard('通知渠道')
+ await user.click(channelCard.getAllByRole('button').at(-1)!)
+ await user.click(await screen.findByText('钉钉', { selector: '.v-list-item-title' }))
+
+ expect(screen.getByText('通知4 / dingtalk')).toBeInTheDocument()
+ })
+
it('compresses chained ClawBot renames and migrates the original source before saving channels', async () => {
const user = userEvent.setup()
await renderNotificationSettings()
diff --git a/src/views/setup/NotificationSettingsStep.vue b/src/views/setup/NotificationSettingsStep.vue
index 4c8119a3..7e58e176 100644
--- a/src/views/setup/NotificationSettingsStep.vue
+++ b/src/views/setup/NotificationSettingsStep.vue
@@ -118,6 +118,19 @@ const notificationTypes = [
+
+
+
+
+ 钉钉
+
+
+
+
+
+
+
+
+
+
+
+
+
+