Fix TS6 connection: patch clientinit version at handler level

The @honeybbq/teamspeak-client library sends clientinit directly via
handler.sendPacket() during the handshake, bypassing the commandMiddleware
chain entirely. This meant the ts6VersionMiddleware never intercepted the
handshake clientinit, so TS6 servers always received version 3.5.3 and
silently rejected it (never responding with initserver), causing idle timeout.

Fix: monkey-patch handler.sendPacket() to intercept clientinit packets and
upgrade the version to 3.6.2 before they're sent over the wire.

https://claude.ai/code/session_01QzvMLUT3UkhsffShcY1qzD
This commit is contained in:
Claude committed 2026-04-04 14:24:06 +00:00
1 parent 278f892fae
commit 7ad9559653
3 files changed
+49 -87

No files matched your search

+23 -10
View File
@@ -10,7 +10,6 @@ import {
type Identity,
type TextMessage,
type ClientInfo,
type CommandMiddleware,
} from "@honeybbq/teamspeak-client";
import type { Logger } from "../logger.js";
import {
@@ -18,7 +17,7 @@ import {
type ServerProtocol,
} from "./protocol-detect.js";
import { TS6HttpQuery } from "./http-query.js";
import { ts6VersionMiddleware } from "./ts6-compat.js";
import { patchClientInitVersion } from "./ts6-compat.js";
export { CODEC_OPUS_MUSIC } from "./voice.js";
export type { ServerProtocol } from "./protocol-detect.js";
@@ -154,13 +153,6 @@ export class TS3Client extends EventEmitter {
this.logger.warn(msg);
};
// Apply TS6 version middleware if connecting to a TS6 server
const commandMiddleware: CommandMiddleware[] = [];
if (this.detectedProtocol === "ts6") {
commandMiddleware.push(ts6VersionMiddleware("3.6.2"));
this.logger.info("Applying TS6 compatibility: upgrading client_version to 3.6.2");
}
this.client = new TS3FullClient(this.identity, addr, this.options.nickname, {
logger: {
debug: (msg) => this.logger.debug(msg),
@@ -168,9 +160,30 @@ export class TS3Client extends EventEmitter {
warn: throttledWarn,
error: (msg) => this.logger.error(msg),
},
commandMiddleware,
});
// Patch handler.sendPacket to intercept clientinit during handshake for TS6.
// The library's commandMiddleware only applies to post-connection commands
// (sendCommandNoWait/execCommand), but the handshake's clientinit is sent
// directly via handler.sendPacket, bypassing middleware entirely.
// This monkey-patch ensures the version is upgraded for TS6 servers.
if (this.detectedProtocol === "ts6") {
this.logger.info("Applying TS6 compatibility: upgrading client_version to 3.6.2");
const origSendPacket = this.client.handler.sendPacket.bind(this.client.handler);
this.client.handler.sendPacket = (pType: number, data: Uint8Array, flags: number) => {
// PacketType.Command = 2
if (pType === 2) {
const str = Buffer.from(data).toString("utf-8");
if (str.startsWith("clientinit ")) {
const patched = patchClientInitVersion(str);
origSendPacket(pType, Buffer.from(patched), flags);
return;
}
}
origSendPacket(pType, data, flags);
};
}
this.client.on("textMessage", (msg: TextMessage) => {
const tsMsg: TS3TextMessage = {
invokerName: msg.invokerName,
+8 -31
View File
@@ -1,45 +1,22 @@
import { describe, it, expect } from "vitest";
import { ts6VersionMiddleware } from "./ts6-compat.js";
import { patchClientInitVersion } from "./ts6-compat.js";
describe("ts6-compat", () => {
it("patches clientinit version to 3.6.2", async () => {
const middleware = ts6VersionMiddleware("3.6.2");
let captured = "";
const next = async (cmd: string) => {
captured = cmd;
};
const handler = middleware(next);
it("patches clientinit version to 3.6.2", () => {
const original =
"clientinit client_nickname=Bot client_version=3.5.3\\s[Build:\\s1587971024] " +
"client_platform=Windows client_version_sign=Kvmj7qX6wJCPI5GVT71samfmhz/bvs7M+OTXWB/JWxdQbxDe17xda7dzUWLX7pjvdJTqZmbse1HBmTxThPKvAg== " +
"client_key_offset=42 hwid=abc123";
await handler(original);
const patched = patchClientInitVersion(original);
expect(captured).toContain("client_version=3.6.2\\s[Build:\\s1695203293]");
expect(captured).toContain(
expect(patched).toContain("client_version=3.6.2\\s[Build:\\s1695203293]");
expect(patched).toContain(
"client_version_sign=4OH3unxjGNBYS5EN4RFNrEo3UJz2Jn5KW1JqMDh3Yy93mSd0IwPm3FBrv8hCJgLuv99y6yBSN7pOmOpFjDaCw=="
);
// Other fields preserved
expect(captured).toContain("client_nickname=Bot");
expect(captured).toContain("client_key_offset=42");
expect(captured).toContain("hwid=abc123");
});
it("does not modify non-clientinit commands", async () => {
const middleware = ts6VersionMiddleware("3.6.2");
let captured = "";
const handler = middleware(async (cmd) => {
captured = cmd;
});
const original = "sendtextmessage targetmode=2 msg=hello";
await handler(original);
expect(captured).toBe(original);
});
it("throws for unknown target version", () => {
expect(() => ts6VersionMiddleware("9.9.9")).toThrow("Unknown version");
expect(patched).toContain("client_nickname=Bot");
expect(patched).toContain("client_key_offset=42");
expect(patched).toContain("hwid=abc123");
});
});
+18 -46
View File
@@ -1,57 +1,29 @@
import type { CommandMiddleware } from "@honeybbq/teamspeak-client";
/**
* Known-compatible client versions with their signatures.
*
* The version_sign is a P-256 ECDSA signature over the version string,
* verified by TeamSpeak servers. Only officially signed versions work.
* TS6 version used for compatibility. The version_sign is a P-256 ECDSA
* signature verified by TeamSpeak servers; only officially signed pairs work.
*/
const KNOWN_VERSIONS: Record<
string,
{ version: string; platform: string; sign: string }
> = {
// TS3 client 3.5.3 (default in @honeybbq/teamspeak-client)
"3.5.3": {
version: "3.5.3 [Build: 1587971024]",
platform: "Windows",
sign: "Kvmj7qX6wJCPI5GVT71samfmhz/bvs7M+OTXWB/JWxdQbxDe17xda7dzUWLX7pjvdJTqZmbse1HBmTxThPKvAg==",
},
// TS3 client 3.6.2 (used by NeteaseTSBot, known to work with TS6 servers)
"3.6.2": {
version: "3.6.2 [Build: 1695203293]",
platform: "Windows",
sign: "4OH3unxjGNBYS5EN4RFNrEo3UJz2Jn5KW1JqMDh3Yy93mSd0IwPm3FBrv8hCJgLuv99y6yBSN7pOmOpFjDaCw==",
},
const TS6_VERSION = {
version: "3.6.2 [Build: 1695203293]",
platform: "Windows",
sign: "4OH3unxjGNBYS5EN4RFNrEo3UJz2Jn5KW1JqMDh3Yy93mSd0IwPm3FBrv8hCJgLuv99y6yBSN7pOmOpFjDaCw==",
};
/**
* Command middleware that upgrades the client_version in `clientinit` to
* a newer version known to work with TS6 servers.
* Patch a raw `clientinit` command string to use the 3.6.2 version.
*
* TS6 servers may reject connections from older client versions. The default
* library sends version 3.5.3; this middleware patches it to 3.6.2 which is
* the version NeteaseTSBot uses successfully with TS6.
* The @honeybbq/teamspeak-client library hardcodes version 3.5.3 in the
* handshake and sends it directly via handler.sendPacket(), bypassing the
* commandMiddleware chain. TS6 servers silently reject 3.5.3 (they never
* respond with `initserver`), causing an idle timeout.
*
* The version_sign must match the version string — it's a server-verified
* ECDSA signature, so we can only use known-valid pairs.
* This function is called from a monkey-patched handler.sendPacket() so it
* intercepts the actual handshake packets.
*/
export function ts6VersionMiddleware(
targetVersion = "3.6.2",
): CommandMiddleware {
const vInfo = KNOWN_VERSIONS[targetVersion];
if (!vInfo) {
throw new Error(`Unknown version ${targetVersion}. Known: ${Object.keys(KNOWN_VERSIONS).join(", ")}`);
}
return (next) => async (cmd) => {
// Only intercept clientinit commands
if (cmd.startsWith("clientinit ")) {
cmd = replaceField(cmd, "client_version", vInfo.version);
cmd = replaceField(cmd, "client_platform", vInfo.platform);
cmd = replaceField(cmd, "client_version_sign", vInfo.sign);
}
return next(cmd);
};
export function patchClientInitVersion(cmd: string): string {
cmd = replaceField(cmd, "client_version", TS6_VERSION.version);
cmd = replaceField(cmd, "client_platform", TS6_VERSION.platform);
cmd = replaceField(cmd, "client_version_sign", TS6_VERSION.sign);
return cmd;
}
/**