gimmickCellar

CoolChat (again)

May 24th, 2026 (edited)
36
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.63 KB | None | 0 0
  1. // ==UserScript==
  2. // @name CoolChat
  3. // @namespace greasyfork.org/en/users/1213777-logan-usw
  4. // @version 3.0.0a
  5. // @description Markdown, Previews, X/YT/Spotify/GIF Embeds, Emote Fix, Wikimedia optimization, and Custom gimmickCellar Emotes. TheNiceOne added support for ...network.
  6. // @author gimmickCellar, TheNiceOne
  7. // @match https://ourworldoftext.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=ourworldoftext.com
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (() => {
  14. // 1. Inject Twitter Widget Script
  15. if (!document.getElementById('twitter-wjs')) {
  16. const twScript = document.createElement('script');
  17. twScript.id = 'twitter-wjs';
  18. twScript.src = "https://platform.twitter.com/widgets.js";
  19. twScript.charset = "utf-8";
  20. twScript.async = true;
  21. document.head.appendChild(twScript);
  22. }
  23.  
  24. const css = `
  25. .msg-md-bold { font-weight: bold; }
  26. .msg-md-italic { font-style: italic; }
  27. .msg-md-strike { text-decoration: line-through; }
  28. .msg-md-code { background: rgba(255,255,255,0.1); font-family: monospace; padding: 1px 4px; border-radius: 3px; color: #ffadad; }
  29. .msg-link { color: #3498db; text-decoration: underline; }
  30.  
  31. /* Directional Colors */
  32. .msg-color-green { color: #789922 !important; }
  33. .msg-color-purple { color: #825fdb !important; }
  34. .msg-color-gold { color: #dbac5f !important; }
  35. .msg-color-blue { color: #5f94db !important; }
  36.  
  37. /* Custom Emote Class (16px) */
  38. .coolchat-custom-emote {
  39. background-image: url('https://files.catbox.moe/hlimbl.png');
  40. background-size: auto 16px;
  41. height: 16px;
  42. width: 16px;
  43. vertical-align: middle;
  44. display: inline-block;
  45. margin: 0 1px;
  46. }
  47.  
  48. /* Media Previews */
  49. .owot-media-container { display: block; margin-top: 8px; max-width: 500px; width: 100%; }
  50. .owot-preview-img {
  51. max-width: 100%; max-height: 300px; object-fit: contain;
  52. display: block; border-radius: 4px; border: 1px solid #333; background: #111;
  53. }
  54. .owot-embed-iframe { width: 100%; aspect-ratio: 16 / 9; border-radius: 8px; border: none; background: #000; overflow: hidden; margin-bottom: 5px; }
  55. .owot-embed-spotify { width: 100%; height: 80px; border-radius: 12px; border: none; margin-bottom: 5px; }
  56. .owot-embed-gif { max-width: 300px; aspect-ratio: 1 / 1; border-radius: 8px; border: none; }
  57. .tweet-loading { font-size: 0.8em; color: #888; font-style: italic; padding: 10px; border: 1px dashed #444; border-radius: 8px; }
  58. `;
  59.  
  60. const styleSheet = document.createElement("style");
  61. styleSheet.innerHTML = css;
  62. document.head.appendChild(styleSheet);
  63.  
  64. const customEmoteMap = {
  65. "gc": 0, "gimmickcellar": 0,
  66. "gcfuckyou": 1,
  67. "gcthinkaaa": 2,
  68. "gcermactually": 3
  69. };
  70.  
  71. function optimizeUrl(url) {
  72. if (url.includes('upload.wikimedia.org') && !url.includes('/thumb/')) {
  73. const parts = url.split('/');
  74. const filename = parts[parts.length - 1];
  75. return url.replace('/commons/', '/commons/thumb/') + '/500px-' + filename;
  76. }
  77. return url;
  78. }
  79.  
  80. async function embedX(url, container) {
  81. const loadingNote = document.createElement('div');
  82. loadingNote.className = 'tweet-loading';
  83. loadingNote.innerText = "Loading X post...";
  84. container.appendChild(loadingNote);
  85. try {
  86. const apiUrl = `https://publish.twitter.com/oembed?url=${encodeURIComponent(url)}&theme=dark&dnt=true&omit_script=true`;
  87. const response = await fetch(apiUrl);
  88. const data = await response.json();
  89. loadingNote.remove();
  90. const tweetWrapper = document.createElement('div');
  91. tweetWrapper.innerHTML = data.html;
  92. container.appendChild(tweetWrapper);
  93. if (window.twttr && window.twttr.widgets) window.twttr.widgets.load(tweetWrapper);
  94. } catch (err) { loadingNote.innerText = "Failed to load X post."; }
  95. }
  96.  
  97. function processMessage(msgElem) {
  98. let rawText = msgElem.textContent.replace(/^\u00A0/, "");
  99. let workingHtml = msgElem.innerHTML;
  100.  
  101. let emoteTokens = [];
  102.  
  103. // Shield Native Emotes
  104. workingHtml = workingHtml.replace(/<div[^>]*class=["']chat_emote["'][^>]*><\/div>/g, (match) => {
  105. emoteTokens.push(match);
  106. return `__EMOTE_TOKEN_${emoteTokens.length - 1}__`;
  107. });
  108.  
  109. // Shield Custom Emotes (Scaled to 16px)
  110. workingHtml = workingHtml.replace(/:([a-z0-9_]+):/gi, (match, name) => {
  111. const lowerName = name.toLowerCase();
  112. if (customEmoteMap.hasOwnProperty(lowerName)) {
  113. const index = customEmoteMap[lowerName];
  114. const xPos = -(index * 16);
  115. const customDiv = `<div title=":${name}:" class="coolchat-custom-emote" style="background-position: ${xPos}px 0px;"></div>`;
  116. emoteTokens.push(customDiv);
  117. return `__EMOTE_TOKEN_${emoteTokens.length - 1}__`;
  118. }
  119. return match;
  120. });
  121.  
  122. const trimmed = rawText.trim();
  123. if (trimmed.startsWith(">")) msgElem.classList.add("msg-color-green");
  124. else if (trimmed.startsWith("<") && !trimmed.startsWith("<3") && !trimmed.includes(">")) msgElem.classList.add("msg-color-purple");
  125. else if (trimmed.startsWith("^")) msgElem.classList.add("msg-color-gold");
  126. else if (trimmed.startsWith("V ") || trimmed === "V" || trimmed.startsWith("v ") || trimmed === "v") msgElem.classList.add("msg-color-blue");
  127.  
  128. let parserDiv = document.createElement('div');
  129. parserDiv.innerHTML = workingHtml;
  130. let html = parserDiv.innerHTML;
  131.  
  132. html = html
  133. .replace(/\*\*(.*?)\*\*/g, '<span class="msg-md-bold">$1</span>')
  134. .replace(/\*(.*?)\*/g, '<span class="msg-md-italic">$1</span>')
  135. .replace(/~~(.*?)~~/g, '<span class="msg-md-strike">$1</span>')
  136. .replace(/`(.*?)`/g, '<code class="msg-md-code">$1</code>');
  137.  
  138. const urlRegex = /(https?:\/\/[^\s<]+)/gi;
  139. const urls = rawText.match(urlRegex) || [];
  140. html = html.replace(urlRegex, '<a href="$1" target="_blank" class="msg-link">$1</a>');
  141.  
  142. for (let i = 0; i < emoteTokens.length; i++) {
  143. html = html.replace(`__EMOTE_TOKEN_${i}__`, emoteTokens[i]);
  144. }
  145.  
  146. msgElem.innerHTML = html;
  147.  
  148. if (urls.length > 0) {
  149. const container = document.createElement('div');
  150. container.className = 'owot-media-container';
  151. msgElem.appendChild(container);
  152.  
  153. urls.forEach(url => {
  154. const lowUrl = url.toLowerCase();
  155. if (lowUrl.includes('twitter.com') || lowUrl.includes('x.com')) {
  156. if (url.includes('/status/')) return embedX(url, container);
  157. }
  158. const tenorMatch = url.match(/tenor\.com\/view\/.*-(\d+)/i);
  159. if (tenorMatch) {
  160. const f = document.createElement('iframe');
  161. f.src = `https://tenor.com/embed/${tenorMatch[1]}`;
  162. f.className = 'owot-embed-gif'; f.scrolling = "no";
  163. return container.appendChild(f);
  164. }
  165. const giphyMatch = url.match(/giphy\.com\/gifs\/.*-([a-zA-Z0-9]+)/i) || url.match(/giphy\.com\/gifs\/([a-zA-Z0-9]+)/i);
  166. if (giphyMatch) {
  167. const f = document.createElement('iframe');
  168. f.src = `https://giphy.com/embed/${giphyMatch[1]}`;
  169. f.className = 'owot-embed-gif'; f.scrolling = "no";
  170. return container.appendChild(f);
  171. }
  172. let ytMatch = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/shorts\/)([a-zA-Z0-9_-]{11})/i);
  173. if (ytMatch) {
  174. const f = document.createElement('iframe');
  175. f.src = `https://www.youtube.com/embed/${ytMatch[1]}`;
  176. f.className = 'owot-embed-iframe'; f.allowFullscreen = true;
  177. return container.appendChild(f);
  178. }
  179. if (lowUrl.includes('open.spotify.com')) {
  180. const sMatch = url.match(/spotify\.com\/(track|album|playlist)\/([a-zA-Z0-9]+)/i);
  181. if (sMatch) {
  182. const f = document.createElement('iframe');
  183. f.src = `https://open.spotify.com/embed/${sMatch[1]}/${sMatch[2]}`;
  184. f.className = 'owot-embed-spotify'; f.allow = "encrypted-media";
  185. return container.appendChild(f);
  186. }
  187. }
  188. const cleanUrl = url.split('?')[0].toLowerCase();
  189. if (cleanUrl.match(/\.(jpeg|jpg|gif|png|webp|avif|svg)$/)) {
  190. const img = document.createElement('img');
  191. img.src = optimizeUrl(url);
  192. img.className = 'owot-preview-img'; img.loading = "lazy";
  193. container.appendChild(img);
  194. } else if (cleanUrl.match(/\.(mp4|webm|ogg)$/)) {
  195. const vid = document.createElement('video');
  196. vid.src = url; vid.className = 'owot-embed-iframe'; vid.controls = true; vid.muted = true;
  197. container.appendChild(vid);
  198. } else if (cleanUrl.match(/\.(mp3|wav|ogg|m4a)$/)) {
  199. const aud = document.createElement('audio');
  200. aud.src = url; aud.controls = true;
  201. container.appendChild(aud);
  202. }
  203. });
  204. }
  205. }
  206.  
  207. const chatWatcher = new MutationObserver((mutations) => {
  208. mutations.forEach((mutation) => {
  209. mutation.addedNodes.forEach((node) => {
  210. if (node.nodeType !== 1) return;
  211. const msgContent = node.querySelector('.message__content') ||
  212. (node.classList && node.classList.contains('message__content') ? node : node.lastChild);
  213. if (msgContent && msgContent.nodeType === 1 && !msgContent.dataset.parsed) {
  214. setTimeout(() => {
  215. if (msgContent.dataset.parsed) return;
  216. msgContent.dataset.parsed = "true";
  217. processMessage(msgContent);
  218. }, 0);
  219. }
  220. });
  221. });
  222. });
  223.  
  224. const start = () => {
  225. const p = document.getElementById('page_chatfield') || (window.elm && elm.page_chatfield);
  226. const g = document.getElementById('global_chatfield') || (window.elm && elm.global_chatfield);
  227. if (p && g) {
  228. chatWatcher.observe(p, { childList: true, subtree: true });
  229. chatWatcher.observe(g, { childList: true, subtree: true });
  230. window.networkinterval = setInterval(() => {
  231. if(elm.network_chatfield || document.getElementById('network_chatfield')) {
  232. w.coolChatAdd("network_chatfield", "network_chatfield");
  233. clearInterval(window.networkinterval);
  234. }
  235. }, 500)
  236. } else { setTimeout(start, 500); }
  237. };
  238. window.w.coolChatAdd = (tabId, elmIndex) => {
  239. const t = document.getElementById(tabId) || (window.elm && elm[elmIndex]);
  240. chatWatcher.observe(t, { childList: true, subtree: true });
  241. }
  242. start();
  243. })();
Add Comment
Please, Sign In to add comment