import http from "node:http"; import https from "node:https"; export interface HttpQueryOptions { host: string; port: number; // 10080 (HTTP) or 10443 (HTTPS) useTls?: boolean; apiKey?: string; timeoutMs?: number; } export interface HttpQueryResult { status: number; body: unknown; } /** * TS6 HTTP Query client. * * TeamSpeak 6 Server replaces the TS3 raw-TCP ServerQuery (port 10011) * with an HTTP/HTTPS API on ports 10080/10443. * * Common endpoints (TS6 HTTP Query): * GET / → server info / health check * POST /api-key → create API key * GET /1/serverlist → list virtual servers * GET /1/clientlist?sid={sid} → list clients * POST /1/sendtextmessage → send text message * POST /1/clientmove → move a client * GET /1/channellist?sid={sid} → list channels * POST /1/clientupdate → update client properties */ export class TS6HttpQuery { private options: Required; constructor(options: HttpQueryOptions) { this.options = { host: options.host, port: options.port, useTls: options.useTls ?? options.port === 10443, apiKey: options.apiKey ?? "", timeoutMs: options.timeoutMs ?? 5000, }; } async request( method: "GET" | "POST" | "PUT" | "DELETE", path: string, body?: Record, ): Promise { const { host, port, useTls, apiKey, timeoutMs } = this.options; const transport = useTls ? https : http; const headers: Record = { Accept: "application/json", }; if (apiKey) { headers["x-api-key"] = apiKey; } let bodyStr: string | undefined; if (body) { bodyStr = JSON.stringify(body); headers["Content-Type"] = "application/json"; headers["Content-Length"] = String(Buffer.byteLength(bodyStr)); } return new Promise((resolve, reject) => { let settled = false; const fail = (err: Error) => { if (settled) return; settled = true; reject(err); }; const req = transport.request( { hostname: host, port, path, method, timeout: timeoutMs, headers, rejectUnauthorized: false, // self-signed certs common on self-hosted }, (res) => { let data = ""; res.setEncoding("utf-8"); res.on("data", (chunk: string) => (data += chunk)); res.on("error", fail); res.on("end", () => { if (settled) return; settled = true; let parsed: unknown; try { parsed = JSON.parse(data); } catch { parsed = data; } resolve({ status: res.statusCode ?? 0, body: parsed, }); }); }, ); req.on("error", fail); req.on("timeout", () => { req.destroy(); fail(new Error("TS6 HTTP Query timeout")); }); if (bodyStr) { req.write(bodyStr); } req.end(); }); } /** Check if the TS6 HTTP Query is reachable */ async healthCheck(): Promise { try { const result = await this.request("GET", "/"); return result.status >= 200 && result.status < 500; } catch { return false; } } /** List virtual servers */ async serverList(): Promise { return this.request("GET", "/1/serverlist"); } /** List clients on a virtual server */ async clientList(sid = 1): Promise { return this.request("GET", `/1/clientlist?sid=${sid}`); } /** List channels on a virtual server */ async channelList(sid = 1): Promise { return this.request("GET", `/1/channellist?sid=${sid}`); } /** Send a text message */ async sendTextMessage( targetMode: number, target: number, msg: string, sid = 1, ): Promise { return this.request("POST", `/1/sendtextmessage?sid=${sid}`, { targetmode: targetMode, target, msg, }); } /** Update client properties (e.g., description) */ async clientUpdate( properties: Record, sid = 1, ): Promise { return this.request("POST", `/1/clientupdate?sid=${sid}`, properties); } /** Move a client to a channel */ async clientMove( clid: number, cid: number, cpw?: string, sid = 1, ): Promise { const body: Record = { clid, cid }; if (cpw) body.cpw = cpw; return this.request("POST", `/1/clientmove?sid=${sid}`, body); } }