gimmickCellar

Custom Chat Tags

Apr 11th, 2026 (edited)
62
0
Never
6
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.86 KB | None | 0 0
  1. // ==UserScript==
  2. // @name Custom Tags for OWOT
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.1
  5. // @description Highly compressed custom tag system using Base128 binary bit-packing into invisible characters. Safely supports HTML formatting.
  6. // @author gimmickCellar
  7. // @match *://*.ourworldoftext.com/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. const TAG_START = 0xE0000;
  15.  
  16. let myTag = localStorage.getItem("customChatTag") || "";
  17. let myTagColor = localStorage.getItem("customChatTagColor") || "#aaaaaa";
  18.  
  19. // --- HTML Sanitization (XSS Protection) ---
  20. function sanitizeHTML(html) {
  21. if (!html) return "";
  22. let doc = new DOMParser().parseFromString(html, 'text/html');
  23. let walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_ELEMENT, null, false);
  24. let nodesToRemove = [];
  25.  
  26. // Only allow basic text formatting tags
  27. const ALLOWED_TAGS = ['B', 'I', 'U', 'S', 'SPAN', 'STRONG', 'EM', 'MARK', 'SUB', 'SUP', 'CODE', 'DEL'];
  28. const ALLOWED_ATTRS = ['style', 'title'];
  29.  
  30. let node;
  31. while (node = walker.nextNode()) {
  32. if (!ALLOWED_TAGS.includes(node.tagName)) {
  33. nodesToRemove.push(node);
  34. } else {
  35. // Sanitize attributes
  36. for (let i = node.attributes.length - 1; i >= 0; i--) {
  37. let attr = node.attributes[i];
  38. if (!ALLOWED_ATTRS.includes(attr.name.toLowerCase())) {
  39. node.removeAttribute(attr.name);
  40. } else if (attr.name.toLowerCase() === 'style') {
  41. // Prevent CSS injection that breaks the layout or executes code
  42. if (/(expression|url|behavior|position|display|transform|margin|padding|top|left|right|bottom|z-index|float|clear)/i.test(attr.value)) {
  43. node.removeAttribute('style');
  44. }
  45. }
  46. }
  47. }
  48. }
  49.  
  50. // Remove bad nodes, but keep their internal text so message context isn't lost
  51. for (let i = nodesToRemove.length - 1; i >= 0; i--) {
  52. let n = nodesToRemove[i];
  53. let text = document.createTextNode(n.textContent);
  54. n.parentNode.replaceChild(text, n);
  55. }
  56.  
  57. return doc.body.innerHTML;
  58. }
  59.  
  60. function escapeHtml(str) {
  61. return String(str).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
  62. }
  63.  
  64. // --- Compression & Binary Utilities ---
  65. function hexToRgb(hex) {
  66. hex = hex.replace(/^#/, '');
  67. if (hex.length === 3) hex = hex.split('').map(c => c+c).join('');
  68. let int = parseInt(hex, 16) || 0;
  69. return [(int >> 16) & 0xFF, (int >> 8) & 0xFF, int & 0xFF];
  70. }
  71.  
  72. function rgbToHex(r, g, b) {
  73. return "#" + (1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1);
  74. }
  75.  
  76. function packBase128(bytes) {
  77. let packed = [];
  78. let current = 0, bits = 0;
  79. for (let i = 0; i < bytes.length; i++) {
  80. current = (current << 8) | bytes[i];
  81. bits += 8;
  82. while (bits >= 7) {
  83. bits -= 7;
  84. packed.push((current >> bits) & 0x7F);
  85. }
  86. }
  87. if (bits > 0) {
  88. packed.push((current << (7 - bits)) & 0x7F);
  89. }
  90. return packed;
  91. }
  92.  
  93. function unpackBase128(packed) {
  94. let bytes = [];
  95. let current = 0, bits = 0;
  96. for (let i = 0; i < packed.length; i++) {
  97. current = (current << 7) | packed[i];
  98. bits += 7;
  99. while (bits >= 8) {
  100. bits -= 8;
  101. bytes.push((current >> bits) & 0xFF);
  102. }
  103. }
  104. return bytes;
  105. }
  106.  
  107. // --- Encoding & Decoding ---
  108. function encodeTag(tagStr, colorStr) {
  109. if (!tagStr) return "";
  110. let [r, g, b] = hexToRgb(colorStr);
  111. let textBytes = new TextEncoder().encode(tagStr);
  112.  
  113. let payload = new Uint8Array(3 + textBytes.length);
  114. payload[0] = r; payload[1] = g; payload[2] = b;
  115. payload.set(textBytes, 3);
  116.  
  117. let packed7Bit = packBase128(payload);
  118.  
  119. let result = "";
  120. for(let i = 0; i < packed7Bit.length; i++) {
  121. result += String.fromCodePoint(TAG_START + packed7Bit[i]);
  122. }
  123. return result;
  124. }
  125.  
  126. function decodeTag(msg) {
  127. if (typeof msg !== "string") return null;
  128. let packed7Bit = [];
  129. let i = 0;
  130.  
  131. while (i < msg.length) {
  132. let cp = msg.codePointAt(i);
  133. if (cp >= TAG_START && cp <= 0xE007F) {
  134. packed7Bit.push(cp - TAG_START);
  135. i += 2;
  136. } else {
  137. break;
  138. }
  139. }
  140.  
  141. if (packed7Bit.length === 0) return null;
  142.  
  143. try {
  144. let bytes = unpackBase128(packed7Bit);
  145. if (bytes.length < 3) return null;
  146.  
  147. let colorStr = rgbToHex(bytes[0], bytes[1], bytes[2]);
  148. let tagBytes = new Uint8Array(bytes.slice(3));
  149. let tagStr = new TextDecoder().decode(tagBytes);
  150.  
  151. return { tag: tagStr, color: colorStr, lengthConsumed: i };
  152. } catch(e) {
  153. return null;
  154. }
  155. }
  156.  
  157. // --- OWOT Integration ---
  158.  
  159. // 1. Intercept outgoing messages
  160. OWOT.on("chatSend", function(e) {
  161. if (!myTag || e.message.trim().length === 0 || e.message.startsWith("/")) return;
  162.  
  163. let encoded = encodeTag(myTag, myTagColor);
  164. let msgLim = state.userModel.is_staff ? 3030 : 400;
  165.  
  166. if (encoded.length + e.message.length > msgLim) {
  167. e.message = e.message.substring(0, Math.max(0, msgLim - encoded.length));
  168. }
  169.  
  170. e.message = encoded + e.message;
  171. });
  172.  
  173. // 2. Intercept incoming messages AND chat history right before render
  174. if (typeof window.addChat === "function") {
  175. const originalAddChat = window.addChat;
  176.  
  177. window.addChat = function(chatfield, id, type, nickname, message, realUsername, op, admin, staff, color, date, dataObj) {
  178. if (id === 0 && realUsername === "[ Server ]") {
  179. return originalAddChat.apply(this, arguments);
  180. }
  181.  
  182. let decoded = decodeTag(message);
  183. if (decoded) {
  184. // Strip the invisible characters from the message
  185. message = message.slice(decoded.lengthConsumed);
  186.  
  187. // Sanitize the HTML payload
  188. let tagText = sanitizeHTML(decoded.tag);
  189. let tagColor = escapeHtml(decoded.color);
  190.  
  191. if (!dataObj) dataObj = {};
  192.  
  193. let origRankName = dataObj.rankName;
  194. let origRankColor = dataObj.rankColor;
  195.  
  196. if (!origRankName) {
  197. if (op) { origRankName = "OP"; origRankColor = "#0033cc"; op = false; }
  198. else if (admin) { origRankName = "A"; origRankColor = "#FF0000"; admin = false; }
  199. else if (staff) { origRankName = "M"; origRankColor = "#009933"; staff = false; }
  200. }
  201.  
  202. if (origRankName) {
  203. // Combine tags safely
  204. dataObj.rankName = tagText + `)&nbsp;<span style="color:${origRankColor}; font-weight:bold">(` + origRankName;
  205. dataObj.rankColor = tagColor;
  206. } else {
  207. dataObj.rankName = tagText;
  208. dataObj.rankColor = tagColor;
  209. }
  210. }
  211.  
  212. // Call original renderer with our modified arguments
  213. return originalAddChat.call(this, chatfield, id, type, nickname, message, realUsername, op, admin, staff, color, date, dataObj);
  214. };
  215. } else {
  216. console.warn("OWOT Invisible Chat Tags: window.addChat not found!");
  217. }
  218.  
  219. // 3. Command hooks
  220. OWOT.chat.registerCommand("tag", function(args) {
  221. myTag = args.join(" ").trim();
  222. localStorage.setItem("customChatTag", myTag);
  223.  
  224. let safePreview = sanitizeHTML(myTag);
  225. clientChatResponse(myTag ? "Chat tag set to: " + safePreview : "Chat tag disabled.");
  226. }, ["text/html"], "Set your custom invisible chat tag (HTML supported)");
  227.  
  228. OWOT.chat.registerCommand("tagcolor", function(args) {
  229. if (!args[0]) return clientChatResponse("Current tag color: " + myTagColor);
  230. myTagColor = args[0];
  231. localStorage.setItem("customChatTagColor", myTagColor);
  232. clientChatResponse("Chat tag color set to: " + myTagColor);
  233. }, ["color"], "Set the default color of your chat tag");
  234.  
  235. console.log("OWOT Compressed Invisible HTML Chat Tags loaded!");
  236. })();
Advertisement
Comments
  • Texnulix
    109 days
    Comment was deleted
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
Add Comment
Please, Sign In to add comment