From 1b09c8470f8f8d614b15ab920ededd832f7631a0 Mon Sep 17 00:00:00 2001 From: Faunksys Date: Thu, 27 Nov 2025 19:47:17 +0200 Subject: [PATCH 1/5] Added Both Revoke and Read function TheShovel, what's up pookie ;P --- static/extensions/Faunks/Blobs.js | 55 +++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/static/extensions/Faunks/Blobs.js b/static/extensions/Faunks/Blobs.js index c4ec8942..ebf2afe1 100644 --- a/static/extensions/Faunks/Blobs.js +++ b/static/extensions/Faunks/Blobs.js @@ -33,6 +33,31 @@ }, }, }, + { + opcode: "readBlob", + blockType: Scratch.BlockType.REPORTER, + text: "read blob [URL] as a [TYPE]", + arguments: { + URL: { + type: Scratch.ArgumentType.STRING + }, + TYPE: { + type: Scratch.ArgumentType.STRING, + defaultValue: "text/plain", + menu: "types" + } + }, + }, + { + opcode: "revokeBlob", + blockType: Scratch.BlockType.COMMAND, + text: "Revoke blob with the URL of [URL]", + arguments: { + URL: { + type: Scratch.ArgumentType.STRING + } + }, + }, ] ,menus: { types: { @@ -103,10 +128,36 @@ } toBlob(args, util) { - const text = String(args.DATA); // ensure it's a string (why can't it be just like python or smth) + const text = String(args.DATA); const blob = new Blob([text], { type: args.TYPE }); return URL.createObjectURL(blob); - } + } + revokeBlob(args, util){ + URL.revokeObjectURL(args.URL); + } + readBlob(args, util){ + async function readBlobContent(blobUrl, mime) { + const response = await fetch(blobUrl); + const blob = await response.blob(); + + if (mime.startsWith("text/") || mime === "application/json") { + const text = await blob.text(); + return mime === "application/json" ? JSON.parse(text) : text; + } + + if (mime.startsWith("image/") || + mime.startsWith("audio/") || + mime.startsWith("video/") || + mime === "application/octet-stream" || + mime.startsWith("application/") + ) { + return await blob.arrayBuffer(); + } + + return await blob.arrayBuffer(); + } + return readBlobContent(args.URL, args.TYPE) + } } Scratch.extensions.register(new faunks_Blobs()); })(Scratch); From b98564f0c74920d5ecf4df266a832009fa9a2326 Mon Sep 17 00:00:00 2001 From: Faunksys Date: Fri, 28 Nov 2025 15:37:00 +0200 Subject: [PATCH 2/5] Fixed Unsafe stuff --- static/extensions/Faunks/Blobs.js | 32 +++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/static/extensions/Faunks/Blobs.js b/static/extensions/Faunks/Blobs.js index ebf2afe1..663300d6 100644 --- a/static/extensions/Faunks/Blobs.js +++ b/static/extensions/Faunks/Blobs.js @@ -135,29 +135,33 @@ revokeBlob(args, util){ URL.revokeObjectURL(args.URL); } - readBlob(args, util){ - async function readBlobContent(blobUrl, mime) { - const response = await fetch(blobUrl); + readBlob(args, util) { + async function readBlobContent(url, mime) { + const response = await fetch(url); const blob = await response.blob(); + + if (!url.startsWith("blob:")) return ""; + + if (blob.size === 0) return ""; // js for u TheShovel if (mime.startsWith("text/") || mime === "application/json") { const text = await blob.text(); - return mime === "application/json" ? JSON.parse(text) : text; + return mime === "application/json" ? text : text; } - if (mime.startsWith("image/") || - mime.startsWith("audio/") || - mime.startsWith("video/") || - mime === "application/octet-stream" || - mime.startsWith("application/") - ) { - return await blob.arrayBuffer(); + const buffer = await blob.arrayBuffer(); + const bytes = new Uint8Array(buffer); + + let binary = ""; + for (let i = 0; i < bytes.length; i++) { + binary += String.fromCharCode(bytes[i]); } - return await blob.arrayBuffer(); - } - return readBlobContent(args.URL, args.TYPE) + return btoa(binary); } + + return readBlobContent(args.URL, args.TYPE); +} } Scratch.extensions.register(new faunks_Blobs()); })(Scratch); From 2483fe370fa211143aab66e458588f3d699e5e24 Mon Sep 17 00:00:00 2001 From: Faunksys Date: Mon, 1 Dec 2025 15:20:57 +0200 Subject: [PATCH 3/5] Switched to DataView [and more small fixes] I couldn't remove that one loop and I spent a long time trying to do so, if you have any more stuff you would like me to change I would be more than happy to do so --- static/extensions/Faunks/Blobs.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/static/extensions/Faunks/Blobs.js b/static/extensions/Faunks/Blobs.js index 663300d6..20a2afd4 100644 --- a/static/extensions/Faunks/Blobs.js +++ b/static/extensions/Faunks/Blobs.js @@ -36,7 +36,7 @@ { opcode: "readBlob", blockType: Scratch.BlockType.REPORTER, - text: "read blob [URL] as a [TYPE]", + text: "Read blob [URL] as a [TYPE]", arguments: { URL: { type: Scratch.ArgumentType.STRING @@ -132,31 +132,34 @@ const blob = new Blob([text], { type: args.TYPE }); return URL.createObjectURL(blob); } + revokeBlob(args, util){ URL.revokeObjectURL(args.URL); } + readBlob(args, util) { async function readBlobContent(url, mime) { + + if (!url.startsWith("blob:")) return ""; // I WOULD return "Url Error", but it would make it a bit harder to track if an output has been returned or not + const response = await fetch(url); const blob = await response.blob(); - if (!url.startsWith("blob:")) return ""; if (blob.size === 0) return ""; // js for u TheShovel if (mime.startsWith("text/") || mime === "application/json") { - const text = await blob.text(); - return mime === "application/json" ? text : text; + return await blob.text(); } const buffer = await blob.arrayBuffer(); - const bytes = new Uint8Array(buffer); - - let binary = ""; - for (let i = 0; i < bytes.length; i++) { - binary += String.fromCharCode(bytes[i]); + const view = new DataView(buffer); + + let binary = ''; // = decoder.decode(bytes); Decoder doesn't work with btoa, and replacing it reduces efficiency. + + for (let i = 0; i < view.byteLength; i++) { + binary += String.fromCharCode(view.getUint8(i)); } - return btoa(binary); } From fbe1f143757f2b14039be52284c4ccd058ff0194 Mon Sep 17 00:00:00 2001 From: Faunksys Date: Tue, 2 Dec 2025 16:28:19 +0200 Subject: [PATCH 4/5] Update Blobs.js --- static/extensions/Faunks/Blobs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/extensions/Faunks/Blobs.js b/static/extensions/Faunks/Blobs.js index 20a2afd4..4460c11f 100644 --- a/static/extensions/Faunks/Blobs.js +++ b/static/extensions/Faunks/Blobs.js @@ -140,7 +140,7 @@ readBlob(args, util) { async function readBlobContent(url, mime) { - if (!url.startsWith("blob:")) return ""; // I WOULD return "Url Error", but it would make it a bit harder to track if an output has been returned or not + if (!url.startsWith("blob:")) throw new URIError("must be a blob url"); // Thanks const response = await fetch(url); const blob = await response.blob(); From f27c4e6fb7f344e19a3ce18063b0cfb694767980 Mon Sep 17 00:00:00 2001 From: Steve0Greatness Date: Fri, 19 Dec 2025 20:42:43 -0800 Subject: [PATCH 5/5] Use encoding types instead of just using plaintext. Here's some bullet points on what this does: * Allow extension users to choose what they want to be outputted, rather than deciding automatically based on the MIME. * Correct base64 encoding: encoding through first writing it to a string is not only a relatively slow way of doing such, but is also not safe, due to the potential for data loss. * Added 4 encoding types, available in both the read and write blocks for blob URLs: base 64, data URL, plain text, byte array. * Base 64 reads it as base 64; note: not a string encoded to base 64, but as binary data that was base 64 encoded. * Data URL resolves the data URL and uses its content to write to the object URL. * Plaintext just takes in the input as plaintext, just as it was done before. * Byte array takes in an array of numbers that are between 0 and 255 and reads it as a string of bytes. Outputted as a JSON string so as to be compatible with other extensions such as Arrays. --- static/extensions/Faunks/Blobs.js | 97 ++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 28 deletions(-) diff --git a/static/extensions/Faunks/Blobs.js b/static/extensions/Faunks/Blobs.js index 4460c11f..961682f6 100644 --- a/static/extensions/Faunks/Blobs.js +++ b/static/extensions/Faunks/Blobs.js @@ -21,7 +21,7 @@ { opcode: "toBlob", blockType: Scratch.BlockType.REPORTER, - text: "Turn [DATA] to a [TYPE] blob", + text: "Turn [DATA] from [ENCODING] to a [TYPE] blob", arguments: { DATA: { type: Scratch.ArgumentType.STRING @@ -31,20 +31,25 @@ defaultValue: "text/plain", menu: "types" }, + ENCODING: { + type: Scratch.ArgumentType.STRING, + defaultValue: "b64", + menu: "encodings", + } }, }, { opcode: "readBlob", blockType: Scratch.BlockType.REPORTER, - text: "Read blob [URL] as a [TYPE]", + text: "Read blob [URL] as [ENCODING]", arguments: { URL: { type: Scratch.ArgumentType.STRING }, - TYPE: { + ENCODING: { type: Scratch.ArgumentType.STRING, - defaultValue: "text/plain", - menu: "types" + defaultValue: "b64", + menu: "encodings", } }, }, @@ -60,6 +65,15 @@ }, ] ,menus: { + encodings: { + acceptReporters: true, + items: [ + { text: "Base64", value: "b64" }, + { text: "Data URL", value: "dataurl" }, + { text: "Plain Text", value: "plain" }, + { text: "Byte Array", value: "array" } + ] + }, types: { acceptReporters: true, items: [ @@ -127,9 +141,29 @@ } } - toBlob(args, util) { - const text = String(args.DATA); - const blob = new Blob([text], { type: args.TYPE }); + async toBlob(args, util) { + var part = args.DATA; + const encoding = args.ENCODING; + + switch (encoding) { + case "array": + part = JSON.parse(args.DATA); + break; + case "b64": + var url = `data:application/octet-stream;base64,` + args.DATA; + case "dataurl": + var url = url ?? args.DATA; + + if (!url.startsWith("data:")) throw new URIError("only data URLs are allowed to be resolved"); + + part = await (await fetch(url)).bytes(); + break; + default: + // Plain text used to be default behavior. I would've made it throw, otherwise. + break; + } + + const blob = new Blob([part], { type: args.TYPE }); return URL.createObjectURL(blob); } @@ -137,34 +171,41 @@ URL.revokeObjectURL(args.URL); } - readBlob(args, util) { - async function readBlobContent(url, mime) { - - if (!url.startsWith("blob:")) throw new URIError("must be a blob url"); // Thanks + async readBlob(args, util) { + const url = args.URL; + const encoding = args.ENCODING; + + if (!url.startsWith("blob:")) throw new URIError("must be a blob url"); const response = await fetch(url); const blob = await response.blob(); - - if (blob.size === 0) return ""; // js for u TheShovel - - if (mime.startsWith("text/") || mime === "application/json") { - return await blob.text(); + if (blob.size === 0) switch (encoding) { + case "array": return "[]"; + case "dataurl": return `data:${blob.type},`; + default: return ""; } - const buffer = await blob.arrayBuffer(); - const view = new DataView(buffer); - - let binary = ''; // = decoder.decode(bytes); Decoder doesn't work with btoa, and replacing it reduces efficiency. - - for (let i = 0; i < view.byteLength; i++) { - binary += String.fromCharCode(view.getUint8(i)); + + switch (encoding) { + case "plain": return await blob.text(); + case "array": return JSON.stringify(Array.from(await blob.bytes())); + default: + const dataURL = await readBlobToDataURL(blob); + + if (encoding == "dataurl") return dataURL; + return dataURL.slice(dataURL.indexOf(",") + 1); } - return btoa(binary); } - - return readBlobContent(args.URL, args.TYPE); -} } + + function readBlobToDataURL(blob) { + return new Promise(res => { + const reader = new FileReader(); + reader.onload = e => res(e.target.result); + reader.readAsDataURL(blob); + }); + } + Scratch.extensions.register(new faunks_Blobs()); })(Scratch);