初始化模版工程
This commit is contained in:
233
remote-control/config/manager.ts
Normal file
233
remote-control/config/manager.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
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'
|
||||
Reference in New Issue
Block a user