Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Custom Tags for OWOT
- // @namespace http://tampermonkey.net/
- // @version 1.1
- // @description Highly compressed custom tag system using Base128 binary bit-packing into invisible characters. Safely supports HTML formatting.
- // @author gimmickCellar
- // @match *://*.ourworldoftext.com/*
- // @grant none
- // ==/UserScript==
- (function() {
- 'use strict';
- const TAG_START = 0xE0000;
- let myTag = localStorage.getItem("customChatTag") || "";
- let myTagColor = localStorage.getItem("customChatTagColor") || "#aaaaaa";
- // --- HTML Sanitization (XSS Protection) ---
- function sanitizeHTML(html) {
- if (!html) return "";
- let doc = new DOMParser().parseFromString(html, 'text/html');
- let walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_ELEMENT, null, false);
- let nodesToRemove = [];
- // Only allow basic text formatting tags
- const ALLOWED_TAGS = ['B', 'I', 'U', 'S', 'SPAN', 'STRONG', 'EM', 'MARK', 'SUB', 'SUP', 'CODE', 'DEL'];
- const ALLOWED_ATTRS = ['style', 'title'];
- let node;
- while (node = walker.nextNode()) {
- if (!ALLOWED_TAGS.includes(node.tagName)) {
- nodesToRemove.push(node);
- } else {
- // Sanitize attributes
- for (let i = node.attributes.length - 1; i >= 0; i--) {
- let attr = node.attributes[i];
- if (!ALLOWED_ATTRS.includes(attr.name.toLowerCase())) {
- node.removeAttribute(attr.name);
- } else if (attr.name.toLowerCase() === 'style') {
- // Prevent CSS injection that breaks the layout or executes code
- if (/(expression|url|behavior|position|display|transform|margin|padding|top|left|right|bottom|z-index|float|clear)/i.test(attr.value)) {
- node.removeAttribute('style');
- }
- }
- }
- }
- }
- // Remove bad nodes, but keep their internal text so message context isn't lost
- for (let i = nodesToRemove.length - 1; i >= 0; i--) {
- let n = nodesToRemove[i];
- let text = document.createTextNode(n.textContent);
- n.parentNode.replaceChild(text, n);
- }
- return doc.body.innerHTML;
- }
- function escapeHtml(str) {
- return String(str).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
- }
- // --- Compression & Binary Utilities ---
- function hexToRgb(hex) {
- hex = hex.replace(/^#/, '');
- if (hex.length === 3) hex = hex.split('').map(c => c+c).join('');
- let int = parseInt(hex, 16) || 0;
- return [(int >> 16) & 0xFF, (int >> 8) & 0xFF, int & 0xFF];
- }
- function rgbToHex(r, g, b) {
- return "#" + (1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1);
- }
- function packBase128(bytes) {
- let packed = [];
- let current = 0, bits = 0;
- for (let i = 0; i < bytes.length; i++) {
- current = (current << 8) | bytes[i];
- bits += 8;
- while (bits >= 7) {
- bits -= 7;
- packed.push((current >> bits) & 0x7F);
- }
- }
- if (bits > 0) {
- packed.push((current << (7 - bits)) & 0x7F);
- }
- return packed;
- }
- function unpackBase128(packed) {
- let bytes = [];
- let current = 0, bits = 0;
- for (let i = 0; i < packed.length; i++) {
- current = (current << 7) | packed[i];
- bits += 7;
- while (bits >= 8) {
- bits -= 8;
- bytes.push((current >> bits) & 0xFF);
- }
- }
- return bytes;
- }
- // --- Encoding & Decoding ---
- function encodeTag(tagStr, colorStr) {
- if (!tagStr) return "";
- let [r, g, b] = hexToRgb(colorStr);
- let textBytes = new TextEncoder().encode(tagStr);
- let payload = new Uint8Array(3 + textBytes.length);
- payload[0] = r; payload[1] = g; payload[2] = b;
- payload.set(textBytes, 3);
- let packed7Bit = packBase128(payload);
- let result = "";
- for(let i = 0; i < packed7Bit.length; i++) {
- result += String.fromCodePoint(TAG_START + packed7Bit[i]);
- }
- return result;
- }
- function decodeTag(msg) {
- if (typeof msg !== "string") return null;
- let packed7Bit = [];
- let i = 0;
- while (i < msg.length) {
- let cp = msg.codePointAt(i);
- if (cp >= TAG_START && cp <= 0xE007F) {
- packed7Bit.push(cp - TAG_START);
- i += 2;
- } else {
- break;
- }
- }
- if (packed7Bit.length === 0) return null;
- try {
- let bytes = unpackBase128(packed7Bit);
- if (bytes.length < 3) return null;
- let colorStr = rgbToHex(bytes[0], bytes[1], bytes[2]);
- let tagBytes = new Uint8Array(bytes.slice(3));
- let tagStr = new TextDecoder().decode(tagBytes);
- return { tag: tagStr, color: colorStr, lengthConsumed: i };
- } catch(e) {
- return null;
- }
- }
- // --- OWOT Integration ---
- // 1. Intercept outgoing messages
- OWOT.on("chatSend", function(e) {
- if (!myTag || e.message.trim().length === 0 || e.message.startsWith("/")) return;
- let encoded = encodeTag(myTag, myTagColor);
- let msgLim = state.userModel.is_staff ? 3030 : 400;
- if (encoded.length + e.message.length > msgLim) {
- e.message = e.message.substring(0, Math.max(0, msgLim - encoded.length));
- }
- e.message = encoded + e.message;
- });
- // 2. Intercept incoming messages AND chat history right before render
- if (typeof window.addChat === "function") {
- const originalAddChat = window.addChat;
- window.addChat = function(chatfield, id, type, nickname, message, realUsername, op, admin, staff, color, date, dataObj) {
- if (id === 0 && realUsername === "[ Server ]") {
- return originalAddChat.apply(this, arguments);
- }
- let decoded = decodeTag(message);
- if (decoded) {
- // Strip the invisible characters from the message
- message = message.slice(decoded.lengthConsumed);
- // Sanitize the HTML payload
- let tagText = sanitizeHTML(decoded.tag);
- let tagColor = escapeHtml(decoded.color);
- if (!dataObj) dataObj = {};
- let origRankName = dataObj.rankName;
- let origRankColor = dataObj.rankColor;
- if (!origRankName) {
- if (op) { origRankName = "OP"; origRankColor = "#0033cc"; op = false; }
- else if (admin) { origRankName = "A"; origRankColor = "#FF0000"; admin = false; }
- else if (staff) { origRankName = "M"; origRankColor = "#009933"; staff = false; }
- }
- if (origRankName) {
- // Combine tags safely
- dataObj.rankName = tagText + `) <span style="color:${origRankColor}; font-weight:bold">(` + origRankName;
- dataObj.rankColor = tagColor;
- } else {
- dataObj.rankName = tagText;
- dataObj.rankColor = tagColor;
- }
- }
- // Call original renderer with our modified arguments
- return originalAddChat.call(this, chatfield, id, type, nickname, message, realUsername, op, admin, staff, color, date, dataObj);
- };
- } else {
- console.warn("OWOT Invisible Chat Tags: window.addChat not found!");
- }
- // 3. Command hooks
- OWOT.chat.registerCommand("tag", function(args) {
- myTag = args.join(" ").trim();
- localStorage.setItem("customChatTag", myTag);
- let safePreview = sanitizeHTML(myTag);
- clientChatResponse(myTag ? "Chat tag set to: " + safePreview : "Chat tag disabled.");
- }, ["text/html"], "Set your custom invisible chat tag (HTML supported)");
- OWOT.chat.registerCommand("tagcolor", function(args) {
- if (!args[0]) return clientChatResponse("Current tag color: " + myTagColor);
- myTagColor = args[0];
- localStorage.setItem("customChatTagColor", myTagColor);
- clientChatResponse("Chat tag color set to: " + myTagColor);
- }, ["color"], "Set the default color of your chat tag");
- console.log("OWOT Compressed Invisible HTML Chat Tags loaded!");
- })();
Advertisement