diff --git a/CHANGELOG.md b/CHANGELOG.md index f1f261c..e413d5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ 本项目遵循 [约定式提交](https://www.conventionalcommits.org/zh-hans/) 与 [语义化版本](https://semver.org/lang/zh-CN/)。 +## [1.4.1] - 2026-09-27 + +### 变更 + +- **控制台输入框不再区分聊天与指令**:两者本来就是同一个入口 —— + 以 `/` 开头按游戏指令发送,否则按公屏消息发送。因此删掉了「公屏聊天 / 游戏指令」下拉框 + 和重复的「快捷公屏消息」输入框,只保留**一个输入框 + 快捷发送 + 移动测试**。 + 接口返回新增 `kind` 字段说明实际走的是哪条路,Toast 也会区分「消息已发送 / 指令已发送」 +- 快捷发送按钮补上 `/u restore confirm`(配合定时消息常用) + ## [1.4.0] - 2026-09-27 大地图改为跟随机器人、支持可调图幅;新增定时重复发送消息;控制台界面重做并适配移动端。 diff --git a/README.md b/README.md index 0bd6cc8..fdb36d5 100644 --- a/README.md +++ b/README.md @@ -441,7 +441,17 @@ npm start - **中栏**: - 地图页签 —— **大地图**(瓦片 + 领土多边形 + 全部机器人坐标)与 **领土地图**(区块级) - 日志页签 —— **运行日志** / **公屏聊天** / **开拓日志** -- **右栏**:控制台 / 聊天框(含移动测试)+ 8 个标签页的配置表单 +- **右栏**:控制台(统一输入框 + 快捷发送 + 移动测试)+ 8 个标签页的配置表单 + +### 控制台输入框:聊天与指令是同一个入口 + +不需要切换「聊天 / 指令」模式 —— **以 `/` 开头就按游戏指令发送,否则按公屏消息发送**。 +服务端 `/api/bots/:id/chat` 就是这么分流的,返回里带 `kind` 字段说明实际走的是哪条路。 + +另外提供: + +- **快捷发送**:`/u return`、`/u claimmode`、`/u restore confirm`、`/spawn`、`/list` 一键发出 +- **移动测试**:前 / 后 / 左 / 右 / 跳,绕开寻路器直接按控制键 2 秒并回报真实位移 ### 响应式与移动端 @@ -510,8 +520,8 @@ npm start | POST | `/api/bots/:id/move` | 手动移动测试(`direction` / `seconds`),返回真实位移 | | POST | `/api/bots` | 控制台新增玩家(`username` / `password` / `label`) | | DELETE | `/api/bots/:id` | 移除控制台新增的玩家(`.env` 里的会返回 400) | -| POST | `/api/bots/:id/command` | 向该机器人发送游戏指令(要求已登录验证) | -| POST | `/api/bots/:id/chat` | 向该机器人发送公屏消息(要求已登录验证) | +| POST | `/api/bots/:id/chat` | 发送内容:**以 `/` 开头按指令发送,否则按公屏消息发送**(要求已登录验证) | +| POST | `/api/bots/:id/command` | 同上,但强制按指令发送(等价于给内容加 `/` 前缀) | | GET | `/api/bots/:id/chat` | 该机器人的公屏日志(`limit` / `since` / `kind` / `keyword`) | | POST | `/api/bots/:id/chat/export` | 导出该机器人的公屏日志到 `data/` | | GET | `/api/expansion-log` | 开拓日志(`limit` / `botId` / `state` / `since`)+ 统计 | diff --git a/src/bot/botPool.js b/src/bot/botPool.js index c796dd0..27f4ecc 100644 --- a/src/bot/botPool.js +++ b/src/bot/botPool.js @@ -477,18 +477,31 @@ class BotPool extends EventEmitter { * @param {string} message 聊天正文(以 `/` 开头时按指令发送) * @returns {{ok: boolean, message: string}} */ + /** + * 向某机器人发送内容:聊天与指令是同一个入口,**以 `/` 开头按指令发送**,否则按公屏消息发送。 + * + * @param {string} id + * @param {string} message 内容(可以带或不带前导 `/`) + * @returns {{ok: boolean, kind: string, message: string}} + */ sendChat(id, message) { const entry = this.require(id); const text = String(message || '').trim(); - if (!text) return { ok: false, message: '消息为空' }; + if (!text) return { ok: false, kind: 'chat', message: '内容为空' }; + const isCommand = text.startsWith('/'); + const kind = isCommand ? 'command' : 'chat'; if (!entry.manager.canSendChat()) { if (entry.manager.state !== 'online' || !entry.manager.bot) { - return { ok: false, message: `${entry.connection.username} 当前未连接(状态:${entry.manager.state}),消息未发送` }; + return { ok: false, kind, message: `${entry.connection.username} 当前未连接(状态:${entry.manager.state}),未发送` }; } - return { ok: false, message: `${entry.connection.username} 未完成登录验证,已拒绝发送公屏消息(服务器要求验证时会自动放行,用于发送验证码)` }; + return { ok: false, kind, message: `${entry.connection.username} 未完成登录验证,已拒绝发送(服务器要求验证时会自动放行,用于发送验证码)` }; } const ok = entry.manager.sendChat(text); - return { ok, message: ok ? '已发送' : '发送失败' }; + return { + ok, + kind, + message: ok ? (isCommand ? '指令已发送:' + text : '消息已发送') : '发送失败' + }; } /** diff --git a/src/web/public/app.js b/src/web/public/app.js index e4f8b31..ec02f1f 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -709,8 +709,8 @@ function renderStatus(status) { const canOperate = bot.canOperate === true; $('consoleHint').textContent = canOperate - ? '登录验证通过:可以直接向该机器人发送指令或公屏消息。' - : '该机器人尚未通过游戏内登录验证,发送指令 / 消息会被拒绝。'; + ? '以 / 开头按游戏指令发送,否则按公屏消息发送。' + : '该机器人尚未通过游戏内登录验证,发送会被拒绝(服务器要求验证时会自动放行,用于发验证码)。'; $('consoleHint').style.color = canOperate ? 'var(--muted)' : 'var(--yellow)'; // ===== 任务卡片 ===== @@ -1990,13 +1990,9 @@ function bindEvents() { $('cmdInput').addEventListener('keydown', (e) => { if (e.key === 'Enter') sendConsole(); }); - $('btnQuickChat').addEventListener('click', sendQuickChat); - $('quickChatInput').addEventListener('keydown', (e) => { - if (e.key === 'Enter') sendQuickChat(); - }); + // 快捷发送:把预设内容填进唯一的输入框并立即发送 for (const btn of document.querySelectorAll('[data-cmd]')) { btn.addEventListener('click', () => { - $('sendMode').value = 'command'; $('cmdInput').value = btn.dataset.cmd; sendConsole(); }); @@ -2028,33 +2024,22 @@ function bindEvents() { }); $('btnLogout').addEventListener('click', doLogout); + /** + * 发送控制台内容。 + * + * 聊天和指令不区分模式:**以 `/` 开头按游戏指令发送,否则按公屏消息发送** —— + * 服务端 `/api/bots/:id/chat` 就是这么分流的,前端不需要多一个下拉框。 + */ async function sendConsole() { const text = $('cmdInput').value.trim(); if (!text || !selectedBotId) return; - const mode = $('sendMode').value; - const path = mode === 'command' - ? `/api/bots/${encodeURIComponent(selectedBotId)}/command` - : `/api/bots/${encodeURIComponent(selectedBotId)}/chat`; - const body = mode === 'command' ? { command: text } : { message: text }; - try { - await api(path, { method: 'POST', body: JSON.stringify(body) }); - $('cmdInput').value = ''; - toast('已发送'); - } catch (err) { - toast('发送失败:' + err.message, true); - } - } - - async function sendQuickChat() { - const text = $('quickChatInput').value.trim(); - if (!text || !selectedBotId) return; try { await api(`/api/bots/${encodeURIComponent(selectedBotId)}/chat`, { method: 'POST', body: JSON.stringify({ message: text }) }); - $('quickChatInput').value = ''; - toast('公屏消息已发送'); + $('cmdInput').value = ''; + toast(text.startsWith('/') ? '指令已发送:' + text : '消息已发送'); } catch (err) { toast('发送失败:' + err.message, true); } diff --git a/src/web/public/index.html b/src/web/public/index.html index e8283c4..9405318 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -267,27 +267,21 @@
-

控制台 / 聊天框

+

控制台

-
-

登录验证通过后可向该机器人发送指令或公屏消息。

+

以 / 开头按游戏指令发送,否则按公屏消息发送。登录验证通过后才允许发送。

- - +
- - - - -
-
- - + 快捷发送 + + + + +
移动测试 diff --git a/src/web/server.js b/src/web/server.js index f6f2dc6..c14e73b 100644 --- a/src/web/server.js +++ b/src/web/server.js @@ -957,12 +957,14 @@ class SingleBotPool { sendChat(id, message) { const entry = this.require(id); const text = String(message || '').trim(); - if (!text) return { ok: false, message: '消息为空' }; + if (!text) return { ok: false, kind: 'chat', message: '内容为空' }; + const isCommand = text.startsWith('/'); + const kind = isCommand ? 'command' : 'chat'; if (!entry.manager.canSendChat()) { - return { ok: false, message: '未完成登录验证,已拒绝发送公屏消息' }; + return { ok: false, kind, message: '未完成登录验证,已拒绝发送' }; } const ok = entry.manager.sendChat(text); - return { ok, message: ok ? '已发送' : '发送失败' }; + return { ok, kind, message: ok ? (isCommand ? '指令已发送:' + text : '消息已发送') : '发送失败' }; } } diff --git a/test/botPool.test.js b/test/botPool.test.js index 7990581..fcaec6a 100644 --- a/test/botPool.test.js +++ b/test/botPool.test.js @@ -202,7 +202,23 @@ test('空聊天消息被拒绝', () => { const { pool } = setup(); const result = pool.sendChat('BotA', ' '); assert.strictEqual(result.ok, false); - assert.ok(result.message.includes('消息为空')); + assert.ok(result.message.includes('内容为空')); +}); + +test('聊天与指令共用同一入口:以 / 开头按指令发送', () => { + const { pool } = setup(); + const entry = pool.get('BotA'); + entry.manager.bot = { chat: () => {} }; + entry.manager.state = 'online'; + entry.manager.loggedIn = true; + + const chat = pool.sendChat('BotA', '大家好'); + assert.strictEqual(chat.kind, 'chat'); + assert.ok(chat.message.includes('消息已发送')); + + const command = pool.sendChat('BotA', '/u restore confirm'); + assert.strictEqual(command.kind, 'command', '以 / 开头应识别为指令'); + assert.ok(command.message.includes('指令已发送')); }); test('完成登录验证后允许发送指令与聊天', () => {