mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 13:02:49 +08:00
feat: add TCP connection, UDP voice, and high-level TS3Client
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
e059ca5bfe
commit
a6bfd9d32c
3 files changed
+397
No files matched your search
@@ -0,0 +1,134 @@
|
||||
import net from "node:net";
|
||||
import { EventEmitter } from "node:events";
|
||||
import { encodeCommand, decodeResponse, parseErrorLine } from "./commands.js";
|
||||
|
||||
export interface ConnectionOptions {
|
||||
host: string;
|
||||
port: number; // ServerQuery port, typically 10011
|
||||
}
|
||||
|
||||
export interface CommandResult {
|
||||
errorId: number;
|
||||
errorMessage: string;
|
||||
data: Record<string, string>[];
|
||||
}
|
||||
|
||||
export class TS3Connection extends EventEmitter {
|
||||
private socket: net.Socket | null = null;
|
||||
private buffer = "";
|
||||
private commandQueue: Array<{
|
||||
resolve: (result: CommandResult) => void;
|
||||
reject: (err: Error) => void;
|
||||
}> = [];
|
||||
private connected = false;
|
||||
private responseLines: string[] = [];
|
||||
|
||||
constructor(private options: ConnectionOptions) {
|
||||
super();
|
||||
}
|
||||
|
||||
async connect(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.socket = net.createConnection(
|
||||
this.options.port,
|
||||
this.options.host,
|
||||
() => {
|
||||
this.connected = true;
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
|
||||
this.socket.setEncoding("utf-8");
|
||||
this.socket.on("data", (data: string) => this.handleData(data));
|
||||
this.socket.on("error", (err) => {
|
||||
if (!this.connected) {
|
||||
reject(err);
|
||||
}
|
||||
this.emit("error", err);
|
||||
});
|
||||
this.socket.on("close", () => {
|
||||
this.connected = false;
|
||||
this.emit("close");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private handleData(data: string): void {
|
||||
this.buffer += data;
|
||||
const lines = this.buffer.split("\n\r");
|
||||
|
||||
this.buffer = lines.pop() ?? "";
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) continue;
|
||||
|
||||
if (trimmed.startsWith("TS3") || trimmed.startsWith("Welcome"))
|
||||
continue;
|
||||
|
||||
if (trimmed.startsWith("notify")) {
|
||||
this.handleNotification(trimmed);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trimmed.startsWith("error ")) {
|
||||
const error = parseErrorLine(trimmed);
|
||||
const pending = this.commandQueue.shift();
|
||||
if (pending) {
|
||||
pending.resolve({
|
||||
errorId: error.id,
|
||||
errorMessage: error.msg,
|
||||
data:
|
||||
this.responseLines.length > 0
|
||||
? decodeResponse(this.responseLines.join("\n"))
|
||||
: [],
|
||||
});
|
||||
}
|
||||
this.responseLines = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
this.responseLines.push(trimmed);
|
||||
}
|
||||
}
|
||||
|
||||
private handleNotification(line: string): void {
|
||||
const spaceIndex = line.indexOf(" ");
|
||||
const notifyPart = spaceIndex === -1 ? line : line.substring(0, spaceIndex);
|
||||
const eventName = notifyPart.replace(/^notify/, "");
|
||||
const data =
|
||||
spaceIndex === -1
|
||||
? {}
|
||||
: decodeResponse(line.substring(spaceIndex + 1))[0];
|
||||
this.emit("notify", eventName, data);
|
||||
this.emit(`notify:${eventName}`, data);
|
||||
}
|
||||
|
||||
async send(
|
||||
command: string,
|
||||
params: Record<string, string | number> = {}
|
||||
): Promise<CommandResult> {
|
||||
if (!this.socket || !this.connected) {
|
||||
throw new Error("Not connected");
|
||||
}
|
||||
|
||||
const encoded = encodeCommand(command, params);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.commandQueue.push({ resolve, reject });
|
||||
this.socket!.write(encoded);
|
||||
});
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
if (this.socket) {
|
||||
this.socket.destroy();
|
||||
this.socket = null;
|
||||
this.connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
isConnected(): boolean {
|
||||
return this.connected;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user