@honeybbq/teamspeak 0.0.1 → 1.0.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +726 -2
- package/dist/client/abort.d.ts +5 -0
- package/dist/client/abort.d.ts.map +1 -0
- package/dist/client/abort.js +23 -0
- package/dist/client/abort.js.map +1 -0
- package/dist/client/address.d.ts +17 -0
- package/dist/client/address.d.ts.map +1 -0
- package/dist/client/address.js +66 -0
- package/dist/client/address.js.map +1 -0
- package/dist/client/api/voice.d.ts +24 -0
- package/dist/client/api/voice.d.ts.map +1 -0
- package/dist/client/api/voice.js +33 -0
- package/dist/client/api/voice.js.map +1 -0
- package/dist/client/capabilities.d.ts +68 -0
- package/dist/client/capabilities.d.ts.map +1 -0
- package/dist/client/capabilities.js +59 -0
- package/dist/client/capabilities.js.map +1 -0
- package/dist/client/client.d.ts +196 -0
- package/dist/client/client.d.ts.map +1 -0
- package/dist/client/client.js +1523 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/commands.d.ts +66 -0
- package/dist/client/commands.d.ts.map +1 -0
- package/dist/client/commands.js +162 -0
- package/dist/client/commands.js.map +1 -0
- package/dist/client/crypto/crypt-ops.d.ts +7 -0
- package/dist/client/crypto/crypt-ops.d.ts.map +1 -0
- package/dist/client/crypto/crypt-ops.js +2 -0
- package/dist/client/crypto/crypt-ops.js.map +1 -0
- package/dist/client/crypto/crypt.d.ts +37 -0
- package/dist/client/crypto/crypt.d.ts.map +1 -0
- package/dist/client/crypto/crypt.js +203 -0
- package/dist/client/crypto/crypt.js.map +1 -0
- package/dist/client/crypto/eax.d.ts +12 -0
- package/dist/client/crypto/eax.d.ts.map +1 -0
- package/dist/client/crypto/eax.js +118 -0
- package/dist/client/crypto/eax.js.map +1 -0
- package/dist/client/crypto/identity.d.ts +97 -0
- package/dist/client/crypto/identity.d.ts.map +1 -0
- package/dist/client/crypto/identity.js +412 -0
- package/dist/client/crypto/identity.js.map +1 -0
- package/dist/client/crypto/index.d.ts +7 -0
- package/dist/client/crypto/index.d.ts.map +1 -0
- package/dist/client/crypto/index.js +5 -0
- package/dist/client/crypto/index.js.map +1 -0
- package/dist/client/crypto/primitives.d.ts +43 -0
- package/dist/client/crypto/primitives.d.ts.map +1 -0
- package/dist/client/crypto/primitives.js +116 -0
- package/dist/client/crypto/primitives.js.map +1 -0
- package/dist/client/diagnostics.d.ts +45 -0
- package/dist/client/diagnostics.d.ts.map +1 -0
- package/dist/client/diagnostics.js +30 -0
- package/dist/client/diagnostics.js.map +1 -0
- package/dist/client/discovery/index.d.ts +2 -0
- package/dist/client/discovery/index.d.ts.map +1 -0
- package/dist/client/discovery/index.js +2 -0
- package/dist/client/discovery/index.js.map +1 -0
- package/dist/client/discovery/resolver.d.ts +26 -0
- package/dist/client/discovery/resolver.d.ts.map +1 -0
- package/dist/client/discovery/resolver.js +275 -0
- package/dist/client/discovery/resolver.js.map +1 -0
- package/dist/client/errors.d.ts +82 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +201 -0
- package/dist/client/errors.js.map +1 -0
- package/dist/client/events.d.ts +3 -0
- package/dist/client/events.d.ts.map +1 -0
- package/dist/client/events.js +8 -0
- package/dist/client/events.js.map +1 -0
- package/dist/client/handshake/crypt-handshake.d.ts +17 -0
- package/dist/client/handshake/crypt-handshake.d.ts.map +1 -0
- package/dist/client/handshake/crypt-handshake.js +128 -0
- package/dist/client/handshake/crypt-handshake.js.map +1 -0
- package/dist/client/handshake/crypt-init2.d.ts +7 -0
- package/dist/client/handshake/crypt-init2.d.ts.map +1 -0
- package/dist/client/handshake/crypt-init2.js +37 -0
- package/dist/client/handshake/crypt-init2.js.map +1 -0
- package/dist/client/handshake/index.d.ts +4 -0
- package/dist/client/handshake/index.d.ts.map +1 -0
- package/dist/client/handshake/index.js +4 -0
- package/dist/client/handshake/index.js.map +1 -0
- package/dist/client/handshake/license.d.ts +35 -0
- package/dist/client/handshake/license.d.ts.map +1 -0
- package/dist/client/handshake/license.js +169 -0
- package/dist/client/handshake/license.js.map +1 -0
- package/dist/client/handshake.d.ts +27 -0
- package/dist/client/handshake.d.ts.map +1 -0
- package/dist/client/handshake.js +126 -0
- package/dist/client/handshake.js.map +1 -0
- package/dist/client/helpers.d.ts +6 -0
- package/dist/client/helpers.d.ts.map +1 -0
- package/dist/client/helpers.js +13 -0
- package/dist/client/helpers.js.map +1 -0
- package/dist/client/hooks.d.ts +7 -0
- package/dist/client/hooks.d.ts.map +1 -0
- package/dist/client/hooks.js +28 -0
- package/dist/client/hooks.js.map +1 -0
- package/dist/client/index.d.ts +26 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +28 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/reconnect.d.ts +12 -0
- package/dist/client/reconnect.d.ts.map +1 -0
- package/dist/client/reconnect.js +44 -0
- package/dist/client/reconnect.js.map +1 -0
- package/dist/client/throttle.d.ts +20 -0
- package/dist/client/throttle.d.ts.map +1 -0
- package/dist/client/throttle.js +83 -0
- package/dist/client/throttle.js.map +1 -0
- package/dist/client/trace.d.ts +223 -0
- package/dist/client/trace.d.ts.map +1 -0
- package/dist/client/trace.js +13 -0
- package/dist/client/trace.js.map +1 -0
- package/dist/client/transfer.d.ts +111 -0
- package/dist/client/transfer.d.ts.map +1 -0
- package/dist/client/transfer.js +243 -0
- package/dist/client/transfer.js.map +1 -0
- package/dist/client/transport/generation-window.d.ts +30 -0
- package/dist/client/transport/generation-window.d.ts.map +1 -0
- package/dist/client/transport/generation-window.js +97 -0
- package/dist/client/transport/generation-window.js.map +1 -0
- package/dist/client/transport/handler.d.ts +61 -0
- package/dist/client/transport/handler.d.ts.map +1 -0
- package/dist/client/transport/handler.js +793 -0
- package/dist/client/transport/handler.js.map +1 -0
- package/dist/client/transport/index.d.ts +6 -0
- package/dist/client/transport/index.d.ts.map +1 -0
- package/dist/client/transport/index.js +5 -0
- package/dist/client/transport/index.js.map +1 -0
- package/dist/client/transport/packet.d.ts +36 -0
- package/dist/client/transport/packet.d.ts.map +1 -0
- package/dist/client/transport/packet.js +55 -0
- package/dist/client/transport/packet.js.map +1 -0
- package/dist/client/transport/quicklz.d.ts +24 -0
- package/dist/client/transport/quicklz.d.ts.map +1 -0
- package/dist/client/transport/quicklz.js +179 -0
- package/dist/client/transport/quicklz.js.map +1 -0
- package/dist/client/types.d.ts +226 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +39 -0
- package/dist/client/types.js.map +1 -0
- package/dist/core/api/channels.d.ts +182 -0
- package/dist/core/api/channels.d.ts.map +1 -0
- package/dist/core/api/channels.js +155 -0
- package/dist/core/api/channels.js.map +1 -0
- package/dist/core/api/clients.d.ts +304 -0
- package/dist/core/api/clients.d.ts.map +1 -0
- package/dist/core/api/clients.js +260 -0
- package/dist/core/api/clients.js.map +1 -0
- package/dist/core/api/command.d.ts +305 -0
- package/dist/core/api/command.d.ts.map +1 -0
- package/dist/core/api/command.js +363 -0
- package/dist/core/api/command.js.map +1 -0
- package/dist/core/api/files.d.ts +153 -0
- package/dist/core/api/files.d.ts.map +1 -0
- package/dist/core/api/files.js +181 -0
- package/dist/core/api/files.js.map +1 -0
- package/dist/core/api/groups.d.ts +248 -0
- package/dist/core/api/groups.d.ts.map +1 -0
- package/dist/core/api/groups.js +271 -0
- package/dist/core/api/groups.js.map +1 -0
- package/dist/core/api/moderation.d.ts +205 -0
- package/dist/core/api/moderation.d.ts.map +1 -0
- package/dist/core/api/moderation.js +209 -0
- package/dist/core/api/moderation.js.map +1 -0
- package/dist/core/api/parse.d.ts +16 -0
- package/dist/core/api/parse.d.ts.map +1 -0
- package/dist/core/api/parse.js +40 -0
- package/dist/core/api/parse.js.map +1 -0
- package/dist/core/api/server.d.ts +310 -0
- package/dist/core/api/server.d.ts.map +1 -0
- package/dist/core/api/server.js +290 -0
- package/dist/core/api/server.js.map +1 -0
- package/dist/core/command/command.d.ts +20 -0
- package/dist/core/command/command.d.ts.map +1 -0
- package/dist/core/command/command.js +112 -0
- package/dist/core/command/command.js.map +1 -0
- package/dist/core/command/ids.d.ts +125 -0
- package/dist/core/command/ids.d.ts.map +1 -0
- package/dist/core/command/ids.js +85 -0
- package/dist/core/command/ids.js.map +1 -0
- package/dist/core/command/parser.d.ts +17 -0
- package/dist/core/command/parser.d.ts.map +1 -0
- package/dist/core/command/parser.js +65 -0
- package/dist/core/command/parser.js.map +1 -0
- package/dist/core/command/request.d.ts +61 -0
- package/dist/core/command/request.d.ts.map +1 -0
- package/dist/core/command/request.js +64 -0
- package/dist/core/command/request.js.map +1 -0
- package/dist/core/command/responses.d.ts +59 -0
- package/dist/core/command/responses.d.ts.map +1 -0
- package/dist/core/command/responses.js +74 -0
- package/dist/core/command/responses.js.map +1 -0
- package/dist/core/command-name.d.ts +333 -0
- package/dist/core/command-name.d.ts.map +1 -0
- package/dist/core/command-name.js +331 -0
- package/dist/core/command-name.js.map +1 -0
- package/dist/core/enums.d.ts +146 -0
- package/dist/core/enums.d.ts.map +1 -0
- package/dist/core/enums.js +132 -0
- package/dist/core/enums.js.map +1 -0
- package/dist/core/errorcode.d.ts +413 -0
- package/dist/core/errorcode.d.ts.map +1 -0
- package/dist/core/errorcode.js +410 -0
- package/dist/core/errorcode.js.map +1 -0
- package/dist/core/errors.d.ts +111 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +233 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/events.d.ts +207 -0
- package/dist/core/events.d.ts.map +1 -0
- package/dist/core/events.js +6 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/helpers.d.ts +42 -0
- package/dist/core/helpers.d.ts.map +1 -0
- package/dist/core/helpers.js +156 -0
- package/dist/core/helpers.js.map +1 -0
- package/dist/core/index.d.ts +35 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +34 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/internal.d.ts +23 -0
- package/dist/core/internal.d.ts.map +1 -0
- package/dist/core/internal.js +20 -0
- package/dist/core/internal.js.map +1 -0
- package/dist/core/middleware.d.ts +14 -0
- package/dist/core/middleware.d.ts.map +1 -0
- package/dist/core/middleware.js +18 -0
- package/dist/core/middleware.js.map +1 -0
- package/dist/core/notification-name.d.ts +154 -0
- package/dist/core/notification-name.d.ts.map +1 -0
- package/dist/core/notification-name.js +152 -0
- package/dist/core/notification-name.js.map +1 -0
- package/dist/core/notifications.d.ts +43 -0
- package/dist/core/notifications.d.ts.map +1 -0
- package/dist/core/notifications.js +116 -0
- package/dist/core/notifications.js.map +1 -0
- package/dist/core/permission-name.d.ts +533 -0
- package/dist/core/permission-name.d.ts.map +1 -0
- package/dist/core/permission-name.js +531 -0
- package/dist/core/permission-name.js.map +1 -0
- package/dist/core/permission.d.ts +31 -0
- package/dist/core/permission.d.ts.map +1 -0
- package/dist/core/permission.js +20 -0
- package/dist/core/permission.js.map +1 -0
- package/dist/core/property-name.d.ts +403 -0
- package/dist/core/property-name.d.ts.map +1 -0
- package/dist/core/property-name.js +405 -0
- package/dist/core/property-name.js.map +1 -0
- package/dist/core/reason.d.ts +39 -0
- package/dist/core/reason.d.ts.map +1 -0
- package/dist/core/reason.js +36 -0
- package/dist/core/reason.js.map +1 -0
- package/dist/core/reconnect.d.ts +59 -0
- package/dist/core/reconnect.d.ts.map +1 -0
- package/dist/core/reconnect.js +55 -0
- package/dist/core/reconnect.js.map +1 -0
- package/dist/core/types.d.ts +110 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +27 -0
- package/dist/core/types.js.map +1 -0
- package/dist/query/client.d.ts +194 -0
- package/dist/query/client.d.ts.map +1 -0
- package/dist/query/client.js +967 -0
- package/dist/query/client.js.map +1 -0
- package/dist/query/errors.d.ts +61 -0
- package/dist/query/errors.d.ts.map +1 -0
- package/dist/query/errors.js +100 -0
- package/dist/query/errors.js.map +1 -0
- package/dist/query/index.d.ts +36 -0
- package/dist/query/index.d.ts.map +1 -0
- package/dist/query/index.js +34 -0
- package/dist/query/index.js.map +1 -0
- package/dist/query/knownhosts.d.ts +61 -0
- package/dist/query/knownhosts.d.ts.map +1 -0
- package/dist/query/knownhosts.js +223 -0
- package/dist/query/knownhosts.js.map +1 -0
- package/dist/query/lines.d.ts +14 -0
- package/dist/query/lines.d.ts.map +1 -0
- package/dist/query/lines.js +29 -0
- package/dist/query/lines.js.map +1 -0
- package/dist/query/options.d.ts +187 -0
- package/dist/query/options.d.ts.map +1 -0
- package/dist/query/options.js +213 -0
- package/dist/query/options.js.map +1 -0
- package/dist/query/reconnect.d.ts +29 -0
- package/dist/query/reconnect.d.ts.map +1 -0
- package/dist/query/reconnect.js +87 -0
- package/dist/query/reconnect.js.map +1 -0
- package/dist/query/ssh.d.ts +345 -0
- package/dist/query/ssh.d.ts.map +1 -0
- package/dist/query/ssh.js +241 -0
- package/dist/query/ssh.js.map +1 -0
- package/dist/query/stream.d.ts +38 -0
- package/dist/query/stream.d.ts.map +1 -0
- package/dist/query/stream.js +70 -0
- package/dist/query/stream.js.map +1 -0
- package/dist/query/tcp.d.ts +38 -0
- package/dist/query/tcp.d.ts.map +1 -0
- package/dist/query/tcp.js +53 -0
- package/dist/query/tcp.js.map +1 -0
- package/dist/query/transport.d.ts +36 -0
- package/dist/query/transport.d.ts.map +1 -0
- package/dist/query/transport.js +2 -0
- package/dist/query/transport.js.map +1 -0
- package/dist/query/web.d.ts +54 -0
- package/dist/query/web.d.ts.map +1 -0
- package/dist/query/web.js +146 -0
- package/dist/query/web.js.map +1 -0
- package/dist/versions/index.d.ts +3094 -0
- package/dist/versions/index.d.ts.map +1 -0
- package/dist/versions/index.js +9237 -0
- package/dist/versions/index.js.map +1 -0
- package/package.json +118 -4
- package/src/client/abort.ts +24 -0
- package/src/client/address.ts +74 -0
- package/src/client/api/voice.ts +61 -0
- package/src/client/capabilities.ts +106 -0
- package/src/client/client.ts +1809 -0
- package/src/client/commands.ts +193 -0
- package/src/client/crypto/crypt-ops.ts +6 -0
- package/src/client/crypto/crypt.ts +275 -0
- package/src/client/crypto/eax.ts +152 -0
- package/src/client/crypto/identity.ts +484 -0
- package/src/client/crypto/index.ts +21 -0
- package/src/client/crypto/primitives.ts +141 -0
- package/src/client/diagnostics.ts +58 -0
- package/src/client/discovery/index.ts +1 -0
- package/src/client/discovery/resolver.ts +325 -0
- package/src/client/errors.ts +217 -0
- package/src/client/events.ts +12 -0
- package/src/client/handshake/crypt-handshake.ts +157 -0
- package/src/client/handshake/crypt-init2.ts +48 -0
- package/src/client/handshake/index.ts +3 -0
- package/src/client/handshake/license.ts +208 -0
- package/src/client/handshake.ts +150 -0
- package/src/client/helpers.ts +10 -0
- package/src/client/hooks.ts +30 -0
- package/src/client/index.ts +93 -0
- package/src/client/reconnect.ts +43 -0
- package/src/client/throttle.ts +92 -0
- package/src/client/trace.ts +237 -0
- package/src/client/transfer.ts +436 -0
- package/src/client/transport/generation-window.ts +108 -0
- package/src/client/transport/handler.ts +926 -0
- package/src/client/transport/index.ts +14 -0
- package/src/client/transport/packet.ts +69 -0
- package/src/client/transport/quicklz.ts +209 -0
- package/src/client/types.ts +268 -0
- package/src/core/api/channels.ts +396 -0
- package/src/core/api/clients.ts +654 -0
- package/src/core/api/command.ts +563 -0
- package/src/core/api/files.ts +405 -0
- package/src/core/api/groups.ts +622 -0
- package/src/core/api/moderation.ts +525 -0
- package/src/core/api/parse.ts +52 -0
- package/src/core/api/server.ts +709 -0
- package/src/core/command/command.ts +125 -0
- package/src/core/command/ids.ts +149 -0
- package/src/core/command/parser.ts +68 -0
- package/src/core/command/request.ts +119 -0
- package/src/core/command/responses.ts +114 -0
- package/src/core/command-name.ts +334 -0
- package/src/core/enums.ts +167 -0
- package/src/core/errorcode.ts +412 -0
- package/src/core/errors.ts +258 -0
- package/src/core/events.ts +219 -0
- package/src/core/helpers.ts +161 -0
- package/src/core/index.ts +126 -0
- package/src/core/internal.ts +23 -0
- package/src/core/middleware.ts +24 -0
- package/src/core/notification-name.ts +155 -0
- package/src/core/notifications.ts +157 -0
- package/src/core/permission-name.ts +538 -0
- package/src/core/permission.ts +34 -0
- package/src/core/property-name.ts +411 -0
- package/src/core/reason.ts +38 -0
- package/src/core/reconnect.ts +98 -0
- package/src/core/types.ts +138 -0
- package/src/query/client.ts +1132 -0
- package/src/query/errors.ts +108 -0
- package/src/query/index.ts +65 -0
- package/src/query/knownhosts.ts +257 -0
- package/src/query/lines.ts +28 -0
- package/src/query/options.ts +388 -0
- package/src/query/reconnect.ts +84 -0
- package/src/query/ssh.ts +614 -0
- package/src/query/stream.ts +91 -0
- package/src/query/tcp.ts +85 -0
- package/src/query/transport.ts +35 -0
- package/src/query/web.ts +195 -0
- package/src/versions/index.ts +10774 -0
package/README.md
CHANGED
|
@@ -1,3 +1,727 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# @honeybbq/teamspeak
|
|
4
|
+
|
|
5
|
+
**A clean-room TeamSpeak client protocol library written in pure TypeScript.**
|
|
6
|
+
|
|
7
|
+
Works with TeamSpeak 3 and TeamSpeak 6 servers. No proprietary SDK, no copied code.
|
|
8
|
+
|
|
9
|
+
[](https://github.com/honeybbq/teamspeak-js/actions/workflows/ci.yml)
|
|
10
|
+
[](https://www.npmjs.com/package/@honeybbq/teamspeak)
|
|
11
|
+
[](https://www.npmjs.com/package/@honeybbq/teamspeak?activeTab=versions)
|
|
12
|
+
[](https://scorecard.dev/viewer/?uri=github.com/HoneyBBQ/teamspeak-js)
|
|
13
|
+
[](https://github.com/HoneyBBQ/teamspeak-js/blob/main/package.json)
|
|
14
|
+
[](https://github.com/HoneyBBQ/teamspeak-js/blob/main/LICENSE)
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
**[API reference](https://honeybbq.github.io/teamspeak-js/)**
|
|
19
|
+
|
|
20
|
+
Upgrading from 0.x? The package was renamed; see the [migration guide](https://github.com/HoneyBBQ/teamspeak-js/blob/main/docs/migrating-to-1.0.md).
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- **Full protocol handshake** — ECDH key exchange, RSA puzzle, EAX-encrypted transport
|
|
25
|
+
- **Command & notification system** — Send commands, receive server events
|
|
26
|
+
- **Event-driven API** — Register handlers for text messages, client enter/leave, channel moves, kicks, etc.
|
|
27
|
+
- **Voice data** — Send Opus voice packets (codec 4 & 5)
|
|
28
|
+
- **File transfers** — Upload, download, and delete files on the server
|
|
29
|
+
- **Address resolution** — SRV records, TSDNS, and direct addresses; server nicknames on request (`nicknameLookup: true`), since that lookup goes to named.myteamspeak.com
|
|
30
|
+
- **Middleware** — Pluggable command and event middleware chains
|
|
31
|
+
- **Built-in rate limiter** — Token-bucket throttling to prevent server-side flood kicks
|
|
32
|
+
- **Identity management** — Generate, import/export, and upgrade security level of identities
|
|
33
|
+
- **Full command set** — 111 typed functions covering every command the voice protocol answers
|
|
34
|
+
- **Reconnection** — Opt-in retry with backoff, tuned for TeamSpeak's antiflood ban
|
|
35
|
+
- **Server capabilities** — Reports the server version and the uid hash it uses (SHA-1 on TS3, SHA-256 on TS6), and the server's name, icon, banner and welcome message
|
|
36
|
+
- **Custom transports** — Supply your own socket, for proxies, tunnels or tests
|
|
37
|
+
- **ESM with types** — Ships ES modules with full TypeScript declarations
|
|
38
|
+
- **Zero native deps** — Pure TypeScript, no native addons required
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
1.0 is in beta, published under the `beta` tag. A plain `npm install` still
|
|
43
|
+
gets 0.3, so ask for the beta:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install @honeybbq/teamspeak@beta
|
|
47
|
+
# or
|
|
48
|
+
pnpm add @honeybbq/teamspeak@beta
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Everything in this README is the 1.0 API. You need Node.js 22.12 or newer.
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { Client, generateIdentity } from "@honeybbq/teamspeak/client";
|
|
57
|
+
|
|
58
|
+
// Generate a new identity (or load an existing one)
|
|
59
|
+
const identity = await generateIdentity(8);
|
|
60
|
+
|
|
61
|
+
// Create the client
|
|
62
|
+
const client = new Client(identity, "localhost", "TSBot", {
|
|
63
|
+
serverPassword: process.env.TEAMSPEAK_SERVER_PASSWORD,
|
|
64
|
+
defaultChannel: "Lobby",
|
|
65
|
+
defaultChannelPassword: process.env.TEAMSPEAK_DEFAULT_CHANNEL_PASSWORD,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Register event handlers
|
|
69
|
+
client.on("connected", ({ reconnected }) => {
|
|
70
|
+
console.log(reconnected ? "Reconnected to server!" : "Connected to server!");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
client.on("textMessage", (msg) => {
|
|
74
|
+
console.log(`[${msg.invokerName}]: ${msg.message}`);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
client.on("disconnected", ({ error, willReconnect }) => {
|
|
78
|
+
console.log("Disconnected:", error?.message ?? "clean", willReconnect ? "(reconnecting)" : "");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Connect; resolves once the handshake completes.
|
|
82
|
+
await client.connect({ signal: AbortSignal.timeout(15_000) });
|
|
83
|
+
|
|
84
|
+
// Stay connected until interrupted
|
|
85
|
+
process.on("SIGINT", async () => {
|
|
86
|
+
await client.disconnect();
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## API Overview
|
|
91
|
+
|
|
92
|
+
### Client Lifecycle
|
|
93
|
+
|
|
94
|
+
| Method | Description |
|
|
95
|
+
| --------------------------------------------- | ------------------------------------------------- |
|
|
96
|
+
| `new Client(identity, addr, nickname, opts?)` | Create a new client |
|
|
97
|
+
| `connect({ signal? })` | Connect; resolves once the handshake completes |
|
|
98
|
+
| `disconnect({ signal? })` | Leave the server and close the connection |
|
|
99
|
+
| `status` | `"disconnected"`, `"connecting"` or `"connected"` |
|
|
100
|
+
| `clientID` | Our clid, assigned by the server |
|
|
101
|
+
| `channelID` | The channel we are in |
|
|
102
|
+
| `serverCapabilities` | Server version, family and uid hash |
|
|
103
|
+
| `uid` | Our own uid, hashed as the server does |
|
|
104
|
+
|
|
105
|
+
`connect()` rejects with an `InvalidAddressError` for an address that cannot be
|
|
106
|
+
one — no host, or a port outside 1–65535 — before anything starts; a host name
|
|
107
|
+
that does not resolve fails with a `TeamspeakError` whose `cause` is the lookup's
|
|
108
|
+
error. A `connect()` called while `disconnect()` is still saying goodbye waits
|
|
109
|
+
for it to finish, then connects.
|
|
110
|
+
|
|
111
|
+
Commands, raw `exec`/`execNoWait`, voice and whispers need a connected client:
|
|
112
|
+
until `connect()` resolves, and from the moment `disconnect()` begins, they fail
|
|
113
|
+
with a `NotConnectedError` and send nothing.
|
|
114
|
+
|
|
115
|
+
### Events
|
|
116
|
+
|
|
117
|
+
Name an event with an `EventName` constant, or with its string: the two are the
|
|
118
|
+
same, and the listener's payload type follows either.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { EventName } from "@honeybbq/teamspeak/client";
|
|
122
|
+
|
|
123
|
+
client.on(EventName.TextMessage, (message) => console.log(message.message));
|
|
124
|
+
client.on("textMessage", (message) => console.log(message.message)); // the same
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
| Method | Description |
|
|
128
|
+
| ----------------------------- | --------------------------------------------- |
|
|
129
|
+
| `on("connected", handler)` | Fires when fully connected (`ConnectedEvent`) |
|
|
130
|
+
| `on("disconnected", handler)` | Fires on disconnect (`DisconnectedEvent`) |
|
|
131
|
+
| `on("textMessage", handler)` | Fires on text messages |
|
|
132
|
+
| `on("clientEnter", handler)` | Fires when a client joins |
|
|
133
|
+
| `on("clientLeave", handler)` | Fires when a client leaves |
|
|
134
|
+
| `on("clientMoved", handler)` | Fires when a client moves channels |
|
|
135
|
+
| `on("kicked", handler)` | Fires when the bot is kicked |
|
|
136
|
+
| `on("poked", handler)` | Fires when poked by a client |
|
|
137
|
+
| `on("voiceData", handler)` | Fires on incoming voice data |
|
|
138
|
+
|
|
139
|
+
`on()` and `once()` return a function that removes the listener, which is also
|
|
140
|
+
a `Disposable` (`using listener = client.on(...)`). Listeners run one at a
|
|
141
|
+
time, in the order their events arrived, so a listener may call the client and
|
|
142
|
+
await an answer. `disconnected` delivers `undefined` after `disconnect()`, and
|
|
143
|
+
the cause otherwise — a `ConnectionTimeoutError`, or a `RemovedError` for a kick, a ban
|
|
144
|
+
or a server stopping. `await using client = new Client(...)` disconnects when
|
|
145
|
+
the scope ends.
|
|
146
|
+
|
|
147
|
+
`connected` is delivered after `connect()` resolves, so a listener added right
|
|
148
|
+
after `await client.connect()` still hears it. `disconnect()` waits a second at
|
|
149
|
+
most for the server to acknowledge its goodbye, less if its `signal` aborts
|
|
150
|
+
sooner, and resolves once `disconnected` has been delivered. A `connect()` waits
|
|
151
|
+
for an earlier session's `disconnected` to be delivered before it starts, so no
|
|
152
|
+
listener or `events()` iterator of the new session hears the old one end.
|
|
153
|
+
|
|
154
|
+
A listener that throws does not stop the others: the event still reaches the
|
|
155
|
+
listeners after it, and the exception surfaces as an `uncaughtException`, as an
|
|
156
|
+
EventEmitter listener's would.
|
|
157
|
+
|
|
158
|
+
Voice is the exception to "every event in order": while more than 256
|
|
159
|
+
deliveries are waiting, voice frames are dropped (and counted in a warning at
|
|
160
|
+
most every ten seconds) rather than queued without bound.
|
|
161
|
+
|
|
162
|
+
`events(name, { signal })` is the same as an async iterator, buffering events
|
|
163
|
+
that arrive while the loop body runs. The buffer is unbounded (voice excepted,
|
|
164
|
+
as above), so stop iterating when you stop reading. Aborting `signal` stops
|
|
165
|
+
listening and makes the pending and later `next()` calls reject with the
|
|
166
|
+
signal's reason, as Node's `events.on` does; `break`ing out of the loop, or a
|
|
167
|
+
disconnect, ends it normally:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
for await (const msg of client.events("textMessage", { signal })) {
|
|
171
|
+
await reply(msg);
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`notification` is the catch-all: it delivers every notification the server
|
|
176
|
+
sends, as it arrived, the ones that also have a typed event among them, after
|
|
177
|
+
that event. It is how to reach one the SDK does not model; `NotificationName`
|
|
178
|
+
names the known ones:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { NotificationName } from "@honeybbq/teamspeak";
|
|
182
|
+
|
|
183
|
+
client.on("notification", (n) => {
|
|
184
|
+
if (n.name === NotificationName.ChannelEdited) console.log(n.params);
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Commands
|
|
189
|
+
|
|
190
|
+
Every command the voice protocol answers is a typed function, grouped by area.
|
|
191
|
+
Each is addressed by branded ids, so a `ChannelID` cannot be passed where a
|
|
192
|
+
`ClientID` is meant. The constructors — `asChannelID(5)`, `asClientID(3)` and
|
|
193
|
+
the rest — throw a `RangeError` for anything but an integer in the id's wire
|
|
194
|
+
range: 0–65535 for a `ClientID` and the file transfer ids, 0–2^32−1 for a
|
|
195
|
+
`PermissionID` (an `IconID` also takes the signed form), 0–2^64−1 for the
|
|
196
|
+
`bigint` ids. Required arguments are positional; everything optional
|
|
197
|
+
goes in one options object, always the last argument, which also carries
|
|
198
|
+
`signal` and `timeoutMs` to abandon the command or bound how long it waits.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import {
|
|
202
|
+
listChannels,
|
|
203
|
+
createChannel,
|
|
204
|
+
moveClient,
|
|
205
|
+
kickClient,
|
|
206
|
+
sendChannelMessage,
|
|
207
|
+
addServerGroupPermission,
|
|
208
|
+
iterateClientDatabase,
|
|
209
|
+
permissionByName,
|
|
210
|
+
PermissionName,
|
|
211
|
+
asClientID,
|
|
212
|
+
Codec,
|
|
213
|
+
Reason,
|
|
214
|
+
} from "@honeybbq/teamspeak";
|
|
215
|
+
|
|
216
|
+
const channels = await listChannels(client, { signal: AbortSignal.timeout(5_000) });
|
|
217
|
+
|
|
218
|
+
const cid = await createChannel(client, {
|
|
219
|
+
name: "Lounge",
|
|
220
|
+
topic: "the quiet one",
|
|
221
|
+
codec: Codec.OpusVoice,
|
|
222
|
+
isPermanent: true,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
await moveClient(client, client.clientID, cid, { password: "channel-password" });
|
|
226
|
+
await sendChannelMessage(client, "hello");
|
|
227
|
+
await kickClient(client, someone, Reason.KickChannel, { reasonMessage: "take a break" });
|
|
228
|
+
|
|
229
|
+
await addServerGroupPermission(client, sgid, permissionByName(PermissionName.ClientTalkPower), 50, {
|
|
230
|
+
skip: true,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
for await (const entry of iterateClientDatabase(client)) {
|
|
234
|
+
console.log(entry.nickname, entry.lastConnectedAt);
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
A command that sends content — `createChannel`'s channel, `editChannel`'s
|
|
239
|
+
settings, `updateClient`, `editClient`, `editClientDatabase`,
|
|
240
|
+
`addBan`'s `BanFields` — takes it as its own
|
|
241
|
+
argument, and `{ signal, timeoutMs }` as a separate one after it: set among the
|
|
242
|
+
content, they are not read.
|
|
243
|
+
|
|
244
|
+
Options objects use camelCase names (`maxClients` for `channel_maxclients`),
|
|
245
|
+
and only the fields you set are sent: an unset field leaves the setting alone,
|
|
246
|
+
while `""` sends it empty — `editChannel(client, cid, { password: "" })` clears
|
|
247
|
+
a password. A key an options type does not name is never sent. The exceptions
|
|
248
|
+
are `editServer` and `editInstance`, which take the raw `virtualserver_*` and
|
|
249
|
+
`serverinstance_*` properties as `getServerInfo` reports them; `PropertyName` names
|
|
250
|
+
the keys.
|
|
251
|
+
|
|
252
|
+
Results are camelCase too. Timestamps are `Date`s (`undefined` where the server
|
|
253
|
+
sends 0 for "never"), durations are milliseconds with an `Ms` suffix
|
|
254
|
+
(`idleTimeMs`, `durationMs`), and a permission is a `Permission`, built with
|
|
255
|
+
`permissionByID` or `permissionByName`. Small enumerations — `GroupType`,
|
|
256
|
+
`Codec`, `PrivilegeKeyType`, `LogLevel` and the rest — are objects of named values with
|
|
257
|
+
a type of the same name.
|
|
258
|
+
|
|
259
|
+
| Module | Covers |
|
|
260
|
+
| ------------ | -------------------------------------------------------------- |
|
|
261
|
+
| `channels` | Create, edit, move, delete, subscribe, channel permissions |
|
|
262
|
+
| `clients` | List, move, kick, poke, edit, the client database, permissions |
|
|
263
|
+
| `server` | Server and instance settings, groups, temp passwords, bindings |
|
|
264
|
+
| `groups` | Channel groups, the permission catalogue, privilege keys |
|
|
265
|
+
| `moderation` | Bans, complaints, offline messages, text messages, the log |
|
|
266
|
+
| `files` | List, delete, rename, create directories, transfer negotiation |
|
|
267
|
+
|
|
268
|
+
Many of these commands are answered over the voice protocol with a
|
|
269
|
+
notification rather than data rows — `servergrouplist` with
|
|
270
|
+
`notifyservergrouplist`, `ftinitupload` with `notifystartupload` after the
|
|
271
|
+
command has already succeeded. The client pairs each answer with its command,
|
|
272
|
+
so this is invisible to callers; [docs/voice-protocol-responses.md](https://github.com/HoneyBBQ/teamspeak-js/blob/main/docs/voice-protocol-responses.md)
|
|
273
|
+
has the measured table. A refusal is always thrown, never returned as an empty
|
|
274
|
+
result. A list or find with nothing to report answers `[]`. A command that
|
|
275
|
+
exists to return something — `createChannel`'s id, `initFileUpload`'s key,
|
|
276
|
+
`getClientProperties`'s fields — throws a `NoDataError` when the server answers
|
|
277
|
+
without it, rather than returning an id of 0 or an empty object.
|
|
278
|
+
|
|
279
|
+
For anything outside the typed set, `client.exec(line, { signal?, timeoutMs? })`
|
|
280
|
+
sends a raw command line and resolves to the rows that answer it.
|
|
281
|
+
|
|
282
|
+
### File Transfers
|
|
283
|
+
|
|
284
|
+
Negotiate with the `files` module, then move the bytes over TCP:
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
import { Readable } from "node:stream";
|
|
288
|
+
import { initFileUpload } from "@honeybbq/teamspeak";
|
|
289
|
+
|
|
290
|
+
const info = await initFileUpload(client, cid, "/notes.txt", BigInt(data.length));
|
|
291
|
+
await client.uploadFileData(host, info, Readable.from([data]));
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
An upload does not replace a file already at the path unless passed
|
|
295
|
+
`{ overwrite: true }`.
|
|
296
|
+
|
|
297
|
+
| Function | Description |
|
|
298
|
+
| ---------------------------------------------------- | --------------------------------- |
|
|
299
|
+
| `initFileUpload(client, ...)` | Negotiate an upload |
|
|
300
|
+
| `initFileDownload(client, ...)` | Negotiate a download |
|
|
301
|
+
| `client.uploadFileData(host, info, data, options?)` | Transfer file data to the server |
|
|
302
|
+
| `client.downloadFileData(host, info, out, options?)` | Receive file data from the server |
|
|
303
|
+
|
|
304
|
+
The transfer functions take `FileTransferOptions`:
|
|
305
|
+
|
|
306
|
+
| Option | Description |
|
|
307
|
+
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
308
|
+
| `signal` | An `AbortSignal` that abandons the transfer; it rejects with the signal's reason |
|
|
309
|
+
| `socketFactory` | Opens the connection instead of a direct TCP dial, to route it through a proxy or tunnel |
|
|
310
|
+
| `trace` | Hooks called at each milestone of the transfer (`dialStart`, `dialDone`, `progress`, `done`, `failed`); none sees the transfer key |
|
|
311
|
+
| `idleTimeoutMs` | The longest, in milliseconds, a transfer may go without moving a byte, the key write included. Default 30 000; `0` disables it |
|
|
312
|
+
|
|
313
|
+
A transfer that goes idle for `idleTimeoutMs` fails with a `FileTransferError`
|
|
314
|
+
whose `cause` is a `TimeoutError`, and its connection is destroyed. A download
|
|
315
|
+
that sends more bytes than the negotiated size, or ends short of it, fails with
|
|
316
|
+
a `FileTransferError` too; bytes past the announced size are never written.
|
|
317
|
+
|
|
318
|
+
### Identity
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
import { generateIdentity, parseIdentity } from "@honeybbq/teamspeak/client";
|
|
322
|
+
|
|
323
|
+
// Generate a new identity with security level 8
|
|
324
|
+
const identity = await generateIdentity(8);
|
|
325
|
+
|
|
326
|
+
// Export to string for persistent storage
|
|
327
|
+
const exported = identity.export();
|
|
328
|
+
|
|
329
|
+
// Import from a previously exported string
|
|
330
|
+
const restored = parseIdentity(exported);
|
|
331
|
+
|
|
332
|
+
// Raise the security level (CPU-intensive). Identities are immutable: this
|
|
333
|
+
// returns a new one with the same key.
|
|
334
|
+
const stronger = await identity.upgradeToLevel(10, { signal });
|
|
335
|
+
|
|
336
|
+
// The unique identifier, hashed as a TS3 (sha1) or TS6 (sha256) server does
|
|
337
|
+
const uid = identity.uid("sha1");
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
The private key never leaves the identity except through `export()`:
|
|
341
|
+
`toString()`, `toJSON()` and `util.inspect()` describe it without the key.
|
|
342
|
+
`parseIdentity` takes exactly what `export()` writes — the key as canonical
|
|
343
|
+
base64 of at most 32 bytes, a colon, and a decimal offset that fits in 64 bits
|
|
344
|
+
— and throws an `InvalidIdentityError` for anything else.
|
|
345
|
+
|
|
346
|
+
### Options
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
import { Client } from "@honeybbq/teamspeak/client";
|
|
350
|
+
import { consoleLogger } from "@honeybbq/teamspeak";
|
|
351
|
+
|
|
352
|
+
const client = new Client(identity, "ts.example.com", "Bot", {
|
|
353
|
+
logger: consoleLogger, // silent unless set
|
|
354
|
+
resolver: customResolver,
|
|
355
|
+
commandMiddleware: [timing],
|
|
356
|
+
eventMiddleware: [filterMiddleware],
|
|
357
|
+
serverPassword: process.env.TEAMSPEAK_SERVER_PASSWORD,
|
|
358
|
+
defaultChannel: "Lobby",
|
|
359
|
+
defaultChannelPassword: process.env.TEAMSPEAK_DEFAULT_CHANNEL_PASSWORD,
|
|
360
|
+
});
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Address resolution — the built-in one or a custom `resolver` — has ten seconds
|
|
364
|
+
in all: the `signal` a resolver is given aborts then, and the client connects
|
|
365
|
+
to the address as given.
|
|
366
|
+
|
|
367
|
+
`commandMiddleware` wraps the typed commands: each middleware receives the
|
|
368
|
+
request (its command name and parameters) with its options and returns the
|
|
369
|
+
answering rows, so it can time, retry or answer a command itself. Raw `exec`
|
|
370
|
+
and `execNoWait` lines do not pass through it.
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
import type { CommandMiddleware } from "@honeybbq/teamspeak";
|
|
374
|
+
|
|
375
|
+
const timing: CommandMiddleware = (next) => async (request, options) => {
|
|
376
|
+
const started = Date.now();
|
|
377
|
+
try {
|
|
378
|
+
return await next(request, options);
|
|
379
|
+
} finally {
|
|
380
|
+
console.log(request.command, Date.now() - started, "ms");
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Connection auth options:
|
|
386
|
+
|
|
387
|
+
- `serverPassword`: plain-text server password; sent as the TeamSpeak protocol hash during `clientinit`
|
|
388
|
+
- `defaultChannel`: default channel name requested during `clientinit`
|
|
389
|
+
- `defaultChannelPassword`: plain-text password for `defaultChannel`; sent as the TeamSpeak protocol hash
|
|
390
|
+
|
|
391
|
+
### Client version
|
|
392
|
+
|
|
393
|
+
By default `clientinit` sends a recent Windows version. The `version` option
|
|
394
|
+
sends another, and `@honeybbq/teamspeak/versions` has one export for each
|
|
395
|
+
version that is known to work, named `<Platform>_<version>_<build>`:
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
import { Client } from "@honeybbq/teamspeak/client";
|
|
399
|
+
import { Windows_3_6_2_1695203293 } from "@honeybbq/teamspeak/versions";
|
|
400
|
+
|
|
401
|
+
const client = new Client(identity, "ts.example.com", "Bot", {
|
|
402
|
+
version: Windows_3_6_2_1695203293,
|
|
403
|
+
});
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
A `ClientVersion` is a platform, a version string and a signature, and only
|
|
407
|
+
the ones in that entry carry a signature a server accepts. A server refuses a
|
|
408
|
+
build below its configured minimum, so an old version fails to connect on a
|
|
409
|
+
server that has raised it. `new Client` throws when a field is empty or the
|
|
410
|
+
signature is not the base64 of 64 bytes. Importing `@honeybbq/teamspeak/client`
|
|
411
|
+
does not load the table; only the entry you import it from does.
|
|
412
|
+
|
|
413
|
+
### Tracing
|
|
414
|
+
|
|
415
|
+
`trace` takes hooks that are called at each milestone of the client's life, for timing, metrics, logs or a progress display. For each connection attempt, the one `connect()` starts and each one the reconnect loop makes, the milestones come in this order: `resolveStart`, `resolveDone`, `connectStart`, `connectDone`, `handshakeDone`, `sessionStart`, `welcomeDone`. An attempt that never gets a session ends with `attemptFailed` instead, which says in which phase (`"open"`, `"handshake"` or `"join"`) and why. When a session ends, `sessionEnd` is called with the cause (`undefined` after `disconnect()`), preceded on a `disconnect()` by `disconnectStart` and `disconnectDone`, which says whether the server acknowledged the goodbye. `reconnectScheduled` fires when the reconnect loop is about to wait for its next attempt, and `reconnectStopped` when it ends without a session. Every attempt ends with exactly one of `sessionStart` and `attemptFailed`, and every session with exactly one `sessionEnd`.
|
|
416
|
+
|
|
417
|
+
The infos carry what each milestone knows: the resolver's candidates, the local address, the RSA puzzle's level and how long it took, how many packets were sent again, the server's public key (to pin a server's identity), the nickname the server accepted, the server's `initserver` parameters without any secret, and where the client landed with how many channels and clients the welcome delivered. Durations are in milliseconds.
|
|
418
|
+
|
|
419
|
+
```ts
|
|
420
|
+
const client = new Client(identity, "ts.example.com", "Bot", {
|
|
421
|
+
trace: {
|
|
422
|
+
resolveDone: (i) => console.log(`resolved to ${i.target} in ${i.durationMs.toFixed(0)} ms`),
|
|
423
|
+
handshakeDone: (i) =>
|
|
424
|
+
console.log(`encrypted after ${i.durationMs.toFixed(0)} ms, ${i.resends} resends`),
|
|
425
|
+
sessionStart: (i) => console.log(`joined as ${i.nickname} after ${i.durationMs.toFixed(0)} ms`),
|
|
426
|
+
welcomeDone: (i) => console.log(`${i.channels} channels, ${i.clients} clients in view`),
|
|
427
|
+
attemptFailed: (i) => console.log(`attempt ${i.attempt} failed while ${i.phase}:`, i.error),
|
|
428
|
+
sessionEnd: (i) => console.log(`session over after ${(i.durationMs / 1000).toFixed(0)} s`),
|
|
429
|
+
reconnectScheduled: (i) => console.log(`retry ${i.attempt} in ${i.delayMs} ms`),
|
|
430
|
+
},
|
|
431
|
+
});
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
A file transfer has a trace of its own, given in the options of `uploadFileData`, `downloadFileData` and `dialFileTransfer` (and of the client's methods that forward to them): `dialStart`, `dialDone`, `progress` (at most once per 64 KiB, and once more at the end with the final count), then exactly one of `done` and `failed`, which names the stage (`"dial"` or `"transfer"`). The transfer key never appears in any of it.
|
|
435
|
+
|
|
436
|
+
The rules for a hook, in both traces:
|
|
437
|
+
|
|
438
|
+
- It runs synchronously, at the moment of the milestone, and the client waits for it. Keep it quick and hand slow work to your own task.
|
|
439
|
+
- What it returns is ignored. A promise it returns is not awaited.
|
|
440
|
+
- It must not call `connect()` or `disconnect()`. To give up on an attempt, abort the signal you passed to `connect()`; to leave once connected, call `disconnect()` from a `connected` listener.
|
|
441
|
+
- It may read `client.status`, `client.clientID` and the like. `sessionStart` runs before `connect()` resolves and before `connected` is delivered, and `sessionEnd` before `disconnected`.
|
|
442
|
+
- If it throws, or the promise it returns rejects, the error is reported the way a throwing event listener's is, as an uncaught exception, and the client carries on as if nothing happened.
|
|
443
|
+
|
|
444
|
+
The object is copied when the client is made (or the transfer call starts), so changing it afterwards has no effect.
|
|
445
|
+
|
|
446
|
+
### Diagnostics
|
|
447
|
+
|
|
448
|
+
The client logs nothing unless given a `logger`. For tracing, it publishes on
|
|
449
|
+
two [`diagnostics_channel`](https://nodejs.org/api/diagnostics_channel.html)
|
|
450
|
+
channels, which cost nothing without a subscriber:
|
|
451
|
+
|
|
452
|
+
- `honeybbq:teamspeak:command` (`commandChannel`), a tracing channel: `start`
|
|
453
|
+
when a command is sent, `asyncEnd` when its answer arrives, `error` when it
|
|
454
|
+
fails.
|
|
455
|
+
- `honeybbq:teamspeak:incoming` (`incomingChannel`): every command the server sends.
|
|
456
|
+
|
|
457
|
+
Both carry the command's name, never its parameters, which hold password hashes
|
|
458
|
+
and privilege keys.
|
|
459
|
+
|
|
460
|
+
### Reconnection
|
|
461
|
+
|
|
462
|
+
Off by default: a silent reconnect makes an outage look like a healthy session.
|
|
463
|
+
An explicit `disconnect()` never reconnects, whatever the policy says.
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
const client = new Client(identity, "ts.example.com", "Bot", {
|
|
467
|
+
reconnect: {
|
|
468
|
+
backoff: { initialDelayMs: 1_000, maxDelayMs: 60_000, factor: 2, jitter: 0.2 },
|
|
469
|
+
// Optional: decide for yourself which failures are worth retrying.
|
|
470
|
+
shouldRetry: (err, attempt) => attempt < 10,
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
The defaults are conservative because TeamSpeak **bans an IP after roughly
|
|
476
|
+
three rapid connects** — a retry loop without a delay bans itself, and the ban
|
|
477
|
+
outlasts the outage it was recovering from. Recovery is also not instant even
|
|
478
|
+
when it succeeds: the server holds the dropped session for a while and refuses a
|
|
479
|
+
reconnect with `521 too many clones already connected` until it reaps it, so the
|
|
480
|
+
loop keeps trying rather than giving up on the first refusal. It stops once a
|
|
481
|
+
`connect()` of your own has connected the client, and it does not retry an
|
|
482
|
+
address that cannot be one (`InvalidAddressError`).
|
|
483
|
+
|
|
484
|
+
### Custom transports
|
|
485
|
+
|
|
486
|
+
`fileTransferSocketFactory` and `socketFactory` are the only seams into the
|
|
487
|
+
network layer. Without them the SDK always creates its own sockets, so an
|
|
488
|
+
application running behind a proxy has no way through. `socketFactory` returns
|
|
489
|
+
a `DatagramSocket` — a `node:dgram` socket, or anything with the few methods
|
|
490
|
+
it names — and `fileTransferSocketFactory` any `Duplex` stream, such as the
|
|
491
|
+
socket a SOCKS library returns.
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
const client = new Client(identity, "ts.example.com", "Bot", {
|
|
495
|
+
// UDP, for the voice and command channel.
|
|
496
|
+
socketFactory: (family) => createSocket(family === 6 ? "udp6" : "udp4"),
|
|
497
|
+
// TCP, dialled separately by each file transfer.
|
|
498
|
+
fileTransferSocketFactory: (host, port) => createConnection({ host, port }),
|
|
499
|
+
});
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Server capabilities
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
const caps = client.serverCapabilities;
|
|
506
|
+
// {
|
|
507
|
+
// version: "6.0.0-beta13.1 [Build: 1790080330]", major: 6, family: "ts6",
|
|
508
|
+
// uidAlgorithm: "sha256", name: "TeamSpeak 6 Server", iconID: undefined,
|
|
509
|
+
// hostBannerImageURL: undefined, welcomeMessage: "…"
|
|
510
|
+
// }
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
Read from `initserver` during the handshake, so it costs no extra round trip.
|
|
514
|
+
The uid hash matters because TS3 hashes a client's public key with SHA-1 and TS6
|
|
515
|
+
with SHA-256 — a client that assumes either one computes an identifier the other
|
|
516
|
+
server never sends.
|
|
517
|
+
|
|
518
|
+
## ServerQuery
|
|
519
|
+
|
|
520
|
+
The `@experimental` subpath `@honeybbq/teamspeak/query` administers a server
|
|
521
|
+
the way the voice protocol cannot: the same typed commands the core exports,
|
|
522
|
+
plus the ones only an administrator may run, over the three transports a
|
|
523
|
+
ServerQuery speaks.
|
|
524
|
+
|
|
525
|
+
| Transport | Where | Server | Login | Notifications |
|
|
526
|
+
| --------- | ------------ | ---------------- | ------------------- | ------------- |
|
|
527
|
+
| `tcp` | 10011 | TeamSpeak 3 only | the `login` command | yes |
|
|
528
|
+
| `ssh` | 10022 | both | the SSH login | yes |
|
|
529
|
+
| `web` | 10080 (HTTP) | TeamSpeak 6 | the api key | **no** |
|
|
530
|
+
|
|
531
|
+
Only raw TCP and SSH carry notifications: the server pushes them down the same
|
|
532
|
+
connection. WebQuery has no session to push to and refuses to register, so the
|
|
533
|
+
client never tries, and a listener for a server event there throws rather than
|
|
534
|
+
sitting silent. `connected` and `disconnected` work on every transport, since
|
|
535
|
+
they come from the client itself.
|
|
536
|
+
|
|
537
|
+
```typescript
|
|
538
|
+
import { QueryClient } from "@honeybbq/teamspeak/query";
|
|
539
|
+
import { listChannels } from "@honeybbq/teamspeak";
|
|
540
|
+
|
|
541
|
+
const client = new QueryClient({
|
|
542
|
+
transport: { kind: "tcp", host: "ts.example.com" }, // 10011
|
|
543
|
+
login: { name: "serveradmin", password: process.env.QUERY_PASSWORD! },
|
|
544
|
+
server: { id: 1 },
|
|
545
|
+
nickname: "bot",
|
|
546
|
+
events: { categories: ["textserver", "textprivate"] },
|
|
547
|
+
reconnect: { backoff: { initialDelayMs: 1_000 } },
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
client.on("textMessage", (message) => console.log(message.invokerName, message.message));
|
|
551
|
+
|
|
552
|
+
await client.connect();
|
|
553
|
+
|
|
554
|
+
for (const channel of await listChannels(client)) console.log(channel.name);
|
|
555
|
+
|
|
556
|
+
await client.disconnect();
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
The three transports are figures of speech the client rebuilds on every
|
|
560
|
+
connection, which is what makes reconnecting possible; `custom` opens one
|
|
561
|
+
itself, for a proxy, a tunnel or a test. Each built-in transport also offers
|
|
562
|
+
the seam into its own connection — `connect` for `tcp`, returning a `Duplex`,
|
|
563
|
+
and `connection` for `ssh`, taking a client or a factory for one — so an
|
|
564
|
+
application behind a TLS terminator or a SOCKS proxy, or one that already
|
|
565
|
+
holds an SSH client, can supply it. `ssh2` is an optional peer dependency:
|
|
566
|
+
without it the other two transports still work, and an `ssh` transport throws
|
|
567
|
+
a `MissingDependencyError` naming the package.
|
|
568
|
+
|
|
569
|
+
`serverFamily` reports which generation the server is, read from its own
|
|
570
|
+
`version` answer, and `transport` reads back the live connection for metrics.
|
|
571
|
+
|
|
572
|
+
### SSH host keys
|
|
573
|
+
|
|
574
|
+
The `ssh` transport checks the server's host key before it sends the login, and
|
|
575
|
+
there is no default for how: the login is a credential, normally
|
|
576
|
+
`serveradmin`'s, and a client that accepts any key hands it to whatever answers
|
|
577
|
+
the port. Give exactly one of these, or the client refuses to be built, naming
|
|
578
|
+
all three:
|
|
579
|
+
|
|
580
|
+
```typescript
|
|
581
|
+
const client = new QueryClient({
|
|
582
|
+
transport: {
|
|
583
|
+
kind: "ssh",
|
|
584
|
+
host: "ts.example.com",
|
|
585
|
+
username: "serveradmin",
|
|
586
|
+
password: process.env.QUERY_PASSWORD!,
|
|
587
|
+
knownHosts: "/etc/ssh/ssh_known_hosts",
|
|
588
|
+
},
|
|
589
|
+
});
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
- `knownHosts`: the path of an OpenSSH `known_hosts` file, read each time a
|
|
593
|
+
connection is attempted and never found on its own: `~/.ssh/known_hosts` is
|
|
594
|
+
used only if you name it. The check is `StrictHostKeyChecking=yes`: a host the
|
|
595
|
+
file does not mention is refused, so is one recorded with another key, and so
|
|
596
|
+
is a key a `@revoked` line names. Plain, `[host]:port` and hashed
|
|
597
|
+
(`ssh-keygen -H`) entries are read. The query port is not 22, so record it as
|
|
598
|
+
`ssh-keyscan -p 10022 ts.example.com`, which writes `[ts.example.com]:10022`;
|
|
599
|
+
an entry for port 22 does not vouch for 10022.
|
|
600
|
+
- `hostKey`: your own check, `(key) => void`, that throws to refuse. `key` has
|
|
601
|
+
the host, port, `keyType`, the key's bytes and its `fingerprint`, the
|
|
602
|
+
`SHA256:…` that `ssh-keygen -lf` prints. It has to be synchronous and return
|
|
603
|
+
nothing: a returned value, a `false` included, or a promise is refused rather
|
|
604
|
+
than guessed at.
|
|
605
|
+
- `insecureIgnoreHostKey: true`: accept any key. It gives up what the host key
|
|
606
|
+
is for, and is for a test or a throwaway server.
|
|
607
|
+
|
|
608
|
+
A key that is refused fails the connection with an `SshHostKeyError`, whose
|
|
609
|
+
`cause` says why, and nothing was sent: the password has not left the process.
|
|
610
|
+
Reconnection does not retry it. A connection you supply yourself through
|
|
611
|
+
`connection` is checked by whoever made it, so these options are refused there.
|
|
612
|
+
|
|
613
|
+
**`@experimental`**: this entry point is new, and its API may change in a
|
|
614
|
+
minor release — the options, the transport union and the event map are all
|
|
615
|
+
still settling. Pin a version and read the changelog if you depend on it.
|
|
616
|
+
|
|
617
|
+
## Compatibility
|
|
618
|
+
|
|
619
|
+
The package follows semver from 1.0. A few sets are open by nature and may
|
|
620
|
+
gain members in a minor release: the events (`EventMap`, `ServerEventName`),
|
|
621
|
+
`ClientStatus`, `ServerFamily`, `UIDAlgorithm`, `ErrorCode`, `Reason` and the
|
|
622
|
+
enumeration tables. Write a `switch` over any of them with a default case. A
|
|
623
|
+
field the server sends as a number that one of those tables names — a codec, a
|
|
624
|
+
text message's target mode, a group type — is a plain `number`, since a server
|
|
625
|
+
may send a value newer than the table.
|
|
626
|
+
|
|
627
|
+
`DatagramSocket` describes the part of a socket the client uses; should a
|
|
628
|
+
later release use more, the new members will be optional.
|
|
629
|
+
|
|
630
|
+
### Supported environments
|
|
631
|
+
|
|
632
|
+
| Environment | Supported |
|
|
633
|
+
| ----------- | ------------------------------------------------------------------------------ |
|
|
634
|
+
| Node.js | The active and maintenance LTS releases, from 22.12; CI tests 22.12, 24 and 26 |
|
|
635
|
+
| TypeScript | 5.2 and newer, for the published types |
|
|
636
|
+
| Modules | ESM only |
|
|
637
|
+
|
|
638
|
+
When a Node.js release reaches end of life, a minor release may raise
|
|
639
|
+
`engines.node` past it; that is not a breaking change. The published types use
|
|
640
|
+
`Disposable`, so the `lib` option must include `esnext.disposable` (or `es2026`
|
|
641
|
+
or `esnext`), and the subpath exports need `moduleResolution` set to `node16`,
|
|
642
|
+
`nodenext` or `bundler`. CI type-checks a consumer of the packed package with
|
|
643
|
+
TypeScript 5.2. Raising that minimum follows TypeScript's own support window,
|
|
644
|
+
happens in a minor release, and comes with a changelog note.
|
|
645
|
+
|
|
646
|
+
### Differences from the Go SDK
|
|
647
|
+
|
|
648
|
+
The [Go SDK](https://github.com/honeybbq/teamspeak-go) makes the same calls
|
|
649
|
+
with the same semantics, apart from what the languages make differ:
|
|
650
|
+
|
|
651
|
+
- Backoff fields: JavaScript takes `undefined` as "the default" and `0` as 0.
|
|
652
|
+
Go cannot tell an unset field from 0, so there a zero field takes the default
|
|
653
|
+
and a negative jitter disables it.
|
|
654
|
+
- A listener that throws: here the exception is rethrown from a microtask, an
|
|
655
|
+
uncaught exception as an EventEmitter listener's would be, and the other
|
|
656
|
+
listeners still get the event. Go recovers a panicking handler, logs it at
|
|
657
|
+
Error, and keeps delivering.
|
|
658
|
+
- Durations are milliseconds with an `Ms` suffix here, `time.Duration` in Go;
|
|
659
|
+
`listClients`' `uid` flag (on by default) is Go's `OmitUID`.
|
|
660
|
+
|
|
661
|
+
## Entry points
|
|
662
|
+
|
|
663
|
+
| Import from | What it is |
|
|
664
|
+
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
665
|
+
| `@honeybbq/teamspeak` | What every TeamSpeak connection shares: the command language, ids, errors, the typed commands, the event types and the status and reconnection types |
|
|
666
|
+
| `@honeybbq/teamspeak/client` | The voice client, identities, file transfers, diagnostics and its retry policy |
|
|
667
|
+
| `@honeybbq/teamspeak/versions` | Client versions a server accepts, for the client's `version` option; data only, one export each |
|
|
668
|
+
| `@honeybbq/teamspeak/query` | The ServerQuery client, running the same typed commands over raw TCP, SSH or WebQuery; `@experimental` |
|
|
669
|
+
|
|
670
|
+
Each entry exports only its own symbols. The client entry does not re-export
|
|
671
|
+
the core, so a program imports the `Client` from `@honeybbq/teamspeak/client`
|
|
672
|
+
and the typed commands, ids and errors from `@honeybbq/teamspeak`.
|
|
673
|
+
|
|
674
|
+
The protocol internals (crypto, handshake, transport) are not exported. They
|
|
675
|
+
change with the protocol, and exporting them would have made every change a
|
|
676
|
+
breaking one.
|
|
677
|
+
|
|
678
|
+
`@honeybbq/teamspeak-client` and `@honeybbq/teamspeak-core` are deprecated and
|
|
679
|
+
get no further releases. Coming from 0.x? The
|
|
680
|
+
[migration guide](https://github.com/HoneyBBQ/teamspeak-js/blob/main/docs/migrating-to-1.0.md) lists every change.
|
|
681
|
+
|
|
682
|
+
## Architecture
|
|
683
|
+
|
|
684
|
+
```
|
|
685
|
+
src/
|
|
686
|
+
├── core/ # Shared: command language, ids, errors, typed commands
|
|
687
|
+
├── client/ # The voice client (@honeybbq/teamspeak/client)
|
|
688
|
+
│ ├── client.ts # Client lifecycle, connection management
|
|
689
|
+
│ ├── commands.ts # Command sending and response tracking
|
|
690
|
+
│ ├── api/voice.ts # Commands only the voice protocol has
|
|
691
|
+
│ ├── capabilities.ts # Server version and uid hash detection
|
|
692
|
+
│ ├── reconnect.ts # Backoff policy and permanent-failure detection
|
|
693
|
+
│ ├── events.ts # Event handler registration and middleware
|
|
694
|
+
│ ├── notifications.ts # Server notification parsing and dispatch
|
|
695
|
+
│ ├── handshake.ts # Protocol handshake orchestration
|
|
696
|
+
│ ├── transfer.ts # File transfer data connections
|
|
697
|
+
│ ├── throttle.ts # Token-bucket rate limiter
|
|
698
|
+
│ ├── crypto/ # ECDH, EAX encryption, identity management
|
|
699
|
+
│ ├── handshake/ # Crypto handshake and license verification
|
|
700
|
+
│ ├── transport/ # UDP packet framing, ACK, compression
|
|
701
|
+
│ └── discovery/ # SRV / TSDNS / direct address resolution
|
|
702
|
+
└── query/ # ServerQuery client (not exported yet)
|
|
703
|
+
examples/
|
|
704
|
+
└── connect.ts # Minimal connection example
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
## Related
|
|
708
|
+
|
|
709
|
+
- **[teamspeak-go](https://github.com/honeybbq/teamspeak-go)** — The original Go implementation this library is ported from
|
|
710
|
+
|
|
711
|
+
## Acknowledgments
|
|
712
|
+
|
|
713
|
+
Protocol knowledge was primarily informed by the [TSLib](https://github.com/Splamy/TS3AudioBot) implementation in [TS3AudioBot](https://github.com/Splamy/TS3AudioBot) by Splamy. Huge thanks to the TS3AudioBot project and its contributors.
|
|
714
|
+
|
|
715
|
+
## Disclaimer
|
|
716
|
+
|
|
717
|
+
TeamSpeak is a registered trademark of [TeamSpeak Systems GmbH](https://teamspeak.com/). This project is not affiliated with, endorsed by, or associated with TeamSpeak Systems GmbH in any way.
|
|
718
|
+
|
|
719
|
+
This library is a **clean-room implementation** developed from publicly available documentation, protocol analysis of network traffic, and independent research. No proprietary TeamSpeak SDK code, headers, or libraries were used in its creation.
|
|
720
|
+
|
|
721
|
+
## Contributing
|
|
722
|
+
|
|
723
|
+
See [CONTRIBUTING.md](https://github.com/HoneyBBQ/teamspeak-js/blob/main/.github/CONTRIBUTING.md) for setup, checks and the integration suite.
|
|
724
|
+
|
|
725
|
+
## License
|
|
726
|
+
|
|
727
|
+
[MIT](https://github.com/HoneyBBQ/teamspeak-js/blob/main/LICENSE)
|