G

Untitled

public
Guest Dec 02, 2023 Never 59
Clone
Plaintext paste1.txt 593 lines (528 loc) | 17.17 KB
1
// ==UserScript==
2
// @name Garticphone DRAW bot
3
// @namespace http://tampermonkey.net/
4
// @version 0.1
5
// @license GNU
6
// @description Auto drawing bot!
7
// @author petmshall (peter-marshall5)
8
9
// @match *://garticphone.com/*
10
// @connect garticphone.com
11
// @exclude *://garticphone.com/_next/*
12
13
// @icon https://www.google.com/s2/favicons?domain=garticphone.com
14
15
// @grant unsafeWindow
16
// @grant GM_xmlhttpRequest
17
// @grant GM_log
18
19
// @run-at document-start
20
// ==/UserScript==
21
22
23
24
25
26
function requestText (url) {
27
return fetch(url).then((d) => {return d.text()})
28
}
29
30
function requestBuffer (url) {
31
return fetch(url).then((d) => {return d.arrayBuffer()})
32
}
33
34
// Generate decimal to hexadecimal conversion table
35
let hexTable = []
36
for (let i = 0; i < 256; i++) {
37
let hex = i.toString(16)
38
if (hex.length < 2) {
39
hex = '0' + hex
40
}
41
hexTable.push(hex)
42
}
43
44
function rgbToHex (r, g, b) {
45
return `#${hexTable[r]}${hexTable[g]}${hexTable[b]}`
46
}
47
48
// Check if in a gamemode with animation
49
// Ex. Animation, Background, Solo
50
function isAnimation () {
51
return Boolean(document.getElementsByClassName('note').length)
52
}
53
54
// Proxy to modify client script
55
Node.prototype.appendChild = new Proxy( Node.prototype.appendChild, {
56
async apply (target, thisArg, [element]) {
57
if (element.tagName == "SCRIPT") {
58
if (element.src.indexOf('draw') != -1) {
59
let text = await requestText(element.src)
60
text = editScript(text)
61
let blob = new Blob([text])
62
element.src = URL.createObjectURL(blob)
63
}
64
}
65
return Reflect.apply( ...arguments )
66
}
67
})
68
69
/* stroke configuration note */
70
/* [toolID, strokeID, [color, 18, 0.6], [x0, y0]. [x1, y1], ..., [xn, yn]] */
71
72
function editScript (text) {
73
// Find the final draw function
74
let functionFinalDraw = text.match(/function\s\w{1,}\(\w{0,}\){[^\{]+{[^\}]{0,}return\[\]\.concat\(Object\(\w{0,}\.*\w{0,}\)\(\w{0,}\),\[\w{0,}\]\)[^\}]{0,}}[^\}]{0,}}/g)[0]
75
// find the variable that setData is part of
76
let setDataVar = functionFinalDraw.match(/\w{1,}(?=\.setData)/g)[0]
77
// Expose setData to the script
78
text = text.replace(/\(\(function\(\){if\(!\w{1,}\.disabled\)/, `((function(){;window.setData = ${setDataVar}.setData;if(!${setDataVar}.disabled)`)
79
return text
80
}
81
82
// Stores the current turn in the game
83
let turnNum = null
84
// Stores the websocket that is currently in use
85
let currWs = null
86
87
// Custom websocket class to capture current websocket
88
class customWebSocket extends WebSocket {
89
constructor(...args) {
90
let ws = super(...args)
91
currWs = ws
92
// console.log(ws)
93
ws.addEventListener('message', (e) => {
94
// console.log(e.data)
95
if (e.data && typeof e.data == 'string' && e.data.includes('[')) {
96
let t = JSON.parse(e.data.replace(/[^\[]{0,}/, ''))[2]
97
if (t?.hasOwnProperty('turnNum')) turnNum = t.turnNum
98
}
99
})
100
return ws
101
}
102
}
103
unsafeWindow.WebSocket = customWebSocket
104
105
let drawEnabled = true
106
107
CanvasRenderingContext2D.prototype.stroke = new Proxy( CanvasRenderingContext2D.prototype.stroke, {
108
async apply (target, thisArg, [element]) {
109
if (drawEnabled) return Reflect.apply( ...arguments )
110
return
111
}
112
})
113
114
CanvasRenderingContext2D.prototype.fill = new Proxy( CanvasRenderingContext2D.prototype.fill, {
115
async apply (target, thisArg, [element]) {
116
if (drawEnabled) return Reflect.apply( ...arguments )
117
return
118
}
119
})
120
121
CanvasRenderingContext2D.prototype.clearRect = new Proxy( CanvasRenderingContext2D.prototype.clearRect, {
122
async apply (target, thisArg, [element]) {
123
if (drawEnabled) return Reflect.apply( ...arguments )
124
return
125
}
126
})
127
128
// Converts an image element to the format that Gartic Phone uses
129
function draw (image, fit='zoom', width=758, height=424, penSize=2) {
130
console.log('[Autodraw] Drawing image')
131
132
let canvas = document.createElement('canvas')
133
canvas.width = width
134
canvas.height = height
135
let ctx = canvas.getContext('2d')
136
ctx.imageSmoothingQuality = 'high'
137
138
// White background
139
ctx.fillStyle = 'white'
140
ctx.fillRect(0, 0, width, height)
141
142
// Calculate the image position and dimensions
143
let imageX = 0
144
let imageY = 0
145
let imageWidth = width
146
let imageHeight = height
147
// Stretch to fit by default (do nothing)
148
if (fit != 'stretch') {
149
const imageAspectRatio = image.width / image.height
150
const canvasAspectRatio = canvas.width / canvas.height
151
if (fit == 'zoom') {
152
// Zoom to fit
153
if (imageAspectRatio > canvasAspectRatio) {
154
imageWidth = image.width * (height / image.height)
155
imageX = (width - imageWidth) / 2
156
} else if (imageAspectRatio < canvasAspectRatio) {
157
imageHeight = image.height * (width / image.width)
158
imageY = (height - imageHeight) / 2
159
}
160
} else {
161
// Shrink to fit
162
if (imageAspectRatio < canvasAspectRatio) {
163
imageWidth = image.width * (height / image.height)
164
imageX = (width - imageWidth) / 2
165
} else if (imageAspectRatio > canvasAspectRatio) {
166
imageHeight = image.height * (width / image.width)
167
imageY = (height - imageHeight) / 2
168
}
169
}
170
}
171
172
// Draw the image on the canvas
173
ctx.drawImage(image, imageX, imageY, imageWidth, imageHeight)
174
175
// Draw the image on the game canvas
176
let gc = document.querySelector('.jsx-187140558')
177
gc.getContext('2d')
178
.drawImage(canvas, 0, 0, gc.width, gc.height)
179
180
// Get RGB data from canvas
181
let data = ctx.getImageData(0, 0, width, 424).data
182
183
let packets = []
184
let story = []
185
let strokeId = 0
186
187
if (isAnimation()) {
188
// Gamemodes with animation require different format
189
let pos = 0
190
for (let y = 0; y < height; y++) {
191
for (let x = 0; x < width; x++) {
192
let color = rgbToHex(data[pos], data[pos+1], data[pos+2])
193
packets.push(`42[2,7,{"t":${turnNum},"d":1,"v":[1,${strokeId},["${color}",${penSize},${data[pos+3]/255}],[${x},${y}]]}]`)
194
story.push([1, strokeId, [color, 2, data[3]/255], [x, y]])
195
strokeId++
196
pos += 4
197
}
198
}
199
drawEnabled = false
200
unsafeWindow.setData((function(e){ return story })())
201
} else {
202
// Other gamemodes
203
let dict = {}
204
let pos = 0
205
for (let y = 0; y < height; y++) {
206
for (let x = 0; x < width; x++) {
207
// let pos = i * 4
208
let color = rgbToHex(data[pos], data[pos+1], data[pos+2])
209
if (dict[color] == undefined) {
210
// Huge stability improvement
211
// Use unique stroke ID
212
dict[color] = [8, strokeId, [color, data[3]/255], x, y, 1, 1]
213
strokeId++
214
} else {
215
dict[color].push(x, y, 1, 1)
216
}
217
pos += 4
218
}
219
}
220
221
for (let key in dict) {
222
story.push(dict[key])
223
let stroke = `42[2,7,{"t":${turnNum},"d":1,"v":`+JSON.stringify(dict[key])+`}]`
224
packets.push(stroke)
225
}
226
drawEnabled = false
227
unsafeWindow.setData((function(e){ return story })())
228
}
229
230
// Send packets to server
231
drawEnabled = true
232
return sendPackets(packets, story)
233
//.then(() => drawEnabled = true)
234
}
235
236
function sendPackets (packets, story) {
237
console.log('[Autodraw] Sending packets')
238
return new Promise(function(resolve) {
239
let p = 0
240
let sent = 0
241
let pongCount = 2
242
let rateLimitActive = false
243
let pongsRecieved = 0
244
function pongHandler (e) {
245
if (e.data == '3') {
246
pongsRecieved++
247
console.log('[Autodraw] Pong ' + pongsRecieved + ' / ' + pongCount)
248
if (pongsRecieved >= pongCount) {
249
console.log('[Autodraw] All pongs recieved')
250
currWs.removeEventListener('message', pongHandler)
251
resolve()
252
}
253
}
254
}
255
currWs.addEventListener('message', pongHandler)
256
currWs.send('2')
257
let pingInterval = setInterval(() => {
258
currWs.send('2')
259
pongCount++
260
}, 10000)
261
function sendChunk () {
262
// Check if websocket is in OPEN state
263
if (currWs.readyState != WebSocket.OPEN) {
264
console.log('[Autodraw] Reconnecting', currWs.readyState)
265
setTimeout(sendChunk, 200)
266
return
267
}
268
269
// Only send data when nothing is buffered
270
if (currWs.bufferedAmount > 0) {
271
// Schedule for next javascript tick
272
setTimeout(sendChunk, 0)
273
return
274
}
275
276
// Limit to 100Kb at a time
277
while (currWs.bufferedAmount < 100000) {
278
currWs.send(packets[p])
279
280
sent += packets[p].length
281
282
p++
283
284
if (p >= packets.length) {
285
clearInterval(pingInterval)
286
currWs.send('2')
287
// Exit if the websocket closes
288
console.log('[Autodraw] Finished sending packets')
289
currWs.addEventListener('close', resolve)
290
return
291
}
292
}
293
setTimeout(sendChunk, 0)
294
}
295
sendChunk()
296
})
297
}
298
299
let doneButton
300
let bottomContainer
301
302
// Fake "Done" button that shows while drawing
303
// Prevents submitting before all packets are sent
304
let fakeButton = document.createElement('button')
305
fakeButton.disabled = true
306
fakeButton.style.display = 'none'
307
fakeButton.innerHTML = '<i class="jsx-3322258600 pencil"></i><strong>Drawing...</strong>'
308
309
function disableButton (e) {
310
if (!doneButton) return e
311
doneButton.style.display = 'none'
312
fakeButton.style.display = ''
313
return e
314
}
315
316
function enableButton (e) {
317
if (!doneButton) return e
318
doneButton.style.display = ''
319
fakeButton.style.display = 'none'
320
return e
321
}
322
323
let currentImage
324
325
function loadImage (objectURL) {
326
// Store an image file
327
console.log('[Autodraw] Selected image')
328
dropPreview.style.display = 'block'
329
dropText.style.display = 'none'
330
currentImage = objectURL
331
dropPreview.src = objectURL
332
}
333
334
function unloadImage () {
335
dropPreview.style.display = 'none'
336
dropText.style.display = 'block'
337
currentImage = null
338
dropPreview.src = 'favicon.ico'
339
}
340
341
function startDrawing () {
342
if (!currentImage) {
343
console.error('[Autodraw] No image loaded')
344
return
345
}
346
if (unsafeWindow.location.href.indexOf('draw') == -1) {
347
console.error('[Autodraw] You are not in the drawing section')
348
return
349
}
350
if (!unsafeWindow.setData) {
351
console.error('[Autodraw] window.setData is missing! (Injector malfunction)')
352
return
353
}
354
disableButton()
355
closeDialog()
356
setTimeout(() => {
357
createImage(currentImage)
358
.then(draw)
359
.then(enableButton)
360
.then(() => {
361
console.log('[Autodraw] Done!')
362
closeDialog()
363
unloadImage()
364
})
365
}, 500)
366
}
367
368
function pickFile () {
369
return new Promise(function(resolve) {
370
let picker = document.createElement('input')
371
picker.type = 'file'
372
picker.click()
373
picker.oninput = function() {
374
resolve(URL.createObjectURL(picker.files[0]))
375
}
376
})
377
}
378
379
function createImage (url) {
380
console.log('[Autodraw] Loading image')
381
return new Promise(function(resolve) {
382
let image = document.createElement('img')
383
image.onload = function() {
384
console.log('[Autodraw] Image loaded')
385
resolve(image)
386
}
387
image.src = url
388
})
389
}
390
391
function injectUI () {
392
// Get the side menu container
393
const redoButton = unsafeWindow.document.querySelector(".tool.redo")
394
if (!redoButton) {
395
return
396
}
397
398
const buttonClass = redoButton.classList[0]
399
if (!buttonClass) {
400
console.log('[Autodraw] Could not find tool button class')
401
}
402
403
const sideMenu = redoButton.parentElement;
404
if (!sideMenu || sideMenu.children.length > 10) {
405
return
406
}
407
sideMenu.style.height = 'unset'
408
409
bottomContainer = document.querySelector('.bottom')
410
411
doneButton = bottomContainer.querySelector('.small')
412
const doneButtonClass = doneButton.classList[0]
413
414
fakeButton.classList = doneButtonClass + ' small'
415
416
// Add the fake button
417
bottomContainer.appendChild(fakeButton)
418
419
// Create the "Add image" button
420
const addImageButton = document.createElement('div')
421
addImageButton.classList = buttonClass + ' tool addimage'
422
addImageButton.style.margin = '6px 0 1px 0'
423
addImageButton.style.backgroundSize = '100%'
424
addImageButton.style.color = '#d16283'
425
426
// Add style
427
const style = document.createElement('style')
428
style.innerText = '.' + buttonClass + `.addimage::after {
429
content: "+";
430
margin: 2px;
431
flex: 1 1 0%;
432
border-radius: 3px;
433
align-self: stretch;
434
font: 60px Black;
435
transform: translate(0px, -20px);
436
}`
437
unsafeWindow.document.head.appendChild(style)
438
sideMenu.appendChild(addImageButton)
439
440
// Click handler
441
addImageButton.onclick = openDialog
442
}
443
444
function openDialog () {
445
container.style.display = 'flex'
446
setTimeout(() => {
447
container.style.opacity = '1'
448
}, 0)
449
}
450
451
function closeDialog () {
452
container.style.opacity = '0'
453
setTimeout(() => {
454
container.style.display = 'none'
455
}, 200)
456
}
457
458
// Create the UI
459
const container = document.createElement('div')
460
container.style.width = '100%'
461
container.style.height = '100%'
462
container.style.position = 'absolute'
463
container.style.top = '0px'
464
container.style.left = '0px'
465
container.style.background = 'rgba(0,0,0,0.8)'
466
container.style.justifyContent = 'center'
467
container.style.alignItems = 'center'
468
container.style.display = 'none' // Set to "flex" to show
469
container.style.opacity = 0
470
container.style.zIndex = '5'
471
container.classList = 'autodraw-container'
472
const modal = document.createElement('div')
473
modal.style.width = '60%'
474
modal.style.height = '60%'
475
modal.style.background = 'white'
476
modal.style.padding = '25px 30px'
477
modal.style.borderRadius = '12px'
478
modal.style.display = 'flex'
479
modal.style.flexDirection = 'column'
480
modal.style.alignItems = 'center'
481
modal.style.fontFamily = 'Black'
482
container.appendChild(modal)
483
const closeButton = document.createElement('div')
484
closeButton.innerText = '' // "X" symbol
485
closeButton.style.fontFamily = 'ico' // Icon font
486
closeButton.style.fontSize = '24px'
487
closeButton.style.color = 'black'
488
closeButton.style.textAlign = 'right'
489
closeButton.style.margin = '0 0 0 100%'
490
closeButton.style.lineHeight = '5px' // Center in corner
491
closeButton.style.textTransform = 'uppercase'
492
closeButton.style.height = '0px' // Don't offset the next line
493
closeButton.style.cursor = 'pointer'
494
closeButton.onclick = closeDialog
495
modal.appendChild(closeButton)
496
const title = document.createElement('h2')
497
title.classList = 'jsx-143026286'
498
title.innerText = 'Insert Image'
499
title.style.fontFamily = 'Black'
500
title.style.fontSize = '24px'
501
title.style.color = 'rgb(48, 26, 107)'
502
title.style.textAlign = 'center'
503
title.style.lineHeight = '29px'
504
title.style.textTransform = 'uppercase'
505
title.style.display = 'flex'
506
title.style.flexDirection = 'row'
507
modal.appendChild(title)
508
const dropArea = document.createElement('div')
509
dropArea.style.width = '100%'
510
dropArea.style.height = '100%'
511
dropArea.style.alignItems = 'center'
512
dropArea.style.display = 'flex'
513
dropArea.style.justifyContent = 'center'
514
dropArea.style.border = '4px dashed gray'
515
dropArea.style.borderRadius = '17px'
516
dropArea.style.cursor = 'pointer'
517
dropArea.style.overflow = 'hidden'
518
// dropArea.style.margin = '0 0 10px'
519
dropArea.onclick = function() {
520
pickFile().then(loadImage)
521
}
522
dropArea.addEventListener('dragover', (e) => {
523
e.preventDefault()
524
})
525
dropArea.addEventListener('drop', (e) => {
526
e.preventDefault()
527
loadImage(URL.createObjectURL(e.dataTransfer.files[0]))
528
})
529
const dropText = document.createElement('div')
530
dropText.style.padding = '20px'
531
dropText.innerText = 'Drag and drop images here or click to choose a file'
532
dropArea.appendChild(dropText)
533
const dropPreview = document.createElement('img')
534
dropPreview.style.display = 'none'
535
dropPreview.style.maxWidth = '95%'
536
dropPreview.style.maxHeight = '95%'
537
dropPreview.style.borderRadius = '6px'
538
dropPreview.style.objectFit = 'cover'
539
dropPreview.src = 'favicon.ico'
540
dropArea.appendChild(dropPreview)
541
modal.appendChild(dropArea)
542
const bottomDiv = document.createElement('div')
543
bottomDiv.style.width = '100%'
544
bottomDiv.style.display = 'flex'
545
bottomDiv.style.flexDirection = 'row'
546
bottomDiv.style.margin = '20px 0 0'
547
bottomDiv.style.justifyContent = 'center'
548
modal.appendChild(bottomDiv)
549
const insertButton = document.createElement('button')
550
insertButton.classList = 'insert-button'
551
insertButton.innerText = 'DRAW IMAGE'
552
insertButton.onclick = function() {
553
startDrawing()
554
}
555
bottomDiv.appendChild(insertButton)
556
const uiStyle = document.createElement('style')
557
uiStyle.innerText = `
558
.insert-button:hover {
559
background-color: rgb(64, 32, 194);
560
}
561
.insert-button {
562
margin: 0px 8px;
563
cursor: pointer;
564
border: none;
565
background-color: rgb(86, 53, 220);
566
border-radius: 7px;
567
width: 160px;
568
height: 42px;
569
font-family: Black;
570
font-size: 17px;
571
color: rgb(255, 255, 255);
572
text-align: center;
573
text-transform: uppercase;
574
}
575
.autodraw-container {
576
transition: opacity linear 0.2s;
577
}`
578
579
function injectAll () {
580
setInterval(injectUI, 300)
581
582
// Add UI
583
document.body.appendChild(container)
584
document.head.appendChild(uiStyle)
585
}
586
587
unsafeWindow.startDrawing = startDrawing
588
let stateCheck = setInterval(() => {
589
if (unsafeWindow.document.readyState === 'complete') {
590
injectAll()
591
clearInterval(stateCheck)
592
}
593
}, 100);