mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
chore(deps): bump @honeybbq/teamspeak-client to 0.2.1, drop ts6-compat shim
Version 0.2.1 ships a universal clientinit format that works natively
against both TS3 and TS6 servers:
client_version: "3.?.? [Build: 5680278000]"
client_version_sign: DX5NIYLvfJEUjuIbCidnoeozxIDRRkpq3I9vVMBmE9L2qnekOo
BzSenkzsg2lC9CMv8K5hkEzhr2TYUYSwUXCg==
The old ts6-compat.ts workaround (monkey-patching handler.sendPacket to
rewrite clientinit's client_version to "3.6.2") is now actively wrong:
it replaces the library's new correct version/signature pair with a
stale one that TS6 servers reject, which is why the first 0.2.1 TS6
handshake attempt still hung at `received initivexpand2`.
Changes:
- package.json: "@honeybbq/teamspeak-client": "^0.1.0" -> "^0.2.1"
- src/ts-protocol/client.ts: remove patchClientInitVersion import and
the sendPacket monkey-patch block. Leave an inline comment so anyone
reading the git blame understands why the shim is gone.
- Delete src/ts-protocol/ts6-compat.ts and ts6-compat.test.ts — no
callers remain.
Other 0.2.x notes worth knowing (no code change here, just documenting):
- ClientOptions gained serverPassword / defaultChannel /
defaultChannelPassword that are sent DURING clientinit. We still call
our own joinChannel() post-connect because the existing flow works
and switching is an orthogonal refactor.
- 0.1.1 contains the P-256 DER encoding fix (PR #5 by ZHANGTIANYAO1).
Identities generated by 0.1.0 are cryptographically incompatible with
0.2.x's corrected handshake path — a bot whose identity column was
populated before this upgrade will hang at `received initivexpand2`
and fall through the 15s connect deadline. Workaround: clear the
identity column so the next start generates a fresh key. Server
groups assigned to the old UID must be re-granted once against the
new one.
Verification:
- tsc --noEmit clean
- vitest: 93/93 unit tests pass (1 test file removed with ts6-compat)
- scripts/test_full_feature.py against a local TS6 server: 51/51 pass,
including handshake, voice playback, identity persistence, WebSocket
stateChange broadcasts, and all corner-case regressions.
- Live: bot connected to TS6 in ~80ms after identity regeneration,
played NetEase audio through the voice channel, clean stop.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
4643f70f4a
commit
ca4cb1790a
5 files changed
+11
-111
No files matched your search
@@ -17,7 +17,6 @@ import {
|
||||
type ServerProtocol,
|
||||
} from "./protocol-detect.js";
|
||||
import { TS6HttpQuery } from "./http-query.js";
|
||||
import { patchClientInitVersion } from "./ts6-compat.js";
|
||||
|
||||
export { CODEC_OPUS_MUSIC } from "./voice.js";
|
||||
export type { ServerProtocol } from "./protocol-detect.js";
|
||||
@@ -206,37 +205,12 @@ export class TS3Client extends EventEmitter {
|
||||
});
|
||||
|
||||
await this.client.connect();
|
||||
|
||||
// Patch handler.sendPacket to intercept clientinit during handshake for TS6.
|
||||
//
|
||||
// Why after connect(): The library's connect() internally calls #S() which
|
||||
// replaces this.handler with a fresh PacketHandler. Any patch applied before
|
||||
// connect() would be discarded. Patching here is safe because connect() only
|
||||
// establishes the UDP socket and sends the first Init1 packet — the actual
|
||||
// clientinit command is sent later in async message callbacks (after Init1
|
||||
// round-trips complete), which cannot fire before this sync continuation.
|
||||
//
|
||||
// Why not commandMiddleware: The library's middleware chain only applies to
|
||||
// post-connection commands (sendCommandNoWait/execCommand). The handshake's
|
||||
// clientinit is sent directly via handler.sendPacket(), bypassing middleware.
|
||||
if (this.detectedProtocol === "ts6") {
|
||||
this.logger.info("Applying TS6 compatibility: upgrading client_version to 3.6.2");
|
||||
const handler = this.client.handler;
|
||||
const origSendPacket = handler.sendPacket.bind(handler);
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
// Note: @honeybbq/teamspeak-client 0.2.x ships a universal clientinit
|
||||
// (client_version "3.?.? [Build: 5680278000]" + matching signature)
|
||||
// that works against both TS3 and TS6 servers. The old 3.6.2 monkey-
|
||||
// patch on handler.sendPacket was removed when we bumped to 0.2.1 — it
|
||||
// would have replaced the library's new correct version with a stale
|
||||
// signature and made TS6 handshakes fail.
|
||||
await this.client.waitConnected();
|
||||
this.clientId = this.client.clientID();
|
||||
this.voiceFramesSent = 0;
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { patchClientInitVersion } from "./ts6-compat.js";
|
||||
|
||||
describe("ts6-compat", () => {
|
||||
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";
|
||||
|
||||
const patched = patchClientInitVersion(original);
|
||||
|
||||
expect(patched).toContain("client_version=3.6.2\\s[Build:\\s1695203293]");
|
||||
expect(patched).toContain(
|
||||
"client_version_sign=4OH3unxjGNBYS5EN4RFNrEo3UJz2Jn5KW1JqMDh3Yy93mSd0IwPm3FBrv8hCJgLuv99y6yBSN7pOmOpFjDaCw=="
|
||||
);
|
||||
// Other fields preserved
|
||||
expect(patched).toContain("client_nickname=Bot");
|
||||
expect(patched).toContain("client_key_offset=42");
|
||||
expect(patched).toContain("hwid=abc123");
|
||||
});
|
||||
});
|
||||
@@ -1,52 +0,0 @@
|
||||
/**
|
||||
* TS6 version used for compatibility. The version_sign is a P-256 ECDSA
|
||||
* signature verified by TeamSpeak servers; only officially signed pairs work.
|
||||
*/
|
||||
const TS6_VERSION = {
|
||||
version: "3.6.2 [Build: 1695203293]",
|
||||
platform: "Windows",
|
||||
sign: "4OH3unxjGNBYS5EN4RFNrEo3UJz2Jn5KW1JqMDh3Yy93mSd0IwPm3FBrv8hCJgLuv99y6yBSN7pOmOpFjDaCw==",
|
||||
};
|
||||
|
||||
/**
|
||||
* Patch a raw `clientinit` command string to use the 3.6.2 version.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This function is called from a monkey-patched handler.sendPacket() so it
|
||||
* intercepts the actual handshake packets.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace an escaped field value in a TS3 command string.
|
||||
* Fields are in the format: key=escaped_value (space-separated)
|
||||
*/
|
||||
function replaceField(cmd: string, key: string, value: string): string {
|
||||
// Escape the value using TS3 escaping rules
|
||||
const escaped = escapeTS3(value);
|
||||
const regex = new RegExp(`${key}=\\S*`);
|
||||
if (regex.test(cmd)) {
|
||||
return cmd.replace(regex, `${key}=${escaped}`);
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
function escapeTS3(value: string): string {
|
||||
return value
|
||||
.replace(/\\/g, "\\\\")
|
||||
.replace(/\//g, "\\/")
|
||||
.replace(/ /g, "\\s")
|
||||
.replace(/\|/g, "\\p")
|
||||
.replace(/\n/g, "\\n")
|
||||
.replace(/\r/g, "\\r")
|
||||
.replace(/\t/g, "\\t");
|
||||
}
|
||||
Reference in new issue
Block a user