soiz1's picture
Upload 811 files
30c32c8 verified
raw
history blame
31.9 kB
const formatMessage = require('format-message');
const BlockType = require('../../extension-support/block-type');
const ArgumentType = require('../../extension-support/argument-type');
const ProjectPermissionManager = require('../../util/project-permissions');
const SandboxRunner = require('../../util/sandboxed-javascript-runner');
const beatgammit = {
deflate: require('./beatgammit-deflate'),
inflate: require('./beatgammit-inflate')
};
const {
validateArray
} = require('../../util/json-block-utilities');
const ArrayBufferUtil = require('../../util/array buffer');
const BufferParser = new ArrayBufferUtil();
const Cast = require('../../util/cast');
// const Cast = require('../../util/cast');
const warningIcon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAC8SURBVDhPpZPBDYMwDEWhJw4MQzdgG0bi0APDlHuPZRv6X2xUaqJWpE8y2Pk/JkRJFVnXtVOMiqdig5yxzm1HJDZu+gWexqcZDCjuqHtcRo/gfTdRkf2yy7kGMG4i/5wlGYSXObqL9MFsRQw06C0voq9ZhxcHasH7m4cV/AUNFkuLWGgwW17EzB5wPB9Wn+aanmoysVGRJAovI5PLydAqzh7l1mWDAUV2JQE8n5P3SORo3xTxOjMWrnNVvQChGZRpEqnWPQAAAABJRU5ErkJggg==";
/**
* Class for Prism blocks
* @constructor
*/
class JgPrismBlocks {
constructor(runtime) {
/**
* The runtime instantiating this block package.
* @type {Runtime}
*/
this.runtime = runtime;
this.audioPlayer = new Audio();
this.isJSPermissionGranted = false;
this.isCameraScreenshotEnabled = false;
this.mouseScrollDelta = { x: 0, y: 0, z: 0 };
addEventListener("wheel", e => {
this.mouseScrollDelta.x = e.deltaX;
this.mouseScrollDelta.y = e.deltaY;
this.mouseScrollDelta.z = e.deltaZ;
});
setInterval(() => {
this.mouseScrollDelta = { x: 0, y: 0, z: 0 };
}, 65);
this.encodeCharacterLength = 6;
}
/**
* dummy function for reseting user provided permisions when a save is loaded
*/
deserialize() {
this.isJSPermissionGranted = false;
this.isCameraScreenshotEnabled = false;
}
/**
* @returns {object} metadata for this extension and its blocks.
*/
getInfo() {
return {
id: 'jgPrism',
name: 'Prism',
color1: '#BC7FFF',
color2: '#AD66FF',
blocks: [
{
opcode: 'playAudioFromUrl',
text: formatMessage({
id: 'jgPrism.blocks.playAudioFromUrl',
default: 'play audio from [URL]',
description: 'Plays sound from a URL.'
}),
blockType: BlockType.COMMAND,
hideFromPalette: true,
arguments: {
URL: {
type: ArgumentType.STRING,
defaultValue: 'https://extensions.turbowarp.org/meow.mp3'
}
}
},
{
opcode: 'setAudioToLooping',
text: formatMessage({
id: 'jgPrism.blocks.setAudioToLooping',
default: 'set audio to loop',
description: 'Sets the audio to be looping.'
}),
hideFromPalette: true,
blockType: BlockType.COMMAND
},
{
opcode: 'setAudioToNotLooping',
text: formatMessage({
id: 'jgPrism.blocks.setAudioToNotLooping',
default: 'set audio to not loop',
description: 'Sets the audio to not be looping.'
}),
hideFromPalette: true,
blockType: BlockType.COMMAND
},
{
opcode: 'pauseAudio',
text: formatMessage({
id: 'jgPrism.blocks.pauseAudio',
default: 'pause audio',
description: 'Pauses the audio player.'
}),
hideFromPalette: true,
blockType: BlockType.COMMAND
},
{
opcode: 'playAudio',
text: formatMessage({
id: 'jgPrism.blocks.playAudio',
default: 'resume audio',
description: 'Resumes the audio player.'
}),
hideFromPalette: true,
blockType: BlockType.COMMAND
},
{
opcode: 'setAudioPlaybackSpeed',
text: formatMessage({
id: 'jgPrism.blocks.setAudioPlaybackSpeed',
default: 'set audio speed to [SPEED]%',
description: 'Sets the speed of the audio player.'
}),
hideFromPalette: true,
blockType: BlockType.COMMAND,
arguments: {
SPEED: {
type: ArgumentType.NUMBER,
defaultValue: 100
}
}
},
{
opcode: 'getAudioPlaybackSpeed',
text: formatMessage({
id: 'jgRuntime.blocks.getAudioPlaybackSpeed',
default: 'audio speed',
description: 'Block that returns the playback speed of the audio player.'
}),
hideFromPalette: true,
disableMonitor: false,
blockType: BlockType.REPORTER
},
{
opcode: 'setAudioPosition',
text: formatMessage({
id: 'jgPrism.blocks.setAudioPosition',
default: 'set audio position to [POSITION] seconds',
description: 'Sets the position of the current audio in the audio player.'
}),
blockType: BlockType.COMMAND,
hideFromPalette: true,
arguments: {
POSITION: {
type: ArgumentType.NUMBER,
defaultValue: 5
}
}
},
{
opcode: 'getAudioPosition',
text: 'audio position',
disableMonitor: false,
hideFromPalette: true,
blockType: BlockType.REPORTER
},
{
opcode: 'setAudioVolume',
text: formatMessage({
id: 'jgPrism.blocks.setAudioVolume',
default: 'set audio volume to [VOLUME]%',
description: 'Sets the volume of the current audio in the audio player.'
}),
blockType: BlockType.COMMAND,
hideFromPalette: true,
arguments: {
VOLUME: {
type: ArgumentType.NUMBER,
defaultValue: 100
}
}
},
{
opcode: 'getAudioVolume',
text: formatMessage({
id: 'jgRuntime.blocks.getAudioVolume',
default: 'audio volume',
description: 'Block that returns the volume of the audio player.'
}),
disableMonitor: false,
hideFromPalette: true,
blockType: BlockType.REPORTER
},
{
blockType: BlockType.LABEL,
text: "Data URIs"
},
{
opcode: 'screenshotStage',
text: formatMessage({
id: 'jgRuntime.blocks.screenshotStage',
default: 'screenshot the stage',
description: 'Block that screenshots the stage and returns a Data URI of it.'
}),
blockType: BlockType.REPORTER,
disableMonitor: true
},
{
opcode: 'dataUriOfCostume',
text: formatMessage({
id: 'jgRuntime.blocks.dataUriOfCostume',
default: 'data url of costume #[INDEX]',
description: 'Block that returns a Data URI of the costume at the index.'
}),
blockType: BlockType.REPORTER,
disableMonitor: true,
// these blocks will be replaced in the future
// hideFromPalette: true,
arguments: {
INDEX: {
type: ArgumentType.NUMBER,
defaultValue: "1"
}
}
},
{
opcode: 'dataUriFromImageUrl',
text: formatMessage({
id: 'jgRuntime.blocks.dataUriFromImageUrl',
default: 'data url of image at url: [URL]',
description: 'Block that returns a Data URI of the content fetched from the URL.'
}),
blockType: BlockType.REPORTER,
disableMonitor: true,
// these blocks will be replaced in the future
// hideFromPalette: true,
arguments: {
URL: {
type: ArgumentType.STRING,
defaultValue: "url"
}
}
},
{
opcode: 'dataUriFromArrayBuffer',
text: formatMessage({
id: 'jgRuntime.blocks.dataUriFromArrayBuffer',
default: 'convert array buffer [BUFFER] to data url',
description: 'Block that returns a Data URI from an array buffer.'
}),
blockType: BlockType.REPORTER,
disableMonitor: true,
arguments: {
BUFFER: {
type: ArgumentType.STRING,
defaultValue: "[72,101,108,108,111]"
}
}
},
{
opcode: 'arrayBufferFromDataUri',
text: formatMessage({
id: 'jgRuntime.blocks.arrayBufferFromDataUri',
default: 'convert data url [URL] to array buffer',
description: 'Block that returns an array buffer from a Data URL.'
}),
blockType: BlockType.REPORTER,
disableMonitor: true,
arguments: {
URL: {
type: ArgumentType.STRING,
defaultValue: "data:text/plain;base64,SGVsbG8="
}
}
},
// {
// blockType: BlockType.LABEL,
// text: "More Mouse Inputs"
// },
{
opcode: 'currentMouseScrollX',
text: formatMessage({
id: 'jgRuntime.blocks.currentMouseScrollX',
default: 'mouse scroll x',
description: 'im too lazy to write these anymore tbh'
}),
disableMonitor: false,
hideFromPalette: true,
blockIconURI: warningIcon,
blockType: BlockType.REPORTER
},
{
opcode: 'currentMouseScroll',
text: formatMessage({
id: 'jgRuntime.blocks.currentMouseScroll',
default: 'mouse scroll y',
description: 'im too lazy to write these anymore tbh'
}),
disableMonitor: false,
hideFromPalette: true,
blockIconURI: warningIcon,
blockType: BlockType.REPORTER
},
{
opcode: 'currentMouseScrollZ',
text: formatMessage({
id: 'jgRuntime.blocks.currentMouseScrollZ',
default: 'mouse scroll z',
description: 'im too lazy to write these anymore tbh'
}),
disableMonitor: false,
hideFromPalette: true,
blockIconURI: warningIcon,
blockType: BlockType.REPORTER
},
{
blockType: BlockType.LABEL,
text: "Base64"
},
{
opcode: 'base64Encode',
text: formatMessage({
id: 'jgRuntime.blocks.base64Encode',
default: 'base64 encode [TEXT]',
description: 'Block that encodes and returns the result of it.'
}),
blockType: BlockType.REPORTER,
disableMonitor: true,
arguments: {
TEXT: {
type: ArgumentType.STRING,
defaultValue: "abc"
}
}
},
{
opcode: 'base64Decode',
text: formatMessage({
id: 'jgRuntime.blocks.base64Decode',
default: 'base64 decode [TEXT]',
description: 'Block that decodes and returns the result of it.'
}),
blockType: BlockType.REPORTER,
disableMonitor: true,
arguments: {
TEXT: {
type: ArgumentType.STRING,
defaultValue: "YWJj"
}
}
},
// {
// blockType: BlockType.LABEL,
// text: "String Character Codes"
// },
{
opcode: 'fromCharacterCodeString',
text: formatMessage({
id: 'jgRuntime.blocks.fromCharacterCodeString',
default: 'character from character code [TEXT]',
description: 'Block that decodes and returns the result of it.'
}),
blockType: BlockType.REPORTER,
disableMonitor: true,
hideFromPalette: true,
blockIconURI: warningIcon,
arguments: {
TEXT: {
type: ArgumentType.NUMBER,
defaultValue: 97
}
}
},
{
opcode: 'toCharacterCodeString',
text: formatMessage({
id: 'jgRuntime.blocks.toCharacterCodeString',
default: 'character code of [TEXT]',
description: 'Block that encodes and returns the result of it.'
}),
blockType: BlockType.REPORTER,
disableMonitor: true,
hideFromPalette: true,
blockIconURI: warningIcon,
arguments: {
TEXT: {
type: ArgumentType.STRING,
defaultValue: "a"
}
}
},
"---",
"---",
{
blockType: BlockType.LABEL,
text: "JS Deflate by BeatGammit"
},
{
opcode: 'lib_deflate_deflateArray',
text: formatMessage({
id: 'jgRuntime.blocks.lib_deflate_deflateArray',
default: 'deflate [ARRAY]',
description: 'abc'
}),
blockType: BlockType.REPORTER,
disableMonitor: true,
arguments: {
ARRAY: {
type: ArgumentType.STRING,
defaultValue: "[]"
}
}
},
{
opcode: 'lib_deflate_inflateArray',
text: formatMessage({
id: 'jgRuntime.blocks.lib_deflate_inflateArray',
default: 'inflate [ARRAY]',
description: 'abc'
}),
blockType: BlockType.REPORTER,
disableMonitor: true,
arguments: {
ARRAY: {
type: ArgumentType.STRING,
defaultValue: "[]"
}
}
},
{
blockType: BlockType.LABEL,
text: "Numerical Encoding by cs2627883"
},
{
opcode: 'NumericalEncode',
blockType: BlockType.REPORTER,
text: 'encode [DATA] to number',
arguments: {
DATA: {
type: ArgumentType.STRING,
defaultValue: 'Hello!'
}
}
},
{
opcode: 'NumericalDecode',
blockType: BlockType.REPORTER,
text: 'decode [ENCODED] from number',
arguments: {
ENCODED: {
type: ArgumentType.STRING,
defaultValue: '000072000101000108000108000111000033'
}
}
},
// "---",
// {
// blockType: BlockType.LABEL,
// text: "Deprecated blocks"
// },
// "---",
// {
// blockType: BlockType.LABEL,
// text: "Don't use any of the blocks below."
// },
// {
// blockType: BlockType.LABEL,
// text: "They will be removed soon."
// },
// "---",
// {
// blockType: BlockType.LABEL,
// text: "(Deprecated) JavaScript"
// },
{
opcode: 'evaluate',
text: formatMessage({
id: 'jgRuntime.blocks.evaluate',
default: 'eval [JAVASCRIPT]',
description: 'Block that runs JavaScript code.'
}),
blockType: BlockType.COMMAND,
blockIconURI: warningIcon,
hideFromPalette: true,
arguments: {
JAVASCRIPT: {
type: ArgumentType.STRING,
defaultValue: "console.log('Hello!')"
}
}
},
{
opcode: 'evaluate2',
text: formatMessage({
id: 'jgRuntime.blocks.evaluate2',
default: 'eval [JAVASCRIPT]',
description: 'Block that runs JavaScript code and returns the result of it.'
}),
blockType: BlockType.REPORTER,
disableMonitor: true,
blockIconURI: warningIcon,
hideFromPalette: true,
arguments: {
JAVASCRIPT: {
type: ArgumentType.STRING,
defaultValue: "Math.random()"
}
}
},
{
opcode: 'evaluate3',
text: formatMessage({
id: 'jgRuntime.blocks.evaluate3',
default: 'eval [JAVASCRIPT]',
description: 'Block that runs JavaScript code.'
}),
blockType: BlockType.HAT,
blockIconURI: warningIcon,
hideFromPalette: true,
arguments: {
JAVASCRIPT: {
type: ArgumentType.STRING,
defaultValue: "Math.round(Math.random()) == 1"
}
}
}
]
};
}
playAudioFromUrl(args) {
if (!this.audioPlayer) this.audioPlayer = new Audio();
this.audioPlayer.pause();
this.audioPlayer.src = `${args.URL}`;
this.audioPlayer.currentTime = 0;
this.audioPlayer.play();
}
setAudioToLooping() {
this.audioPlayer.loop = true;
}
setAudioToNotLooping() {
this.audioPlayer.loop = false;
}
pauseAudio() {
this.audioPlayer.pause();
}
playAudio() {
this.audioPlayer.play();
}
setAudioPlaybackSpeed(args) {
this.audioPlayer.playbackRate = (isNaN(Number(args.SPEED)) ? 100 : Number(args.SPEED)) / 100;
}
getAudioPlaybackSpeed() {
return this.audioPlayer.playbackRate * 100;
}
setAudioPosition(args) {
this.audioPlayer.currentTime = isNaN(Number(args.POSITION)) ? 0 : Number(args.POSITION);
}
getAudioPosition() {
return this.audioPlayer.currentTime;
}
setAudioVolume(args) {
this.audioPlayer.volume = (isNaN(Number(args.VOLUME)) ? 100 : Number(args.VOLUME)) / 100;
}
getAudioVolume() {
return this.audioPlayer.volume * 100;
}
// eslint-disable-next-line no-unused-vars
evaluate(args, util, realBlockInfo) {
return new Promise((resolve, reject) => {
// if (!(this.isJSPermissionGranted)) {
// this.isJSPermissionGranted = ProjectPermissionManager.RequestPermission("javascript");
// if (!this.isJSPermissionGranted) return;
// }
// // otherwise
SandboxRunner.execute(String(args.JAVASCRIPT)).then(result => {
if (!result.success) {
alert(result.value);
console.error(result.value);
return;
}
resolve(result.value)
})
})
}
// eslint-disable-next-line no-unused-vars
evaluate2(args, util, realBlockInfo) {
return new Promise((resolve, reject) => {
// if (!(this.isJSPermissionGranted)) {
// this.isJSPermissionGranted = ProjectPermissionManager.RequestPermission("javascript");
// if (!this.isJSPermissionGranted) return "";
// }
// // otherwise
SandboxRunner.execute(String(args.JAVASCRIPT)).then(result => {
if (!result.success) {
console.error(result.value);
}
resolve(result.value)
})
// let result = "";
// try {
// // eslint-disable-next-line no-eval
// result = eval(String(args.JAVASCRIPT));
// } catch (e) {
// result = e;
// console.error(e);
// }
// return result;
// return "";
})
}
// eslint-disable-next-line no-unused-vars
evaluate3(args, util, realBlockInfo) {
return new Promise((resolve, reject) => {
SandboxRunner.execute(String(args.JAVASCRIPT)).then(result => {
if (!result.success) {
console.error(result.value);
}
resolve(result.value === true)
})
})
// if (!(this.isJSPermissionGranted)) {
// this.isJSPermissionGranted = ProjectPermissionManager.RequestPermission("javascript");
// if (!this.isJSPermissionGranted) return false;
// }
// // otherwise
// let result = true;
// try {
// // eslint-disable-next-line no-eval
// result = eval(String(args.JAVASCRIPT));
// } catch (e) {
// result = false;
// console.error(e);
// }
// // // otherwise
// return result === true;
// return false;
}
screenshotStage() {
// should we look for an external canvas
if (this.runtime.prism_screenshot_checkForExternalCanvas) {
// if so, does one exist (this will check for more than 1 in the future)
if (this.runtime.prism_screenshot_externalCanvas) {
// we dont need to check camera permissions since external canvases
// will never have the ability to get camera data
return this.runtime.prism_screenshot_externalCanvas.toDataURL();
}
}
// DO NOT REMOVE, USER HAS NOT GIVEN PERMISSION TO SAVE CAMERA IMAGES.
if (this.runtime.ext_videoSensing || this.runtime.ioDevices.video.provider.enabled) {
// user's camera is on, ask for permission to take a picture of them
if (!(this.isCameraScreenshotEnabled)) {
this.isCameraScreenshotEnabled = ProjectPermissionManager.RequestPermission("cameraPictures");
if (!this.isCameraScreenshotEnabled) return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAMSURBVBhXY/j//z8ABf4C/qc1gYQAAAAASUVORK5CYII="; // 1 pixel of white
}
}
return new Promise(resolve => {
vm.renderer.requestSnapshot(uri => {
resolve(uri);
});
});
}
dataUriOfCostume(args, util) {
const index = Number(args.INDEX);
if (isNaN(index)) return "";
if (index < 1) return "";
const target = util.target;
// eslint-disable-next-line no-undefined
if (target.sprite.costumes[index - 1] === undefined || target.sprite.costumes[index - 1] === null) return "";
const dataURI = target.sprite.costumes[index - 1].asset.encodeDataURI();
return String(dataURI);
}
dataUriFromImageUrl(args) {
return new Promise(resolve => {
if (window && !window.FileReader) return resolve("");
if (window && !window.fetch) return resolve("");
fetch(`https://api.allorigins.win/raw?url=${encodeURIComponent(String(args.URL))}`).then(r => {
r.blob().then(blob => {
const reader = new FileReader();
reader.onload = e => {
resolve(e.target.result);
};
reader.readAsDataURL(blob);
})
.catch(() => {
resolve("");
});
})
.catch(() => {
resolve("");
});
});
}
dataUriFromArrayBuffer(args) {
const array = validateArray(args.BUFFER);
if (!array.isValid) return 'data:text/plain;base64,';
const buffer = BufferParser.arrayToBuffer(array.array);
let binary = '';
let bytes = new Uint8Array(buffer);
let len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
// use "application/octet-stream", we have no idea what the buffer actually contains
return `data:application/octet-stream;base64,${btoa(binary)}`;
}
arrayBufferFromDataUri(args) {
const dataUrl = Cast.toString(args.URL);
return new Promise((resolve) => {
fetch(dataUrl).then(res => {
res.arrayBuffer().then(buffer => {
const array = BufferParser.bufferToArray(buffer);
resolve(JSON.stringify(array));
}).catch(() => {
resolve('[]');
});
}).catch(() => {
resolve('[]');
});
});
}
currentMouseScrollX() {
return this.mouseScrollDelta.x;
}
currentMouseScroll() {
return this.mouseScrollDelta.y;
}
currentMouseScrollZ() {
return this.mouseScrollDelta.z;
}
base64Encode(args) {
let result = "";
try {
result = btoa(String(args.TEXT));
} catch {
// what a shame
}
return result;
}
base64Decode(args) {
let result = "";
try {
result = atob(String(args.TEXT));
} catch {
// what a shame
}
return result;
}
fromCharacterCodeString(args) {
return String.fromCharCode(args.TEXT);
}
toCharacterCodeString(args) {
return String(args.TEXT).charCodeAt(0);
}
lib_deflate_deflateArray(args) {
const array = validateArray(args.ARRAY).array;
return JSON.stringify(beatgammit.deflate(array));
}
lib_deflate_inflateArray(args) {
const array = validateArray(args.ARRAY).array;
return JSON.stringify(beatgammit.inflate(array));
}
NumericalEncode(args) {
const toencode = String(args.DATA);
let encoded = "";
for (let i = 0; i < toencode.length; ++i) {
// Get char code of character
let encodedchar = String(toencode.charCodeAt(i));
// Pad encodedchar with 0s to ensure all encodedchars are the same length
encodedchar = "0".repeat(this.encodeCharacterLength - encodedchar.length) + encodedchar;
encoded += encodedchar;
}
return encoded;
}
NumericalDecode(args) {
const todecode = String(args.ENCODED);
if (todecode == "") {
return "";
}
let decoded = "";
// Create regex to split by char length
const regex = new RegExp('.{1,' + this.encodeCharacterLength + '}', 'g');
// Split into array of characters
let encodedchars = todecode.match(regex);
for (let i = 0; i < encodedchars.length; i++) {
// Get character from char code
let decodedchar = String.fromCharCode(encodedchars[i]);
decoded += decodedchar;
}
return decoded;
}
}
module.exports = JgPrismBlocks;