mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-01 20:42:50 +08:00
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:
3 files changed
+49
-87
No files matched your search
+23
-10
@@ -10,7 +10,6 @@ import {
|
|||||||
type Identity,
|
type Identity,
|
||||||
type TextMessage,
|
type TextMessage,
|
||||||
type ClientInfo,
|
type ClientInfo,
|
||||||
type CommandMiddleware,
|
|
||||||
} from "@honeybbq/teamspeak-client";
|
} from "@honeybbq/teamspeak-client";
|
||||||
import type { Logger } from "../logger.js";
|
import type { Logger } from "../logger.js";
|
||||||
import {
|
import {
|
||||||
@@ -18,7 +17,7 @@ import {
|
|||||||
type ServerProtocol,
|
type ServerProtocol,
|
||||||
} from "./protocol-detect.js";
|
} from "./protocol-detect.js";
|
||||||
import { TS6HttpQuery } from "./http-query.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 { CODEC_OPUS_MUSIC } from "./voice.js";
|
||||||
export type { ServerProtocol } from "./protocol-detect.js";
|
export type { ServerProtocol } from "./protocol-detect.js";
|
||||||
@@ -154,13 +153,6 @@ export class TS3Client extends EventEmitter {
|
|||||||
this.logger.warn(msg);
|
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, {
|
this.client = new TS3FullClient(this.identity, addr, this.options.nickname, {
|
||||||
logger: {
|
logger: {
|
||||||
debug: (msg) => this.logger.debug(msg),
|
debug: (msg) => this.logger.debug(msg),
|
||||||
@@ -168,9 +160,30 @@ export class TS3Client extends EventEmitter {
|
|||||||
warn: throttledWarn,
|
warn: throttledWarn,
|
||||||
error: (msg) => this.logger.error(msg),
|
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) => {
|
this.client.on("textMessage", (msg: TextMessage) => {
|
||||||
const tsMsg: TS3TextMessage = {
|
const tsMsg: TS3TextMessage = {
|
||||||
invokerName: msg.invokerName,
|
invokerName: msg.invokerName,
|
||||||
|
|||||||
@@ -1,45 +1,22 @@
|
|||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect } from "vitest";
|
||||||
import { ts6VersionMiddleware } from "./ts6-compat.js";
|
import { patchClientInitVersion } from "./ts6-compat.js";
|
||||||
|
|
||||||
describe("ts6-compat", () => {
|
describe("ts6-compat", () => {
|
||||||
it("patches clientinit version to 3.6.2", async () => {
|
it("patches clientinit version to 3.6.2", () => {
|
||||||
const middleware = ts6VersionMiddleware("3.6.2");
|
|
||||||
let captured = "";
|
|
||||||
const next = async (cmd: string) => {
|
|
||||||
captured = cmd;
|
|
||||||
};
|
|
||||||
const handler = middleware(next);
|
|
||||||
|
|
||||||
const original =
|
const original =
|
||||||
"clientinit client_nickname=Bot client_version=3.5.3\\s[Build:\\s1587971024] " +
|
"clientinit client_nickname=Bot client_version=3.5.3\\s[Build:\\s1587971024] " +
|
||||||
"client_platform=Windows client_version_sign=Kvmj7qX6wJCPI5GVT71samfmhz/bvs7M+OTXWB/JWxdQbxDe17xda7dzUWLX7pjvdJTqZmbse1HBmTxThPKvAg== " +
|
"client_platform=Windows client_version_sign=Kvmj7qX6wJCPI5GVT71samfmhz/bvs7M+OTXWB/JWxdQbxDe17xda7dzUWLX7pjvdJTqZmbse1HBmTxThPKvAg== " +
|
||||||
"client_key_offset=42 hwid=abc123";
|
"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(patched).toContain("client_version=3.6.2\\s[Build:\\s1695203293]");
|
||||||
expect(captured).toContain(
|
expect(patched).toContain(
|
||||||
"client_version_sign=4OH3unxjGNBYS5EN4RFNrEo3UJz2Jn5KW1JqMDh3Yy93mSd0IwPm3FBrv8hCJgLuv99y6yBSN7pOmOpFjDaCw=="
|
"client_version_sign=4OH3unxjGNBYS5EN4RFNrEo3UJz2Jn5KW1JqMDh3Yy93mSd0IwPm3FBrv8hCJgLuv99y6yBSN7pOmOpFjDaCw=="
|
||||||
);
|
);
|
||||||
// Other fields preserved
|
// Other fields preserved
|
||||||
expect(captured).toContain("client_nickname=Bot");
|
expect(patched).toContain("client_nickname=Bot");
|
||||||
expect(captured).toContain("client_key_offset=42");
|
expect(patched).toContain("client_key_offset=42");
|
||||||
expect(captured).toContain("hwid=abc123");
|
expect(patched).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");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,57 +1,29 @@
|
|||||||
import type { CommandMiddleware } from "@honeybbq/teamspeak-client";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Known-compatible client versions with their signatures.
|
* TS6 version used for compatibility. The version_sign is a P-256 ECDSA
|
||||||
*
|
* signature verified by TeamSpeak servers; only officially signed pairs work.
|
||||||
* The version_sign is a P-256 ECDSA signature over the version string,
|
|
||||||
* verified by TeamSpeak servers. Only officially signed versions work.
|
|
||||||
*/
|
*/
|
||||||
const KNOWN_VERSIONS: Record<
|
const TS6_VERSION = {
|
||||||
string,
|
version: "3.6.2 [Build: 1695203293]",
|
||||||
{ version: string; platform: string; sign: string }
|
platform: "Windows",
|
||||||
> = {
|
sign: "4OH3unxjGNBYS5EN4RFNrEo3UJz2Jn5KW1JqMDh3Yy93mSd0IwPm3FBrv8hCJgLuv99y6yBSN7pOmOpFjDaCw==",
|
||||||
// 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==",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command middleware that upgrades the client_version in `clientinit` to
|
* Patch a raw `clientinit` command string to use the 3.6.2 version.
|
||||||
* a newer version known to work with TS6 servers.
|
|
||||||
*
|
*
|
||||||
* TS6 servers may reject connections from older client versions. The default
|
* The @honeybbq/teamspeak-client library hardcodes version 3.5.3 in the
|
||||||
* library sends version 3.5.3; this middleware patches it to 3.6.2 which is
|
* handshake and sends it directly via handler.sendPacket(), bypassing the
|
||||||
* the version NeteaseTSBot uses successfully with TS6.
|
* 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
|
* This function is called from a monkey-patched handler.sendPacket() so it
|
||||||
* ECDSA signature, so we can only use known-valid pairs.
|
* intercepts the actual handshake packets.
|
||||||
*/
|
*/
|
||||||
export function ts6VersionMiddleware(
|
export function patchClientInitVersion(cmd: string): string {
|
||||||
targetVersion = "3.6.2",
|
cmd = replaceField(cmd, "client_version", TS6_VERSION.version);
|
||||||
): CommandMiddleware {
|
cmd = replaceField(cmd, "client_platform", TS6_VERSION.platform);
|
||||||
const vInfo = KNOWN_VERSIONS[targetVersion];
|
cmd = replaceField(cmd, "client_version_sign", TS6_VERSION.sign);
|
||||||
if (!vInfo) {
|
return cmd;
|
||||||
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);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in new issue
Block a user