From b090a8ec216127c3cd3e557856d2005c6933b735 Mon Sep 17 00:00:00 2001 From: saopig1 <4x7sw862st@gmail.com> Date: Fri, 26 Jun 2026 20:35:31 +0800 Subject: [PATCH] feat(ts-protocol): surface invokerGroups on TS3TextMessage via pure mapper --- src/ts-protocol/client.ts | 27 +++++++++++------ src/ts-protocol/text-message.test.ts | 43 ++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 src/ts-protocol/text-message.test.ts diff --git a/src/ts-protocol/client.ts b/src/ts-protocol/client.ts index f743421..31b317e 100644 --- a/src/ts-protocol/client.ts +++ b/src/ts-protocol/client.ts @@ -61,6 +61,24 @@ export interface TS3TextMessage { invokerUid: string; message: string; targetMode: number; // 1=private, 2=channel, 3=server + invokerGroups: string[]; // sender's TS server-group ids; [] when not in view cache +} + +/** + * Map the library's TextMessage to our wrapper. Preserves invokerGroups (the + * sender's TS server groups), which the library populates only when the sender + * is in the bot's client-view cache; otherwise it is []. Used by the chat + * command permission gate. + */ +export function toTS3TextMessage(msg: TextMessage): TS3TextMessage { + return { + invokerName: msg.invokerName, + invokerId: String(msg.invokerID), + invokerUid: msg.invokerUID, + message: msg.message, + targetMode: msg.targetMode, + invokerGroups: msg.invokerGroups ?? [], + }; } export class TS3Client extends EventEmitter { @@ -203,14 +221,7 @@ export class TS3Client extends EventEmitter { }); this.client.on("textMessage", (msg: TextMessage) => { - const tsMsg: TS3TextMessage = { - invokerName: msg.invokerName, - invokerId: String(msg.invokerID), - invokerUid: msg.invokerUID, - message: msg.message, - targetMode: msg.targetMode, - }; - this.emit("textMessage", tsMsg); + this.emit("textMessage", toTS3TextMessage(msg)); }); this.client.on("disconnected", (err) => { diff --git a/src/ts-protocol/text-message.test.ts b/src/ts-protocol/text-message.test.ts new file mode 100644 index 0000000..e83e30b --- /dev/null +++ b/src/ts-protocol/text-message.test.ts @@ -0,0 +1,43 @@ +import { describe, it, expect } from "vitest"; +import { toTS3TextMessage } from "./client.js"; +import type { TextMessage } from "@honeybbq/teamspeak-client"; + +function makeMsg(over: Partial = {}): TextMessage { + return { + invokerName: "Alice", + invokerUID: "uid-abc", + message: "!stop", + invokerGroups: ["6", "8"], + targetMode: 2, + targetID: 0n, + invokerID: 5, + ...over, + }; +} + +describe("toTS3TextMessage", () => { + it("maps core fields and stringifies invokerID", () => { + const r = toTS3TextMessage(makeMsg()); + expect(r.invokerName).toBe("Alice"); + expect(r.invokerId).toBe("5"); + expect(r.invokerUid).toBe("uid-abc"); + expect(r.message).toBe("!stop"); + expect(r.targetMode).toBe(2); + }); + + it("preserves the sender's server groups", () => { + expect(toTS3TextMessage(makeMsg({ invokerGroups: ["6"] })).invokerGroups).toEqual(["6"]); + }); + + it("defaults missing invokerGroups to an empty array", () => { + const partial = { + invokerName: "Bob", + invokerUID: "u", + message: "!stop", + targetMode: 1, + targetID: 0n, + invokerID: 7, + } as unknown as TextMessage; + expect(toTS3TextMessage(partial).invokerGroups).toEqual([]); + }); +});