// ==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.002
// @run-at document-start
// @grant GM_xmlhttpRequest
// ==/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);
var delayscripts=[]; // exportFunction doesn't make this next one work: hence more trickiness required...
delayscripts.push("if (!window.AbortController) { window.AbortController=function(){"+
" this.abort=function(){ this.signal.aborted++; }; this.signal={aborted:0}; } };");
// 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:
var firstscript=1;
window.addEventListener('beforescriptexecute', function(e) {
if (firstscript) { firstscript=0; addScript(delayscripts.join("\n")); }
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
}
});