From 421fa1d6c44add3d1d45f898c4e499cc753c22d4 Mon Sep 17 00:00:00 2001 From: Cubester Date: Wed, 1 Jan 2025 18:34:50 -0500 Subject: [PATCH 1/4] Rich Presence extension! --- docs/CubesterYT/RichPresence.md | 89 +++++++++++ extensions/CubesterYT/RichPresence.js | 216 ++++++++++++++++++++++++++ extensions/extensions.json | 1 + 3 files changed, 306 insertions(+) create mode 100644 docs/CubesterYT/RichPresence.md create mode 100644 extensions/CubesterYT/RichPresence.js diff --git a/docs/CubesterYT/RichPresence.md b/docs/CubesterYT/RichPresence.md new file mode 100644 index 0000000000..ba773a112d --- /dev/null +++ b/docs/CubesterYT/RichPresence.md @@ -0,0 +1,89 @@ +# Rich Presence +This extension allows you to use Rich Presence in your projects. + +## Enabling Rich Presence + +The Rich Presence SDK will be automatically downloaded and enabled when a project using the Rich Presence extension is put into the [TurboWarp Packager](https://packager.turbowarp.org/). You will need to select one of these environments: + +## Basic Information + +Remember that Rich Presence is only properly enabled when your project is packaged in a few specific environments. You can detect if this is the case using: + +```scratch + +``` + +Then you can log in to the client using: + +```scratch +set client id () and log in :: #4D5057 +``` + +You can use this to fetch the current `client id`: + +```scratch +(client id :: #4D5057) +``` + +## Presence Data + +Presence Data is what is displayed when Rich Presence is active + +Using this will allow you to set data to your Rich Presence: + +```scratch +set [details v] to () :: #4D5057 +``` + +It has these menu options: + - `details`: Specify the details of the activity (Ex. "Traveling to Dango Land!") + - `state`: Specify the state of the activity (Ex. "Searching for Dangos") + - `large image key`: Specify the key of the image you want to display as the main image shown on the activity (Ex. "DangoCat" would be the name/key of an image you uploaded to Developer Assets) + - `large image text`: Specify the text that would display when hovering over the large image (Ex. "Dango Cat!") + - `small image key`: Same as `large image key` except this is for the smaller image that displays as a circle on the bottom right of the large image (Ex. "OdenDog" would be the name/key of the image you uploaded to Developer Assets) + - `small image text`: Same as `large image text` except this is for the small image (Ex. "Oden Dog!") + - `party size`: **[Must be an integer]** Displays next to `state` and specifies the current size of the party. It doesn't have to have anything to do with "parties", so it can be used in other cases (Ex. "2", could be used to say 2 dangos found) + - `party max`: **[Must be an integer]** Displays next to `party size` and specifies the max party size. Just like `party size`, it doesn't have to be used for "parties" only (Ex. "8", could be used to say 8 dangos total, together with `party size` and `state`, could say "Searching for Dangos (2 of 8)") + - `JSON`: Allows you to specify the JSON of the Presence Data yourself, so you can use other features not listed on your own. **[Note that `instance` will ALWAYS be overridden to be `false`]** + +Using this will allow you to toggle a timestamp on your Rich Presence: + +```scratch +turn timestamp [on v] :: #4D5057 +``` + +Finally, this will allow you to fetch the current Presence Data: + +```scratch +(rich presence data :: #4D5057) +``` + +## Setting the Presence + +To set the Rich Presence, you first need to check if the client is ready to go, use this to check if it's ready: + +```scratch + +``` + +Once it's ready, use this to set the presence: + +```scratch +update rich presence :: #4D5057 +``` + +Every time you want to update the presence, run the above block. Here's an example script: + +```scratch +when green flag clicked +if then + set client id (1234567890123456789) and log in :: #4D5057 + set [details v] to [Hello World!] :: #4D5057 + turn timestamp [on v] :: #4D5057 + forever + if then + update rich presence :: #4D5057 + wait (4) seconds +``` + +The Rich Presence SDK has a rate limit of 5 requests per 20 seconds, so you need to roughly follow that. As such, 4 seconds is what we recommend as the minimum. \ No newline at end of file diff --git a/extensions/CubesterYT/RichPresence.js b/extensions/CubesterYT/RichPresence.js new file mode 100644 index 0000000000..4c5ffcc59f --- /dev/null +++ b/extensions/CubesterYT/RichPresence.js @@ -0,0 +1,216 @@ +// Name: Rich Presence +// ID: cubesterRichPresence +// Description: Adds rich presence support to your project. +// By: CubesterYT + +// Version V.1.0.0 + +(function (Scratch) { + "use strict"; + + /* globals RPC */ + + const canUseRPC = typeof RPC !== "undefined"; + const client = canUseRPC && new RPC.Client({ transport: "ipc" }); + + let clientID = ""; + let clientData = {}; + let clientReady = false; + + if (canUseRPC) { + client.on("ready", () => { + clientReady = true; + }); + } + + class RichPresence { + getInfo() { + return { + id: "cubesterRichPresence", + name: "Rich Presence", + color1: "#4D5057", + docsURI: "https://extensions.turbowarp.org/CubesterYT/RichPresence", + + blocks: [ + { + opcode: "hasPresence", + text: Scratch.translate("has rich presence support?"), + blockType: Scratch.BlockType.BOOLEAN, + }, + { + opcode: "setClientID", + text: Scratch.translate("set client id [ID] and log in"), + blockType: Scratch.BlockType.COMMAND, + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + }, + }, + { + opcode: "clientID", + text: Scratch.translate("client id"), + blockType: Scratch.BlockType.REPORTER, + }, + + "---", + + { + opcode: "setData", + text: Scratch.translate("set [DATA] to [INPUT]"), + blockType: Scratch.BlockType.COMMAND, + arguments: { + DATA: { + type: Scratch.ArgumentType.STRING, + menu: "DATA", + }, + INPUT: { + type: Scratch.ArgumentType.STRING, + }, + }, + }, + { + opcode: "setTimestamp", + text: "turn timestamp [TOGGLE]", + blockType: Scratch.BlockType.COMMAND, + arguments: { + TOGGLE: { + type: Scratch.ArgumentType.STRING, + menu: "TOGGLE", + }, + }, + }, + { + opcode: "presenceData", + text: Scratch.translate("rich presence data"), + blockType: Scratch.BlockType.REPORTER, + }, + + "---", + + { + opcode: "presenceReady", + text: Scratch.translate("is rich presence ready?"), + blockType: Scratch.BlockType.BOOLEAN, + }, + { + opcode: "updatePresence", + text: Scratch.translate("update rich presence"), + blockType: Scratch.BlockType.COMMAND, + }, + ], + menus: { + DATA: { + acceptReporters: false, + items: [ + { text: Scratch.translate("details"), value: "details" }, + { text: Scratch.translate("state"), value: "state" }, + { + text: Scratch.translate("large image key"), + value: "large image key", + }, + { + text: Scratch.translate("large image text"), + value: "large image text", + }, + { + text: Scratch.translate("small image key"), + value: "small image key", + }, + { + text: Scratch.translate("small image text"), + value: "small image text", + }, + { text: Scratch.translate("party size"), value: "party size" }, + { text: Scratch.translate("party max"), value: "party max" }, + "JSON", + ], + }, + TOGGLE: { + acceptReporters: false, + items: ["on", "off"], + }, + }, + }; + } + + hasPresence() { + return canUseRPC; + } + setClientID(args) { + if (!canUseRPC) return; + args.ID = Scratch.Cast.toNumber(args.ID); + clientID = args.ID; + client.login({ clientID }).catch(console.error); + } + clientID() { + if (!canUseRPC) return "Rich Presence unavailable"; + return clientID; + } + setData(args) { + if (!canUseRPC) return; + args.INPUT = Scratch.Cast.toString(args.INPUT); + switch (args.DATA) { + case "details": + clientData.details = args.INPUT; + break; + case "state": + clientData.state = args.INPUT; + break; + case "large image key": + clientData.largeImageKey = args.INPUT; + break; + case "large image text": + clientData.largeImageText = args.INPUT; + break; + case "small image key": + clientData.smallImageKey = args.INPUT; + break; + case "small image text": + clientData.smallImageText = args.INPUT; + break; + case "party size": + clientData.partySize = Scratch.Cast.toNumber(args.INPUT); + break; + case "party max": + clientData.partyMax = Scratch.Cast.toNumber(args.INPUT); + case "JSON": + try { + clientData = JSON.parse(args.INPUT); + } catch (error) {} + break; + default: + return; // This shouldn't happen, but it's good to have. + } + } + setTimestamp(args) { + if (!canUseRPC) return; + switch (args.TOGGLE) { + case "on": + clientData.startTimestamp = Date.now(); + break; + case "off": + clientData.startTimestamp = null; + break; + default: + return; // This shouldn't happen, but it's good to have. + } + } + presenceData() { + if (!canUseRPC) return "Rich Presence unavailable"; + return JSON.stringify(clientData); + } + presenceReady() { + if (!canUseRPC) return false; + return clientReady; + } + updatePresence() { + if (!canUseRPC) return; + clientData.instance = false; + client.setActivity(clientData); + } + } + + Scratch.extensions.register(new RichPresence()); +})(Scratch); diff --git a/extensions/extensions.json b/extensions/extensions.json index 9027c00ac9..d0d0b16bcf 100644 --- a/extensions/extensions.json +++ b/extensions/extensions.json @@ -92,6 +92,7 @@ "veggiecan/LongmanDictionary", "CubesterYT/TurboHook", "Alestore/nfcwarp", + "CubesterYT/RichPresence", "steamworks", "itchio", "gamejolt", From 0b2b2d69dee107cfcec9e49c8dbccd7c96f6d29a Mon Sep 17 00:00:00 2001 From: Cubester Date: Wed, 1 Jan 2025 18:40:07 -0500 Subject: [PATCH 2/4] Whoops --- extensions/CubesterYT/RichPresence.js | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/CubesterYT/RichPresence.js b/extensions/CubesterYT/RichPresence.js index 4c5ffcc59f..86968af459 100644 --- a/extensions/CubesterYT/RichPresence.js +++ b/extensions/CubesterYT/RichPresence.js @@ -175,6 +175,7 @@ break; case "party max": clientData.partyMax = Scratch.Cast.toNumber(args.INPUT); + break; case "JSON": try { clientData = JSON.parse(args.INPUT); From c51cf5e937c7911b01a1aa48d8886a085f26bd03 Mon Sep 17 00:00:00 2001 From: Cubester Date: Wed, 1 Jan 2025 18:41:41 -0500 Subject: [PATCH 3/4] I keep forgetting about licenses --- extensions/CubesterYT/RichPresence.js | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/CubesterYT/RichPresence.js b/extensions/CubesterYT/RichPresence.js index 86968af459..fee6f8aa31 100644 --- a/extensions/CubesterYT/RichPresence.js +++ b/extensions/CubesterYT/RichPresence.js @@ -2,6 +2,7 @@ // ID: cubesterRichPresence // Description: Adds rich presence support to your project. // By: CubesterYT +// License: MPL-2.0 // Version V.1.0.0 From 4345a6c86f9c87fdcf6147c99b7f7fc0784822b9 Mon Sep 17 00:00:00 2001 From: Cubester Date: Wed, 1 Jan 2025 18:53:56 -0500 Subject: [PATCH 4/4] We should translate these as well --- extensions/CubesterYT/RichPresence.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extensions/CubesterYT/RichPresence.js b/extensions/CubesterYT/RichPresence.js index fee6f8aa31..d6d59c8eb4 100644 --- a/extensions/CubesterYT/RichPresence.js +++ b/extensions/CubesterYT/RichPresence.js @@ -130,7 +130,10 @@ }, TOGGLE: { acceptReporters: false, - items: ["on", "off"], + items: [ + { text: Scratch.translate("on"), value: "on" }, + { text: Scratch.translate("off"), value: "off" }, + ], }, }, };