diff --git a/packages/client/src/api/hermes/config.ts b/packages/client/src/api/hermes/config.ts index 53243ee..82be950 100644 --- a/packages/client/src/api/hermes/config.ts +++ b/packages/client/src/api/hermes/config.ts @@ -59,6 +59,7 @@ export interface AppConfig { wecom?: Record feishu?: Record dingtalk?: Record + qqbot?: Record platforms?: Record [key: string]: any } diff --git a/packages/client/src/components/hermes/settings/PlatformSettings.vue b/packages/client/src/components/hermes/settings/PlatformSettings.vue index fc4481a..bc1ae83 100644 --- a/packages/client/src/components/hermes/settings/PlatformSettings.vue +++ b/packages/client/src/components/hermes/settings/PlatformSettings.vue @@ -52,6 +52,10 @@ function getCreds(key: string) { return (settingsStore.platforms[key] || {}) as Record } +function boolValue(value: unknown) { + return value === true || value === 'true' +} + // Weixin QR code login state const wxQrUrl = ref('') const wxQrId = ref('') @@ -152,6 +156,18 @@ const platforms = [ exclusive: true, icon: '', }, + { + key: 'dingtalk', + name: 'DingTalk', + exclusive: true, + icon: '', + }, + { + key: 'qqbot', + name: 'QQBot', + exclusive: true, + icon: '', + }, { key: 'weixin', name: 'Weixin', @@ -302,6 +318,12 @@ const platforms = [ + + + + + + @@ -310,6 +332,25 @@ const platforms = [ + + + - - - - - - - - - -
diff --git a/packages/server/src/controllers/hermes/config.ts b/packages/server/src/controllers/hermes/config.ts index b6a24f9..82d1c05 100644 --- a/packages/server/src/controllers/hermes/config.ts +++ b/packages/server/src/controllers/hermes/config.ts @@ -7,7 +7,7 @@ import { safeFileStore } from '../../services/safe-file-store' const PLATFORM_SECTIONS = new Set([ 'telegram', 'discord', 'slack', 'whatsapp', 'matrix', - 'weixin', 'wecom', 'feishu', 'dingtalk', + 'weixin', 'wecom', 'feishu', 'dingtalk', 'qqbot', 'approvals', ]) @@ -25,6 +25,12 @@ const envPlatformMap: Record = { DINGTALK_CLIENT_ID: ['dingtalk', 'extra.client_id'], DINGTALK_CLIENT_SECRET: ['dingtalk', 'extra.client_secret'], DINGTALK_APP_KEY: ['dingtalk', 'extra.app_key'], + DINGTALK_ALLOWED_USERS: ['dingtalk', 'allowed_users'], + DINGTALK_ALLOW_ALL_USERS: ['dingtalk', 'allow_all_users'], + QQ_APP_ID: ['qqbot', 'extra.app_id'], + QQ_CLIENT_SECRET: ['qqbot', 'extra.client_secret'], + QQ_ALLOWED_USERS: ['qqbot', 'allowed_users'], + QQ_ALLOW_ALL_USERS: ['qqbot', 'allow_all_users'], WECOM_BOT_ID: ['wecom', 'extra.bot_id'], WECOM_SECRET: ['wecom', 'extra.secret'], WEIXIN_TOKEN: ['weixin', 'token'], @@ -82,7 +88,7 @@ async function readEnvPlatforms(): Promise> { if (val === undefined || val === '') continue if (!platforms[platform]) platforms[platform] = {} let finalVal: any = val - if (cfgPath === 'enabled') finalVal = val === 'true' + if (cfgPath === 'enabled' || cfgPath === 'allow_all_users') finalVal = val === 'true' setNested(platforms[platform], cfgPath, finalVal) } return platforms @@ -100,7 +106,7 @@ export async function getConfig(ctx: any) { if (Object.keys(envPlatforms).length > 0) { const existing = config.platforms || {} for (const [platform, vals] of Object.entries(envPlatforms)) { - existing[platform] = { ...(existing[platform] || {}), ...(vals as Record) } + existing[platform] = deepMerge(existing[platform] || {}, vals as Record) } config.platforms = existing } diff --git a/tests/server/config-controller-file-lock.test.ts b/tests/server/config-controller-file-lock.test.ts index 11cd80c..cebc5be 100644 --- a/tests/server/config-controller-file-lock.test.ts +++ b/tests/server/config-controller-file-lock.test.ts @@ -105,4 +105,40 @@ describe('config controller locked file updates', () => { expect(config.platforms.weixin.extra.base_url).toBe('https://old.example') expect(config.model.default).toBe('glm-5.1') }) + + it('writes QQBot credentials to env and overlays them into platform config reads', async () => { + await writeFile(join(hermesHome, 'config.yaml'), [ + 'platforms:', + ' qqbot:', + ' extra:', + ' markdown_support: true', + '', + ].join('\n'), 'utf-8') + await writeFile(join(hermesHome, '.env'), 'OPENROUTER_API_KEY=keep\n', 'utf-8') + const { updateCredentials, getConfig } = await loadController() + + await updateCredentials(makeCtx({ + platform: 'qqbot', + values: { + extra: { app_id: 'qq-app', client_secret: 'qq-secret' }, + allowed_users: 'user-1,user-2', + allow_all_users: false, + }, + })) + + const env = await readFile(join(hermesHome, '.env'), 'utf-8') + expect(env).toContain('OPENROUTER_API_KEY=keep') + expect(env).toContain('QQ_APP_ID=qq-app') + expect(env).toContain('QQ_CLIENT_SECRET=qq-secret') + expect(env).toContain('QQ_ALLOWED_USERS=user-1,user-2') + expect(env).toContain('QQ_ALLOW_ALL_USERS=false') + + const ctx = makeCtx({}) + await getConfig(ctx) + expect(ctx.body.platforms.qqbot.extra.app_id).toBe('qq-app') + expect(ctx.body.platforms.qqbot.extra.client_secret).toBe('qq-secret') + expect(ctx.body.platforms.qqbot.extra.markdown_support).toBe(true) + expect(ctx.body.platforms.qqbot.allowed_users).toBe('user-1,user-2') + expect(ctx.body.platforms.qqbot.allow_all_users).toBe(false) + }) })