mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
feat(commands): add canRunCommand gate helper + admin-set source of truth
This commit is contained in:
1 parent
0c7f7e128b
commit
f98ce47c52
2 files changed
+61
-7
No files matched your search
@@ -1,5 +1,6 @@
|
|||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect } from "vitest";
|
||||||
import { parseCommand } from "./commands.js";
|
import { parseCommand } from "./commands.js";
|
||||||
|
import { canRunCommand, isAdminCommand } from "./commands.js";
|
||||||
|
|
||||||
describe("Command Parser", () => {
|
describe("Command Parser", () => {
|
||||||
it("parses simple command", () => {
|
it("parses simple command", () => {
|
||||||
@@ -60,3 +61,36 @@ describe("Command Parser", () => {
|
|||||||
expect(result!.args).toBe("3");
|
expect(result!.args).toBe("3");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("isAdminCommand classification", () => {
|
||||||
|
it("treats stop/clear/remove/move/vol/mode as admin", () => {
|
||||||
|
for (const c of ["stop", "clear", "remove", "move", "vol", "mode"]) {
|
||||||
|
expect(isAdminCommand(c)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it("treats follow and play as NOT admin", () => {
|
||||||
|
expect(isAdminCommand("follow")).toBe(false);
|
||||||
|
expect(isAdminCommand("play")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("canRunCommand", () => {
|
||||||
|
it("allows any public command regardless of groups", () => {
|
||||||
|
expect(canRunCommand("play", [], [6])).toBe(true);
|
||||||
|
expect(canRunCommand("follow", [], [6])).toBe(true);
|
||||||
|
});
|
||||||
|
it("allows admin command when enforcement is off (empty adminGroups)", () => {
|
||||||
|
expect(canRunCommand("stop", [], [])).toBe(true);
|
||||||
|
});
|
||||||
|
it("allows admin command when an invoker group matches (string vs number)", () => {
|
||||||
|
expect(canRunCommand("stop", ["6"], [6])).toBe(true);
|
||||||
|
expect(canRunCommand("stop", [6], [6])).toBe(true);
|
||||||
|
expect(canRunCommand("vol", ["8", "6"], [6])).toBe(true);
|
||||||
|
});
|
||||||
|
it("denies admin command when no invoker group matches", () => {
|
||||||
|
expect(canRunCommand("stop", ["8"], [6])).toBe(false);
|
||||||
|
});
|
||||||
|
it("denies admin command when invoker has no groups and enforcement is on", () => {
|
||||||
|
expect(canRunCommand("clear", [], [6])).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
+27
-7
@@ -5,14 +5,13 @@ export interface ParsedCommand {
|
|||||||
flags: Set<string>;
|
flags: Set<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PUBLIC_COMMANDS = new Set([
|
/**
|
||||||
"play", "add", "queue", "list", "now", "lyrics", "vote", "help",
|
* The fixed set of "admin" chat commands. This is the SINGLE source of truth
|
||||||
"playlist", "album", "fm", "prev", "next", "skip", "pause", "resume",
|
* for which commands the permission gate restricts; reclassifying a command is
|
||||||
"artist",
|
* a one-line edit here. Everything not in this set is public.
|
||||||
]);
|
*/
|
||||||
|
|
||||||
export const ADMIN_COMMANDS = new Set([
|
export const ADMIN_COMMANDS = new Set([
|
||||||
"stop", "clear", "move", "vol", "mode", "follow", "remove",
|
"stop", "clear", "remove", "move", "vol", "mode",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export function parseCommand(
|
export function parseCommand(
|
||||||
@@ -59,3 +58,24 @@ export function parseCommand(
|
|||||||
export function isAdminCommand(commandName: string): boolean {
|
export function isAdminCommand(commandName: string): boolean {
|
||||||
return ADMIN_COMMANDS.has(commandName);
|
return ADMIN_COMMANDS.has(commandName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decide whether a chat command may run, given the invoker's TS server groups
|
||||||
|
* and the configured admin groups. Pure + synchronous so it is trivially unit
|
||||||
|
* tested and reused by the async gate in BotInstance.
|
||||||
|
*
|
||||||
|
* Allowed iff: (1) it is a public command, OR (2) enforcement is off
|
||||||
|
* (adminGroups empty), OR (3) some invoker group is in adminGroups.
|
||||||
|
* invokerGroups (strings from TS) and adminGroups (numbers) are normalized to
|
||||||
|
* strings before comparison so "6" matches 6.
|
||||||
|
*/
|
||||||
|
export function canRunCommand(
|
||||||
|
commandName: string,
|
||||||
|
invokerGroups: readonly (string | number)[],
|
||||||
|
adminGroups: readonly number[],
|
||||||
|
): boolean {
|
||||||
|
if (!isAdminCommand(commandName)) return true;
|
||||||
|
if (adminGroups.length === 0) return true;
|
||||||
|
const admin = new Set(adminGroups.map((g) => String(g)));
|
||||||
|
return invokerGroups.some((g) => admin.has(String(g)));
|
||||||
|
}
|
||||||
Reference in new issue
Block a user