From 84053a41420782257803daf1183a955e35775bbc Mon Sep 17 00:00:00 2001 From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com> Date: Sat, 22 Jul 2023 22:53:07 +0100 Subject: [PATCH 01/17] DT/cameracontrols: minor fixes (#768) - Set cameraBG value - Disabled interpolation when loading --- extensions/DT/cameracontrols.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/DT/cameracontrols.js b/extensions/DT/cameracontrols.js index 0c5f0cc4ab..960dca6c99 100644 --- a/extensions/DT/cameracontrols.js +++ b/extensions/DT/cameracontrols.js @@ -19,6 +19,7 @@ vm.runtime.runtimeOptions.fencing = false; vm.renderer.offscreenTouching = true; + vm.setInterpolation(false); function updateCamera(x = cameraX, y = cameraY, scale = cameraZoom / 100, rot = -cameraDirection + 90) { rot = rot / 180 * Math.PI; @@ -347,6 +348,7 @@ setCol(args, util) { const rgb = Scratch.Cast.toRgbColorList(args.val); Scratch.vm.renderer.setBackgroundColor(rgb[0] / 255, rgb[1] / 255, rgb[2] / 255); + cameraBG = args.val; } getCol() { return cameraBG; From d4c4678fcb89c86d941b1d3cf6eba919e5842aac Mon Sep 17 00:00:00 2001 From: NexusKitten <127152751+NexusKitten@users.noreply.github.com> Date: Sun, 23 Jul 2023 18:06:22 -0400 Subject: [PATCH 02/17] Add NexusKitten/moremotion extension (#732) --- extensions/NexusKitten/moremotion.js | 360 +++++++++++++++++++++++++++ website/index.ejs | 6 + 2 files changed, 366 insertions(+) create mode 100644 extensions/NexusKitten/moremotion.js diff --git a/extensions/NexusKitten/moremotion.js b/extensions/NexusKitten/moremotion.js new file mode 100644 index 0000000000..fef57e9f43 --- /dev/null +++ b/extensions/NexusKitten/moremotion.js @@ -0,0 +1,360 @@ +(function(Scratch) { + 'use strict'; + + if (!Scratch.extensions.unsandboxed) { + throw new Error('More Motion must run unsandboxed'); + } + + class nkmoremotion { + getInfo() { + return { + id: 'nkmoremotion', + name: 'More Motion', + color1: '#4c97ff', + color2: '#3373cc', + blocks: [ + { + filter: [Scratch.TargetType.STAGE], + blockType: Scratch.BlockType.LABEL, + text: 'Stage selected: no motion blocks' + }, + { + filter: [Scratch.TargetType.SPRITE], + opcode: 'changexy', + blockType: Scratch.BlockType.COMMAND, + text: 'change x: [X] y: [Y]', + arguments: { + X: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '0' + }, + Y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '0' + } + } + }, + { + filter: [Scratch.TargetType.SPRITE], + opcode: 'pointto', + blockType: Scratch.BlockType.COMMAND, + text: 'point towards x: [X] y: [Y]', + arguments: { + X: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '0' + }, + Y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '0' + } + } + }, + { + filter: [Scratch.TargetType.SPRITE], + opcode: 'rotationStyle', + blockType: Scratch.BlockType.REPORTER, + text: 'rotation style', + disableMonitor: true + }, + '---', + { + filter: [Scratch.TargetType.SPRITE], + opcode: 'fence', + blockType: Scratch.BlockType.COMMAND, + text: 'manually fence' + }, + '---', + { + filter: [Scratch.TargetType.SPRITE], + opcode: 'steptowards', + blockType: Scratch.BlockType.COMMAND, + text: 'move [STEPS] steps towards x: [X] y: [Y]', + arguments: { + STEPS: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '10' + }, + X: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '0' + }, + Y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '0' + } + } + }, + { + filter: [Scratch.TargetType.SPRITE], + opcode: 'tweentowards', + blockType: Scratch.BlockType.COMMAND, + text: 'move [PERCENT]% of the way to x: [X] y: [Y]', + arguments: { + PERCENT: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '10' + }, + X: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '0' + }, + Y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '0' + } + } + }, + '---', + { + filter: [Scratch.TargetType.SPRITE], + opcode: 'directionto', + blockType: Scratch.BlockType.REPORTER, + text: 'direction to x: [X] y: [Y]', + arguments: { + X: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '0' + }, + Y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '0' + } + } + }, + { + filter: [Scratch.TargetType.SPRITE], + opcode: 'distanceto', + blockType: Scratch.BlockType.REPORTER, + text: 'distance from x: [X] y: [Y]', + arguments: { + X: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '0' + }, + Y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '0' + } + } + }, + { + filter: [Scratch.TargetType.SPRITE], + opcode: 'spritewh', + blockType: Scratch.BlockType.REPORTER, + text: 'sprite [WHAT]', + disableMonitor: true, + arguments: { + WHAT: { + type: Scratch.ArgumentType.STRING, + menu: 'WHAT' + } + } + }, + '---', + { + filter: [Scratch.TargetType.SPRITE], + opcode: 'touchingxy', + blockType: Scratch.BlockType.BOOLEAN, + text: 'touching x: [X] y: [Y]?', + arguments: { + X: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '0' + }, + Y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '0' + } + } + }, + { + filter: [Scratch.TargetType.SPRITE], + opcode: 'touchingrect', + blockType: Scratch.BlockType.BOOLEAN, + text: 'touching rectangle x1: [X1] y1: [Y1] x2: [X2] y2: [Y2]?', + arguments: { + X1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '-100' + }, + Y1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '-100' + }, + X2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '100' + }, + Y2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '100' + } + } + }, + ], + menus: { + WHAT: { + acceptreporters: true, + items: [ + 'width', + 'height', + 'costume width', + 'costume height' + ] + } + } + }; + } + + changexy(args, util) { + const x = Scratch.Cast.toNumber(args.X); + const y = Scratch.Cast.toNumber(args.Y); + util.target.setXY(util.target.x + x, util.target.y + y); + } + + // LORAX APPROVED + pointto(args, util) { + const x = Scratch.Cast.toNumber(args.X); + const y = Scratch.Cast.toNumber(args.Y); + if (util.target.y > y) { + util.target.setDirection(((180 / Math.PI) * Math.atan((x - util.target.x) / (y - util.target.y))) + 180); + } else { + util.target.setDirection(((180 / Math.PI) * Math.atan((x - util.target.x) / (y - util.target.y)))); + } + } + + rotationStyle(args, util) { + return util.target.rotationStyle; + } + + fence(args, util) { + const newpos = Scratch.vm.renderer.getFencedPositionOfDrawable(util.target.drawableID, [util.target.x, util.target.y]); + util.target.setXY(newpos[0], newpos[1]); + } + + directionto(args, util) { + const x = Scratch.Cast.toNumber(args.X); + const y = Scratch.Cast.toNumber(args.Y); + if (util.target.y > y) { + return ((180 / Math.PI) * Math.atan((x - util.target.x) / (y - util.target.y))) + 180; + } else { + return ((180 / Math.PI) * Math.atan((x - util.target.x) / (y - util.target.y))); + } + } + + distanceto(args, util) { + const x = Scratch.Cast.toNumber(args.X); + const y = Scratch.Cast.toNumber(args.Y); + // Shoutout to Pythagoras! + return Math.sqrt(((x - util.target.x) ** 2) + ((y - util.target.y) ** 2)); + } + + steptowards(args, util) { + const x = Scratch.Cast.toNumber(args.X); + const y = Scratch.Cast.toNumber(args.Y); + const steps = Scratch.Cast.toNumber(args.STEPS); + const val = steps / (Math.sqrt(((x - util.target.x) ** 2) + ((y - util.target.y) ** 2))); + if (val >= 1) { + util.target.setXY(x, y); + } else { + util.target.setXY(((x - util.target.x) * (val)) + util.target.x, ((y - util.target.y) * (val)) + util.target.y); + } + } + + tweentowards(args, util) { + const x = Scratch.Cast.toNumber(args.X); + const y = Scratch.Cast.toNumber(args.Y); + const val = Scratch.Cast.toNumber(args.PERCENT); + // Essentially a smooth glide script. + util.target.setXY(((x - util.target.x) * (val / 100)) + util.target.x, ((y - util.target.y) * (val / 100)) + util.target.y); + } + + touchingrect(args, util) { + let left = Scratch.Cast.toNumber(args.X1); + let right = Scratch.Cast.toNumber(args.X2); + let bottom = Scratch.Cast.toNumber(args.Y1); + let top = Scratch.Cast.toNumber(args.Y2); + + // Fix argument order if they got it backwards + if (left > right) { + let temp = left; + left = right; + right = temp; + } + if (bottom > top) { + let temp = bottom; + bottom = top; + bottom = temp; + } + + const drawable = Scratch.vm.renderer._allDrawables[util.target.drawableID]; + if (!drawable) { + return false; + } + + // See renderer.isTouchingDrawables + + const drawableBounds = drawable.getFastBounds(); + drawableBounds.snapToInt(); + + // This is bad, need to rewrite this when renderer exports Rectangle + const Rectangle = Object.getPrototypeOf(drawableBounds).constructor; + + /** @type {RenderWebGL.Rectangle} */ + const containsBounds = new Rectangle(); + containsBounds.initFromBounds(left, right, bottom, top); + containsBounds.snapToInt(); + + if (!containsBounds.intersects(drawableBounds)) { + return false; + } + + drawable.updateCPURenderAttributes(); + + /** @type {RenderWebGL.Rectangle} */ + const intersectingBounds = Rectangle.intersect(drawableBounds, containsBounds); + for (let x = intersectingBounds.left; x < intersectingBounds.right; x++) { + for (let y = intersectingBounds.bottom; y < intersectingBounds.top; y++) { + // technically should be a twgl vec3, but does not actually need to be + if (drawable.isTouching([x, y])) { + return true; + } + } + } + return false; + } + + touchingxy(args, util) { + const x = Scratch.Cast.toNumber(args.X); + const y = Scratch.Cast.toNumber(args.Y); + const drawable = Scratch.vm.renderer._allDrawables[util.target.drawableID]; + if (!drawable) { + return false; + } + // Position should technically be a twgl vec3, but it doesn't actually need to be + drawable.updateCPURenderAttributes(); + return drawable.isTouching([x, y]); + } + + spritewh(args, util) { + if (args.WHAT === 'width' || args.WHAT === 'height') { + const bounds = Scratch.vm.renderer.getBounds(util.target.drawableID); + if (args.WHAT === 'width') { + return Math.ceil(bounds.width); + } else { + return Math.ceil(bounds.height); + } + } else if (args.WHAT === 'costume width' || args.WHAT === 'costume height') { + const costume = util.target.sprite.costumes[util.target.currentCostume]; + if (args.WHAT === 'costume width') { + return Math.ceil(costume.size[0]); + } else { + return Math.ceil(costume.size[1]); + } + } + } + } + + Scratch.extensions.register(new nkmoremotion()); +})(Scratch); diff --git a/website/index.ejs b/website/index.ejs index c785b75872..5001a74b61 100644 --- a/website/index.ejs +++ b/website/index.ejs @@ -506,6 +506,12 @@

Expands upon the looks category, allowing you to show/hide, get costume data and edit SVG skins on sprites. Created by LilyMakesThings.

+
+ <%- banner('NexusKitten/moremotion') %> +

More Motion

+

More motion-related blocks. Created by NamelessCat.

+
+
<%- banner('navigator') %>

Navigator

From 378f3e9605514f76eab7a2177bdb222b2990aa1d Mon Sep 17 00:00:00 2001 From: TheShovel <68913917+TheShovel@users.noreply.github.com> Date: Mon, 24 Jul 2023 01:10:33 +0300 Subject: [PATCH 03/17] TheShovel/CanvasEffects: add more effects (#747) - Sepia - Transparency - Scale - SkewX - SkewY - Offset X - Offset Y - Rotation - Border radius --- extensions/TheShovel/CanvasEffects.js | 74 +++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/extensions/TheShovel/CanvasEffects.js b/extensions/TheShovel/CanvasEffects.js index 4d53b09cc2..ed9c7552e0 100644 --- a/extensions/TheShovel/CanvasEffects.js +++ b/extensions/TheShovel/CanvasEffects.js @@ -7,21 +7,40 @@ const canvas = Scratch.renderer.canvas; const updateStyle = () => { - const filter = `blur(${blur}px) contrast(${contrast / 100}) saturate(${saturation}%) hue-rotate(${color}deg) brightness(${brightness}%) invert(${invert}%)`; + // Gotta keep the translation to % because of the stage size, window size and so on + const transform = `rotate(${rotation}deg) scale(${scale}%) skew(${skewX}deg, ${skewY}deg) translate(${offsetX}%, ${0 - offsetY}%)`; + if (canvas.style.transform !== transform) { + canvas.style.transform = transform; + } + const filter = `blur(${blur}px) contrast(${contrast / 100}) saturate(${saturation}%) hue-rotate(${color}deg) brightness(${brightness}%) invert(${invert}%) sepia(${sepia}%) opacity(${100 - transparency}%)`; if (canvas.style.filter !== filter) { canvas.style.filter = filter; } + const cssBorderRadius = borderRadius === 0 ? '' : `${borderRadius}%`; + if (canvas.style.borderRadius !== cssBorderRadius) { + canvas.style.borderRadius = cssBorderRadius; + } const imageRendering = resizeMode === 'pixelated' ? 'pixelated' : ''; if (canvas.style.imageRendering !== imageRendering) { canvas.style.imageRendering = imageRendering; } }; - // scratch-gui will sometimes reset the cursor when resizing the window or going in/out of fullscreen + // scratch-gui may reset canvas styles when resizing the window or going in/out of fullscreen new MutationObserver(updateStyle).observe(canvas, { attributeFilter: ['style'], attributes: true }); + let borderRadius = 0; + let rotation = 0; + let offsetY = 0; + let offsetX = 0; + let skewY = 0; + let skewX = 0; + let scale = 100; + // Thanks SharkPool for telling me about these + let transparency = 0; + let sepia = 0; let blur = 0; let contrast = 100; let saturation = 100; @@ -96,7 +115,7 @@ menus: { EFFECTMENU: { acceptReporters: true, - items: ['blur', 'contrast', 'saturation', 'color shift', 'brightness', 'invert'] + items: ['blur', 'contrast', 'saturation', 'color shift', 'brightness', 'invert', 'sepia', 'transparency', 'scale', 'skew X', 'skew Y', 'offset X', 'offset Y', 'rotation', 'border radius'] }, RENDERMODE: { acceptReporters: true, @@ -104,7 +123,8 @@ }, EFFECTGETMENU: { acceptReporters: true, - items: ['blur', 'contrast', 'saturation', 'color shift', 'brightness', 'invert', 'resize rendering mode'] + // this contains 'resize rendering mode', EFFECTMENU does not + items: ['blur', 'contrast', 'saturation', 'color shift', 'brightness', 'invert', 'resize rendering mode', 'sepia', 'transparency', 'scale', 'skew X', 'skew Y', 'offset X', 'offset Y', 'rotation', 'border radius'] } } }; @@ -124,10 +144,29 @@ return invert; } else if (EFFECT === 'resize rendering mode') { return resizeMode; + } else if (EFFECT === 'sepia') { + return sepia; + } else if (EFFECT === 'transparency') { + return transparency; + } else if (EFFECT === 'scale') { + return scale; + } else if (EFFECT === 'skew X') { + return skewX; + } else if (EFFECT === 'skew Y') { + return skewY; + } else if (EFFECT === 'offset X') { + return offsetX; + } else if (EFFECT === 'offset Y') { + return offsetY; + } else if (EFFECT === 'rotation') { + return rotation; + } else if (EFFECT === 'border radius') { + return borderRadius; } return ''; } seteffect({EFFECT, NUMBER}) { + NUMBER = Scratch.Cast.toNumber(NUMBER); if (EFFECT === 'blur') { blur = NUMBER; } else if (EFFECT === 'contrast') { @@ -140,10 +179,37 @@ brightness = NUMBER; } else if (EFFECT === 'invert') { invert = NUMBER; + } else if (EFFECT === 'sepia') { + sepia = NUMBER; + } else if (EFFECT === 'transparency') { + transparency = NUMBER; + } else if (EFFECT === 'scale') { + scale = NUMBER; + } else if (EFFECT === 'skew X') { + skewX = NUMBER; + } else if (EFFECT === 'skew Y') { + skewY = NUMBER; + } else if (EFFECT === 'offset X') { + offsetX = NUMBER; + } else if (EFFECT === 'offset Y') { + offsetY = NUMBER; + } else if (EFFECT === 'rotation') { + rotation = NUMBER; + } else if (EFFECT === 'border radius') { + borderRadius = NUMBER; } updateStyle(); } cleareffects() { + borderRadius = 0; + rotation = 0; + offsetY = 0; + offsetX = 0; + skewY = 0; + skewX = 0; + scale = 100; + transparency = 0; + sepia = 0; blur = 0; contrast = 100; saturation = 100; From 36f80eab99ab72863da714b7dc572f114699841f Mon Sep 17 00:00:00 2001 From: CST1229 <68464103+CST1229@users.noreply.github.com> Date: Thu, 27 Jul 2023 23:46:59 +0200 Subject: [PATCH 04/17] CST1229/zip: fix compression level menu (#791) --- extensions/CST1229/zip.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/extensions/CST1229/zip.js b/extensions/CST1229/zip.js index 1be08830d0..07091f33d8 100644 --- a/extensions/CST1229/zip.js +++ b/extensions/CST1229/zip.js @@ -92,7 +92,7 @@ }, COMPRESSION: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 6, + defaultValue: "6", menu: "compressionLevel", }, }, @@ -326,16 +326,16 @@ compressionLevel: { acceptReporters: true, items: [ - { text: "no compression (fastest)", value: 0 }, - { text: "1 (fast, large)", value: 1 }, - { text: "2", value: 2 }, - { text: "3", value: 3 }, - { text: "4", value: 4 }, - { text: "5", value: 5 }, - { text: "6", value: 6 }, - { text: "7", value: 7 }, - { text: "8", value: 8 }, - { text: "9 (slowest, smallest)", value: 9 }, + { text: "no compression (fastest)", value: "0" }, + { text: "1 (fast, large)", value: "1" }, + { text: "2", value: "2" }, + { text: "3", value: "3" }, + { text: "4", value: "4" }, + { text: "5", value: "5" }, + { text: "6", value: "6" }, + { text: "7", value: "7" }, + { text: "8", value: "8" }, + { text: "9 (slowest, smallest)", value: "9" }, ], }, fileMeta: { From 54cd0d19f874fef594f5386748ba5c1be9ea9baa Mon Sep 17 00:00:00 2001 From: Obvious Alex C <76855369+David-Orangemoon@users.noreply.github.com> Date: Thu, 27 Jul 2023 22:56:00 -0400 Subject: [PATCH 05/17] obviousAlexC/newgroundsIO: fix API problems (#792) --- extensions/obviousAlexC/newgroundsIO.js | 1027 ++++++++++++++++++++--- 1 file changed, 914 insertions(+), 113 deletions(-) diff --git a/extensions/obviousAlexC/newgroundsIO.js b/extensions/obviousAlexC/newgroundsIO.js index f40aec5e52..985627460c 100644 --- a/extensions/obviousAlexC/newgroundsIO.js +++ b/extensions/obviousAlexC/newgroundsIO.js @@ -1,7 +1,7 @@ (function (Scratch) { "use strict"; - /*! + /*! The following code is from NewgroundsIO-JS The original code is available at https://github.com/PsychoGoldfishNG/NewgroundsIO-JS @@ -30,6 +30,8 @@ */ /* eslint-disable */ + //Newgrounds.io JS library taken from https://github.com/PsychoGoldfishNG/NewgroundsIO-JS/blob/main/dist/NewgroundsIO.js the official Github for Newgrounds.io + /* ====================== ./NewgroundsIO-JS/src/NGIO.js ====================== */ /** Start Class NGIO **/ @@ -1530,38 +1532,829 @@ (c) 2009-2013 by Jeff Mott. All rights reserved. code.google.com/p/crypto-js/wiki/License */ - /* eslint-disable */ - var CryptoJS=CryptoJS||function(u,p){var d={},l=d.lib={},s=function(){},t=l.Base={extend:function(a){s.prototype=this;var c=new s;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}, - r=l.WordArray=t.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=p?c:4*a.length},toString:function(a){return(a||v).stringify(this)},concat:function(a){var c=this.words,e=a.words,j=this.sigBytes;a=a.sigBytes;this.clamp();if(j%4)for(var k=0;k>>2]|=(e[k>>>2]>>>24-8*(k%4)&255)<<24-8*((j+k)%4);else if(65535>>2]=e[k>>>2];else c.push.apply(c,e);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< - 32-8*(c%4);a.length=u.ceil(c/4)},clone:function(){var a=t.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],e=0;e>>2]>>>24-8*(j%4)&255;e.push((k>>>4).toString(16));e.push((k&15).toString(16))}return e.join("")},parse:function(a){for(var c=a.length,e=[],j=0;j>>3]|=parseInt(a.substr(j, - 2),16)<<24-4*(j%8);return new r.init(e,c/2)}},b=w.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var e=[],j=0;j>>2]>>>24-8*(j%4)&255));return e.join("")},parse:function(a){for(var c=a.length,e=[],j=0;j>>2]|=(a.charCodeAt(j)&255)<<24-8*(j%4);return new r.init(e,c)}},x=w.Utf8={stringify:function(a){try{return decodeURIComponent(escape(b.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return b.parse(unescape(encodeURIComponent(a)))}}, - q=l.BufferedBlockAlgorithm=t.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=x.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,e=c.words,j=c.sigBytes,k=this.blockSize,b=j/(4*k),b=a?u.ceil(b):u.max((b|0)-this._minBufferSize,0);a=b*k;j=u.min(4*a,j);if(a){for(var q=0;q>>2]>>>24-8*(r%4)&255)<<16|(l[r+1>>>2]>>>24-8*((r+1)%4)&255)<<8|l[r+2>>>2]>>>24-8*((r+2)%4)&255,v=0;4>v&&r+0.75*v>>6*(3-v)&63));if(l=t.charAt(64))for(;d.length%4;)d.push(l);return d.join("")},parse:function(d){var l=d.length,s=this._map,t=s.charAt(64);t&&(t=d.indexOf(t),-1!=t&&(l=t));for(var t=[],r=0,w=0;w< - l;w++)if(w%4){var v=s.indexOf(d.charAt(w-1))<<2*(w%4),b=s.indexOf(d.charAt(w))>>>6-2*(w%4);t[r>>>2]|=(v|b)<<24-8*(r%4);r++}return p.create(t,r)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})(); - (function(u){function p(b,n,a,c,e,j,k){b=b+(n&a|~n&c)+e+k;return(b<>>32-j)+n}function d(b,n,a,c,e,j,k){b=b+(n&c|a&~c)+e+k;return(b<>>32-j)+n}function l(b,n,a,c,e,j,k){b=b+(n^a^c)+e+k;return(b<>>32-j)+n}function s(b,n,a,c,e,j,k){b=b+(a^(n|~c))+e+k;return(b<>>32-j)+n}for(var t=CryptoJS,r=t.lib,w=r.WordArray,v=r.Hasher,r=t.algo,b=[],x=0;64>x;x++)b[x]=4294967296*u.abs(u.sin(x+1))|0;r=r.MD5=v.extend({_doReset:function(){this._hash=new w.init([1732584193,4023233417,2562383102,271733878])}, - _doProcessBlock:function(q,n){for(var a=0;16>a;a++){var c=n+a,e=q[c];q[c]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360}var a=this._hash.words,c=q[n+0],e=q[n+1],j=q[n+2],k=q[n+3],z=q[n+4],r=q[n+5],t=q[n+6],w=q[n+7],v=q[n+8],A=q[n+9],B=q[n+10],C=q[n+11],u=q[n+12],D=q[n+13],E=q[n+14],x=q[n+15],f=a[0],m=a[1],g=a[2],h=a[3],f=p(f,m,g,h,c,7,b[0]),h=p(h,f,m,g,e,12,b[1]),g=p(g,h,f,m,j,17,b[2]),m=p(m,g,h,f,k,22,b[3]),f=p(f,m,g,h,z,7,b[4]),h=p(h,f,m,g,r,12,b[5]),g=p(g,h,f,m,t,17,b[6]),m=p(m,g,h,f,w,22,b[7]), - f=p(f,m,g,h,v,7,b[8]),h=p(h,f,m,g,A,12,b[9]),g=p(g,h,f,m,B,17,b[10]),m=p(m,g,h,f,C,22,b[11]),f=p(f,m,g,h,u,7,b[12]),h=p(h,f,m,g,D,12,b[13]),g=p(g,h,f,m,E,17,b[14]),m=p(m,g,h,f,x,22,b[15]),f=d(f,m,g,h,e,5,b[16]),h=d(h,f,m,g,t,9,b[17]),g=d(g,h,f,m,C,14,b[18]),m=d(m,g,h,f,c,20,b[19]),f=d(f,m,g,h,r,5,b[20]),h=d(h,f,m,g,B,9,b[21]),g=d(g,h,f,m,x,14,b[22]),m=d(m,g,h,f,z,20,b[23]),f=d(f,m,g,h,A,5,b[24]),h=d(h,f,m,g,E,9,b[25]),g=d(g,h,f,m,k,14,b[26]),m=d(m,g,h,f,v,20,b[27]),f=d(f,m,g,h,D,5,b[28]),h=d(h,f, - m,g,j,9,b[29]),g=d(g,h,f,m,w,14,b[30]),m=d(m,g,h,f,u,20,b[31]),f=l(f,m,g,h,r,4,b[32]),h=l(h,f,m,g,v,11,b[33]),g=l(g,h,f,m,C,16,b[34]),m=l(m,g,h,f,E,23,b[35]),f=l(f,m,g,h,e,4,b[36]),h=l(h,f,m,g,z,11,b[37]),g=l(g,h,f,m,w,16,b[38]),m=l(m,g,h,f,B,23,b[39]),f=l(f,m,g,h,D,4,b[40]),h=l(h,f,m,g,c,11,b[41]),g=l(g,h,f,m,k,16,b[42]),m=l(m,g,h,f,t,23,b[43]),f=l(f,m,g,h,A,4,b[44]),h=l(h,f,m,g,u,11,b[45]),g=l(g,h,f,m,x,16,b[46]),m=l(m,g,h,f,j,23,b[47]),f=s(f,m,g,h,c,6,b[48]),h=s(h,f,m,g,w,10,b[49]),g=s(g,h,f,m, - E,15,b[50]),m=s(m,g,h,f,r,21,b[51]),f=s(f,m,g,h,u,6,b[52]),h=s(h,f,m,g,k,10,b[53]),g=s(g,h,f,m,B,15,b[54]),m=s(m,g,h,f,e,21,b[55]),f=s(f,m,g,h,v,6,b[56]),h=s(h,f,m,g,x,10,b[57]),g=s(g,h,f,m,t,15,b[58]),m=s(m,g,h,f,D,21,b[59]),f=s(f,m,g,h,z,6,b[60]),h=s(h,f,m,g,C,10,b[61]),g=s(g,h,f,m,j,15,b[62]),m=s(m,g,h,f,A,21,b[63]);a[0]=a[0]+f|0;a[1]=a[1]+m|0;a[2]=a[2]+g|0;a[3]=a[3]+h|0},_doFinalize:function(){var b=this._data,n=b.words,a=8*this._nDataBytes,c=8*b.sigBytes;n[c>>>5]|=128<<24-c%32;var e=u.floor(a/ - 4294967296);n[(c+64>>>9<<4)+15]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360;n[(c+64>>>9<<4)+14]=(a<<8|a>>>24)&16711935|(a<<24|a>>>8)&4278255360;b.sigBytes=4*(n.length+1);this._process();b=this._hash;n=b.words;for(a=0;4>a;a++)c=n[a],n[a]=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360;return b},clone:function(){var b=v.clone.call(this);b._hash=this._hash.clone();return b}});t.MD5=v._createHelper(r);t.HmacMD5=v._createHmacHelper(r)})(Math); - (function(){var u=CryptoJS,p=u.lib,d=p.Base,l=p.WordArray,p=u.algo,s=p.EvpKDF=d.extend({cfg:d.extend({keySize:4,hasher:p.MD5,iterations:1}),init:function(d){this.cfg=this.cfg.extend(d)},compute:function(d,r){for(var p=this.cfg,s=p.hasher.create(),b=l.create(),u=b.words,q=p.keySize,p=p.iterations;u.length>>2]&255}};d.BlockCipher=v.extend({cfg:v.cfg.extend({mode:b,padding:q}),reset:function(){v.reset.call(this);var a=this.cfg,b=a.iv,a=a.mode;if(this._xformMode==this._ENC_XFORM_MODE)var c=a.createEncryptor;else c=a.createDecryptor,this._minBufferSize=1;this._mode=c.call(a, - this,b&&b.words)},_doProcessBlock:function(a,b){this._mode.processBlock(a,b)},_doFinalize:function(){var a=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){a.pad(this._data,this.blockSize);var b=this._process(!0)}else b=this._process(!0),a.unpad(b);return b},blockSize:4});var n=d.CipherParams=l.extend({init:function(a){this.mixIn(a)},toString:function(a){return(a||this.formatter).stringify(this)}}),b=(p.format={}).OpenSSL={stringify:function(a){var b=a.ciphertext;a=a.salt;return(a?s.create([1398893684, - 1701076831]).concat(a).concat(b):b).toString(r)},parse:function(a){a=r.parse(a);var b=a.words;if(1398893684==b[0]&&1701076831==b[1]){var c=s.create(b.slice(2,4));b.splice(0,4);a.sigBytes-=16}return n.create({ciphertext:a,salt:c})}},a=d.SerializableCipher=l.extend({cfg:l.extend({format:b}),encrypt:function(a,b,c,d){d=this.cfg.extend(d);var l=a.createEncryptor(c,d);b=l.finalize(b);l=l.cfg;return n.create({ciphertext:b,key:c,iv:l.iv,algorithm:a,mode:l.mode,padding:l.padding,blockSize:a.blockSize,formatter:d.format})}, - decrypt:function(a,b,c,d){d=this.cfg.extend(d);b=this._parse(b,d.format);return a.createDecryptor(c,d).finalize(b.ciphertext)},_parse:function(a,b){return"string"==typeof a?b.parse(a,this):a}}),p=(p.kdf={}).OpenSSL={execute:function(a,b,c,d){d||(d=s.random(8));a=w.create({keySize:b+c}).compute(a,d);c=s.create(a.words.slice(b),4*c);a.sigBytes=4*b;return n.create({key:a,iv:c,salt:d})}},c=d.PasswordBasedCipher=a.extend({cfg:a.cfg.extend({kdf:p}),encrypt:function(b,c,d,l){l=this.cfg.extend(l);d=l.kdf.execute(d, - b.keySize,b.ivSize);l.iv=d.iv;b=a.encrypt.call(this,b,c,d.key,l);b.mixIn(d);return b},decrypt:function(b,c,d,l){l=this.cfg.extend(l);c=this._parse(c,l.format);d=l.kdf.execute(d,b.keySize,b.ivSize,c.salt);l.iv=d.iv;return a.decrypt.call(this,b,c,d.key,l)}})}(); - (function(){for(var u=CryptoJS,p=u.lib.BlockCipher,d=u.algo,l=[],s=[],t=[],r=[],w=[],v=[],b=[],x=[],q=[],n=[],a=[],c=0;256>c;c++)a[c]=128>c?c<<1:c<<1^283;for(var e=0,j=0,c=0;256>c;c++){var k=j^j<<1^j<<2^j<<3^j<<4,k=k>>>8^k&255^99;l[e]=k;s[k]=e;var z=a[e],F=a[z],G=a[F],y=257*a[k]^16843008*k;t[e]=y<<24|y>>>8;r[e]=y<<16|y>>>16;w[e]=y<<8|y>>>24;v[e]=y;y=16843009*G^65537*F^257*z^16843008*e;b[k]=y<<24|y>>>8;x[k]=y<<16|y>>>16;q[k]=y<<8|y>>>24;n[k]=y;e?(e=z^a[a[a[G^z]]],j^=a[a[j]]):e=j=1}var H=[0,1,2,4,8, - 16,32,64,128,27,54],d=d.AES=p.extend({_doReset:function(){for(var a=this._key,c=a.words,d=a.sigBytes/4,a=4*((this._nRounds=d+6)+1),e=this._keySchedule=[],j=0;j>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255]):(k=k<<8|k>>>24,k=l[k>>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255],k^=H[j/d|0]<<24);e[j]=e[j-d]^k}c=this._invKeySchedule=[];for(d=0;dd||4>=j?k:b[l[k>>>24]]^x[l[k>>>16&255]]^q[l[k>>> - 8&255]]^n[l[k&255]]},encryptBlock:function(a,b){this._doCryptBlock(a,b,this._keySchedule,t,r,w,v,l)},decryptBlock:function(a,c){var d=a[c+1];a[c+1]=a[c+3];a[c+3]=d;this._doCryptBlock(a,c,this._invKeySchedule,b,x,q,n,s);d=a[c+1];a[c+1]=a[c+3];a[c+3]=d},_doCryptBlock:function(a,b,c,d,e,j,l,f){for(var m=this._nRounds,g=a[b]^c[0],h=a[b+1]^c[1],k=a[b+2]^c[2],n=a[b+3]^c[3],p=4,r=1;r>>24]^e[h>>>16&255]^j[k>>>8&255]^l[n&255]^c[p++],s=d[h>>>24]^e[k>>>16&255]^j[n>>>8&255]^l[g&255]^c[p++],t= - d[k>>>24]^e[n>>>16&255]^j[g>>>8&255]^l[h&255]^c[p++],n=d[n>>>24]^e[g>>>16&255]^j[h>>>8&255]^l[k&255]^c[p++],g=q,h=s,k=t;q=(f[g>>>24]<<24|f[h>>>16&255]<<16|f[k>>>8&255]<<8|f[n&255])^c[p++];s=(f[h>>>24]<<24|f[k>>>16&255]<<16|f[n>>>8&255]<<8|f[g&255])^c[p++];t=(f[k>>>24]<<24|f[n>>>16&255]<<16|f[g>>>8&255]<<8|f[h&255])^c[p++];n=(f[n>>>24]<<24|f[g>>>16&255]<<16|f[h>>>8&255]<<8|f[k&255])^c[p++];a[b]=q;a[b+1]=s;a[b+2]=t;a[b+3]=n},keySize:8});u.AES=p._createHelper(d)})(); - /* eslint-enable */ - /* ====================== ./NewgroundsIO-JS/src/NewgroundsIO/Core.js ====================== */ + var CryptoJS = + CryptoJS || + (function (u, p) { + var d = {}, + l = (d.lib = {}), + s = function () {}, + t = (l.Base = { + extend: function (a) { + s.prototype = this; + var c = new s(); + a && c.mixIn(a); + c.hasOwnProperty("init") || + (c.init = function () { + c.$super.init.apply(this, arguments); + }); + c.init.prototype = c; + c.$super = this; + return c; + }, + create: function () { + var a = this.extend(); + a.init.apply(a, arguments); + return a; + }, + init: function () {}, + mixIn: function (a) { + for (var c in a) a.hasOwnProperty(c) && (this[c] = a[c]); + a.hasOwnProperty("toString") && (this.toString = a.toString); + }, + clone: function () { + return this.init.prototype.extend(this); + }, + }), + r = (l.WordArray = t.extend({ + init: function (a, c) { + a = this.words = a || []; + this.sigBytes = c != p ? c : 4 * a.length; + }, + toString: function (a) { + return (a || v).stringify(this); + }, + concat: function (a) { + var c = this.words, + e = a.words, + j = this.sigBytes; + a = a.sigBytes; + this.clamp(); + if (j % 4) + for (var k = 0; k < a; k++) + c[(j + k) >>> 2] |= + ((e[k >>> 2] >>> (24 - 8 * (k % 4))) & 255) << + (24 - 8 * ((j + k) % 4)); + else if (65535 < e.length) + for (k = 0; k < a; k += 4) c[(j + k) >>> 2] = e[k >>> 2]; + else c.push.apply(c, e); + this.sigBytes += a; + return this; + }, + clamp: function () { + var a = this.words, + c = this.sigBytes; + a[c >>> 2] &= 4294967295 << (32 - 8 * (c % 4)); + a.length = u.ceil(c / 4); + }, + clone: function () { + var a = t.clone.call(this); + a.words = this.words.slice(0); + return a; + }, + random: function (a) { + for (var c = [], e = 0; e < a; e += 4) + c.push((4294967296 * u.random()) | 0); + return new r.init(c, a); + }, + })), + w = (d.enc = {}), + v = (w.Hex = { + stringify: function (a) { + var c = a.words; + a = a.sigBytes; + for (var e = [], j = 0; j < a; j++) { + var k = (c[j >>> 2] >>> (24 - 8 * (j % 4))) & 255; + e.push((k >>> 4).toString(16)); + e.push((k & 15).toString(16)); + } + return e.join(""); + }, + parse: function (a) { + for (var c = a.length, e = [], j = 0; j < c; j += 2) + e[j >>> 3] |= parseInt(a.substr(j, 2), 16) << (24 - 4 * (j % 8)); + return new r.init(e, c / 2); + }, + }), + b = (w.Latin1 = { + stringify: function (a) { + var c = a.words; + a = a.sigBytes; + for (var e = [], j = 0; j < a; j++) + e.push( + String.fromCharCode((c[j >>> 2] >>> (24 - 8 * (j % 4))) & 255) + ); + return e.join(""); + }, + parse: function (a) { + for (var c = a.length, e = [], j = 0; j < c; j++) + e[j >>> 2] |= (a.charCodeAt(j) & 255) << (24 - 8 * (j % 4)); + return new r.init(e, c); + }, + }), + x = (w.Utf8 = { + stringify: function (a) { + try { + return decodeURIComponent(escape(b.stringify(a))); + } catch (c) { + throw Error("Malformed UTF-8 data"); + } + }, + parse: function (a) { + return b.parse(unescape(encodeURIComponent(a))); + }, + }), + q = (l.BufferedBlockAlgorithm = t.extend({ + reset: function () { + this._data = new r.init(); + this._nDataBytes = 0; + }, + _append: function (a) { + "string" == typeof a && (a = x.parse(a)); + this._data.concat(a); + this._nDataBytes += a.sigBytes; + }, + _process: function (a) { + var c = this._data, + e = c.words, + j = c.sigBytes, + k = this.blockSize, + b = j / (4 * k), + b = a ? u.ceil(b) : u.max((b | 0) - this._minBufferSize, 0); + a = b * k; + j = u.min(4 * a, j); + if (a) { + for (var q = 0; q < a; q += k) this._doProcessBlock(e, q); + q = e.splice(0, a); + c.sigBytes -= j; + } + return new r.init(q, j); + }, + clone: function () { + var a = t.clone.call(this); + a._data = this._data.clone(); + return a; + }, + _minBufferSize: 0, + })); + l.Hasher = q.extend({ + cfg: t.extend(), + init: function (a) { + this.cfg = this.cfg.extend(a); + this.reset(); + }, + reset: function () { + q.reset.call(this); + this._doReset(); + }, + update: function (a) { + this._append(a); + this._process(); + return this; + }, + finalize: function (a) { + a && this._append(a); + return this._doFinalize(); + }, + blockSize: 16, + _createHelper: function (a) { + return function (b, e) { + return new a.init(e).finalize(b); + }; + }, + _createHmacHelper: function (a) { + return function (b, e) { + return new n.HMAC.init(a, e).finalize(b); + }; + }, + }); + var n = (d.algo = {}); + return d; + })(Math); + (function () { + var u = CryptoJS, + p = u.lib.WordArray; + u.enc.Base64 = { + stringify: function (d) { + var l = d.words, + p = d.sigBytes, + t = this._map; + d.clamp(); + d = []; + for (var r = 0; r < p; r += 3) + for ( + var w = + (((l[r >>> 2] >>> (24 - 8 * (r % 4))) & 255) << 16) | + (((l[(r + 1) >>> 2] >>> (24 - 8 * ((r + 1) % 4))) & 255) << 8) | + ((l[(r + 2) >>> 2] >>> (24 - 8 * ((r + 2) % 4))) & 255), + v = 0; + 4 > v && r + 0.75 * v < p; + v++ + ) + d.push(t.charAt((w >>> (6 * (3 - v))) & 63)); + if ((l = t.charAt(64))) for (; d.length % 4; ) d.push(l); + return d.join(""); + }, + parse: function (d) { + var l = d.length, + s = this._map, + t = s.charAt(64); + t && ((t = d.indexOf(t)), -1 != t && (l = t)); + for (var t = [], r = 0, w = 0; w < l; w++) + if (w % 4) { + var v = s.indexOf(d.charAt(w - 1)) << (2 * (w % 4)), + b = s.indexOf(d.charAt(w)) >>> (6 - 2 * (w % 4)); + t[r >>> 2] |= (v | b) << (24 - 8 * (r % 4)); + r++; + } + return p.create(t, r); + }, + _map: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", + }; + })(); + (function (u) { + function p(b, n, a, c, e, j, k) { + b = b + ((n & a) | (~n & c)) + e + k; + return ((b << j) | (b >>> (32 - j))) + n; + } + function d(b, n, a, c, e, j, k) { + b = b + ((n & c) | (a & ~c)) + e + k; + return ((b << j) | (b >>> (32 - j))) + n; + } + function l(b, n, a, c, e, j, k) { + b = b + (n ^ a ^ c) + e + k; + return ((b << j) | (b >>> (32 - j))) + n; + } + function s(b, n, a, c, e, j, k) { + b = b + (a ^ (n | ~c)) + e + k; + return ((b << j) | (b >>> (32 - j))) + n; + } + for ( + var t = CryptoJS, + r = t.lib, + w = r.WordArray, + v = r.Hasher, + r = t.algo, + b = [], + x = 0; + 64 > x; + x++ + ) + b[x] = (4294967296 * u.abs(u.sin(x + 1))) | 0; + r = r.MD5 = v.extend({ + _doReset: function () { + this._hash = new w.init([ + 1732584193, 4023233417, 2562383102, 271733878, + ]); + }, + _doProcessBlock: function (q, n) { + for (var a = 0; 16 > a; a++) { + var c = n + a, + e = q[c]; + q[c] = + (((e << 8) | (e >>> 24)) & 16711935) | + (((e << 24) | (e >>> 8)) & 4278255360); + } + var a = this._hash.words, + c = q[n + 0], + e = q[n + 1], + j = q[n + 2], + k = q[n + 3], + z = q[n + 4], + r = q[n + 5], + t = q[n + 6], + w = q[n + 7], + v = q[n + 8], + A = q[n + 9], + B = q[n + 10], + C = q[n + 11], + u = q[n + 12], + D = q[n + 13], + E = q[n + 14], + x = q[n + 15], + f = a[0], + m = a[1], + g = a[2], + h = a[3], + f = p(f, m, g, h, c, 7, b[0]), + h = p(h, f, m, g, e, 12, b[1]), + g = p(g, h, f, m, j, 17, b[2]), + m = p(m, g, h, f, k, 22, b[3]), + f = p(f, m, g, h, z, 7, b[4]), + h = p(h, f, m, g, r, 12, b[5]), + g = p(g, h, f, m, t, 17, b[6]), + m = p(m, g, h, f, w, 22, b[7]), + f = p(f, m, g, h, v, 7, b[8]), + h = p(h, f, m, g, A, 12, b[9]), + g = p(g, h, f, m, B, 17, b[10]), + m = p(m, g, h, f, C, 22, b[11]), + f = p(f, m, g, h, u, 7, b[12]), + h = p(h, f, m, g, D, 12, b[13]), + g = p(g, h, f, m, E, 17, b[14]), + m = p(m, g, h, f, x, 22, b[15]), + f = d(f, m, g, h, e, 5, b[16]), + h = d(h, f, m, g, t, 9, b[17]), + g = d(g, h, f, m, C, 14, b[18]), + m = d(m, g, h, f, c, 20, b[19]), + f = d(f, m, g, h, r, 5, b[20]), + h = d(h, f, m, g, B, 9, b[21]), + g = d(g, h, f, m, x, 14, b[22]), + m = d(m, g, h, f, z, 20, b[23]), + f = d(f, m, g, h, A, 5, b[24]), + h = d(h, f, m, g, E, 9, b[25]), + g = d(g, h, f, m, k, 14, b[26]), + m = d(m, g, h, f, v, 20, b[27]), + f = d(f, m, g, h, D, 5, b[28]), + h = d(h, f, m, g, j, 9, b[29]), + g = d(g, h, f, m, w, 14, b[30]), + m = d(m, g, h, f, u, 20, b[31]), + f = l(f, m, g, h, r, 4, b[32]), + h = l(h, f, m, g, v, 11, b[33]), + g = l(g, h, f, m, C, 16, b[34]), + m = l(m, g, h, f, E, 23, b[35]), + f = l(f, m, g, h, e, 4, b[36]), + h = l(h, f, m, g, z, 11, b[37]), + g = l(g, h, f, m, w, 16, b[38]), + m = l(m, g, h, f, B, 23, b[39]), + f = l(f, m, g, h, D, 4, b[40]), + h = l(h, f, m, g, c, 11, b[41]), + g = l(g, h, f, m, k, 16, b[42]), + m = l(m, g, h, f, t, 23, b[43]), + f = l(f, m, g, h, A, 4, b[44]), + h = l(h, f, m, g, u, 11, b[45]), + g = l(g, h, f, m, x, 16, b[46]), + m = l(m, g, h, f, j, 23, b[47]), + f = s(f, m, g, h, c, 6, b[48]), + h = s(h, f, m, g, w, 10, b[49]), + g = s(g, h, f, m, E, 15, b[50]), + m = s(m, g, h, f, r, 21, b[51]), + f = s(f, m, g, h, u, 6, b[52]), + h = s(h, f, m, g, k, 10, b[53]), + g = s(g, h, f, m, B, 15, b[54]), + m = s(m, g, h, f, e, 21, b[55]), + f = s(f, m, g, h, v, 6, b[56]), + h = s(h, f, m, g, x, 10, b[57]), + g = s(g, h, f, m, t, 15, b[58]), + m = s(m, g, h, f, D, 21, b[59]), + f = s(f, m, g, h, z, 6, b[60]), + h = s(h, f, m, g, C, 10, b[61]), + g = s(g, h, f, m, j, 15, b[62]), + m = s(m, g, h, f, A, 21, b[63]); + a[0] = (a[0] + f) | 0; + a[1] = (a[1] + m) | 0; + a[2] = (a[2] + g) | 0; + a[3] = (a[3] + h) | 0; + }, + _doFinalize: function () { + var b = this._data, + n = b.words, + a = 8 * this._nDataBytes, + c = 8 * b.sigBytes; + n[c >>> 5] |= 128 << (24 - (c % 32)); + var e = u.floor(a / 4294967296); + n[(((c + 64) >>> 9) << 4) + 15] = + (((e << 8) | (e >>> 24)) & 16711935) | + (((e << 24) | (e >>> 8)) & 4278255360); + n[(((c + 64) >>> 9) << 4) + 14] = + (((a << 8) | (a >>> 24)) & 16711935) | + (((a << 24) | (a >>> 8)) & 4278255360); + b.sigBytes = 4 * (n.length + 1); + this._process(); + b = this._hash; + n = b.words; + for (a = 0; 4 > a; a++) + (c = n[a]), + (n[a] = + (((c << 8) | (c >>> 24)) & 16711935) | + (((c << 24) | (c >>> 8)) & 4278255360)); + return b; + }, + clone: function () { + var b = v.clone.call(this); + b._hash = this._hash.clone(); + return b; + }, + }); + t.MD5 = v._createHelper(r); + t.HmacMD5 = v._createHmacHelper(r); + })(Math); + (function () { + var u = CryptoJS, + p = u.lib, + d = p.Base, + l = p.WordArray, + p = u.algo, + s = (p.EvpKDF = d.extend({ + cfg: d.extend({ keySize: 4, hasher: p.MD5, iterations: 1 }), + init: function (d) { + this.cfg = this.cfg.extend(d); + }, + compute: function (d, r) { + for ( + var p = this.cfg, + s = p.hasher.create(), + b = l.create(), + u = b.words, + q = p.keySize, + p = p.iterations; + u.length < q; + + ) { + n && s.update(n); + var n = s.update(d).finalize(r); + s.reset(); + for (var a = 1; a < p; a++) (n = s.finalize(n)), s.reset(); + b.concat(n); + } + b.sigBytes = 4 * q; + return b; + }, + })); + u.EvpKDF = function (d, l, p) { + return s.create(p).compute(d, l); + }; + })(); + CryptoJS.lib.Cipher || + (function (u) { + var p = CryptoJS, + d = p.lib, + l = d.Base, + s = d.WordArray, + t = d.BufferedBlockAlgorithm, + r = p.enc.Base64, + w = p.algo.EvpKDF, + v = (d.Cipher = t.extend({ + cfg: l.extend(), + createEncryptor: function (e, a) { + return this.create(this._ENC_XFORM_MODE, e, a); + }, + createDecryptor: function (e, a) { + return this.create(this._DEC_XFORM_MODE, e, a); + }, + init: function (e, a, b) { + this.cfg = this.cfg.extend(b); + this._xformMode = e; + this._key = a; + this.reset(); + }, + reset: function () { + t.reset.call(this); + this._doReset(); + }, + process: function (e) { + this._append(e); + return this._process(); + }, + finalize: function (e) { + e && this._append(e); + return this._doFinalize(); + }, + keySize: 4, + ivSize: 4, + _ENC_XFORM_MODE: 1, + _DEC_XFORM_MODE: 2, + _createHelper: function (e) { + return { + encrypt: function (b, k, d) { + return ("string" == typeof k ? c : a).encrypt(e, b, k, d); + }, + decrypt: function (b, k, d) { + return ("string" == typeof k ? c : a).decrypt(e, b, k, d); + }, + }; + }, + })); + d.StreamCipher = v.extend({ + _doFinalize: function () { + return this._process(!0); + }, + blockSize: 1, + }); + var b = (p.mode = {}), + x = function (e, a, b) { + var c = this._iv; + c ? (this._iv = u) : (c = this._prevBlock); + for (var d = 0; d < b; d++) e[a + d] ^= c[d]; + }, + q = (d.BlockCipherMode = l.extend({ + createEncryptor: function (e, a) { + return this.Encryptor.create(e, a); + }, + createDecryptor: function (e, a) { + return this.Decryptor.create(e, a); + }, + init: function (e, a) { + this._cipher = e; + this._iv = a; + }, + })).extend(); + q.Encryptor = q.extend({ + processBlock: function (e, a) { + var b = this._cipher, + c = b.blockSize; + x.call(this, e, a, c); + b.encryptBlock(e, a); + this._prevBlock = e.slice(a, a + c); + }, + }); + q.Decryptor = q.extend({ + processBlock: function (e, a) { + var b = this._cipher, + c = b.blockSize, + d = e.slice(a, a + c); + b.decryptBlock(e, a); + x.call(this, e, a, c); + this._prevBlock = d; + }, + }); + b = b.CBC = q; + q = (p.pad = {}).Pkcs7 = { + pad: function (a, b) { + for ( + var c = 4 * b, + c = c - (a.sigBytes % c), + d = (c << 24) | (c << 16) | (c << 8) | c, + l = [], + n = 0; + n < c; + n += 4 + ) + l.push(d); + c = s.create(l, c); + a.concat(c); + }, + unpad: function (a) { + a.sigBytes -= a.words[(a.sigBytes - 1) >>> 2] & 255; + }, + }; + d.BlockCipher = v.extend({ + cfg: v.cfg.extend({ mode: b, padding: q }), + reset: function () { + v.reset.call(this); + var a = this.cfg, + b = a.iv, + a = a.mode; + if (this._xformMode == this._ENC_XFORM_MODE) + var c = a.createEncryptor; + else (c = a.createDecryptor), (this._minBufferSize = 1); + this._mode = c.call(a, this, b && b.words); + }, + _doProcessBlock: function (a, b) { + this._mode.processBlock(a, b); + }, + _doFinalize: function () { + var a = this.cfg.padding; + if (this._xformMode == this._ENC_XFORM_MODE) { + a.pad(this._data, this.blockSize); + var b = this._process(!0); + } else (b = this._process(!0)), a.unpad(b); + return b; + }, + blockSize: 4, + }); + var n = (d.CipherParams = l.extend({ + init: function (a) { + this.mixIn(a); + }, + toString: function (a) { + return (a || this.formatter).stringify(this); + }, + })), + b = ((p.format = {}).OpenSSL = { + stringify: function (a) { + var b = a.ciphertext; + a = a.salt; + return ( + a ? s.create([1398893684, 1701076831]).concat(a).concat(b) : b + ).toString(r); + }, + parse: function (a) { + a = r.parse(a); + var b = a.words; + if (1398893684 == b[0] && 1701076831 == b[1]) { + var c = s.create(b.slice(2, 4)); + b.splice(0, 4); + a.sigBytes -= 16; + } + return n.create({ ciphertext: a, salt: c }); + }, + }), + a = (d.SerializableCipher = l.extend({ + cfg: l.extend({ format: b }), + encrypt: function (a, b, c, d) { + d = this.cfg.extend(d); + var l = a.createEncryptor(c, d); + b = l.finalize(b); + l = l.cfg; + return n.create({ + ciphertext: b, + key: c, + iv: l.iv, + algorithm: a, + mode: l.mode, + padding: l.padding, + blockSize: a.blockSize, + formatter: d.format, + }); + }, + decrypt: function (a, b, c, d) { + d = this.cfg.extend(d); + b = this._parse(b, d.format); + return a.createDecryptor(c, d).finalize(b.ciphertext); + }, + _parse: function (a, b) { + return "string" == typeof a ? b.parse(a, this) : a; + }, + })), + p = ((p.kdf = {}).OpenSSL = { + execute: function (a, b, c, d) { + d || (d = s.random(8)); + a = w.create({ keySize: b + c }).compute(a, d); + c = s.create(a.words.slice(b), 4 * c); + a.sigBytes = 4 * b; + return n.create({ key: a, iv: c, salt: d }); + }, + }), + c = (d.PasswordBasedCipher = a.extend({ + cfg: a.cfg.extend({ kdf: p }), + encrypt: function (b, c, d, l) { + l = this.cfg.extend(l); + d = l.kdf.execute(d, b.keySize, b.ivSize); + l.iv = d.iv; + b = a.encrypt.call(this, b, c, d.key, l); + b.mixIn(d); + return b; + }, + decrypt: function (b, c, d, l) { + l = this.cfg.extend(l); + c = this._parse(c, l.format); + d = l.kdf.execute(d, b.keySize, b.ivSize, c.salt); + l.iv = d.iv; + return a.decrypt.call(this, b, c, d.key, l); + }, + })); + })(); + (function () { + for ( + var u = CryptoJS, + p = u.lib.BlockCipher, + d = u.algo, + l = [], + s = [], + t = [], + r = [], + w = [], + v = [], + b = [], + x = [], + q = [], + n = [], + a = [], + c = 0; + 256 > c; + c++ + ) + a[c] = 128 > c ? c << 1 : (c << 1) ^ 283; + for (var e = 0, j = 0, c = 0; 256 > c; c++) { + var k = j ^ (j << 1) ^ (j << 2) ^ (j << 3) ^ (j << 4), + k = (k >>> 8) ^ (k & 255) ^ 99; + l[e] = k; + s[k] = e; + var z = a[e], + F = a[z], + G = a[F], + y = (257 * a[k]) ^ (16843008 * k); + t[e] = (y << 24) | (y >>> 8); + r[e] = (y << 16) | (y >>> 16); + w[e] = (y << 8) | (y >>> 24); + v[e] = y; + y = (16843009 * G) ^ (65537 * F) ^ (257 * z) ^ (16843008 * e); + b[k] = (y << 24) | (y >>> 8); + x[k] = (y << 16) | (y >>> 16); + q[k] = (y << 8) | (y >>> 24); + n[k] = y; + e ? ((e = z ^ a[a[a[G ^ z]]]), (j ^= a[a[j]])) : (e = j = 1); + } + var H = [0, 1, 2, 4, 8, 16, 32, 64, 128, 27, 54], + d = (d.AES = p.extend({ + _doReset: function () { + for ( + var a = this._key, + c = a.words, + d = a.sigBytes / 4, + a = 4 * ((this._nRounds = d + 6) + 1), + e = (this._keySchedule = []), + j = 0; + j < a; + j++ + ) + if (j < d) e[j] = c[j]; + else { + var k = e[j - 1]; + j % d + ? 6 < d && + 4 == j % d && + (k = + (l[k >>> 24] << 24) | + (l[(k >>> 16) & 255] << 16) | + (l[(k >>> 8) & 255] << 8) | + l[k & 255]) + : ((k = (k << 8) | (k >>> 24)), + (k = + (l[k >>> 24] << 24) | + (l[(k >>> 16) & 255] << 16) | + (l[(k >>> 8) & 255] << 8) | + l[k & 255]), + (k ^= H[(j / d) | 0] << 24)); + e[j] = e[j - d] ^ k; + } + c = this._invKeySchedule = []; + for (d = 0; d < a; d++) + (j = a - d), + (k = d % 4 ? e[j] : e[j - 4]), + (c[d] = + 4 > d || 4 >= j + ? k + : b[l[k >>> 24]] ^ + x[l[(k >>> 16) & 255]] ^ + q[l[(k >>> 8) & 255]] ^ + n[l[k & 255]]); + }, + encryptBlock: function (a, b) { + this._doCryptBlock(a, b, this._keySchedule, t, r, w, v, l); + }, + decryptBlock: function (a, c) { + var d = a[c + 1]; + a[c + 1] = a[c + 3]; + a[c + 3] = d; + this._doCryptBlock(a, c, this._invKeySchedule, b, x, q, n, s); + d = a[c + 1]; + a[c + 1] = a[c + 3]; + a[c + 3] = d; + }, + _doCryptBlock: function (a, b, c, d, e, j, l, f) { + for ( + var m = this._nRounds, + g = a[b] ^ c[0], + h = a[b + 1] ^ c[1], + k = a[b + 2] ^ c[2], + n = a[b + 3] ^ c[3], + p = 4, + r = 1; + r < m; + r++ + ) + var q = + d[g >>> 24] ^ + e[(h >>> 16) & 255] ^ + j[(k >>> 8) & 255] ^ + l[n & 255] ^ + c[p++], + s = + d[h >>> 24] ^ + e[(k >>> 16) & 255] ^ + j[(n >>> 8) & 255] ^ + l[g & 255] ^ + c[p++], + t = + d[k >>> 24] ^ + e[(n >>> 16) & 255] ^ + j[(g >>> 8) & 255] ^ + l[h & 255] ^ + c[p++], + n = + d[n >>> 24] ^ + e[(g >>> 16) & 255] ^ + j[(h >>> 8) & 255] ^ + l[k & 255] ^ + c[p++], + g = q, + h = s, + k = t; + q = + ((f[g >>> 24] << 24) | + (f[(h >>> 16) & 255] << 16) | + (f[(k >>> 8) & 255] << 8) | + f[n & 255]) ^ + c[p++]; + s = + ((f[h >>> 24] << 24) | + (f[(k >>> 16) & 255] << 16) | + (f[(n >>> 8) & 255] << 8) | + f[g & 255]) ^ + c[p++]; + t = + ((f[k >>> 24] << 24) | + (f[(n >>> 16) & 255] << 16) | + (f[(g >>> 8) & 255] << 8) | + f[h & 255]) ^ + c[p++]; + n = + ((f[n >>> 24] << 24) | + (f[(g >>> 16) & 255] << 16) | + (f[(h >>> 8) & 255] << 8) | + f[k & 255]) ^ + c[p++]; + a[b] = q; + a[b + 1] = s; + a[b + 2] = t; + a[b + 3] = n; + }, + keySize: 8, + })); + u.AES = p._createHelper(d); + })(); + + /* ====================== ./NewgroundsIO-JS/src/NewgroundsIO/Core.js ====================== */ /** * NewgroundsIO Namespace @@ -1874,47 +2667,47 @@ let response = new NewgroundsIO.objects.Response(); Scratch.canFetch(this.GATEWAY_URI).then(allowed => { - if (!allowed) return callback(null); - // eslint-disable-next-line no-restricted-syntax - var xhr = new XMLHttpRequest(); - xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - var o_return; - try { - o_return = JSON.parse(xhr.responseText); - } catch (e) { - o_return = { success: false, app_id: core.app_id }; - o_return.error = { message: String(e), code: 8002 }; - } - - let response = core._populateResponse(o_return); - - core.dispatchEvent( - new CustomEvent("serverResponse", { detail: response }) - ); - - if (callback) { - if (thisArg) callback.call(thisArg, response); - else callback(response); + if (!allowed) return callback(null); + // eslint-disable-next-line no-restricted-syntax + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState == 4) { + var o_return; + try { + o_return = JSON.parse(xhr.responseText); + } catch (e) { + o_return = { success: false, app_id: core.app_id }; + o_return.error = { message: String(e), code: 8002 }; + } + + let response = core._populateResponse(o_return); + + core.dispatchEvent( + new CustomEvent("serverResponse", { detail: response }) + ); + + if (callback) { + if (thisArg) callback.call(thisArg, response); + else callback(response); + } } - } - }; - - // jhax is a hack to get around JS frameworks that add a toJSON method to Array (wich breaks the native implementation). - var jhax = - typeof Array.prototype.toJSON != "undefined" - ? Array.prototype.toJSON - : null; - if (jhax) delete Array.prototype.toJSON; - - let formData = new FormData(); - formData.append("request", JSON.stringify(request)); - if (jhax) Array.prototype.toJSON = jhax; - - xhr.open("POST", this.GATEWAY_URI, true); - - xhr.send(formData); - }); + }; + + // jhax is a hack to get around JS frameworks that add a toJSON method to Array (wich breaks the native implementation). + var jhax = + typeof Array.prototype.toJSON != "undefined" + ? Array.prototype.toJSON + : null; + if (jhax) delete Array.prototype.toJSON; + + let formData = new FormData(); + formData.append("request", JSON.stringify(request)); + if (jhax) Array.prototype.toJSON = jhax; + + xhr.open("POST", this.GATEWAY_URI, true); + + xhr.send(formData); + }); } /** @@ -2568,11 +3361,11 @@ class startSession extends NewgroundsIO.BaseComponent { /** - * Constructor - * @param {object} props An object of initial properties for this instance - * @param {Boolean} props.force If true, will create a new session even if the user already has an existing one. + * Constructor + * @param {object} props An object of initial properties for this instance + * @param {Boolean} props.force If true, will create a new session even if the user already has an existing one. undefined * Note: Any previous session ids will no longer be valid if this is used. - */ + */ constructor(props) { super(); let _this = this; @@ -5232,18 +6025,18 @@ } Scratch.canFetch(this.url).then(allowed => { - if (!allowed) return callback(null); - // eslint-disable-next-line no-restricted-syntax - var xhr = new XMLHttpRequest(); - xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - if (thisArg) callback.call(thisArg, xhr.responseText); - else callback(xhr.responseText); - } - }; - xhr.open("GET", this.url, true); - xhr.send(); - }); + if (!allowed) return callback(null); + // eslint-disable-next-line no-restricted-syntax + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState == 4) { + if (thisArg) callback.call(thisArg, xhr.responseText); + else callback(xhr.responseText); + } + }; + xhr.open("GET", this.url, true); + xhr.send(); + }); } /** @@ -6166,14 +6959,14 @@ }, this); /* - this.__ngioCore.executeComponent(endSession, function(response) { - this._onEndSession(response); - if (typeof(callback) === "function") { - if (thisArg) callback.call(thisArg, this); - else callback(this); - } - }, this); - */ + this.__ngioCore.executeComponent(endSession, function(response) { + this._onEndSession(response); + if (typeof(callback) === "function") { + if (thisArg) callback.call(thisArg, this); + else callback(this); + } + }, this); + */ } /** @@ -8227,6 +9020,7 @@ NewgroundsIO.results.ScoreBoard = {}; NewgroundsIO.results.ScoreBoard.postScore = postScore; })(); + /* eslint-enable */ let menuIco = @@ -8267,10 +9061,14 @@ } const vm = Scratch.vm; + const runtime = vm.runtime; + const url_Location = window.location.href.split("/")[2]; + let isNG = url_Location == "www.newgrounds.com" || url_Location == "uploads.ungrounded.net"; + let ConnectionStatus = "Awaiting"; let loggedIn = false; let saveCompleted = false; @@ -8318,46 +9116,53 @@ ConnectionStatus = "Illegal Host"; NGIO.loadOfficialUrl(); } + break; // user needs to log in case NGIO.STATUS_LOGIN_REQUIRED: ConnectionStatus = "Login Required"; loggedIn = false; + break; // user needs to log in case NGIO.STATUS_READY: if (NGIO.hasUser) { ConnectionStatus = "Logged In"; - userDat.logged = NGIO.hasUser; loggedIn = true; + userDat.icon = NGIO.user.icons.large; + userDat.name = NGIO.user.name; + userDat.supporter = NGIO.user.supporter; + userDat.id = NGIO.user.id; + break; } else { ConnectionStatus = "Opted Out"; loggedIn = false; + break; } - break; // user needs to log in case NGIO.STATUS_WAITING_FOR_USER: ConnectionStatus = "Awaiting"; loggedIn = false; + break; } } function onSaveComplete(slot) { saveCompleted = true; - console.log("Slot " + slot.id + " was updated"); } let scorePosted = false; + ("use strict"); class NewgroundsAPI { getInfo() { return { id: "NGIO", - name: "Newgrounds", + name: "Newgrounds!", color1: "#EB7522", color2: "#4F280E", @@ -8747,6 +9552,7 @@ onLoginSuccess() { if (!userDat.logged) { + userDat.logged = NGIO.hasUser; if (userDat.logged == true) { userDat.icon = NGIO.user.icons.large; userDat.name = NGIO.user.name; @@ -8789,7 +9595,6 @@ } connect({ gameID, code }) { - console.log("Trying to init game"); NGIO.init(gameID, code, NGOptions); //Add a hook for the connection status to Newgrounds. NGIO.getConnectionStatus(statusReport); @@ -8821,7 +9626,7 @@ } getUserDat({ datType }) { - if (isNG && userDat.logged) { + if (isNG && loggedIn == true) { if (datType == "MedalScore") { return NGIO.medalScore; } else { @@ -8854,7 +9659,6 @@ saveData({ Data, Slot }) { if (isNG) { - console.log("saving " + Data + " to slot " + Slot); NGIO.setSaveSlotData(Slot, Data, onSaveComplete); } } @@ -8874,7 +9678,7 @@ return saveDat; }); } else { - return "Nothing. Newgrounds can't return Data!"; + return "Not Newgrounds can't return Data!"; } } @@ -8889,10 +9693,9 @@ //Medals unlockMedal({ medalID }) { - NGIO.getConnectionStatus(statusReport); - if (isNG && userDat.logged) { + if (isNG && loggedIn == true) { + NGIO.getConnectionStatus(statusReport); if (loggedIn) { - console.log("NGIO Tick"); NGIO.keepSessionAlive(); } NGIO.unlockMedal(medalID); @@ -8900,10 +9703,10 @@ } isMedalUnlocked({ medalID }) { - NGIO.getConnectionStatus(statusReport); - if (isNG && userDat.logged) { + if (isNG && loggedIn == true) { + NGIO.getConnectionStatus(statusReport); if (loggedIn) { - return NGIO.getMedal(medalID).unlocked; + NGIO.keepSessionAlive(); } return NGIO.getMedal(medalID).unlocked; } else { @@ -8914,10 +9717,8 @@ //Keep Session alive if game is connected. setInterval(() => { - userDat.logged = NGIO.hasUser; NGIO.getConnectionStatus(statusReport); if (loggedIn) { - console.log("NGIO Tick"); NGIO.keepSessionAlive(); } }, 1500); From 8ea952801960254cc54347ba6584662a8956fa05 Mon Sep 17 00:00:00 2001 From: CST1229 <68464103+CST1229@users.noreply.github.com> Date: Fri, 28 Jul 2023 05:06:37 +0200 Subject: [PATCH 06/17] cloudlink: add docsURI (#783) --- extensions/cloudlink.js | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/cloudlink.js b/extensions/cloudlink.js index 03ac77a836..618b1ae0bf 100644 --- a/extensions/cloudlink.js +++ b/extensions/cloudlink.js @@ -153,6 +153,7 @@ class CloudLink { "name": 'CloudLink', "blockIconURI": this.cl_block, "menuIconURI": this.cl_icon, + "docsURI": "https://hackmd.io/@MikeDEV/HJiNYwOfo", "blocks": [ { "opcode": 'returnGlobalData', From 633f02a5d9b076205e4b5fe7243f5650c573b07e Mon Sep 17 00:00:00 2001 From: GarboMuffin Date: Fri, 28 Jul 2023 19:31:11 -0500 Subject: [PATCH 07/17] Update CONTRIBUTING.md (#797) --- CONTRIBUTING.md | 123 ++++++++++++++++++++++-------------------------- 1 file changed, 57 insertions(+), 66 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 96b706319c..736f0cdef8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing extensions -Before you submit extensions, please read the NEW custom extension tutorial in full: +Before you submit extensions, please read the custom extension tutorial **in full**: - https://docs.turbowarp.org/development/extensions/introduction @@ -9,77 +9,61 @@ Please pay special attention to: - Unsandboxed extensions: https://docs.turbowarp.org/development/extensions/unsandboxed - Maintaining backward compatibility: https://docs.turbowarp.org/development/extensions/compatibility - A better development server: https://docs.turbowarp.org/development/extensions/better-development-server + - Maintaining compatibility: https://docs.turbowarp.org/development/extensions/compatibility -Pull requests that don't follow the guidelines outlined in these documents tend to take much longer to be reviewed and merged. +Read this document **in full** too. Pull requests that don't follow the guidelines will take *much* longer to be reviewed. -## Local development server +## Acceptance criteria -We recommend using our local development server: +Strictly, nothing is banned, but the following are *highly* discouraged: -```bash -# Clone the repository -git clone https://github.com/TurboWarp/extensions.git -cd extensions - -# Install dependencies -npm ci - -# Start development server -npm run dev -``` - -This starts an HTTP server on [http://localhost:8000/](http://localhost:8000/) in development mode which adds a couple of extra tools to the homepage. - -After installing npm dependencies, TypeScript aware editors such as Visual Studio Code will give you smart autocomplete suggestions for most Scratch and extension APIs based on [@turbowarp/types](https://github.com/TurboWarp/types) and [@turbowarp/types-tw](https://github.com/TurboWarp/types-tw). Note that these types are not perfect; some methods are missing or incorrect. Please report any issues you find. + - Broad "Utilities" extensions (break them up into multiple extensions, see https://github.com/TurboWarp/extensions/issues/674) + - Extensions that are very similar to existing ones (consider modifying the existing one instead) + - Novelties or one-use personal extensions (load the extension as a local file instead) + - Joke extensions (they aren't funny when they cause us to get bug reports) -If you encounter a TypeScript error, as long as you understand the error, feel free to add `// @ts-ignore`, `// @ts-expect-error`, or just ignore the error entirely. We currently do not require extensions to pass type checking. - -## Alternative development server - -If for some reason you can't set up our local development server, you can start any other HTTP server in the `extensions` folder. You won't get some of the nice things our server has, but it may be good enough. If you have Python 3 installed this is very simple: - -```bash -cd extensions -python3 -m http.server -``` +Some extensions were added before these rules existed. That doesn't mean you will be exempted too. -Note that browsers tend to aggressively cache JavaScript files that don't opt out of caching as our development server does, so you may have to do hard reloads to ensure that changes to your scripts are applied. +## Important context -## Types of extensions we accept +Every merged extension is more code that we will be expected to maintain indefinitely, even if you disappear. Remember: broken extensions mean that real projects by real people break. If the renderer is rewritten one day, we will have to ensure that extensions like Clipping and blending, RGB channels, and Augmented Reality still work. That's not a small commitment. -We strive to be tolerant of accepting almost any extension, including one-use novelty extensions or extensions that are similar to ones that already exist. +We're all volunteers who all have lives outside of Scratch extensions. Many have full time jobs or are full time students. We'll get to you as soon as we can, so please be patient. -Extensions end up in one of these categories depending on various qualities: +## Writing extensions - - Extensions that are in the repository, but not listed on the website - - Extensions that are listed on the website - - Extensions that are listed in the editor's builtin extension library +Extension source code goes in the [`extensions`](extensions) folder. For example, an extension placed at `extensions/hello-world.js` would be accessible at [http://localhost:8000/hello-world.js](http://localhost:8000/hello-world.js) using our development server. -## Writing extensions +New extensions should be added in a user folder. You can name your folder anything you want; common choices are your GitHub username or your Scratch username. If your username is `TestMuffin123`, `TestMuffin123`, `TestMuffin` and `Muffin` are all accepted -- we are very lenient about this. Do note that user folders are just for organization; other people are still allowed to edit your extension. Renaming your folder later isn't something we allow (rare exceptions, open an issue), so please get it right the first time. -Extension source code goes in the `extensions` folder. For example, an extension placed at `extensions/hello-world.js` would be accessible at [http://localhost:8000/hello-world.js](http://localhost:8000/hello-world.js). +Extensions must be self-contained. All libraries and hardcoded resources should be embedded into the extension's JavaScript file. If you include minified code, please link where to find the unminified code and include a copy of the license. -New extensions should be added in a user folder. You can name your folder your GitHub username, your Scratch username, or something else. For example, if your GitHub username is "TestMuffin", you could make a `TestMuffin` folder inside of the `extensions` folder and put your extensions inside there. You could then access a file placed at `extensions/TestMuffin/hello-world.js` at [http://localhost:8000/TestMuffin/hello-world.js](http://localhost:8000/TestMuffin/hello-world.js). +Static resources go in the `website` folder. This is where some example assets used by extensions such as fetch are placed. This is also where documentation goes for now, though we are in the process of modernizing that. -Static resources go in the `website` folder. This is where some example resources used by extensions such as fetch are placed. It works similarly to the `extensions` folder. +To add an extension to the website homepage, modify [`website/index.ejs`](website/index.ejs). See the existing entries for a template to copy. Place your extension wherever you want in the list. We will move it for you if we disagree. -Extensions must not use `eval()`, `new Function()`, untrusted `