A downloadable project

An extension for https://penguinmod.com/editor.html

! Warning !

Not proved to work!

To use v2, simply rename the file extension name to a .js instead of a .txt

Code:

/*

   This extension was made with TurboBuilder!

   https://turbobuilder-steel.vercel.app/

*/

(async function(Scratch) {

    const variables = {};

    const blocks = [];

    const menus = [];

    if (!Scratch.extensions.unsandboxed) {

        alert("This extension needs to be unsandboxed to run!")

        return

    }

    function doSound(ab, cd, runtime) {

        const audioEngine = runtime.audioEngine;

        const fetchAsArrayBufferWithTimeout = (url) =>

            new Promise((resolve, reject) => {

                const xhr = new XMLHttpRequest();

                let timeout = setTimeout(() => {

                    xhr.abort();

                    reject(new Error("Timed out"));

                }, 5000);

                xhr.onload = () => {

                    clearTimeout(timeout);

                    if (xhr.status === 200) {

                        resolve(xhr.response);

                    } else {

                        reject(new Error(`HTTP error ${xhr.status} while fetching ${url}`));

                    }

                };

                xhr.onerror = () => {

                    clearTimeout(timeout);

                    reject(new Error(`Failed to request ${url}`));

                };

                xhr.responseType = "arraybuffer";

                xhr.open("GET", url);

                xhr.send();

            });

        const soundPlayerCache = new Map();

        const decodeSoundPlayer = async (url) => {

            const cached = soundPlayerCache.get(url);

            if (cached) {

                if (cached.sound) {

                    return cached.sound;

                }

                throw cached.error;

            }

            try {

                const arrayBuffer = await fetchAsArrayBufferWithTimeout(url);

                const soundPlayer = await audioEngine.decodeSoundPlayer({

                    data: {

                        buffer: arrayBuffer,

                    },

                });

                soundPlayerCache.set(url, {

                    sound: soundPlayer,

                    error: null,

                });

                return soundPlayer;

            } catch (e) {

                soundPlayerCache.set(url, {

                    sound: null,

                    error: e,

                });

                throw e;

            }

        };

        const playWithAudioEngine = async (url, target) => {

            const soundBank = target.sprite.soundBank;

            let soundPlayer;

            try {

                const originalSoundPlayer = await decodeSoundPlayer(url);

                soundPlayer = originalSoundPlayer.take();

            } catch (e) {

                console.warn(

                    "Could not fetch audio; falling back to primitive approach",

                    e

                );

                return false;

            }

            soundBank.addSoundPlayer(soundPlayer);

            await soundBank.playSound(target, soundPlayer.id);

            delete soundBank.soundPlayers[soundPlayer.id];

            soundBank.playerTargets.delete(soundPlayer.id);

            soundBank.soundEffects.delete(soundPlayer.id);

            return true;

        };

        const playWithAudioElement = (url, target) =>

            new Promise((resolve, reject) => {

                const mediaElement = new Audio(url);

                mediaElement.volume = target.volume / 100;

                mediaElement.onended = () => {

                    resolve();

                };

                mediaElement

                    .play()

                    .then(() => {

                        // Wait for onended

                    })

                    .catch((err) => {

                        reject(err);

                    });

            });

        const playSound = async (url, target) => {

            try {

                if (!(await Scratch.canFetch(url))) {

                    throw new Error(`Permission to fetch ${url} denied`);

                }

                const success = await playWithAudioEngine(url, target);

                if (!success) {

                    return await playWithAudioElement(url, target);

                }

            } catch (e) {

                console.warn(`All attempts to play ${url} failed`, e);

            }

        };

        playSound(ab, cd)

    }

    class Extension {

        getInfo() {

            return {

                "id": "extensionID",

                "name": "Experimental MS",

                "color1": "#ff0000",

                "color2": "#b80000",

                "tbShow": true,

                "blocks": blocks

            }

        }

    }

    blocks.push({

        opcode: `is leap year?`,

        blockType: Scratch.BlockType.BOOLEAN,

        text: `is leap year?`,

        arguments: {},

        disableMonitor: true

    });

    Extension.prototype[`is leap year?`] = async (args, util) => {

        return ((new Date(new Date(Date.now()).getYear(), 1, 29)).getDate() === 29)

    };

    return ((new Date(new Date(Date.now()).getYear(), 1, 29)).getDate() === 29)

    blocks.push({

        opcode: `null`,

        blockType: Scratch.BlockType.REPORTER,

        text: `null`,

        arguments: {},

        disableMonitor: true

    });

    Extension.prototype[`null`] = async (args, util) => {

        return null

    };

    return null

    Scratch.extensions.register(new Extension());

})(Scratch);


Download

Download
MS blocks 5 kB
Download
Experimental_MS.js 5 kB