From 1bd3378d9f74458d802943d3cc095441eba6f8a2 Mon Sep 17 00:00:00 2001
From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com>
Date: Mon, 13 Nov 2023 22:11:51 -0800
Subject: [PATCH 01/13] Create Spotify.js
---
extensions/SharkPool/Spotify.js | 467 ++++++++++++++++++++++++++++++++
1 file changed, 467 insertions(+)
create mode 100644 extensions/SharkPool/Spotify.js
diff --git a/extensions/SharkPool/Spotify.js b/extensions/SharkPool/Spotify.js
new file mode 100644
index 0000000000..2144e62eae
--- /dev/null
+++ b/extensions/SharkPool/Spotify.js
@@ -0,0 +1,467 @@
+// Name: Spotify API
+// ID: SPspotify
+// Description: Fetch Statistics and Information and Play Songs from Spotify
+// By: SharkPool
+
+// Version 1.0.0
+
+(function (Scratch) {
+ "use strict";
+ if (!Scratch.extensions.unsandboxed) {
+ throw new Error("Spotify API must run unsandboxed");
+ }
+
+ const menuIconURI = "https://upload.wikimedia.org/wikipedia/commons/8/84/Spotify_icon.svg";
+ const blockIconURI = "https://uxwing.com/wp-content/themes/uxwing/download/brands-and-social-media/spotify-white-icon.svg";
+
+ const proxy = "https://api.codetabs.com/v1/proxy?quest=";
+ let audioInstances = [];
+ let lastFetchedSong = [];
+ let lastFetchedArtist = [];
+ let lastFetchedPlaylist = [];
+
+ class SPspotify {
+ constructor() {
+ Scratch.vm.runtime.on("PROJECT_STOP_ALL", () => {
+ this.stopAll();
+ });
+ }
+ getInfo() {
+ return {
+ id: "SPspotify",
+ name: "Spotify API",
+ menuIconURI,
+ blockIconURI,
+ color1: "#1db954",
+ color2: "#158c3f",
+ color3: "#106e31",
+ blocks: [
+ {
+ func: "disclaimer",
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Song Disclaimer",
+ },
+ {
+ opcode: "getID",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get special ID from [URL]",
+ arguments: {
+ URL: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "https://open.spotify.com"
+ }
+ }
+ },
+ {
+ opcode: "isThingID",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is [URL] a [THING]?",
+ arguments: {
+ THING: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "TYPES",
+ defaultValue: "song"
+ },
+ URL: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "https://open.spotify.com"
+ }
+ }
+ },
+ { blockType: Scratch.BlockType.LABEL, text: "Songs" },
+ {
+ opcode: "getSongURL",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get song from ID [URL]",
+ arguments: {
+ URL: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1mCsF9Tw4AkIZOjvZbZZdT"
+ }
+ }
+ },
+ {
+ opcode: "playSongURL",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "play song from ID [URL]",
+ arguments: {
+ URL: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1mCsF9Tw4AkIZOjvZbZZdT"
+ }
+ }
+ },
+ {
+ opcode: "stopAll",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "stop all songs"
+ },
+ "---",
+ {
+ opcode: "getSongAtt",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get [THING] from song ID [URL]",
+ arguments: {
+ THING: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "SONG_ATTS",
+ defaultValue: "name"
+ },
+ URL: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1mCsF9Tw4AkIZOjvZbZZdT"
+ }
+ }
+ },
+ { blockType: Scratch.BlockType.LABEL, text: "Artists" },
+ {
+ opcode: "getArtistAtt",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get [THING] from artist ID [URL]",
+ arguments: {
+ THING: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "ARTIST_ATTS",
+ defaultValue: "artist"
+ },
+ URL: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "3bmFPbLMiLxtR9tFrTcKcP"
+ }
+ }
+ },
+ { blockType: Scratch.BlockType.LABEL, text: "Playlists" },
+ {
+ opcode: "getPlaylistAtt",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get [THING] from playlist ID [URL]",
+ arguments: {
+ THING: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "PLAYLIST_ATTS",
+ defaultValue: "name"
+ },
+ URL: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "6pyzEFjkdLGH8gXowr8Pg7"
+ }
+ }
+ },
+ ],
+ menus: {
+ TYPES: {
+ acceptReporters: true,
+ items: ["song", "artist", "playlist"]
+ },
+ SEARCH_TYPES: {
+ acceptReporters: true,
+ items: [
+ "song names",
+ "artist names",
+ "playlist names",
+ "song IDs",
+ "artist IDs",
+ "playlist IDs"
+ ]
+ },
+ SONG_ATTS: {
+ acceptReporters: true,
+ items: [
+ "name",
+ "artist",
+ "artist ID",
+ "cover",
+ "release date",
+ "length",
+ "listens"
+ ]
+ },
+ ARTIST_ATTS: {
+ acceptReporters: true,
+ items: ["artist", "description", "monthly listeners", "top 5 songs"]
+ },
+ PLAYLIST_ATTS: {
+ acceptReporters: true,
+ items: [
+ "name",
+ "creator",
+ "cover",
+ "description",
+ "likes",
+ "song count",
+ "top 30 songs"
+ ]
+ }
+ },
+ };
+ }
+
+ disclaimer() {
+ const disclaimerContainer = document.createElement("div");
+ disclaimerContainer.id = "disclaimer-container";
+
+ const closeButton = document.createElement("button");
+ closeButton.textContent = "Close";
+ closeButton.style.cssText = "font-weight: bold; display: block; margin: auto; border: 3px solid #106e31; border-radius: 15px; padding: 10px; width: 100px; background-color: #1db954; color: white;";
+ closeButton.addEventListener("click", () => { disclaimerContainer.style.display = "none"; });
+ const image = document.createElement("img");
+ image.src = menuIconURI;
+ image.alt = "Disclaimer Image";
+ image.style.cssText = "width: 50px; margin-right: 10px; margin-bottom: -15px; display: inline-block;";
+
+ const title = document.createElement("h2");
+ title.textContent = "Disclaimer";
+ title.style.cssText = "margin-bottom: 10px; text-align: center; color: white; display: inline-block;";
+ const disclaimerText = document.createElement("p");
+ disclaimerText.textContent = "Unfortunately, the Spotify API can only play 30-second previews of songs.";
+ disclaimerText.style.cssText = "text-align: center; color: white;";
+
+ const contentContainer = document.createElement("div");
+ contentContainer.appendChild(image);
+ contentContainer.appendChild(title);
+ contentContainer.appendChild(disclaimerText);
+ contentContainer.style.textAlign = "center";
+ disclaimerContainer.appendChild(contentContainer);
+ disclaimerContainer.appendChild(closeButton);
+
+ disclaimerContainer.style.cssText = "position: fixed; width: 250px; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 9999; border: 5px outset #1db954; border-radius: 15px; padding: 10px; background-color: #18191A; color: white; font-family: arial;";
+ document.body.appendChild(disclaimerContainer);
+ }
+
+ replaceSpecial(inputArray) {
+ if (!inputArray) return inputArray;
+ const replacements = {
+ "&": "&",
+ "<": "<",
+ ">": ">",
+ "!": "!",
+ "@": "@",
+ "#": "#",
+ "$": "$",
+ "%": "%",
+ "^": "^",
+ "*": "*",
+ "(": "(",
+ ")": ")",
+ "_": "_",
+ "+": "+",
+ "=": "=",
+ "?": "?",
+ "'": "'",
+ """: "\"",
+ "'": "'"
+ };
+ return inputArray.map((inputString) => {
+ const regex = new RegExp(Object.keys(replacements).join("|"), "g");
+ return inputString.replace(regex, (match) => replacements[match]);
+ });
+ }
+
+ getID(args) {
+ if (args.URL.startsWith("https://open.spotify.com/")) {
+ const urlParts = args.URL.split("/");
+ return urlParts[4] ? urlParts[4] : "";
+ }
+ return "";
+ }
+
+ isThingID(args) {
+ if (args.THING === "song") args.THING = "track";
+ if (args.URL.startsWith("https://open.spotify.com/")) {
+ const urlParts = args.URL.split("/");
+ if (urlParts[3]) return urlParts[3] === args.THING;
+ }
+ return false;
+ }
+
+ async getSongURL(args) {
+ try {
+ let response;
+ if (lastFetchedSong[0] === args.URL) {
+ response = lastFetchedSong[1];
+ } else {
+ response = await Scratch.fetch(`${proxy}https://open.spotify.com/track/${args.URL}`);
+ if (!response.ok) return "Error: 404";
+ response = await response.text();
+ lastFetchedSong = [args.URL, response];
+ }
+ const regex = / {
+ audio.pause();
+ audio.currentTime = 0;
+ });
+ audioInstances = [];
+ }
+
+ async getSongAtt(args) {
+ try {
+ let response;
+ if (lastFetchedSong[0] === args.URL) {
+ response = lastFetchedSong[1];
+ } else {
+ response = await Scratch.fetch(`${proxy}https://open.spotify.com/track/${args.URL}`);
+ if (!response.ok) return "Error: 404";
+ response = await response.text();
+ lastFetchedSong = [args.URL, response];
+ }
+ let regex;
+ let match;
+ switch (args.THING) {
+ case "artist":
+ regex = /- song and lyrics by ([^|]+) \| Spotify<\/title>/;
+ match = response.match(regex);
+ match = this.replaceSpecial(match);
+ return match && match[1] ? match[1] : "";
+ case "artist ID":
+ regex = //;
+ match = response.match(regex);
+ return match && match[1] ? match[1] : "";
+ case "cover":
+ regex = //;
+ match = response.match(regex);
+ return match && match[1] ? match[1] : "";
+ case "release date":
+ regex = //;
+ match = response.match(regex);
+ return match && match[1] ? match[1] : "";
+ case "length":
+ regex = //;
+ match = response.match(regex);
+ return match && match[1] ? match[1] + " seconds" : "";
+ case "listens":
+ regex = /"type">([^<]+)<\/span><\/a><\/div><\/div>/;
+ match = response.match(regex);
+ match = match.map(match => match.replace(/"/g, "\""));
+ match = match.map(match => match.replace(/'/g, "'"));
+ return match && match[1] ? match[1] : "";
+ }
+ } catch (error) {
+ return `Error: ${error}`;
+ }
+ return "";
+ }
+
+ async getArtistAtt(args) {
+ try {
+ let response;
+ if (lastFetchedArtist[0] === args.URL) {
+ response = lastFetchedArtist[1];
+ } else {
+ response = await Scratch.fetch(`${proxy}https://open.spotify.com/artist/${args.URL}`);
+ if (!response.ok) return "Error: 404";
+ response = await response.text();
+ lastFetchedArtist = [args.URL, response];
+ }
+ let regex;
+ let match;
+ switch (args.THING) {
+ case "description":
+ regex = /]*class="Type__TypeElement-sc-goli3j-0 gLgnHU G_f5DJd2sgHWeto5cwbi"[^>]*data-encore-id="type">([\s\S]*?)<\/span><\/div><\/div><\/div><\/div>/g;
+ match = [...response.matchAll(regex)].map(match => match[1].trim());
+ // Formatting
+ match = match.map(match => match.replace(/]*>(.*?)<\/a>/g, "$1"));
+ match = match.map(match => match.replace(/<\/a>/g, ""));
+ match = match.map(match => match.replace(/<\/span><\/div>\s*]*>\s*
]*>/g, " "));
+ match = this.replaceSpecial(match);
+ return match;
+ case "monthly listeners":
+ regex = />([0-9,]+) monthly listeners<\/div>/;
+ match = response.match(regex);
+ return match && match[1] ? match[1] : 0;
+ case "top 5 songs":
+ regex = /track:([^-]+)-/g;
+ match = [...response.matchAll(regex)].map(match => match[1]);
+ match = match.filter((_, index) => index % 2 === 0);
+ return match.length > 0 ? JSON.stringify([...new Set(match)]) : "[]";
+ default:
+ regex = //;
+ match = response.match(regex);
+ match = this.replaceSpecial(match);
+ return match && match[1] ? match[1] : "";
+ }
+ } catch (error) {
+ return `Error: ${error}`;
+ }
+ return "";
+ }
+
+ async getPlaylistAtt(args) {
+ try {
+ let response;
+ if (lastFetchedPlaylist[0] === args.URL) {
+ response = lastFetchedPlaylist[1];
+ } else {
+ response = await Scratch.fetch(`${proxy}https://open.spotify.com/playlist/${args.URL}`);
+ if (!response.ok) return "Error: 404";
+ response = await response.text();
+ lastFetchedPlaylist = [args.URL, response];
+ }
+ let regex;
+ let match;
+ switch (args.THING) {
+ case "creator":
+ regex = /]*href="\/user\/([^"]+)">([^<]+)<\/a>/;
+ match = response.match(regex);
+ match = this.replaceSpecial(match);
+ return match && match[2] ? match[2] : "";
+ case "cover":
+ regex = //;
+ match = response.match(regex);
+ return match && match[1] ? match[1] : "";
+ case "description":
+ regex = />([^<]+)<\/span><\/div>/;
+ match = response.match(regex);
+ match = this.replaceSpecial(match);
+ return match && match[1] ? match[1] : "";
+ case "likes":
+ regex = />(\d+) likes<\/span>(\d+) like<\/span>/;
+ match = response.match(regex);
+ return match && match[1] ? match[1] : "";
+ case "top 30 songs":
+ regex = /track\/([^"]+)"/g;
+ match = response.matchAll(regex);
+ match = Array.from(match, match => match[1]);
+ return match ? JSON.stringify(match) : "[]";
+ default:
+ regex = //;
+ match = response.match(regex);
+ match = this.replaceSpecial(match);
+ return match && match[1] ? match[1] : "";
+ }
+ } catch (error) {
+ return `Error: ${error}`;
+ }
+ return "";
+ }
+ }
+
+ Scratch.extensions.register(new SPspotify());
+})(Scratch);
From 3c991c126c779c91cb32cd3966f60ab159218262 Mon Sep 17 00:00:00 2001
From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com>
Date: Mon, 13 Nov 2023 22:16:39 -0800
Subject: [PATCH 02/13] Update Spotify.js
---
extensions/SharkPool/Spotify.js | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/extensions/SharkPool/Spotify.js b/extensions/SharkPool/Spotify.js
index 2144e62eae..57da7efbb0 100644
--- a/extensions/SharkPool/Spotify.js
+++ b/extensions/SharkPool/Spotify.js
@@ -296,7 +296,9 @@
async playSongURL(args) {
const audio = new Audio();
+ // eslint-disable
audio.src = await this.getSongURL(args);
+ // eslint-enable
audio.play();
audioInstances.push(audio);
}
@@ -358,7 +360,6 @@
} catch (error) {
return `Error: ${error}`;
}
- return "";
}
async getArtistAtt(args) {
@@ -402,7 +403,6 @@
} catch (error) {
return `Error: ${error}`;
}
- return "";
}
async getPlaylistAtt(args) {
@@ -459,7 +459,6 @@
} catch (error) {
return `Error: ${error}`;
}
- return "";
}
}
From 04225108c5394515f403fd1824d02dd85461756f Mon Sep 17 00:00:00 2001
From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com>
Date: Mon, 13 Nov 2023 22:18:36 -0800
Subject: [PATCH 03/13] Update Spotify.js
---
extensions/SharkPool/Spotify.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/extensions/SharkPool/Spotify.js b/extensions/SharkPool/Spotify.js
index 57da7efbb0..db4c184bd2 100644
--- a/extensions/SharkPool/Spotify.js
+++ b/extensions/SharkPool/Spotify.js
@@ -296,9 +296,9 @@
async playSongURL(args) {
const audio = new Audio();
- // eslint-disable
+ /* eslint-disable */
audio.src = await this.getSongURL(args);
- // eslint-enable
+ /* eslint-enable */
audio.play();
audioInstances.push(audio);
}
From 0faa99a66dc6200246d6937fab1b71b9bee08d3e Mon Sep 17 00:00:00 2001
From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com>
Date: Mon, 13 Nov 2023 22:20:17 -0800
Subject: [PATCH 04/13] Update Spotify.js
---
extensions/SharkPool/Spotify.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/extensions/SharkPool/Spotify.js b/extensions/SharkPool/Spotify.js
index db4c184bd2..2afcc28fb2 100644
--- a/extensions/SharkPool/Spotify.js
+++ b/extensions/SharkPool/Spotify.js
@@ -295,8 +295,8 @@
}
async playSongURL(args) {
- const audio = new Audio();
/* eslint-disable */
+ const audio = new Audio();
audio.src = await this.getSongURL(args);
/* eslint-enable */
audio.play();
From 594e1229b26e0c42cd338abb2fd1d888a8444ce5 Mon Sep 17 00:00:00 2001
From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com>
Date: Mon, 13 Nov 2023 22:24:53 -0800
Subject: [PATCH 05/13] Create Spotify.svg
---
images/SharkPool/Spotify.svg | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
create mode 100644 images/SharkPool/Spotify.svg
diff --git a/images/SharkPool/Spotify.svg b/images/SharkPool/Spotify.svg
new file mode 100644
index 0000000000..c788ec792b
--- /dev/null
+++ b/images/SharkPool/Spotify.svg
@@ -0,0 +1,22 @@
+
+
From d80fe719199eeba2f25ef63c802d500517f33fc4 Mon Sep 17 00:00:00 2001
From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com>
Date: Tue, 14 Nov 2023 19:18:45 -0800
Subject: [PATCH 06/13] Update Spotify.js
---
extensions/SharkPool/Spotify.js | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/extensions/SharkPool/Spotify.js b/extensions/SharkPool/Spotify.js
index 2afcc28fb2..7c46bdf8d7 100644
--- a/extensions/SharkPool/Spotify.js
+++ b/extensions/SharkPool/Spotify.js
@@ -3,7 +3,7 @@
// Description: Fetch Statistics and Information and Play Songs from Spotify
// By: SharkPool
-// Version 1.0.0
+// Version 1.0.1
(function (Scratch) {
"use strict";
@@ -353,8 +353,7 @@
default:
regex = //;
match = response.match(regex);
- match = match.map(match => match.replace(/"/g, "\""));
- match = match.map(match => match.replace(/'/g, "'"));
+ match = this.replaceSpecial(match);
return match && match[1] ? match[1] : "";
}
} catch (error) {
From 7d28bd7d7d281b5af4f2b4b8bef97ee322f2036f Mon Sep 17 00:00:00 2001
From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com>
Date: Fri, 17 Nov 2023 22:37:13 -0800
Subject: [PATCH 07/13] Update Spotify.js
---
extensions/SharkPool/Spotify.js | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/extensions/SharkPool/Spotify.js b/extensions/SharkPool/Spotify.js
index 7c46bdf8d7..1af5fbb9f4 100644
--- a/extensions/SharkPool/Spotify.js
+++ b/extensions/SharkPool/Spotify.js
@@ -3,7 +3,7 @@
// Description: Fetch Statistics and Information and Play Songs from Spotify
// By: SharkPool
-// Version 1.0.1
+// Version 1.0.2
(function (Scratch) {
"use strict";
@@ -280,7 +280,8 @@
if (lastFetchedSong[0] === args.URL) {
response = lastFetchedSong[1];
} else {
- response = await Scratch.fetch(`${proxy}https://open.spotify.com/track/${args.URL}`);
+ if (!await Scratch.canFetch(`${proxy}https://open.spotify.com/track/${args.URL}`)) return "";
+ response = await fetch(`${proxy}https://open.spotify.com/track/${args.URL}`);
if (!response.ok) return "Error: 404";
response = await response.text();
lastFetchedSong = [args.URL, response];
@@ -317,7 +318,8 @@
if (lastFetchedSong[0] === args.URL) {
response = lastFetchedSong[1];
} else {
- response = await Scratch.fetch(`${proxy}https://open.spotify.com/track/${args.URL}`);
+ if (!await Scratch.canFetch(`${proxy}https://open.spotify.com/track/${args.URL}`)) return "";
+ response = await fetch(`${proxy}https://open.spotify.com/track/${args.URL}`);
if (!response.ok) return "Error: 404";
response = await response.text();
lastFetchedSong = [args.URL, response];
@@ -367,7 +369,8 @@
if (lastFetchedArtist[0] === args.URL) {
response = lastFetchedArtist[1];
} else {
- response = await Scratch.fetch(`${proxy}https://open.spotify.com/artist/${args.URL}`);
+ if (!await Scratch.canFetch(`${proxy}https://open.spotify.com/artist/${args.URL}`)) return "";
+ response = await fetch(`${proxy}https://open.spotify.com/artist/${args.URL}`);
if (!response.ok) return "Error: 404";
response = await response.text();
lastFetchedArtist = [args.URL, response];
@@ -410,7 +413,8 @@
if (lastFetchedPlaylist[0] === args.URL) {
response = lastFetchedPlaylist[1];
} else {
- response = await Scratch.fetch(`${proxy}https://open.spotify.com/playlist/${args.URL}`);
+ if (!await Scratch.canFetch(`${proxy}https://open.spotify.com/playlist/${args.URL}`)) return "";
+ response = await fetch(`${proxy}https://open.spotify.com/playlist/${args.URL}`);
if (!response.ok) return "Error: 404";
response = await response.text();
lastFetchedPlaylist = [args.URL, response];
From cab257b4399fbb765fffc89139a550bccd583918 Mon Sep 17 00:00:00 2001
From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com>
Date: Thu, 7 Dec 2023 20:40:34 -0800
Subject: [PATCH 08/13] Update Spotify.js
---
extensions/SharkPool/Spotify.js | 54 ++++++++++++++++++++++++++-------
1 file changed, 43 insertions(+), 11 deletions(-)
diff --git a/extensions/SharkPool/Spotify.js b/extensions/SharkPool/Spotify.js
index 1af5fbb9f4..96f14cc1bb 100644
--- a/extensions/SharkPool/Spotify.js
+++ b/extensions/SharkPool/Spotify.js
@@ -3,13 +3,11 @@
// Description: Fetch Statistics and Information and Play Songs from Spotify
// By: SharkPool
-// Version 1.0.2
+// Version 1.1.0
(function (Scratch) {
"use strict";
- if (!Scratch.extensions.unsandboxed) {
- throw new Error("Spotify API must run unsandboxed");
- }
+ if (!Scratch.extensions.unsandboxed) throw new Error("Spotify API must run unsandboxed");
const menuIconURI = "https://upload.wikimedia.org/wikipedia/commons/8/84/Spotify_icon.svg";
const blockIconURI = "https://uxwing.com/wp-content/themes/uxwing/download/brands-and-social-media/spotify-white-icon.svg";
@@ -22,6 +20,14 @@
class SPspotify {
constructor() {
+ this.audioContext = new AudioContext();
+ this.gainNode = this.audioContext.createGain();
+ this.gainNode.gain.value = 1;
+ this.gainNode.connect(this.audioContext.destination);
+ // PenguinMod thing
+ if (Scratch.extensions.isPenguinMod) {
+ Scratch.vm.runtime.registerExtensionAudioContext("SPspotify", this.audioContext, this.gainNode);
+ }
Scratch.vm.runtime.on("PROJECT_STOP_ALL", () => {
this.stopAll();
});
@@ -295,19 +301,45 @@
return "";
}
+ getUrlAudioBuffer(url) {
+ return new Promise((resolve, reject) => {
+ fetch(url)
+ .then(response => {
+ response.arrayBuffer()
+ .then(arrayBuffer => {
+ this.audioContext.decodeAudioData(arrayBuffer, (audioBuffer) => {
+ resolve(audioBuffer);
+ }, reject).catch(reject);
+ })
+ .catch(reject);
+ })
+ .catch(reject);
+ });
+ }
+
async playSongURL(args) {
+ const url = await this.getSongURL(args);
+ if (!(await Scratch.canFetch(url))) return;
+ let buffer = null;
+ try {
+ buffer = await this.getUrlAudioBuffer(url);
+ } catch (err) {
+ return console.warn('Couldn\'t decode AudioBuffer', err);
+ }
/* eslint-disable */
- const audio = new Audio();
- audio.src = await this.getSongURL(args);
+ const node = this.audioContext.createBufferSource();
+ node.buffer = buffer;
+ node.connect(this.gainNode);
+ node.start(0);
/* eslint-enable */
- audio.play();
- audioInstances.push(audio);
+ audioInstances.push(node);
}
stopAll() {
- audioInstances.forEach(audio => {
- audio.pause();
- audio.currentTime = 0;
+ audioInstances.forEach(node => {
+ try {
+ node.stop();
+ } catch {}
});
audioInstances = [];
}
From 67b6726001f63f98bfcec16558266fdd58281f96 Mon Sep 17 00:00:00 2001
From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com>
Date: Thu, 7 Dec 2023 20:44:32 -0800
Subject: [PATCH 09/13] Update Spotify.js
---
extensions/SharkPool/Spotify.js | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/extensions/SharkPool/Spotify.js b/extensions/SharkPool/Spotify.js
index 96f14cc1bb..5841313823 100644
--- a/extensions/SharkPool/Spotify.js
+++ b/extensions/SharkPool/Spotify.js
@@ -287,6 +287,7 @@
response = lastFetchedSong[1];
} else {
if (!await Scratch.canFetch(`${proxy}https://open.spotify.com/track/${args.URL}`)) return "";
+ // eslint-disable-next-line
response = await fetch(`${proxy}https://open.spotify.com/track/${args.URL}`);
if (!response.ok) return "Error: 404";
response = await response.text();
@@ -303,6 +304,7 @@
getUrlAudioBuffer(url) {
return new Promise((resolve, reject) => {
+ // eslint-disable-next-line
fetch(url)
.then(response => {
response.arrayBuffer()
@@ -351,6 +353,7 @@
response = lastFetchedSong[1];
} else {
if (!await Scratch.canFetch(`${proxy}https://open.spotify.com/track/${args.URL}`)) return "";
+ // eslint-disable-next-line
response = await fetch(`${proxy}https://open.spotify.com/track/${args.URL}`);
if (!response.ok) return "Error: 404";
response = await response.text();
@@ -402,6 +405,7 @@
response = lastFetchedArtist[1];
} else {
if (!await Scratch.canFetch(`${proxy}https://open.spotify.com/artist/${args.URL}`)) return "";
+ // eslint-disable-next-line
response = await fetch(`${proxy}https://open.spotify.com/artist/${args.URL}`);
if (!response.ok) return "Error: 404";
response = await response.text();
@@ -446,6 +450,7 @@
response = lastFetchedPlaylist[1];
} else {
if (!await Scratch.canFetch(`${proxy}https://open.spotify.com/playlist/${args.URL}`)) return "";
+ // eslint-disable-next-line
response = await fetch(`${proxy}https://open.spotify.com/playlist/${args.URL}`);
if (!response.ok) return "Error: 404";
response = await response.text();
From d8004558ac6ee6568fc287c9a6a774ed2820708f Mon Sep 17 00:00:00 2001
From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com>
Date: Thu, 21 Dec 2023 18:47:30 -0800
Subject: [PATCH 10/13] Update Spotify.js
---
extensions/SharkPool/Spotify.js | 38 +++++++++++++--------------------
1 file changed, 15 insertions(+), 23 deletions(-)
diff --git a/extensions/SharkPool/Spotify.js b/extensions/SharkPool/Spotify.js
index 5841313823..3726edfad3 100644
--- a/extensions/SharkPool/Spotify.js
+++ b/extensions/SharkPool/Spotify.js
@@ -3,14 +3,17 @@
// Description: Fetch Statistics and Information and Play Songs from Spotify
// By: SharkPool
-// Version 1.1.0
+// Version 1.1.1
(function (Scratch) {
"use strict";
if (!Scratch.extensions.unsandboxed) throw new Error("Spotify API must run unsandboxed");
- const menuIconURI = "https://upload.wikimedia.org/wikipedia/commons/8/84/Spotify_icon.svg";
- const blockIconURI = "https://uxwing.com/wp-content/themes/uxwing/download/brands-and-social-media/spotify-white-icon.svg";
+ const menuIconURI =
+"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0OTYgNTEyIj4KICA8cGF0aCBkPSJNMjQ4IDhDMTExLjEgOCAwIDExOS4xIDAgMjU2czExMS4xIDI0OCAyNDggMjQ4IDI0OC0xMTEuMSAyNDgtMjQ4UzM4NC45IDggMjQ4IDhaIiBmaWxsPSIjMWVkNzYwIi8+CiAgPHBhdGggZD0iTTQwNi42IDIzMS4xYy01LjIgMC04LjQtMS4zLTEyLjktMy45LTcxLjItNDIuNS0xOTguNS01Mi43LTI4MC45LTI5LjctMy42IDEtOC4xIDIuNi0xMi45IDIuNi0xMy4yIDAtMjMuMy0xMC4zLTIzLjMtMjMuNiAwLTEzLjYgOC40LTIxLjMgMTcuNC0yMy45IDM1LjItMTAuMyA3NC42LTE1LjIgMTE3LjUtMTUuMiA3MyAwIDE0OS41IDE1LjIgMjA1LjQgNDcuOCA3LjggNC41IDEyLjkgMTAuNyAxMi45IDIyLjYgMCAxMy42LTExIDIzLjMtMjMuMiAyMy4zem0tMzEgNzYuMmMtNS4yIDAtOC43LTIuMy0xMi4zLTQuMi02Mi41LTM3LTE1NS43LTUxLjktMjM4LjYtMjkuNC00LjggMS4zLTcuNCAyLjYtMTEuOSAyLjYtMTAuNyAwLTE5LjQtOC43LTE5LjQtMTkuNHM1LjItMTcuOCAxNS41LTIwLjdjMjcuOC03LjggNTYuMi0xMy42IDk3LjgtMTMuNiA2NC45IDAgMTI3LjYgMTYuMSAxNzcgNDUuNSA4LjEgNC44IDExLjMgMTEgMTEuMyAxOS43LS4xIDEwLjgtOC41IDE5LjUtMTkuNCAxOS41em0tMjYuOSA2NS42Yy00LjIgMC02LjgtMS4zLTEwLjctMy42LTYyLjQtMzcuNi0xMzUtMzkuMi0yMDYuNy0yNC41LTMuOSAxLTkgMi42LTExLjkgMi42LTkuNyAwLTE1LjgtNy43LTE1LjgtMTUuOCAwLTEwLjMgNi4xLTE1LjIgMTMuNi0xNi44IDgxLjktMTguMSAxNjUuNi0xNi41IDIzNyAyNi4yIDYuMSAzLjkgOS43IDcuNCA5LjcgMTYuNXMtNy4xIDE1LjQtMTUuMiAxNS40eiIvPgo8L3N2Zz4=";
+
+ const blockIconURI =
+"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTExLjk5MSIgY2xpcC1ydWxlPSJldmVub2RkIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGltYWdlLXJlbmRlcmluZz0ib3B0aW1pemVRdWFsaXR5IiB0ZXh0LXJlbmRlcmluZz0iZ2VvbWV0cmljUHJlY2lzaW9uIiBzaGFwZS1yZW5kZXJpbmc9Imdlb21ldHJpY1ByZWNpc2lvbiI+PHBhdGggZD0iTTI1NS45OTguMDAzQzExNC42MTYuMDAzIDAgMTE0LjYxNiAwIDI1NS45OTdjMCAxNDEuMzg1IDExNC42MTYgMjU1Ljk5NCAyNTUuOTk4IDI1NS45OTRDMzk3LjM5NSA1MTEuOTkxIDUxMiAzOTcuMzg2IDUxMiAyNTUuOTk3IDUxMiAxMTQuNjI0IDM5Ny4zOTUuMDE1IDI1NS45OTQuMDE1bC4wMDQtLjAxNXYuMDAzem0xMTcuNCAzNjkuMjJjLTQuNTg1IDcuNTE5LTE0LjQyNyA5LjkwOC0yMS45NDkgNS4yODgtNjAuMTA0LTM2LjcxNC0xMzUuNzcxLTQ1LjAyNy0yMjQuODgyLTI0LjY2OC04LjU4NyAxLjk1NC0xNy4xNDYtMy40MjUtMTkuMTA0LTEyLjAxNS0xLjk2Ny04LjU5MSAzLjM5NC0xNy4xNSAxMi4wMDMtMTkuMTA0IDk3LjUxOC0yMi4yOCAxODEuMTY0LTEyLjY4OCAyNDguNjQ1IDI4LjU1IDcuNTIyIDQuNjE2IDkuOTA3IDE0LjQyNyA1LjI4OCAyMS45NWwtLjAwMS0uMDAxem0zMS4zMzUtNjkuNzAzYy01Ljc3OSA5LjM4OS0xOC4wNjcgMTIuMzUzLTI3LjQ1MiA2LjU3OC02OC44MTMtNDIuMjk4LTE3My43MDMtNTQuNTQ4LTI1NS4wOTYtMjkuODM3LTEwLjU1NiAzLjE4Ny0yMS43MDQtMi43NjEtMjQuOTA2LTEzLjI5OC0zLjE4LTEwLjU1NiAyLjc3Mi0yMS42OCAxMy4zMDktMjQuODkxIDkyLjk3MS0yOC4yMDggMjA4LjU1MS0xNC41NDYgMjg3LjU3NCAzNC4wMTUgOS4zODUgNS43NzkgMTIuMzUgMTguMDY3IDYuNTc1IDI3LjQ0MXYtLjAwNGwtLjAwNC0uMDA0em0yLjY5Mi03Mi41ODRjLTgyLjUxMS00OS4wMDYtMjE4LjYzNS01My41MS0yOTcuNDA5LTI5LjYwMy0xMi42NDkgMy44MzctMjYuMDI3LTMuMzAyLTI5Ljg2LTE1Ljk1NS0zLjgzMi0xMi42NTYgMy4zMDMtMjYuMDIzIDE1Ljk2LTI5Ljg2NyA5MC40MjgtMjcuNDUyIDI0MC43NTMtMjIuMTQ5IDMzNS43NDcgMzQuMjQ1IDExLjQwMSA2Ljc1NCAxNS4xMzMgMjEuNDQ2IDguMzc1IDMyLjgwOS02LjcyOCAxMS4zNzgtMjEuNDYyIDE1LjEzLTMyLjgwMiA4LjM3MWgtLjAxMXoiIGZpbGwtcnVsZT0ibm9uemVybyIgZmlsbD0iI2ZmZiIvPjwvc3ZnPg==";
const proxy = "https://api.codetabs.com/v1/proxy?quest=";
let audioInstances = [];
@@ -20,17 +23,11 @@
class SPspotify {
constructor() {
- this.audioContext = new AudioContext();
- this.gainNode = this.audioContext.createGain();
+ this.audioNode = Scratch.vm.runtime.audioEngine.inputNode;
+ this.gainNode = this.audioNode.context.createGain();
this.gainNode.gain.value = 1;
- this.gainNode.connect(this.audioContext.destination);
- // PenguinMod thing
- if (Scratch.extensions.isPenguinMod) {
- Scratch.vm.runtime.registerExtensionAudioContext("SPspotify", this.audioContext, this.gainNode);
- }
- Scratch.vm.runtime.on("PROJECT_STOP_ALL", () => {
- this.stopAll();
- });
+ this.gainNode.connect(this.audioNode);
+ Scratch.vm.runtime.on("PROJECT_STOP_ALL", () => { this.stopAll() });
}
getInfo() {
return {
@@ -111,7 +108,6 @@
THING: {
type: Scratch.ArgumentType.STRING,
menu: "SONG_ATTS",
- defaultValue: "name"
},
URL: {
type: Scratch.ArgumentType.STRING,
@@ -128,7 +124,6 @@
THING: {
type: Scratch.ArgumentType.STRING,
menu: "ARTIST_ATTS",
- defaultValue: "artist"
},
URL: {
type: Scratch.ArgumentType.STRING,
@@ -145,7 +140,6 @@
THING: {
type: Scratch.ArgumentType.STRING,
menu: "PLAYLIST_ATTS",
- defaultValue: "name"
},
URL: {
type: Scratch.ArgumentType.STRING,
@@ -304,12 +298,11 @@
getUrlAudioBuffer(url) {
return new Promise((resolve, reject) => {
- // eslint-disable-next-line
fetch(url)
.then(response => {
response.arrayBuffer()
.then(arrayBuffer => {
- this.audioContext.decodeAudioData(arrayBuffer, (audioBuffer) => {
+ Scratch.vm.runtime.audioEngine.inputNode.context.decodeAudioData(arrayBuffer, (audioBuffer) => {
resolve(audioBuffer);
}, reject).catch(reject);
})
@@ -326,21 +319,19 @@
try {
buffer = await this.getUrlAudioBuffer(url);
} catch (err) {
- return console.warn('Couldn\'t decode AudioBuffer', err);
+ return console.warn("Couldn't decode AudioBuffer", err);
}
- /* eslint-disable */
- const node = this.audioContext.createBufferSource();
+ const node = this.audioNode.context.createBufferSource();
node.buffer = buffer;
node.connect(this.gainNode);
node.start(0);
- /* eslint-enable */
audioInstances.push(node);
}
stopAll() {
audioInstances.forEach(node => {
try {
- node.stop();
+ node.disconnect(this.gainNode);
} catch {}
});
audioInstances = [];
@@ -415,6 +406,7 @@
let match;
switch (args.THING) {
case "description":
+ // has to be very case-specific since Spotify names their classes very similarly
regex = /]*class="Type__TypeElement-sc-goli3j-0 gLgnHU G_f5DJd2sgHWeto5cwbi"[^>]*data-encore-id="type">([\s\S]*?)<\/span><\/div><\/div><\/div><\/div>/g;
match = [...response.matchAll(regex)].map(match => match[1].trim());
// Formatting
From a87bb3343121d6ab39ecc6f9b04b076409c4ff6b Mon Sep 17 00:00:00 2001
From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com>
Date: Thu, 21 Dec 2023 18:48:34 -0800
Subject: [PATCH 11/13] Update Spotify.js
---
extensions/SharkPool/Spotify.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/extensions/SharkPool/Spotify.js b/extensions/SharkPool/Spotify.js
index 3726edfad3..325e139bef 100644
--- a/extensions/SharkPool/Spotify.js
+++ b/extensions/SharkPool/Spotify.js
@@ -298,6 +298,7 @@
getUrlAudioBuffer(url) {
return new Promise((resolve, reject) => {
+ // eslint-disable-next-line
fetch(url)
.then(response => {
response.arrayBuffer()
From efa344632693fd9e3cc738fa1a769805b65704cb Mon Sep 17 00:00:00 2001
From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com>
Date: Thu, 21 Dec 2023 18:57:12 -0800
Subject: [PATCH 12/13] Update Spotify.js
---
extensions/SharkPool/Spotify.js | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/extensions/SharkPool/Spotify.js b/extensions/SharkPool/Spotify.js
index 325e139bef..69731709e2 100644
--- a/extensions/SharkPool/Spotify.js
+++ b/extensions/SharkPool/Spotify.js
@@ -197,13 +197,18 @@
}
disclaimer() {
+ const overlay = document.createElement("div");
+ overlay.style.cssText = "position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 9998;";
const disclaimerContainer = document.createElement("div");
disclaimerContainer.id = "disclaimer-container";
const closeButton = document.createElement("button");
closeButton.textContent = "Close";
closeButton.style.cssText = "font-weight: bold; display: block; margin: auto; border: 3px solid #106e31; border-radius: 15px; padding: 10px; width: 100px; background-color: #1db954; color: white;";
- closeButton.addEventListener("click", () => { disclaimerContainer.style.display = "none"; });
+ closeButton.addEventListener("click", () => {
+ disclaimerContainer.remove();
+ overlay.remove();
+ });
const image = document.createElement("img");
image.src = menuIconURI;
image.alt = "Disclaimer Image";
@@ -225,6 +230,7 @@
disclaimerContainer.appendChild(closeButton);
disclaimerContainer.style.cssText = "position: fixed; width: 250px; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 9999; border: 5px outset #1db954; border-radius: 15px; padding: 10px; background-color: #18191A; color: white; font-family: arial;";
+ document.body.appendChild(overlay);
document.body.appendChild(disclaimerContainer);
}
From 9db500f8aa0f9a41c315f67de649bc40feb0133f Mon Sep 17 00:00:00 2001
From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com>
Date: Sat, 16 Nov 2024 15:40:36 -0800
Subject: [PATCH 13/13] Spotify -- Rewrite
---
extensions/SharkPool/Spotify.js | 407 ++++++++++++++------------------
1 file changed, 172 insertions(+), 235 deletions(-)
diff --git a/extensions/SharkPool/Spotify.js b/extensions/SharkPool/Spotify.js
index 69731709e2..722cc08006 100644
--- a/extensions/SharkPool/Spotify.js
+++ b/extensions/SharkPool/Spotify.js
@@ -3,7 +3,7 @@
// Description: Fetch Statistics and Information and Play Songs from Spotify
// By: SharkPool
-// Version 1.1.1
+// Version 1.2.0
(function (Scratch) {
"use strict";
@@ -15,19 +15,18 @@
const blockIconURI =
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTExLjk5MSIgY2xpcC1ydWxlPSJldmVub2RkIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGltYWdlLXJlbmRlcmluZz0ib3B0aW1pemVRdWFsaXR5IiB0ZXh0LXJlbmRlcmluZz0iZ2VvbWV0cmljUHJlY2lzaW9uIiBzaGFwZS1yZW5kZXJpbmc9Imdlb21ldHJpY1ByZWNpc2lvbiI+PHBhdGggZD0iTTI1NS45OTguMDAzQzExNC42MTYuMDAzIDAgMTE0LjYxNiAwIDI1NS45OTdjMCAxNDEuMzg1IDExNC42MTYgMjU1Ljk5NCAyNTUuOTk4IDI1NS45OTRDMzk3LjM5NSA1MTEuOTkxIDUxMiAzOTcuMzg2IDUxMiAyNTUuOTk3IDUxMiAxMTQuNjI0IDM5Ny4zOTUuMDE1IDI1NS45OTQuMDE1bC4wMDQtLjAxNXYuMDAzem0xMTcuNCAzNjkuMjJjLTQuNTg1IDcuNTE5LTE0LjQyNyA5LjkwOC0yMS45NDkgNS4yODgtNjAuMTA0LTM2LjcxNC0xMzUuNzcxLTQ1LjAyNy0yMjQuODgyLTI0LjY2OC04LjU4NyAxLjk1NC0xNy4xNDYtMy40MjUtMTkuMTA0LTEyLjAxNS0xLjk2Ny04LjU5MSAzLjM5NC0xNy4xNSAxMi4wMDMtMTkuMTA0IDk3LjUxOC0yMi4yOCAxODEuMTY0LTEyLjY4OCAyNDguNjQ1IDI4LjU1IDcuNTIyIDQuNjE2IDkuOTA3IDE0LjQyNyA1LjI4OCAyMS45NWwtLjAwMS0uMDAxem0zMS4zMzUtNjkuNzAzYy01Ljc3OSA5LjM4OS0xOC4wNjcgMTIuMzUzLTI3LjQ1MiA2LjU3OC02OC44MTMtNDIuMjk4LTE3My43MDMtNTQuNTQ4LTI1NS4wOTYtMjkuODM3LTEwLjU1NiAzLjE4Ny0yMS43MDQtMi43NjEtMjQuOTA2LTEzLjI5OC0zLjE4LTEwLjU1NiAyLjc3Mi0yMS42OCAxMy4zMDktMjQuODkxIDkyLjk3MS0yOC4yMDggMjA4LjU1MS0xNC41NDYgMjg3LjU3NCAzNC4wMTUgOS4zODUgNS43NzkgMTIuMzUgMTguMDY3IDYuNTc1IDI3LjQ0MXYtLjAwNGwtLjAwNC0uMDA0em0yLjY5Mi03Mi41ODRjLTgyLjUxMS00OS4wMDYtMjE4LjYzNS01My41MS0yOTcuNDA5LTI5LjYwMy0xMi42NDkgMy44MzctMjYuMDI3LTMuMzAyLTI5Ljg2LTE1Ljk1NS0zLjgzMi0xMi42NTYgMy4zMDMtMjYuMDIzIDE1Ljk2LTI5Ljg2NyA5MC40MjgtMjcuNDUyIDI0MC43NTMtMjIuMTQ5IDMzNS43NDcgMzQuMjQ1IDExLjQwMSA2Ljc1NCAxNS4xMzMgMjEuNDQ2IDguMzc1IDMyLjgwOS02LjcyOCAxMS4zNzgtMjEuNDYyIDE1LjEzLTMyLjgwMiA4LjM3MWgtLjAxMXoiIGZpbGwtcnVsZT0ibm9uemVybyIgZmlsbD0iI2ZmZiIvPjwvc3ZnPg==";
+ const runtime = Scratch.vm.runtime;
+ // for fetching files, we must use this, its slower but works
const proxy = "https://api.codetabs.com/v1/proxy?quest=";
- let audioInstances = [];
- let lastFetchedSong = [];
- let lastFetchedArtist = [];
- let lastFetchedPlaylist = [];
+ let audioInstances = [], lastFetched = {s: [], a: [], p: [] };
class SPspotify {
constructor() {
- this.audioNode = Scratch.vm.runtime.audioEngine.inputNode;
+ this.audioNode = runtime.audioEngine.inputNode;
this.gainNode = this.audioNode.context.createGain();
this.gainNode.gain.value = 1;
this.gainNode.connect(this.audioNode);
- Scratch.vm.runtime.on("PROJECT_STOP_ALL", () => { this.stopAll() });
+ runtime.on("PROJECT_STOP_ALL", () => this.stopAll());
}
getInfo() {
return {
@@ -42,17 +41,14 @@
{
func: "disclaimer",
blockType: Scratch.BlockType.BUTTON,
- text: "Song Disclaimer",
+ text: "Song Disclaimer"
},
{
opcode: "getID",
blockType: Scratch.BlockType.REPORTER,
text: "get special ID from [URL]",
arguments: {
- URL: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "https://open.spotify.com"
- }
+ URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://open.spotify.com" }
}
},
{
@@ -60,27 +56,27 @@
blockType: Scratch.BlockType.BOOLEAN,
text: "is [URL] a [THING]?",
arguments: {
- THING: {
- type: Scratch.ArgumentType.STRING,
- menu: "TYPES",
- defaultValue: "song"
- },
- URL: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "https://open.spotify.com"
- }
+ THING: { type: Scratch.ArgumentType.STRING, menu: "TYPES" },
+ URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://open.spotify.com" }
}
},
{ blockType: Scratch.BlockType.LABEL, text: "Songs" },
+ {
+ opcode: "getSongAtt",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get [THING] from song ID [URL]",
+ arguments: {
+ THING: { type: Scratch.ArgumentType.STRING, menu: "SONG_ATTS" },
+ URL: { type: Scratch.ArgumentType.STRING, defaultValue: "1mCsF9Tw4AkIZOjvZbZZdT" }
+ }
+ },
+ "---",
{
opcode: "getSongURL",
blockType: Scratch.BlockType.REPORTER,
text: "get song from ID [URL]",
arguments: {
- URL: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "1mCsF9Tw4AkIZOjvZbZZdT"
- }
+ URL: { type: Scratch.ArgumentType.STRING, defaultValue: "1mCsF9Tw4AkIZOjvZbZZdT" }
}
},
{
@@ -88,10 +84,7 @@
blockType: Scratch.BlockType.COMMAND,
text: "play song from ID [URL]",
arguments: {
- URL: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "1mCsF9Tw4AkIZOjvZbZZdT"
- }
+ URL: { type: Scratch.ArgumentType.STRING, defaultValue: "1mCsF9Tw4AkIZOjvZbZZdT" }
}
},
{
@@ -99,81 +92,53 @@
blockType: Scratch.BlockType.COMMAND,
text: "stop all songs"
},
- "---",
- {
- opcode: "getSongAtt",
- blockType: Scratch.BlockType.REPORTER,
- text: "get [THING] from song ID [URL]",
- arguments: {
- THING: {
- type: Scratch.ArgumentType.STRING,
- menu: "SONG_ATTS",
- },
- URL: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "1mCsF9Tw4AkIZOjvZbZZdT"
- }
- }
- },
{ blockType: Scratch.BlockType.LABEL, text: "Artists" },
{
opcode: "getArtistAtt",
blockType: Scratch.BlockType.REPORTER,
text: "get [THING] from artist ID [URL]",
arguments: {
- THING: {
- type: Scratch.ArgumentType.STRING,
- menu: "ARTIST_ATTS",
- },
- URL: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "3bmFPbLMiLxtR9tFrTcKcP"
- }
+ THING: { type: Scratch.ArgumentType.STRING, menu: "ARTIST_ATTS" },
+ URL: { type: Scratch.ArgumentType.STRING, defaultValue: "3bmFPbLMiLxtR9tFrTcKcP" }
}
},
- { blockType: Scratch.BlockType.LABEL, text: "Playlists" },
+ { blockType: Scratch.BlockType.LABEL, text: "Playlists and Albums" },
{
opcode: "getPlaylistAtt",
blockType: Scratch.BlockType.REPORTER,
text: "get [THING] from playlist ID [URL]",
arguments: {
- THING: {
- type: Scratch.ArgumentType.STRING,
- menu: "PLAYLIST_ATTS",
- },
- URL: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "6pyzEFjkdLGH8gXowr8Pg7"
- }
+ THING: { type: Scratch.ArgumentType.STRING, menu: "PLAYLIST_ATTS" },
+ URL: { type: Scratch.ArgumentType.STRING, defaultValue: "1EuOcg2fA7aT9FAni72QBj" }
}
},
+ {
+ opcode: "getAlbumAtt",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get [THING] from album ID [URL]",
+ arguments: {
+ THING: { type: Scratch.ArgumentType.STRING, menu: "ALBUM_ATTS" },
+ URL: { type: Scratch.ArgumentType.STRING, defaultValue: "7caQVORQsbBXFR20EFcm8n" }
+ }
+ }
],
menus: {
TYPES: {
acceptReporters: true,
- items: ["song", "artist", "playlist"]
+ items: ["song", "artist", "playlist", "album"]
},
SEARCH_TYPES: {
acceptReporters: true,
items: [
- "song names",
- "artist names",
- "playlist names",
- "song IDs",
- "artist IDs",
- "playlist IDs"
+ "song names", "artist names", "playlist names",
+ "song IDs", "artist IDs", "playlist IDs"
]
},
SONG_ATTS: {
acceptReporters: true,
items: [
- "name",
- "artist",
- "artist ID",
- "cover",
- "release date",
- "length",
- "listens"
+ "name", "artist", "artist ID", "cover",
+ "release date", "length", "listens"
]
},
ARTIST_ATTS: {
@@ -183,13 +148,14 @@
PLAYLIST_ATTS: {
acceptReporters: true,
items: [
- "name",
- "creator",
- "cover",
- "description",
- "likes",
- "song count",
- "top 30 songs"
+ "name", "creator", "cover", "description",
+ "likes", "song count", "top 30 songs"
+ ]
+ },
+ ALBUM_ATTS: {
+ acceptReporters: true,
+ items: [
+ "name", "creator", "cover", "song count", "top 30 songs"
]
}
},
@@ -198,64 +164,45 @@
disclaimer() {
const overlay = document.createElement("div");
- overlay.style.cssText = "position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 9998;";
- const disclaimerContainer = document.createElement("div");
- disclaimerContainer.id = "disclaimer-container";
+ overlay.setAttribute("style", "position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 9998;");
+ const disclaimContain = document.createElement("div");
+ disclaimContain.id = "disclaimer-container";
+ disclaimContain.setAttribute("style", "position: fixed; width: 250px; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 9999; border: 5px outset #1db954; border-radius: 15px; padding: 10px; background-color: #18191A; color: white; font-family: arial;");
const closeButton = document.createElement("button");
closeButton.textContent = "Close";
- closeButton.style.cssText = "font-weight: bold; display: block; margin: auto; border: 3px solid #106e31; border-radius: 15px; padding: 10px; width: 100px; background-color: #1db954; color: white;";
+ closeButton.setAttribute("style", "font-weight: bold; display: block; margin: auto; border: 3px solid #106e31; border-radius: 15px; padding: 10px; width: 100px; background-color: #1db954; color: white;");
closeButton.addEventListener("click", () => {
- disclaimerContainer.remove();
+ disclaimContain.remove();
overlay.remove();
});
+
const image = document.createElement("img");
image.src = menuIconURI;
- image.alt = "Disclaimer Image";
- image.style.cssText = "width: 50px; margin-right: 10px; margin-bottom: -15px; display: inline-block;";
+ image.setAttribute("style", "width: 50px; margin-right: 10px; margin-bottom: -15px; display: inline-block;");
const title = document.createElement("h2");
title.textContent = "Disclaimer";
- title.style.cssText = "margin-bottom: 10px; text-align: center; color: white; display: inline-block;";
- const disclaimerText = document.createElement("p");
- disclaimerText.textContent = "Unfortunately, the Spotify API can only play 30-second previews of songs.";
- disclaimerText.style.cssText = "text-align: center; color: white;";
+ title.setAttribute("style", "margin-bottom: 10px; text-align: center; color: white; display: inline-block;");
+ const disclaimTxt = document.createElement("p");
+ disclaimTxt.innerHTML = "Unfortunately, Spotify only allows playing 30-second previews of songs.
It is against their Terms of Service to download full songs for commercial use.";
+ disclaimTxt.setAttribute("style", "text-align: center; color: white;");
- const contentContainer = document.createElement("div");
- contentContainer.appendChild(image);
- contentContainer.appendChild(title);
- contentContainer.appendChild(disclaimerText);
- contentContainer.style.textAlign = "center";
- disclaimerContainer.appendChild(contentContainer);
- disclaimerContainer.appendChild(closeButton);
-
- disclaimerContainer.style.cssText = "position: fixed; width: 250px; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 9999; border: 5px outset #1db954; border-radius: 15px; padding: 10px; background-color: #18191A; color: white; font-family: arial;";
- document.body.appendChild(overlay);
- document.body.appendChild(disclaimerContainer);
+ const contentContain = document.createElement("div");
+ contentContain.style.textAlign = "center";
+ contentContain.append(image, title, disclaimTxt);
+ disclaimContain.append(contentContain, closeButton);
+ document.body.append(overlay, disclaimContain);
}
replaceSpecial(inputArray) {
if (!inputArray) return inputArray;
const replacements = {
- "&": "&",
- "<": "<",
- ">": ">",
- "!": "!",
- "@": "@",
- "#": "#",
- "$": "$",
- "%": "%",
- "^": "^",
- "*": "*",
- "(": "(",
- ")": ")",
- "_": "_",
- "+": "+",
- "=": "=",
- "?": "?",
- "'": "'",
- """: "\"",
- "'": "'"
+ "&": "&", "<": "<", ">": ">", "!": "!",
+ "@": "@", "#": "#", "$": "$", "%": "%",
+ "^": "^", "*": "*", "(": "(", ")": ")",
+ "_": "_", "+": "+", "=": "=",
+ "?": "?", "'": "'", """: "\"", "'": "'"
};
return inputArray.map((inputString) => {
const regex = new RegExp(Object.keys(replacements).join("|"), "g");
@@ -277,45 +224,39 @@
const urlParts = args.URL.split("/");
if (urlParts[3]) return urlParts[3] === args.THING;
}
- return false;
+ return false;
}
async getSongURL(args) {
try {
let response;
- if (lastFetchedSong[0] === args.URL) {
- response = lastFetchedSong[1];
- } else {
- if (!await Scratch.canFetch(`${proxy}https://open.spotify.com/track/${args.URL}`)) return "";
+ if (lastFetched.s[0] === args.URL) response = lastFetched.s[1];
+ else {
+ if (!await Scratch.canFetch(`https://open.spotify.com/track/${args.URL}`)) return "";
// eslint-disable-next-line
response = await fetch(`${proxy}https://open.spotify.com/track/${args.URL}`);
if (!response.ok) return "Error: 404";
response = await response.text();
- lastFetchedSong = [args.URL, response];
+ lastFetched.s = [args.URL, response];
}
- const regex = / {
+ return new Promise((resolve) => {
// eslint-disable-next-line
- fetch(url)
- .then(response => {
- response.arrayBuffer()
- .then(arrayBuffer => {
- Scratch.vm.runtime.audioEngine.inputNode.context.decodeAudioData(arrayBuffer, (audioBuffer) => {
- resolve(audioBuffer);
- }, reject).catch(reject);
- })
- .catch(reject);
- })
- .catch(reject);
+ fetch(url).then(response => {
+ response.arrayBuffer().then(arrayBuffer => {
+ runtime.audioEngine.inputNode.context.decodeAudioData(arrayBuffer, (audioBuffer) => {
+ resolve(audioBuffer);
+ }, resolve()).catch(resolve());
+ }).catch(resolve());
+ }).catch(resolve());
});
}
@@ -325,8 +266,8 @@
let buffer = null;
try {
buffer = await this.getUrlAudioBuffer(url);
- } catch (err) {
- return console.warn("Couldn't decode AudioBuffer", err);
+ } catch (e) {
+ return console.warn("Couldn't decode AudioBuffer", e);
}
const node = this.audioNode.context.createBufferSource();
node.buffer = buffer;
@@ -346,157 +287,153 @@
async getSongAtt(args) {
try {
- let response;
- if (lastFetchedSong[0] === args.URL) {
- response = lastFetchedSong[1];
- } else {
- if (!await Scratch.canFetch(`${proxy}https://open.spotify.com/track/${args.URL}`)) return "";
+ let response, match;
+ if (lastFetched.s[0] === args.URL) response = lastFetched.s[1];
+ else {
+ if (!await Scratch.canFetch(`https://open.spotify.com/track/${args.URL}`)) return "";
// eslint-disable-next-line
response = await fetch(`${proxy}https://open.spotify.com/track/${args.URL}`);
if (!response.ok) return "Error: 404";
response = await response.text();
- lastFetchedSong = [args.URL, response];
+ lastFetched.s = [args.URL, response];
}
- let regex;
- let match;
switch (args.THING) {
case "artist":
- regex = /- song and lyrics by ([^|]+) \| Spotify<\/title>/;
- match = response.match(regex);
- match = this.replaceSpecial(match);
+ match = this.replaceSpecial(response.match(/]*name="music:musician_description"[^>]*content="([^"]+)"/));
return match && match[1] ? match[1] : "";
case "artist ID":
- regex = //;
- match = response.match(regex);
+ match = response.match(//);
return match && match[1] ? match[1] : "";
case "cover":
- regex = //;
- match = response.match(regex);
+ match = response.match(//);
return match && match[1] ? match[1] : "";
case "release date":
- regex = //;
- match = response.match(regex);
+ match = response.match(//);
return match && match[1] ? match[1] : "";
case "length":
- regex = //;
- match = response.match(regex);
- return match && match[1] ? match[1] + " seconds" : "";
+ match = response.match(//);
+ return match && match[1] ? parseFloat(match[1].replaceAll(",", "")) : "";
case "listens":
- regex = /"type">([^<]+)<\/span><\/a><\/div><\/div>