Files
test1/remote-control/config/manager.ts
2026-03-20 07:33:46 +00:00

234 lines
6.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { EventEmitter } from 'node:events'
import fs from 'node:fs/promises'
import path from 'node:path'
import type { RemoteControlConfig, ConfigChangeEvent } from './types'
const DEFAULT_CONFIG: RemoteControlConfig = {
discord: {
enabled: false,
botToken: '',
},
dingtalk: {
enabled: false,
clientId: '',
clientSecret: '',
},
lark: {
enabled: false,
appId: '',
appSecret: '',
},
telegram: {
enabled: false,
botToken: '',
},
slack: {
enabled: false,
botToken: '',
appToken: '',
},
}
function maskValue(value: string): string {
if (!value || value.length < 4) return '••••'
return '••••••••' + value.slice(-4)
}
export class ConfigManager extends EventEmitter {
private static instance: ConfigManager
private config: RemoteControlConfig
private configPath: string
private constructor() {
super()
this.config = { ...DEFAULT_CONFIG }
this.configPath = path.join(process.cwd(), 'remote-control', 'data', 'config.json')
}
private loaded = false
static getInstance(): ConfigManager {
if (!ConfigManager.instance) {
ConfigManager.instance = new ConfigManager()
}
return ConfigManager.instance
}
async ensureLoaded(): Promise<void> {
if (this.loaded) return
await this.load()
// 首次加载后,执行一次和保存配置相同的逻辑:启动已启用的 bot
void reconnectAllPlatforms(this.config)
}
async load(): Promise<void> {
try {
const data = await fs.readFile(this.configPath, 'utf-8')
this.config = { ...DEFAULT_CONFIG, ...JSON.parse(data) }
} catch (error) {
this.config = { ...DEFAULT_CONFIG }
await this.save()
}
this.loaded = true
}
private async save(): Promise<void> {
const dir = path.dirname(this.configPath)
await fs.mkdir(dir, { recursive: true })
await fs.writeFile(this.configPath, JSON.stringify(this.config, null, 2), 'utf-8')
}
get(): RemoteControlConfig {
return { ...this.config }
}
getMasked(): RemoteControlConfig {
return {
discord: {
...this.config.discord,
botToken: maskValue(this.config.discord.botToken),
},
dingtalk: {
...this.config.dingtalk,
clientSecret: maskValue(this.config.dingtalk.clientSecret),
},
lark: {
...this.config.lark,
appSecret: maskValue(this.config.lark.appSecret),
},
telegram: {
...this.config.telegram,
botToken: maskValue(this.config.telegram.botToken),
},
slack: {
...this.config.slack,
botToken: maskValue(this.config.slack.botToken),
appToken: maskValue(this.config.slack.appToken),
},
}
}
async update(newConfig: Partial<RemoteControlConfig>, options?: { skipEmit?: boolean }): Promise<void> {
const previousConfig = { ...this.config }
if (newConfig.discord) {
this.config.discord = { ...this.config.discord, ...newConfig.discord }
}
if (newConfig.dingtalk) {
this.config.dingtalk = { ...this.config.dingtalk, ...newConfig.dingtalk }
}
if (newConfig.lark) {
this.config.lark = { ...this.config.lark, ...newConfig.lark }
}
if (newConfig.telegram) {
this.config.telegram = { ...this.config.telegram, ...newConfig.telegram }
}
if (newConfig.slack) {
this.config.slack = { ...this.config.slack, ...newConfig.slack }
}
await this.save()
if (!options?.skipEmit) {
const changedPlatforms = this.getChangedPlatforms(previousConfig, this.config)
this.emit('config-changed', {
previousConfig,
newConfig: this.config,
changedPlatforms,
} as ConfigChangeEvent)
}
}
private getChangedPlatforms(
prev: RemoteControlConfig,
next: RemoteControlConfig
): ('discord' | 'dingtalk' | 'lark' | 'telegram' | 'slack')[] {
const changed: ('discord' | 'dingtalk' | 'lark' | 'telegram' | 'slack')[] = []
if (JSON.stringify(prev.discord) !== JSON.stringify(next.discord)) {
changed.push('discord')
}
if (JSON.stringify(prev.dingtalk) !== JSON.stringify(next.dingtalk)) {
changed.push('dingtalk')
}
if (JSON.stringify(prev.lark) !== JSON.stringify(next.lark)) {
changed.push('lark')
}
if (JSON.stringify(prev.telegram) !== JSON.stringify(next.telegram)) {
changed.push('telegram')
}
if (JSON.stringify(prev.slack) !== JSON.stringify(next.slack)) {
changed.push('slack')
}
return changed
}
}
/**
* 停止所有 bot然后启动已启用的 bot。
* 服务启动和保存配置时复用同一逻辑。
*/
export async function reconnectAllPlatforms(
config: RemoteControlConfig,
errors?: string[],
): Promise<void> {
// Discord
try {
const bot = await import('@/remote-control/bots/discord')
await bot.stopBot()
if (config.discord.enabled && config.discord.botToken) {
await bot.startBot(config.discord.botToken)
}
} catch (err) {
if (config.discord.enabled) errors?.push(`Discord: ${err instanceof Error ? err.message : String(err)}`)
}
// DingTalk
try {
const bot = await import('@/remote-control/bots/dingtalk')
await bot.stopBot()
if (config.dingtalk.enabled && config.dingtalk.clientId && config.dingtalk.clientSecret) {
await bot.startBot(config.dingtalk.clientId, config.dingtalk.clientSecret)
}
} catch (err) {
if (config.dingtalk.enabled) errors?.push(`钉钉: ${err instanceof Error ? err.message : String(err)}`)
}
// Lark
try {
const bot = await import('@/remote-control/bots/lark')
await bot.stopBot()
if (config.lark.enabled && config.lark.appId && config.lark.appSecret) {
await bot.startBot(config.lark.appId, config.lark.appSecret)
}
} catch (err) {
if (config.lark.enabled) errors?.push(`飞书: ${err instanceof Error ? err.message : String(err)}`)
}
// Telegram
try {
const bot = await import('@/remote-control/bots/telegram')
await bot.stopBot()
if (config.telegram.enabled && config.telegram.botToken) {
await bot.startBot(config.telegram.botToken)
}
} catch (err) {
if (config.telegram.enabled) errors?.push(`Telegram: ${err instanceof Error ? err.message : String(err)}`)
}
// Slack
try {
const bot = await import('@/remote-control/bots/slack')
await bot.stopBot()
if (config.slack.enabled && config.slack.botToken && config.slack.appToken) {
await bot.startBot(config.slack.botToken, config.slack.appToken)
}
} catch (err) {
if (config.slack.enabled) errors?.push(`Slack: ${err instanceof Error ? err.message : String(err)}`)
}
}
export type { RemoteControlConfig, ConfigChangeEvent } from './types'