Minimal mod for bonk.io. Use /info to see commands
// ==UserScript==
// @name Rie's Mod (Lite)
// @version 1.0.0
// @description Minimal mod for bonk.io. Use /info to see commands
// @author khayrie
// @match https://bonk.io/*
// @match https://bonkisback.io/*
// @match https://multiplayer.gg/physics/*
// @grant unsafeWindow
// @run-at document-end
// @namespace https://github.com/khayrie
// ==/UserScript==
(function() {
'use strict';
// Core state
let gameReady = false;
let gameDocument = null;
const CUSTOM_NAME = { value: " ", active: true };
const customLevels = {};
const customNotes = {};
const customNames = {}; // Store custom names set for other players
const remoteCustomizations = {};
const nicknames = {};
const uw = unsafeWindow;
// Selectors for name and level elements
const NAME_SELECTORS = [
'#pretty_top_name', '.newbonklobby_playerentry_name', '.ingamescoreboard_playername',
'.ingamechatname', '.newbonklobby_chat_msg_name', '#ingamewinner_top', '.replay_playername'
];
const LEVEL_SELECTORS = [
'#pretty_top_level', '.newbonklobby_playerentry_level', '.ingamescoreboard_playerlevel'
];
const UPDATE_INTERVAL = 500;
let lastUpdate = 0;
let isUpdatingDOM = false;
// Prevent multiple initializations
if (window.rieModLiteInitialized) return;
window.rieModLiteInitialized = true;
function safeCall(fn, fallback = null) {
try { return fn(); } catch (e) { return fallback; }
}
function log(msg) { console.log('[RieMod Lite+]', msg); }
function getGameDocument() {
if (gameDocument) return gameDocument;
const frame = document.getElementById('maingameframe');
if (frame && frame.contentDocument) {
gameDocument = frame.contentDocument;
return gameDocument;
}
return uw.Gdocument || document;
}
function getDisplayName(playerId) {
if (!uw.playerids?.[playerId]?.userName) return "Guest";
if (customNames[playerId]) return customNames[playerId];
if (nicknames[playerId]) return nicknames[playerId];
if (playerId == uw.myid && CUSTOM_NAME.active) return CUSTOM_NAME.value;
if (remoteCustomizations[playerId]?.name && playerId != uw.myid) return remoteCustomizations[playerId].name;
return uw.playerids[playerId].userName;
}
function getLevelDisplay(playerId) {
if (!uw.playerids?.[playerId]?.userName) return null;
if (customNotes[playerId]) return customNotes[playerId];
if (playerId !== uw.myid && remoteCustomizations[playerId]?.note) return remoteCustomizations[playerId].note;
if (customLevels[playerId]) return customLevels[playerId];
if (playerId !== uw.myid && remoteCustomizations[playerId]?.level) return remoteCustomizations[playerId].level;
return null;
}
function hookPIXIText(gameWin) {
if (!gameWin?.PIXI?.Text?.prototype || gameWin.PIXI.Text.prototype._rieModHooked) return;
const originalUpdate = gameWin.PIXI.Text.prototype.updateText;
gameWin.PIXI.Text.prototype.updateText = function() {
if (typeof this.text !== 'string' || !gameReady) return originalUpdate.call(this);
try {
for (const id in uw.playerids || {}) {
const player = uw.playerids[id];
if (!player?.userName) continue;
const displayName = getDisplayName(id);
if (displayName === player.userName) continue;
const safeName = player.userName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(safeName, 'ig');
if (regex.test(this.text)) {
this.text = this.text.replace(regex, displayName);
}
}
} catch (e) {}
return originalUpdate.call(this);
};
gameWin.PIXI.Text.prototype._rieModHooked = true;
}
function updateAllDOM(doc) {
if (!doc || !gameReady || !uw.playerids || isUpdatingDOM) return;
const now = Date.now();
if (now - lastUpdate < UPDATE_INTERVAL) return;
lastUpdate = now;
isUpdatingDOM = true;
try {
for (const selector of NAME_SELECTORS) {
const elements = doc.querySelectorAll(selector);
for (const el of elements) {
for (const id in uw.playerids) {
const player = uw.playerids[id];
if (!player?.userName) continue;
const displayName = getDisplayName(id);
if (displayName === player.userName) continue;
const safeName = player.userName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(safeName, 'i');
if (el.textContent && regex.test(el.textContent)) {
el.textContent = el.textContent.replace(new RegExp(safeName, 'ig'), displayName);
}
}
}
}
for (const selector of LEVEL_SELECTORS) {
const elements = doc.querySelectorAll(selector);
for (const el of elements) {
const playerId = getPlayerIdFromElement(el, doc);
if (!playerId) continue;
const displayText = getLevelDisplay(playerId);
if (displayText !== null) {
el.textContent = displayText;
}
}
}
} catch (e) {
console.warn('[RieMod Lite+] DOM update error:', e);
} finally {
isUpdatingDOM = false;
}
}
function getPlayerIdFromElement(el, doc) {
if (!el || !uw.playerids) return null;
let parent = el.parentElement;
let nameElement = null;
while (parent && !nameElement) {
try {
nameElement = parent.querySelector('.newbonklobby_playerentry_name, .ingamescoreboard_playername, .ingamechatname');
} catch (e) {}
if (nameElement) break;
parent = parent.parentElement;
}
if (!nameElement?.textContent) return null;
const nameText = nameElement.textContent.trim();
for (const id in uw.playerids) {
const player = uw.playerids[id];
if (player?.userName && getDisplayName(id) === nameText) return id;
}
return null;
}
function broadcast(type, data) {
if (!uw.sendToServer || !gameReady) return;
try {
uw.sendToServer(JSON.stringify({ type, senderId: uw.myid, ...data }));
} catch (e) {}
}
function handleCustomMessage(data) {
try {
const msg = JSON.parse(data);
if (!msg.senderId) return;
switch(msg.type) {
case "bonk_customizer":
remoteCustomizations[msg.senderId] = { name: msg.name, level: msg.level };
break;
case "bonk_level":
if (msg.targetId && msg.level) customLevels[msg.targetId] = msg.level;
break;
case "bonk_note":
if (msg.targetId) {
if (msg.note) remoteCustomizations[msg.senderId] = { ...(remoteCustomizations[msg.senderId] || {}), note: msg.note };
else if (remoteCustomizations[msg.senderId]) delete remoteCustomizations[msg.senderId].note;
}
break;
case "bonk_nick":
if (msg.targetId) {
if (msg.nickname) nicknames[msg.targetId] = msg.nickname;
else delete nicknames[msg.targetId];
}
break;
case "bonk_name":
if (msg.targetId && msg.name) customNames[msg.targetId] = msg.name;
break;
case "bonk_clearnicks":
Object.keys(nicknames).forEach(id => delete nicknames[id]);
break;
case "bonk_resetall":
if (msg.senderId === uw.myid) {
Object.keys(customNames).forEach(id => delete customNames[id]);
Object.keys(customLevels).forEach(id => delete customLevels[id]);
Object.keys(customNotes).forEach(id => delete customNotes[id]);
}
break;
case "bonk_resetnames":
if (msg.senderId === uw.myid) {
Object.keys(customNames).forEach(id => delete customNames[id]);
}
break;
}
} catch (e) {}
}
// ✅ Search by real name OR nickname, handles symbols/spaces
function findPlayerIdByName(searchTerm) {
if (!searchTerm || !uw.playerids) return null;
const cleanSearch = searchTerm.toLowerCase().trim();
// Exact match on real username
for (const id in uw.playerids) {
const player = uw.playerids[id];
if (player?.userName && player.userName.toLowerCase() === cleanSearch) return id;
}
// Includes match on real username
for (const id in uw.playerids) {
const player = uw.playerids[id];
if (player?.userName && player.userName.toLowerCase().includes(cleanSearch)) return id;
}
// Exact match on nickname
for (const id in nicknames) {
if (nicknames[id] && nicknames[id].toLowerCase() === cleanSearch) return id;
}
// Includes match on nickname
for (const id in nicknames) {
if (nicknames[id] && nicknames[id].toLowerCase().includes(cleanSearch)) return id;
}
// Exact match on custom name
for (const id in customNames) {
if (customNames[id] && customNames[id].toLowerCase() === cleanSearch) return id;
}
// Includes match on custom name
for (const id in customNames) {
if (customNames[id] && customNames[id].toLowerCase().includes(cleanSearch)) return id;
}
return null;
}
// ✅ Handles quotes, spaces, symbols like @#!$%^&*()
function parseArgs(input) {
const tokens = [];
let current = '';
let inQuote = false;
let escapeNext = false;
for (let i = 0; i < input.length; i++) {
const char = input[i];
if (escapeNext) {
current += char;
escapeNext = false;
continue;
}
if (char === '\\') {
escapeNext = true;
continue;
}
if (char === '"' && !escapeNext) {
inQuote = !inQuote;
continue;
}
if (char === ' ' && !inQuote) {
if (current !== '') {
tokens.push(current);
current = '';
}
continue;
}
current += char;
}
if (current !== '') tokens.push(current);
return tokens;
}
function showHelp() {
const lines = [
"⚙️ Rie's Mod Lite+ v7.0.5 - Commands",
"",
"🎭 NAME",
"/name - Toggle YOUR custom name on/off",
"/name <text> - Set YOUR display name",
"/name <player> <text> - Set ANOTHER player's display name",
"",
"📊 LEVEL & NOTE",
"/level <player> <num> - Set player's level (0-9999)",
"/note <player> <text> - Set custom text for player's level",
"",
"🔍 INFO",
"/whois <player> - Show real name vs nickname vs display",
"/whoall - List ALL players with real names",
"",
"🔄 RESET",
"/resetall - Reset ALL your changes (names/levels/notes)",
"/resetnames - Reset only name changes",
"",
"💡 Tips:",
"• Use quotes for spaces: /note ki1la \"Hello @#$!\"",
"• Works with real names OR nicknames",
"• Changes are client-side only"
];
const display = uw.displayInChat;
if (!display) return;
for (const line of lines) {
if (line.startsWith("⚙️")) display(line, "#ffffff", "#555555");
else if (line.startsWith("🎭") || line.startsWith("📊") || line.startsWith("🔍") || line.startsWith("🔄")) display(line, "#888888", "#444444");
else if (line.startsWith("/")) display(line, "#aaaaaa", "#3a3a3a");
else if (line.startsWith("💡")) display(line, "#66bb6a", "#2a552a");
else display(line, "#ffffff", "#333333");
}
}
function addCommands() {
if (typeof uw.commandhandle !== 'function') { setTimeout(addCommands, 500); return; }
const original = uw.commandhandle;
uw.commandhandle = function(chat_val) {
if (!gameReady) return original(chat_val);
const showMsg = (msg, color = "#ffffff") => {
if (uw.displayInChat) uw.displayInChat(msg, color, "#333333");
};
const error = (msg) => { showMsg("❌ " + msg, "#ef5350"); return ""; };
const success = (msg) => { showMsg("✅ " + msg, "#66bb6a"); return ""; };
// /info - Show help
if (chat_val === '/info') {
showHelp();
return "";
}
// /whoall - List all players with real names
if (chat_val === '/whoall') {
if (!uw.playerids) return error("No players found");
let count = 0;
for (const id in uw.playerids) {
const player = uw.playerids[id];
if (player?.userName) {
const display = getDisplayName(id);
const isMe = id == uw.myid ? " (YOU)" : "";
showMsg(`👤 ${player.userName}${display !== player.userName ? ` → "${display}"` : ""}${isMe}`, "#ffffff");
count++;
}
}
return success(`Total players: ${count}`);
}
// /whois <player> - Show real name vs nickname
if (chat_val.startsWith('/whois ')) {
const search = chat_val.substring(7).trim();
const playerId = findPlayerIdByName(search);
if (!playerId) return error('Player "' + search + '" not found');
const realName = uw.playerids?.[playerId]?.userName || "Unknown";
const nick = nicknames[playerId] || "None";
const customName = customNames[playerId] || "None";
const display = getDisplayName(playerId);
return success(`${search}: Real="${realName}" | Nick="${nick}" | CustomName="${customName}" | Display="${display}"`);
}
// /resetall - Reset all changes
if (chat_val === '/resetall') {
Object.keys(customNames).forEach(id => delete customNames[id]);
Object.keys(customLevels).forEach(id => delete customLevels[id]);
Object.keys(customNotes).forEach(id => delete customNotes[id]);
broadcast("bonk_resetall", {});
return success("🔄 All changes reset!");
}
// /resetnames - Reset only name changes
if (chat_val === '/resetnames') {
Object.keys(customNames).forEach(id => delete customNames[id]);
broadcast("bonk_resetnames", {});
return success("🔄 All name changes reset!");
}
// /name - Toggle or set name
if (chat_val === '/name') {
CUSTOM_NAME.active = !CUSTOM_NAME.active;
broadcast("bonk_customizer", { name: CUSTOM_NAME.value, level: customLevels[uw.myid] || "" });
return success(CUSTOM_NAME.active ? "Custom name ENABLED" : "Custom name DISABLED");
}
// /name <text> - Set YOUR display name
if (chat_val.startsWith('/name ')) {
const args = parseArgs(chat_val.substring(6).trim());
// Check if it's /name <player> <text> (2+ args)
if (args.length >= 2) {
const playerId = findPlayerIdByName(args[0]);
if (!playerId) return error('Player "' + args[0] + '" not found');
// Extract full text after player name
const rest = chat_val.substring(6).trim();
const playerEnd = rest.indexOf(args[0]) + args[0].length;
let nameText = rest.substring(playerEnd).trim();
if (nameText.startsWith('"') && nameText.endsWith('"')) {
nameText = nameText.slice(1, -1);
}
if (!nameText.trim()) return error("Name cannot be empty");
customNames[playerId] = nameText;
broadcast("bonk_name", { targetId: playerId, name: nameText });
return success("🏷️ Set " + args[0] + "'s name to: " + nameText);
}
// Single arg = set your own name
const newName = chat_val.substring(6);
if (newName.trim()) {
CUSTOM_NAME.value = newName;
CUSTOM_NAME.active = true;
broadcast("bonk_customizer", { name: CUSTOM_NAME.value, level: customLevels[uw.myid] || "" });
return success("🏷️ YOUR name set to: " + CUSTOM_NAME.value);
}
return error("Name cannot be empty");
}
// /level <player> <number>
if (chat_val.startsWith('/level ')) {
const args = parseArgs(chat_val.substring(7).trim());
if (args.length < 2) return error("Usage: /level <player> <0-9999>");
const pid = findPlayerIdByName(args[0]);
if (!pid) return error('Player "' + args[0] + '" not found');
const num = parseInt(args[1]);
if (isNaN(num) || num < 0 || num > 9999) return error("Level must be 0-9999");
const display = "Level " + num;
customLevels[pid] = display;
broadcast("bonk_level", { targetId: pid, level: display });
return success("📊 Set " + args[0] + "'s level to: " + display);
}
// /note <player> <text>
if (chat_val.startsWith('/note ')) {
const rest = chat_val.substring(6).trim();
const args = parseArgs(rest);
if (args.length < 2) return error("Usage: /note <player> <text>\n💡 Use quotes: /note ki1la \"Hello @#$!\"");
const pid = findPlayerIdByName(args[0]);
if (!pid) return error('Player "' + args[0] + '" not found');
const playerEnd = rest.indexOf(args[0]) + args[0].length;
let noteText = rest.substring(playerEnd).trim();
if (noteText.startsWith('"') && noteText.endsWith('"')) {
noteText = noteText.slice(1, -1);
}
if (!noteText.trim()) {
delete customNotes[pid];
broadcast("bonk_note", { targetId: pid, note: "" });
return success("✅ Note cleared for " + args[0]);
}
customNotes[pid] = noteText;
broadcast("bonk_note", { targetId: pid, note: noteText });
return success("✅ Note set for " + args[0]);
}
return original(chat_val);
};
}
function init() {
const frame = document.getElementById('maingameframe');
if (!frame) { setTimeout(init, 500); return; }
const gameWin = frame.contentWindow;
const gameDoc = frame.contentDocument;
if (!gameWin?.PIXI || typeof uw.playerids === 'undefined' || !gameDoc?.body) {
setTimeout(init, 500);
return;
}
gameDocument = gameDoc;
gameReady = true;
if (typeof uw.handleCustomMessageOriginal === 'undefined') {
uw.handleCustomMessageOriginal = uw.handleCustomMessage || (() => {});
uw.handleCustomMessage = function(data) {
safeCall(() => handleCustomMessage(data));
safeCall(() => uw.handleCustomMessageOriginal(data));
};
}
safeCall(() => hookPIXIText(gameWin));
addCommands();
setInterval(() => {
if (gameReady && !isUpdatingDOM) {
safeCall(() => updateAllDOM(getGameDocument()));
}
}, UPDATE_INTERVAL);
broadcast("bonk_customizer", { name: CUSTOM_NAME.value, level: customLevels[uw.myid] || "" });
log('Initialized - Lite+ v7.0.5');
}
const originalCreateOrJoinRoom = uw.createOrJoinRoom;
uw.createOrJoinRoom = function() {
gameReady = false;
lastUpdate = 0;
isUpdatingDOM = false;
Object.keys(remoteCustomizations).forEach(k => delete remoteCustomizations[k]);
return originalCreateOrJoinRoom.apply(this, arguments);
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();