// ==UserScript== // @name Picarto Polyfills for older browsers like FF52 // @namespace http://michrev.com/ // @description Restore some compatibility between Picarto and older browsers (like FF 52) -- StevenRoy // @include https://www.picarto.tv/* // @include https://picarto.tv/* // @include https://www.picarto.tv // @include https://picarto.tv // @version 2.001 // @run-at document-start // @grant GM_xmlhttpRequest // @downloadURL none // ==/UserScript== "use strict"; // _____________ __ __ // / ___________/ / \,-' / // / /__ ___ / /\,-/ / // \___ \ / __\ / / / / //______/ / / / / / / / //_______/ /_/ /_/ /_/ // This is what "spread syntax" in object literals looks like: // {...r, r:e, g:t, b:n} // { ...methods, r, g, b } // But because that feature doesn't exist in the latest FF for 32-bit Windows, it needs to be converted. // (It didn't exist until 2018 and every major browser stopped updating long before then. Screw them.) // These examples become: // Object.assign({},r,{r:e, g:t, b:n}) // Object.assign({},methods,{ r, g, b }) // There's a similar feature for arrays but -that- is already supported! Go figure. function grabscript(url){ console.log("Fetching:",url); var response=GM_xmlhttpRequest({ method: "GET", url: url, synchronous: true // Imagine trying to make this script without this feature. }); if (!response.responseText) { throw("Something stupid happened while trying to load:",url,response); } console.log('Script fetched, length:'+response.responseText.length); return response.responseText; } // This adds (actually re-adds) a script to the current page so it can run. function addScript(text) { var newScript = document.createElement('script'); newScript.type = "text/javascript"; newScript.textContent = text; var head = document.getElementsByTagName('head')[0]; // console.log("We're adding the thing, length:",text.length); head.appendChild(newScript); // console.log('We added the thing, length:',text.length); return newScript; } unsafeWindow.String.prototype.trimStart=unsafeWindow.String.prototype.trimLeft; unsafeWindow.String.prototype.trimEnd=unsafeWindow.String.prototype.trimRight; unsafeWindow.URL.canParse=exportFunction(function canParse(url) { var base = arguments.length < 2 || arguments[1] === undefined ? undefined : arguments[1]; try { return !!new URL(url, base); } catch (error) { return false; } },unsafeWindow); // So, here's the deal: // On other pages, I could just use beforescriptexecute to intercept problem scripts, // fix the syntax error with a single responseText.replace(), then re-add it. // In this case, the script I need to intercept isn't loaded like a script! // When something is loaded using the Worker object, beforescriptexecute does NOTHING! // So I tried to edit the Worker constructor, using ES6 subclasses and exportFunction // to create a version of the object that could load, edit and inject the fixed code. That was hard... // I came so dang close to that actually working, too, but started running into // Error: Permission denied to access property "addEventListener" // ...Seems pages can't access Worker objects loaded from blobs created in userscripts // because of some "same-origin security" bull. // I spent so many frustrating hours trying to find a way around that... // Finally I decided, I had to try a different approach, and I got creative: window.addEventListener('beforescriptexecute', function(e) { var src = e.target.src; console.log("Checking script:"+(src?src:"(unknown)")); if (src && src.search(/\/static\/js\/1\.[0-9a-f]+\.chunk\.js/) != -1) { // static/js/1.b2dade89.chunk.js console.log('Intercepted probable chat script'); // This is the script that loads the Worker var onl=e.target.onload; // There's a callback when the script loads. We gotta keep the callback when we replace the script! // console.log('onload:',onl); e.target.onload=e.target.onerror=null; // Prevent the onerror handler when we cancel this. e.preventDefault(); e.stopPropagation(); var scr=grabscript(src); // load the script so we can edit it... // Original code: _=new Worker("/chatworker.min.js?ver=".concat(m.l)), console.log('grabbed, adding'); scr=addScript("window.AAA=1;console.log('Running edited script');\n"+scr.replace(/new Worker/g,"editedWorker")+"\n\n"+ // And now that script has the function that does the loading, editing, and fixing of the other script... 'function editedWorker(n){console.log("Fetching worker: "+n);var d=new XMLHttpRequest(); d.open("GET","https://picarto.tv"+n,false); d.send(null);'+ 'console.log("Fetch status:"+d.status);'+ 'd=d.responseText.replace(/\\{\\.\\.\\.([^,}]+)(?:,([^}]+))?\\}/g,(m,p1,p2)=>{return "Object.assign({},"+p1+(p2? ",{"+p2+"}" :"")+")";})'+ '.replace(/importScripts\\("([a-z.]+)"\\)[,;]/g,(m,p1)=>{'+ 'return \'importScripts("https://picarto.tv/\'+p1+\'");\'; // absolute paths required here for some stupid reason\n'+ '});'+ 'return new Worker(URL.createObjectURL(new Blob(["console.log(\'Fixed worker running - SrM was here\');\\n"+d ])));'+ '}'); console.log('added'); if (window.AAA) { onl(); } else { scr.onload=scr.onerror=onl; } // New script, same callback } });