maloomf9 / Picarto Chat Extender

// ==UserScript==
// @name        Picarto Chat Extender
// @namespace   http://www.furaffinity.net/user/Maloo
// @description Adds in additional interfaces for the Picarto chat script for userscripts to use. Add "https://picarto.tv/js/chat/chat_new_client.min.js$~xmlhttprequest" to your adblocker if you are not running Firefox/Greasemonkey.
// @license     MIT
// @include     https://picarto.tv/*
// @exclude		https://picarto.tv/communities*
// @exclude		https://picarto.tv/
// @exclude		https://picarto.tv/settings*
// @version     1.4
// @grant       none
// @run-at		document-start
// ==/UserScript==

var verString = "1.5 (2020-04-03)";


/*
Known Script Hashes, with dates observed and variables:
E71D7DF4: (9/17/17) e/G/c
CA9ABC7C: (9/25/17) e/q/l
E3ACCB3A: (10/11/17) e/q/l
F12DDC6B: (10/23/17) e/W/l
5C7AFE02: (10/28/17) e/q/l
7B66DC7C: (11/20/17) e/q/l
BF9BAEE9: (12/4/17) externalScope/socket/statusMsg (no longer minified? RegExes hopefully expanded to cover both minified and unminified)
7C151ACA: (1/4/18) e/G/l
06FFD550: (5/8/18) e/V/c
*/

var i;
var changed = 1; // How many scripts need to be edited with



if(navigator.userAgent.toLowerCase().indexOf('firefox') > -1){
     // On firefox, we can block the script. For now.
    changed = 1; // How many scripts need to be edited with
    window.addEventListener('beforescriptexecute', function(e) {

    ///for external script:
	var src = e.target.src;
	if (src.search(/chat_new_client\.min\.js/) != -1) {
                changed--;
		e.preventDefault();
		e.stopPropagation();
		console.log("Blocked Chat Script");
	}

	if(changed === 0) window.removeEventListener(e.type, arguments.callee, true);

}, true);


    ////////////////////////////////////////////////
} else console.log ("Not Firefox, make sure to block chat script w/Adblock! \"https://picarto.tv/js/chat/chat_new_client.min.js$~xmlhttprequest\"");


function exec(fn) {
    var script = document.createElement('script');
    script.setAttribute("type", "application/javascript");
    script.textContent = '(' + fn + ')();';
    modifyHeadSafe(function () {document.head.appendChild(script);}); // run the script
    modifyHeadSafe(function () {document.head.removeChild(script);}); // clean up
}

function passStrVarToWindow(toPass, varName) {
    var script = document.createElement('script');
    script.setAttribute("type", "application/javascript");
    script.textContent = varName + ' = "' + toPass + '";';
    modifyHeadSafe(function () {document.head.appendChild(script);}); // run the script
    modifyHeadSafe(function () {document.head.removeChild(script);}); // clean up
}

//Add permanent window scope script. Now with less giant strings without syntax highlighting!
function addWindowScript(fn, ident)
{
    var script = document.createElement('script');
    script.setAttribute("type", "application/javascript");
    script.setAttribute("id", ident);
    script.textContent = fn.toString().slice(fn.toString().indexOf("{") + 1,-2); //KLUUUUUUDGE.
    modifyHeadSafe(function () {document.head.appendChild(script)});
}

var headFunctions = [];
function modifyHeadSafe(toDo){
    if ((document.head) && (headFunctions.length == 0))
    {
        if (toDo != null) toDo();
    }
    else
    {
        //console.log("Pushing headFunction #" + headFunctions.length);
        headFunctions.push(toDo);
    }
}

var waitForHead = setInterval(function() {
    if (document.head)
    {
        while (headFunctions.length != 0)
        {
            //console.log("Executing headFunction #" + headFunctions.length);
            headFunctions.shift()();
        }
        clearInterval(waitForHead);
    }
},50);

//Create container for holding functions and messages to be executed after script load
addWindowScript(function() {
window.CustomFunctions = {
         callbacks:[],
		 addMsgBacklog:[],
         execute: function(){
			for (i = 0; i < this.callbacks.length; i++) {
              this.callbacks[i]();
          }
            var tsTemp = window.addMsg.toString();
            if (tsTemp.search(/CustomFunctions/) == -1)
            {
                for (i = 0; i < this.addMsgBacklog.length; i++) {
                    window.addMsg(this.addMsgBacklog[i]);
                }
            }
            else console.log("Recursion Protection!");
         }
       };
}, "PicChatExt_CF");

//Temporary Function to pass messages into backlog.
exec(function() {
window.addMsg = function(msg) {
	console.log("\"" + msg + "\" added to backlog.");
	CustomFunctions.addMsgBacklog.push(msg);
};
});

exec(function() {
  if (!window.initChat)
  {
    window.initChat = function() {
    	console.log("initChat Error Supressed");
  	};
  }
});

passStrVarToWindow(verString, "verString");

