Greasy Fork is available in English.
Voxiom.io WebSocket client, auth, session, ping, and reconnection.
// ==UserScript==
// @name Voxiom Bot Manager Basic- QQhacks
// @namespace https://voxiom.io/
// @version 2.0.0
// @description Voxiom.io WebSocket client, auth, session, ping, and reconnection.
// @author QQhacks + Kaleb Danovich
// @match https://voxiom.io/*
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const UI_ID = 'voxiom-bot-mgr';
const DEFAULT_WS_TIMEOUT = 10000;
if (document.getElementById(UI_ID)) return;
function el(tag, attrs = {}, ...children) {
const e = document.createElement(tag);
for (const k in attrs) {
if (k === 'style') Object.assign(e.style, attrs[k]);
else if (k.startsWith('on') && typeof attrs[k] === 'function') e.addEventListener(k.slice(2), attrs[k]);
else e.setAttribute(k, attrs[k]);
}
for (const c of children) {
if (typeof c === 'string') e.appendChild(document.createTextNode(c));
else if (c) e.appendChild(c);
}
return e;
}
const style = document.createElement('style');
style.textContent = `
#${UI_ID} { position: fixed; right: 12px; top: 12px; width: 320px; z-index: 999999; font-family: Arial, Helvetica, sans-serif; }
#${UI_ID} .panel { background: rgba(20,20,20,0.92); color: #eee; border-radius: 8px; padding: 10px; box-shadow: 0 6px 18px rgba(0,0,0,0.6); font-size: 13px; }
#${UI_ID} .header { display:flex; align-items:center; justify-content:space-between; gap:8px; }
#${UI_ID} .title { font-weight:700; letter-spacing:0.4px; }
#${UI_ID} .controls { margin-top:8px; display:grid; grid-template-columns: 1fr 80px; gap:8px; align-items:center; }
#${UI_ID} input[type="text"], #${UI_ID} input[type="number"], #${UI_ID} textarea { width:100%; padding:6px 8px; border-radius:4px; border:1px solid rgba(255,255,255,0.08); background: rgba(255,255,255,0.02); color: #eee; box-sizing:border-box; font-size:13px; }
#${UI_ID} textarea { height:64px; resize:vertical; }
#${UI_ID} button { background:#2b8cff; color:white; border:none; padding:6px 8px; border-radius:4px; cursor:pointer; font-weight:600; }
#${UI_ID} button.secondary { background:#444; }
#${UI_ID} .small { font-size:12px; color:#ccc; }
#${UI_ID} .log { margin-top:8px; height:120px; overflow:auto; background: rgba(0,0,0,0.35); padding:8px; border-radius:6px; font-family: monospace; font-size:12px; color:#bfe; }
#${UI_ID} .status { margin-top:8px; display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
#${UI_ID} .pill { background: rgba(255,255,255,0.04); padding:4px 8px; border-radius:999px; font-size:12px; color:#ddd; }
#${UI_ID} .footer { margin-top:8px; display:flex; gap:8px; justify-content:space-between; align-items:center; }
#${UI_ID} .collapse-btn { background:transparent; border:1px solid rgba(255,255,255,0.06); color:#ddd; padding:4px 6px; border-radius:4px; cursor:pointer; }
#${UI_ID} .modes { margin-top:8px; display:flex; gap:8px; }
#${UI_ID} .mode-btn { flex:1; padding:8px 6px; border-radius:6px; color:#fff; font-weight:700; cursor:pointer; border:2px solid rgba(255,255,255,0.06); text-align:center; box-shadow: 0 2px 6px rgba(0,0,0,0.4); transition: box-shadow 160ms, transform 120ms; }
#${UI_ID} .mode-btn:active { transform: translateY(1px); }
#${UI_ID} .mode-pill { font-size:11px; color:#ddd; padding:4px 8px; border-radius:999px; background: rgba(255,255,255,0.03); }
#${UI_ID} .mode-pillar { background: linear-gradient(180deg,#2b8cff,#1a6fe6); }
#${UI_ID} .mode-freeroam { background: linear-gradient(180deg,#2ecc71,#22b85a); }
#${UI_ID} .mode-dig { background: linear-gradient(180deg,#ff9a3c,#ff7a00); }
#${UI_ID} .mode-active {
box-shadow: 0 0 18px rgba(255,255,255,0.06), 0 0 28px currentColor;
outline: 2px solid rgba(255,255,255,0.06);
transform: translateY(-2px);
}
`;
document.head.appendChild(style);
const container = el('div', { id: UI_ID });
const panel = el('div', { class: 'panel' });
const header = el('div', { class: 'header' },
el('div', { class: 'title' }, 'VOXIOM BOT MGR'),
el('div', {},
el('button', { class: 'collapse-btn', id: `${UI_ID}-toggle` }, 'Hide')
)
);
const gameCodeInput = el('input', { type: 'text', placeholder: 'Game code (e.g. X4Ixz)', id: `${UI_ID}-gamecode` });
const wsUrlInput = el('input', { type: 'text', placeholder: 'WebSocket URL (capture or paste)', id: `${UI_ID}-wsurl` });
const botsInput = el('input', { type: 'number', min: 1, value: 10, id: `${UI_ID}-bots` });
const joinPayloadInput = el('textarea', { placeholder: 'Optional join payload (JSON or raw). Leave empty to just connect.', id: `${UI_ID}-payload` });
const captureBtn = el('button', { id: `${UI_ID}-capture`, class: 'secondary' }, 'Capture WS');
const deployBtn = el('button', { id: `${UI_ID}-deploy` }, 'Deploy');
const stopBtn = el('button', { id: `${UI_ID}-stop`, class: 'secondary' }, 'Stop');
const killBtn = el('button', { id: `${UI_ID}-kill`, class: 'secondary' }, 'Kill All');
const cycleCheckbox = el('input', { type: 'checkbox', id: `${UI_ID}-cycle` });
const cycleLabel = el('label', { for: `${UI_ID}-cycle`, class: 'small' }, 'Cycle (redeploy on disconnect)');
const statusBar = el('div', { class: 'status' },
el('div', { class: 'pill', id: `${UI_ID}-active` }, 'Active: 0'),
el('div', { class: 'pill', id: `${UI_ID}-total` }, 'Total: 0'),
el('div', { class: 'pill', id: `${UI_ID}-dead` }, 'Dead: 0')
);
const logBox = el('div', { class: 'log', id: `${UI_ID}-log` });
const controls = el('div', { class: 'controls' },
gameCodeInput,
botsInput
);
const controls2 = el('div', { style: { marginTop: '8px' } },
wsUrlInput
);
const controls3 = el('div', { style: { marginTop: '8px' } },
joinPayloadInput
);
const pillarBtn = el('div', { class: 'mode-btn mode-pillar', id: `${UI_ID}-mode-pillar`, role: 'button', tabindex: 0 }, 'Pillar Mode');
const freeRoamBtn = el('div', { class: 'mode-btn mode-freeroam', id: `${UI_ID}-mode-freeroam`, role: 'button', tabindex: 0 }, 'Free Roam');
const digBtn = el('div', { class: 'mode-btn mode-dig', id: `${UI_ID}-mode-dig`, role: 'button', tabindex: 0 }, 'Dig Down');
const modesRow = el('div', { class: 'modes' }, pillarBtn, freeRoamBtn, digBtn);
const footer = el('div', { class: 'footer' },
el('div', {}, captureBtn, ' ', deployBtn, ' ', stopBtn, ' ', killBtn),
el('div', {}, cycleCheckbox, ' ', cycleLabel)
);
panel.appendChild(header);
panel.appendChild(controls);
panel.appendChild(controls2);
panel.appendChild(controls3);
panel.appendChild(modesRow);
panel.appendChild(statusBar);
panel.appendChild(logBox);
panel.appendChild(footer);
container.appendChild(panel);
document.body.appendChild(container);
const toggleBtn = document.getElementById(`${UI_ID}-toggle`);
let collapsed = false;
toggleBtn.addEventListener('click', () => {
collapsed = !collapsed;
if (collapsed) {
panel.style.opacity = '0.9';
panel.style.transform = 'translateX(6px)';
panel.style.width = '160px';
Array.from(panel.children).forEach((c, i) => { if (i > 0) c.style.display = 'none'; });
toggleBtn.textContent = 'Show';
} else {
Array.from(panel.children).forEach((c) => c.style.display = '');
toggleBtn.textContent = 'Hide';
panel.style.width = '320px';
}
});
function log(msg) {
const box = document.getElementById(`${UI_ID}-log`);
const time = new Date().toLocaleTimeString();
box.appendChild(document.createTextNode(`[${time}] ${msg}\n`));
box.scrollTop = box.scrollHeight;
}
function setStatus(active, total, dead) {
document.getElementById(`${UI_ID}-active`).textContent = `Active: ${active}`;
document.getElementById(`${UI_ID}-total`).textContent = `Total: ${total}`;
document.getElementById(`${UI_ID}-dead`).textContent = `Dead: ${dead}`;
}
let bots = new Map();
let stats = { active: 0, total: 0, dead: 0 };
let nextBotId = 1;
let capturedWsUrl = null;
let captureTimeoutHandle = null;
const MODES = {
PILLAR: 'pillar',
FREEROAM: 'freeroam',
DIG: 'dig'
};
let currentMode = MODES.PILLAR;
class GameWebSocketClient {
constructor(options) {
this.url = options.url;
this.authToken = options.authToken || null;
this.sessionId = null;
this.ws = null;
this.connected = false;
this.closing = false;
this.messageHandlers = new Map();
this.pingInterval = options.pingInterval || 15000;
this.reconnectDelay = options.reconnectDelay || 2000;
this.maxReconnectDelay = options.maxReconnectDelay || 30000;
this.reconnectAttempts = 0;
this.pingTimer = null;
this.handshakeTimeout = options.handshakeTimeout || 8000;
this.handshakeTimer = null;
this.initialPayload = options.initialPayload || null;
this.mode = options.mode || MODES.PILLAR;
this.onOpen = options.onOpen || (() => {});
this.onClose = options.onClose || (() => {});
this.onError = options.onError || (() => {});
this.onSession = options.onSession || (() => {});
this.onEvent = options.onEvent || (() => {});
this.onReconnect = options.onReconnect || (() => {});
this.onDisconnect = options.onDisconnect || (() => {});
}
connect() {
if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) return;
this.closing = false;
this.ws = new WebSocket(this.url);
this.ws.binaryType = 'arraybuffer';
this.ws.onopen = () => {
this.connected = true;
this.reconnectAttempts = 0;
this.onOpen();
this.startHandshake();
};
this.ws.onmessage = e => {
if (typeof e.data === 'string') return;
this.handleBinaryMessage(e.data);
};
this.ws.onerror = err => {
this.onError(err);
};
this.ws.onclose = ev => {
const wasConnected = this.connected;
this.connected = false;
this.clearTimers();
this.onClose(ev);
this.onDisconnect(ev);
if (!this.closing && wasConnected) this.scheduleReconnect();
else if (!this.closing && !wasConnected) this.scheduleReconnect();
};
}
startHandshake() {
this.sendAuth();
this.handshakeTimer = setTimeout(() => {
if (!this.sessionId) {
this.onError(new Error('Handshake timeout'));
if (this.ws && this.ws.readyState === WebSocket.OPEN) this.ws.close(4000, 'handshake timeout');
}
}, this.handshakeTimeout);
}
sendAuth() {
const payload = this.buildAuthPayload();
this.sendBinary(payload);
}
buildAuthPayload() {
const tokenBytes = new TextEncoder().encode(this.authToken || '');
const modeBytes = new TextEncoder().encode(this.mode || '');
const len = 1 + 4 + tokenBytes.length + 4 + modeBytes.length;
const buf = new ArrayBuffer(len);
const view = new DataView(buf);
let offset = 0;
view.setUint8(offset, 0x01);
offset += 1;
view.setUint32(offset, tokenBytes.length, true);
offset += 4;
new Uint8Array(buf, offset, tokenBytes.length).set(tokenBytes);
offset += tokenBytes.length;
view.setUint32(offset, modeBytes.length, true);
offset += 4;
new Uint8Array(buf, offset, modeBytes.length).set(modeBytes);
return buf;
}
buildSessionAckPayload(sessionId) {
const idBytes = new TextEncoder().encode(sessionId);
const len = 1 + 4 + idBytes.length;
const buf = new ArrayBuffer(len);
const view = new DataView(buf);
let offset = 0;
view.setUint8(offset, 0x02);
offset += 1;
view.setUint32(offset, idBytes.length, true);
offset += 4;
new Uint8Array(buf, offset, idBytes.length).set(idBytes);
return buf;
}
buildPingPayload() {
const buf = new ArrayBuffer(1);
const view = new DataView(buf);
view.setUint8(0, 0x03);
return buf;
}
buildEventPayload(type, data) {
const typeBytes = new TextEncoder().encode(type);
const dataBytes = new TextEncoder().encode(JSON.stringify(data || {}));
const len = 1 + 4 + typeBytes.length + 4 + dataBytes.length;
const buf = new ArrayBuffer(len);
const view = new DataView(buf);
let offset = 0;
view.setUint8(offset, 0x10);
offset += 1;
view.setUint32(offset, typeBytes.length, true);
offset += 4;
new Uint8Array(buf, offset, typeBytes.length).set(typeBytes);
offset += typeBytes.length;
view.setUint32(offset, dataBytes.length, true);
offset += 4;
new Uint8Array(buf, offset, dataBytes.length).set(dataBytes);
return buf;
}
handleBinaryMessage(buffer) {
const view = new DataView(buffer);
let offset = 0;
const opcode = view.getUint8(offset);
offset += 1;
if (opcode === 0x11) {
const len = view.getUint32(offset, true);
offset += 4;
const idBytes = new Uint8Array(buffer, offset, len);
const sessionId = new TextDecoder().decode(idBytes);
this.sessionId = sessionId;
this.onSession(sessionId);
const ack = this.buildSessionAckPayload(sessionId);
this.sendBinary(ack);
if (this.initialPayload) {
let payloadData = null;
try {
payloadData = JSON.parse(this.initialPayload);
} catch (_) {
payloadData = { raw: this.initialPayload };
}
this.sendEvent('join', payloadData);
}
this.startPing();
if (this.handshakeTimer) {
clearTimeout(this.handshakeTimer);
this.handshakeTimer = null;
}
} else if (opcode === 0x12) {
const lenType = view.getUint32(offset, true);
offset += 4;
const typeBytes = new Uint8Array(buffer, offset, lenType);
const type = new TextDecoder().decode(typeBytes);
offset += lenType;
const lenData = view.getUint32(offset, true);
offset += 4;
const dataBytes = new Uint8Array(buffer, offset, lenData);
const json = new TextDecoder().decode(dataBytes);
let parsed = null;
try {
parsed = JSON.parse(json);
} catch (_) {
parsed = null;
}
this.onEvent({ type, data: parsed });
const handler = this.messageHandlers.get(type);
if (handler) handler(parsed);
} else if (opcode === 0x13) {
this.handleServerPing();
} else if (opcode === 0x20) {
const code = view.getUint16(offset, true);
offset += 2;
const lenMsg = view.getUint32(offset, true);
offset += 4;
const msgBytes = new Uint8Array(buffer, offset, lenMsg);
const msg = new TextDecoder().decode(msgBytes);
this.onError(new Error(`Server error ${code}: ${msg}`));
}
}
handleServerPing() {
const pong = this.buildPingPayload();
this.sendBinary(pong);
}
startPing() {
this.clearPing();
this.pingTimer = setInterval(() => {
if (!this.connected || !this.ws || this.ws.readyState !== WebSocket.OPEN) return;
const ping = this.buildPingPayload();
this.sendBinary(ping);
}, this.pingInterval);
}
clearPing() {
if (this.pingTimer) {
clearInterval(this.pingTimer);
this.pingTimer = null;
}
}
clearTimers() {
this.clearPing();
if (this.handshakeTimer) {
clearTimeout(this.handshakeTimer);
this.handshakeTimer = null;
}
}
sendBinary(buffer) {
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
try {
this.ws.send(buffer);
} catch (e) {
this.onError(e);
}
}
sendEvent(type, data) {
const payload = this.buildEventPayload(type, data);
this.sendBinary(payload);
}
on(type, handler) {
this.messageHandlers.set(type, handler);
}
off(type) {
this.messageHandlers.delete(type);
}
scheduleReconnect() {
if (this.closing) return;
this.reconnectAttempts += 1;
const delay = Math.min(this.reconnectDelay * this.reconnectAttempts, this.maxReconnectDelay);
this.onReconnect({ attempt: this.reconnectAttempts, delay });
setTimeout(() => {
if (this.closing) return;
this.connect();
}, delay);
}
close() {
this.closing = true;
this.clearTimers();
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.close(1000, 'client closing');
}
this.ws = null;
this.connected = false;
}
}
function updateStats() {
setStatus(stats.active, stats.total, stats.dead);
}
function captureWebSocketUrl(timeoutMs = 8000) {
return new Promise((resolve, reject) => {
if (capturedWsUrl) {
resolve(capturedWsUrl);
return;
}
const OriginalWS = window.WebSocket;
let captured = null;
function PatchedWS(url, protocols) {
try {
if (!captured) {
captured = url;
capturedWsUrl = url;
log('Captured WebSocket URL: ' + url);
cleanup();
resolve(url);
}
} catch (e) {}
return new OriginalWS(url, protocols);
}
PatchedWS.prototype = OriginalWS.prototype;
PatchedWS.CONNECTING = OriginalWS.CONNECTING;
PatchedWS.OPEN = OriginalWS.OPEN;
PatchedWS.CLOSING = OriginalWS.CLOSING;
PatchedWS.CLOSED = OriginalWS.CLOSED;
window.WebSocket = PatchedWS;
captureTimeoutHandle = setTimeout(() => {
cleanup();
if (captured) resolve(captured);
else reject(new Error('No WebSocket connection detected within timeout'));
}, timeoutMs);
function cleanup() {
if (captureTimeoutHandle) {
clearTimeout(captureTimeoutHandle);
captureTimeoutHandle = null;
}
try { window.WebSocket = OriginalWS; } catch (e) {}
}
});
}
function setMode(mode) {
if (!Object.values(MODES).includes(mode)) return;
currentMode = mode;
pillarBtn.classList.toggle('mode-active', mode === MODES.PILLAR);
freeRoamBtn.classList.toggle('mode-active', mode === MODES.FREEROAM);
digBtn.classList.toggle('mode-active', mode === MODES.DIG);
log('Mode set to: ' + mode);
}
function getMode() {
return currentMode;
}
pillarBtn.addEventListener('click', () => setMode(MODES.PILLAR));
freeRoamBtn.addEventListener('click', () => setMode(MODES.FREEROAM));
digBtn.addEventListener('click', () => setMode(MODES.DIG));
[pillarBtn, freeRoamBtn, digBtn].forEach((btn) => {
btn.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
btn.click();
}
});
});
setMode(currentMode);
function spawnBots(count, url, payload, cycle) {
for (let i = 0; i < count; i++) {
const id = nextBotId++;
stats.total++;
updateStats();
const client = new GameWebSocketClient({
url,
authToken: gameCodeInput.value.trim() || null,
pingInterval: 10000,
reconnectDelay: 2000,
maxReconnectDelay: 20000,
handshakeTimeout: DEFAULT_WS_TIMEOUT,
initialPayload: payload || null,
mode: currentMode,
onOpen: () => {
log(`#${id} socket open`);
},
onSession: (sessionId) => {
stats.active++;
updateStats();
log(`#${id} session established: ${sessionId}`);
},
onEvent: (evt) => {},
onError: (err) => {
stats.dead++;
updateStats();
log(`#${id} error: ${err.message || err}`);
},
onClose: (ev) => {
if (stats.active > 0) stats.active--;
stats.dead++;
updateStats();
log(`#${id} closed (${ev.code} ${ev.reason || ''})`);
},
onReconnect: (info) => {
log(`#${id} reconnect attempt ${info.attempt}, delay ${info.delay}ms`);
},
onDisconnect: () => {
if (cycle) {
log(`#${id} cycling: redeploying...`);
setTimeout(() => {
spawnBots(1, url, payload, cycle);
}, 500);
}
}
});
bots.set(id, client);
client.connect();
}
}
function stopAllBots() {
for (const [id, client] of bots.entries()) {
try {
client.close();
} catch (e) {}
}
log('Stop command sent to all bots');
}
function killAllBots() {
for (const [id, client] of bots.entries()) {
try {
client.close();
} catch (e) {}
bots.delete(id);
}
stats = { active: 0, total: 0, dead: 0 };
updateStats();
log('All bots terminated');
}
captureBtn.addEventListener('click', async () => {
log('Attempting to capture WebSocket URL (will temporarily wrap window.WebSocket)...');
try {
const url = await captureWebSocketUrl(8000);
wsUrlInput.value = url;
log('Captured WS URL: ' + url);
} catch (e) {
log('Capture failed: ' + e.message);
alert('Capture failed: ' + e.message + '\n\nIf the game already connected before you clicked Capture, try reloading the page and click Capture immediately.');
}
});
deployBtn.addEventListener('click', () => {
const gameCode = document.getElementById(`${UI_ID}-gamecode`).value.trim();
let url = document.getElementById(`${UI_ID}-wsurl`).value.trim();
const botsCount = parseInt(document.getElementById(`${UI_ID}-bots`).value, 10) || 0;
const payload = document.getElementById(`${UI_ID}-payload`).value.trim();
const cycle = document.getElementById(`${UI_ID}-cycle`).checked;
if (!url) {
if (gameCode) {
const host = location.hostname;
url = `wss://${host}/`;
log('No WS URL provided; using default derived URL: ' + url + ' (you may need to paste the exact WS URL)');
} else {
alert('Please provide a WebSocket URL or a game code.');
return;
}
}
if (!botsCount || botsCount <= 0) {
alert('Enter a positive number of bots to deploy.');
return;
}
log(`Deploying ${botsCount} bots to ${url} (mode: ${currentMode})`);
spawnBots(botsCount, url, payload || null, cycle);
});
stopBtn.addEventListener('click', () => {
stopAllBots();
});
killBtn.addEventListener('click', () => {
if (!confirm('Terminate all bot clients now?')) return;
killAllBots();
});
window.VoxiomBotMgr = {
spawn: (n, url, payload, cycle) => spawnBots(n, url, payload, cycle),
stopAll: stopAllBots,
killAll: killAllBots,
captureWs: captureWebSocketUrl,
getStats: () => ({ ...stats }),
setMode: setMode,
getMode: getMode
};
//must default update otherwise engine will not work
updateStats();
log('Voxiom Bot Manager ready. Use Capture WS to detect server or paste WS URL manually.');
})();