Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

atsq — Asynchronous TeamSpeak Query

CI Release PyPI Python License Ruff uv

The successor to py-ts3: an asyncio-native ServerQuery client for TeamSpeak 3 and TeamSpeak 6 over SSH — automatic keepalive, reconnect and event dispatch, on modern Python (3.12–3.15). Every release is tested live against real TS3 and TS6 servers.

Install

uv add atsq          # or: pip install atsq

Usage

import atsq

client = atsq.Client("ts.example.com", 10022, password="...",
                     server_id=1,                    # or server_port=9987
                     nickname="My Bot",              # re-applied on reconnect
                     register_events=atsq.ALL_EVENTS)

@client.on("cliententerview")
async def on_join(event: atsq.Event) -> None:
    if event["client_type"] == atsq.ClientType.VOICE:
        print("joined:", event["client_unique_identifier"])

@client.on("clientleftview")
async def on_leave(event: atsq.Event) -> None:
    print("left:", event.get("clid"))

await client.run_forever()   # reconnects with backoff; keepalive automatic
One-shot administrative session
async with await atsq.connect("ts.example.com", 10022,
                              password="...", server_id=1) as ts:
    for row in await ts.client_list("uid", "mytsid"):    # -mytsid: TS6 only
        print(row["clid"], row["client_nickname"])
    cid = await ts.channel_create("Lounge", channel_flag_permanent=1)
    bans = await ts.ban_find(ip="203.0.113.7")            # TS6 only, [] if none

Pull-style event consumption instead of handlers:

async for event in client.events():
    handle(event)
# or: event = await client.wait_for_event(timeout=240)
Generic commands, pipelining and wire constants

Anything without a typed wrapper goes through the escape-safe exec(), including pipelined bulk commands (many parameter blocks, one round trip):

rows = await ts.exec("servergrouplist")
await ts.exec("clientmove", clid=5, cid=42)
await ts.exec("channeladdperm", cid=60, blocks=[
    {"permsid": "i_channel_needed_join_power", "permvalue": 75},
    {"permsid": "i_channel_needed_subscribe_power", "permvalue": 60},
])

Wire constants are StrEnums that compare directly against event/row values:

from atsq import ReasonId, TargetMode, ClientType, LEAVE_REASONS

if event["reasonid"] == ReasonId.CONNECT and event["client_type"] == ClientType.VOICE:
    ...
if event.get("reasonid") in LEAVE_REASONS:
    ...
File transfer (icons, avatars, channel files)

Same API against TS3 and TS6:

ft = atsq.FileTransfer(client)
icon_id = await ft.upload_icon(png_bytes)            # crc32-named, returns the id
data = await ft.download("/file.bin", cid=42)
rows = await ft.file_list(cid=42, path="/")          # [] for empty dirs
await ft.delete_file("/file.bin", cid=42)
TS3 → TS6 identity mapping

TS6 hashes client UIDs with SHA-256 instead of SHA-1, so the same identity gets a new UID and data keyed by TS3 UIDs no longer matches. TS6 clientinfo also reports the identity's client_public_key_raw, and both UIDs are hashes of it:

info = await ts.client_info(clid)
key = info["client_public_key_raw"]                   # TS6 only
assert atsq.ts6_uid_from_public_key(key) == info["client_unique_identifier"]
legacy_uid = atsq.ts3_uid_from_public_key(key)        # the client's TS3 UID

Verified on TS6 6.0.0-beta13.1 with a real client, over SSH and HTTP query; see dialect findings.

Error handling
try:
    await ts.use(99)
except atsq.QueryError as e:        # error id != 0; str(e) carries the server msg
    print(e.error_id, e.msg)
except atsq.QueryTimeoutError:      # no response in time (connection is closed)
    ...
except atsq.ConnectionClosedError:  # connection gone
    ...

atsq.FloodError (id 524) is retried automatically (flood_retries, default 2).

