您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a button to import images into Jackbox drawing-based games!
// ==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); }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址