Automate bonk.io without opening a browser.
bonktools is a TypeScript library that speaks the game’s protocol directly. Create rooms, control teams, detect AFK players and keep a room running 24 hours a day.
npm install bonktoolsGet startedRead the documentation
import { createRoom } from 'bonktools';
const room = await createRoom({
auth: { type: 'registered', username, password },
desiredState: {
roomName: 'My Room', password: '',
maxPlayers: 6, mode: 'b', rounds: 3,
},
});
room.lockTeams(); // only the host moves players
room.enableAntiAfk(); // 12 s idle = AFK
room.on('player-afk', (id) => room.kickPlayer(id));
room.on('chat-message', ({ message }) => {
if (message === '!ping') room.chat('Pong!');
});
console.log(room.shareLink); // https://bonk.io/123456abcdeFeatures
- Direct protocol connection. Socket.IO v2 and the bonk.io TLS chain are already handled. No Puppeteer, no virtual display, no fragile UI automation. Protocol →
- 24/7 rooms that recover. BonkSession recreates rooms that go down, with exponential backoff, creation throttling and a reconcile every 60 s. 24/7 sessions →
- Typed events. Over 30 events with typed payloads, and the room state always available in room.state. Events →
- Team control. lockTeams() stops players from switching on their own: only the host moves players. Anyone who joins later already sees the room locked. Teams and lock →
- Anti-AFK. Detects 12 s without moving and without chatting, reading movement from the WebRTC input frames. Anti-AFK →
- Spectators see the match. Whoever joins while a match is running gets the match state right away, without restarting it for everyone. Matches →
How it works
You choose the level of abstraction: a raw socket, a room with state and events, or a room pool that keeps itself alive. Each room is a lightweight connection; there is no need to render the game just to host a room.
Your code
│
BonkSession room pool · shared login · throttle · 60 s reconcile
BonkRoom state · typed events · teams · lock · anti-AFK · reconnection
BonkTransport Socket.IO v2 (EIO=3) · TLS · timesync
│
bonk.io + PeerJS/WebRTC for the players' input frames- Failures under control. Transient drops are recreated. Bans, full rooms and exhausted retries are terminal and notify you.
- Documented protocol. The reverse engineering is recorded in detail, with packet formats and pitfalls. See the protocol →
Examples
Create and join rooms
import { createRoom, joinRoom } from 'bonktools';
// Create a room (the bot becomes the host)
const room = await createRoom({
auth: { type: 'registered', username, password },
desiredState: { roomName: 'HERMES', password: '', maxPlayers: 6, mode: 'b', rounds: 3 },
hidden: false,
});
console.log('Link:', room.shareLink);
// Join an existing room
const guest = await joinRoom('https://bonk.io/123456abcde', {
auth: { type: 'guest', guestName: 'BonkBot' },
role: 'spectator',
});Events and state
room.on('player-join', (p) => room.chat(`Welcome, ${p.userName}!`));
room.on('team-change', (p) => console.log(`player ${p.id} → team ${p.team}`));
room.on('game-start', () => console.log('match started'));
room.on('game-end', () => console.log('match ended'));
room.on('room-dead', (reason) => console.error('room went down:', reason.kind));
room.on('room-rebuilt', (link) => console.log('room recreated:', link));
// state is always at hand
room.state.players.forEach((p) => console.log(p.userName, p.team));Teams and anti-AFK
room.lockTeams(); // nobody switches teams on their own
room.enableAntiAfk({ thresholdMs: 12_000 }); // 12 s without moving or chatting
room.on('player-afk', (id) => {
room.chat(`${room.state.players.get(id)?.userName} is AFK!`);
room.kickPlayer(id);
});
room.on('player-back', () => room.chat('is back!'));
room.setTeam(id, 3); // the host can still move players (3 = blue)
room.isAfk(id); // one-off check24/7 session
import { BonkSession } from 'bonktools';
const session = new BonkSession({
auth: { type: 'registered', username, password },
throttle: { capacity: 1, refillPerSec: 0.3 },
});
await session.getToken();
session.on('room-added', (localId) => {
const { room } = session.rooms.get(localId)!;
console.log('room active:', room.shareLink);
});
session.on('room-dead-terminal', ({ localId, reason }) => console.error(localId, reason));
// declarative: recreates the room by itself if it goes down
await session.startFromConfig({
rooms: [{ id: 'main', name: 'ATLAS', maxPlayers: 6, mode: 'b', rounds: 3 }],
throttle: { maxConcurrentRooms: 1, roomCreationDelayMs: 3000, roomCreationJitterMs: 2000 },
});Example: a 24/7 football room
The repository includes a complete reference bot built only with the library. It builds the teams, picks captains, rotates the champion and handles AFK players. Use it as a base for yours.
- Always equal teams. Based on
maxTeamSize: never 2v1, 3v1 or 3v4. - Captains pick. When the spectators can fill the teams, the captain types the number of the player they want; the last player joins on their own.
- The winner stays blue. The loser goes to the end of the queue and the first in the queue becomes the red captain.
- AFK without fuss.
!afktoggles, AFK players are never picked, and someone who is playing cannot declare themselves AFK. - Locked teams. Only the bot moves players.
How the pick works
Blue
- Ana captain
Red
empty
Spectators (queue)
empty
The first player goes to blue and becomes captain. The solo match starts right away.
Commands
| Command | Who can use it | What it does |
|---|---|---|
!afk | any player | Toggles AFK: goes to spectators and can’t be picked. During a match, whoever is playing can’t go AFK. |
2 (just the number) | captain whose turn it is | Picks player number 2 from the list, no command needed (30 s; with no answer, the first is picked). |
!win blue · !win red | player on the field | Reports the winner and triggers the team rotation. |
!start · !stop | player on the field | Starts or stops the match (!stop holds the restart until the next !start). |
Quality
Game rooms show bugs that unit tests miss, so every feature goes through three layers of validation:
- 123 tests in the library. Room, teams, lock, anti-AFK, codec and reconnection.
- 32 tests in the 24/7 room, with fuzzing. Over 600 random sequences of joins, leaves, AFK and picks, checking the team proportions on every match.
- Real players. Real browsers playing; we even measure whether the disc actually moves on screen.
Versions
0.1.0Foundation: transport, room with state and events, 24/7 session.0.1.1Per-player balance (BALANCE_SET) reflected in the state.0.1.2A spectator who joins while a match is running now sees the game.0.1.3Anti-AFK and the WebRTC handshake, with thepeer-inputevent.0.1.4lockTeams()andunlockTeams(), plus thegs.tland team color fixes.
Get started in 3 steps
Requires Node.js 20.18 or newer and a bonk.io account.
- Install.bash
npm install bonktools - Write the bot.bot.ts
import { createRoom } from 'bonktools'; const room = await createRoom({ auth: { type: 'registered', username: process.env.BONK_USER!, password: process.env.BONK_PASS! }, desiredState: { roomName: 'Hello, bonk!', password: '', maxPlayers: 6, mode: 'b', rounds: 3 }, }); console.log(room.shareLink); - Run it.bash
BONK_USER=your_user BONK_PASS=your_password npx tsx bot.ts # → https://bonk.io/123456abcde