JackboxDrawer-Lite

Adds a button to import images into Jackbox drawing-based games!

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         JackboxDrawer-Lite
// @description  Adds a button to import images into Jackbox drawing-based games!
// @namespace    ipodtouch0218/JackboxDrawer-Lite
// @version      1.0.0
// @include      *://jackbox.tv/*
// ==/UserScript==

//Catch outgoing messages through stringify and replace drawing data.
//Has to be done through eval to break through GreaseMonkey's sandboxing.
window.eval(`
tempvar = null
ignore = 0
oldStringify = JSON.stringify
JSON.stringify = function(arg) {
  if (ignore > 0) {
    ignore--
    return oldStringify(arg);
  }
  if (typeof(arg.params) == 'undefined' || arg.params == null) {
    return oldStringify(arg);
  }
  data = arg.params
  if (typeof(tempvar) == 'undefined' || tempvar === null) {
    //No custom code ready, most likely a vanilla subimssion. Ignore this one.
    return oldStringify(arg);
  }
  eval(tempvar);
  tempvar = null;
  return oldStringify(arg);
}
`)

//Game variables
var currentGameId = null;
var button = null;
var games = {
  "drawful_1": {
    submitDrawing: function(img) {
      window.eval("tempvar = \"var test = '" + img + "'; if (typeof (data.body.picture) !== 'undefined') { data.body.picture = test; } else { data.body.drawing = test; }\"");
      document.getElementById("drawful-submitdrawing").click();
    },
    isInDrawingMode: function() {
      return !document.getElementsByClassName("state-draw")[0].getAttribute("class").includes("pt-page-off");
    },
    getSketchpad: function() {
      return document.getElementsByClassName("sketchpad")[0];
    },
    isActiveGame: function() {
      return document.getElementById("page-drawful") != null;
    },
    addImportButton: function() {
      button = document.createElement("input");
      button.setAttribute("type", "file");
      button.setAttribute("class", "button-drawful button-large pure-button pure-input-1");
      button.setAttribute("id", "import-button");
      button.setAttribute("accept", "image/*");
      button.setAttribute("style", "margin: 0 auto;");
      button.addEventListener("change", uploadBitmapImage);
      
      attach = document.getElementsByClassName("state-draw")[0];
      attach.appendChild(button);
    }
  },
  /*
  "drawful_2": {
    submitDrawing: function() {
      document.getElementById("submitdrawing").click()
    },
    isInDrawingMode: function() {
      return document.getElementsByClassName("Draw")[0] != null
    },
    getSketchpad: function() {
      return document.getElementById("fullLayer")
    }
  },
  */
  "bidiots": {
    submitDrawing: function(img) {
      window.eval("tempvar=\"data.body.drawing='" + img + "'\"");
      document.getElementById("auction-submitdrawing").click();
    },
    isInDrawingMode: function() {
      return !document.getElementById("state-draw").getAttribute("class").includes("pt-page-off");
    },
    getSketchpad: function() {
      return document.getElementById("auction-sketchpad");
    },
    isActiveGame: function() {
      return document.getElementById("page-auction") !== null;
    },
    addImportButton: function() {
      button = document.createElement("input");
      button.setAttribute("type", "file");
      button.setAttribute("class", "container button-auction button-large pure-button");
      button.setAttribute("id", "import-button");
      button.setAttribute("accept", "image/*");
      button.setAttribute("style", "margin: 0 auto;");
      button.addEventListener("change", uploadBitmapImage);
      
      attach = document.getElementById("state-draw");
      attach.appendChild(button);
    }
  },
  "tee_ko": {
	scaling: 5,
    submitDrawing: function(img) {
      window.eval("img=" + JSON.stringify(img).replace("\\",""));
      window.eval("tempvar='data.body.pictureLines=img'");
      document.getElementById("awshirt-submitdrawing").click();
    },
    isInDrawingMode: function() {
      return !document.getElementById("state-draw").getAttribute("class").includes("pt-page-off");
    },
    getSketchpad: function() {
      return document.getElementsByClassName("awshirt-sketchpad")[0];
    },
    isActiveGame: function() {
      return document.getElementById("page-awshirt") !== null;
    },
    addImportButton: function() {
      button = document.createElement("input");
      button.setAttribute("type", "file");
      button.setAttribute("class", "awshirt-button btn btn-block");
      button.setAttribute("id", "import-button");
      button.setAttribute("accept", "image/*");
      button.addEventListener("change", uploadVectorImage);
      
      attach = document.getElementsByClassName("post-sketchpad")[0];
      attach.appendChild(button);
    }
  },
  "push_the_button": {
	scaling: 6,
    submitDrawing: function(img) {
      img.forEach(line => {
        points = "";
        line["points"].forEach(point => {
          points += point["x"] + "," + point["y"] + "|";
        });
        line["points"] = points.substring(0,points.length-1)
      });
      window.eval("img=" + JSON.stringify(img).replace("\\",""));
      window.eval("tempvar='data.body.lines=img'");
      document.getElementById("submitdrawing").click();
    },
    isInDrawingMode: function() {
      return document.getElementsByClassName("Draw")[0] != null;
    },
    getSketchpad: function() {
      return document.getElementById("fullLayer");
    },
    isActiveGame: function() {
      return document.getElementsByClassName("pushthebutton")[0] != null;
    },
    addImportButton: function() {
      if (document.getElementsByClassName("Draw")[0] == null)
        return;
      button = document.createElement("input");
      button.setAttribute("type", "file");
      button.setAttribute("class", "button");
      button.setAttribute("id", "import-button");
      button.setAttribute("accept", "image/*");
      button.setAttribute("style", "margin: 0 auto;");
      button.addEventListener("change", uploadVectorImage);
      
      attach = document.getElementById("post-sketchpad");
      attach.appendChild(button);
    }
  },
  "trivia_murder_party_1": {
    submitDrawing: function(img) {
      window.eval("tempvar=\"data.body.drawing='" + img + "'\"");
      document.getElementById("enter-single-drawing-submit").click();
    },
    isInDrawingMode: function() {
      return !document.getElementById("state-enter-single-drawing").getAttribute("class").includes("pt-page-off");
    },
    getSketchpad: function() {
      return document.getElementById("sketchpad");
    },
    isActiveGame: function() {
      return document.getElementById("page-triviadeath") !== null;
    },
    addImportButton: function() {
      button = document.createElement("input");
      button.setAttribute("type", "file");
      button.setAttribute("class", "light-text button-game button-large pure-button pure-input-1");
      button.setAttribute("id", "import-button");
      button.setAttribute("accept", "image/*");
      button.setAttribute("style", "margin: 0 auto; color: white;");
      button.addEventListener("change", uploadBitmapImage);
      
      attach = document.getElementById("state-enter-single-drawing");
      attach.appendChild(button);
    }
  },
  /*
  "patentlystupid": {
    submitDrawing: function() {
      document.getElementById("submitdrawing").click()
    },
    isInDrawingMode: function() {
      return document.getElementsByClassName("Draw")[0] != null
    },
    getSketchpad: function() {
      return document.getElementById("fullLayer")
    }
  },
  */
  "champd_up": {
	scaling: 8,
    submitDrawing: function(img) {
      if (document.getElementsByClassName("button choice-button btn btn-lg")[0].innerText == "SUBMIT") {
        img.forEach(line => {
          points = "";
          line["points"].forEach(point => {
            points += point["x"] + "," + point["y"] + "|";
          });
          line["points"] = points.substring(0,points.length-1)
        });
        window.eval("img=" + JSON.stringify(img).replace("\\",""));
        window.eval("tempvar='data.val.lines=img'");
		document.getElementsByClassName("button choice-button btn btn-lg")[0].click();
      }
    },
    submitName: function() {
      btn = document.getElementsByClassName("button choice-button btn btn-lg")[0];
      if (btn.getAttribute("data-action") == "name") {
        btn.click();
        document.getElementsByClassName("swal2-input")[0].value = "test";
        document.getElementsByClassName("swal2-confirm swal2-styled")[0].click();
      }
    },
    canSubmitNormally: function() {
      return document.getElementsByClassName("button choice-button btn btn-lg")[0].innerText == "SUBMIT";
    },
    isInDrawingMode: function() {
      return document.getElementsByClassName("Draw")[0] != null;
    },
    getSketchpad: function() {
      return document.getElementsByClassName("sketchpad fullLayer")[0];
    },
    isActiveGame: function() {
      return document.getElementsByClassName("worldchamps")[0] != null;
    },
    addImportButton: function() {
      if (document.getElementsByClassName("Draw")[0] == null)
        return;
      button = document.createElement("input");
      button.setAttribute("type", "file");
      button.setAttribute("class", "button btn btn-lg");
      button.setAttribute("id", "import-button");
      button.setAttribute("accept", "image/*");
      button.setAttribute("style", "margin: 0 auto;");
      button.addEventListener("change", uploadVectorImage);
      
      attach = document.getElementsByClassName("choices")[0];
      attach.appendChild(button);
    }
  }
}

