mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
feat(bot): lock settings reads from guests + persist guestMode
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
d763043305
commit
e0acbf5457
2 files changed
+79
-2
No files matched your search
@@ -159,3 +159,58 @@ describe("bot router /settings", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("bot router /settings guest-mode gating + persistence", () => {
|
||||
let tmpDir: string;
|
||||
let configPath: string;
|
||||
let config: BotConfig;
|
||||
let botDb: BotDatabase;
|
||||
|
||||
beforeEach(() => {
|
||||
botDb = createDatabase(":memory:");
|
||||
tmpDir = mkdtempSync(join(tmpdir(), "botsettings-gm-"));
|
||||
configPath = join(tmpDir, "config.json");
|
||||
config = getDefaultConfig();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
botDb.close();
|
||||
rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
/** Mounts createBotRouter with an injected req.user (no session/cookie). */
|
||||
function mountBot(injectUser: () => unknown): express.Express {
|
||||
const fakeManager = { getAllBots: () => [] } as unknown as BotManager;
|
||||
const avatarStore = createAvatarStore(tmpDir);
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use((req, _res, next) => { (req as { user?: unknown }).user = injectUser(); next(); });
|
||||
app.use(
|
||||
"/api/bot",
|
||||
createBotRouter(fakeManager, config, configPath, pino({ level: "silent" }), botDb, avatarStore),
|
||||
);
|
||||
return app;
|
||||
}
|
||||
|
||||
it("GET /settings is 403 for guests and includes guestMode for admins", async () => {
|
||||
const guestApp = mountBot(() => ({ role: "guest", guest: {} }));
|
||||
expect((await request(guestApp).get("/api/bot/settings")).status).toBe(403);
|
||||
const adminApp = mountBot(() => ({ role: "admin" }));
|
||||
const res = await request(adminApp).get("/api/bot/settings");
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.guestMode).toBeDefined();
|
||||
expect(res.body.guestMode.enabled).toBe(false);
|
||||
});
|
||||
|
||||
it("POST /settings persists a guestMode block", async () => {
|
||||
const adminApp = mountBot(() => ({ role: "admin" }));
|
||||
const res = await request(adminApp).post("/api/bot/settings").send({
|
||||
guestMode: { enabled: true, bots: ["bot1"], permissions: { playNext: true } },
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.guestMode.enabled).toBe(true);
|
||||
expect(res.body.guestMode.bots).toEqual(["bot1"]);
|
||||
expect(res.body.guestMode.permissions.playNext).toBe(true);
|
||||
expect(res.body.guestMode.permissions.addToQueue).toBe(true); // untouched default
|
||||
});
|
||||
});
|
||||
+24
-2
@@ -6,6 +6,8 @@ import type { Logger } from "../../logger.js";
|
||||
import type { BotDatabase } from "../../data/database.js";
|
||||
import type { AvatarStore } from "../../data/avatars.js";
|
||||
import { requirePermission, requireBotAccess } from "../middleware/requirePermission.js";
|
||||
import { requireNotGuest } from "../middleware/requireNotGuest.js";
|
||||
import { GUEST_PERMISSION_FLAGS } from "../../data/permissions.js";
|
||||
|
||||
export function createBotRouter(
|
||||
botManager: BotManager,
|
||||
@@ -29,17 +31,18 @@ export function createBotRouter(
|
||||
|
||||
// GET /api/bot/settings — 读取全局 bot 行为设置
|
||||
// NOTE: must be registered before "/:id" so it isn't shadowed by the param route.
|
||||
router.get("/settings", (_req, res) => {
|
||||
router.get("/settings", requireNotGuest, (_req, res) => {
|
||||
res.json({
|
||||
idleTimeoutMinutes: config.idleTimeoutMinutes ?? 0,
|
||||
autoPauseOnEmpty: config.autoPauseOnEmpty,
|
||||
guestMode: config.guestMode,
|
||||
});
|
||||
});
|
||||
|
||||
// POST /api/bot/settings — 保存全局 bot 行为设置 (gated: changing global bot
|
||||
// behavior is a bot.manage operation, consistent with PR #80's permission model)
|
||||
router.post("/settings", requirePermission("bot.manage"), (req, res) => {
|
||||
const { idleTimeoutMinutes, autoPauseOnEmpty } = req.body;
|
||||
const { idleTimeoutMinutes, autoPauseOnEmpty, guestMode } = req.body;
|
||||
|
||||
const hasIdle = idleTimeoutMinutes !== undefined;
|
||||
if (hasIdle && (typeof idleTimeoutMinutes !== "number" || idleTimeoutMinutes < 0)) {
|
||||
@@ -51,6 +54,24 @@ export function createBotRouter(
|
||||
|
||||
if (hasIdle) config.idleTimeoutMinutes = idleTimeoutMinutes;
|
||||
if (hasAutoPause) config.autoPauseOnEmpty = autoPauseOnEmpty;
|
||||
|
||||
if (guestMode !== undefined && guestMode !== null && typeof guestMode === "object") {
|
||||
const gm = config.guestMode;
|
||||
if (typeof guestMode.enabled === "boolean") gm.enabled = guestMode.enabled;
|
||||
if (guestMode.bots === "all") {
|
||||
gm.bots = "all";
|
||||
} else if (Array.isArray(guestMode.bots)) {
|
||||
gm.bots = guestMode.bots.filter((id: unknown): id is string => typeof id === "string");
|
||||
}
|
||||
if (guestMode.permissions && typeof guestMode.permissions === "object") {
|
||||
for (const f of GUEST_PERMISSION_FLAGS) {
|
||||
if (typeof guestMode.permissions[f] === "boolean") {
|
||||
gm.permissions[f] = guestMode.permissions[f];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
saveConfig(configPath, config);
|
||||
|
||||
// 通知所有 bot 实例更新
|
||||
@@ -62,6 +83,7 @@ export function createBotRouter(
|
||||
res.json({
|
||||
idleTimeoutMinutes: config.idleTimeoutMinutes ?? 0,
|
||||
autoPauseOnEmpty: config.autoPauseOnEmpty,
|
||||
guestMode: config.guestMode,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user