JackboxDrawer

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

当前为 2020-07-13 提交的版本,查看 最新版本

  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.0.3
  6. // @include *://jackbox.tv/*
  7. // ==/UserScript==
  8.  
  9. //Catch outgoing messages and replace drawing data.
  10. //Has to be done through eval to break through GreaseMonkey's sandboxing.
  11. //Luckily, every single time data is sent, a message is logged. We override console.log and wait until
  12. //the message "[Blobcast Client] send" is passed through, and then we can modify args[2], which contains
  13. //the JSON data of the message we're about to upload to jackbox's servers.
  14. window.eval(`
  15. oldConsoleLog = console.log;
  16. console.log = function(...args) {
  17. oldConsoleLog(args.join(' ')); //Send data to the existing console.log. We don't wanna lose out on any debug info.
  18. if (typeof(tempvar) == 'undefined' || tempvar === null) {
  19. //No custom code ready, most likely a vanilla subimssion. Ignore this one.
  20. return;
  21. }
  22. if (args[0] == '[Blobcast Client] send') {
  23. //Perform the switch-a-roo.
  24. //The specifics of the code that's ran depends on the game, and that's
  25. //all handled from the Java-app side of things. Check the source
  26. //code there for specifics.
  27. eval(tempvar);
  28. tempvar = null;
  29. }
  30. }
  31. `);
  32.  
  33. //Handle games and their differences.
  34. var games = {
  35. "drawful_1": {
  36. submitDrawing: function() {
  37. document.getElementById("drawful-submitdrawing").click();
  38. },
  39. isInDrawingMode: function() {
  40. return !document.getElementsByClassName("state-draw")[0].getAttribute("class").includes("pt-page-off");
  41. },
  42. getSketchpad: function() {
  43. return document.getElementsByClassName("sketchpad")[0];
  44. }
  45. },
  46. "drawful_2": {
  47. submitDrawing: function() {
  48. document.getElementById("drawful-submitdrawing").click();
  49. },
  50. isInDrawingMode: function() {
  51. return !document.getElementsByClassName("state-draw")[0].getAttribute("class").includes("pt-page-off");
  52. },
  53. getSketchpad: function() {
  54. return document.getElementsByClassName("sketchpad")[0];
  55. }
  56. },
  57. "bidiots": {
  58. submitDrawing: function() {
  59. document.getElementById("auction-submitdrawing").click();
  60. },
  61. isInDrawingMode: function() {
  62. return !document.getElementById("state-draw").getAttribute("class").includes("pt-page-off");
  63. },
  64. getSketchpad: function() {
  65. return document.getElementById("auction-sketchpad");
  66. }
  67. },
  68. "tee_ko": {
  69. submitDrawing: function() {
  70. document.getElementById("awshirt-submitdrawing").click();
  71. },
  72. isInDrawingMode: function() {
  73. return !document.getElementById("state-draw").getAttribute("class").includes("pt-page-off");
  74. },
  75. getSketchpad: function() {
  76. return document.getElementsByClassName("awshirt-sketchpad")[0];
  77. }
  78. },
  79. "push_the_button": {
  80. submitDrawing: function() {
  81. document.getElementById("submitdrawing").click();
  82. },
  83. isInDrawingMode: function() {
  84. return document.getElementsByClassName("Draw")[0] === null;
  85. },
  86. getSketchpad: function() {
  87. return document.getElementById("fullLayer");
  88. }
  89. },
  90. "trivia_murder_party_1": {
  91. submitDrawing: function() {
  92. document.getElementById("enter-single-drawing-submit").click();
  93. },
  94. isInDrawingMode: function() {
  95. return !document.getElementById("state-enter-single-drawing").getAttribute("class").includes("pt-page-off");
  96. },
  97. getSketchpad: function() {
  98. return document.getElementById("sketchpad");
  99. }
  100. }
  101. }
  102. //Keeps track of the game we're currently playing.
  103. var gameID = null;
  104.  
  105. function updateGame(id) {
  106. gameID = id;
  107. if (typeof(socket) !== 'undefined' && socket !== null) {
  108. //Update the drawing app on what game we're playing.
  109. socket.send("updategame:" + id);
  110. }
  111. }
  112.  
  113. //Is ran every time the document changes. Useful for finding which game we're currently playing.
  114. var callback = function(mutationsList, observer) {
  115. if (document.getElementById("page-drawful") !== null) {
  116. //Drawful 1 and 2 actually share the same ID, but have different graphcis modes.
  117. //Luckily, the drawing div has "drawful2-page" as a class in Drawful 2.
  118. if (document.getElementsByClassName("state-draw")[0].getAttribute("class").includes("drawful2-page")) {
  119. updateGame("drawful_2");
  120. } else {
  121. updateGame("drawful_1");
  122. }
  123. } else if (document.getElementById("page-auction") !== null) {
  124. updateGame("bidiots");
  125. } else if (document.getElementById("page-awshirt") !== null) {
  126. //Fun fact. Tee KO is actually internally called "awshirt" both on the website and in the game files.
  127. updateGame("tee_ko");
  128. } else if (document.getElementsByClassName("Push The Button")[0] != null) {
  129. //Yes, the class name has spaces.
  130. updateGame("push_the_button");
  131. } else if (document.getElementById("page-triviadeath") !== null) {
  132. updateGame("trivia_murder_party_1");
  133. }
  134. };
  135.  
  136. //Initiate the DOM observer to run "callback" every time it changes.
  137. var observer = new MutationObserver(callback);
  138. var targetNode = document.getElementById('content-region');
  139. var config = { attributes: false, childList: true, subtree: true };
  140. observer.observe(targetNode, config);
  141.  
  142. //Info related to communicating with the Java app.
  143. var socket = null;
  144. var open = false;
  145. var firsttry = false;
  146.  
  147. //We want to automatically attempt reconnects if the connection is dropped, use setInterval with some
  148. //checks to make sure we don't make multiple connections.
  149. setInterval(function() {
  150. if (open || socket !== null) {
  151. return;
  152. }
  153. socket = new WebSocket("ws://127.0.0.1:2460");
  154. socket.onopen = function(e) {
  155. alert("Connection established with JackboxDrawer program.");
  156. open = true;
  157. callback(null,null);
  158. };
  159. socket.onmessage = function(event) {
  160. //Check for the proper version.
  161. if (event.data.startsWith("version")) {
  162. var version = event.data.split(":")[1];
  163. if (version > 103) {
  164. alert("Please update the JackboxDrawer Greasemonkey script!");
  165. } else if (version < 103) {
  166. alert("Please update the JackboxDrawer Java program!\nThe download can be found here: https://github.com/ipodtouch0218/JackboxDrawer/releases");
  167. }
  168. return;
  169. }
  170. //Save incoming code from the websocket in "tempvar". Needs to be eval'd to get through Greasemonkey's sandboxing.
  171. //We could also use window.wrappedJSObject but this is what I thought of first. Either way, potental security breach right here.
  172. window.eval("var tempvar = `" + event.data + "`");
  173. //Check to make sure we can actually DRAW right now.
  174. //If not, even attempting to submit a drawing can easily crash our webpage.
  175. var currentGame = games[gameID];
  176. if (typeof (currentGame) === 'undefined' || currentGame === null) {
  177. alert("Game not supported!");
  178. return;
  179. }
  180. if (!currentGame.isInDrawingMode()) {
  181. alert("Cannot submit drawing: Not in drawing mode!");
  182. window.eval("var tempvar = null;");
  183. return;
  184. }
  185. //Find the current sketchpad. We need it for later...
  186. var sketchpad = currentGame.getSketchpad();
  187. if (sketchpad === null) {
  188. //Couldn't find it, we're probably can't draw right now. Somehow, the previous checks failed.
  189. return;
  190. }
  191. //Simulate drawing on the sketchpad with mouse events. We can't access the sketchpad's info directly
  192. //as it's kept track of internally, and the game never attempts to send any data if it's blank.
  193. var rect = sketchpad.getBoundingClientRect();
  194. var mouseEvent = document.createEvent('MouseEvents');
  195. mouseEvent.clientX = rect.x + rect.width / 2;
  196. mouseEvent.clientY = rect.y + rect.height / 2;
  197. mouseEvent.initEvent("mousedown", true, false);
  198. sketchpad.dispatchEvent(mouseEvent);
  199. mouseEvent.clientX += 2;
  200. mouseEvent.initEvent("mousemove", true, false);
  201. sketchpad.dispatchEvent(mouseEvent);
  202. mouseEvent.initEvent("mouseup", true, false);
  203. sketchpad.dispatchEvent(mouseEvent);
  204. //Submit drawing and get ready to switch-a-roo.
  205. currentGame.submitDrawing();
  206. };
  207. //Socket died, my dude.
  208. socket.onclose = function(event) {
  209. if (!open) return;
  210. alert("Connection lost with JackboxDrawer program. Retrying...");
  211. open = false;
  212. socket = null;
  213. };
  214. //Socket died PAINFULLY, my dude.
  215. socket.onerror = function(error) {
  216. if (!firsttry) {
  217. alert("Failed to connect to JackboxDrawer. \nI will attempt to reconnect in the background...");
  218. firsttry = true;
  219. }
  220. socket = null;
  221. }
  222. }, 1000);