addWindowScript(function () {

String.prototype.hashCode = function(){
	var hash = 0;
	if (this.length === 0) return hash;
	for (i = 0; i < this.length; i++) {
		var char = this.charCodeAt(i);
		hash = ((hash<<5)-hash)+char;
		hash = (hash & hash) >>> 0; // Convert to 32bit UNSIGNED integer
	}
	return hash;
};

function hexRep(number, width) { return (number).toString(16).slice(-width).toUpperCase(); }

var jsContent;
//console.log("Borp1");
function reqListener () {

	var scriptHash = hexRep(this.responseText.hashCode(), 8);
	console.log("Recieved Chat JS. Hash: " + scriptHash );
	jsContent = this.responseText;
    var tempreg = /function\s*\(/;
    var temp = tempreg.exec(jsContent);
	var exScopeVar = jsContent.substring(temp.index + temp[0].length, jsContent.search(/\)\{/));
    //console.log(temp);
    window.DEBUGtemp = temp;
	var inSocktVar = jsContent.substring(jsContent.search(/\w+\s?=\s?new picarto\.Client/), jsContent.search(/\s?=\s?new picarto\.Client\([^)]+\)/));
	var tempLoc = jsContent.search(/\s?=\s?new picarto\.Client\([^)]+\)/);
	var crSocktStr = jsContent.substring(tempLoc, tempLoc + jsContent.match(/\s?=\s?new picarto\.Client\([^)]+\)/)[0].length);
	var statusMsgF = jsContent.substring(jsContent.search(/\w+\(\w+\)\s?\{\s*\w+\(['"]<div><span class=(\\'|'|\\"|")update.*(\\'|'|\\"|")>['"]\s*\+/), jsContent.search(/\(\w+\)\s?\{\s*\w+\(['"]<div><span class=(\\'|'|\\"|")update.*(\\'|'|\\"|")>['"]\s*\+/));
	if ((exScopeVar.length < 1) || (inSocktVar.length < 1) || (statusMsgF.length < 1)) {console.log("Variables not isolated successfully!"); console.log("exScopeVar: " + exScopeVar + " inSocktVar: " + inSocktVar + " statusMsgF: " + statusMsgF); return;}
	jsContent = jsContent.replace(/[;,]\s?\w+(\.|\['|\[")socket"?'?\]?\s?=\s?\w+[,;]/, ',' + exScopeVar + '.addMsg = ' + statusMsgF + ',');
	console.log("exScopeVar: " + exScopeVar + " inSocktVar: " + inSocktVar + " statusMsgF: " + statusMsgF);
	jsContent = jsContent.replace(/\w+\s?=\s?new picarto\.Client\([^)]+\)/, inSocktVar + crSocktStr);
	tempreg = new RegExp("[;,]\s*" + inSocktVar + "\.Connect\(\)");
	tempLoc = jsContent.search(tempreg);
	jsContent = jsContent.slice(0, tempLoc) + ';' + exScopeVar + '.socket = ' + inSocktVar + jsContent.slice(tempLoc);
    //console.log("Borp5");
	var waitFor = setInterval(function() {
    if (document.querySelector("#channel_chat > audio + script, .panel_popoutchat > audio + script") && window.addMsg)
    {
      //console.log("Dad Found!");
      //console.log(window.addMsg);
      clearInterval(waitFor);
      if (typeof window.socket !== 'undefined') {
        window.alert("===Picarto Chat Extender===\nChat Script was not blocked.\nThis userscript downloads and modifies\nthe chat script dynamically.\nIf you are not using Firefox,\nyou must use Adblock to block the script\nwith the following filter\n\nhttps://picarto.tv/js/chat/chat_new_client.min.js$~xmlhttprequest");
        throw new Error("Chat Script not blocked!");
      }


      var initContent;
      var iscript = document.createElement('script');
      var cscript = document.createElement('script');
      initContent = document.querySelector("#channel_chat > audio + script, .panel_popoutchat > audio + script");
      cscript.setAttribute("type", "application/javascript");
      cscript.setAttribute("id", "ChatClient");
      iscript.setAttribute("type", "application/javascript");
      iscript.setAttribute("id", "ChatInit");
      cscript.textContent = jsContent;
      iscript.textContent = initContent.innerHTML;
      initContent.parentNode.removeChild(initContent);

      document.head.appendChild(cscript);
      document.head.appendChild(iscript);

      window.initContent = initContent;

      console.log("Chat Extender Script Loaded.");


      setTimeout(function() {
				window.addMsg("Chat Extender Ver " + verString + " Loaded Successfully.\nChat Script Hash: " + scriptHash);
       window.CustomFunctions.execute();
      }, 1000);
    }}, 100);

  //console.log("TimeoutAdded");
}




var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("GET", "https://picarto.tv/js/chat/chat_new_client.min.js");
oReq.send();
//console.log("Borp2");

}, "PicChatExt_RepScript");