Bots for Agar.io clones!
// ==UserScript==
// @name Xero-Bots | .io Bots
// @namespace https://xerobots.gt.tc
// @version N/A
// @description Bots for Agar.io clones!
// @ Please note that some of the sites included in this script may no longer be hosted, or the bots on them may have been patched.
// @author XEROBOTS
// @match *://agarz.com/*
// @match *://eatcells.com/*
// @match *://cellcraft.io/*
// @match *://sakura-agar.net/*
// @match *://happyagar.online/*
// @match *://bublz.us/*
// @match *://agar-kicoo.tk/*
// @match *://cell.sh/*
// @match *://ultrex.io/*
// @match *://bubleroyal.com/*
// @match *://agar-team.com/*
// @match *://agargo.com/*
// @match *://petridish.pw/*
// @match *://agarmen.com/*
// @match *://mortalcell.com/*
// @match *://ogar.eatcells.com/*
// @match *://mad-agar.pw/*
// @match *://roogar.io/*
// @match *://slither.com/*
// @match *://slither.io/*
// @match *://agar.boston/*
// @match *://agar.live/*
// @match *://es.agar.live/*
// @match *://ru.agar.live/*
// @match *://pt.agar.live/*
// @match *://tr.agar.live/*
// @match *://agar.cc/*
// @match *://tr.agar.cc/*
// @match *://agariogame.org/*
// @match *://agariobr.com.br/*
// @match *://agarioonline.org/*
// @match *://agarioio.com/*
// @match *://agario.onl/*
// @match *://agario.zafer2.com/*
// @match *://agariomoddedserver.com/*
// @match *://agarprivateservers.org/*
// @match *://aquar.io/*
// @match *://bubbleam.pl/*
// @match *://buble.am/*
// @match *://dalr.ae/*
// @match *://easyagario.icu/*
// @match *://imsolo.pro/*
// @match *://oceanar.io/*
// @match *://ogarium.com/*
// @match *://ogar.mivabe.nl/*
// @match *://ryuten.io/*
// @match *://agar.emupedia.net/*
// @match *://agario.xingkong.tw/*
// @match *://gota.io/*
// @match *://play.gota.io/*
// @match *://agar.lol/*
// @match *://agar.fun/*
// @match *://agario.lol/*
// @match *://agario.fun/*
// @match *://agario.skin/*
// @match *://agar.cam/*
// @match *://agarj.com/*
// @match *://agarws.com/*
// @match *://scrux.io/*
// @match *://one.sigmally.com/*
// @match *://privateagario.net/*
// @match *://playagario.org/*
// @match *://agario.monster/*
// @match *://agariov.org/*
// @match *://agariohere.com/*
// @match *://agariogame.club/*
// @match *://agario.cloud/*
// @match *://agarioarabic.com/*
// @match *://agario-play.com/*
// @match *://alis.io/*
// @match *://alis.nosx.pw/*
// @match *://agario.org.uk/*
// @match *://agario.guru/*
// @match *://agario.fans/*
// @match *://agrics.org/*
// @grant none
// @icon https://use.xerobots.gt.tc/icon.png
// @license MIT
// @run-at document-start
// ==/UserScript==
/*
The MIT License (MIT)
Copyright (c) XEROBOTS 2025
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
class Client {
constructor() {
this.isDebugging = false;
[this.socketServerURL, this.requestGuiURL] = this.isDebugging ? ['ws://localhost:3000', 'http://localhost:3000/botGUI'] : ['wss://use.xerobots.gt.tc', 'https://use.xerobots.gt.tc/botGUI'];
this.clientSocketURL = '';
this.coordinates = { x: 0, y: 0 };
this.startedBots = false;
this.injectedUI = false;
this.queue = [];
this.ModeActive = false;
this.keybinds = {
split: localStorage.getItem("kb_split") || 'v',
eject: localStorage.getItem("kb_eject") || 'f',
startStop: localStorage.getItem("kb_startStop") || '8',
followFarm: localStorage.getItem("kb_followFarm") || '9'
};
this.getID = id => document.getElementById(id);
this.injectUI();
this.setKeyboardInput();
this.connectToSocket();
this.beginMouseInterval();
}
async injectUI(retries = 0) {
const maxRetries = 3;
const retryDelay = 3000;
const timeout = 5000;
try {
this.uiCode = await this.fetchWithTimeout(this.fetchGUI(), timeout);
if (!this.uiCode) {
throw new Error('Error fetching the Bot GUI');
}
if (!this.isUIAppended()) {
this.appendGUI(this.uiCode);
} else return;
} catch (error) {
alert('Failed to fetch botUI, retrying...');
if (retries < maxRetries) {
await this.delay(retryDelay);
return this.injectUI(retries + 1);
} else {
return alert(`An error occurred: ${error.message}. If this issue persists, refresh the page or contact a developer.`);
}
}
}
isUIAppended() {
const uiContainer = document.getElementById('uiRoot');
return !!uiContainer;
}
async fetchWithTimeout(fetchPromise, timeout) {
let timeoutId;
const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(() => reject(new Error('Request timed out')), timeout);
});
return Promise.race([fetchPromise, timeoutPromise]).finally(() => clearTimeout(timeoutId));
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async fetchGUI() {
const guiUrl = await fetch(this.requestGuiURL);
if (!guiUrl.ok) {
throw new Error(`Fetch failed with status code ${guiUrl.status} and text "${guiUrl.statusText}"`);
}
return await guiUrl.text();
}
appendGUI(html) {
const div = document.createElement('div');
div.id = 'uiRoot';
div.innerHTML = html;
document.body.appendChild(div);
this.divIDs = {
botCount: 'uiRootBotCounter',
startButton: 'uiRootStartStop',
botStatus: 'uiRootBotStatus',
keybindBtn: 'uiRootKeybindsBtn',
saveKeybinds: 'saveKeybinds',
keySplit: 'keySplit',
keyEject: 'keyEject',
ModeButton: 'uiRootModeToggle'
};
this.injectedUI = true;
this.getID(this.divIDs.startButton).addEventListener('click', (event) => {
if (event.isTrusted) {
this.setStartedInput();
}
});
this.getID(this.divIDs.ModeButton).addEventListener('click', (event) => {
if (event.isTrusted) {
this.toggleMode();
}
});
this.getID(this.divIDs.keybindBtn).addEventListener('click', () => {
this.toggleKeybindPopup();
});
this.getID(this.divIDs.saveKeybinds).addEventListener('click', () => {
this.saveKeybinds();
});
while (this.queue.length) {
let [functionName, args] = this.queue.shift();
this[functionName](...args);
}
}
toggleMode() {
const btn = this.getID(this.divIDs.ModeButton);
const turningOn = btn.textContent === 'Follow';
if (turningOn) {
btn.textContent = 'Farm';
btn.style.color = '#4287f5';
this.ModeActive = true;
this.sendUint8(10); // Start Mass Farm
} else {
btn.textContent = 'Follow';
btn.style.color = '#4287f5';
this.ModeActive = false;
this.sendUint8(11); // Stop Mass Farm
}
}
updateGUIStatus(color, status) {
if (!this.injectedUI) {
this.queue.push(['updateGUIStatus', [color, status]]);
return;
}
this.getID(this.divIDs.botStatus).style.color = color;
this.getID(this.divIDs.botStatus).innerHTML = status;
}
updateGUICounter(spawned, max) {
if (!this.injectedUI) return;
this.getID(this.divIDs.botCount).innerHTML = spawned + " / " + max;
}
updateGUIStarted(check) {
if (!this.injectedUI) return;
if (!check) {
this.getID(this.divIDs.startButton).innerHTML = 'Start';
this.getID(this.divIDs.startButton).style.color = '#ebd907';
} else {
this.getID(this.divIDs.startButton).innerHTML = 'Stop';
this.getID(this.divIDs.startButton).style.color = '#ebd907';
}
}
resetGUI() {
if (!this.injectedUI) {
this.queue.push(['resetGUI', []]);
return;
}
this.updateGUIStatus('#FF6961', 'Disconnected');
this.updateGUIStarted(this.startedBots);
this.getID(this.divIDs.botCount).innerHTML = "0 / 0";
}
setKeyboardInput() {
window.addEventListener('keypress', (event) => {
if (event.isTrusted) {
const key = event.key.toLowerCase();
if (key === this.keybinds.split) {
this.sendUint8(2); // split command
} else if (key === this.keybinds.eject) {
this.sendUint8(3); // eject command
} else if (key === this.keybinds.startStop) {
this.setStartedInput(); // start/stop bots toggle
} else if (key === this.keybinds.followFarm) {
this.toggleMode(); // follow/farm toggle
}
}
});
}
beginMouseInterval() {
this.isMouseMoved = false;
this.mouseInt = setInterval(() => {
if (!this.isMouseMoved) return;
this.isMouseMoved = false;
var test = this.Buffer(26);
test.setUint8(0, 0x4, true);
test.setFloat64(1, this.coordinates.x, true);
test.setFloat64(9, this.coordinates.y, true);
this.sendMsg(test);
}, 50);
}
dataParse(args) {
if (args instanceof ArrayBuffer) return new Uint8Array(args);
if (args instanceof DataView) return new Uint8Array(args.buffer);
if (args instanceof Uint8Array) return args;
if (args instanceof Array) return new Uint8Array(args);
throw new Error(`Unsupported data type: ${args}`);
}
mouseNavigation(data, ws) {
try {
data = this.dataParse(data);
switch (data.length) {
case 21:
case 17:
data = new DataView(data.buffer);
var datax = data.getFloat64(1, true);
var datay = data.getFloat64(9, true);
this.clientSocketURL = ws.url;
if (this.coordinates.x !== datax || this.coordinates.y !== datay) {
this.isMouseMoved = true;
}
this.coordinates.x = datax;
this.coordinates.y = datay;
break;
case 9:
case 13:
data = new DataView(data.buffer);
datax = data.getInt32(1, true);
datay = data.getInt32(5, true);
this.clientSocketURL = ws.url;
if (this.coordinates.x !== datax || this.coordinates.y !== datay) {
this.isMouseMoved = true;
}
this.coordinates.x = datax;
this.coordinates.y = datay;
break;
default:
this.clientSocketURL = ws.url;
break;
}
} catch (error) {
console.log(`Error in parsing data: ${error}`);
}
}
connectToSocket() {
this.socket = new WebSocket(this.socketServerURL);
this.socket.binaryType = 'arraybuffer';
this.socket.onmessage = this.onmessage.bind(this);
this.socket.onerror = this.onerror.bind(this);
this.socket.onclose = this.onclose.bind(this);
this.socket.onopen = this.onopen.bind(this);
}
onopen() {
this.updateGUIStatus('#50C878', 'Connected');
}
onmessage(message) {
try {
message = this.dataParse(message.data);
var data = new DataView(message.buffer);
var opcode = data.getUint8(0);
switch (opcode) {
case 0x6:
var spawnedBots = data.getUint32(1, true);
var maxBots = data.getUint32(10, true);
this.updateGUICounter(spawnedBots, maxBots);
break;
}
} catch (error) {
console.log(`Error in parsing data: ${error}`);
}
}
onclose(msg) {
if (msg.code == 1006) {
setTimeout(this.connectToSocket.bind(this), 6000);
} else {
setTimeout(this.connectToSocket.bind(this), 10000);
}
this.startedBots = false;
this.resetGUI();
}
onerror() {}
setStartedInput() {
if (this.startedBots) {
this.startedBots = !this.stopBots();
} else {
this.startedBots = this.startBots();
}
this.updateGUIStarted(this.startedBots);
}
startBots() {
if (!this.clientSocketURL || this.clientSocketURL.includes(this.socketServerURL)) return false;
var serverData = this.Buffer(1 + 2 * this.clientSocketURL.length);
serverData.setUint8(0, 0);
for (var i = 0; i < this.clientSocketURL.length; ++i) {
serverData.setUint16(1 + 2 * i, this.clientSocketURL.charCodeAt(i), true);
}
this.sendMsg(serverData);
return true;
}
stopBots() {
this.sendUint8(1);
return true;
}
get open() {
return this.socket && this.socket.readyState === 1;
}
Buffer(buffer = 1) {
return new DataView(new ArrayBuffer(buffer));
}
sendUint8(offset) {
var oneByte = this.Buffer(1);
oneByte.setUint8(0, offset);
this.sendMsg(oneByte);
}
sendMsg(message) {
try {
if (this.open) {
this.socket.send(message);
}
} catch (error) {
console.log(`Failed to send data: ${error}`);
}
}
toggleKeybindPopup() {
const popup = document.getElementById('keybindPopup');
popup.style.display = popup.style.display === 'block' ? 'none' : 'block';
document.getElementById('keySplit').value = this.keybinds.split.toUpperCase();
document.getElementById('keyEject').value = this.keybinds.eject.toUpperCase();
document.getElementById('keyStartStop').value = this.keybinds.startStop.toUpperCase();
document.getElementById('keyFollowFarm').value = this.keybinds.followFarm.toUpperCase();
}
saveKeybinds() {
const split = document.getElementById('keySplit').value.trim().toLowerCase();
const eject = document.getElementById('keyEject').value.trim().toLowerCase();
const startStop = document.getElementById('keyStartStop').value.trim().toLowerCase();
const followFarm = document.getElementById('keyFollowFarm').value.trim().toLowerCase();
const keys = [split, eject, startStop, followFarm];
if (keys.every(k => k.length === 1)) {
localStorage.setItem("kb_split", split);
localStorage.setItem("kb_eject", eject);
localStorage.setItem("kb_startStop", startStop);
localStorage.setItem("kb_followFarm", followFarm);
this.keybinds.split = split;
this.keybinds.eject = eject;
this.keybinds.startStop = startStop;
this.keybinds.followFarm = followFarm;
document.getElementById('keybindPopup').style.display = 'none';
} else {
}
}
}
let usr;
WebSocket.prototype.send = new Proxy(WebSocket.prototype.send, {
apply: (target, thisArg, args) => {
var ret = target.apply(thisArg, args);
if (thisArg.url.includes(usr?.socketServerURL)) return ret;
usr?.mouseNavigation(...args, thisArg);
return ret;
}
});
window.addEventListener('load', () => {
usr = new Client();
});