diff --git a/src/bot/manager.ts b/src/bot/manager.ts index 9c5485b..052100b 100644 --- a/src/bot/manager.ts +++ b/src/bot/manager.ts @@ -93,6 +93,27 @@ export class BotManager { this.logger.info({ botId: id }, "Bot instance removed"); } + updateBot(id: string, params: Partial): void { + const instances = this.database.getBotInstances(); + const existing = instances.find((i) => i.id === id); + if (!existing) throw new Error(`Bot ${id} not found`); + + this.database.saveBotInstance({ + ...existing, + name: params.name ?? existing.name, + serverAddress: params.serverAddress ?? existing.serverAddress, + serverPort: params.serverPort ?? existing.serverPort, + nickname: params.nickname ?? existing.nickname, + defaultChannel: params.defaultChannel ?? existing.defaultChannel, + channelPassword: params.channelPassword ?? existing.channelPassword, + }); + this.logger.info({ botId: id }, "Bot instance config updated (restart to apply)"); + } + + getBotConfig(id: string): import("../data/database.js").BotInstance | undefined { + return this.database.getBotInstances().find((i) => i.id === id); + } + getBot(id: string): BotInstance | undefined { return this.bots.get(id); } diff --git a/src/web/api/bot.ts b/src/web/api/bot.ts index a85e308..2d4d51b 100644 --- a/src/web/api/bot.ts +++ b/src/web/api/bot.ts @@ -24,6 +24,16 @@ export function createBotRouter( res.json(bot.getStatus()); }); + // Get saved config for a bot + router.get("/:id/config", (req, res) => { + const saved = botManager.getBotConfig(req.params.id); + if (!saved) { + res.status(404).json({ error: "Bot config not found" }); + return; + } + res.json(saved); + }); + router.post("/", async (req, res) => { try { const { @@ -57,6 +67,26 @@ export function createBotRouter( } }); + // Update bot config (must be stopped first to apply connection changes) + router.put("/:id", async (req, res) => { + try { + const bot = botManager.getBot(req.params.id); + if (!bot) { + res.status(404).json({ error: "Bot not found" }); + return; + } + const { name, serverAddress, serverPort, nickname, defaultChannel, channelPassword } = req.body; + // Update in database + botManager.updateBot(req.params.id, { + name, serverAddress, serverPort, nickname, defaultChannel, channelPassword, + }); + res.json({ success: true }); + } catch (err) { + logger.error({ err }, "Failed to update bot"); + res.status(500).json({ error: (err as Error).message }); + } + }); + router.delete("/:id", async (req, res) => { try { await botManager.removeBot(req.params.id); diff --git a/web/src/views/Settings.vue b/web/src/views/Settings.vue index c581a93..ce2d82c 100644 --- a/web/src/views/Settings.vue +++ b/web/src/views/Settings.vue @@ -36,17 +36,79 @@ + + + +
+
+ +
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+ +
+
+ +

创建新实例

-
- - - +
+ +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
@@ -315,6 +377,21 @@ const store = usePlayerStore(); const newBotName = ref(''); const newBotServer = ref(''); +const newBotPort = ref(9987); +const newBotNickname = ref('MusicBot'); +const newBotChannel = ref(''); + +// Edit bot +const editingBot = ref(null); +const editForm = reactive({ + name: '', + serverAddress: '', + serverPort: 9987, + nickname: '', + defaultChannel: '', + channelPassword: '', +}); + const neteaseCookie = ref(''); const qqCookie = ref(''); const bilibiliCookie = ref(''); @@ -465,12 +542,59 @@ async function createBot() { await axios.post('/api/bot', { name: newBotName.value, serverAddress: newBotServer.value, - serverPort: 9987, - nickname: newBotName.value, + serverPort: newBotPort.value || 9987, + nickname: newBotNickname.value || newBotName.value, + defaultChannel: newBotChannel.value || undefined, autoStart: false, }); newBotName.value = ''; newBotServer.value = ''; + newBotPort.value = 9987; + newBotNickname.value = 'MusicBot'; + newBotChannel.value = ''; + await store.fetchBots(); + } catch { + // Ignore + } +} + +async function deleteBot(botId: string, botName: string) { + if (!confirm(`确认删除机器人 "${botName}"?此操作不可撤销。`)) return; + try { + await axios.delete(`/api/bot/${botId}`); + await store.fetchBots(); + } catch { + // Ignore + } +} + +function openEditBot(bot: any) { + editingBot.value = bot.id; + // We need to fetch the saved config from server + // For now use the status info we have + editForm.name = bot.name; + editForm.serverAddress = ''; + editForm.serverPort = 9987; + editForm.nickname = ''; + editForm.defaultChannel = ''; + editForm.channelPassword = ''; + // Try to get saved config + axios.get(`/api/bot/${bot.id}/config`).then(res => { + if (res.data) { + editForm.serverAddress = res.data.serverAddress ?? ''; + editForm.serverPort = res.data.serverPort ?? 9987; + editForm.nickname = res.data.nickname ?? ''; + editForm.defaultChannel = res.data.defaultChannel ?? ''; + editForm.channelPassword = res.data.channelPassword ?? ''; + } + }).catch(() => {}); +} + +async function saveEditBot() { + if (!editingBot.value) return; + try { + await axios.put(`/api/bot/${editingBot.value}`, editForm); + editingBot.value = null; await store.fetchBots(); } catch { // Ignore @@ -848,6 +972,75 @@ onUnmounted(() => { &:hover { background: var(--color-primary); color: white; } } +.btn-edit { + padding: 6px 8px; + font-size: 14px; +} + +.btn-delete { + padding: 6px 8px; + font-size: 14px; + &:hover { background: #f44336; color: white; } +} + +.btn-secondary { + padding: 10px 20px; + background: var(--hover-bg); + border-radius: var(--radius-sm); + font-size: 13px; + font-weight: 600; +} + +.bot-actions { + display: flex; + gap: 6px; + align-items: center; +} + +.form-group { + margin-bottom: 12px; + label { + display: block; + font-size: 12px; + font-weight: 600; + margin-bottom: 4px; + opacity: 0.7; + } +} + +.edit-modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 200; + display: flex; + align-items: center; + justify-content: center; +} + +.edit-modal { + background: var(--bg-secondary); + border-radius: var(--radius-lg); + padding: 28px; + width: 480px; + max-width: 90vw; + max-height: 80vh; + overflow-y: auto; +} + +.modal-title { + font-size: 20px; + font-weight: 700; + margin-bottom: 20px; +} + +.modal-actions { + display: flex; + gap: 10px; + justify-content: flex-end; + margin-top: 20px; +} + .spin { animation: spin 1s linear infinite; }