初始化模版工程

This commit is contained in:
Cloud Bot
2026-03-20 07:33:46 +00:00
commit 23717e0ecd
386 changed files with 51675 additions and 0 deletions

View 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'