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);
}