function uploadBitmapImage() {
  debug("Bitmap upload");
  createImageBitmap(button.files[0]).then(function(img) {
    sketchpad = games[currentGameId]["getSketchpad"]();
    ctx = sketchpad.getContext("2d");
    ctx.drawImage(img, 0, 0, sketchpad.width, sketchpad.height);
    debug("Canvas populated");

    uri = sketchpad.toDataURL('image/png');
    uri = uri.replace(/^data:image.+;base64,/, '');
      
    submitImage(uri);
  });
}

function uploadVectorImage() {
  scale = games[currentGameId]["scaling"];
  debug("Vector upload");
  
  createImageBitmap(button.files[0]).then(function(img) {
    sketchpad = games[currentGameId]["getSketchpad"]();
    ctx = sketchpad.getContext("2d");
    ctx.drawImage(img, 0, 0, (sketchpad.width/scale), (sketchpad.height/scale));
    debug("Canvas populated");
    
    resizedImage = ctx.getImageData(0, 0, (sketchpad.width/scale), (sketchpad.height/scale));
    
    submitImage(vectorizeImage(resizedImage, (sketchpad.width/scale), (sketchpad.height/scale), scale));
  });
}

function colorDistanceSquared(color1, color2) {
  redAvg = (color1["red"] + color2["red"]) / 2;
  redDiff = color2["red"]-color1["red"];
  greenDiff = color2["green"]-color1["green"];
  blueDiff = color2["blue"]-color1["blue"];
  if (redAvg < 128) {
    return (2 * (redDiff * redDiff)) + 
      (4 * (greenDiff * greenDiff)) +
      (3 * (blueDiff * blueDiff)); 
  } else {
    return (3 * (redDiff * redDiff)) + 
      (4 * (greenDiff * greenDiff)) +
      (2 * (blueDiff * blueDiff)); 
  }
}