Defaults worth knowing
  • Keepalive: automatic whoami after 240 s idle (servers kick at ~300 s). Configure via keepalive_interval; 0 disables.
  • Flood protection: an error 524 is retried automatically after the wait the server asks for (flood_retries, default 2; 0 disables). Allowlisted IPs (query_ip_allowlist.txt) never hit it in the first place.
  • Reconnect (run_forever): exponential backoff 5 s → 300 s; a server message containing "banned" waits 300 s. use/servernotifyregister and on_ready re-run after every reconnect.
  • Host keys: verification is off by default (TeamSpeak servers generate ephemeral query host keys). Pin one in production: atsq.connect(..., known_hosts=...) (forwarded to asyncssh).
  • close() sends quit: on TS6 a query client that silently drops the SSH connection never produces a notifyclientleftview; a clean quit does (on both generations).
  • Guest sessions (TS6 beta13+): atsq.connect(host, username="guest") needs no password; await ts.login("serveradmin", pw) elevates in-band (and re-selects the virtual server, which login/logout reset).
  • Event sources: ALL_EVENTS includes the TS6-only bans source (banupdate events with op=add|del); it is skipped on TS3. server_notify_unregister("textserver") is selective on both generations (TS3 drops everything, so atsq re-registers the rest).
  • Snapshots work via plain exec("serversnapshotcreate") / exec("serversnapshotdeploy", version=..., data=...) — deploy deselects the session; call use again afterwards.
Migrating from py-ts3
py-ts3 atsq
TS3ServerConnection("telnet://user:pass@host:10011") await atsq.connect(host, 10022, username=..., password=...) (SSH)
conn.exec_("clientlist", "uid") await ts.exec("clientlist", "uid") or await ts.client_list("uid")
response resp[0]["cldbid"] same shape: rows[0]["cldbid"] (list[dict[str, str]])
conn.wait_for_event(timeout=240) await client.wait_for_event(timeout=240) or @client.on(...)
event[0]["reasonid"] event["reasonid"] (Event is a Mapping[str, str])
conn.send_keepalive() automatic (or await ts.send_keepalive())
ts3.query.TS3QueryError atsq.QueryError (str() still contains the server msg)
ts3.query.TS3TimeoutError atsq.QueryTimeoutError
query builder .pipe() exec(cmd, blocks=[{...}, {...}], **shared)
ts3.definitions constants atsq.ReasonId / TargetMode / ClientType / LEAVE_REASONS
ts3.filetransfer.TS3FileTransfer atsq.FileTransfer (asyncio, TS3+TS6)
manual reconnect loop await client.run_forever()

Enable SSH query on a TS3 server with TS3SERVER_QUERY_PROTOCOLS=raw,ssh; on TS6 it is the only line protocol (TSSERVER_QUERY_SSH_ENABLED=1, password via TSSERVER_QUERY_ADMIN_PASSWORD).

Docs

  • TS3 vs TS6 dialect findings — probe-verified differences between the generations and how atsq handles them.
  • Testing policy — every function is covered by a unit test and a live test against real servers; CI enforces it with a coverage gate.

Development

uv sync
uv run pytest                        # unit + fake-transport tests (no docker)
./scripts/run-integration-tests.sh   # full suite vs real TS3 + TS6 in docker

License

MIT

Metadata

Release files for atsq 1.0.0a6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for atsq 1.0.0a6
File Size Uploaded
atsq-1.0.0a6.tar.gz 95.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for atsq 1.0.0a6
File Interpreter ABI Platform
atsq-1.0.0a6-py3-none-any.whl Python 3 none any Details

Total release size: 123.5 kB

Release files / atsq-1.0.0a6.tar.gz

Download URL atsq-1.0.0a6.tar.gz
Size 95.7 kB
Tags Source
SHA-256 checksum
How to use checksums
45c2cdddb980b0ecec851b3f7a54f466650ffbdae504823a718064d424642cf5
BLAKE2b-256 checksum
How to use checksums
8036b86e620daacbbd1f6a160f83867336a2bfbd9238f036f5152388cddb4718
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / atsq-1.0.0a6-py3-none-any.whl

Download URL atsq-1.0.0a6-py3-none-any.whl
Size 27.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4a1d9e9a8e79423a0d35422bce0368d589707d45a0691e486aee883b2eadc515
BLAKE2b-256 checksum
How to use checksums
39451ea67635454665d67d08f0969eff420e6dd58f2e8f41db48547240e88ef7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page