JackboxDrawer

Allows custom brush sizes, colors, and even importing images into Jackbox's drawing games!

安装此脚本?
作者推荐脚本

您可能也喜欢JackboxDrawer-Lite

安装此脚本
  1. // ==UserScript==
  2. // @name JackboxDrawer
  3. // @description Allows custom brush sizes, colors, and even importing images into Jackbox's drawing games!
  4. // @namespace ipodtouch0218/JackboxDrawer
  5. // @version 1.6.1
  6. // @include *://jackbox.tv/*
  7. // ==/UserScript==
  8.  
  9. //Catch outgoing messages through stringify and replace drawing data.
  10. //Has to be done through eval to break through GreaseMonkey's sandboxing.
  11. window.eval(`
  12. ignore = 0
  13. oldStringify = JSON.stringify
  14. JSON.stringify = function(arg) {
  15. if (ignore > 0) {
  16. console.log('ignoring ' + arg.params)
  17. ignore--
  18. return oldStringify(arg)
  19. }
  20. if (typeof(arg.params) == 'undefined' || arg.params == null) {
  21. return oldStringify(arg)
  22. }
  23. data = arg.params
  24. if (typeof(tempvar) == 'undefined' || tempvar === null) {
  25. //No custom code ready, most likely a vanilla subimssion. Ignore this one.
  26. return oldStringify(arg)
  27. }
  28. eval(tempvar)
  29. tempvar = null
  30. return oldStringify(arg)
  31. }
  32. `)
  33.  
  34. //Handle games and their differences.
  35. var games = {
  36. "drawful_1": {
  37. submitDrawing: function() {
  38. document.getElementById("drawful-submitdrawing").click()
  39. },
  40. isInDrawingMode: function() {
  41. return !document.getElementsByClassName("state-draw")[0].getAttribute("class").includes("pt-page-off")
  42. },
  43. getSketchpad: function() {
  44. return document.getElementsByClassName("sketchpad")[0]
  45. }
  46. },
  47. "drawful_2": {
  48. submitDrawing: function() {
  49. document.getElementById("submitdrawing").click()
  50. },
  51. isInDrawingMode: function() {
  52. return document.getElementsByClassName("Draw")[0] != null
  53. },
  54. getSketchpad: function() {
  55. return document.getElementById("fullLayer")
  56. }
  57. },
  58. "bidiots": {
  59. submitDrawing: function() {
  60. document.getElementById("auction-submitdrawing").click()
  61. },
  62. isInDrawingMode: function() {
  63. return !document.getElementById("state-draw").getAttribute("class").includes("pt-page-off")
  64. },
  65. getSketchpad: function() {
  66. return document.getElementById("auction-sketchpad")
  67. }
  68. },
  69. "tee_ko": {
  70. submitDrawing: function() {
  71. document.getElementById("awshirt-submitdrawing").click()
  72. },
  73. isInDrawingMode: function() {
  74. return !document.getElementById("state-draw").getAttribute("class").includes("pt-page-off")
  75. },
  76. getSketchpad: function() {
  77. return document.getElementsByClassName("awshirt-sketchpad")[0]
  78. }
  79. },
  80. "push_the_button": {
  81. submitDrawing: function() {
  82. document.getElementById("submitdrawing").click()
  83. },
  84. isInDrawingMode: function() {
  85. return document.getElementsByClassName("Draw")[0] != null
  86. },
  87. getSketchpad: function() {
  88. return document.getElementById("fullLayer")
  89. }
  90. },
  91. "trivia_murder_party_1": {
  92. submitDrawing: function() {
  93. document.getElementById("enter-single-drawing-submit").click()
  94. },
  95. isInDrawingMode: function() {
  96. return !document.getElementById("state-enter-single-drawing").getAttribute("class").includes("pt-page-off")
  97. },
  98. getSketchpad: function() {
  99. return document.getElementById("sketchpad")
  100. }
  101. },
  102. "patentlystupid": {
  103. submitDrawing: function() {
  104. document.getElementById("submitdrawing").click()
  105. },
  106. isInDrawingMode: function() {
  107. return document.getElementsByClassName("Draw")[0] != null
  108. },
  109. getSketchpad: function() {
  110. return document.getElementById("fullLayer")
  111. }
  112. },
  113. "champd_up": {
  114. submitDrawing: function() {
  115. if (document.getElementsByClassName("button choice-button btn btn-lg")[0].innerText == "SUBMIT") {
  116. document.getElementsByClassName("button choice-button btn btn-lg")[0].click()
  117. }
  118. },
  119. submitName: function() {
  120. btn = document.getElementsByClassName("button choice-button btn btn-lg")[0]
  121. if (btn.getAttribute("data-action") == "name") {
  122. btn.click()
  123. document.getElementsByClassName("swal2-input")[0].value = "test"
  124. document.getElementsByClassName("swal2-confirm swal2-styled")[0].click()
  125. }
  126. },
  127. canSubmitNormally: function() {
  128. return document.getElementsByClassName("button choice-button btn btn-lg")[0].innerText == "SUBMIT"
  129. },
  130. isInDrawingMode: function() {
  131. return document.getElementsByClassName("Draw")[0] != null
  132. },
  133. getSketchpad: function() {
  134. return document.getElementsByClassName("sketchpad fullLayer")[0]
  135. }
  136. }
  137. }
  138. //Keeps track of the game we're currently playing.
  139. gameID = null
  140.  
  141. function updateGame(id) {
  142. gameID = id
  143. if (typeof(socket) !== 'undefined' && socket !== null) {
  144. //Update the drawing app on what game we're playing.
  145. socket.send("updategame:" + id)
  146. }
  147. }
  148.  
  149. //Is ran every time the document changes. Useful for finding which game we're currently playing.
  150. var callback = function(mutationsList, observer) {
  151. if (document.getElementById("page-drawful") != null) {
  152. updateGame("drawful_1")
  153. } else if (document.getElementsByClassName("drawful2international")[0] != null) {
  154. updateGame("drawful_2")
  155. } else if (document.getElementById("page-auction") !== null) {
  156. updateGame("bidiots")
  157. } else if (document.getElementById("page-awshirt") !== null) {
  158. //Fun fact. Tee KO is actually internally called "awshirt" both on the website and in the game files.
  159. updateGame("tee_ko")
  160. } else if (document.getElementsByClassName("pushthebutton")[0] != null) {
  161. //Yes, the class name has spaces.
  162. updateGame("push_the_button")
  163. } else if (document.getElementById("page-triviadeath") !== null) {
  164. updateGame("trivia_murder_party_1")
  165. } else if (document.getElementsByClassName("worldchamps")[0] != null) {
  166. updateGame("champd_up")
  167. } else if (document.getElementsByClassName("patentlystupid")[0] != null) {
  168. updateGame("patentlystupid")
  169. }
  170. }
  171.  
  172. //chrome doesn't like the observer being setup now, lets just add it later???
  173. setTimeout(function() {
  174. //Initiate the DOM observer to run "callback" every time it changes.
  175. var observer = new MutationObserver(callback)
  176. var targetNode = document.getElementById('app')
  177. var config = { attributes: true, childList: true, subtree: true }
  178. observer.observe(targetNode, config)
  179. }, 1000)
  180.  
  181. //Info related to communicating with the Java app.
  182. var socket = null
  183. var open = false
  184. var firsttry = false
  185.  
  186. //We want to automatically attempt reconnects if the connection is dropped, use setInterval with some
  187. //checks to make sure we don't make multiple connections.
  188. setInterval(function() {
  189. if (open || socket !== null) {
  190. return
  191. }
  192. socket = new WebSocket("ws://127.0.0.1:2460")
  193. socket.onopen = function(e) {
  194. alert("Connection established with JackboxDrawer program.")
  195. open = true
  196. callback(null,null)
  197. }
  198. socket.onmessage = function(event) {
  199. //Check for the proper version.
  200. if (event.data.startsWith("version")) {
  201. var version = event.data.split(":")[1]
  202. if (version > 161) {
  203. alert("Please update the JackboxDrawer Greasemonkey script!\nThe download can be found here: https://greasyfork.org/en/scripts/406893-jackboxdrawer")
  204. } else if (version < 161) {
  205. alert("Please update the JackboxDrawer Java program!\nThe download can be found here: https://github.com/ipodtouch0218/JackboxDrawer/releases")
  206. }
  207. return
  208. }
  209. //Save incoming code from the websocket in "tempvar". Needs to be eval'd to get through Greasemonkey's sandboxing.
  210. //We could also use window.wrappedJSObject but this is what I thought of first. Either way, potental security breach right here.
  211. window.eval("var tempvar = `" + event.data.replace("SUBMITNAME;","") + "`")
  212. //Check to make sure we can actually DRAW right now.
  213. //If not, even attempting to submit a drawing can easily crash our webpage.
  214. var currentGame = games[gameID]
  215. if (typeof (currentGame) === 'undefined' || currentGame === null) {
  216. alert("Game not supported!")
  217. return
  218. }
  219. if (!currentGame.isInDrawingMode()) {
  220. alert("Cannot submit: Not in drawing mode!")
  221. window.eval("var tempvar = null")
  222. return
  223. }
  224. if (gameID == "champd_up") {
  225. if (event.data.startsWith("SUBMITNAME;")) {
  226. currentGame.submitName()
  227. return
  228. }
  229. }
  230. //Find the current sketchpad. We need it for later...
  231. var sketchpad = currentGame.getSketchpad()
  232. if (sketchpad === null) {
  233. //Couldn't find it, we're probably can't draw right now. Somehow, the previous checks failed.
  234. return
  235. }
  236. submit = true
  237. if (gameID == "patentlystupid") {
  238. window.eval("ignore = 1")
  239. } else if (gameID == "champd_up") {
  240. if (currentGame.canSubmitNormally()) {
  241. window.eval("ignore = 1")
  242. } else {
  243. alert("You must submit a name first!\nUse the text box and \"Submit\" button under the color picker first!");
  244. submit = false
  245. }
  246. }
  247. //Simulate drawing on the sketchpad with mouse events. We can't access the sketchpad's info directly
  248. //as it's kept track of internally, and the game never attempts to send any data if it's blank.
  249. var rect = sketchpad.getBoundingClientRect()
  250. var mouseEvent = document.createEvent('MouseEvents')
  251. mouseEvent.clientX = rect.x + rect.width / 2
  252. mouseEvent.clientY = rect.y + rect.height / 2
  253. mouseEvent.initEvent("mousedown", true, false)
  254. sketchpad.dispatchEvent(mouseEvent)
  255. mouseEvent.clientX += 2
  256. mouseEvent.initEvent("mousemove", true, false)
  257. sketchpad.dispatchEvent(mouseEvent)
  258. mouseEvent.initEvent("mouseup", true, false)
  259. sketchpad.dispatchEvent(mouseEvent)
  260. //Submit drawing and get ready to switch-a-roo.
  261. if (submit)
  262. currentGame.submitDrawing()
  263. }
  264. //Socket died, my dude.
  265. socket.onclose = function(event) {
  266. if (!open) return
  267. alert("Connection lost with JackboxDrawer program. Retrying...")
  268. open = false
  269. socket = null
  270. }
  271. //Socket died PAINFULLY, my dude.
  272. socket.onerror = function(error) {
  273. if (!firsttry) {
  274. alert("Failed to connect to JackboxDrawer. \nI will attempt to reconnect in the background...")
  275. firsttry = true
  276. }
  277. socket = null
  278. }
  279. }, 1000)