// ==UserScript==
// @name Monkey Chat Handler (Typing Style)
// @namespace http://tampermonkey.net/
// @version 3.0002
// @license MIT
// @description A mod that handles monke bot's chats with a cool typing animation.
// @author You & AI Assistant
// @match https://bonk.io/gameframe-release.html
// @icon https://www.google.com/s2/favicons?sz=64&domain=bonk.io
// @grant GM_addStyle
// ==/UserScript==
let Currentvursion =3.0002
GM_addStyle(`
.cool-chat-message {
border-left: 3px solid #4CAF50;
padding: 5px 8px;
margin: 4px 0;
word-wrap: break-word;
}
.cool-chat-message a {
color: #87CEEB;
text-decoration: none;
font-weight: bold;
transition: color 0.2s ease, text-decoration 0.2s ease;
}
.cool-chat-message a:hover {
color: #98FB98;
text-decoration: underline;
}
`);
let autoCanv = function() {
Object.defineProperty(this, 'chatBox1', {
get: function() { return document.getElementById('newbonklobby_chat_content'); }
});
Object.defineProperty(this, 'chatBox2', {
get: function() { return document.getElementById('ingamechatcontent'); }
});
};
let canv = new autoCanv();
function linkify(text) {
const urlRegex = /(https?:\/\/[^\s]+)/g;
return text.replace(urlRegex, url => `<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>`);
}
async function displayInChat(msg, color = '#228B22', inner = true, size) {
const TYPING_SPEED_MS = 30;
const chatBox1 = canv.chatBox1;
const chatBox2 = canv.chatBox2;
if (!chatBox1 && !chatBox2) {
console.error("No chatbox elements found!");
return;
}
const scrollToBottom = () => {
if (chatBox1) chatBox1.scrollTop = chatBox1.scrollHeight;
if (chatBox2) chatBox2.scrollTop = chatBox2.scrollHeight;
};
const finalHtml = inner ? linkify(msg) : linkify(msg.replace(/</g, "<").replace(/>/g, ">"));
const tempDiv = document.createElement('div');
tempDiv.innerHTML = finalHtml;
const plainText = tempDiv.textContent || "";
const createMessageElements = () => {
const messageContainer = document.createElement('div');
messageContainer.className = 'cool-chat-message';
const typingSpan = document.createElement('span');
typingSpan.style.color = color;
if (size) {
typingSpan.style.fontSize = size + 'px';
}
messageContainer.appendChild(typingSpan);
return { messageContainer, typingSpan };
};
const elements1 = chatBox1 ? createMessageElements() : null;
const elements2 = chatBox2 ? createMessageElements() : null;
if (elements1) chatBox1.appendChild(elements1.messageContainer);
if (elements2) chatBox2.appendChild(elements2.messageContainer);
scrollToBottom();
for (let i = 0; i < plainText.length; i++) {
const char = plainText[i];
if (elements1) elements1.typingSpan.textContent += char;
if (elements2) elements2.typingSpan.textContent += char;
scrollToBottom();
await new Promise(resolve => setTimeout(resolve, TYPING_SPEED_MS));
}
if (elements1) elements1.typingSpan.innerHTML = finalHtml;
if (elements2) elements2.typingSpan.innerHTML = finalHtml;
}
let autoName = function() {
Object.defineProperty(this, 'name', {
get: function() {
return document.getElementById("pretty_top_name")?.textContent;
}
});
};
let name = new autoName();
let monkTypes = {
handleJoining: {
run: function(data) {
if (data[0].to == name.name && data[0].version==Currentvursion) {
pack(`4,{"initiator":"monke","type":"returnMessage","confirmed":"yes","from":"${name.name}"}`);
}
},
variables: ['data'],
},
chatMessage: {
run: function(data) {
if (data[0].to == name.name) {
const message = Mycipher.decrypt(data[0].message);
displayInChat(message);
}
},
variables: ['data'],
},
};
let monkVariableTypes = {
data: {
description: 'returns the data from the monk command',
returned: function(data) {
return data;
}
}
};
function handleMonk(data) {
let runner = monkTypes[data.type];
if (!runner) return;
runner.run(runner.variables.map(variable => monkVariableTypes[variable].returned(data)));
}
let Mycipher;
let oldsend = WebSocket.prototype.send;
let code = function(xx) {
if (xx == "5") {
window.bonkws = this;
let old_onmsg = this.onmessage;
this.onmessage = function(yy) {
if (yy.data.startsWith("42[")) {
try {
let data = JSON.parse(yy.data.slice(2));
if (data[0] == 21) {
if(data[1].wl.startsWith('monke:')){
//expected monke:encryption key:rounds
let fullstring=data[1].wl;
let parts = data[1].wl.split(':');
Mycipher = new EduCipher(parts[1]);
pack(`4,{"initiator":"monke","type":"returnMessage","key":"${Mycipher.encrypt(parts[1])}","from":"${name.name}"}`);
let newString=yy.data.replace('"'+fullstring+'"',parts[2]);
old_onmsg.call(this,{data:newString});
return
}}
if (data[0] == 7 && data[2].initiator == 'monke') {
handleMonk(data[2]);
}
} catch (e) {}
}
return old_onmsg.call(this, yy);
};
}
return oldsend.call(this, xx);
};
function fixOverwriteBitch() {
WebSocket.prototype.send = code;
}
fixOverwriteBitch();
async function pack(input) {
if (window.bonkws && window.bonkws.readyState === 1) {
await window.bonkws.send("42[" + input + "]");
} else {
console.error("Bonk WebSocket not ready to send message.");
}
}
class EduCipher {
constructor(masterKey) {
this.BLOCK_SIZE = 16;
this.NUM_ROUNDS = 8;
const { substitutionTable, reverseSubstitutionTable } = this._createSubstitutionTables(masterKey);
this.substitutionTable = substitutionTable;
this.reverseSubstitutionTable = reverseSubstitutionTable;
this.roundKeys = this._expandKeyToRoundKeys(masterKey);
}
_createSubstitutionTables(key) {
const substitutionTable = new Uint8Array(256);
const reverseSubstitutionTable = new Uint8Array(256);
for (let i = 0; i < 256; i++) {
substitutionTable[i] = i;
}
let shuffleIndex = 0;
for (let i = 0; i < 256; i++) {
const keyByte = key.charCodeAt(i % key.length);
shuffleIndex = (shuffleIndex + substitutionTable[i] + keyByte) % 256;
[substitutionTable[i], substitutionTable[shuffleIndex]] = [substitutionTable[shuffleIndex], substitutionTable[i]];
}
for (let i = 0; i < 256; i++) {
reverseSubstitutionTable[substitutionTable[i]] = i;
}
return { substitutionTable, reverseSubstitutionTable };
}
_expandKeyToRoundKeys(masterKey) {
const expandedKeys = [];
let currentKey = new Uint8Array(this.BLOCK_SIZE);
for (let i = 0; i < this.BLOCK_SIZE; i++) {
currentKey[i] = masterKey.charCodeAt(i % masterKey.length) || 0;
}
for (let round = 0; round < this.NUM_ROUNDS + 1; round++) {
expandedKeys.push(new Uint8Array(currentKey));
currentKey[0] = this.substitutionTable[currentKey[0]] ^ (round + 1);
for (let i = 1; i < this.BLOCK_SIZE; i++) {
currentKey[i] = this.substitutionTable[currentKey[i]] ^ currentKey[i - 1];
}
}
return expandedKeys;
}
encrypt(message) {
const messageAsBytes = new TextEncoder().encode(message);
const paddedMessage = this._addPadding(messageAsBytes);
const encryptedBlocks = new Uint8Array(paddedMessage.length);
const iv = this._getInsecureRandomBytes(this.BLOCK_SIZE);
let previousCipherBlock = iv;
for (let i = 0; i < paddedMessage.length; i += this.BLOCK_SIZE) {
let blockToEncrypt = paddedMessage.slice(i, i + this.BLOCK_SIZE);
for (let j = 0; j < this.BLOCK_SIZE; j++) {
blockToEncrypt[j] ^= previousCipherBlock[j];
}
const encryptedBlock = this._encryptBlock(blockToEncrypt);
encryptedBlocks.set(encryptedBlock, i);
previousCipherBlock = encryptedBlock;
}
const finalPayload = new Uint8Array(iv.length + encryptedBlocks.length);
finalPayload.set(iv);
finalPayload.set(encryptedBlocks, iv.length);
return btoa(String.fromCharCode(...finalPayload));
}
decrypt(encryptedBase64) {
const combinedBytes = Uint8Array.from(atob(encryptedBase64), c => c.charCodeAt(0));
const iv = combinedBytes.slice(0, this.BLOCK_SIZE);
const encryptedMessage = combinedBytes.slice(this.BLOCK_SIZE);
const decryptedBlocks = new Uint8Array(encryptedMessage.length);
let previousCipherBlock = iv;
for (let i = 0; i < encryptedMessage.length; i += this.BLOCK_SIZE) {
const blockToDecrypt = encryptedMessage.slice(i, i + this.BLOCK_SIZE);
const decryptedBlock = this._decryptBlock(blockToDecrypt);
for (let j = 0; j < this.BLOCK_SIZE; j++) {
decryptedBlock[j] ^= previousCipherBlock[j];
}
decryptedBlocks.set(decryptedBlock, i);
previousCipherBlock = blockToDecrypt;
}
const unpaddedBytes = this._removePadding(decryptedBlocks);
return new TextDecoder().decode(unpaddedBytes);
}
_encryptBlock(block) {
let state = new Uint8Array(block);
this._mixWithRoundKey(state, this.roundKeys[0]);
for (let round = 1; round < this.NUM_ROUNDS; round++) {
this._substituteBytes(state, this.substitutionTable);
this._shiftBytes(state);
this._mixWithRoundKey(state, this.roundKeys[round]);
}
this._substituteBytes(state, this.substitutionTable);
this._mixWithRoundKey(state, this.roundKeys[this.NUM_ROUNDS]);
return state;
}
_decryptBlock(block) {
let state = new Uint8Array(block);
this._mixWithRoundKey(state, this.roundKeys[this.NUM_ROUNDS]);
this._substituteBytes(state, this.reverseSubstitutionTable);
for (let round = this.NUM_ROUNDS - 1; round >= 1; round--) {
this._mixWithRoundKey(state, this.roundKeys[round]);
this._reverseShiftBytes(state);
this._substituteBytes(state, this.reverseSubstitutionTable);
}
this._mixWithRoundKey(state, this.roundKeys[0]);
return state;
}
_substituteBytes(state, table) {
for (let i = 0; i < state.length; i++) state[i] = table[state[i]];
}
_shiftBytes(state) {
const firstByte = state[0];
state.copyWithin(0, 1);
state[state.length - 1] = firstByte;
}
_reverseShiftBytes(state) {
const lastByte = state[state.length - 1];
state.copyWithin(1, 0);
state[0] = lastByte;
}
_mixWithRoundKey(state, roundKey) {
for (let i = 0; i < state.length; i++) state[i] ^= roundKey[i];
}
_addPadding(data) {
const padLength = this.BLOCK_SIZE - (data.length % this.BLOCK_SIZE);
const padding = new Uint8Array(padLength).fill(padLength);
const result = new Uint8Array(data.length + padLength);
result.set(data);
result.set(padding, data.length);
return result;
}
_removePadding(data) {
const padLength = data[data.length - 1];
if (padLength === 0 || padLength > data.length) {
throw new Error("Invalid padding detected. Decryption failed.");
}
return data.slice(0, data.length - padLength);
}
_getInsecureRandomBytes(length) {
const randomBytes = new Uint8Array(length);
for (let i = 0; i < length; i++) {
randomBytes[i] = Math.floor(Math.random() * 256);
}
return randomBytes;
}
}