Greasy Fork is available in English.

scratch extended

adds new functionality to scratch.mit.edu

// ==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)]
}