_profile

OnlineSequencer chat embed fixer

Jul 19th, 2025 (edited)
171
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name        OS chatEmbedFixer 2
  3. // @namespace   eepic
  4. // @description (Violentmonkey) Fixes embeds on OnlineSequencer chat. Hides duplicate embeds / discord messages. Supports Tenor + Vocaroo embeds.
  5. // @icon        https://onlinesequencer.net/favicon.ico
  6. // @version     2.0.1
  7. // @match       *://onlinesequencer.net/*
  8. // @grant       GM_xmlhttpRequest
  9. // @run-at      document-end
  10. // ==/UserScript==
  11.  
  12. // Log
  13. // 2025/07/18 - 1.0.0 - Script is finished
  14. // 2025/07/19 - 1.1.0 - Duplicate discord messages/embeds will now hide
  15. // ...        - 1.1.1 - Supports startpage proxy images
  16. // 2026/01/20 - 1.2.0 - Since my change was finally merged, there is no need for the minifed ajaxChat function now :D
  17. // 2026/01/23 - 1.2.1 - document-start -> document-end -_- .. also, hide overflow-x (because of pooo)
  18. // 2026/01/26 - 2.0.0 - MAJOR REFACTOR - properly fetches URLs this time, and also works in the comments section.
  19. // 2026/01/27 - 2.0.1 - i am only adding 0.0.1 because its midnight and i bugfixed. it was a pain in the
  20.  
  21. let mode;
  22. let observeElement; //asdokfjnpalhp,uawehjlcfmsndjkbonksmalfghdsxzdfghvjkgfdertfgvxcdzswretghgterwe3
  23. let loadedTime = Date.now();
  24. const allowed_types = new Set([
  25.   'image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/webp',
  26.   'audio/wav', 'audio/x-wav', 'audio/flac', 'audio/mpeg', 'audio/ogg', 'audio/webm', 'audio/mp4', 'audio/x-m4a',
  27.   'video/mp4', 'video/x-matroska', 'video/webm', 'video/ogg'
  28. ]);
  29.  
  30. // properly fetch this time
  31. function getEmbedType(url, callback) {
  32.   GM_xmlhttpRequest({
  33.     method: 'HEAD',
  34.     url: url,
  35.     timeout: 677,
  36.     onload: function(res) {
  37.       if (res.status >= 200 && res.status < 300) { // should be ok
  38.         let headers = res.responseHeaders;
  39.         let matchAll = headers.matchAll(/^content-type: ([^\n\r;]+)/gmi);
  40.         let allowed = true;
  41.         let firstType;
  42.         for (const i of matchAll) {
  43.           let thisType = i[1]?.toLowerCase().trim();
  44.           if (!firstType) firstType = thisType;
  45.           if (!allowed_types.has(thisType)) {
  46.             allowed = false;
  47.             break;
  48.           }
  49.         }
  50.         callback(allowed ? firstType : undefined);
  51.       }
  52.     },
  53.     onerror: function(e) {
  54.       console.warn('Failed to fetch url:', e);
  55.       callback();
  56.     },
  57.     ontimeout: () => callback()
  58.   });
  59. }
  60.  
  61. function embedElement(parent, type, callback, doTheThingAtTheEndBecauseItHasSrcOkayPlease) {
  62.   const wrapper = document.createElement('div');
  63.   const element = document.createElement(type);
  64.  
  65.   wrapper.style.left = '0';
  66.   wrapper.style.width = '100%';
  67.   wrapper.style.height = '100%';
  68.   wrapper.style.position = 'relative';
  69.  
  70.  
  71.   // apparently you have to do this *before* setting src. lol!
  72.   // lululu luflan pa ☆~~ lalalala
  73.   if (doTheThingAtTheEndBecauseItHasSrcOkayPlease && mode === 'chat') {
  74.     element.onload = () => {
  75.       queueMicrotask(() => { // don't ask why this works, it just works. probably
  76.         observeElement.scrollTop = observeElement.scrollHeight;
  77.       });
  78.     }
  79.   }
  80.  
  81.   callback(wrapper, element);
  82.   wrapper.appendChild(element);
  83.   parent.appendChild(wrapper);
  84. }
  85.  
  86. function replaceMessageWithSmall(message, text) {
  87.     message.innerHTML = '';
  88.  
  89.     const wrapper = document.createElement('div');
  90.     wrapper.textContent = text;
  91.     wrapper.style.color = '#9e9e9e';
  92.     wrapper.style.fontSize = '12px';
  93.  
  94.     const outer = document.createElement('div');
  95.     outer.style.textAlign = 'left';
  96.     outer.appendChild(wrapper);
  97.     message.appendChild(outer);
  98. }
  99.  
  100. function checkForEmbeds(chat) {
  101.   if (chat.classList.contains('embed-checked')) return;
  102.   chat.classList.add('embed-checked');
  103.   let message = chat.querySelector('.message');
  104.  
  105.   // if already has an image, fix, return
  106.   const img = message.querySelector('.mycode_img')
  107.   if (img) {
  108.     // ensure img is not video
  109.     getEmbedType(img.src, embedType => {
  110.       if (embedType?.includes('video')) {
  111.         embedElement(message, 'video', (wrapper, video) => {
  112.           video.src = img.src;
  113.           img.remove();
  114.           video.controls = true;
  115.           video.style.maxWidth = '100%';
  116.           video.style.maxHeight = '300px';
  117.           video.style.objectFit = 'contain';
  118.         })
  119.       } else {
  120.         img.style.maxWidth = '100%';
  121.         img.style.maxHeight = '300px';
  122.         img.style.objectFit = 'contain';
  123.       }
  124.     });
  125.     return;
  126.   }
  127.  
  128.   // check each url, only embed one
  129.   let allcodeUrl = Array.from(message.querySelectorAll('a'));
  130.  
  131.   function checkThis(i) {
  132.     let thisUrl = allcodeUrl[i]?.href;
  133.     if (!thisUrl) return;
  134.  
  135.     // ignore sequence urls
  136.     if (/https?:\/\/(?:t?e?s?t?\.?onlinesequener.net|seq\.onl)\/\d+\/?$/.exec(thisUrl)) { // i dont think seq.onl is even working at the time of writing this lmfao
  137.       return;
  138.     }
  139.  
  140.     function onCheckFinished(embedType, url, ...args) {
  141.       let shouldScroll = Math.abs(observeElement.scrollTop + observeElement.clientHeight - observeElement.scrollHeight) <= 3;
  142.       if (!shouldScroll && Date.now() - loadedTime < 5000) shouldScroll = true; // hacky but it works(?) and im happy
  143.  
  144.       // check dupe
  145.       if (mode == 'chat') {
  146.         let lastChat = chat.previousElementSibling;
  147.         if (lastChat) {
  148.           chat.__CEF_embedded = url;
  149.           const lastEmbedded = lastChat.__CEF_embedded == url;
  150.           const lastDupe = lastChat.__CEF_isDupeOf?.__CEF_embedded == url;
  151.  
  152.           const thisChatSender = chat.querySelector('.name a')?.textContent || '!self';
  153.           const lastChatSender = lastChat.querySelector('.name a')?.textContent || '!self';
  154.  
  155.           // all this just to detect if user whispered to themself ... FML
  156.           _goAwayNoob = (() => { // i hate nesting if statements
  157.             if (thisChatSender !== '!self' || lastChatSender == '!self') return;
  158.             const lastMessageChildren = lastChat.querySelector('.message')?.children;
  159.             if (!lastMessageChildren) return;
  160.             const lastMessageFirstChild = lastMessageChildren[0];
  161.             if (lastMessageFirstChild?.classList.contains('mycode_i') && lastMessageFirstChild?.textContent == '(whisper)') { // can be spoofed but whatever idc
  162.               let whisperSentTo = message.children[0]?.textContent
  163.               if (!whisperSentTo) return;
  164.               whisperSentTo =  /-> (.*(?!$))/g.exec(whisperSentTo);
  165.               if (whisperSentTo[1] == lastChatSender) {
  166.                 chat.__CEF_isDupeOf = lastChat;
  167.                 replaceMessageWithSmall(message, '[Duplicate embed hidden. How dare you whisper to yourself!]');
  168.                 return true;
  169.               }
  170.             }
  171.           })()
  172.           if (_goAwayNoob) return;
  173.           if ((lastEmbedded || lastDupe) && thisChatSender == lastChatSender) {
  174.             chat.__CEF_isDupeOf = lastEmbedded ? lastChat : lastChat.__CEF_isDupeOf;
  175.             replaceMessageWithSmall(message, '[Duplicate embed hidden]');
  176.             return;
  177.           }
  178.         }
  179.       }
  180.       switch(embedType) {
  181.         case 'vocaroo':
  182.           embedElement(message, 'iframe', (wrapper, iframe) => {
  183.             wrapper.style.height = '60px';
  184.             iframe.src = url;
  185.             iframe.style.position = 'absolute';
  186.             iframe.style.top = '0';
  187.             iframe.style.left = '0';
  188.             iframe.style.width = '90%';
  189.             iframe.style.height = '100%';
  190.             iframe.style.border = '0';
  191.             iframe.allowFullscreen = true;
  192.           }, shouldScroll)
  193.           break;
  194.         case 'tenor':
  195.           embedElement(message, 'img', (wrapper, img) => {
  196.             img.src = url;
  197.             img.alt = 'Embedded Tenor GIF';
  198.             img.style.maxWidth = '100%';
  199.             img.style.maxHeight = '300px';
  200.             img.style.objectFit = 'contain';
  201.             wrapper.style.textAlign = 'left';
  202.           }, shouldScroll);
  203.           break;
  204.         case 'default':
  205.           let embedType2 = args[0];
  206.           let elementType = embedType2.includes('video') ? 'video' : embedType2.includes('audio') ? 'audio' : 'img';
  207.           embedElement(message, elementType, (wrapper, element) => {
  208.             element.style.maxWidth = '100%';
  209.             element.style.maxHeight = '300px';
  210.             element.style.objectFit = 'contain';
  211.             element.src = url;
  212.             if (elementType == 'video' || elementType == 'audio') {
  213.               element.controls = true;
  214.             }
  215.           }, shouldScroll)
  216.           break;
  217.       }
  218.  
  219.       // don't ask if *this* one works because i have no clue im so done with this lol
  220.       if (shouldScroll) {
  221.         setTimeout(() => {observeElement.scrollTop = observeElement.scrollHeight + 10000}, 0);
  222.       }
  223.     }
  224.  
  225.     // trusted urls
  226.     if (thisUrl.includes('vocaroo.com/') || thisUrl.includes('voca.ro/')) {
  227.       const vocarooIdMatch = thisUrl.match(/(?:vocaroo\.com|voca\.ro)\/([a-zA-Z0-9]+)/);
  228.       if (vocarooIdMatch && vocarooIdMatch[1]) {
  229.         const vocarooId = vocarooIdMatch[1];
  230.         const embedUrl = `https://vocaroo.com/embed/${vocarooId}`;
  231.         onCheckFinished('vocaroo', embedUrl);
  232.       }
  233.     } else if (thisUrl.includes('tenor.com/view')) {
  234.       // zzzzzzz
  235.       GM_xmlhttpRequest({
  236.         method: 'GET',
  237.         url: thisUrl,
  238.         onload: function (response) {
  239.           const imgMatch = response.responseText.match(/<meta[^>]+property="og:image"[^>]+content="([^"]+)"/i);
  240.           if (imgMatch && imgMatch[1]) {
  241.             const imgUrl = imgMatch[1];
  242.             onCheckFinished('tenor', imgUrl);
  243.           }
  244.         },
  245.         onerror: function (err) {
  246.           console.warn('Failed to fetch Tenor embed:', err);
  247.         }
  248.       });
  249.       return;
  250.     } else {
  251.         let thisType = getEmbedType(thisUrl, thisType => {
  252.         if (thisType) {
  253.           onCheckFinished('default',thisUrl,thisType);
  254.         } else {
  255.           checkThis(i+1);
  256.         }
  257.       })
  258.     }
  259.   }
  260.   checkThis(0);
  261. }
  262.  
  263. function startObserve() {
  264.   let comments = document.querySelector('#comments');
  265.  
  266.   if (window.location.href.includes('/forum/chat_frame.php')) {
  267.     mode = 'chat';
  268.   } else if (comments) {
  269.     if (!document.querySelector('#sequencer_main_row')) {
  270.         console.log('sequencer not found ono');
  271.         return;
  272.     }
  273.     mode = 'comments';
  274.   }
  275.   if (!mode) return;
  276.   console.log(`chatEmbedFixer running mode ${mode}`);
  277.  
  278.   observeElement = mode === 'chat' ? document.querySelector('#chat_table') : comments;
  279.   if (!observeElement) {
  280.     setTimeout(startObserve, 250);
  281.     return;
  282.   }
  283.   if (mode === 'chat') observeElement.style.overflowX = 'hidden';
  284.   const observer = new MutationObserver((mutationsList) => {
  285.     for (const mutation of mutationsList) {
  286.       for (const node of mutation.addedNodes) {
  287.         if (node.className === 'chat') {
  288.           checkForEmbeds(node);
  289.         }
  290.       }
  291.     }
  292.   });
  293.   observer.observe(observeElement, {childList: true});
  294.   observeElement.querySelectorAll('.chat').forEach(checkForEmbeds);
  295. }
  296. startObserve();
Advertisement
Add Comment
Please, Sign In to add comment