function mixColors(color1, color2, t) {
  if (color1 == null) return color2;
  if (color2 == null) return color1;
  t2 = 1-t;
  return createColor(Math.floor((t * color1["red"]) + (t2 * color2["red"])), 
    Math.floor((t * color1["green"]) + (t2 * color2["green"])),
    Math.floor((t * color1["blue"]) + (t2 * color2["blue"])),
    1);
}

function createColor(red, green, blue, alpha) {
  return {"red": red, "green": green, "blue": blue, "alpha": alpha};
}

function colorToHex(color) {
  r = Number(color["red"]).toString(16);
  g = Number(color["green"]).toString(16);
  b = Number(color["blue"]).toString(16);
  return "#" + 
    (r.length == 1 ? "0" : "") + r + 
    (g.length == 1 ? "0" : "") + g + 
    (b.length == 1 ? "0" : "") + b;
}

function createPoint(x, y) {
  return {"x": x, "y": y};
}

function createLine(thickness, points, color) {
  return {"thickness": thickness, "points": points, "color": colorToHex(color)};
}    

function vectorizeImage(img, w, h, thickness) {
  lines = [];
  data = img.data;
  for (x = 0; x < w; x++) {
    currentLine = null;
    currentColor = null;
    colorCount = 1;
    for (y = 0; y < h; y++) {
      point = createPoint(x * thickness, y * thickness);
      pixelColor = createColor(data[(x + (y * w))*4 + 0],
        data[(x + (y * w))*4 + 1],
        data[(x + (y * w))*4 + 2],
        data[(x + (y * w))*4 + 3]);
      
      if (pixelColor.alpha < 70) {
        if (currentLine != null) {
          currentLine["points"].push(point);
          currentLine = null;
        }
        continue;
      }
      
      //first line of a row
      if (currentLine == null) {
        currentLine = createLine(thickness+1, [point], pixelColor);
        currentColor = pixelColor;
        colorCount = 1;
        lines.push(currentLine);
        continue;
      }
      
      if (y+1 >= h) {
        currentLine["points"].push(createPoint(x*thickness, y*thickness));
        break;
      }
      
      colorDistance = colorDistanceSquared(currentColor, pixelColor);
      //debug(colorDistance);
      if (colorDistance > 6400) {
        //too different to be grouped
        currentLine["points"].push(point);
        
        colorCount = 1;
        currentLine = createLine(thickness+1, [point], pixelColor);
        currentColor = pixelColor;
        lines.push(currentLine);
      } else {
        //group points... but not for some games. ugh.
        currentColor = mixColors(currentColor, pixelColor, 1-(1/++colorCount));
        currentLine["color"] = colorToHex(currentColor);
        
        if (currentGameId == "champd_up" || currentGameId == "push_the_button") {
          currentLine["points"].push(point);
        }
      }
    }
  }
  return lines;
}

