// ==UserScript==
// @name scratch extended
// @version 0.18
// @description adds new functionality to scratch.mit.edu
// @run-at document-start
// @author You
// @match *://scratch.mit.edu/*
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAkFJREFUeF7l2sFRAzEMBVBSBww3qqAP6qAUiqEFCuDMjQl1wPhgxuw4kS39r28P3JgkjvRsybvenJ5e379v/vHfKRPg6+O5S3378CKbgjSAS8nXzFUIKQBt8ue3zz+zffd4//u/AoEOcC35mrkSgQowkrwagQYwk7wSgQLgSV6FAAeIJK9AgAKMJl+b3nFHaLeHrMYIA5hNviarRoAAeJNfASEM4Em+XvBEPou6dg4BIBJAjBHBcAMgA0eONYvhAmAEzBhzBCMEgO7gCgQKQGQPz0ZYDqAs21mEyG00BaAkEVkFowj1O2QAJVB0H2gbl7USZACjM8ReCVIAFUJva0svgZllirrFZZ0ou5rgcRasWkUhjFzYzL4HApBZDrMJWu+HAeyKAAXYEQEOsBsCBWAnBBrALghUgB0Q6ACrI6QArIyQBrAqQirAigjpAKshSABWQpABrIIgBVgBwQ2APKBQnie4ABg/eVMhhADQP3lTIEAB2KfA7elO9LlDHQsOoEBIPxWuS1X5UKSFlgGUIFgII/1A+mBkJEBvOcyOnb4CagOZDbR8zgqWMea1o3FXE2wHRAaMHMt6HhDaBY6DIwJHjDGadPu+8ApAlIMq+RI7DMB7Y9POBmtHofYARDkwt1OrLKArwFsOipmHNsGe8mhdX5sh1PV+agl4tshegBnJw5sgaiVkJZ8CMLM7eC+drUYnK4HZcsiceXoTnC0HRfJpJXBpJfSQrJulyHLvfZZyHWAFiTxRtr7Lel0CYAWV+foPdui0399khioAAAAASUVORK5CYII=
// @grant none
// @license GPLv3
// @namespace https://greasyfork.org/users/1184528
// ==/UserScript==
window.getvm = getvm
function getvm() {
return new Promise((resolve) => {
var c
if (window.vm) c = test(true)
if (c) resolve(c)
new Promise(() => {
const oldBind = Function.prototype.bind
Function.prototype.bind = function (...args) {
if (
args[0] &&
args[0].hasOwnProperty("editingTarget") &&
args[0].hasOwnProperty("runtime")
) {
Function.prototype.bind = oldBind
window.vm = args[0]
test()
// vm.runtime._primitives.operator_gt = (e, y) => {
// alert("got it")
// }
}
return oldBind.apply(this, args)
}
})
function test(once) {
if (
document?.querySelectorAll("canvas") &&
(window?.vm ||
document
?.querySelector("#app")
?._reactRootContainer?._internalRoot?.current?.child?.stateNode?.store?.getState?.()
?.scratchGui?.vm) &&
(window?.vm ||
(document
?.querySelector("#app")
?._reactRootContainer?._internalRoot?.current?.child?.stateNode?.store?.getState?.()
?.scratchGui?.vm?.runtime?._lastStepDoneThreads?.length &&
window.vm.runtime.getTargetForStage()))
) {
window.vm =
window?.vm ||
document
.querySelector("#app")
._reactRootContainer._internalRoot.current.child.stateNode.store.getState()
.scratchGui.vm
if (once) return window.vm
resolve(window.vm)
} else {
if (once) return false
setTimeout(test, 1)
}
}
})
}
getvm().then((vm) => {
window.vm = vm
vm.runtime._primitives.operator_gt = (...e) => {
var c = w(...e)
return c
}
var b = setInterval(() => {
if (canvas() && vm.runtime.getTargetForStage()) {
clearInterval(b)
loaded()
}
})
})
var w
window.canvas = canvas
function loaded() {
function gtgotran(e, y) {
datatype = "gt"
var func = e.OPERAND1
var args = String(e.OPERAND2)
if (
/^[$!#%()*+,-./:;=?@\[\]^_`{|}~ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789]{20}$/.test(
args
)
)
args =
Object.values(y?.thread?.target?.comments)?.filter?.(
(e) => e.id == args
)?.[0]?.text ?? args
if (func === "isloaded") {
scratchvar("__script loaded", "true")
return true
}
window.__scratchextended.thread = y?.thread
window.__scratchextended.eventdata = y?.thread?.target
if (window.__scratchextendendebug)
console.log(y?.thread?.target?.sprite?.name)
if (func === "") {
return (y.thread.lastvalue = parse(args).join(""))
} else if (__scratchextended[func]) {
return (y.thread.lastvalue =
run(func, parse(args, func !== "escape")) || "")
} else {
return Number(e.OPERAND1) > Number(e.OPERAND2)
}
}
w = gtgotran
var mousestate = {
left: false,
center: false,
right: false,
}
function mousechanged(e) {
e.preventDefault()
switch (e.buttons) {
case 0:
mousestate = {
left: false,
center: false,
right: false,
}
break
case 1:
mousestate = {
left: true,
center: false,
right: false,
}
break
case 2:
mousestate = {
left: false,
center: false,
right: true,
}
break
case 3:
mousestate = {
left: true,
center: false,
right: true,
}
break
case 4:
mousestate = {
left: false,
center: true,
right: false,
}
break
case 5:
mousestate = {
left: true,
center: true,
right: false,
}
break
case 6:
mousestate = {
left: false,
center: true,
right: true,
}
break
case 7:
mousestate = {
left: true,
center: true,
right: true,
}
break
}
}
canvas().oncontextmenu = (e) => e.preventDefault()
canvas().onmousedown = mousechanged
canvas().onmouseup = mousechanged
canvas().addEventListener("click", async () => {
if (pointerlock_active)
await canvas().requestPointerLock({
unadjustedMovement: true,
})
mousepos.x = 0
mousepos.y = 0
})
var messagelisteners = []
var pointerlock_active = false
var mousepos = {
x: 0,
y: 0,
}
canvas().onmousemove = (e) => {
if (pointerlock_active && !!document.pointerLockElement) {
mousepos.x = e.movementX
mousepos.y = -e.movementY
if (Math.abs(mousepos.x) < 5) mousepos.x = 0
if (Math.abs(mousepos.y) < 5) mousepos.y = 0
scratchvar("__mousex", mousepos.x)
scratchvar("__mousey", mousepos.y)
}
}
if (!Array.prototype.none)
Object.defineProperty(Array.prototype, "none", {
value: function (callback) {
return !this.some(callback)
},
})
var rawdata
window.vm =
window?.vm ||
document
.querySelector("#app")
._reactRootContainer._internalRoot.current.child.stateNode.store.getState()
.scratchGui.vm
const stage = vm.runtime.getTargetForStage()
newfunc("vm.runtime._step", () => {
if (!pointerlock_active && !document.pointerLockElement) {
mousepos.x = vm.runtime.ioDevices.mouse._scratchX
mousepos.y = vm.runtime.ioDevices.mouse._scratchY
scratchvar("__mousex", mousepos.x)
scratchvar("__mousey", mousepos.y)
if (pointerlock_active) {
mousepos.x = 0
mousepos.y = 0
}
}
})
document.onpointerlockchange = () => {
scratchvar("__pointerlock", !!document.pointerLockElement)
}
function scratchlist(listname, value, spritename = aaa?.spritename) {
if (value === undefined && /^\[\]$/.test(listname))
return JSON.parse(listname)
if (value !== undefined) {
if (gettarget(spritename)?.getvar(listname, "list"))
gettarget(spritename).getvar(listname, "list").value = [...value]
else console.warn(`list "${listname}" does not exist`)
} else {
return gettarget(spritename)?.getvar(listname, "list")?.value
}
}
function scratchvar(varname, value, spritename = aaa?.spritename) {
if (value !== undefined) {
if (varname === "__out") {
scratchvar("__loading", "done", spritename)
if (scratchlist("debug:outputlog", undefined, spritename))
updatelist(
"debug:outputlog",
(e) => {
e[e.length - 1] = value
return e
},
spritename
)
if (aaa?.before && aaa.before !== "__out") {
if (aaa.before.includes("list:")) {
updatelist(
aaa.before.replace("list:", ""),
(e) => {
e.push(String(value))
return e
},
undefined
)
} else scratchvar(aaa?.before, String(value), varname)
return
}
}
if (varname === "__error") scratchvar("__loading", "error")
if (gettarget(spritename)?.getvar(varname))
gettarget(spritename).getvar(varname).value = String(value)
else {
if (
[
"__mousey",
"__mousex",
"__out",
"__error",
"__loading",
"__script loaded",
].includes(varname)
)
return
console.warn(`var "${varname}" does not exist`)
}
} else {
return gettarget(spritename)?.getvar(varname)?.value
}
}
const projectid = location.href.match(/(?<=\/)[0-9]+(?=\/)/)?.[0] || "local"
listen(window, "keydown", (e) => {
var index = vm.runtime.ioDevices.keyboard._keysPressed.indexOf(
e.key.toUpperCase()
)
if (index !== -1) {
vm.runtime.ioDevices.keyboard._keysPressed.splice(index, 1)
}
vm.runtime.ioDevices.keyboard._keysPressed.push(e.key.toUpperCase())
})
listen(window, "keyup", (e) => {
var index = vm.runtime.ioDevices.keyboard._keysPressed.indexOf(
e.key.toUpperCase()
)
if (index !== -1) {
vm.runtime.ioDevices.keyboard._keysPressed.splice(index, 1)
}
})
function createelem(elem, data, parent) {
elem = document.createElement(elem)
if (data.style)
data.style.split(" ").forEach((e) => {
elem.classList.add(e)
})
Object.assign(elem.style, data)
Object.assign(elem, data)
if (typeof parent == "string") parent = qs(parent)
parent?.appendChild(elem)
return elem
}
var aaa = "asd"
window.__scratchextended = {
inputtype: undefined,
eventdata: undefined,
thread: undefined,
clip(data) {
return new Promise(function (done, error) {
if (data)
navigator.clipboard
.writeText(data)
.then(() => {
done(data)
})
.catch(() => {
done(false)
})
else {
navigator.clipboard
.readText()
.then((e) => {
done(e)
})
.catch((e) => {
error(e)
})
}
})
},
open(a, s, d, e) {
if (e) open(a, s, d).document.write(e)
else open(a, s, d)
return true
},
popup(text, x, y, w, h) {
var win
if (x) {
x = totype(x, "number", true)
y = totype(y, "number", true)
w = totype(w, "number", true)
h = totype(h, "number", true)
x = (screen.width / 100) * x
y = (screen.height / 100) * y
w = (screen.width / 100) * w
h = (screen.height / 100) * h
win = open("", "", `left=${x}, top=${y} width=${w},height=${h}`)
} else win = open("")
if (text) win.document.write(text)
return win
},
eval(data) {
return eval(data)
},
alert(data) {
alert(data)
},
prompt(a, s) {
return prompt(a, s)
},
confirm(a) {
return confirm(a)
},
save(key, data) {
localStorage.setItem(projectid + key, data)
},
load(key) {
return localStorage.getItem(projectid + key)
},
clearsave(a) {
if (a) localStorage.removeItem(key)
else {
var n = localStorage.length
while (n--) {
var key = localStorage.key(n)
if (key.startsWith(projectid)) {
localStorage.removeItem(key)
}
}
}
return true
},
fetch(a, s) {
s = totype(s, "json")
return new Promise(function (done) {
if (s)
fetch(a, s).then((e) => {
e.text().then((e) => {
return e
})
})
else
fetch(a).then((e) => {
e.text().then((e) => {
done(e)
})
})
})
},
pointerlock(a) {
a = totype(a, "bool")
if (a) pointerlock_active = true
else {
pointerlock_active = false
document.exitPointerLock()
}
},
cursor(a) {
return (canvas().style.cursor = a)
},
escape() {
return rawdata.replace(/(['"`\\])/g, "\\$1").replaceAll("${", "\\${")
},
replace(a, s, d, regex) {
regex = totype(regex, "bool") || false
if (regex)
return a.replace(
new RegExp(s.match(/(?<=\/).*(?=\/)/)[0], s.split("/").pop()),
d
)
else return a.replaceAll(s, d)
},
substr(a = "", s = "", d = "") {
return a.substring(s - 1, d)
},
press(key) {
if (key == " ") key = "space"
if (/^[a-z]$/.test(key)) key = key.toUpperCase()
var index = vm.runtime.ioDevices.keyboard._keysPressed.indexOf(key)
if (index !== -1) {
vm.runtime.ioDevices.keyboard._keysPressed.splice(index, 1)
}
vm.runtime.ioDevices.keyboard._keysPressed.push(key)
return true
},
unpress(key) {
if (key == " ") key = "space"
if (/^[a-z]$/.test(key)) key = key.toUpperCase()
var index = vm.runtime.ioDevices.keyboard._keysPressed.indexOf(key)
if (index !== -1) {
vm.runtime.ioDevices.keyboard._keysPressed.splice(index, 1)
}
return true
},
pressed(keys = "", any = "") {
keys = [...keys.matchAll(/[a-z:]+/g)].map((e) => e[0])
keys = keys.map((e) => e.toLowerCase())
var nokeys = [...any.matchAll(/[a-z:]+/g)].map((e) => e[0])
nokeys = nokeys.map((e) => e.toLowerCase())
var x = vm.runtime.ioDevices.keyboard._keysPressed.map((e) =>
e.toLowerCase()
)
if (mousestate.left) x.push("mouse:left")
if (mousestate.right) x.push("mouse:right")
if (mousestate.center) x.push("mouse:center")
if (any === true) return keys.some((e) => x.includes(e))
else
return (
keys.every((e) => x.includes(e)) && nokeys.none((e) => x.includes(e))
)
},
getkeys(key, list) {
if (key == "first") {
return vm.runtime.ioDevices.keyboard._keysPressed[0]
} else if (key == "last") {
return vm.runtime.ioDevices.keyboard._keysPressed[
vm.runtime.ioDevices.keyboard._keysPressed.length - 1
]
} else if (key) {
return vm.runtime.ioDevices.keyboard._keysPressed.includes(key)
} else {
if (list)
scratchlist(list, vm.runtime.ioDevices.keyboard._keysPressed.sort())
else
return JSON.stringify(
vm.runtime.ioDevices.keyboard._keysPressed.sort()
)
}
},
getmouse(button) {
if (button && mousestate[button] !== undefined) return mousestate[button]
else return JSON.stringify(mousepos)
},
fullscreen(on) {
on = totype(on, "bool")
if (on) canvas().requestFullscreen()
else if (on === undefined) return !!document.fullscreenElement
else document.exitFullscreen()
return true
},
clearlogs() {
if (scratchlist("debug:functionlog")) scratchlist("debug:functionlog", [])
if (scratchlist("debug:datalog")) scratchlist("debug:datalog", [])
if (scratchlist("debug:outputlog")) scratchlist("debug:outputlog", [])
return true
},
doesitexist(func) {
return Object.keys(__scratchextended).includes(func)
},
requestperms(...d) {
if (
(
JSON.parse(localStorage.getItem("allowed:" + projectid)) || []
).includes("all")
)
return true
var perms = [
...(JSON.parse(localStorage.getItem("allowed:" + projectid)) || []),
...(JSON.parse(localStorage.getItem("denied:" + projectid)) || []),
...(JSON.parse(sessionStorage.getItem("temprevoked:" + projectid)) ||
[]),
]
data = d.filter((o) => !perms.some((i) => i === o))
if (data.length)
if (confirm(`grant permissions for "${data.join('", "')}"?`)) {
perms = [
...new Set([
...(JSON.parse(localStorage.getItem("allowed:" + projectid)) ||
[]),
...data,
]),
]
localStorage.setItem("allowed:" + projectid, JSON.stringify(perms))
return true
} else {
var perms = [
...new Set([
...(JSON.parse(
sessionStorage.getItem("temprevoked:" + projectid)
) || []),
...data,
]),
]
sessionStorage.setItem(
"temprevoked:" + projectid,
JSON.stringify(perms)
)
return false
}
return (
d.filter((e) =>
JSON.parse(localStorage.getItem("allowed:" + projectid)).includes(e)
).length === d.length
)
},
indexof(x, y) {
return x.indexOf(y + 1)
},
matches(x = "", y = "") {
return y.test(
new RegExp(x.match(/(?<=\/).*(?=\/)/)[0], x.split("/").pop())
)
},
lowercase(data = "") {
return String(data.toLowerCase())
},
uppercase(data = "") {
return String(data.toUpperCase())
},
places(num, places = 3) {
num = totype(num, "number", true)
places = totype(places, "number", true)
return String(Number(num).toFixed(Number(places)))
},
turbo(on) {
on = totype(on, "bool")
if (on === undefined) return vm.runtime.turboMode
else vm.setTurboMode(on)
},
savefile(data = "", name = "temp.txt") {
const blob = new Blob([data])
const url = URL.createObjectURL(blob)
const link = document.createElement("a")
link.href = url
link.download = name
document.body.appendChild(link)
link.click()
link.remove()
URL.revokeObjectURL(url)
return true
},
readfile(accept, readas = "text") {
//accept:"image/png, image/jpeg"
return new Promise((resolve) => {
var s = createelem("input", { type: "file", accept }, document.body)
s.click()
s.onchange = () => {
file = s.files[0]
var f = new FileReader()
f.onloadend = () => {
resolve(f.result)
}
f.onerror = (e) => {
console.warn(e)
scratchvar("__error", e)
scratchvar("__loading", "error")
}
if (readas == "text") f.readAsText(file)
if (readas == "dataurl") f.readAsDataURL(file)
if (readas == "binary") f.readAsBinaryString(file)
}
})
},
split(z, split, list, spritename) {
//splits "z:string" by "split:string" and returns it
if (list) scratchlist(list, z.split(split), spritename)
else return JSON.stringify(z.split(split))
},
join(list, join) {
//joins "list:name of list" by "join:string"
return scratchlist(list, scratchlist(list).join(join))
},
var(varname, value, sprite) {
return scratchvar(varname, value, sprite)
},
list(list, data) {
//if "data:set": replaces "list:name of list" with "data: an array" elsereturns "list:name of list" as as an array
if (data)
scratchlist(
list,
JSON.parse(data).map((e) => {
return typeof e == "object" ? JSON.stringify(e) : e
})
)
else return JSON.stringify(scratchlist(list))
},
fromms(ms = 0) {
ms = totype(ms, "number", true)
return JSON.stringify({
years: Math.floor(ms / 1000 / 60 / 60 / 24 / 360),
days: Math.floor(ms / 1000 / 60 / 60 / 24) % 365,
hours: Math.floor(ms / 1000 / 60 / 60) % 24,
mins: Math.floor(ms / 1000 / 60) % 60,
secs: Math.floor(ms / 1000) % 60,
ms: Math.floor(ms) % 1000,
})
},
toms(ms = 0, secs = 0, mins = 0, hours = 0, days = 0, years = 0) {
ms = totype(ms, "number", true)
secs = totype(secs, "number", true)
mins = totype(mins, "number", true)
hours = totype(hours, "number", true)
days = totype(days, "number", true)
years = totype(years, "number", true)
return (
ms +
secs * 1000 +
mins * 60000 +
hours * 3600000 +
days * 86400000 +
years * 31560000000
)
},
keeponlyone(arr) {
arr = totype(arr, "list", true)
arr = arr.filter((e, i) => i == arr.indexOf(e))
if (scratchlist(a)) scratchlist(a, arr)
else return JSON.stringify(arr)
},
json(json, value = "") {
json = totype(json, "json", true)
if (value.includes("/"))
value.split("/").forEach((e) => {
json = json[e]
})
else json = json[value]
//if(json.reverse)
return typeof json == "string" || typeof json == "number"
? json
: JSON.stringify(json)
},
filter(arr, filter) {
arr = totype(arr, "list", true)
arr = arr.filter(() => eval(filter))
if (scratchlist(list)) scratchlist(list, arr)
else return JSON.stringify(arr)
},
map(arr, map) {
arr = totype(arr, "list", true)
arr = arr.map(() => eval(map))
if (scratchlist(list)) scratchlist(list, arr)
else return JSON.stringify(arr)
},
power(x, y) {
x = totype(x, "number", true)
y = totype(y, "number", true)
return Math.pow(x, y)
},
createvar(varname, sprite) {
if (!gettarget(sprite))
return scratchvar("__error", "sprite does not exist")
gettarget(sprite).createVariable(varname, varname, "", sprite)
return true
},
deletevar(varname, sprite) {
if (!gettarget(sprite))
return scratchvar("__error", "sprite does not exist")
if (gettarget(sprite).getvar(varname, "")?.id) {
gettarget(sprite).deleteVariable(
gettarget(sprite).getvar(varname, "").id
)
return true
} else {
return false
}
},
createlist(list, sprite) {
if (!gettarget(sprite))
return scratchvar("__error", "sprite does not exist")
return true
gettarget(sprite).createVariable(list, list, "list", sprite)
},
deletelist(list, sprite) {
if (!gettarget(sprite))
return scratchvar("__error", "sprite does not exist")
if (gettarget(sprite).getvar(list, "list")?.id) {
gettarget(sprite).deleteVariable(
gettarget(sprite).getvar(list, "list").id
)
return true
} else {
return false
}
},
getallvars() {
return JSON.stringify(vm.runtime.getAllVarNamesOfType())
},
math(func, ...num) {
num = num.map((e) => totype(e, "number", true))
if (num !== undefined) return Math[func].call(this, ...num)
else return Math[func]
},
showvar(varname, sprite) {
if (!gettarget(sprite))
return scratchvar("__error", "sprite does not exist")
if (gettarget(sprite).getvar(varname, "")?.id) {
vm.runtime._primitives.data_showvariable({
VARIABLE: gettarget(sprite).getvar(varname, ""),
})
return true
} else {
return false
}
},
showlist(list, sprite) {
if (!gettarget(sprite))
return scratchvar("__error", "sprite does not exist")
if (gettarget(sprite).getvar(list, "list")?.id) {
vm.runtime._primitives.data_showlist({
LIST: gettarget(sprite).getvar(list, "list"),
})
return true
} else {
return false
}
},
hidevar(varname, sprite) {
if (!gettarget(sprite))
return scratchvar("__error", "sprite does not exist")
if (gettarget(sprite).getvar(varname, "")?.id) {
vm.runtime._primitives.data_hidevariable({
VARIABLE: gettarget(sprite).getvar(varname, ""),
})
return true
} else {
return false
}
},
hidelist(list, sprite) {
if (!gettarget(sprite))
return scratchvar("__error", "sprite does not exist")
if (gettarget(sprite).getvar(list, "list")?.id) {
vm.runtime._primitives.data_hidelist({
LIST: gettarget(sprite).getvar(list, "list"),
})
return true
} else {
return false
}
},
console(type, ...data) {
console[type](...data)
},
distance(x1, y1, x2, y2) {
x1 = totype(x1, "number", true)
y1 = totype(y1, "number", true)
x2 = totype(x2, "number", false) ?? window.__scratchextended.eventdata.x
y2 = totype(y2, "number", false) ?? window.__scratchextended.eventdata.y
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2))
},
pointto(x, y) {
x = totype(x, "number")
y = totype(y, "number")
return gettarget(
window.__scratchextended.eventdata.sprite.name
).setDirection(
scratch_math(
"atan",
(window.__scratchextended.eventdata.x - x) /
(window.__scratchextended.eventdata.y - y)
) +
(window.__scratchextended.eventdata.y >= y) * 180
)
},
onmessage(message) {
if (!message) {
return new Promise(function (done) {
const index = messagelisteners.length
messagelisteners.push(function (e) {
delete messagelisteners[index]
if (window.__scratchextendendebug) console.log(messagelisteners)
done(e)
})
})
} else if (/^\/(.*)\/[gimsuy]$/.test(message)) {
return new Promise(function (done) {
const index = messagelisteners.length
messagelisteners.push(function (e) {
if (
new RegExp(
message.match(/^\/(.*)\/([gimsuy]*)$/)[1],
message.match(/^\/(.*)\/([gimsuy]*)$/)[2]
).test(e)
) {
delete messagelisteners[index]
if (window.__scratchextendendebug) console.log(messagelisteners)
done(e)
}
})
})
} else {
return new Promise(function (done) {
const index = messagelisteners.length
messagelisteners.push(function (e) {
if (e == message) {
delete messagelisteners[index]
if (window.__scratchextendendebug) console.log(messagelisteners)
done(e)
}
})
})
}
},
colorat(x, y, a) {
x = totype(x, "number", true)
y = totype(y, "number", true)
a = totype(a, "bool") || false
return new Promise(function (done) {
var gl = canvas().getContext("webgl") || canvas().getContext("webgl2")
var pixels = new Uint8Array(4)
const xx = x + 240
const yy = y + 180
test()
function test() {
gl.readPixels(xx, yy, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
if (pixels.join(",") === "0,0,0,255") setTimeout(test, 0)
else {
var c = [...pixels]
if (!a) c.pop()
done(JSON.stringify([...c]))
}
}
})
},
arrdistance(arr1, arr2) {
arr1 = totype(arr1, "list", true)
arr2 = totype(arr2, "list", true)
arr1 = [...arr1.matchAll(/[\-0-9]+/g)].map((e) => Number(e[0]))
arr2 = [...arr2.matchAll(/[\-0-9]+/g)].map((e) => Number(e[0]))
return dist(arr1, arr2)
function dist(x, y) {
var d = 0
x.forEach((_e, i) => {
d += x[i] - y[i]
})
return Math.abs(d)
}
},
clickat(x, y, time) {
x = totype(x, "number", true)
y = totype(y, "number", true)
time = totype(time, "number")
return new Promise(function (done) {
x = Number(x)
y = -Number(y)
x += qol.rect(canvas()).x + 240
y += qol.rect(canvas()).y + 180
canvas().dispatchEvent(
new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true,
screenX: x,
screenY: y,
clientX: x,
clientY: y,
})
)
canvas().dispatchEvent(
new MouseEvent("mousedown", {
view: window,
bubbles: true,
cancelable: true,
screenX: x,
screenY: y,
clientX: x,
clientY: y,
})
)
setTimeout(() => {
canvas().dispatchEvent(
new MouseEvent("mouseup", {
view: window,
bubbles: true,
cancelable: true,
screenX: x,
screenY: y,
clientX: x,
clientY: y,
})
)
done("")
}, time || 1)
})
},
nextanswer(answer) {
return new Promise(function (done) {
var last = vm.runtime._events.QUESTION
vm.runtime._events.QUESTION = () => {
vm.runtime._events.ANSWER(answer)
setTimeout(() => {
vm.runtime._events.ANSWER(answer)
vm.runtime._events.QUESTION = last
done(answer || "")
})
}
})
},
jsonkeys(json, list, sprite) {
if (scratchlist(list, undefined, sprite))
return scratchlist(list, Object.keys(JSON.parse(json)), sprite)
else return JSON.stringify(Object.keys(JSON.parse(json)))
},
jsonvalues(json, list, sprite) {
if (scratchlist(list, undefined, sprite))
return scratchlist(list, Object.values(JSON.parse(json)), sprite)
else return JSON.stringify(Object.values(JSON.parse(json)))
},
compress(data, value) {
value = totype(value, "number")
try {
return _c(data, value).value
} catch (e) {
error(e)
return "ERROR"
}
},
decompress(data) {
try {
return de_c(data)
} catch (e) {
error(e)
return "ERROR"
}
},
pausetimer() {
vm.runtime.ioDevices.clock.pause()
},
resumetimer() {
vm.runtime.ioDevices.clock.resume()
},
tempvar(name, value) {
return (window.__scratchextended.thread[name] =
value ?? window.__scratchextended.thread[name])
},
testfunction(...a) {
window.testfunction(...a)
},
}
scratchvar("__script loaded", "true")
send("__script loaded")
function run(func, args) {
scratchvar("__script loaded", "true")
data = args
if (scratchlist("debug:functionlog"))
updatelist(
"debug:functionlog",
(e) => {
e.push(func)
return e
},
undefined
)
if (scratchlist("debug:datalog"))
updatelist(
"debug:datalog",
(e) => {
e.push(JSON.stringify(args))
return e
},
undefined
)
if (scratchlist("debug:outputlog"))
updatelist(
"debug:outputlog",
(e) => {
e.push("")
return e
},
undefined
)
if (window.__scratchextendendebug) console.log(func, data)
try {
var perms = JSON.parse(localStorage.getItem("allowed:" + projectid)) || []
var req = func.toLowerCase()
var noperms =
JSON.parse(localStorage.getItem("denied:" + projectid)) || []
if (noperms.includes("all") || noperms.includes(req)) return
var defaultperms =
JSON.parse(localStorage.getItem("scratch:defaultperms")) || []
if (
["requestperms", "doesitexist"].includes(req) ||
perms.includes(req) ||
defaultperms.includes(req) ||
perms.includes("all") ||
defaultperms.includes("all")
)
try {
return window.__scratchextended[req](...args)
} catch (E) {
scratchvar("__loading", "error")
console.trace(E)
}
else {
if (noperms.includes(req)) {
localStorage.setItem("denied:" + projectid, JSON.stringify(noperms))
return
}
var c = confirm(`grant permission for "${req}"?`)
if (c) {
perms.push(req)
localStorage.setItem("allowed:" + projectid, JSON.stringify(perms))
return window.__scratchextended[req](...args)
} else {
noperms.push(req)
localStorage.setItem("denied:" + projectid, JSON.stringify(noperms))
}
}
} catch (e) {
scratchvar("__loading", "error")
console.trace(e)
}
}
// vm.runtime._events.SAY = (event, type, rawdata) => {
// datatype = "say"
// window.__scratchextended.eventdata = event
// if (__scratchextended[rawdata?.match(/^[a-z0-9]*?(?=[ '"`(])/i)?.[0]])
// run(rawdata?.match(/^[a-z0-9]*?(?=[ '"`(])/i)?.[0], parse(rawdata))
// }
function updatelist(listname, func, spritename) {
scratchlist(
listname,
func([...scratchlist(listname, undefined, spritename)]),
spritename
)
}
function parse(data, canhavevar) {
rawdata = data
var rand = Math.random()
data = data.replaceAll(/\\\\/g, rand).replaceAll("\\n", "\n")
aaa = (
data.match(
/(?:(?<!\\)(["'`])[^]*?(?<!\\)\1)|(?<=\()(?<!(["'`]))[^]*(?=\))/gim
) || []
).map((e) => {
e = e
.replace(/\\(['"`])/g, "$1")
.replace(/^["'`]/, "")
.replace(/["'`]$/, "")
.replaceAll(rand, "\\")
if (canhavevar)
e = e
.replaceAll(
/(?<!\\)\$\{([a-z0-9_!@#$%^&*() ,/;'\[\]\\:"<>?\{|~`+\}]+?(?:\.[a-z0-9_!@#$%^&*() ,/;'\[\]\\:"<>?\{|~`+\}]+?)?)\}/g,
(_full, one) =>
window.__scratchextended.thread[one] ??
(one.includes(".")
? scratchvar(one.split(".")[1], undefined, one.split(".")[0])
: scratchvar(one))
)
.replaceAll("\\${", "${")
return e
})
return aaa
}
function newfunc(func1, func2) {
eval(`var a = ${func1}
var s = ${func2}
${func1} = function (...args) {
try{
s(arguments)
}
catch(e){console.error("${func1.replaceAll('"', '\\"')}", e)}
return a.call(this, ...args)
}`)
}
function gettarget(sprite) {
if (sprite) var x = vm.runtime.getSpriteTargetByName(sprite)
else var x = vm.runtime.getTargetForStage()
x.getvar = x.lookupVariableByNameAndType
return x
}
window.gettarget = gettarget
window.stage = stage
function totype(inp, type, forced) {
//number, string, list, object, json, bool
try {
switch (type) {
case "number":
if (inp == "true") inp = 1
if (inp == "false") inp = 0
if (/^-?[0-9]*\.?[0-9]+$/.test(inp)) return Number(inp)
if (inp === "NaN" || inp == "nan") return NaN
return fail(inp, type, forced)
case "string":
return inp
case "list":
inp = JSON.parse(inp)
if (inp.reverse) return inp
return fail(inp, type, forced)
case "object":
inp = JSON.parse(inp.replaceAll("'", '"').replaceAll("`", '"'))
if (/^[\-0-9]+$/.test(inp) || inp === true || inp === false)
return undefined
if (Object.keys(inp).length !== undefined && inp.length === undefined)
return inp
return fail(inp, type, forced)
case "bool":
if (inp === "1" || inp === "true") return true
if (inp === "0" || inp === "false") return false
return fail(inp, type)
case "json":
if (
totype(inp, "object") !== undefined ||
totype(inp, "list") !== undefined
)
return totype(inp, "object") || totype(inp, "list")
else {
fail(inp, type, forced)
}
}
} catch (s) {
return fail(inp, type, forced)
}
function fail(inp, type, forced) {
if (forced) {
throw new Error(`"${inp}" must be of type "${type}"`)
} else return undefined
}
}
function scratch_math(operator, n) {
switch (operator) {
case "sin":
return Math.round(Math.sin((Math.PI * n) / 180) * 1e10) / 1e10
case "cos":
return Math.round(Math.cos((Math.PI * n) / 180) * 1e10) / 1e10
case "asin":
return (Math.asin(n) * 180) / Math.PI
case "acos":
return (Math.acos(n) * 180) / Math.PI
case "atan":
return (Math.atan(n) * 180) / Math.PI
case "log":
return Math.log(n) / Math.LN10
}
return 0
}
function send(data) {
vm.runtime.startHats("event_whenbroadcastreceived", {
BROADCAST_OPTION: data,
})
}
newfunc("vm.runtime.startHats", function (...args) {
args = args[0]
if (args[0] == "event_whenbroadcastreceived") {
if (
!messagelisteners[messagelisteners.length - 1] &&
messagelisteners.length
)
messagelisteners.pop()
messagelisteners.forEach((e) => {
e(args[1].BROADCAST_OPTION)
})
if (window.__scratchextendendebug) console.log(messagelisteners)
}
})
var allowedchars =
"asdfghjklqwertyuiopzxcvbnm,./;'[]`1234567890-=}{\":?><|+_)(*&^%$#@!~\\ASDFGHJKLZXCVBNMQWERTYUIOP "
function best_c(data) {
var arr = [Infinity, ""]
for (var i = 1; i++ < 20; ) {
var s = _c(data, i).value
if (s.length < arr[0]) arr = [s.length, s, i]
}
return {
value: arr[1],
p: Math.round((s.length / data.length) * 10000) / 100,
}
}
var asd = []
for (var i = 100; i++ < 999; ) {
var c = String(i)
while (c.length < 4) c = "1" + c
eval(`c = "\\u${c}"`)
if (c) asd.push(c)
}
asd = asd.filter((e) => {
return asd.join("").includes(e)
})
asd = asd.filter((e) => !allowedchars.split("").includes(e))
function de_c(text) {
const split1 = "\u1000"
const split2 = "\u1001"
text = text.split(split1)
var arr = text[0].split(split2)
arr.forEach((e, i) => {
text[1] = text[1].replaceAll(asd[i], e)
})
return text[1]
}
function _c(t, jj) {
if (!jj) return best_c(t)
var origlength = t.length
const split1 = "\u1000"
function g(count) {
var d = ""
while (count-- > 0) {
d += "."
}
return d
}
var dots = g(jj)
const split2 = "\u1001"
var t1 = [...t.matchAll(new RegExp(dots, "g"))]
var c = t.split("")
c.shift()
c = c.join("")
var t2 = [...c.matchAll(new RegExp(dots, "g"))]
t1 = t1.map((e) => JSON.stringify(e))
t2 = t2.map((e) => JSON.stringify(e))
var t1dupes = t1.length - keeponlyone([...t1]).length
var t2dupes = t2.length - keeponlyone([...t2]).length
var output = t2dupes > t1dupes ? t2 : t1
var arr = []
output.forEach((e, i) => {
if (output.indexOf(e) !== i) arr.push(JSON.parse(e))
})
arr = keeponlyone(arr.flat())
var x = arr.join(split2)
arr.forEach((e, i) => {
t = t.replaceAll(e, asd[i])
})
return {
value: x + split1 + t,
p: Math.round((t.length / origlength) * 10000) / 100,
}
}
}
function canvas() {
return (
window?.vm?.runtime?.renderer?.canvas ||
document.querySelector(
"#app > div > div.gui_body-wrapper_-N0sA.box_box_2jjDp > div > div.gui_stage-and-target-wrapper_69KBf.box_box_2jjDp > div.stage-wrapper_stage-wrapper_2bejr.box_box_2jjDp > div.stage-wrapper_stage-canvas-wrapper_3ewmd.box_box_2jjDp > div > div.stage_stage_1fD7k.box_box_2jjDp > div:nth-child(1) > canvas"
) ||
document.querySelector(
"#view > div > div.inner > div:nth-child(2) > div.guiPlayer > div.stage-wrapper_stage-wrapper_2bejr.box_box_2jjDp > div.stage-wrapper_stage-canvas-wrapper_3ewmd.box_box_2jjDp > div > div.stage_stage_1fD7k.box_box_2jjDp > div:nth-child(1) > canvas"
) ||
document.querySelector(
"#app > div > div > div > div.gui_body-wrapper_-N0sA.box_box_2jjDp > div > div.gui_stage-and-target-wrapper_69KBf.box_box_2jjDp > div.stage-wrapper_stage-wrapper_2bejr.box_box_2jjDp > div.stage-wrapper_stage-canvas-wrapper_3ewmd.box_box_2jjDp > div > div.stage_stage_1fD7k.box_box_2jjDp > div:nth-child(1) > canvas"
)
)
}
listen(window, "mousemove", function () {
if (!location.href.includes("/editor")) return
if (!window?.vm?.editingTarget?.comments) return
var temp = false
a(vm.editingTarget.comments).foreach(function (a, s) {
var f = vm.editingTarget.blocks._blocks[s.blockId]
if (!f) return
if (f.comment !== a) {
f.comment = a
return
}
if (f?.opcode !== "operator_gt") return
if (
vm.editingTarget.blocks._blocks[f.inputs.OPERAND2.block]?.fields?.TEXT
?.value == a
)
return
if (
vm.editingTarget.blocks._blocks[f.inputs.OPERAND2.block]?.fields?.TEXT
?.value === undefined
)
return
vm.editingTarget.blocks._blocks[f.inputs.OPERAND2.block].fields.TEXT.value =
a
f.comment = a
return (temp = true)
})
if (temp) vm.emitWorkspaceUpdate()
a(vm.editingTarget.comments).foreach(function (a, s) {
var f = vm.editingTarget.blocks._blocks[s.blockId]
if (!f) delete vm.editingTarget.comments[a]
else f.comment = a
})
})
function listen(elem, type, cb) {
elem.addEventListener(type, cb)
return [elem, type, cb]
}
function unlisten(data) {
return data[0].removeEventListener(data[1], data[2])
}
function foreachobj(arr, func) {
Reflect.ownKeys(arr).forEach((e, i) => {
func(e, arr[e], i)
})
}
function keeponlyone(arr) {
return [...new Set(arr)]
}