function submitImage(data) {
  submit = true;
  if (currentGameId == "patentlystupid") {
    window.eval("ignore = 1");
  } else if (currentGameId == "champd_up") {
    if (games[currentGameId]["canSubmitNormally"]()) {
      window.eval("ignore = 1");
    } else {
      alert("You must submit a name first!\nUse the text box and \"Submit\" button under the color picker first!");
      submit = false;
	  button.value = null;
    }
  }
  
  //Simulate drawing on the sketchpad with mouse events. We can't access the sketchpad's info directly
  //as it's kept track of internally, and the game never attempts to send any data if it's blank.
  var rect = sketchpad.getBoundingClientRect();
  var mouseEvent = document.createEvent('MouseEvents');
  
  mouseEvent.clientX = rect.x + rect.width / 2;
  mouseEvent.clientY = rect.y + rect.height / 2;
  mouseEvent.initEvent("mousedown", true, false);
  sketchpad.dispatchEvent(mouseEvent);
  mouseEvent.clientX += 2;
  mouseEvent.initEvent("mousemove", true, false);
  sketchpad.dispatchEvent(mouseEvent);
  mouseEvent.initEvent("mouseup", true, false);
  sketchpad.dispatchEvent(mouseEvent);
    
  //Submit drawing and get ready to switch-a-roo.
  if (submit) {
    games[currentGameId]["submitDrawing"](data);
    button.value = null;
  }
}

//Observer stuff
var callback = function(mutations, observer) {
  for (var game in games) {
    if (games[game]["isActiveGame"]()) {
      currentGameId = game;
      debug("Game set to " + game);
      if (document.getElementById("import-button") == null)
        games[game]["addImportButton"]();
      break;
    }
  }
}

setTimeout(function() {
  observer = new MutationObserver(callback);
  targetNode = document.getElementById("app");
  config = { attributes: false, childList: true, subtree: true };
  observer.observe(targetNode, config);
  
  debug("Started observer");
}, 500);

function debug(message) {
  console.log("[JackboxDrawer-Lite] " + message);
}