From bd77c0cd4c7d778d36bfde05ceef81f6206fd6ed Mon Sep 17 00:00:00 2001 From: "Mike J. Renaker / \"MikeDEV" Date: Thu, 9 Apr 2026 21:12:19 -0400 Subject: [PATCH 01/11] Update CloudLink This is my (hopefully) final PR relating to Classic CloudLink. This is a slightly altered variant of CloudLink Improved, created by @Mistium (https://github.com/Mistium/extensions.mistium/blob/main/files%2FCloudlink4_Improved.js). This addresses various bugs encountered since the last major PR and enhances QoL. --- extensions/cloudlink.js | 1031 +++++++++++++++++++-------------------- 1 file changed, 513 insertions(+), 518 deletions(-) diff --git a/extensions/cloudlink.js b/extensions/cloudlink.js index ed7b621a4c..314e0a1888 100644 --- a/extensions/cloudlink.js +++ b/extensions/cloudlink.js @@ -1,11 +1,21 @@ -// Name: CloudLink V4 +// Name: Cloudlink // ID: cloudlink // Description: A powerful WebSocket extension for Scratch. // By: MikeDEV // License: MIT +/* eslint-disable */ +// prettier-ignore +/* generated l10n code */ +Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my username": "(VANHA - ÄLÄ KÄYTÄ UUSISSA PROJEKTEISSA) oma käyttäjänimi", "_A name": "nimi", "_All data": "kaikki data", "_Another name": "toinen nimi", "_Apple": "omena", "_Banana": "banaani", "_Direct data": "kohdennettu data", "_Global data": "globaali data", "_Global variables": "globaalit muuttujat", "_Hide old blocks": "Piilota vanhat lohkot", "_ID [ID] connected?": "onko tunniste [ID] yhdistetty?", "_Private data": "yksityinen data", "_Private variables": "yksityiset muuttujat", "_Show old blocks": "Näytä vanhat lohkot", "_Status code": "tilakoodi", "_When I receive new [TYPE] data for [VAR]": "kun vastaanotan uuden kohteen [TYPE] datan muuttujalle [VAR]", "_[NUM] from JSON array [ARRAY]": "[NUM] JSON-taulukossa [ARRAY]", "_[PATH] of [JSON_STRING]": "[PATH] JSON-koodissa [JSON_STRING]", "_attach listener [ID] to next packet": "lisää kuuntelija [ID] seuraavaan datapakettiin", "_clear all packets for [TYPE]": "tyhjennä kaikki kohteen [TYPE] datapaketit", "_connect to [IP]": "yhdistä palvelimeen [IP]", "_connect to server [ID]": "yhdistä palvelimeen nro [ID]", "_connected?": "onko yhdistetty?", "_convert [toBeJSONified] to JSON": "muunna [toBeJSONified] JSON-muotoon", "_direct": "kohdennettu", "_direct data": "kohdennettu data", "_disconnect": "katkaise yhteys", "_extension version": "laajennuksen versio", "_failed to connnect?": "epäonnistuiko yhteyden muodostaminen?", "_fetch data from URL [url]": "hae data URL-osoitteesta [url]", "_global data": "globaali data", "_got new [TYPE] data for variable [VAR]?": "onko uusi [TYPE] [VAR] data saapunut?", "_got new [TYPE]?": "onko uusi [TYPE] saapunut?", "_got new packet with listener [ID]?": "onko uusi datapaketti kuuntelijalla [ID] saapunut?", "_id": "tunniste", "_is [JSON_STRING] valid JSON?": "onko [JSON_STRING] kelvollista JSON-koodia?", "_link status": "yhteyden tila", "_link to room(s) [ROOMS]": "yhdistä huoneisiin [ROOMS]", "_linked to rooms?": "onko yhdistetty huoneisiin?", "_lost connection?": "katkesiko yhteys?", "_my IP address": "oma IP-osoite", "_my user object": "oma käyttäjäolio", "_my username": "oma käyttäjänimi", "_packet queue for [TYPE]": "kohteen [TYPE] datapakettijono", "_private data": "yksityinen data", "_reset got new [ID] listener status": "nollaa uusi kuuntelijan [ID] tila", "_reset got new [TYPE] [VAR] status": "nollaa uusi kohteen [TYPE] muuttujan [VAR] tila", "_reset got new [TYPE] status": "nollaa uusi kohteen [TYPE] tila", "_response for listener [ID]": "vastaus kuuntelijalle [ID]", "_select room(s) [ROOMS] for next packet": "valitse huoneet [ROOMS] seuraavalle datapaketille", "_send [DATA]": "lähetä [DATA]", "_send [DATA] to [ID]": "lähetä [DATA] käyttäjälle [ID]", "_send command [CMD] [ID] [DATA]": "lähetä komento [CMD] [ID] [DATA]", "_send command without ID [CMD] [DATA]": "lähetä komento ilman tunnistetta [CMD] [DATA]", "_send request with method [method] for URL [url] with data [data] and headers [headers]": "lähetä pyyntö menetelmällä [method] URL-osoitteeseen [url] datalla [data] ja otsakkeilla [headers]", "_send variable [VAR] to [ID] with data [DATA]": "lähetä muuttuja [VAR] käyttäjälle [ID] datalla [DATA]", "_send variable [VAR] with data [DATA]": "lähetä muuttuja [VAR] datalla [DATA]", "_server MOTD": "palvelimen viesti", "_server list": "palvelinluettelo", "_server version": "palvelimen versio", "_set [NAME] as username": "aseta käyttäjänimeksi [NAME]", "_size of queue for [TYPE]": "kohteen [TYPE] jonon koko", "_status code": "tilakoodi", "_unlink from all rooms": "katkaise yhteys kaikkiin huoneisiin", "_username synced?": "onko käyttäjänimi synkronoitu?", "_usernames": "käyttäjänimet", "_val": "arvo", "_when I receive new [TYPE] message": "kun vastaanotan uuden kohteen [TYPE] viestin", "_when I receive new message with listener [ID]": "kun vastaanotan uuden viestin kuuntelijalla [ID]", "_when connected": "kun yhteys muodostuu", "_when disconnected": "kun yhteys katkeaa" }, "nl": { "_[PATH] of [JSON_STRING]": "[PATH] van [JSON_STRING]", "_id": "ID" }, "ru": { "_[PATH] of [JSON_STRING]": "[PATH] из [JSON_STRING]", "_id": "ID" }, "zh-cn": { "_(OLD - DO NOT USE IN NEW PROJECTS) my username": "(旧版 - 不要在新项目中使用它) 我的用户名", "_A name": "一个名字", "_All data": "所有数据", "_Another name": "另一个名称", "_Apple": "苹果", "_Banana": "香蕉", "_Direct data": "直接数据", "_Global data": "全局数据", "_Global variables": "全局变量", "_Hide old blocks": "隐藏旧积木", "_ID [ID] connected?": "ID[ID]连接?", "_Private data": "私有数据", "_Private variables": "私有变量", "_Show old blocks": "显示旧积木", "_Status code": "状态码", "_When I receive new [TYPE] data for [VAR]": "当我收到新的用于[VAR]的[TYPE]信息", "_[NUM] from JSON array [ARRAY]": "JSON数组[ARRAY]的[NUM]", "_[PATH] of [JSON_STRING]": "[JSON_STRING]中的[PATH]", "_[TYPE] [VAR] data": "[TYPE][VAR]数据", "_attach listener [ID] to next packet": "附加监听器 [ID] 到下一个数据包", "_clear all packets for [TYPE]": "清空[TYPE]的所有数据包", "_connect to [IP]": "连接到[IP]", "_connect to server [ID]": "连接到服务器[ID]", "_connected?": "已连接?", "_convert [toBeJSONified] to JSON": "将[toBeJSONified]转为JSON", "_direct": "直接", "_direct data": "直接数据", "_disconnect": "断开连接", "_extension version": "扩展版本", "_failed to connnect?": "连接失败?", "_fetch data from URL [url]": "从 URL [url]获取数据", "_global data": "全局数据", "_got new [TYPE] data for variable [VAR]?": "收到新的用于变量[VAR]的[TYPE]数据?", "_got new [TYPE]?": "收到新的[TYPE]?", "_got new packet with listener [ID]?": "从监听器[ID]收到新的包?", "_id": "ID", "_is [JSON_STRING] valid JSON?": "[JSON_STRING]是合法JSON?", "_link status": "链接状态", "_link to room(s) [ROOMS]": "连接到房间(列表)[ROOMS]", "_linked to rooms?": "已连接到房间?", "_lost connection?": "连接丢失?", "_my IP address": "我的IP地址", "_my user object": "我的用户对象", "_my username": "我的用户名", "_packet queue for [TYPE]": "[TYPE]的包队列", "_private data": "私有数据", "_reset got new [ID] listener status": "重置收到新的[ID]监听器的状态", "_reset got new [TYPE] [VAR] status": "重置收到新的[TYPE][VAR]状态", "_reset got new [TYPE] status": "重置收到新的[TYPE]状态", "_response for listener [ID]": "监听器[ID]的回应", "_select room(s) [ROOMS] for next packet": "为下一个数据包选择房间(列表)[ROOMS]", "_send [DATA]": "发送[DATA]", "_send [DATA] to [ID]": "发送[DATA]给[ID]", "_send command [CMD] [ID] [DATA]": "发送命令[CMD][ID][DATA]", "_send command without ID [CMD] [DATA]": "发送没有ID[CMD][DATA]的命令", "_send request with method [method] for URL [url] with data [data] and headers [headers]": "发送[method]方法的请求给URL[url]携带数据[data]头部信息 [headers]", "_send variable [VAR] to [ID] with data [DATA]": "发送变量[VAR]给[ID]附带数据[DATA]", "_send variable [VAR] with data [DATA]": "发送变量[VAR]附带数据[DATA]", "_server MOTD": "服务器MOTD", "_server list": "服务器列表", "_server version": "服务器版本", "_set [NAME] as username": "设置[NAME]为用户名", "_size of queue for [TYPE]": "[TYPE]的队列大小", "_status code": "状态码", "_unlink from all rooms": "从所有房间断开连接", "_username synced?": "已同步用户名?", "_usernames": "用户名列表", "_when I receive new [TYPE] message": "当我收到新的[TYPE]信息", "_when I receive new message with listener [ID]": "当我通过监听器[ID]接收到新消息时`", "_when connected": "当建立连接", "_when disconnected": "当断开连接" } }); +/* end generated l10n code */ (function (Scratch) { + /* + + Based on https://github.com/Mistium/extensions.mistium/blob/main/files%2FCloudlink4_Improved.js. + Copyright (c) Mistium 2025. + CloudLink Extension for TurboWarp v0.1.2. This extension should be fully compatible with projects developed using @@ -34,9 +44,9 @@ */ // Require extension to be unsandboxed. - "use strict"; + 'use strict'; if (!Scratch.extensions.unsandboxed) { - throw new Error("The CloudLink extension must run unsandboxed."); + throw new Error('The CloudLink extension must run unsandboxed.'); } // Declare icons as static SVG URIs @@ -45,6 +55,7 @@ const cl_block = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxNzYuMzk4NTQiIGhlaWdodD0iMTIyLjY3MDY5IiB2aWV3Qm94PSIwLDAsMTc2LjM5ODU0LDEyMi42NzA2OSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE1MS44MDA3MywtMTE4LjY2NDY2KSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PGc+PHBhdGggZD0iTTI4Ni4xMjAzNywxNTcuMTc3NTVjMjMuMjQwODYsMCA0Mi4wNzg5LDE4LjgzOTQ2IDQyLjA3ODksNDIuMDc4OWMwLDIzLjIzOTQ0IC0xOC44MzgwMyw0Mi4wNzg5IC00Mi4wNzg5LDQyLjA3ODloLTkyLjI0MDc0Yy0yMy4yNDA4NiwwIC00Mi4wNzg5LC0xOC44Mzk0NiAtNDIuMDc4OSwtNDIuMDc4OWMwLC0yMy4yMzk0NCAxOC44MzgwMywtNDIuMDc4OSA0Mi4wNzg5LC00Mi4wNzg5aDQuMTg4ODdjMS44MTE1MywtMjEuNTcwNTUgMTkuODkzNTcsLTM4LjUxMjg5IDQxLjkzMTUsLTM4LjUxMjg5YzIyLjAzNzkzLDAgNDAuMTE5OTcsMTYuOTQyMzQgNDEuOTMxNSwzOC41MTI4OXoiIGZpbGw9IiNmZmZmZmYiLz48cGF0aCBkPSJNMjg5LjA4NjU1LDIxNi45NjA3NHY5LjA0NjY3aC0yNi45MTY2M2gtOS4wNDY2N3YtOS4wNDY2N3YtNTQuNTAzMzloOS4wNDY2N3Y1NC41MDMzOXoiIGZpbGw9IiMwMGMyOGMiLz48cGF0aCBkPSJNMjIyLjQwOTI1LDIyNi4wMDc0MWMtOC4zNTMyLDAgLTE2LjM2NDMxLC0zLjMxODM0IC0yMi4yNzA5LC05LjIyNDkyYy01LjkwNjYxLC01LjkwNjU4IC05LjIyNDkxLC0xMy45MTc2OCAtOS4yMjQ5MSwtMjIuMjcwODljMCwtOC4zNTMyIDMuMzE4MjksLTE2LjM2NDMxIDkuMjI0OTEsLTIyLjI3MDljNS45MDY1OSwtNS45MDY2MSAxMy45MTc3LC05LjIyNDkxIDIyLjI3MDksLTkuMjI0OTFoMjEuMTA4OXY4LjkzNDk4aC0yMS4xMDg5djAuMTAyNTdjLTUuOTU2MjgsMCAtMTEuNjY4NjQsMi4zNjYxNiAtMTUuODgwMzcsNi41Nzc4OWMtNC4yMTE3Myw0LjIxMTczIC02LjU3Nzg5LDkuOTI0MDggLTYuNTc3ODksMTUuODgwMzdjMCw1Ljk1NjI4IDIuMzY2MTYsMTEuNjY4NjQgNi41Nzc4OSwxNS44ODAzN2M0LjIxMTczLDQuMjExNzMgOS45MjQwOCw2LjU3NzkzIDE1Ljg4MDM3LDYuNTc3OTN2MC4xMDI1M2gyMS4xMDg5djguOTM0OTh6IiBmaWxsPSIjMDBjMjhjIi8+PC9nPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjg4LjE5OTI2OTk5OTk5OTk4OjYxLjMzNTM0NDk5OTk5OTk5LS0+"; + // Declare VM const vm = Scratch.vm; const runtime = vm.runtime; @@ -78,6 +89,7 @@ // Store extension state var clVars = { + // Editor-specific variable for hiding old, legacy-support blocks. hideCLDeprecatedBlocks: true, @@ -231,25 +243,15 @@ handshakeAttempted: false, // Storage for the publically available CloudLink instances. - serverList: { - 0: { - id: "Localhost", - url: "ws://127.0.0.1:3000/", - }, - 7: { - id: "MikeDEV's Spare CL 0.2.0 Server", - url: "wss://cl.mikedev101.cc/", - }, - }, - }; + serverList: {}, + } function generateVersionString() { return `${version.editorType} ${version.versionString}`; } // Makes values safe for Scratch to represent. - // eslint-disable-next-line require-await - async function makeValueScratchSafe(data) { + function makeValueScratchSafe(data) { if (typeof data == "object") { try { return JSON.stringify(data); @@ -334,48 +336,41 @@ function sendMessage(message) { // Prevent running this while disconnected if (clVars.socket == null) { - console.warn( - "[CloudLink] Ignoring attempt to send a packet while disconnected." - ); + //console.warn("[CloudLink] Ignoring attempt to send a packet while disconnected."); return; } // See if the outgoing val argument can be converted into JSON - if (Object.prototype.hasOwnProperty.call(message, "val")) { + if (message.hasOwnProperty("val")) { try { message.val = JSON.parse(message.val); - } catch {} + } catch { } } // Attach listeners if (clVars.listeners.enablerState) { + // 0.1.8.x was the first server version to support listeners. if (clVars.linkState.identifiedProtocol >= 2) { message.listener = clVars.listeners.enablerValue; // Create listener - clVars.listeners.varStates[message.listener] = { + clVars.listeners.varStates[String(args.ID)] = { hasNew: false, varState: {}, eventHatTick: false, }; + } else { - console.warn( - "[CloudLink] Server is too old! Must be at least 0.1.8.x to support listeners." - ); + //console.warn("[CloudLink] Server is too old! Must be at least 0.1.8.x to support listeners."); } clVars.listeners.enablerState = false; } // Check if server supports rooms - if ( - (message.cmd == "link" || message.cmd == "unlink") && - clVars.linkState.identifiedProtocol < 2 - ) { + if (((message.cmd == "link") || (message.cmd == "unlink")) && (clVars.linkState.identifiedProtocol < 2)) { // 0.1.8.x was the first server version to support rooms. - console.warn( - "[CloudLink] Server is too old! Must be at least 0.1.8.x to support room linking/unlinking." - ); + //console.warn("[CloudLink] Server is too old! Must be at least 0.1.8.x to support room linking/unlinking."); return; } @@ -384,22 +379,19 @@ try { outgoing = JSON.stringify(message); } catch (SyntaxError) { - console.warn( - "[CloudLink] Failed to send a packet, invalid syntax:", - message - ); + //console.warn("[CloudLink] Failed to send a packet, invalid syntax:", message); return; } // Send the message - console.log("[CloudLink] TX:", message); + //console.log("[CloudLink] TX:", message); clVars.socket.send(outgoing); } // Only sends the handshake command. function sendHandshake() { if (clVars.handshakeAttempted) return; - console.log("[CloudLink] Sending handshake..."); + //console.log("[CloudLink] Sending handshake..."); sendMessage({ cmd: "handshake", val: { @@ -409,15 +401,14 @@ versionNumber: version.versionNumber, }, }, - listener: "handshake_cfg", + listener: "handshake_cfg" }); clVars.handshakeAttempted = true; } // Compare the version string of the server to known compatible variants to configure clVars.linkState.identifiedProtocol. - // eslint-disable-next-line require-await async function setServerVersion(version) { - console.log(`[CloudLink] Server version: ${String(version)}`); + //console.log(`[CloudLink] Server version: ${String(version)}`); clVars.server_version = version; // Auto-detect versions @@ -430,17 +421,16 @@ "S2.2": 0, // 0.1.5 "0.1.": 0, // 0.1.5 or legacy "S2.": 0, // Legacy - "S1.": -1, // Obsolete + "S1.": -1 // Obsolete }; for (const [key, value] of Object.entries(versions)) { if (version.includes(key)) { if (clVars.linkState.identifiedProtocol < value) { + // Disconnect if protcol is too old if (value == -1) { - console.warn( - `[CloudLink] Server is too old to enable leagacy support. Disconnecting.` - ); + //console.warn(`[CloudLink] Server is too old to enable leagacy support. Disconnecting.`); return clVars.socket.close(1000, ""); } @@ -448,35 +438,27 @@ clVars.linkState.identifiedProtocol = value; } } - } + }; // Log configured spec version - console.log( - `[CloudLink] Configured protocol spec to v${clVars.linkState.identifiedProtocol}.` - ); + // // ////console.log(`[CloudLink] Configured protocol spec to v${clVars.linkState.identifiedProtocol}.`); // Fix timing bug clVars.linkState.status = 2; // Fire event hats (only one not broken) - runtime.startHats("cloudlink_onConnect"); + runtime.startHats('cloudlink_onConnect'); // Don't nag user if they already trusted this server if (clVars.currentServerUrl === clVars.lastServerUrl) return; // Ask user if they wish to stay connected if the server is unsupported - if ( - clVars.linkState.identifiedProtocol < 4 && - !confirm( - `You have connected to an old CloudLink server, running version ${clVars.server_version}.\n\nFor your security and privacy, we recommend you disconnect from this server and connect to an up-to-date server.\n\nClick/tap "OK" to stay connected.` - ) - ) { + if ((clVars.linkState.identifiedProtocol < 4) && (!confirm( + `You have connected to an old CloudLink server, running version ${clVars.server_version}.\n\nFor your security and privacy, we recommend you disconnect from this server and connect to an up-to-date server.\n\nClick/tap \"OK\" to stay connected.` + ))) { // Close the connection if they choose "Cancel" clVars.linkState.isAttemptingGracefulDisconnect = true; - clVars.socket.close( - 1000, - "Client going away (legacy server rejected by end user)" - ); + clVars.socket.close(1000, "Client going away (legacy server rejected by end user)"); return; } @@ -489,24 +471,18 @@ // Parse the message JSON let packet = {}; try { - packet = JSON.parse(data); + packet = JSON.parse(data) } catch (SyntaxError) { - console.error( - "[CloudLink] Incoming message parse failure! Is this really a CloudLink server?", - data - ); + //console.error("[CloudLink] Incoming message parse failure! Is this really a CloudLink server?", data); return; - } + }; // Handle packet commands - if (!Object.prototype.hasOwnProperty.call(packet, "cmd")) { - console.error( - '[CloudLink] Incoming message read failure! This message doesn\'t contain the required "cmd" key. Is this really a CloudLink server?', - packet - ); + if (!packet.hasOwnProperty("cmd")) { + //console.error("[CloudLink] Incoming message read failure! This message doesn't contain the required \"cmd\" key. Is this really a CloudLink server?", packet); return; } - console.log("[CloudLink] RX:", packet); + //console.log("[CloudLink] RX:", packet); switch (packet.cmd) { case "gmsg": clVars.gmsg.varState = packet.val; @@ -544,7 +520,7 @@ case "direct": // Handle events from older server versions - if (Object.prototype.hasOwnProperty.call(packet.val, "cmd")) { + if (packet.val.hasOwnProperty("cmd")) { switch (packet.val.cmd) { // Server 0.1.5 (at least) case "vers": @@ -554,9 +530,7 @@ // Server 0.1.7 (at least) case "motd": - console.log( - `[CloudLink] Message of the day: "${packet.val.val}"` - ); + //console.log(`[CloudLink] Message of the day: \"${packet.val.val}\"`); clVars.motd = packet.val.val; return; } @@ -570,7 +544,7 @@ break; case "client_obj": - console.log("[CloudLink] Client object for this session:", packet.val); + //console.log("[CloudLink] Client object for this session:", packet.val); clVars.myUserObject = packet.val; break; @@ -578,9 +552,7 @@ // Store direct value // Protocol v0 (0.1.5 and legacy) don't implement status codes. if (clVars.linkState.identifiedProtocol == 0) { - console.warn( - "[CloudLink] Received a statuscode message while using protocol v0. This event shouldn't happen. It's likely that this server is modified (did MikeDEV overlook some unexpected behavior?)." - ); + //console.warn("[CloudLink] Received a statuscode message while using protocol v0. This event shouldn't happen. It's likely that this server is modified (did MikeDEV overlook some unexpected behavior?)."); return; } @@ -592,30 +564,28 @@ // Protocol v2 (0.1.8.x) uses "code" instead. // Protocol v3-v4 (0.1.9.x - latest, 0.2.0) adds "code_id" to the payload. Ignored by Scratch clients. else { + // Handle setup listeners - if (Object.prototype.hasOwnProperty.call(packet, "listener")) { + if (packet.hasOwnProperty("listener")) { switch (packet.listener) { case "username_cfg": + // Username accepted if (packet.code.includes("I:100")) { clVars.myUserObject = packet.val; clVars.username.value = packet.val.username; clVars.username.accepted = true; - console.log( - `[CloudLink] Username has been set to "${clVars.username.value}" successfully!` - ); + //console.log(`[CloudLink] Username has been set to \"${clVars.username.value}\" successfully!`); // Username rejected / error } else { - console.log( - `[CloudLink] Username rejected by the server! Error code ${packet.code}.}` - ); + //console.log(`[CloudLink] Username rejected by the server! Error code ${packet.code}.}`); } return; case "handshake_cfg": // Prevent handshake responses being stored in the statuscode variables - console.log("[CloudLink] Server responded to our handshake!"); + //console.log("[CloudLink] Server responded to our handshake!"); return; case "link": @@ -624,13 +594,11 @@ if (packet.code.includes("I:100")) { clVars.rooms.isAttemptingLink = false; clVars.rooms.isLinked = true; - console.log("[CloudLink] Room linked successfully!"); + //console.log("[CloudLink] Room linked successfully!"); // Room link rejected / error } else { - console.log( - `[CloudLink] Room link rejected! Error code ${packet.code}.}` - ); + //console.log(`[CloudLink] Room link rejected! Error code ${packet.code}.}`); } return; @@ -640,13 +608,11 @@ if (packet.code.includes("I:100")) { clVars.rooms.isAttemptingUnlink = false; clVars.rooms.isLinked = false; - console.log("[CloudLink] Room unlinked successfully!"); + //console.log("[CloudLink] Room unlinked successfully!"); // Room link rejected / error } else { - console.log( - `[CloudLink] Room unlink rejected! Error code ${packet.code}.}` - ); + //console.log(`[CloudLink] Room unlink rejected! Error code ${packet.code}.}`); } return; } @@ -665,25 +631,21 @@ case "ulist": // Protocol v0-v1 (0.1.5 and legacy - 0.1.7) use a semicolon (;) separated string for the userlist. if ( - clVars.linkState.identifiedProtocol == 0 || - clVars.linkState.identifiedProtocol == 1 + (clVars.linkState.identifiedProtocol == 0) + || + (clVars.linkState.identifiedProtocol == 1) ) { // Split the username list string - clVars.ulist = String(packet.val).split(";"); + clVars.ulist = String(packet.val).split(';'); // Get rid of blank entry at the end of the list - clVars.ulist.pop(); + clVars.ulist.pop(clVars.ulist.length); // Check if username has been set (since older servers don't implement statuscodes or listeners) - if ( - clVars.username.attempted && - clVars.ulist.includes(clVars.username.temp) - ) { + if ((clVars.username.attempted) && (clVars.ulist.includes(clVars.username.temp))) { clVars.username.value = clVars.username.temp; clVars.username.accepted = true; - console.log( - `[CloudLink] Username has been set to "${clVars.username.value}" successfully!` - ); + //console.log(`[CloudLink] Username has been set to \"${clVars.username.value}\" successfully!`); } } @@ -695,32 +657,40 @@ // Protocol v3-v4 (0.1.9.x - latest, 0.2.0) uses "mode" to add/set/remove entries to the userlist. else { // Check for "mode" key - if (!Object.prototype.hasOwnProperty.call(packet, "mode")) { - console.warn( - '[CloudLink] Userlist message did not specify "mode" while running in protocol mode 3 or 4.' - ); + if (!packet.hasOwnProperty("mode")) { + //console.warn("[CloudLink] Userlist message did not specify \"mode\" while running in protocol mode 3 or 4."); return; - } + }; // Handle methods switch (packet.mode) { - case "set": + case 'set': clVars.ulist = packet.val; break; - case "add": + case 'add': clVars.ulist.push(packet.val); + clVars.recentlyJoinedUser = packet.val; + Scratch.vm.runtime.startHats('cloudlink_whenuserconnects'); break; - case "remove": - clVars.ulist.slice(clVars.ulist.indexOf(packet.val), 1); + case 'remove': + let index = -1 + for (let i = 0; i < clVars.ulist.length; i++) { + let user = clVars.ulist[i] + if (user.uuid == packet.val.uuid) { + index = i + break; + } + } + clVars.ulist.splice(index, 1); + clVars.recentlyLeftUser = packet.val; + Scratch.vm.runtime.startHats('cloudlink_whenuserdisconnects'); break; default: - console.warn( - `[CloudLink] Unrecognised userlist mode: "${packet.mode}".` - ); + //console.warn(`[CloudLink] Unrecognised userlist mode: \"${packet.mode}\".`); break; } } - console.log("[CloudLink] Updating userlist:", clVars.ulist); + //console.log("[CloudLink] Updating userlist:", clVars.ulist); break; case "server_version": @@ -729,26 +699,25 @@ break; case "client_ip": - console.log(`[CloudLink] Client IP address: ${packet.val}`); - console.warn( - "[CloudLink] This server has relayed your identified IP address to you. Under normal circumstances, this will be erased server-side when you disconnect, but you should still be careful. Unless you trust this server, it is not recommended to send login credentials or personal info." - ); + //console.log(`[CloudLink] Client IP address: ${packet.val}`); + //console.warn("[CloudLink] This server has relayed your identified IP address to you. Under normal circumstances, this will be erased server-side when you disconnect, but you should still be careful. Unless you trust this server, it is not recommended to send login credentials or personal info."); clVars.client_ip = packet.val; break; case "motd": - console.log(`[CloudLink] Message of the day: "${packet.val}"`); + //console.log(`[CloudLink] Message of the day: \"${packet.val}\"`); clVars.motd = packet.val; break; default: - console.warn(`[CloudLink] Unrecognised command: "${packet.cmd}".`); + //console.warn(`[CloudLink] Unrecognised command: \"${packet.cmd}\".`); return; } // Handle listeners - if (Object.prototype.hasOwnProperty.call(packet, "listener")) { + if (packet.hasOwnProperty("listener")) { if (clVars.listeners.current.includes(String(packet.listener))) { + // Remove the listener from the currently listening list clVars.listeners.current.splice( clVars.listeners.current.indexOf(String(packet.listener)), @@ -768,9 +737,7 @@ // Basic netcode needed to make the extension work async function newClient(url) { if (!(await Scratch.canFetch(url))) { - console.warn( - "[CloudLink] Did not get permission to connect, aborting..." - ); + //console.warn("[CloudLink] Did not get permission to connect, aborting..."); return; } @@ -779,12 +746,11 @@ clVars.linkState.disconnectType = 0; // Establish a connection to the server - console.log("[CloudLink] Connecting to server:", url); + //console.log("[CloudLink] Connecting to server:", url); try { - // eslint-disable-next-line extension/check-can-fetch clVars.socket = new WebSocket(url); } catch (e) { - console.warn("[CloudLink] An exception has occurred:", e); + //console.warn("[CloudLink] An exception has occurred:", e); return; } @@ -793,13 +759,11 @@ clVars.currentServerUrl = url; // Set the link state to connected. - console.log("[CloudLink] Connected."); + //console.log("[CloudLink] Connected."); // If a server_version message hasn't been received in over half a second, try to broadcast a handshake clVars.handshakeTimeout = window.setTimeout(function () { - console.log( - "[CloudLink] Hmm... This server hasn't sent us it's server info. Going to attempt a handshake." - ); + //console.log("[CloudLink] Hmm... This server hasn't sent us it's server info. Going to attempt a handshake."); sendHandshake(); }, 500); @@ -817,27 +781,20 @@ switch (clVars.linkState.status) { case 1: // Was connecting // Set the link state to ungraceful disconnect. - console.log(`[CloudLink] Connection failed (${event.code}).`); + //console.log(`[CloudLink] Connection failed (${event.code}).`); clVars.linkState.status = 4; clVars.linkState.disconnectType = 1; break; case 2: // Was already connected - if ( - event.wasClean || - clVars.linkState.isAttemptingGracefulDisconnect - ) { + if (event.wasClean || clVars.linkState.isAttemptingGracefulDisconnect) { // Set the link state to graceful disconnect. - console.log( - `[CloudLink] Disconnected (${event.code} ${event.reason}).` - ); + //console.log(`[CloudLink] Disconnected (${event.code} ${event.reason}).`); clVars.linkState.status = 3; clVars.linkState.disconnectType = 0; } else { // Set the link state to ungraceful disconnect. - console.log( - `[CloudLink] Lost connection (${event.code} ${event.reason}).` - ); + //console.log(`[CloudLink] Lost connection (${event.code} ${event.reason}).`); clVars.linkState.status = 4; clVars.linkState.disconnectType = 2; } @@ -848,39 +805,59 @@ resetOnClose(); // Run all onClose event blocks - runtime.startHats("cloudlink_onClose"); + runtime.startHats('cloudlink_onClose'); // Return promise (during setup) return; - }; + } + } + + // GET the serverList + try { + Scratch.fetch( + "https://raw.githubusercontent.com/MikeDev101/cloudlink/master/serverlist.json" + ) + .then((response) => { + return response.text(); + }) + .then((data) => { + clVars.serverList = JSON.parse(data); + }) + .catch((err) => { + //console.log("[CloudLink] An error has occurred while parsing the public server list:", err); + clVars.serverList = {}; + }); + } catch (err) { + //console.log("[CloudLink] An error has occurred while fetching the public server list:", err); + clVars.serverList = {}; } // Declare the CloudLink library. class CloudLink { getInfo() { return { - id: "cloudlink", - // eslint-disable-next-line extension/should-translate - name: "CloudLink V4", + id: 'cloudlink', + name: 'CloudLink', blockIconURI: cl_block, menuIconURI: cl_icon, docsURI: "https://github.com/MikeDev101/cloudlink/wiki/Scratch-Client", blocks: [ + { opcode: "returnGlobalData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("global data"), + text: Scratch.translate("global data") }, { opcode: "returnPrivateData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("private data"), + text: Scratch.translate("private data") }, { opcode: "returnDirectData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("direct data"), + text: Scratch.translate("direct data") }, "---", @@ -888,13 +865,13 @@ { opcode: "returnLinkData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("link status"), + text: Scratch.translate("link status") }, { opcode: "returnStatusCode", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("status code"), + text: Scratch.translate("status code") }, "---", @@ -902,22 +879,20 @@ { opcode: "returnUserListData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("usernames"), + text: Scratch.translate("usernames") }, { opcode: "returnUsernameDataNew", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("my username"), + text: Scratch.translate("my username") }, { opcode: "returnUsernameData", blockType: Scratch.BlockType.REPORTER, hideFromPalette: clVars.hideCLDeprecatedBlocks, - text: Scratch.translate( - "(OLD - DO NOT USE IN NEW PROJECTS) my username" - ), + text: Scratch.translate("(OLD - DO NOT USE IN NEW PROJECTS) my username") }, "---", @@ -925,25 +900,25 @@ { opcode: "returnVersionData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("extension version"), + text: Scratch.translate("extension version") }, { opcode: "returnServerVersion", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("server version"), + text: Scratch.translate("server version") }, { opcode: "returnServerList", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("server list"), + text: Scratch.translate("server list") }, { opcode: "returnMOTD", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("server MOTD"), + text: Scratch.translate("server MOTD") }, "---", @@ -951,13 +926,13 @@ { opcode: "returnClientIP", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("my IP address"), + text: Scratch.translate("my IP address") }, { opcode: "returnUserObject", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("my user object"), + text: Scratch.translate("my user object") }, "---", @@ -1031,12 +1006,11 @@ arguments: { PATH: { type: Scratch.ArgumentType.STRING, - defaultValue: "fruit/apples", + defaultValue: 'fruit/apples', }, JSON_STRING: { type: Scratch.ArgumentType.STRING, - defaultValue: - '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', + defaultValue: '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', }, }, }, @@ -1045,7 +1019,7 @@ opcode: "getFromJSONArray", blockType: Scratch.BlockType.REPORTER, hideFromPalette: clVars.hideCLDeprecatedBlocks, - text: Scratch.translate("[NUM] from JSON array [ARRAY]"), + text: Scratch.translate('[NUM] from JSON array [ARRAY]'), arguments: { NUM: { type: Scratch.ArgumentType.NUMBER, @@ -1054,8 +1028,8 @@ ARRAY: { type: Scratch.ArgumentType.STRING, defaultValue: '["foo","bar"]', - }, - }, + } + } }, { @@ -1079,12 +1053,12 @@ arguments: { JSON_STRING: { type: Scratch.ArgumentType.STRING, - defaultValue: - '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', + defaultValue: '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', }, }, }, + "---", { @@ -1104,9 +1078,7 @@ opcode: "requestURL", blockType: Scratch.BlockType.REPORTER, hideFromPalette: clVars.hideCLDeprecatedBlocks, - text: Scratch.translate( - "send request with method [method] for URL [url] with data [data] and headers [headers]" - ), + text: Scratch.translate("send request with method [method] for URL [url] with data [data] and headers [headers]"), arguments: { method: { type: Scratch.ArgumentType.STRING, @@ -1148,9 +1120,7 @@ { opcode: "onListener", blockType: Scratch.BlockType.HAT, - text: Scratch.translate( - "when I receive new message with listener [ID]" - ), + text: Scratch.translate("when I receive new message with listener [ID]"), isEdgeActivated: true, arguments: { ID: { @@ -1287,9 +1257,9 @@ arguments: { IP: { type: Scratch.ArgumentType.STRING, - defaultValue: "wss://cl.mikedev101.cc/", - }, - }, + defaultValue: "ws://127.0.0.1:3000/", + } + } }, { @@ -1299,15 +1269,15 @@ arguments: { ID: { type: Scratch.ArgumentType.NUMBER, - defaultValue: "7", - }, - }, + defaultValue: 1, + } + } }, { opcode: "closeSocket", blockType: Scratch.BlockType.COMMAND, - text: Scratch.translate("disconnect"), + text: Scratch.translate("disconnect") }, "---", @@ -1336,20 +1306,21 @@ defaultValue: "example-listener", }, }, + }, "---", { - opcode: "linkToRooms", + opcode: 'linkToRooms', blockType: Scratch.BlockType.COMMAND, text: Scratch.translate("link to room(s) [ROOMS]"), arguments: { ROOMS: { type: Scratch.ArgumentType.STRING, - defaultValue: JSON.stringify(["test"]), + defaultValue: Scratch.translate('["test"]'), }, - }, + } }, { @@ -1359,7 +1330,7 @@ arguments: { ROOMS: { type: Scratch.ArgumentType.STRING, - defaultValue: JSON.stringify(["test"]), + defaultValue: Scratch.translate('["test"]'), }, }, }, @@ -1419,9 +1390,7 @@ { opcode: "sendPDataAsVar", blockType: Scratch.BlockType.COMMAND, - text: Scratch.translate( - "send variable [VAR] to [ID] with data [DATA]" - ), + text: Scratch.translate("send variable [VAR] to [ID] with data [DATA]"), arguments: { DATA: { type: Scratch.ArgumentType.STRING, @@ -1526,6 +1495,47 @@ "---", + { + opcode: "getNextPacket", + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate("pop next packet for [TYPE]"), + arguments: { + TYPE: { + type: Scratch.ArgumentType.STRING, + menu: "allmenu", + defaultValue: "All data", + }, + }, + }, + + { + opcode: "newPacketsExist", + blockType: Scratch.BlockType.BOOLEAN, + text: Scratch.translate("new packets exist for [TYPE]"), + arguments: { + TYPE: { + type: Scratch.ArgumentType.STRING, + menu: "allmenu", + defaultValue: "All data", + }, + }, + }, + + { + opcode: "getAndClearPacketQueue", + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate("pop all packets for [TYPE]"), + arguments: { + TYPE: { + type: Scratch.ArgumentType.STRING, + menu: "allmenu", + defaultValue: "All data", + }, + }, + }, + + "---", + { opcode: "clearAllPackets", blockType: Scratch.BlockType.COMMAND, @@ -1536,7 +1546,33 @@ menu: "allmenu", defaultValue: "All data", }, - }, + } + }, + + { + opcode: "whenuserdisconnects", + blockType: Scratch.BlockType.EVENT, + text: Scratch.translate("when any user disconnects"), + isEdgeActivated: false, + }, + + { + opcode: "whenuserconnects", + blockType: Scratch.BlockType.EVENT, + text: Scratch.translate("when any user connects"), + isEdgeActivated: false, + }, + + { + opcode: "recentlyjoined", + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate("recently joined user"), + }, + + { + opcode: "recentlyleft", + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate("recently left user"), }, "---", @@ -1556,71 +1592,45 @@ }, "---", + ], menus: { datamenu: { items: [ - { text: Scratch.translate("Global data"), value: "Global data" }, - { - text: Scratch.translate("Private data"), - value: "Private data", - }, - { text: Scratch.translate("Direct data"), value: "Direct data" }, - { text: Scratch.translate("Status code"), value: "Status code" }, - ], + { text: Scratch.translate('Global data'), value: 'Global data' }, + { text: Scratch.translate('Private data'), value: 'Private data' }, + { text: Scratch.translate('Direct data'), value: 'Direct data' }, + { text: Scratch.translate('Status code'), value: 'Status code' } + ] }, varmenu: { items: [ - { - text: Scratch.translate("Global variables"), - value: "Global variables", - }, - { - text: Scratch.translate("Private variables"), - value: "Private variables", - }, - ], + { text: Scratch.translate('Global variables'), value: "Global variables" }, + { text: Scratch.translate('Private variables'), value: "Private variables" } + ] }, allmenu: { items: [ - { text: Scratch.translate("Global data"), value: "Global data" }, - { - text: Scratch.translate("Private data"), - value: "Private data", - }, - { text: Scratch.translate("Direct data"), value: "Direct data" }, - { text: Scratch.translate("Status code"), value: "Status code" }, - { - text: Scratch.translate("Global variables"), - value: "Global variables", - }, - { - text: Scratch.translate("Private variables"), - value: "Private variables", - }, - { text: Scratch.translate("All data"), value: "All data" }, - ], + { text: Scratch.translate('Global data'), value: 'Global data' }, + { text: Scratch.translate('Private data'), value: 'Private data' }, + { text: Scratch.translate('Direct data'), value: 'Direct data' }, + { text: Scratch.translate('Status code'), value: 'Status code' }, + { text: Scratch.translate("Global variables"), value: "Global variables" }, + { text: Scratch.translate("Private variables"), value: "Private variables" }, + { text: Scratch.translate("All data"), value: "All data" } + ] }, almostallmenu: { items: [ - { text: Scratch.translate("Global data"), value: "Global data" }, - { - text: Scratch.translate("Private data"), - value: "Private data", - }, - { text: Scratch.translate("Direct data"), value: "Direct data" }, - { text: Scratch.translate("Status code"), value: "Status code" }, - { - text: Scratch.translate("Global variables"), - value: "Global variables", - }, - { - text: Scratch.translate("Private variables"), - value: "Private variables", - }, - ], + { text: Scratch.translate('Global data'), value: 'Global data' }, + { text: Scratch.translate('Private data'), value: 'Private data' }, + { text: Scratch.translate('Direct data'), value: 'Direct data' }, + { text: Scratch.translate('Status code'), value: 'Status code' }, + { text: Scratch.translate("Global variables"), value: "Global variables" }, + { text: Scratch.translate("Private variables"), value: "Private variables" } + ] }, - }, + } }; } @@ -1715,35 +1725,111 @@ // Reporter - Returns data for a specific listener ID. // ID - String (listener ID) returnListenerData(args) { - if ( - !Object.prototype.hasOwnProperty.call( - clVars.listeners.varStates, - String(args.ID) - ) - ) { - console.warn(`[CloudLink] Listener ID ${args.ID} does not exist!`); + if (!clVars.listeners.varStates.hasOwnProperty(String(args.ID))) { + //console.warn(`[CloudLink] Listener ID ${args.ID} does not exist!`); return ""; } return clVars.listeners.varStates[String(args.ID)].varState; } + getNextPacket(args) { + let temp = "" + switch (args.TYPE) { + case 'Global data': + temp = clVars.gmsg.queue[0]; + clVars.gmsg.queue.shift(); + break; + case 'Private data': + temp = clVars.pmsg.queue[0]; + clVars.pmsg.queue.shift(); + break; + case 'Direct data': + temp = clVars.direct.queue[0]; + clVars.direct.queue.shift(); + break; + case 'Status code': + temp = clVars.statuscode.queue[0]; + clVars.statuscode.queue.shift(); + break; + case 'Global variables': + temp = clVars.gvar.queue[0]; + clVars.gvar.queue.shift(); + break; + case 'Private variables': + temp = clVars.pvar.queue[0]; + clVars.pvar.queue.shift(); + break; + default: + } + return makeValueScratchSafe(JSON.stringify(temp)); + } + + getAndClearPacketQueue(args) { + let temp = "" + switch (args.TYPE) { + case 'Global data': + temp = clVars.gmsg.queue; + clVars.gmsg.queue = []; + break; + case 'Private data': + temp = clVars.pmsg.queue; + clVars.pmsg.queue = []; + break; + case 'Direct data': + temp = clVars.direct.queue; + clVars.direct.queue = []; + break; + case 'Status code': + temp = clVars.statuscode.queue; + clVars.statuscode.queue = []; + break; + case 'Global variables': + temp = clVars.gvar.queue; + clVars.gvar.queue = []; + break; + case 'Private variables': + temp = clVars.pvar.queue; + clVars.pvar.queue = []; + break; + default: + } + return makeValueScratchSafe(JSON.stringify(temp)); + } + + newPacketsExist(args) { + switch (args.TYPE) { + case 'Global data': + return clVars.gmsg.queue.length > 0; + case 'Private data': + return clVars.pmsg.queue.length > 0; + case 'Direct data': + return clVars.direct.queue.length > 0; + case 'Status code': + return clVars.statuscode.queue.length > 0; + case 'Global variables': + return clVars.gvar.queue.length > 0; + case 'Private variables': + return clVars.pvar.queue.length > 0; + } + } + // Reporter - Returns the size of the message queue. // TYPE - String (menu allmenu) readQueueSize(args) { switch (args.TYPE) { - case "Global data": + case 'Global data': return clVars.gmsg.queue.length; - case "Private data": + case 'Private data': return clVars.pmsg.queue.length; - case "Direct data": + case 'Direct data': return clVars.direct.queue.length; - case "Status code": + case 'Status code': return clVars.statuscode.queue.length; - case "Global variables": + case 'Global variables': return clVars.gvar.queue.length; - case "Private variables": + case 'Private variables': return clVars.pvar.queue.length; - case "All data": + case 'All data': return ( clVars.gmsg.queue.length + clVars.pmsg.queue.length + @@ -1759,26 +1845,26 @@ // TYPE - String (menu allmenu) readQueueData(args) { switch (args.TYPE) { - case "Global data": + case 'Global data': return makeValueScratchSafe(clVars.gmsg.queue); - case "Private data": + case 'Private data': return makeValueScratchSafe(clVars.pmsg.queue); - case "Direct data": + case 'Direct data': return makeValueScratchSafe(clVars.direct.queue); - case "Status code": + case 'Status code': return makeValueScratchSafe(clVars.statuscode.queue); - case "Global variables": + case 'Global variables': return makeValueScratchSafe(clVars.gvar.queue); - case "Private variables": + case 'Private variables': return makeValueScratchSafe(clVars.pvar.queue); - case "All data": + case 'All data': return makeValueScratchSafe({ gmsg: clVars.gmsg.queue, pmsg: clVars.pmsg.queue, direct: clVars.direct.queue, statuscode: clVars.statuscode.queue, gvar: clVars.gvar.queue, - pvar: clVars.pvar.queue, + pvar: clVars.pvar.queue }); } } @@ -1787,29 +1873,15 @@ // TYPE - String (menu varmenu), VAR - String (variable name) returnVarData(args) { switch (args.TYPE) { - case "Global variables": - if ( - !Object.prototype.hasOwnProperty.call( - clVars.gvar.varStates, - String(args.VAR) - ) - ) { - console.warn( - `[CloudLink] Global variable ${args.VAR} does not exist!` - ); + case 'Global variables': + if (!clVars.gvar.varStates.hasOwnProperty(String(args.VAR))) { + //console.warn(`[CloudLink] Global variable ${args.VAR} does not exist!`); return ""; } return clVars.gvar.varStates[String(args.VAR)].varState; - case "Private variables": - if ( - !Object.prototype.hasOwnProperty.call( - clVars.pvar.varStates, - String(args.VAR) - ) - ) { - console.warn( - `[CloudLink] Private variable ${args.VAR} does not exist!` - ); + case 'Private variables': + if (!clVars.pvar.varStates.hasOwnProperty(String(args.VAR))) { + //console.warn(`[CloudLink] Private variable ${args.VAR} does not exist!`); return ""; } return clVars.pvar.varStates[String(args.VAR)].varState; @@ -1820,25 +1892,23 @@ // PATH - String, JSON_STRING - String parseJSON(args) { try { - const path = args.PATH.toString() - .split("/") - .map((prop) => decodeURIComponent(prop)); - if (path[0] === "") path.splice(0, 1); - if (path[path.length - 1] === "") path.splice(-1, 1); + const path = args.PATH.toString().split('/').map(prop => decodeURIComponent(prop)); + if (path[0] === '') path.splice(0, 1); + if (path[path.length - 1] === '') path.splice(-1, 1); let json; try { - json = JSON.parse(" " + args.JSON_STRING); + json = JSON.parse(' ' + args.JSON_STRING); } catch (e) { return e.message; - } - path.forEach((prop) => (json = json[prop])); - if (json === null) return "null"; - else if (json === undefined) return ""; - else if (typeof json === "object") return JSON.stringify(json); + }; + path.forEach(prop => json = json[prop]); + if (json === null) return 'null'; + else if (json === undefined) return ''; + else if (typeof json === 'object') return JSON.stringify(json); else return json.toString(); } catch (err) { - return ""; - } + return ''; + }; } // Reporter - Returns an entry from a JSON array (0-based). @@ -1850,7 +1920,7 @@ } else { let data = json_array[args.NUM]; - if (typeof data == "object") { + if (typeof (data) == "object") { data = JSON.stringify(data); // Make the JSON safe for Scratch } @@ -1862,9 +1932,9 @@ // url - String fetchURL(args) { return Scratch.fetch(args.url, { method: "GET" }) - .then((response) => response.text()) - .catch((error) => { - console.warn(`[CloudLink] Fetch error: ${error}`); + .then(response => response.text()) + .catch(error => { + //console.warn(`[CloudLink] Fetch error: ${error}`); }); } @@ -1874,21 +1944,21 @@ if (args.method == "GET" || args.method == "HEAD") { return Scratch.fetch(args.url, { method: args.method, - headers: JSON.parse(args.headers), + headers: JSON.parse(args.headers) }) - .then((response) => response.text()) - .catch((error) => { - console.warn(`[CloudLink] Request error: ${error}`); + .then(response => response.text()) + .catch(error => { + //console.warn(`[CloudLink] Request error: ${error}`); }); } else { return Scratch.fetch(args.url, { method: args.method, headers: JSON.parse(args.headers), - body: JSON.parse(args.data), + body: args.data }) - .then((response) => response.text()) - .catch((error) => { - console.warn(`[CloudLink] Request error: ${error}`); + .then(response => response.text()) + .catch(error => { + //console.warn(`[CloudLink] Request error: ${error}`); }); } } @@ -1901,13 +1971,7 @@ if (clVars.linkState.status != 2) return false; // Listener must exist - if ( - !Object.prototype.hasOwnProperty.call( - clVars.listeners.varStates, - args.ID - ) - ) - return false; + if (!clVars.listeners.varStates.hasOwnProperty(args.ID)) return false; // Run event if (clVars.listeners.varStates[args.ID].eventHatTick) { @@ -1926,42 +1990,42 @@ // Run event switch (args.TYPE) { - case "Global data": + case 'Global data': if (clVars.gmsg.eventHatTick) { clVars.gmsg.eventHatTick = false; return true; } break; - case "Private data": + case 'Private data': if (clVars.pmsg.eventHatTick) { clVars.pmsg.eventHatTick = false; return true; } break; - case "Direct data": + case 'Direct data': if (clVars.direct.eventHatTick) { clVars.direct.eventHatTick = false; return true; } break; - case "Status code": + case 'Status code': if (clVars.statuscode.eventHatTick) { clVars.statuscode.eventHatTick = false; return true; } break; - case "Global variables": + case 'Global variables': if (clVars.gvar.eventHatTick) { clVars.gvar.eventHatTick = false; return true; } break; - case "Private variables": + case 'Private variables': if (clVars.pvar.eventHatTick) { clVars.pvar.eventHatTick = false; return true; @@ -1980,15 +2044,10 @@ // Run event switch (args.TYPE) { - case "Global variables": + case 'Global variables': + // Variable must exist - if ( - !Object.prototype.hasOwnProperty.call( - clVars.gvar.varStates, - String(args.VAR) - ) - ) - break; + if (!clVars.gvar.varStates.hasOwnProperty(String(args.VAR))) break; if (clVars.gvar.varStates[String(args.VAR)].eventHatTick) { clVars.gvar.varStates[String(args.VAR)].eventHatTick = false; return true; @@ -1996,15 +2055,10 @@ break; - case "Private variables": + case 'Private variables': + // Variable must exist - if ( - !Object.prototype.hasOwnProperty.call( - clVars.pvar.varStates, - String(args.VAR) - ) - ) - break; + if (!clVars.pvar.varStates.hasOwnProperty(String(args.VAR))) break; if (clVars.pvar.varStates[String(args.VAR)].eventHatTick) { clVars.pvar.varStates[String(args.VAR)].eventHatTick = false; return true; @@ -2018,64 +2072,61 @@ // Reporter - Returns a JSON-ified value. // toBeJSONified - String makeJSON(args) { - if (typeof args.toBeJSONified == "string") { + if (typeof (args.toBeJSONified) == "string") { try { JSON.parse(args.toBeJSONified); return String(args.toBeJSONified); } catch (err) { return "Not JSON!"; } - } else if (typeof args.toBeJSONified == "object") { + } else if (typeof (args.toBeJSONified) == "object") { return JSON.stringify(args.toBeJSONified); } else { return "Not JSON!"; - } + }; } // Boolean - Returns true if connected. getComState() { - return clVars.linkState.status == 2 && clVars.socket != null; + return ((clVars.linkState.status == 2) && (clVars.socket != null)); } // Boolean - Returns true if linked to rooms (other than "default") getRoomState() { - return clVars.socket != null && clVars.rooms.isLinked; + return ((clVars.socket != null) && (clVars.rooms.isLinked)); } // Boolean - Returns true if the connection was dropped. getComLostConnectionState() { - return ( - clVars.linkState.status == 4 && clVars.linkState.disconnectType == 2 - ); + return ((clVars.linkState.status == 4) && (clVars.linkState.disconnectType == 2)); } // Boolean - Returns true if the client failed to establish a connection. getComFailedConnectionState() { - return ( - clVars.linkState.status == 4 && clVars.linkState.disconnectType == 1 - ); + return ((clVars.linkState.status == 4) && (clVars.linkState.disconnectType == 1)); } // Boolean - Returns true if the username was set successfully. getUsernameState() { - return clVars.socket != null && clVars.username.accepted; + return ((clVars.socket != null) && (clVars.username.accepted)); } // Boolean - Returns true if there is new gmsg/pmsg/direct/statuscode data. // TYPE - String (menu datamenu) returnIsNewData(args) { + // Must be connected if (clVars.socket == null) return false; // Run event switch (args.TYPE) { - case "Global data": + case 'Global data': return clVars.gmsg.hasNew; - case "Private data": + case 'Private data': return clVars.pmsg.hasNew; - case "Direct data": + case 'Direct data': return clVars.direct.hasNew; - case "Status code": + case 'Status code': return clVars.statuscode.hasNew; } } @@ -2084,29 +2135,15 @@ // TYPE - String (menu varmenu), VAR - String (variable name) returnIsNewVarData(args) { switch (args.TYPE) { - case "Global variables": - if ( - !Object.prototype.hasOwnProperty.call( - clVars.gvar.varStates, - String(args.VAR) - ) - ) { - console.warn( - `[CloudLink] Global variable ${args.VAR} does not exist!` - ); + case 'Global variables': + if (!clVars.gvar.varStates.hasOwnProperty(String(args.VAR))) { + //console.warn(`[CloudLink] Global variable ${args.VAR} does not exist!`); return false; } return clVars.gvar.varStates[String(args.ID)].hasNew; - case "Private variables": - if ( - !Object.prototype.hasOwnProperty.call( - clVars.pvar.varStates, - String(args.VAR) - ) - ) { - console.warn( - `[CloudLink] Private variable ${args.VAR} does not exist!` - ); + case 'Private variables': + if (!clVars.pvar.varStates.hasOwnProperty(String(args.VAR))) { + //console.warn(`[CloudLink] Private variable ${args.VAR} does not exist!`); return false; } return clVars.pvar.varStates[String(args.ID)].hasNew; @@ -2116,13 +2153,8 @@ // Boolean - Returns true if a listener has a new value. // ID - String (listener ID) returnIsNewListener(args) { - if ( - !Object.prototype.hasOwnProperty.call( - clVars.listeners.varStates, - String(args.ID) - ) - ) { - console.warn(`[CloudLink] Listener ID ${args.ID} does not exist!`); + if (!clVars.listeners.varStates.hasOwnProperty(String(args.ID))) { + //console.warn(`[CloudLink] Listener ID ${args.ID} does not exist!`); return false; } return clVars.listeners.varStates[String(args.ID)].hasNew; @@ -2131,21 +2163,24 @@ // Boolean - Returns true if a username/ID/UUID/object exists in the userlist. // ID - String (username or user object) checkForID(args) { + // Legacy ulist handling if (clVars.ulist.includes(args.ID)) return true; // New ulist handling if (clVars.linkState.identifiedProtocol > 2) { if (this.isValidJSON(args.ID)) { - return clVars.ulist.some( - (o) => - o.username === JSON.parse(args.ID).username && - o.id == JSON.parse(args.ID).id - ); + return clVars.ulist.some(o => ( + (o.username === JSON.parse(args.ID).username) + && + (o.id == JSON.parse(args.ID).id) + )); } else { - return clVars.ulist.some( - (o) => o.username === String(args.ID) || o.id == args.ID - ); + return clVars.ulist.some(o => ( + (o.username === String(args.ID)) + || + (o.id == args.ID) + )); } } else return false; } @@ -2158,16 +2193,16 @@ return true; } catch { return false; - } + }; } // Command - Establishes a connection to a server. // IP - String (websocket URL) openSocket(args) { if (clVars.socket != null) { - console.warn("[CloudLink] Already connected to a server."); + //console.warn("[CloudLink] Already connected to a server."); return; - } + }; return newClient(args.IP); } @@ -2175,32 +2210,23 @@ // ID - Number (server entry #) openSocketPublicServers(args) { if (clVars.socket != null) { - console.warn("[CloudLink] Already connected to a server."); + //console.warn("[CloudLink] Already connected to a server."); return; - } - // This is the only server that's listed and works. - if (Scratch.Cast.toNumber(args.ID) >= 1) { - args.ID = 7; - } - if ( - !Object.prototype.hasOwnProperty.call( - clVars.serverList, - String(args.ID) - ) - ) { - console.warn("[CloudLink] Not a valid server ID!"); + }; + if (!clVars.serverList.hasOwnProperty(String(args.ID))) { + //console.warn("[CloudLink] Not a valid server ID!"); return; - } + }; return newClient(clVars.serverList[String(args.ID)]["url"]); } // Command - Closes the connection. closeSocket() { if (clVars.socket == null) { - console.warn("[CloudLink] Already disconnected."); + //console.warn("[CloudLink] Already disconnected."); return; - } - console.log("[CloudLink] Disconnecting..."); + }; + //console.log("[CloudLink] Disconnecting..."); clVars.linkState.isAttemptingGracefulDisconnect = true; clVars.socket.close(1000, "Client going away"); } @@ -2213,15 +2239,15 @@ // Prevent running if an attempt is currently processing. if (clVars.username.attempted) { - console.warn("[CloudLink] Already attempting to set username!"); + //console.warn("[CloudLink] Already attempting to set username!"); return; - } + }; // Prevent running if the username is already set. if (clVars.username.accepted) { - console.warn("[CloudLink] Already set username!"); + //console.warn("[CloudLink] Already set username!"); return; - } + }; // Update state clVars.username.attempted = true; @@ -2234,28 +2260,25 @@ // Command - Prepares the next transmitted message to have a listener ID attached to it. // ID - String (listener ID) createListener(args) { + // Must be connected to set a username. if (clVars.socket == null) return; // Require server support if (clVars.linkState.identifiedProtocol < 2) { - console.warn( - "[CloudLink] Server is too old! Must be at least 0.1.8.x to support listeners." - ); + //console.warn("[CloudLink] Server is too old! Must be at least 0.1.8.x to support listeners."); return; } // Prevent running if the username hasn't been set. if (!clVars.username.accepted) { - console.warn( - "[CloudLink] Username must be set before creating a listener!" - ); + //console.warn("[CloudLink] Username must be set before creating a listener!"); return; - } + }; // Must be used once per packet if (clVars.listeners.enablerState) { - console.warn("[CloudLink] Cannot create multiple listeners at a time!"); + //console.warn("[CloudLink] Cannot create multiple listeners at a time!"); return; } @@ -2267,36 +2290,33 @@ // Command - Subscribes to various rooms on a server. // ROOMS - String (JSON Array or single string) linkToRooms(args) { + // Must be connected to set a username. if (clVars.socket == null) return; // Require server support if (clVars.linkState.identifiedProtocol < 2) { - console.warn( - "[CloudLink] Server is too old! Must be at least 0.1.8.x to support rooms." - ); + //console.warn("[CloudLink] Server is too old! Must be at least 0.1.8.x to support rooms."); return; } // Prevent running if the username hasn't been set. if (!clVars.username.accepted) { - console.warn( - "[CloudLink] Username must be set before linking to rooms!" - ); + //console.warn("[CloudLink] Username must be set before linking to rooms!"); return; - } + }; // Prevent running if already linked. if (clVars.rooms.isLinked) { - console.warn("[CloudLink] Already linked to rooms!"); + //console.warn("[CloudLink] Already linked to rooms!"); return; - } + }; // Prevent running if a room link is in progress. if (clVars.rooms.isAttemptingLink) { - console.warn("[CloudLink] Currently linking to rooms! Please wait!"); + //console.warn("[CloudLink] Currently linking to rooms! Please wait!"); return; - } + }; clVars.rooms.isAttemptingLink = true; sendMessage({ cmd: "link", val: args.ROOMS, listener: "link" }); @@ -2305,40 +2325,33 @@ // Command - Specifies specific subscribed rooms to transmit messages to. // ROOMS - String (JSON Array or single string) selectRoomsInNextPacket(args) { + // Must be connected to user rooms. if (clVars.socket == null) return; // Require server support if (clVars.linkState.identifiedProtocol < 2) { - console.warn( - "[CloudLink] Server is too old! Must be at least 0.1.8.x to support rooms." - ); + //console.warn("[CloudLink] Server is too old! Must be at least 0.1.8.x to support rooms."); return; } // Prevent running if the username hasn't been set. if (!clVars.username.accepted) { - console.warn( - "[CloudLink] Username must be set before selecting rooms!" - ); + //console.warn("[CloudLink] Username must be set before selecting rooms!"); return; - } + }; // Require once per packet if (clVars.rooms.enablerState) { - console.warn( - "[CloudLink] Cannot use the room selector more than once at a time!" - ); + //console.warn("[CloudLink] Cannot use the room selector more than once at a time!"); return; } // Prevent running if not linked. if (!clVars.rooms.isLinked) { - console.warn( - "[CloudLink] Cannot use room selector while not linked to rooms!" - ); + //console.warn("[CloudLink] Cannot use room selector while not linked to rooms!"); return; - } + }; clVars.rooms.enablerState = true; clVars.rooms.enablerValue = args.ROOMS; @@ -2346,38 +2359,33 @@ // Command - Unsubscribes from all rooms and re-subscribes to the the "default" room on the server. unlinkFromRooms() { + // Must be connected to user rooms. if (clVars.socket == null) return; // Require server support if (clVars.linkState.identifiedProtocol < 2) { - console.warn( - "[CloudLink] Server is too old! Must be at least 0.1.8.x to support rooms." - ); + //console.warn("[CloudLink] Server is too old! Must be at least 0.1.8.x to support rooms."); return; } // Prevent running if the username hasn't been set. if (!clVars.username.accepted) { - console.warn( - "[CloudLink] Username must be set before unjoining rooms!" - ); + //console.warn("[CloudLink] Username must be set before unjoining rooms!"); return; - } + }; // Prevent running if already unlinked. if (!clVars.rooms.isLinked) { - console.warn("[CloudLink] Already unlinked from rooms!"); + //console.warn("[CloudLink] Already unlinked from rooms!"); return; - } + }; // Prevent running if a room unlink is in progress. if (clVars.rooms.isAttemptingUnlink) { - console.warn( - "[CloudLink] Currently unlinking from rooms! Please wait!" - ); + //console.warn("[CloudLink] Currently unlinking from rooms! Please wait!"); return; - } + }; clVars.rooms.isAttemptingUnlink = true; sendMessage({ cmd: "unlink", val: "", listener: "unlink" }); @@ -2386,6 +2394,7 @@ // Command - Sends a gmsg value. // DATA - String sendGData(args) { + // Must be connected. if (clVars.socket == null) return; @@ -2395,16 +2404,15 @@ // Command - Sends a pmsg value. // DATA - String, ID - String (recipient ID) sendPData(args) { + // Must be connected. if (clVars.socket == null) return; // Prevent running if the username hasn't been set. if (!clVars.username.accepted) { - console.warn( - "[CloudLink] Username must be set before sending private messages!" - ); + //console.warn("[CloudLink] Username must be set before sending private messages!"); return; - } + }; sendMessage({ cmd: "pmsg", val: args.DATA, id: args.ID }); } @@ -2412,6 +2420,7 @@ // Command - Sends a gvar value. // DATA - String, VAR - String (variable name) sendGDataAsVar(args) { + // Must be connected. if (clVars.socket == null) return; @@ -2421,16 +2430,15 @@ // Command - Sends a pvar value. // DATA - String, VAR - String (variable name), ID - String (recipient ID) sendPDataAsVar(args) { + // Must be connected. if (clVars.socket == null) return; // Prevent running if the username hasn't been set. if (!clVars.username.accepted) { - console.warn( - "[CloudLink] Username must be set before sending private variables!" - ); + //console.warn("[CloudLink] Username must be set before sending private variables!"); return; - } + }; sendMessage({ cmd: "pvar", val: args.DATA, name: args.VAR, id: args.ID }); } @@ -2438,6 +2446,7 @@ // Command - Sends a raw-format command without specifying an ID. // CMD - String (command), DATA - String runCMDnoID(args) { + // Must be connected. if (clVars.socket == null) return; @@ -2447,16 +2456,15 @@ // Command - Sends a raw-format command with an ID. // CMD - String (command), DATA - String, ID - String (recipient ID) runCMD(args) { + // Must be connected. if (clVars.socket == null) return; // Prevent running if the username hasn't been set. if (!clVars.username.accepted) { - console.warn( - "[CloudLink] Username must be set before using this command!" - ); + //console.warn("[CloudLink] Username must be set before using this command!"); return; - } + }; sendMessage({ cmd: args.CMD, val: args.DATA, id: args.ID }); } @@ -2465,16 +2473,16 @@ // TYPE - String (menu datamenu) resetNewData(args) { switch (args.TYPE) { - case "Global data": + case 'Global data': clVars.gmsg.hasNew = false; break; - case "Private data": + case 'Private data': clVars.pmsg.hasNew = false; break; - case "Direct data": + case 'Direct data': clVars.direct.hasNew = false; break; - case "Status code": + case 'Status code': clVars.statuscode.hasNew = false; break; } @@ -2484,47 +2492,26 @@ // TYPE - String (menu varmenu), VAR - String (variable name) resetNewVarData(args) { switch (args.TYPE) { - case "Global variables": - if ( - !Object.prototype.hasOwnProperty.call( - clVars.gvar.varStates, - String(args.VAR) - ) - ) { - console.warn( - `[CloudLink] Global variable ${args.VAR} does not exist!` - ); + case 'Global variables': + if (!clVars.gvar.varStates.hasOwnProperty(String(args.VAR))) { + //console.warn(`[CloudLink] Global variable ${args.VAR} does not exist!`); return; } clVars.gvar.varStates[String(args.ID)].hasNew = false; - break; - case "Private variables": - if ( - !Object.prototype.hasOwnProperty.call( - clVars.pvar.varStates, - String(args.VAR) - ) - ) { - console.warn( - `[CloudLink] Private variable ${args.VAR} does not exist!` - ); + case 'Private variables': + if (!clVars.pvar.varStates.hasOwnProperty(String(args.VAR))) { + //console.warn(`[CloudLink] Private variable ${args.VAR} does not exist!`); return false; } clVars.pvar.varStates[String(args.ID)].hasNew = false; - break; } } // Command - Resets the "returnIsNewListener" boolean state. // ID - Listener ID resetNewListener(args) { - if ( - !Object.prototype.hasOwnProperty.call( - clVars.listeners.varStates, - String(args.ID) - ) - ) { - console.warn(`[CloudLink] Listener ID ${args.ID} does not exist!`); + if (!clVars.listeners.varStates.hasOwnProperty(String(args.ID))) { + //console.warn(`[CloudLink] Listener ID ${args.ID} does not exist!`); return; } clVars.listeners.varStates[String(args.ID)].hasNew = false; @@ -2534,25 +2521,25 @@ // TYPE - String (menu allmenu) clearAllPackets(args) { switch (args.TYPE) { - case "Global data": + case 'Global data': clVars.gmsg.queue = []; break; - case "Private data": + case 'Private data': clVars.pmsg.queue = []; break; - case "Direct data": + case 'Direct data': clVars.direct.queue = []; break; - case "Status code": + case 'Status code': clVars.statuscode.queue = []; break; - case "Global variables": + case 'Global variables': clVars.gvar.queue = []; break; - case "Private variables": + case 'Private variables': clVars.pvar.queue = []; break; - case "All data": + case 'All data': clVars.gmsg.queue = []; clVars.pmsg.queue = []; clVars.direct.queue = []; @@ -2562,6 +2549,14 @@ break; } } + + recentlyjoined() { + return makeValueScratchSafe(JSON.stringify(clVars?.recentlyJoinedUser ?? {})); + } + + recentlyleft() { + return makeValueScratchSafe(JSON.stringify(clVars?.recentlyLeftUser ?? {})); + } } Scratch.extensions.register(new CloudLink()); })(Scratch); From 70ef9b537cce9339174976dec43af0f1cb1b359a Mon Sep 17 00:00:00 2001 From: "DangoCat[bot]" Date: Fri, 10 Apr 2026 01:15:38 +0000 Subject: [PATCH 02/11] [Automated] Format code --- extensions/cloudlink.js | 510 +++++++++++++++++++++------------------- 1 file changed, 271 insertions(+), 239 deletions(-) diff --git a/extensions/cloudlink.js b/extensions/cloudlink.js index 314e0a1888..0ffd71d920 100644 --- a/extensions/cloudlink.js +++ b/extensions/cloudlink.js @@ -10,7 +10,6 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my username": "(VANHA - ÄLÄ KÄYTÄ UUSISSA PROJEKTEISSA) oma käyttäjänimi", "_A name": "nimi", "_All data": "kaikki data", "_Another name": "toinen nimi", "_Apple": "omena", "_Banana": "banaani", "_Direct data": "kohdennettu data", "_Global data": "globaali data", "_Global variables": "globaalit muuttujat", "_Hide old blocks": "Piilota vanhat lohkot", "_ID [ID] connected?": "onko tunniste [ID] yhdistetty?", "_Private data": "yksityinen data", "_Private variables": "yksityiset muuttujat", "_Show old blocks": "Näytä vanhat lohkot", "_Status code": "tilakoodi", "_When I receive new [TYPE] data for [VAR]": "kun vastaanotan uuden kohteen [TYPE] datan muuttujalle [VAR]", "_[NUM] from JSON array [ARRAY]": "[NUM] JSON-taulukossa [ARRAY]", "_[PATH] of [JSON_STRING]": "[PATH] JSON-koodissa [JSON_STRING]", "_attach listener [ID] to next packet": "lisää kuuntelija [ID] seuraavaan datapakettiin", "_clear all packets for [TYPE]": "tyhjennä kaikki kohteen [TYPE] datapaketit", "_connect to [IP]": "yhdistä palvelimeen [IP]", "_connect to server [ID]": "yhdistä palvelimeen nro [ID]", "_connected?": "onko yhdistetty?", "_convert [toBeJSONified] to JSON": "muunna [toBeJSONified] JSON-muotoon", "_direct": "kohdennettu", "_direct data": "kohdennettu data", "_disconnect": "katkaise yhteys", "_extension version": "laajennuksen versio", "_failed to connnect?": "epäonnistuiko yhteyden muodostaminen?", "_fetch data from URL [url]": "hae data URL-osoitteesta [url]", "_global data": "globaali data", "_got new [TYPE] data for variable [VAR]?": "onko uusi [TYPE] [VAR] data saapunut?", "_got new [TYPE]?": "onko uusi [TYPE] saapunut?", "_got new packet with listener [ID]?": "onko uusi datapaketti kuuntelijalla [ID] saapunut?", "_id": "tunniste", "_is [JSON_STRING] valid JSON?": "onko [JSON_STRING] kelvollista JSON-koodia?", "_link status": "yhteyden tila", "_link to room(s) [ROOMS]": "yhdistä huoneisiin [ROOMS]", "_linked to rooms?": "onko yhdistetty huoneisiin?", "_lost connection?": "katkesiko yhteys?", "_my IP address": "oma IP-osoite", "_my user object": "oma käyttäjäolio", "_my username": "oma käyttäjänimi", "_packet queue for [TYPE]": "kohteen [TYPE] datapakettijono", "_private data": "yksityinen data", "_reset got new [ID] listener status": "nollaa uusi kuuntelijan [ID] tila", "_reset got new [TYPE] [VAR] status": "nollaa uusi kohteen [TYPE] muuttujan [VAR] tila", "_reset got new [TYPE] status": "nollaa uusi kohteen [TYPE] tila", "_response for listener [ID]": "vastaus kuuntelijalle [ID]", "_select room(s) [ROOMS] for next packet": "valitse huoneet [ROOMS] seuraavalle datapaketille", "_send [DATA]": "lähetä [DATA]", "_send [DATA] to [ID]": "lähetä [DATA] käyttäjälle [ID]", "_send command [CMD] [ID] [DATA]": "lähetä komento [CMD] [ID] [DATA]", "_send command without ID [CMD] [DATA]": "lähetä komento ilman tunnistetta [CMD] [DATA]", "_send request with method [method] for URL [url] with data [data] and headers [headers]": "lähetä pyyntö menetelmällä [method] URL-osoitteeseen [url] datalla [data] ja otsakkeilla [headers]", "_send variable [VAR] to [ID] with data [DATA]": "lähetä muuttuja [VAR] käyttäjälle [ID] datalla [DATA]", "_send variable [VAR] with data [DATA]": "lähetä muuttuja [VAR] datalla [DATA]", "_server MOTD": "palvelimen viesti", "_server list": "palvelinluettelo", "_server version": "palvelimen versio", "_set [NAME] as username": "aseta käyttäjänimeksi [NAME]", "_size of queue for [TYPE]": "kohteen [TYPE] jonon koko", "_status code": "tilakoodi", "_unlink from all rooms": "katkaise yhteys kaikkiin huoneisiin", "_username synced?": "onko käyttäjänimi synkronoitu?", "_usernames": "käyttäjänimet", "_val": "arvo", "_when I receive new [TYPE] message": "kun vastaanotan uuden kohteen [TYPE] viestin", "_when I receive new message with listener [ID]": "kun vastaanotan uuden viestin kuuntelijalla [ID]", "_when connected": "kun yhteys muodostuu", "_when disconnected": "kun yhteys katkeaa" }, "nl": { "_[PATH] of [JSON_STRING]": "[PATH] van [JSON_STRING]", "_id": "ID" }, "ru": { "_[PATH] of [JSON_STRING]": "[PATH] из [JSON_STRING]", "_id": "ID" }, "zh-cn": { "_(OLD - DO NOT USE IN NEW PROJECTS) my username": "(旧版 - 不要在新项目中使用它) 我的用户名", "_A name": "一个名字", "_All data": "所有数据", "_Another name": "另一个名称", "_Apple": "苹果", "_Banana": "香蕉", "_Direct data": "直接数据", "_Global data": "全局数据", "_Global variables": "全局变量", "_Hide old blocks": "隐藏旧积木", "_ID [ID] connected?": "ID[ID]连接?", "_Private data": "私有数据", "_Private variables": "私有变量", "_Show old blocks": "显示旧积木", "_Status code": "状态码", "_When I receive new [TYPE] data for [VAR]": "当我收到新的用于[VAR]的[TYPE]信息", "_[NUM] from JSON array [ARRAY]": "JSON数组[ARRAY]的[NUM]", "_[PATH] of [JSON_STRING]": "[JSON_STRING]中的[PATH]", "_[TYPE] [VAR] data": "[TYPE][VAR]数据", "_attach listener [ID] to next packet": "附加监听器 [ID] 到下一个数据包", "_clear all packets for [TYPE]": "清空[TYPE]的所有数据包", "_connect to [IP]": "连接到[IP]", "_connect to server [ID]": "连接到服务器[ID]", "_connected?": "已连接?", "_convert [toBeJSONified] to JSON": "将[toBeJSONified]转为JSON", "_direct": "直接", "_direct data": "直接数据", "_disconnect": "断开连接", "_extension version": "扩展版本", "_failed to connnect?": "连接失败?", "_fetch data from URL [url]": "从 URL [url]获取数据", "_global data": "全局数据", "_got new [TYPE] data for variable [VAR]?": "收到新的用于变量[VAR]的[TYPE]数据?", "_got new [TYPE]?": "收到新的[TYPE]?", "_got new packet with listener [ID]?": "从监听器[ID]收到新的包?", "_id": "ID", "_is [JSON_STRING] valid JSON?": "[JSON_STRING]是合法JSON?", "_link status": "链接状态", "_link to room(s) [ROOMS]": "连接到房间(列表)[ROOMS]", "_linked to rooms?": "已连接到房间?", "_lost connection?": "连接丢失?", "_my IP address": "我的IP地址", "_my user object": "我的用户对象", "_my username": "我的用户名", "_packet queue for [TYPE]": "[TYPE]的包队列", "_private data": "私有数据", "_reset got new [ID] listener status": "重置收到新的[ID]监听器的状态", "_reset got new [TYPE] [VAR] status": "重置收到新的[TYPE][VAR]状态", "_reset got new [TYPE] status": "重置收到新的[TYPE]状态", "_response for listener [ID]": "监听器[ID]的回应", "_select room(s) [ROOMS] for next packet": "为下一个数据包选择房间(列表)[ROOMS]", "_send [DATA]": "发送[DATA]", "_send [DATA] to [ID]": "发送[DATA]给[ID]", "_send command [CMD] [ID] [DATA]": "发送命令[CMD][ID][DATA]", "_send command without ID [CMD] [DATA]": "发送没有ID[CMD][DATA]的命令", "_send request with method [method] for URL [url] with data [data] and headers [headers]": "发送[method]方法的请求给URL[url]携带数据[data]头部信息 [headers]", "_send variable [VAR] to [ID] with data [DATA]": "发送变量[VAR]给[ID]附带数据[DATA]", "_send variable [VAR] with data [DATA]": "发送变量[VAR]附带数据[DATA]", "_server MOTD": "服务器MOTD", "_server list": "服务器列表", "_server version": "服务器版本", "_set [NAME] as username": "设置[NAME]为用户名", "_size of queue for [TYPE]": "[TYPE]的队列大小", "_status code": "状态码", "_unlink from all rooms": "从所有房间断开连接", "_username synced?": "已同步用户名?", "_usernames": "用户名列表", "_when I receive new [TYPE] message": "当我收到新的[TYPE]信息", "_when I receive new message with listener [ID]": "当我通过监听器[ID]接收到新消息时`", "_when connected": "当建立连接", "_when disconnected": "当断开连接" } }); /* end generated l10n code */ (function (Scratch) { - /* Based on https://github.com/Mistium/extensions.mistium/blob/main/files%2FCloudlink4_Improved.js. @@ -44,9 +43,9 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna */ // Require extension to be unsandboxed. - 'use strict'; + "use strict"; if (!Scratch.extensions.unsandboxed) { - throw new Error('The CloudLink extension must run unsandboxed.'); + throw new Error("The CloudLink extension must run unsandboxed."); } // Declare icons as static SVG URIs @@ -55,7 +54,6 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna const cl_block = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxNzYuMzk4NTQiIGhlaWdodD0iMTIyLjY3MDY5IiB2aWV3Qm94PSIwLDAsMTc2LjM5ODU0LDEyMi42NzA2OSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE1MS44MDA3MywtMTE4LjY2NDY2KSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PGc+PHBhdGggZD0iTTI4Ni4xMjAzNywxNTcuMTc3NTVjMjMuMjQwODYsMCA0Mi4wNzg5LDE4LjgzOTQ2IDQyLjA3ODksNDIuMDc4OWMwLDIzLjIzOTQ0IC0xOC44MzgwMyw0Mi4wNzg5IC00Mi4wNzg5LDQyLjA3ODloLTkyLjI0MDc0Yy0yMy4yNDA4NiwwIC00Mi4wNzg5LC0xOC44Mzk0NiAtNDIuMDc4OSwtNDIuMDc4OWMwLC0yMy4yMzk0NCAxOC44MzgwMywtNDIuMDc4OSA0Mi4wNzg5LC00Mi4wNzg5aDQuMTg4ODdjMS44MTE1MywtMjEuNTcwNTUgMTkuODkzNTcsLTM4LjUxMjg5IDQxLjkzMTUsLTM4LjUxMjg5YzIyLjAzNzkzLDAgNDAuMTE5OTcsMTYuOTQyMzQgNDEuOTMxNSwzOC41MTI4OXoiIGZpbGw9IiNmZmZmZmYiLz48cGF0aCBkPSJNMjg5LjA4NjU1LDIxNi45NjA3NHY5LjA0NjY3aC0yNi45MTY2M2gtOS4wNDY2N3YtOS4wNDY2N3YtNTQuNTAzMzloOS4wNDY2N3Y1NC41MDMzOXoiIGZpbGw9IiMwMGMyOGMiLz48cGF0aCBkPSJNMjIyLjQwOTI1LDIyNi4wMDc0MWMtOC4zNTMyLDAgLTE2LjM2NDMxLC0zLjMxODM0IC0yMi4yNzA5LC05LjIyNDkyYy01LjkwNjYxLC01LjkwNjU4IC05LjIyNDkxLC0xMy45MTc2OCAtOS4yMjQ5MSwtMjIuMjcwODljMCwtOC4zNTMyIDMuMzE4MjksLTE2LjM2NDMxIDkuMjI0OTEsLTIyLjI3MDljNS45MDY1OSwtNS45MDY2MSAxMy45MTc3LC05LjIyNDkxIDIyLjI3MDksLTkuMjI0OTFoMjEuMTA4OXY4LjkzNDk4aC0yMS4xMDg5djAuMTAyNTdjLTUuOTU2MjgsMCAtMTEuNjY4NjQsMi4zNjYxNiAtMTUuODgwMzcsNi41Nzc4OWMtNC4yMTE3Myw0LjIxMTczIC02LjU3Nzg5LDkuOTI0MDggLTYuNTc3ODksMTUuODgwMzdjMCw1Ljk1NjI4IDIuMzY2MTYsMTEuNjY4NjQgNi41Nzc4OSwxNS44ODAzN2M0LjIxMTczLDQuMjExNzMgOS45MjQwOCw2LjU3NzkzIDE1Ljg4MDM3LDYuNTc3OTN2MC4xMDI1M2gyMS4xMDg5djguOTM0OTh6IiBmaWxsPSIjMDBjMjhjIi8+PC9nPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjg4LjE5OTI2OTk5OTk5OTk4OjYxLjMzNTM0NDk5OTk5OTk5LS0+"; - // Declare VM const vm = Scratch.vm; const runtime = vm.runtime; @@ -89,7 +87,6 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Store extension state var clVars = { - // Editor-specific variable for hiding old, legacy-support blocks. hideCLDeprecatedBlocks: true, @@ -244,7 +241,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Storage for the publically available CloudLink instances. serverList: {}, - } + }; function generateVersionString() { return `${version.editorType} ${version.versionString}`; @@ -344,12 +341,11 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (message.hasOwnProperty("val")) { try { message.val = JSON.parse(message.val); - } catch { } + } catch {} } // Attach listeners if (clVars.listeners.enablerState) { - // 0.1.8.x was the first server version to support listeners. if (clVars.linkState.identifiedProtocol >= 2) { message.listener = clVars.listeners.enablerValue; @@ -360,7 +356,6 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna varState: {}, eventHatTick: false, }; - } else { //console.warn("[CloudLink] Server is too old! Must be at least 0.1.8.x to support listeners."); } @@ -368,7 +363,10 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna } // Check if server supports rooms - if (((message.cmd == "link") || (message.cmd == "unlink")) && (clVars.linkState.identifiedProtocol < 2)) { + if ( + (message.cmd == "link" || message.cmd == "unlink") && + clVars.linkState.identifiedProtocol < 2 + ) { // 0.1.8.x was the first server version to support rooms. //console.warn("[CloudLink] Server is too old! Must be at least 0.1.8.x to support room linking/unlinking."); return; @@ -401,7 +399,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna versionNumber: version.versionNumber, }, }, - listener: "handshake_cfg" + listener: "handshake_cfg", }); clVars.handshakeAttempted = true; } @@ -421,13 +419,12 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna "S2.2": 0, // 0.1.5 "0.1.": 0, // 0.1.5 or legacy "S2.": 0, // Legacy - "S1.": -1 // Obsolete + "S1.": -1, // Obsolete }; for (const [key, value] of Object.entries(versions)) { if (version.includes(key)) { if (clVars.linkState.identifiedProtocol < value) { - // Disconnect if protcol is too old if (value == -1) { //console.warn(`[CloudLink] Server is too old to enable leagacy support. Disconnecting.`); @@ -438,7 +435,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna clVars.linkState.identifiedProtocol = value; } } - }; + } // Log configured spec version // // ////console.log(`[CloudLink] Configured protocol spec to v${clVars.linkState.identifiedProtocol}.`); @@ -447,18 +444,24 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna clVars.linkState.status = 2; // Fire event hats (only one not broken) - runtime.startHats('cloudlink_onConnect'); + runtime.startHats("cloudlink_onConnect"); // Don't nag user if they already trusted this server if (clVars.currentServerUrl === clVars.lastServerUrl) return; // Ask user if they wish to stay connected if the server is unsupported - if ((clVars.linkState.identifiedProtocol < 4) && (!confirm( - `You have connected to an old CloudLink server, running version ${clVars.server_version}.\n\nFor your security and privacy, we recommend you disconnect from this server and connect to an up-to-date server.\n\nClick/tap \"OK\" to stay connected.` - ))) { + if ( + clVars.linkState.identifiedProtocol < 4 && + !confirm( + `You have connected to an old CloudLink server, running version ${clVars.server_version}.\n\nFor your security and privacy, we recommend you disconnect from this server and connect to an up-to-date server.\n\nClick/tap \"OK\" to stay connected.` + ) + ) { // Close the connection if they choose "Cancel" clVars.linkState.isAttemptingGracefulDisconnect = true; - clVars.socket.close(1000, "Client going away (legacy server rejected by end user)"); + clVars.socket.close( + 1000, + "Client going away (legacy server rejected by end user)" + ); return; } @@ -471,11 +474,11 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Parse the message JSON let packet = {}; try { - packet = JSON.parse(data) + packet = JSON.parse(data); } catch (SyntaxError) { //console.error("[CloudLink] Incoming message parse failure! Is this really a CloudLink server?", data); return; - }; + } // Handle packet commands if (!packet.hasOwnProperty("cmd")) { @@ -564,12 +567,10 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Protocol v2 (0.1.8.x) uses "code" instead. // Protocol v3-v4 (0.1.9.x - latest, 0.2.0) adds "code_id" to the payload. Ignored by Scratch clients. else { - // Handle setup listeners if (packet.hasOwnProperty("listener")) { switch (packet.listener) { case "username_cfg": - // Username accepted if (packet.code.includes("I:100")) { clVars.myUserObject = packet.val; @@ -631,18 +632,20 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna case "ulist": // Protocol v0-v1 (0.1.5 and legacy - 0.1.7) use a semicolon (;) separated string for the userlist. if ( - (clVars.linkState.identifiedProtocol == 0) - || - (clVars.linkState.identifiedProtocol == 1) + clVars.linkState.identifiedProtocol == 0 || + clVars.linkState.identifiedProtocol == 1 ) { // Split the username list string - clVars.ulist = String(packet.val).split(';'); + clVars.ulist = String(packet.val).split(";"); // Get rid of blank entry at the end of the list clVars.ulist.pop(clVars.ulist.length); // Check if username has been set (since older servers don't implement statuscodes or listeners) - if ((clVars.username.attempted) && (clVars.ulist.includes(clVars.username.temp))) { + if ( + clVars.username.attempted && + clVars.ulist.includes(clVars.username.temp) + ) { clVars.username.value = clVars.username.temp; clVars.username.accepted = true; //console.log(`[CloudLink] Username has been set to \"${clVars.username.value}\" successfully!`); @@ -660,29 +663,29 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (!packet.hasOwnProperty("mode")) { //console.warn("[CloudLink] Userlist message did not specify \"mode\" while running in protocol mode 3 or 4."); return; - }; + } // Handle methods switch (packet.mode) { - case 'set': + case "set": clVars.ulist = packet.val; break; - case 'add': + case "add": clVars.ulist.push(packet.val); clVars.recentlyJoinedUser = packet.val; - Scratch.vm.runtime.startHats('cloudlink_whenuserconnects'); + Scratch.vm.runtime.startHats("cloudlink_whenuserconnects"); break; - case 'remove': - let index = -1 + case "remove": + let index = -1; for (let i = 0; i < clVars.ulist.length; i++) { - let user = clVars.ulist[i] + let user = clVars.ulist[i]; if (user.uuid == packet.val.uuid) { - index = i + index = i; break; } } clVars.ulist.splice(index, 1); clVars.recentlyLeftUser = packet.val; - Scratch.vm.runtime.startHats('cloudlink_whenuserdisconnects'); + Scratch.vm.runtime.startHats("cloudlink_whenuserdisconnects"); break; default: //console.warn(`[CloudLink] Unrecognised userlist mode: \"${packet.mode}\".`); @@ -717,7 +720,6 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Handle listeners if (packet.hasOwnProperty("listener")) { if (clVars.listeners.current.includes(String(packet.listener))) { - // Remove the listener from the currently listening list clVars.listeners.current.splice( clVars.listeners.current.indexOf(String(packet.listener)), @@ -787,7 +789,10 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna break; case 2: // Was already connected - if (event.wasClean || clVars.linkState.isAttemptingGracefulDisconnect) { + if ( + event.wasClean || + clVars.linkState.isAttemptingGracefulDisconnect + ) { // Set the link state to graceful disconnect. //console.log(`[CloudLink] Disconnected (${event.code} ${event.reason}).`); clVars.linkState.status = 3; @@ -805,10 +810,10 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna resetOnClose(); // Run all onClose event blocks - runtime.startHats('cloudlink_onClose'); + runtime.startHats("cloudlink_onClose"); // Return promise (during setup) return; - } + }; } // GET the serverList @@ -835,29 +840,28 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna class CloudLink { getInfo() { return { - id: 'cloudlink', - name: 'CloudLink', + id: "cloudlink", + name: "CloudLink", blockIconURI: cl_block, menuIconURI: cl_icon, docsURI: "https://github.com/MikeDev101/cloudlink/wiki/Scratch-Client", blocks: [ - { opcode: "returnGlobalData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("global data") + text: Scratch.translate("global data"), }, { opcode: "returnPrivateData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("private data") + text: Scratch.translate("private data"), }, { opcode: "returnDirectData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("direct data") + text: Scratch.translate("direct data"), }, "---", @@ -865,13 +869,13 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna { opcode: "returnLinkData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("link status") + text: Scratch.translate("link status"), }, { opcode: "returnStatusCode", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("status code") + text: Scratch.translate("status code"), }, "---", @@ -879,20 +883,22 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna { opcode: "returnUserListData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("usernames") + text: Scratch.translate("usernames"), }, { opcode: "returnUsernameDataNew", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("my username") + text: Scratch.translate("my username"), }, { opcode: "returnUsernameData", blockType: Scratch.BlockType.REPORTER, hideFromPalette: clVars.hideCLDeprecatedBlocks, - text: Scratch.translate("(OLD - DO NOT USE IN NEW PROJECTS) my username") + text: Scratch.translate( + "(OLD - DO NOT USE IN NEW PROJECTS) my username" + ), }, "---", @@ -900,25 +906,25 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna { opcode: "returnVersionData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("extension version") + text: Scratch.translate("extension version"), }, { opcode: "returnServerVersion", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("server version") + text: Scratch.translate("server version"), }, { opcode: "returnServerList", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("server list") + text: Scratch.translate("server list"), }, { opcode: "returnMOTD", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("server MOTD") + text: Scratch.translate("server MOTD"), }, "---", @@ -926,13 +932,13 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna { opcode: "returnClientIP", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("my IP address") + text: Scratch.translate("my IP address"), }, { opcode: "returnUserObject", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("my user object") + text: Scratch.translate("my user object"), }, "---", @@ -1006,11 +1012,12 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna arguments: { PATH: { type: Scratch.ArgumentType.STRING, - defaultValue: 'fruit/apples', + defaultValue: "fruit/apples", }, JSON_STRING: { type: Scratch.ArgumentType.STRING, - defaultValue: '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', + defaultValue: + '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', }, }, }, @@ -1019,7 +1026,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna opcode: "getFromJSONArray", blockType: Scratch.BlockType.REPORTER, hideFromPalette: clVars.hideCLDeprecatedBlocks, - text: Scratch.translate('[NUM] from JSON array [ARRAY]'), + text: Scratch.translate("[NUM] from JSON array [ARRAY]"), arguments: { NUM: { type: Scratch.ArgumentType.NUMBER, @@ -1028,8 +1035,8 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna ARRAY: { type: Scratch.ArgumentType.STRING, defaultValue: '["foo","bar"]', - } - } + }, + }, }, { @@ -1053,12 +1060,12 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna arguments: { JSON_STRING: { type: Scratch.ArgumentType.STRING, - defaultValue: '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', + defaultValue: + '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', }, }, }, - "---", { @@ -1078,7 +1085,9 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna opcode: "requestURL", blockType: Scratch.BlockType.REPORTER, hideFromPalette: clVars.hideCLDeprecatedBlocks, - text: Scratch.translate("send request with method [method] for URL [url] with data [data] and headers [headers]"), + text: Scratch.translate( + "send request with method [method] for URL [url] with data [data] and headers [headers]" + ), arguments: { method: { type: Scratch.ArgumentType.STRING, @@ -1120,7 +1129,9 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna { opcode: "onListener", blockType: Scratch.BlockType.HAT, - text: Scratch.translate("when I receive new message with listener [ID]"), + text: Scratch.translate( + "when I receive new message with listener [ID]" + ), isEdgeActivated: true, arguments: { ID: { @@ -1258,8 +1269,8 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna IP: { type: Scratch.ArgumentType.STRING, defaultValue: "ws://127.0.0.1:3000/", - } - } + }, + }, }, { @@ -1270,14 +1281,14 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna ID: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1, - } - } + }, + }, }, { opcode: "closeSocket", blockType: Scratch.BlockType.COMMAND, - text: Scratch.translate("disconnect") + text: Scratch.translate("disconnect"), }, "---", @@ -1306,13 +1317,12 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna defaultValue: "example-listener", }, }, - }, "---", { - opcode: 'linkToRooms', + opcode: "linkToRooms", blockType: Scratch.BlockType.COMMAND, text: Scratch.translate("link to room(s) [ROOMS]"), arguments: { @@ -1320,7 +1330,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna type: Scratch.ArgumentType.STRING, defaultValue: Scratch.translate('["test"]'), }, - } + }, }, { @@ -1390,7 +1400,9 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna { opcode: "sendPDataAsVar", blockType: Scratch.BlockType.COMMAND, - text: Scratch.translate("send variable [VAR] to [ID] with data [DATA]"), + text: Scratch.translate( + "send variable [VAR] to [ID] with data [DATA]" + ), arguments: { DATA: { type: Scratch.ArgumentType.STRING, @@ -1546,7 +1558,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna menu: "allmenu", defaultValue: "All data", }, - } + }, }, { @@ -1592,45 +1604,71 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna }, "---", - ], menus: { datamenu: { items: [ - { text: Scratch.translate('Global data'), value: 'Global data' }, - { text: Scratch.translate('Private data'), value: 'Private data' }, - { text: Scratch.translate('Direct data'), value: 'Direct data' }, - { text: Scratch.translate('Status code'), value: 'Status code' } - ] + { text: Scratch.translate("Global data"), value: "Global data" }, + { + text: Scratch.translate("Private data"), + value: "Private data", + }, + { text: Scratch.translate("Direct data"), value: "Direct data" }, + { text: Scratch.translate("Status code"), value: "Status code" }, + ], }, varmenu: { items: [ - { text: Scratch.translate('Global variables'), value: "Global variables" }, - { text: Scratch.translate('Private variables'), value: "Private variables" } - ] + { + text: Scratch.translate("Global variables"), + value: "Global variables", + }, + { + text: Scratch.translate("Private variables"), + value: "Private variables", + }, + ], }, allmenu: { items: [ - { text: Scratch.translate('Global data'), value: 'Global data' }, - { text: Scratch.translate('Private data'), value: 'Private data' }, - { text: Scratch.translate('Direct data'), value: 'Direct data' }, - { text: Scratch.translate('Status code'), value: 'Status code' }, - { text: Scratch.translate("Global variables"), value: "Global variables" }, - { text: Scratch.translate("Private variables"), value: "Private variables" }, - { text: Scratch.translate("All data"), value: "All data" } - ] + { text: Scratch.translate("Global data"), value: "Global data" }, + { + text: Scratch.translate("Private data"), + value: "Private data", + }, + { text: Scratch.translate("Direct data"), value: "Direct data" }, + { text: Scratch.translate("Status code"), value: "Status code" }, + { + text: Scratch.translate("Global variables"), + value: "Global variables", + }, + { + text: Scratch.translate("Private variables"), + value: "Private variables", + }, + { text: Scratch.translate("All data"), value: "All data" }, + ], }, almostallmenu: { items: [ - { text: Scratch.translate('Global data'), value: 'Global data' }, - { text: Scratch.translate('Private data'), value: 'Private data' }, - { text: Scratch.translate('Direct data'), value: 'Direct data' }, - { text: Scratch.translate('Status code'), value: 'Status code' }, - { text: Scratch.translate("Global variables"), value: "Global variables" }, - { text: Scratch.translate("Private variables"), value: "Private variables" } - ] + { text: Scratch.translate("Global data"), value: "Global data" }, + { + text: Scratch.translate("Private data"), + value: "Private data", + }, + { text: Scratch.translate("Direct data"), value: "Direct data" }, + { text: Scratch.translate("Status code"), value: "Status code" }, + { + text: Scratch.translate("Global variables"), + value: "Global variables", + }, + { + text: Scratch.translate("Private variables"), + value: "Private variables", + }, + ], }, - } + }, }; } @@ -1733,29 +1771,29 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna } getNextPacket(args) { - let temp = "" + let temp = ""; switch (args.TYPE) { - case 'Global data': + case "Global data": temp = clVars.gmsg.queue[0]; clVars.gmsg.queue.shift(); break; - case 'Private data': + case "Private data": temp = clVars.pmsg.queue[0]; clVars.pmsg.queue.shift(); break; - case 'Direct data': + case "Direct data": temp = clVars.direct.queue[0]; clVars.direct.queue.shift(); break; - case 'Status code': + case "Status code": temp = clVars.statuscode.queue[0]; clVars.statuscode.queue.shift(); break; - case 'Global variables': + case "Global variables": temp = clVars.gvar.queue[0]; clVars.gvar.queue.shift(); break; - case 'Private variables': + case "Private variables": temp = clVars.pvar.queue[0]; clVars.pvar.queue.shift(); break; @@ -1765,29 +1803,29 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna } getAndClearPacketQueue(args) { - let temp = "" + let temp = ""; switch (args.TYPE) { - case 'Global data': + case "Global data": temp = clVars.gmsg.queue; clVars.gmsg.queue = []; break; - case 'Private data': + case "Private data": temp = clVars.pmsg.queue; clVars.pmsg.queue = []; break; - case 'Direct data': + case "Direct data": temp = clVars.direct.queue; clVars.direct.queue = []; break; - case 'Status code': + case "Status code": temp = clVars.statuscode.queue; clVars.statuscode.queue = []; break; - case 'Global variables': + case "Global variables": temp = clVars.gvar.queue; clVars.gvar.queue = []; break; - case 'Private variables': + case "Private variables": temp = clVars.pvar.queue; clVars.pvar.queue = []; break; @@ -1798,17 +1836,17 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna newPacketsExist(args) { switch (args.TYPE) { - case 'Global data': + case "Global data": return clVars.gmsg.queue.length > 0; - case 'Private data': + case "Private data": return clVars.pmsg.queue.length > 0; - case 'Direct data': + case "Direct data": return clVars.direct.queue.length > 0; - case 'Status code': + case "Status code": return clVars.statuscode.queue.length > 0; - case 'Global variables': + case "Global variables": return clVars.gvar.queue.length > 0; - case 'Private variables': + case "Private variables": return clVars.pvar.queue.length > 0; } } @@ -1817,19 +1855,19 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // TYPE - String (menu allmenu) readQueueSize(args) { switch (args.TYPE) { - case 'Global data': + case "Global data": return clVars.gmsg.queue.length; - case 'Private data': + case "Private data": return clVars.pmsg.queue.length; - case 'Direct data': + case "Direct data": return clVars.direct.queue.length; - case 'Status code': + case "Status code": return clVars.statuscode.queue.length; - case 'Global variables': + case "Global variables": return clVars.gvar.queue.length; - case 'Private variables': + case "Private variables": return clVars.pvar.queue.length; - case 'All data': + case "All data": return ( clVars.gmsg.queue.length + clVars.pmsg.queue.length + @@ -1845,26 +1883,26 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // TYPE - String (menu allmenu) readQueueData(args) { switch (args.TYPE) { - case 'Global data': + case "Global data": return makeValueScratchSafe(clVars.gmsg.queue); - case 'Private data': + case "Private data": return makeValueScratchSafe(clVars.pmsg.queue); - case 'Direct data': + case "Direct data": return makeValueScratchSafe(clVars.direct.queue); - case 'Status code': + case "Status code": return makeValueScratchSafe(clVars.statuscode.queue); - case 'Global variables': + case "Global variables": return makeValueScratchSafe(clVars.gvar.queue); - case 'Private variables': + case "Private variables": return makeValueScratchSafe(clVars.pvar.queue); - case 'All data': + case "All data": return makeValueScratchSafe({ gmsg: clVars.gmsg.queue, pmsg: clVars.pmsg.queue, direct: clVars.direct.queue, statuscode: clVars.statuscode.queue, gvar: clVars.gvar.queue, - pvar: clVars.pvar.queue + pvar: clVars.pvar.queue, }); } } @@ -1873,13 +1911,13 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // TYPE - String (menu varmenu), VAR - String (variable name) returnVarData(args) { switch (args.TYPE) { - case 'Global variables': + case "Global variables": if (!clVars.gvar.varStates.hasOwnProperty(String(args.VAR))) { //console.warn(`[CloudLink] Global variable ${args.VAR} does not exist!`); return ""; } return clVars.gvar.varStates[String(args.VAR)].varState; - case 'Private variables': + case "Private variables": if (!clVars.pvar.varStates.hasOwnProperty(String(args.VAR))) { //console.warn(`[CloudLink] Private variable ${args.VAR} does not exist!`); return ""; @@ -1892,23 +1930,25 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // PATH - String, JSON_STRING - String parseJSON(args) { try { - const path = args.PATH.toString().split('/').map(prop => decodeURIComponent(prop)); - if (path[0] === '') path.splice(0, 1); - if (path[path.length - 1] === '') path.splice(-1, 1); + const path = args.PATH.toString() + .split("/") + .map((prop) => decodeURIComponent(prop)); + if (path[0] === "") path.splice(0, 1); + if (path[path.length - 1] === "") path.splice(-1, 1); let json; try { - json = JSON.parse(' ' + args.JSON_STRING); + json = JSON.parse(" " + args.JSON_STRING); } catch (e) { return e.message; - }; - path.forEach(prop => json = json[prop]); - if (json === null) return 'null'; - else if (json === undefined) return ''; - else if (typeof json === 'object') return JSON.stringify(json); + } + path.forEach((prop) => (json = json[prop])); + if (json === null) return "null"; + else if (json === undefined) return ""; + else if (typeof json === "object") return JSON.stringify(json); else return json.toString(); } catch (err) { - return ''; - }; + return ""; + } } // Reporter - Returns an entry from a JSON array (0-based). @@ -1920,7 +1960,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna } else { let data = json_array[args.NUM]; - if (typeof (data) == "object") { + if (typeof data == "object") { data = JSON.stringify(data); // Make the JSON safe for Scratch } @@ -1932,8 +1972,8 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // url - String fetchURL(args) { return Scratch.fetch(args.url, { method: "GET" }) - .then(response => response.text()) - .catch(error => { + .then((response) => response.text()) + .catch((error) => { //console.warn(`[CloudLink] Fetch error: ${error}`); }); } @@ -1944,20 +1984,20 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (args.method == "GET" || args.method == "HEAD") { return Scratch.fetch(args.url, { method: args.method, - headers: JSON.parse(args.headers) + headers: JSON.parse(args.headers), }) - .then(response => response.text()) - .catch(error => { + .then((response) => response.text()) + .catch((error) => { //console.warn(`[CloudLink] Request error: ${error}`); }); } else { return Scratch.fetch(args.url, { method: args.method, headers: JSON.parse(args.headers), - body: args.data + body: args.data, }) - .then(response => response.text()) - .catch(error => { + .then((response) => response.text()) + .catch((error) => { //console.warn(`[CloudLink] Request error: ${error}`); }); } @@ -1990,42 +2030,42 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Run event switch (args.TYPE) { - case 'Global data': + case "Global data": if (clVars.gmsg.eventHatTick) { clVars.gmsg.eventHatTick = false; return true; } break; - case 'Private data': + case "Private data": if (clVars.pmsg.eventHatTick) { clVars.pmsg.eventHatTick = false; return true; } break; - case 'Direct data': + case "Direct data": if (clVars.direct.eventHatTick) { clVars.direct.eventHatTick = false; return true; } break; - case 'Status code': + case "Status code": if (clVars.statuscode.eventHatTick) { clVars.statuscode.eventHatTick = false; return true; } break; - case 'Global variables': + case "Global variables": if (clVars.gvar.eventHatTick) { clVars.gvar.eventHatTick = false; return true; } break; - case 'Private variables': + case "Private variables": if (clVars.pvar.eventHatTick) { clVars.pvar.eventHatTick = false; return true; @@ -2044,8 +2084,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Run event switch (args.TYPE) { - case 'Global variables': - + case "Global variables": // Variable must exist if (!clVars.gvar.varStates.hasOwnProperty(String(args.VAR))) break; if (clVars.gvar.varStates[String(args.VAR)].eventHatTick) { @@ -2055,8 +2094,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna break; - case 'Private variables': - + case "Private variables": // Variable must exist if (!clVars.pvar.varStates.hasOwnProperty(String(args.VAR))) break; if (clVars.pvar.varStates[String(args.VAR)].eventHatTick) { @@ -2072,61 +2110,64 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Reporter - Returns a JSON-ified value. // toBeJSONified - String makeJSON(args) { - if (typeof (args.toBeJSONified) == "string") { + if (typeof args.toBeJSONified == "string") { try { JSON.parse(args.toBeJSONified); return String(args.toBeJSONified); } catch (err) { return "Not JSON!"; } - } else if (typeof (args.toBeJSONified) == "object") { + } else if (typeof args.toBeJSONified == "object") { return JSON.stringify(args.toBeJSONified); } else { return "Not JSON!"; - }; + } } // Boolean - Returns true if connected. getComState() { - return ((clVars.linkState.status == 2) && (clVars.socket != null)); + return clVars.linkState.status == 2 && clVars.socket != null; } // Boolean - Returns true if linked to rooms (other than "default") getRoomState() { - return ((clVars.socket != null) && (clVars.rooms.isLinked)); + return clVars.socket != null && clVars.rooms.isLinked; } // Boolean - Returns true if the connection was dropped. getComLostConnectionState() { - return ((clVars.linkState.status == 4) && (clVars.linkState.disconnectType == 2)); + return ( + clVars.linkState.status == 4 && clVars.linkState.disconnectType == 2 + ); } // Boolean - Returns true if the client failed to establish a connection. getComFailedConnectionState() { - return ((clVars.linkState.status == 4) && (clVars.linkState.disconnectType == 1)); + return ( + clVars.linkState.status == 4 && clVars.linkState.disconnectType == 1 + ); } // Boolean - Returns true if the username was set successfully. getUsernameState() { - return ((clVars.socket != null) && (clVars.username.accepted)); + return clVars.socket != null && clVars.username.accepted; } // Boolean - Returns true if there is new gmsg/pmsg/direct/statuscode data. // TYPE - String (menu datamenu) returnIsNewData(args) { - // Must be connected if (clVars.socket == null) return false; // Run event switch (args.TYPE) { - case 'Global data': + case "Global data": return clVars.gmsg.hasNew; - case 'Private data': + case "Private data": return clVars.pmsg.hasNew; - case 'Direct data': + case "Direct data": return clVars.direct.hasNew; - case 'Status code': + case "Status code": return clVars.statuscode.hasNew; } } @@ -2135,13 +2176,13 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // TYPE - String (menu varmenu), VAR - String (variable name) returnIsNewVarData(args) { switch (args.TYPE) { - case 'Global variables': + case "Global variables": if (!clVars.gvar.varStates.hasOwnProperty(String(args.VAR))) { //console.warn(`[CloudLink] Global variable ${args.VAR} does not exist!`); return false; } return clVars.gvar.varStates[String(args.ID)].hasNew; - case 'Private variables': + case "Private variables": if (!clVars.pvar.varStates.hasOwnProperty(String(args.VAR))) { //console.warn(`[CloudLink] Private variable ${args.VAR} does not exist!`); return false; @@ -2163,24 +2204,21 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Boolean - Returns true if a username/ID/UUID/object exists in the userlist. // ID - String (username or user object) checkForID(args) { - // Legacy ulist handling if (clVars.ulist.includes(args.ID)) return true; // New ulist handling if (clVars.linkState.identifiedProtocol > 2) { if (this.isValidJSON(args.ID)) { - return clVars.ulist.some(o => ( - (o.username === JSON.parse(args.ID).username) - && - (o.id == JSON.parse(args.ID).id) - )); + return clVars.ulist.some( + (o) => + o.username === JSON.parse(args.ID).username && + o.id == JSON.parse(args.ID).id + ); } else { - return clVars.ulist.some(o => ( - (o.username === String(args.ID)) - || - (o.id == args.ID) - )); + return clVars.ulist.some( + (o) => o.username === String(args.ID) || o.id == args.ID + ); } } else return false; } @@ -2193,7 +2231,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna return true; } catch { return false; - }; + } } // Command - Establishes a connection to a server. @@ -2202,7 +2240,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (clVars.socket != null) { //console.warn("[CloudLink] Already connected to a server."); return; - }; + } return newClient(args.IP); } @@ -2212,11 +2250,11 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (clVars.socket != null) { //console.warn("[CloudLink] Already connected to a server."); return; - }; + } if (!clVars.serverList.hasOwnProperty(String(args.ID))) { //console.warn("[CloudLink] Not a valid server ID!"); return; - }; + } return newClient(clVars.serverList[String(args.ID)]["url"]); } @@ -2225,7 +2263,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (clVars.socket == null) { //console.warn("[CloudLink] Already disconnected."); return; - }; + } //console.log("[CloudLink] Disconnecting..."); clVars.linkState.isAttemptingGracefulDisconnect = true; clVars.socket.close(1000, "Client going away"); @@ -2241,13 +2279,13 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (clVars.username.attempted) { //console.warn("[CloudLink] Already attempting to set username!"); return; - }; + } // Prevent running if the username is already set. if (clVars.username.accepted) { //console.warn("[CloudLink] Already set username!"); return; - }; + } // Update state clVars.username.attempted = true; @@ -2260,7 +2298,6 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Prepares the next transmitted message to have a listener ID attached to it. // ID - String (listener ID) createListener(args) { - // Must be connected to set a username. if (clVars.socket == null) return; @@ -2274,7 +2311,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before creating a listener!"); return; - }; + } // Must be used once per packet if (clVars.listeners.enablerState) { @@ -2290,7 +2327,6 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Subscribes to various rooms on a server. // ROOMS - String (JSON Array or single string) linkToRooms(args) { - // Must be connected to set a username. if (clVars.socket == null) return; @@ -2304,19 +2340,19 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before linking to rooms!"); return; - }; + } // Prevent running if already linked. if (clVars.rooms.isLinked) { //console.warn("[CloudLink] Already linked to rooms!"); return; - }; + } // Prevent running if a room link is in progress. if (clVars.rooms.isAttemptingLink) { //console.warn("[CloudLink] Currently linking to rooms! Please wait!"); return; - }; + } clVars.rooms.isAttemptingLink = true; sendMessage({ cmd: "link", val: args.ROOMS, listener: "link" }); @@ -2325,7 +2361,6 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Specifies specific subscribed rooms to transmit messages to. // ROOMS - String (JSON Array or single string) selectRoomsInNextPacket(args) { - // Must be connected to user rooms. if (clVars.socket == null) return; @@ -2339,7 +2374,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before selecting rooms!"); return; - }; + } // Require once per packet if (clVars.rooms.enablerState) { @@ -2351,7 +2386,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (!clVars.rooms.isLinked) { //console.warn("[CloudLink] Cannot use room selector while not linked to rooms!"); return; - }; + } clVars.rooms.enablerState = true; clVars.rooms.enablerValue = args.ROOMS; @@ -2359,7 +2394,6 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Unsubscribes from all rooms and re-subscribes to the the "default" room on the server. unlinkFromRooms() { - // Must be connected to user rooms. if (clVars.socket == null) return; @@ -2373,19 +2407,19 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before unjoining rooms!"); return; - }; + } // Prevent running if already unlinked. if (!clVars.rooms.isLinked) { //console.warn("[CloudLink] Already unlinked from rooms!"); return; - }; + } // Prevent running if a room unlink is in progress. if (clVars.rooms.isAttemptingUnlink) { //console.warn("[CloudLink] Currently unlinking from rooms! Please wait!"); return; - }; + } clVars.rooms.isAttemptingUnlink = true; sendMessage({ cmd: "unlink", val: "", listener: "unlink" }); @@ -2394,7 +2428,6 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Sends a gmsg value. // DATA - String sendGData(args) { - // Must be connected. if (clVars.socket == null) return; @@ -2404,7 +2437,6 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Sends a pmsg value. // DATA - String, ID - String (recipient ID) sendPData(args) { - // Must be connected. if (clVars.socket == null) return; @@ -2412,7 +2444,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before sending private messages!"); return; - }; + } sendMessage({ cmd: "pmsg", val: args.DATA, id: args.ID }); } @@ -2420,7 +2452,6 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Sends a gvar value. // DATA - String, VAR - String (variable name) sendGDataAsVar(args) { - // Must be connected. if (clVars.socket == null) return; @@ -2430,7 +2461,6 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Sends a pvar value. // DATA - String, VAR - String (variable name), ID - String (recipient ID) sendPDataAsVar(args) { - // Must be connected. if (clVars.socket == null) return; @@ -2438,7 +2468,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before sending private variables!"); return; - }; + } sendMessage({ cmd: "pvar", val: args.DATA, name: args.VAR, id: args.ID }); } @@ -2446,7 +2476,6 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Sends a raw-format command without specifying an ID. // CMD - String (command), DATA - String runCMDnoID(args) { - // Must be connected. if (clVars.socket == null) return; @@ -2456,7 +2485,6 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Sends a raw-format command with an ID. // CMD - String (command), DATA - String, ID - String (recipient ID) runCMD(args) { - // Must be connected. if (clVars.socket == null) return; @@ -2464,7 +2492,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before using this command!"); return; - }; + } sendMessage({ cmd: args.CMD, val: args.DATA, id: args.ID }); } @@ -2473,16 +2501,16 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // TYPE - String (menu datamenu) resetNewData(args) { switch (args.TYPE) { - case 'Global data': + case "Global data": clVars.gmsg.hasNew = false; break; - case 'Private data': + case "Private data": clVars.pmsg.hasNew = false; break; - case 'Direct data': + case "Direct data": clVars.direct.hasNew = false; break; - case 'Status code': + case "Status code": clVars.statuscode.hasNew = false; break; } @@ -2492,13 +2520,13 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // TYPE - String (menu varmenu), VAR - String (variable name) resetNewVarData(args) { switch (args.TYPE) { - case 'Global variables': + case "Global variables": if (!clVars.gvar.varStates.hasOwnProperty(String(args.VAR))) { //console.warn(`[CloudLink] Global variable ${args.VAR} does not exist!`); return; } clVars.gvar.varStates[String(args.ID)].hasNew = false; - case 'Private variables': + case "Private variables": if (!clVars.pvar.varStates.hasOwnProperty(String(args.VAR))) { //console.warn(`[CloudLink] Private variable ${args.VAR} does not exist!`); return false; @@ -2521,25 +2549,25 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // TYPE - String (menu allmenu) clearAllPackets(args) { switch (args.TYPE) { - case 'Global data': + case "Global data": clVars.gmsg.queue = []; break; - case 'Private data': + case "Private data": clVars.pmsg.queue = []; break; - case 'Direct data': + case "Direct data": clVars.direct.queue = []; break; - case 'Status code': + case "Status code": clVars.statuscode.queue = []; break; - case 'Global variables': + case "Global variables": clVars.gvar.queue = []; break; - case 'Private variables': + case "Private variables": clVars.pvar.queue = []; break; - case 'All data': + case "All data": clVars.gmsg.queue = []; clVars.pmsg.queue = []; clVars.direct.queue = []; @@ -2551,11 +2579,15 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna } recentlyjoined() { - return makeValueScratchSafe(JSON.stringify(clVars?.recentlyJoinedUser ?? {})); + return makeValueScratchSafe( + JSON.stringify(clVars?.recentlyJoinedUser ?? {}) + ); } recentlyleft() { - return makeValueScratchSafe(JSON.stringify(clVars?.recentlyLeftUser ?? {})); + return makeValueScratchSafe( + JSON.stringify(clVars?.recentlyLeftUser ?? {}) + ); } } Scratch.extensions.register(new CloudLink()); From 7cd9aef65bf36a214ec0189881801c1942f36f6c Mon Sep 17 00:00:00 2001 From: "Mike J. Renaker / \"MikeDEV" Date: Fri, 10 Apr 2026 11:55:52 -0400 Subject: [PATCH 03/11] Merge mistium patches + fix serverlist Merged changes from https://github.com/Mistium/extensions.mistium/commit/3d889285e321bc1cff3936a0b02e0049670cebdf and restored the static serverlist property. --- extensions/cloudlink.js | 598 +++++++++++++++++++--------------------- 1 file changed, 279 insertions(+), 319 deletions(-) diff --git a/extensions/cloudlink.js b/extensions/cloudlink.js index 0ffd71d920..c9e829b746 100644 --- a/extensions/cloudlink.js +++ b/extensions/cloudlink.js @@ -1,17 +1,13 @@ -// Name: Cloudlink +// Name: Cloudlink V4 Improved // ID: cloudlink // Description: A powerful WebSocket extension for Scratch. // By: MikeDEV // License: MIT -/* eslint-disable */ -// prettier-ignore -/* generated l10n code */ -Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my username": "(VANHA - ÄLÄ KÄYTÄ UUSISSA PROJEKTEISSA) oma käyttäjänimi", "_A name": "nimi", "_All data": "kaikki data", "_Another name": "toinen nimi", "_Apple": "omena", "_Banana": "banaani", "_Direct data": "kohdennettu data", "_Global data": "globaali data", "_Global variables": "globaalit muuttujat", "_Hide old blocks": "Piilota vanhat lohkot", "_ID [ID] connected?": "onko tunniste [ID] yhdistetty?", "_Private data": "yksityinen data", "_Private variables": "yksityiset muuttujat", "_Show old blocks": "Näytä vanhat lohkot", "_Status code": "tilakoodi", "_When I receive new [TYPE] data for [VAR]": "kun vastaanotan uuden kohteen [TYPE] datan muuttujalle [VAR]", "_[NUM] from JSON array [ARRAY]": "[NUM] JSON-taulukossa [ARRAY]", "_[PATH] of [JSON_STRING]": "[PATH] JSON-koodissa [JSON_STRING]", "_attach listener [ID] to next packet": "lisää kuuntelija [ID] seuraavaan datapakettiin", "_clear all packets for [TYPE]": "tyhjennä kaikki kohteen [TYPE] datapaketit", "_connect to [IP]": "yhdistä palvelimeen [IP]", "_connect to server [ID]": "yhdistä palvelimeen nro [ID]", "_connected?": "onko yhdistetty?", "_convert [toBeJSONified] to JSON": "muunna [toBeJSONified] JSON-muotoon", "_direct": "kohdennettu", "_direct data": "kohdennettu data", "_disconnect": "katkaise yhteys", "_extension version": "laajennuksen versio", "_failed to connnect?": "epäonnistuiko yhteyden muodostaminen?", "_fetch data from URL [url]": "hae data URL-osoitteesta [url]", "_global data": "globaali data", "_got new [TYPE] data for variable [VAR]?": "onko uusi [TYPE] [VAR] data saapunut?", "_got new [TYPE]?": "onko uusi [TYPE] saapunut?", "_got new packet with listener [ID]?": "onko uusi datapaketti kuuntelijalla [ID] saapunut?", "_id": "tunniste", "_is [JSON_STRING] valid JSON?": "onko [JSON_STRING] kelvollista JSON-koodia?", "_link status": "yhteyden tila", "_link to room(s) [ROOMS]": "yhdistä huoneisiin [ROOMS]", "_linked to rooms?": "onko yhdistetty huoneisiin?", "_lost connection?": "katkesiko yhteys?", "_my IP address": "oma IP-osoite", "_my user object": "oma käyttäjäolio", "_my username": "oma käyttäjänimi", "_packet queue for [TYPE]": "kohteen [TYPE] datapakettijono", "_private data": "yksityinen data", "_reset got new [ID] listener status": "nollaa uusi kuuntelijan [ID] tila", "_reset got new [TYPE] [VAR] status": "nollaa uusi kohteen [TYPE] muuttujan [VAR] tila", "_reset got new [TYPE] status": "nollaa uusi kohteen [TYPE] tila", "_response for listener [ID]": "vastaus kuuntelijalle [ID]", "_select room(s) [ROOMS] for next packet": "valitse huoneet [ROOMS] seuraavalle datapaketille", "_send [DATA]": "lähetä [DATA]", "_send [DATA] to [ID]": "lähetä [DATA] käyttäjälle [ID]", "_send command [CMD] [ID] [DATA]": "lähetä komento [CMD] [ID] [DATA]", "_send command without ID [CMD] [DATA]": "lähetä komento ilman tunnistetta [CMD] [DATA]", "_send request with method [method] for URL [url] with data [data] and headers [headers]": "lähetä pyyntö menetelmällä [method] URL-osoitteeseen [url] datalla [data] ja otsakkeilla [headers]", "_send variable [VAR] to [ID] with data [DATA]": "lähetä muuttuja [VAR] käyttäjälle [ID] datalla [DATA]", "_send variable [VAR] with data [DATA]": "lähetä muuttuja [VAR] datalla [DATA]", "_server MOTD": "palvelimen viesti", "_server list": "palvelinluettelo", "_server version": "palvelimen versio", "_set [NAME] as username": "aseta käyttäjänimeksi [NAME]", "_size of queue for [TYPE]": "kohteen [TYPE] jonon koko", "_status code": "tilakoodi", "_unlink from all rooms": "katkaise yhteys kaikkiin huoneisiin", "_username synced?": "onko käyttäjänimi synkronoitu?", "_usernames": "käyttäjänimet", "_val": "arvo", "_when I receive new [TYPE] message": "kun vastaanotan uuden kohteen [TYPE] viestin", "_when I receive new message with listener [ID]": "kun vastaanotan uuden viestin kuuntelijalla [ID]", "_when connected": "kun yhteys muodostuu", "_when disconnected": "kun yhteys katkeaa" }, "nl": { "_[PATH] of [JSON_STRING]": "[PATH] van [JSON_STRING]", "_id": "ID" }, "ru": { "_[PATH] of [JSON_STRING]": "[PATH] из [JSON_STRING]", "_id": "ID" }, "zh-cn": { "_(OLD - DO NOT USE IN NEW PROJECTS) my username": "(旧版 - 不要在新项目中使用它) 我的用户名", "_A name": "一个名字", "_All data": "所有数据", "_Another name": "另一个名称", "_Apple": "苹果", "_Banana": "香蕉", "_Direct data": "直接数据", "_Global data": "全局数据", "_Global variables": "全局变量", "_Hide old blocks": "隐藏旧积木", "_ID [ID] connected?": "ID[ID]连接?", "_Private data": "私有数据", "_Private variables": "私有变量", "_Show old blocks": "显示旧积木", "_Status code": "状态码", "_When I receive new [TYPE] data for [VAR]": "当我收到新的用于[VAR]的[TYPE]信息", "_[NUM] from JSON array [ARRAY]": "JSON数组[ARRAY]的[NUM]", "_[PATH] of [JSON_STRING]": "[JSON_STRING]中的[PATH]", "_[TYPE] [VAR] data": "[TYPE][VAR]数据", "_attach listener [ID] to next packet": "附加监听器 [ID] 到下一个数据包", "_clear all packets for [TYPE]": "清空[TYPE]的所有数据包", "_connect to [IP]": "连接到[IP]", "_connect to server [ID]": "连接到服务器[ID]", "_connected?": "已连接?", "_convert [toBeJSONified] to JSON": "将[toBeJSONified]转为JSON", "_direct": "直接", "_direct data": "直接数据", "_disconnect": "断开连接", "_extension version": "扩展版本", "_failed to connnect?": "连接失败?", "_fetch data from URL [url]": "从 URL [url]获取数据", "_global data": "全局数据", "_got new [TYPE] data for variable [VAR]?": "收到新的用于变量[VAR]的[TYPE]数据?", "_got new [TYPE]?": "收到新的[TYPE]?", "_got new packet with listener [ID]?": "从监听器[ID]收到新的包?", "_id": "ID", "_is [JSON_STRING] valid JSON?": "[JSON_STRING]是合法JSON?", "_link status": "链接状态", "_link to room(s) [ROOMS]": "连接到房间(列表)[ROOMS]", "_linked to rooms?": "已连接到房间?", "_lost connection?": "连接丢失?", "_my IP address": "我的IP地址", "_my user object": "我的用户对象", "_my username": "我的用户名", "_packet queue for [TYPE]": "[TYPE]的包队列", "_private data": "私有数据", "_reset got new [ID] listener status": "重置收到新的[ID]监听器的状态", "_reset got new [TYPE] [VAR] status": "重置收到新的[TYPE][VAR]状态", "_reset got new [TYPE] status": "重置收到新的[TYPE]状态", "_response for listener [ID]": "监听器[ID]的回应", "_select room(s) [ROOMS] for next packet": "为下一个数据包选择房间(列表)[ROOMS]", "_send [DATA]": "发送[DATA]", "_send [DATA] to [ID]": "发送[DATA]给[ID]", "_send command [CMD] [ID] [DATA]": "发送命令[CMD][ID][DATA]", "_send command without ID [CMD] [DATA]": "发送没有ID[CMD][DATA]的命令", "_send request with method [method] for URL [url] with data [data] and headers [headers]": "发送[method]方法的请求给URL[url]携带数据[data]头部信息 [headers]", "_send variable [VAR] to [ID] with data [DATA]": "发送变量[VAR]给[ID]附带数据[DATA]", "_send variable [VAR] with data [DATA]": "发送变量[VAR]附带数据[DATA]", "_server MOTD": "服务器MOTD", "_server list": "服务器列表", "_server version": "服务器版本", "_set [NAME] as username": "设置[NAME]为用户名", "_size of queue for [TYPE]": "[TYPE]的队列大小", "_status code": "状态码", "_unlink from all rooms": "从所有房间断开连接", "_username synced?": "已同步用户名?", "_usernames": "用户名列表", "_when I receive new [TYPE] message": "当我收到新的[TYPE]信息", "_when I receive new message with listener [ID]": "当我通过监听器[ID]接收到新消息时`", "_when connected": "当建立连接", "_when disconnected": "当断开连接" } }); -/* end generated l10n code */ (function (Scratch) { - /* + /* + Based on https://github.com/Mistium/extensions.mistium/blob/main/files%2FCloudlink4_Improved.js. Copyright (c) Mistium 2025. @@ -43,9 +39,9 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna */ // Require extension to be unsandboxed. - "use strict"; + 'use strict'; if (!Scratch.extensions.unsandboxed) { - throw new Error("The CloudLink extension must run unsandboxed."); + throw new Error('The CloudLink extension must run unsandboxed.'); } // Declare icons as static SVG URIs @@ -87,6 +83,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Store extension state var clVars = { + // Editor-specific variable for hiding old, legacy-support blocks. hideCLDeprecatedBlocks: true, @@ -240,8 +237,17 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna handshakeAttempted: false, // Storage for the publically available CloudLink instances. - serverList: {}, - }; + serverList: { + 0: { + id: "Localhost", + url: "ws://127.0.0.1:3000/", + }, + 7: { + id: "MikeDEV's Spare CL 0.2.0 Server", + url: "wss://cl.mikedev101.cc/", + }, + }, + } function generateVersionString() { return `${version.editorType} ${version.versionString}`; @@ -338,24 +344,26 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna } // See if the outgoing val argument can be converted into JSON - if (message.hasOwnProperty("val")) { + if (Object.prototype.hasOwnProperty.call(message, "val")) { try { message.val = JSON.parse(message.val); - } catch {} + } catch { } } // Attach listeners if (clVars.listeners.enablerState) { + // 0.1.8.x was the first server version to support listeners. if (clVars.linkState.identifiedProtocol >= 2) { message.listener = clVars.listeners.enablerValue; // Create listener - clVars.listeners.varStates[String(args.ID)] = { + clVars.listeners.varStates[message.listener] = { hasNew: false, varState: {}, eventHatTick: false, }; + } else { //console.warn("[CloudLink] Server is too old! Must be at least 0.1.8.x to support listeners."); } @@ -363,10 +371,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna } // Check if server supports rooms - if ( - (message.cmd == "link" || message.cmd == "unlink") && - clVars.linkState.identifiedProtocol < 2 - ) { + if (((message.cmd == "link") || (message.cmd == "unlink")) && (clVars.linkState.identifiedProtocol < 2)) { // 0.1.8.x was the first server version to support rooms. //console.warn("[CloudLink] Server is too old! Must be at least 0.1.8.x to support room linking/unlinking."); return; @@ -399,7 +404,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna versionNumber: version.versionNumber, }, }, - listener: "handshake_cfg", + listener: "handshake_cfg" }); clVars.handshakeAttempted = true; } @@ -419,12 +424,13 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna "S2.2": 0, // 0.1.5 "0.1.": 0, // 0.1.5 or legacy "S2.": 0, // Legacy - "S1.": -1, // Obsolete + "S1.": -1 // Obsolete }; for (const [key, value] of Object.entries(versions)) { if (version.includes(key)) { if (clVars.linkState.identifiedProtocol < value) { + // Disconnect if protcol is too old if (value == -1) { //console.warn(`[CloudLink] Server is too old to enable leagacy support. Disconnecting.`); @@ -435,7 +441,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna clVars.linkState.identifiedProtocol = value; } } - } + }; // Log configured spec version // // ////console.log(`[CloudLink] Configured protocol spec to v${clVars.linkState.identifiedProtocol}.`); @@ -444,24 +450,18 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna clVars.linkState.status = 2; // Fire event hats (only one not broken) - runtime.startHats("cloudlink_onConnect"); + runtime.startHats('cloudlink_onConnect'); // Don't nag user if they already trusted this server if (clVars.currentServerUrl === clVars.lastServerUrl) return; // Ask user if they wish to stay connected if the server is unsupported - if ( - clVars.linkState.identifiedProtocol < 4 && - !confirm( - `You have connected to an old CloudLink server, running version ${clVars.server_version}.\n\nFor your security and privacy, we recommend you disconnect from this server and connect to an up-to-date server.\n\nClick/tap \"OK\" to stay connected.` - ) - ) { + if ((clVars.linkState.identifiedProtocol < 4) && (!confirm( + `You have connected to an old CloudLink server, running version ${clVars.server_version}.\n\nFor your security and privacy, we recommend you disconnect from this server and connect to an up-to-date server.\n\nClick/tap \"OK\" to stay connected.` + ))) { // Close the connection if they choose "Cancel" clVars.linkState.isAttemptingGracefulDisconnect = true; - clVars.socket.close( - 1000, - "Client going away (legacy server rejected by end user)" - ); + clVars.socket.close(1000, "Client going away (legacy server rejected by end user)"); return; } @@ -474,14 +474,14 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Parse the message JSON let packet = {}; try { - packet = JSON.parse(data); + packet = JSON.parse(data) } catch (SyntaxError) { //console.error("[CloudLink] Incoming message parse failure! Is this really a CloudLink server?", data); return; - } + }; // Handle packet commands - if (!packet.hasOwnProperty("cmd")) { + if (!Object.prototype.hasOwnProperty.call(packet, "cmd")) { //console.error("[CloudLink] Incoming message read failure! This message doesn't contain the required \"cmd\" key. Is this really a CloudLink server?", packet); return; } @@ -523,7 +523,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna case "direct": // Handle events from older server versions - if (packet.val.hasOwnProperty("cmd")) { + if (Object.prototype.hasOwnProperty.call(packet.val, "cmd")) { switch (packet.val.cmd) { // Server 0.1.5 (at least) case "vers": @@ -567,10 +567,12 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Protocol v2 (0.1.8.x) uses "code" instead. // Protocol v3-v4 (0.1.9.x - latest, 0.2.0) adds "code_id" to the payload. Ignored by Scratch clients. else { + // Handle setup listeners - if (packet.hasOwnProperty("listener")) { + if (Object.prototype.hasOwnProperty.call(packet, "listener")) { switch (packet.listener) { case "username_cfg": + // Username accepted if (packet.code.includes("I:100")) { clVars.myUserObject = packet.val; @@ -632,20 +634,18 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna case "ulist": // Protocol v0-v1 (0.1.5 and legacy - 0.1.7) use a semicolon (;) separated string for the userlist. if ( - clVars.linkState.identifiedProtocol == 0 || - clVars.linkState.identifiedProtocol == 1 + (clVars.linkState.identifiedProtocol == 0) + || + (clVars.linkState.identifiedProtocol == 1) ) { // Split the username list string - clVars.ulist = String(packet.val).split(";"); + clVars.ulist = String(packet.val).split(';'); // Get rid of blank entry at the end of the list clVars.ulist.pop(clVars.ulist.length); // Check if username has been set (since older servers don't implement statuscodes or listeners) - if ( - clVars.username.attempted && - clVars.ulist.includes(clVars.username.temp) - ) { + if ((clVars.username.attempted) && (clVars.ulist.includes(clVars.username.temp))) { clVars.username.value = clVars.username.temp; clVars.username.accepted = true; //console.log(`[CloudLink] Username has been set to \"${clVars.username.value}\" successfully!`); @@ -660,32 +660,32 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Protocol v3-v4 (0.1.9.x - latest, 0.2.0) uses "mode" to add/set/remove entries to the userlist. else { // Check for "mode" key - if (!packet.hasOwnProperty("mode")) { + if (!Object.prototype.hasOwnProperty.call(packet, "mode")) { //console.warn("[CloudLink] Userlist message did not specify \"mode\" while running in protocol mode 3 or 4."); return; - } + }; // Handle methods switch (packet.mode) { - case "set": + case 'set': clVars.ulist = packet.val; break; - case "add": + case 'add': clVars.ulist.push(packet.val); clVars.recentlyJoinedUser = packet.val; - Scratch.vm.runtime.startHats("cloudlink_whenuserconnects"); + Scratch.vm.runtime.startHats('cloudlink_whenuserconnects'); break; - case "remove": - let index = -1; + case 'remove': + let index = -1 for (let i = 0; i < clVars.ulist.length; i++) { - let user = clVars.ulist[i]; + let user = clVars.ulist[i] if (user.uuid == packet.val.uuid) { - index = i; + index = i break; } } clVars.ulist.splice(index, 1); clVars.recentlyLeftUser = packet.val; - Scratch.vm.runtime.startHats("cloudlink_whenuserdisconnects"); + Scratch.vm.runtime.startHats('cloudlink_whenuserdisconnects'); break; default: //console.warn(`[CloudLink] Unrecognised userlist mode: \"${packet.mode}\".`); @@ -718,8 +718,9 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna } // Handle listeners - if (packet.hasOwnProperty("listener")) { + if (Object.prototype.hasOwnProperty.call(packet, "listener")) { if (clVars.listeners.current.includes(String(packet.listener))) { + // Remove the listener from the currently listening list clVars.listeners.current.splice( clVars.listeners.current.indexOf(String(packet.listener)), @@ -789,10 +790,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna break; case 2: // Was already connected - if ( - event.wasClean || - clVars.linkState.isAttemptingGracefulDisconnect - ) { + if (event.wasClean || clVars.linkState.isAttemptingGracefulDisconnect) { // Set the link state to graceful disconnect. //console.log(`[CloudLink] Disconnected (${event.code} ${event.reason}).`); clVars.linkState.status = 3; @@ -810,58 +808,39 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna resetOnClose(); // Run all onClose event blocks - runtime.startHats("cloudlink_onClose"); + runtime.startHats('cloudlink_onClose'); // Return promise (during setup) return; - }; - } - - // GET the serverList - try { - Scratch.fetch( - "https://raw.githubusercontent.com/MikeDev101/cloudlink/master/serverlist.json" - ) - .then((response) => { - return response.text(); - }) - .then((data) => { - clVars.serverList = JSON.parse(data); - }) - .catch((err) => { - //console.log("[CloudLink] An error has occurred while parsing the public server list:", err); - clVars.serverList = {}; - }); - } catch (err) { - //console.log("[CloudLink] An error has occurred while fetching the public server list:", err); - clVars.serverList = {}; + } } // Declare the CloudLink library. class CloudLink { getInfo() { return { - id: "cloudlink", - name: "CloudLink", + id: 'cloudlink', + name: 'CloudLink V4', blockIconURI: cl_block, menuIconURI: cl_icon, docsURI: "https://github.com/MikeDev101/cloudlink/wiki/Scratch-Client", blocks: [ + { opcode: "returnGlobalData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("global data"), + text: Scratch.translate("global data") }, { opcode: "returnPrivateData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("private data"), + text: Scratch.translate("private data") }, { opcode: "returnDirectData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("direct data"), + text: Scratch.translate("direct data") }, "---", @@ -869,13 +848,13 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna { opcode: "returnLinkData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("link status"), + text: Scratch.translate("link status") }, { opcode: "returnStatusCode", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("status code"), + text: Scratch.translate("status code") }, "---", @@ -883,22 +862,20 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna { opcode: "returnUserListData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("usernames"), + text: Scratch.translate("usernames") }, { opcode: "returnUsernameDataNew", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("my username"), + text: Scratch.translate("my username") }, { opcode: "returnUsernameData", blockType: Scratch.BlockType.REPORTER, hideFromPalette: clVars.hideCLDeprecatedBlocks, - text: Scratch.translate( - "(OLD - DO NOT USE IN NEW PROJECTS) my username" - ), + text: Scratch.translate("(OLD - DO NOT USE IN NEW PROJECTS) my username") }, "---", @@ -906,25 +883,25 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna { opcode: "returnVersionData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("extension version"), + text: Scratch.translate("extension version") }, { opcode: "returnServerVersion", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("server version"), + text: Scratch.translate("server version") }, { opcode: "returnServerList", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("server list"), + text: Scratch.translate("server list") }, { opcode: "returnMOTD", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("server MOTD"), + text: Scratch.translate("server MOTD") }, "---", @@ -932,13 +909,13 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna { opcode: "returnClientIP", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("my IP address"), + text: Scratch.translate("my IP address") }, { opcode: "returnUserObject", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("my user object"), + text: Scratch.translate("my user object") }, "---", @@ -1012,12 +989,11 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna arguments: { PATH: { type: Scratch.ArgumentType.STRING, - defaultValue: "fruit/apples", + defaultValue: 'fruit/apples', }, JSON_STRING: { type: Scratch.ArgumentType.STRING, - defaultValue: - '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', + defaultValue: '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', }, }, }, @@ -1026,7 +1002,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna opcode: "getFromJSONArray", blockType: Scratch.BlockType.REPORTER, hideFromPalette: clVars.hideCLDeprecatedBlocks, - text: Scratch.translate("[NUM] from JSON array [ARRAY]"), + text: Scratch.translate('[NUM] from JSON array [ARRAY]'), arguments: { NUM: { type: Scratch.ArgumentType.NUMBER, @@ -1035,8 +1011,8 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna ARRAY: { type: Scratch.ArgumentType.STRING, defaultValue: '["foo","bar"]', - }, - }, + } + } }, { @@ -1060,12 +1036,12 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna arguments: { JSON_STRING: { type: Scratch.ArgumentType.STRING, - defaultValue: - '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', + defaultValue: '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', }, }, }, + "---", { @@ -1085,9 +1061,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna opcode: "requestURL", blockType: Scratch.BlockType.REPORTER, hideFromPalette: clVars.hideCLDeprecatedBlocks, - text: Scratch.translate( - "send request with method [method] for URL [url] with data [data] and headers [headers]" - ), + text: Scratch.translate("send request with method [method] for URL [url] with data [data] and headers [headers]"), arguments: { method: { type: Scratch.ArgumentType.STRING, @@ -1129,9 +1103,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna { opcode: "onListener", blockType: Scratch.BlockType.HAT, - text: Scratch.translate( - "when I receive new message with listener [ID]" - ), + text: Scratch.translate("when I receive new message with listener [ID]"), isEdgeActivated: true, arguments: { ID: { @@ -1269,8 +1241,8 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna IP: { type: Scratch.ArgumentType.STRING, defaultValue: "ws://127.0.0.1:3000/", - }, - }, + } + } }, { @@ -1281,14 +1253,14 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna ID: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1, - }, - }, + } + } }, { opcode: "closeSocket", blockType: Scratch.BlockType.COMMAND, - text: Scratch.translate("disconnect"), + text: Scratch.translate("disconnect") }, "---", @@ -1317,12 +1289,13 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna defaultValue: "example-listener", }, }, + }, "---", { - opcode: "linkToRooms", + opcode: 'linkToRooms', blockType: Scratch.BlockType.COMMAND, text: Scratch.translate("link to room(s) [ROOMS]"), arguments: { @@ -1330,7 +1303,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna type: Scratch.ArgumentType.STRING, defaultValue: Scratch.translate('["test"]'), }, - }, + } }, { @@ -1400,9 +1373,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna { opcode: "sendPDataAsVar", blockType: Scratch.BlockType.COMMAND, - text: Scratch.translate( - "send variable [VAR] to [ID] with data [DATA]" - ), + text: Scratch.translate("send variable [VAR] to [ID] with data [DATA]"), arguments: { DATA: { type: Scratch.ArgumentType.STRING, @@ -1558,7 +1529,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna menu: "allmenu", defaultValue: "All data", }, - }, + } }, { @@ -1604,71 +1575,45 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna }, "---", + ], menus: { datamenu: { items: [ - { text: Scratch.translate("Global data"), value: "Global data" }, - { - text: Scratch.translate("Private data"), - value: "Private data", - }, - { text: Scratch.translate("Direct data"), value: "Direct data" }, - { text: Scratch.translate("Status code"), value: "Status code" }, - ], + { text: Scratch.translate('Global data'), value: 'Global data' }, + { text: Scratch.translate('Private data'), value: 'Private data' }, + { text: Scratch.translate('Direct data'), value: 'Direct data' }, + { text: Scratch.translate('Status code'), value: 'Status code' } + ] }, varmenu: { items: [ - { - text: Scratch.translate("Global variables"), - value: "Global variables", - }, - { - text: Scratch.translate("Private variables"), - value: "Private variables", - }, - ], + { text: Scratch.translate('Global variables'), value: "Global variables" }, + { text: Scratch.translate('Private variables'), value: "Private variables" } + ] }, allmenu: { items: [ - { text: Scratch.translate("Global data"), value: "Global data" }, - { - text: Scratch.translate("Private data"), - value: "Private data", - }, - { text: Scratch.translate("Direct data"), value: "Direct data" }, - { text: Scratch.translate("Status code"), value: "Status code" }, - { - text: Scratch.translate("Global variables"), - value: "Global variables", - }, - { - text: Scratch.translate("Private variables"), - value: "Private variables", - }, - { text: Scratch.translate("All data"), value: "All data" }, - ], + { text: Scratch.translate('Global data'), value: 'Global data' }, + { text: Scratch.translate('Private data'), value: 'Private data' }, + { text: Scratch.translate('Direct data'), value: 'Direct data' }, + { text: Scratch.translate('Status code'), value: 'Status code' }, + { text: Scratch.translate("Global variables"), value: "Global variables" }, + { text: Scratch.translate("Private variables"), value: "Private variables" }, + { text: Scratch.translate("All data"), value: "All data" } + ] }, almostallmenu: { items: [ - { text: Scratch.translate("Global data"), value: "Global data" }, - { - text: Scratch.translate("Private data"), - value: "Private data", - }, - { text: Scratch.translate("Direct data"), value: "Direct data" }, - { text: Scratch.translate("Status code"), value: "Status code" }, - { - text: Scratch.translate("Global variables"), - value: "Global variables", - }, - { - text: Scratch.translate("Private variables"), - value: "Private variables", - }, - ], + { text: Scratch.translate('Global data'), value: 'Global data' }, + { text: Scratch.translate('Private data'), value: 'Private data' }, + { text: Scratch.translate('Direct data'), value: 'Direct data' }, + { text: Scratch.translate('Status code'), value: 'Status code' }, + { text: Scratch.translate("Global variables"), value: "Global variables" }, + { text: Scratch.translate("Private variables"), value: "Private variables" } + ] }, - }, + } }; } @@ -1763,7 +1708,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Reporter - Returns data for a specific listener ID. // ID - String (listener ID) returnListenerData(args) { - if (!clVars.listeners.varStates.hasOwnProperty(String(args.ID))) { + if (!Object.prototype.hasOwnProperty.call(clVars.listeners.varStates, String(args.ID))) { //console.warn(`[CloudLink] Listener ID ${args.ID} does not exist!`); return ""; } @@ -1771,29 +1716,29 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna } getNextPacket(args) { - let temp = ""; + let temp = "" switch (args.TYPE) { - case "Global data": + case 'Global data': temp = clVars.gmsg.queue[0]; clVars.gmsg.queue.shift(); break; - case "Private data": + case 'Private data': temp = clVars.pmsg.queue[0]; clVars.pmsg.queue.shift(); break; - case "Direct data": + case 'Direct data': temp = clVars.direct.queue[0]; clVars.direct.queue.shift(); break; - case "Status code": + case 'Status code': temp = clVars.statuscode.queue[0]; clVars.statuscode.queue.shift(); break; - case "Global variables": + case 'Global variables': temp = clVars.gvar.queue[0]; clVars.gvar.queue.shift(); break; - case "Private variables": + case 'Private variables': temp = clVars.pvar.queue[0]; clVars.pvar.queue.shift(); break; @@ -1803,29 +1748,29 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna } getAndClearPacketQueue(args) { - let temp = ""; + let temp = "" switch (args.TYPE) { - case "Global data": + case 'Global data': temp = clVars.gmsg.queue; clVars.gmsg.queue = []; break; - case "Private data": + case 'Private data': temp = clVars.pmsg.queue; clVars.pmsg.queue = []; break; - case "Direct data": + case 'Direct data': temp = clVars.direct.queue; clVars.direct.queue = []; break; - case "Status code": + case 'Status code': temp = clVars.statuscode.queue; clVars.statuscode.queue = []; break; - case "Global variables": + case 'Global variables': temp = clVars.gvar.queue; clVars.gvar.queue = []; break; - case "Private variables": + case 'Private variables': temp = clVars.pvar.queue; clVars.pvar.queue = []; break; @@ -1836,18 +1781,20 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna newPacketsExist(args) { switch (args.TYPE) { - case "Global data": + case 'Global data': return clVars.gmsg.queue.length > 0; - case "Private data": + case 'Private data': return clVars.pmsg.queue.length > 0; - case "Direct data": + case 'Direct data': return clVars.direct.queue.length > 0; - case "Status code": + case 'Status code': return clVars.statuscode.queue.length > 0; - case "Global variables": + case 'Global variables': return clVars.gvar.queue.length > 0; - case "Private variables": + case 'Private variables': return clVars.pvar.queue.length > 0; + default: + return false; } } @@ -1855,19 +1802,19 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // TYPE - String (menu allmenu) readQueueSize(args) { switch (args.TYPE) { - case "Global data": + case 'Global data': return clVars.gmsg.queue.length; - case "Private data": + case 'Private data': return clVars.pmsg.queue.length; - case "Direct data": + case 'Direct data': return clVars.direct.queue.length; - case "Status code": + case 'Status code': return clVars.statuscode.queue.length; - case "Global variables": + case 'Global variables': return clVars.gvar.queue.length; - case "Private variables": + case 'Private variables': return clVars.pvar.queue.length; - case "All data": + case 'All data': return ( clVars.gmsg.queue.length + clVars.pmsg.queue.length + @@ -1876,6 +1823,8 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna clVars.gvar.queue.length + clVars.pvar.queue.length ); + default: + return 0; } } @@ -1883,27 +1832,29 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // TYPE - String (menu allmenu) readQueueData(args) { switch (args.TYPE) { - case "Global data": + case 'Global data': return makeValueScratchSafe(clVars.gmsg.queue); - case "Private data": + case 'Private data': return makeValueScratchSafe(clVars.pmsg.queue); - case "Direct data": + case 'Direct data': return makeValueScratchSafe(clVars.direct.queue); - case "Status code": + case 'Status code': return makeValueScratchSafe(clVars.statuscode.queue); - case "Global variables": + case 'Global variables': return makeValueScratchSafe(clVars.gvar.queue); - case "Private variables": + case 'Private variables': return makeValueScratchSafe(clVars.pvar.queue); - case "All data": + case 'All data': return makeValueScratchSafe({ gmsg: clVars.gmsg.queue, pmsg: clVars.pmsg.queue, direct: clVars.direct.queue, statuscode: clVars.statuscode.queue, gvar: clVars.gvar.queue, - pvar: clVars.pvar.queue, + pvar: clVars.pvar.queue }); + default: + return ""; } } @@ -1911,18 +1862,20 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // TYPE - String (menu varmenu), VAR - String (variable name) returnVarData(args) { switch (args.TYPE) { - case "Global variables": - if (!clVars.gvar.varStates.hasOwnProperty(String(args.VAR))) { + case 'Global variables': + if (!Object.prototype.hasOwnProperty.call(clVars.gvar.varStates, String(args.VAR))) { //console.warn(`[CloudLink] Global variable ${args.VAR} does not exist!`); return ""; } return clVars.gvar.varStates[String(args.VAR)].varState; - case "Private variables": - if (!clVars.pvar.varStates.hasOwnProperty(String(args.VAR))) { + case 'Private variables': + if (!Object.prototype.hasOwnProperty.call(clVars.pvar.varStates, String(args.VAR))) { //console.warn(`[CloudLink] Private variable ${args.VAR} does not exist!`); return ""; } return clVars.pvar.varStates[String(args.VAR)].varState; + default: + return ""; } } @@ -1930,25 +1883,23 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // PATH - String, JSON_STRING - String parseJSON(args) { try { - const path = args.PATH.toString() - .split("/") - .map((prop) => decodeURIComponent(prop)); - if (path[0] === "") path.splice(0, 1); - if (path[path.length - 1] === "") path.splice(-1, 1); + const path = args.PATH.toString().split('/').map(prop => decodeURIComponent(prop)); + if (path[0] === '') path.splice(0, 1); + if (path[path.length - 1] === '') path.splice(-1, 1); let json; try { - json = JSON.parse(" " + args.JSON_STRING); + json = JSON.parse(' ' + args.JSON_STRING); } catch (e) { return e.message; - } - path.forEach((prop) => (json = json[prop])); - if (json === null) return "null"; - else if (json === undefined) return ""; - else if (typeof json === "object") return JSON.stringify(json); + }; + path.forEach(prop => json = json[prop]); + if (json === null) return 'null'; + else if (json === undefined) return ''; + else if (typeof json === 'object') return JSON.stringify(json); else return json.toString(); } catch (err) { - return ""; - } + return ''; + }; } // Reporter - Returns an entry from a JSON array (0-based). @@ -1960,7 +1911,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna } else { let data = json_array[args.NUM]; - if (typeof data == "object") { + if (typeof (data) == "object") { data = JSON.stringify(data); // Make the JSON safe for Scratch } @@ -1972,8 +1923,8 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // url - String fetchURL(args) { return Scratch.fetch(args.url, { method: "GET" }) - .then((response) => response.text()) - .catch((error) => { + .then(response => response.text()) + .catch(error => { //console.warn(`[CloudLink] Fetch error: ${error}`); }); } @@ -1984,20 +1935,20 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (args.method == "GET" || args.method == "HEAD") { return Scratch.fetch(args.url, { method: args.method, - headers: JSON.parse(args.headers), + headers: JSON.parse(args.headers) }) - .then((response) => response.text()) - .catch((error) => { + .then(response => response.text()) + .catch(error => { //console.warn(`[CloudLink] Request error: ${error}`); }); } else { return Scratch.fetch(args.url, { method: args.method, headers: JSON.parse(args.headers), - body: args.data, + body: args.data }) - .then((response) => response.text()) - .catch((error) => { + .then(response => response.text()) + .catch(error => { //console.warn(`[CloudLink] Request error: ${error}`); }); } @@ -2011,7 +1962,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (clVars.linkState.status != 2) return false; // Listener must exist - if (!clVars.listeners.varStates.hasOwnProperty(args.ID)) return false; + if (!Object.prototype.hasOwnProperty.call(clVars.listeners.varStates, args.ID)) return false; // Run event if (clVars.listeners.varStates[args.ID].eventHatTick) { @@ -2030,42 +1981,42 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Run event switch (args.TYPE) { - case "Global data": + case 'Global data': if (clVars.gmsg.eventHatTick) { clVars.gmsg.eventHatTick = false; return true; } break; - case "Private data": + case 'Private data': if (clVars.pmsg.eventHatTick) { clVars.pmsg.eventHatTick = false; return true; } break; - case "Direct data": + case 'Direct data': if (clVars.direct.eventHatTick) { clVars.direct.eventHatTick = false; return true; } break; - case "Status code": + case 'Status code': if (clVars.statuscode.eventHatTick) { clVars.statuscode.eventHatTick = false; return true; } break; - case "Global variables": + case 'Global variables': if (clVars.gvar.eventHatTick) { clVars.gvar.eventHatTick = false; return true; } break; - case "Private variables": + case 'Private variables': if (clVars.pvar.eventHatTick) { clVars.pvar.eventHatTick = false; return true; @@ -2084,9 +2035,10 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Run event switch (args.TYPE) { - case "Global variables": + case 'Global variables': + // Variable must exist - if (!clVars.gvar.varStates.hasOwnProperty(String(args.VAR))) break; + if (!Object.prototype.hasOwnProperty.call(clVars.gvar.varStates, String(args.VAR))) break; if (clVars.gvar.varStates[String(args.VAR)].eventHatTick) { clVars.gvar.varStates[String(args.VAR)].eventHatTick = false; return true; @@ -2094,9 +2046,10 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna break; - case "Private variables": + case 'Private variables': + // Variable must exist - if (!clVars.pvar.varStates.hasOwnProperty(String(args.VAR))) break; + if (!Object.prototype.hasOwnProperty.call(clVars.pvar.varStates, String(args.VAR))) break; if (clVars.pvar.varStates[String(args.VAR)].eventHatTick) { clVars.pvar.varStates[String(args.VAR)].eventHatTick = false; return true; @@ -2110,64 +2063,61 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Reporter - Returns a JSON-ified value. // toBeJSONified - String makeJSON(args) { - if (typeof args.toBeJSONified == "string") { + if (typeof (args.toBeJSONified) == "string") { try { JSON.parse(args.toBeJSONified); return String(args.toBeJSONified); } catch (err) { return "Not JSON!"; } - } else if (typeof args.toBeJSONified == "object") { + } else if (typeof (args.toBeJSONified) == "object") { return JSON.stringify(args.toBeJSONified); } else { return "Not JSON!"; - } + }; } // Boolean - Returns true if connected. getComState() { - return clVars.linkState.status == 2 && clVars.socket != null; + return ((clVars.linkState.status == 2) && (clVars.socket != null)); } // Boolean - Returns true if linked to rooms (other than "default") getRoomState() { - return clVars.socket != null && clVars.rooms.isLinked; + return ((clVars.socket != null) && (clVars.rooms.isLinked)); } // Boolean - Returns true if the connection was dropped. getComLostConnectionState() { - return ( - clVars.linkState.status == 4 && clVars.linkState.disconnectType == 2 - ); + return ((clVars.linkState.status == 4) && (clVars.linkState.disconnectType == 2)); } // Boolean - Returns true if the client failed to establish a connection. getComFailedConnectionState() { - return ( - clVars.linkState.status == 4 && clVars.linkState.disconnectType == 1 - ); + return ((clVars.linkState.status == 4) && (clVars.linkState.disconnectType == 1)); } // Boolean - Returns true if the username was set successfully. getUsernameState() { - return clVars.socket != null && clVars.username.accepted; + return ((clVars.socket != null) && (clVars.username.accepted)); } // Boolean - Returns true if there is new gmsg/pmsg/direct/statuscode data. // TYPE - String (menu datamenu) returnIsNewData(args) { + // Must be connected if (clVars.socket == null) return false; // Run event switch (args.TYPE) { - case "Global data": + case 'Global data': return clVars.gmsg.hasNew; - case "Private data": + case 'Private data': return clVars.pmsg.hasNew; - case "Direct data": + case 'Direct data': return clVars.direct.hasNew; - case "Status code": + case 'Status code': return clVars.statuscode.hasNew; } } @@ -2176,14 +2126,14 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // TYPE - String (menu varmenu), VAR - String (variable name) returnIsNewVarData(args) { switch (args.TYPE) { - case "Global variables": - if (!clVars.gvar.varStates.hasOwnProperty(String(args.VAR))) { + case 'Global variables': + if (!Object.prototype.hasOwnProperty.call(clVars.gvar.varStates, String(args.VAR))) { //console.warn(`[CloudLink] Global variable ${args.VAR} does not exist!`); return false; } return clVars.gvar.varStates[String(args.ID)].hasNew; - case "Private variables": - if (!clVars.pvar.varStates.hasOwnProperty(String(args.VAR))) { + case 'Private variables': + if (!Object.prototype.hasOwnProperty.call(clVars.pvar.varStates, String(args.VAR))) { //console.warn(`[CloudLink] Private variable ${args.VAR} does not exist!`); return false; } @@ -2194,7 +2144,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Boolean - Returns true if a listener has a new value. // ID - String (listener ID) returnIsNewListener(args) { - if (!clVars.listeners.varStates.hasOwnProperty(String(args.ID))) { + if (!Object.prototype.hasOwnProperty.call(clVars.listeners.varStates, String(args.ID))) { //console.warn(`[CloudLink] Listener ID ${args.ID} does not exist!`); return false; } @@ -2204,21 +2154,24 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Boolean - Returns true if a username/ID/UUID/object exists in the userlist. // ID - String (username or user object) checkForID(args) { + // Legacy ulist handling if (clVars.ulist.includes(args.ID)) return true; // New ulist handling if (clVars.linkState.identifiedProtocol > 2) { if (this.isValidJSON(args.ID)) { - return clVars.ulist.some( - (o) => - o.username === JSON.parse(args.ID).username && - o.id == JSON.parse(args.ID).id - ); + return clVars.ulist.some(o => ( + (o.username === JSON.parse(args.ID).username) + && + (o.id == JSON.parse(args.ID).id) + )); } else { - return clVars.ulist.some( - (o) => o.username === String(args.ID) || o.id == args.ID - ); + return clVars.ulist.some(o => ( + (o.username === String(args.ID)) + || + (o.id == args.ID) + )); } } else return false; } @@ -2231,7 +2184,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna return true; } catch { return false; - } + }; } // Command - Establishes a connection to a server. @@ -2240,7 +2193,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (clVars.socket != null) { //console.warn("[CloudLink] Already connected to a server."); return; - } + }; return newClient(args.IP); } @@ -2250,11 +2203,11 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (clVars.socket != null) { //console.warn("[CloudLink] Already connected to a server."); return; - } - if (!clVars.serverList.hasOwnProperty(String(args.ID))) { + }; + if (!Object.prototype.hasOwnProperty.call(clVars.serverList, String(args.ID))) { //console.warn("[CloudLink] Not a valid server ID!"); return; - } + }; return newClient(clVars.serverList[String(args.ID)]["url"]); } @@ -2263,7 +2216,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (clVars.socket == null) { //console.warn("[CloudLink] Already disconnected."); return; - } + }; //console.log("[CloudLink] Disconnecting..."); clVars.linkState.isAttemptingGracefulDisconnect = true; clVars.socket.close(1000, "Client going away"); @@ -2279,13 +2232,13 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (clVars.username.attempted) { //console.warn("[CloudLink] Already attempting to set username!"); return; - } + }; // Prevent running if the username is already set. if (clVars.username.accepted) { //console.warn("[CloudLink] Already set username!"); return; - } + }; // Update state clVars.username.attempted = true; @@ -2298,6 +2251,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Prepares the next transmitted message to have a listener ID attached to it. // ID - String (listener ID) createListener(args) { + // Must be connected to set a username. if (clVars.socket == null) return; @@ -2311,7 +2265,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before creating a listener!"); return; - } + }; // Must be used once per packet if (clVars.listeners.enablerState) { @@ -2327,6 +2281,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Subscribes to various rooms on a server. // ROOMS - String (JSON Array or single string) linkToRooms(args) { + // Must be connected to set a username. if (clVars.socket == null) return; @@ -2340,19 +2295,19 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before linking to rooms!"); return; - } + }; // Prevent running if already linked. if (clVars.rooms.isLinked) { //console.warn("[CloudLink] Already linked to rooms!"); return; - } + }; // Prevent running if a room link is in progress. if (clVars.rooms.isAttemptingLink) { //console.warn("[CloudLink] Currently linking to rooms! Please wait!"); return; - } + }; clVars.rooms.isAttemptingLink = true; sendMessage({ cmd: "link", val: args.ROOMS, listener: "link" }); @@ -2361,6 +2316,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Specifies specific subscribed rooms to transmit messages to. // ROOMS - String (JSON Array or single string) selectRoomsInNextPacket(args) { + // Must be connected to user rooms. if (clVars.socket == null) return; @@ -2374,7 +2330,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before selecting rooms!"); return; - } + }; // Require once per packet if (clVars.rooms.enablerState) { @@ -2386,7 +2342,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (!clVars.rooms.isLinked) { //console.warn("[CloudLink] Cannot use room selector while not linked to rooms!"); return; - } + }; clVars.rooms.enablerState = true; clVars.rooms.enablerValue = args.ROOMS; @@ -2394,6 +2350,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Unsubscribes from all rooms and re-subscribes to the the "default" room on the server. unlinkFromRooms() { + // Must be connected to user rooms. if (clVars.socket == null) return; @@ -2407,19 +2364,19 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before unjoining rooms!"); return; - } + }; // Prevent running if already unlinked. if (!clVars.rooms.isLinked) { //console.warn("[CloudLink] Already unlinked from rooms!"); return; - } + }; // Prevent running if a room unlink is in progress. if (clVars.rooms.isAttemptingUnlink) { //console.warn("[CloudLink] Currently unlinking from rooms! Please wait!"); return; - } + }; clVars.rooms.isAttemptingUnlink = true; sendMessage({ cmd: "unlink", val: "", listener: "unlink" }); @@ -2428,6 +2385,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Sends a gmsg value. // DATA - String sendGData(args) { + // Must be connected. if (clVars.socket == null) return; @@ -2437,6 +2395,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Sends a pmsg value. // DATA - String, ID - String (recipient ID) sendPData(args) { + // Must be connected. if (clVars.socket == null) return; @@ -2444,7 +2403,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before sending private messages!"); return; - } + }; sendMessage({ cmd: "pmsg", val: args.DATA, id: args.ID }); } @@ -2452,6 +2411,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Sends a gvar value. // DATA - String, VAR - String (variable name) sendGDataAsVar(args) { + // Must be connected. if (clVars.socket == null) return; @@ -2461,6 +2421,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Sends a pvar value. // DATA - String, VAR - String (variable name), ID - String (recipient ID) sendPDataAsVar(args) { + // Must be connected. if (clVars.socket == null) return; @@ -2468,7 +2429,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before sending private variables!"); return; - } + }; sendMessage({ cmd: "pvar", val: args.DATA, name: args.VAR, id: args.ID }); } @@ -2476,6 +2437,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Sends a raw-format command without specifying an ID. // CMD - String (command), DATA - String runCMDnoID(args) { + // Must be connected. if (clVars.socket == null) return; @@ -2485,6 +2447,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Sends a raw-format command with an ID. // CMD - String (command), DATA - String, ID - String (recipient ID) runCMD(args) { + // Must be connected. if (clVars.socket == null) return; @@ -2492,7 +2455,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before using this command!"); return; - } + }; sendMessage({ cmd: args.CMD, val: args.DATA, id: args.ID }); } @@ -2501,16 +2464,16 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // TYPE - String (menu datamenu) resetNewData(args) { switch (args.TYPE) { - case "Global data": + case 'Global data': clVars.gmsg.hasNew = false; break; - case "Private data": + case 'Private data': clVars.pmsg.hasNew = false; break; - case "Direct data": + case 'Direct data': clVars.direct.hasNew = false; break; - case "Status code": + case 'Status code': clVars.statuscode.hasNew = false; break; } @@ -2520,14 +2483,15 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // TYPE - String (menu varmenu), VAR - String (variable name) resetNewVarData(args) { switch (args.TYPE) { - case "Global variables": - if (!clVars.gvar.varStates.hasOwnProperty(String(args.VAR))) { + case 'Global variables': + if (!Object.prototype.hasOwnProperty.call(clVars.gvar.varStates, String(args.VAR))) { //console.warn(`[CloudLink] Global variable ${args.VAR} does not exist!`); return; } clVars.gvar.varStates[String(args.ID)].hasNew = false; - case "Private variables": - if (!clVars.pvar.varStates.hasOwnProperty(String(args.VAR))) { + break; + case 'Private variables': + if (!Object.prototype.hasOwnProperty.call(clVars.pvar.varStates, String(args.VAR))) { //console.warn(`[CloudLink] Private variable ${args.VAR} does not exist!`); return false; } @@ -2538,7 +2502,7 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // Command - Resets the "returnIsNewListener" boolean state. // ID - Listener ID resetNewListener(args) { - if (!clVars.listeners.varStates.hasOwnProperty(String(args.ID))) { + if (!Object.prototype.hasOwnProperty.call(clVars.listeners.varStates, String(args.ID))) { //console.warn(`[CloudLink] Listener ID ${args.ID} does not exist!`); return; } @@ -2549,25 +2513,25 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna // TYPE - String (menu allmenu) clearAllPackets(args) { switch (args.TYPE) { - case "Global data": + case 'Global data': clVars.gmsg.queue = []; break; - case "Private data": + case 'Private data': clVars.pmsg.queue = []; break; - case "Direct data": + case 'Direct data': clVars.direct.queue = []; break; - case "Status code": + case 'Status code': clVars.statuscode.queue = []; break; - case "Global variables": + case 'Global variables': clVars.gvar.queue = []; break; - case "Private variables": + case 'Private variables': clVars.pvar.queue = []; break; - case "All data": + case 'All data': clVars.gmsg.queue = []; clVars.pmsg.queue = []; clVars.direct.queue = []; @@ -2579,15 +2543,11 @@ Scratch.translate.setup({ "fi": { "_(OLD - DO NOT USE IN NEW PROJECTS) my userna } recentlyjoined() { - return makeValueScratchSafe( - JSON.stringify(clVars?.recentlyJoinedUser ?? {}) - ); + return makeValueScratchSafe(JSON.stringify(clVars?.recentlyJoinedUser ?? {})); } recentlyleft() { - return makeValueScratchSafe( - JSON.stringify(clVars?.recentlyLeftUser ?? {}) - ); + return makeValueScratchSafe(JSON.stringify(clVars?.recentlyLeftUser ?? {})); } } Scratch.extensions.register(new CloudLink()); From 3f810306cf98d08a32b92255a3194f40fbfa7aea Mon Sep 17 00:00:00 2001 From: "DangoCat[bot]" Date: Fri, 10 Apr 2026 15:57:28 +0000 Subject: [PATCH 04/11] [Automated] Format code --- extensions/cloudlink.js | 603 +++++++++++++++++++++++----------------- 1 file changed, 352 insertions(+), 251 deletions(-) diff --git a/extensions/cloudlink.js b/extensions/cloudlink.js index c9e829b746..c7eecfe0e2 100644 --- a/extensions/cloudlink.js +++ b/extensions/cloudlink.js @@ -5,7 +5,6 @@ // License: MIT (function (Scratch) { - /* Based on https://github.com/Mistium/extensions.mistium/blob/main/files%2FCloudlink4_Improved.js. @@ -39,9 +38,9 @@ */ // Require extension to be unsandboxed. - 'use strict'; + "use strict"; if (!Scratch.extensions.unsandboxed) { - throw new Error('The CloudLink extension must run unsandboxed.'); + throw new Error("The CloudLink extension must run unsandboxed."); } // Declare icons as static SVG URIs @@ -83,7 +82,6 @@ // Store extension state var clVars = { - // Editor-specific variable for hiding old, legacy-support blocks. hideCLDeprecatedBlocks: true, @@ -247,7 +245,7 @@ url: "wss://cl.mikedev101.cc/", }, }, - } + }; function generateVersionString() { return `${version.editorType} ${version.versionString}`; @@ -347,12 +345,11 @@ if (Object.prototype.hasOwnProperty.call(message, "val")) { try { message.val = JSON.parse(message.val); - } catch { } + } catch {} } // Attach listeners if (clVars.listeners.enablerState) { - // 0.1.8.x was the first server version to support listeners. if (clVars.linkState.identifiedProtocol >= 2) { message.listener = clVars.listeners.enablerValue; @@ -363,7 +360,6 @@ varState: {}, eventHatTick: false, }; - } else { //console.warn("[CloudLink] Server is too old! Must be at least 0.1.8.x to support listeners."); } @@ -371,7 +367,10 @@ } // Check if server supports rooms - if (((message.cmd == "link") || (message.cmd == "unlink")) && (clVars.linkState.identifiedProtocol < 2)) { + if ( + (message.cmd == "link" || message.cmd == "unlink") && + clVars.linkState.identifiedProtocol < 2 + ) { // 0.1.8.x was the first server version to support rooms. //console.warn("[CloudLink] Server is too old! Must be at least 0.1.8.x to support room linking/unlinking."); return; @@ -404,7 +403,7 @@ versionNumber: version.versionNumber, }, }, - listener: "handshake_cfg" + listener: "handshake_cfg", }); clVars.handshakeAttempted = true; } @@ -424,13 +423,12 @@ "S2.2": 0, // 0.1.5 "0.1.": 0, // 0.1.5 or legacy "S2.": 0, // Legacy - "S1.": -1 // Obsolete + "S1.": -1, // Obsolete }; for (const [key, value] of Object.entries(versions)) { if (version.includes(key)) { if (clVars.linkState.identifiedProtocol < value) { - // Disconnect if protcol is too old if (value == -1) { //console.warn(`[CloudLink] Server is too old to enable leagacy support. Disconnecting.`); @@ -441,7 +439,7 @@ clVars.linkState.identifiedProtocol = value; } } - }; + } // Log configured spec version // // ////console.log(`[CloudLink] Configured protocol spec to v${clVars.linkState.identifiedProtocol}.`); @@ -450,18 +448,24 @@ clVars.linkState.status = 2; // Fire event hats (only one not broken) - runtime.startHats('cloudlink_onConnect'); + runtime.startHats("cloudlink_onConnect"); // Don't nag user if they already trusted this server if (clVars.currentServerUrl === clVars.lastServerUrl) return; // Ask user if they wish to stay connected if the server is unsupported - if ((clVars.linkState.identifiedProtocol < 4) && (!confirm( - `You have connected to an old CloudLink server, running version ${clVars.server_version}.\n\nFor your security and privacy, we recommend you disconnect from this server and connect to an up-to-date server.\n\nClick/tap \"OK\" to stay connected.` - ))) { + if ( + clVars.linkState.identifiedProtocol < 4 && + !confirm( + `You have connected to an old CloudLink server, running version ${clVars.server_version}.\n\nFor your security and privacy, we recommend you disconnect from this server and connect to an up-to-date server.\n\nClick/tap \"OK\" to stay connected.` + ) + ) { // Close the connection if they choose "Cancel" clVars.linkState.isAttemptingGracefulDisconnect = true; - clVars.socket.close(1000, "Client going away (legacy server rejected by end user)"); + clVars.socket.close( + 1000, + "Client going away (legacy server rejected by end user)" + ); return; } @@ -474,11 +478,11 @@ // Parse the message JSON let packet = {}; try { - packet = JSON.parse(data) + packet = JSON.parse(data); } catch (SyntaxError) { //console.error("[CloudLink] Incoming message parse failure! Is this really a CloudLink server?", data); return; - }; + } // Handle packet commands if (!Object.prototype.hasOwnProperty.call(packet, "cmd")) { @@ -567,12 +571,10 @@ // Protocol v2 (0.1.8.x) uses "code" instead. // Protocol v3-v4 (0.1.9.x - latest, 0.2.0) adds "code_id" to the payload. Ignored by Scratch clients. else { - // Handle setup listeners if (Object.prototype.hasOwnProperty.call(packet, "listener")) { switch (packet.listener) { case "username_cfg": - // Username accepted if (packet.code.includes("I:100")) { clVars.myUserObject = packet.val; @@ -634,18 +636,20 @@ case "ulist": // Protocol v0-v1 (0.1.5 and legacy - 0.1.7) use a semicolon (;) separated string for the userlist. if ( - (clVars.linkState.identifiedProtocol == 0) - || - (clVars.linkState.identifiedProtocol == 1) + clVars.linkState.identifiedProtocol == 0 || + clVars.linkState.identifiedProtocol == 1 ) { // Split the username list string - clVars.ulist = String(packet.val).split(';'); + clVars.ulist = String(packet.val).split(";"); // Get rid of blank entry at the end of the list clVars.ulist.pop(clVars.ulist.length); // Check if username has been set (since older servers don't implement statuscodes or listeners) - if ((clVars.username.attempted) && (clVars.ulist.includes(clVars.username.temp))) { + if ( + clVars.username.attempted && + clVars.ulist.includes(clVars.username.temp) + ) { clVars.username.value = clVars.username.temp; clVars.username.accepted = true; //console.log(`[CloudLink] Username has been set to \"${clVars.username.value}\" successfully!`); @@ -663,29 +667,29 @@ if (!Object.prototype.hasOwnProperty.call(packet, "mode")) { //console.warn("[CloudLink] Userlist message did not specify \"mode\" while running in protocol mode 3 or 4."); return; - }; + } // Handle methods switch (packet.mode) { - case 'set': + case "set": clVars.ulist = packet.val; break; - case 'add': + case "add": clVars.ulist.push(packet.val); clVars.recentlyJoinedUser = packet.val; - Scratch.vm.runtime.startHats('cloudlink_whenuserconnects'); + Scratch.vm.runtime.startHats("cloudlink_whenuserconnects"); break; - case 'remove': - let index = -1 + case "remove": + let index = -1; for (let i = 0; i < clVars.ulist.length; i++) { - let user = clVars.ulist[i] + let user = clVars.ulist[i]; if (user.uuid == packet.val.uuid) { - index = i + index = i; break; } } clVars.ulist.splice(index, 1); clVars.recentlyLeftUser = packet.val; - Scratch.vm.runtime.startHats('cloudlink_whenuserdisconnects'); + Scratch.vm.runtime.startHats("cloudlink_whenuserdisconnects"); break; default: //console.warn(`[CloudLink] Unrecognised userlist mode: \"${packet.mode}\".`); @@ -720,7 +724,6 @@ // Handle listeners if (Object.prototype.hasOwnProperty.call(packet, "listener")) { if (clVars.listeners.current.includes(String(packet.listener))) { - // Remove the listener from the currently listening list clVars.listeners.current.splice( clVars.listeners.current.indexOf(String(packet.listener)), @@ -790,7 +793,10 @@ break; case 2: // Was already connected - if (event.wasClean || clVars.linkState.isAttemptingGracefulDisconnect) { + if ( + event.wasClean || + clVars.linkState.isAttemptingGracefulDisconnect + ) { // Set the link state to graceful disconnect. //console.log(`[CloudLink] Disconnected (${event.code} ${event.reason}).`); clVars.linkState.status = 3; @@ -808,39 +814,38 @@ resetOnClose(); // Run all onClose event blocks - runtime.startHats('cloudlink_onClose'); + runtime.startHats("cloudlink_onClose"); // Return promise (during setup) return; - } + }; } // Declare the CloudLink library. class CloudLink { getInfo() { return { - id: 'cloudlink', - name: 'CloudLink V4', + id: "cloudlink", + name: "CloudLink V4", blockIconURI: cl_block, menuIconURI: cl_icon, docsURI: "https://github.com/MikeDev101/cloudlink/wiki/Scratch-Client", blocks: [ - { opcode: "returnGlobalData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("global data") + text: Scratch.translate("global data"), }, { opcode: "returnPrivateData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("private data") + text: Scratch.translate("private data"), }, { opcode: "returnDirectData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("direct data") + text: Scratch.translate("direct data"), }, "---", @@ -848,13 +853,13 @@ { opcode: "returnLinkData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("link status") + text: Scratch.translate("link status"), }, { opcode: "returnStatusCode", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("status code") + text: Scratch.translate("status code"), }, "---", @@ -862,20 +867,22 @@ { opcode: "returnUserListData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("usernames") + text: Scratch.translate("usernames"), }, { opcode: "returnUsernameDataNew", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("my username") + text: Scratch.translate("my username"), }, { opcode: "returnUsernameData", blockType: Scratch.BlockType.REPORTER, hideFromPalette: clVars.hideCLDeprecatedBlocks, - text: Scratch.translate("(OLD - DO NOT USE IN NEW PROJECTS) my username") + text: Scratch.translate( + "(OLD - DO NOT USE IN NEW PROJECTS) my username" + ), }, "---", @@ -883,25 +890,25 @@ { opcode: "returnVersionData", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("extension version") + text: Scratch.translate("extension version"), }, { opcode: "returnServerVersion", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("server version") + text: Scratch.translate("server version"), }, { opcode: "returnServerList", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("server list") + text: Scratch.translate("server list"), }, { opcode: "returnMOTD", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("server MOTD") + text: Scratch.translate("server MOTD"), }, "---", @@ -909,13 +916,13 @@ { opcode: "returnClientIP", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("my IP address") + text: Scratch.translate("my IP address"), }, { opcode: "returnUserObject", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate("my user object") + text: Scratch.translate("my user object"), }, "---", @@ -989,11 +996,12 @@ arguments: { PATH: { type: Scratch.ArgumentType.STRING, - defaultValue: 'fruit/apples', + defaultValue: "fruit/apples", }, JSON_STRING: { type: Scratch.ArgumentType.STRING, - defaultValue: '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', + defaultValue: + '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', }, }, }, @@ -1002,7 +1010,7 @@ opcode: "getFromJSONArray", blockType: Scratch.BlockType.REPORTER, hideFromPalette: clVars.hideCLDeprecatedBlocks, - text: Scratch.translate('[NUM] from JSON array [ARRAY]'), + text: Scratch.translate("[NUM] from JSON array [ARRAY]"), arguments: { NUM: { type: Scratch.ArgumentType.NUMBER, @@ -1011,8 +1019,8 @@ ARRAY: { type: Scratch.ArgumentType.STRING, defaultValue: '["foo","bar"]', - } - } + }, + }, }, { @@ -1036,12 +1044,12 @@ arguments: { JSON_STRING: { type: Scratch.ArgumentType.STRING, - defaultValue: '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', + defaultValue: + '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', }, }, }, - "---", { @@ -1061,7 +1069,9 @@ opcode: "requestURL", blockType: Scratch.BlockType.REPORTER, hideFromPalette: clVars.hideCLDeprecatedBlocks, - text: Scratch.translate("send request with method [method] for URL [url] with data [data] and headers [headers]"), + text: Scratch.translate( + "send request with method [method] for URL [url] with data [data] and headers [headers]" + ), arguments: { method: { type: Scratch.ArgumentType.STRING, @@ -1103,7 +1113,9 @@ { opcode: "onListener", blockType: Scratch.BlockType.HAT, - text: Scratch.translate("when I receive new message with listener [ID]"), + text: Scratch.translate( + "when I receive new message with listener [ID]" + ), isEdgeActivated: true, arguments: { ID: { @@ -1241,8 +1253,8 @@ IP: { type: Scratch.ArgumentType.STRING, defaultValue: "ws://127.0.0.1:3000/", - } - } + }, + }, }, { @@ -1253,14 +1265,14 @@ ID: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1, - } - } + }, + }, }, { opcode: "closeSocket", blockType: Scratch.BlockType.COMMAND, - text: Scratch.translate("disconnect") + text: Scratch.translate("disconnect"), }, "---", @@ -1289,13 +1301,12 @@ defaultValue: "example-listener", }, }, - }, "---", { - opcode: 'linkToRooms', + opcode: "linkToRooms", blockType: Scratch.BlockType.COMMAND, text: Scratch.translate("link to room(s) [ROOMS]"), arguments: { @@ -1303,7 +1314,7 @@ type: Scratch.ArgumentType.STRING, defaultValue: Scratch.translate('["test"]'), }, - } + }, }, { @@ -1373,7 +1384,9 @@ { opcode: "sendPDataAsVar", blockType: Scratch.BlockType.COMMAND, - text: Scratch.translate("send variable [VAR] to [ID] with data [DATA]"), + text: Scratch.translate( + "send variable [VAR] to [ID] with data [DATA]" + ), arguments: { DATA: { type: Scratch.ArgumentType.STRING, @@ -1529,7 +1542,7 @@ menu: "allmenu", defaultValue: "All data", }, - } + }, }, { @@ -1575,45 +1588,71 @@ }, "---", - ], menus: { datamenu: { items: [ - { text: Scratch.translate('Global data'), value: 'Global data' }, - { text: Scratch.translate('Private data'), value: 'Private data' }, - { text: Scratch.translate('Direct data'), value: 'Direct data' }, - { text: Scratch.translate('Status code'), value: 'Status code' } - ] + { text: Scratch.translate("Global data"), value: "Global data" }, + { + text: Scratch.translate("Private data"), + value: "Private data", + }, + { text: Scratch.translate("Direct data"), value: "Direct data" }, + { text: Scratch.translate("Status code"), value: "Status code" }, + ], }, varmenu: { items: [ - { text: Scratch.translate('Global variables'), value: "Global variables" }, - { text: Scratch.translate('Private variables'), value: "Private variables" } - ] + { + text: Scratch.translate("Global variables"), + value: "Global variables", + }, + { + text: Scratch.translate("Private variables"), + value: "Private variables", + }, + ], }, allmenu: { items: [ - { text: Scratch.translate('Global data'), value: 'Global data' }, - { text: Scratch.translate('Private data'), value: 'Private data' }, - { text: Scratch.translate('Direct data'), value: 'Direct data' }, - { text: Scratch.translate('Status code'), value: 'Status code' }, - { text: Scratch.translate("Global variables"), value: "Global variables" }, - { text: Scratch.translate("Private variables"), value: "Private variables" }, - { text: Scratch.translate("All data"), value: "All data" } - ] + { text: Scratch.translate("Global data"), value: "Global data" }, + { + text: Scratch.translate("Private data"), + value: "Private data", + }, + { text: Scratch.translate("Direct data"), value: "Direct data" }, + { text: Scratch.translate("Status code"), value: "Status code" }, + { + text: Scratch.translate("Global variables"), + value: "Global variables", + }, + { + text: Scratch.translate("Private variables"), + value: "Private variables", + }, + { text: Scratch.translate("All data"), value: "All data" }, + ], }, almostallmenu: { items: [ - { text: Scratch.translate('Global data'), value: 'Global data' }, - { text: Scratch.translate('Private data'), value: 'Private data' }, - { text: Scratch.translate('Direct data'), value: 'Direct data' }, - { text: Scratch.translate('Status code'), value: 'Status code' }, - { text: Scratch.translate("Global variables"), value: "Global variables" }, - { text: Scratch.translate("Private variables"), value: "Private variables" } - ] + { text: Scratch.translate("Global data"), value: "Global data" }, + { + text: Scratch.translate("Private data"), + value: "Private data", + }, + { text: Scratch.translate("Direct data"), value: "Direct data" }, + { text: Scratch.translate("Status code"), value: "Status code" }, + { + text: Scratch.translate("Global variables"), + value: "Global variables", + }, + { + text: Scratch.translate("Private variables"), + value: "Private variables", + }, + ], }, - } + }, }; } @@ -1708,7 +1747,12 @@ // Reporter - Returns data for a specific listener ID. // ID - String (listener ID) returnListenerData(args) { - if (!Object.prototype.hasOwnProperty.call(clVars.listeners.varStates, String(args.ID))) { + if ( + !Object.prototype.hasOwnProperty.call( + clVars.listeners.varStates, + String(args.ID) + ) + ) { //console.warn(`[CloudLink] Listener ID ${args.ID} does not exist!`); return ""; } @@ -1716,29 +1760,29 @@ } getNextPacket(args) { - let temp = "" + let temp = ""; switch (args.TYPE) { - case 'Global data': + case "Global data": temp = clVars.gmsg.queue[0]; clVars.gmsg.queue.shift(); break; - case 'Private data': + case "Private data": temp = clVars.pmsg.queue[0]; clVars.pmsg.queue.shift(); break; - case 'Direct data': + case "Direct data": temp = clVars.direct.queue[0]; clVars.direct.queue.shift(); break; - case 'Status code': + case "Status code": temp = clVars.statuscode.queue[0]; clVars.statuscode.queue.shift(); break; - case 'Global variables': + case "Global variables": temp = clVars.gvar.queue[0]; clVars.gvar.queue.shift(); break; - case 'Private variables': + case "Private variables": temp = clVars.pvar.queue[0]; clVars.pvar.queue.shift(); break; @@ -1748,29 +1792,29 @@ } getAndClearPacketQueue(args) { - let temp = "" + let temp = ""; switch (args.TYPE) { - case 'Global data': + case "Global data": temp = clVars.gmsg.queue; clVars.gmsg.queue = []; break; - case 'Private data': + case "Private data": temp = clVars.pmsg.queue; clVars.pmsg.queue = []; break; - case 'Direct data': + case "Direct data": temp = clVars.direct.queue; clVars.direct.queue = []; break; - case 'Status code': + case "Status code": temp = clVars.statuscode.queue; clVars.statuscode.queue = []; break; - case 'Global variables': + case "Global variables": temp = clVars.gvar.queue; clVars.gvar.queue = []; break; - case 'Private variables': + case "Private variables": temp = clVars.pvar.queue; clVars.pvar.queue = []; break; @@ -1781,17 +1825,17 @@ newPacketsExist(args) { switch (args.TYPE) { - case 'Global data': + case "Global data": return clVars.gmsg.queue.length > 0; - case 'Private data': + case "Private data": return clVars.pmsg.queue.length > 0; - case 'Direct data': + case "Direct data": return clVars.direct.queue.length > 0; - case 'Status code': + case "Status code": return clVars.statuscode.queue.length > 0; - case 'Global variables': + case "Global variables": return clVars.gvar.queue.length > 0; - case 'Private variables': + case "Private variables": return clVars.pvar.queue.length > 0; default: return false; @@ -1802,19 +1846,19 @@ // TYPE - String (menu allmenu) readQueueSize(args) { switch (args.TYPE) { - case 'Global data': + case "Global data": return clVars.gmsg.queue.length; - case 'Private data': + case "Private data": return clVars.pmsg.queue.length; - case 'Direct data': + case "Direct data": return clVars.direct.queue.length; - case 'Status code': + case "Status code": return clVars.statuscode.queue.length; - case 'Global variables': + case "Global variables": return clVars.gvar.queue.length; - case 'Private variables': + case "Private variables": return clVars.pvar.queue.length; - case 'All data': + case "All data": return ( clVars.gmsg.queue.length + clVars.pmsg.queue.length + @@ -1832,26 +1876,26 @@ // TYPE - String (menu allmenu) readQueueData(args) { switch (args.TYPE) { - case 'Global data': + case "Global data": return makeValueScratchSafe(clVars.gmsg.queue); - case 'Private data': + case "Private data": return makeValueScratchSafe(clVars.pmsg.queue); - case 'Direct data': + case "Direct data": return makeValueScratchSafe(clVars.direct.queue); - case 'Status code': + case "Status code": return makeValueScratchSafe(clVars.statuscode.queue); - case 'Global variables': + case "Global variables": return makeValueScratchSafe(clVars.gvar.queue); - case 'Private variables': + case "Private variables": return makeValueScratchSafe(clVars.pvar.queue); - case 'All data': + case "All data": return makeValueScratchSafe({ gmsg: clVars.gmsg.queue, pmsg: clVars.pmsg.queue, direct: clVars.direct.queue, statuscode: clVars.statuscode.queue, gvar: clVars.gvar.queue, - pvar: clVars.pvar.queue + pvar: clVars.pvar.queue, }); default: return ""; @@ -1862,14 +1906,24 @@ // TYPE - String (menu varmenu), VAR - String (variable name) returnVarData(args) { switch (args.TYPE) { - case 'Global variables': - if (!Object.prototype.hasOwnProperty.call(clVars.gvar.varStates, String(args.VAR))) { + case "Global variables": + if ( + !Object.prototype.hasOwnProperty.call( + clVars.gvar.varStates, + String(args.VAR) + ) + ) { //console.warn(`[CloudLink] Global variable ${args.VAR} does not exist!`); return ""; } return clVars.gvar.varStates[String(args.VAR)].varState; - case 'Private variables': - if (!Object.prototype.hasOwnProperty.call(clVars.pvar.varStates, String(args.VAR))) { + case "Private variables": + if ( + !Object.prototype.hasOwnProperty.call( + clVars.pvar.varStates, + String(args.VAR) + ) + ) { //console.warn(`[CloudLink] Private variable ${args.VAR} does not exist!`); return ""; } @@ -1883,23 +1937,25 @@ // PATH - String, JSON_STRING - String parseJSON(args) { try { - const path = args.PATH.toString().split('/').map(prop => decodeURIComponent(prop)); - if (path[0] === '') path.splice(0, 1); - if (path[path.length - 1] === '') path.splice(-1, 1); + const path = args.PATH.toString() + .split("/") + .map((prop) => decodeURIComponent(prop)); + if (path[0] === "") path.splice(0, 1); + if (path[path.length - 1] === "") path.splice(-1, 1); let json; try { - json = JSON.parse(' ' + args.JSON_STRING); + json = JSON.parse(" " + args.JSON_STRING); } catch (e) { return e.message; - }; - path.forEach(prop => json = json[prop]); - if (json === null) return 'null'; - else if (json === undefined) return ''; - else if (typeof json === 'object') return JSON.stringify(json); + } + path.forEach((prop) => (json = json[prop])); + if (json === null) return "null"; + else if (json === undefined) return ""; + else if (typeof json === "object") return JSON.stringify(json); else return json.toString(); } catch (err) { - return ''; - }; + return ""; + } } // Reporter - Returns an entry from a JSON array (0-based). @@ -1911,7 +1967,7 @@ } else { let data = json_array[args.NUM]; - if (typeof (data) == "object") { + if (typeof data == "object") { data = JSON.stringify(data); // Make the JSON safe for Scratch } @@ -1923,8 +1979,8 @@ // url - String fetchURL(args) { return Scratch.fetch(args.url, { method: "GET" }) - .then(response => response.text()) - .catch(error => { + .then((response) => response.text()) + .catch((error) => { //console.warn(`[CloudLink] Fetch error: ${error}`); }); } @@ -1935,20 +1991,20 @@ if (args.method == "GET" || args.method == "HEAD") { return Scratch.fetch(args.url, { method: args.method, - headers: JSON.parse(args.headers) + headers: JSON.parse(args.headers), }) - .then(response => response.text()) - .catch(error => { + .then((response) => response.text()) + .catch((error) => { //console.warn(`[CloudLink] Request error: ${error}`); }); } else { return Scratch.fetch(args.url, { method: args.method, headers: JSON.parse(args.headers), - body: args.data + body: args.data, }) - .then(response => response.text()) - .catch(error => { + .then((response) => response.text()) + .catch((error) => { //console.warn(`[CloudLink] Request error: ${error}`); }); } @@ -1962,7 +2018,13 @@ if (clVars.linkState.status != 2) return false; // Listener must exist - if (!Object.prototype.hasOwnProperty.call(clVars.listeners.varStates, args.ID)) return false; + if ( + !Object.prototype.hasOwnProperty.call( + clVars.listeners.varStates, + args.ID + ) + ) + return false; // Run event if (clVars.listeners.varStates[args.ID].eventHatTick) { @@ -1981,42 +2043,42 @@ // Run event switch (args.TYPE) { - case 'Global data': + case "Global data": if (clVars.gmsg.eventHatTick) { clVars.gmsg.eventHatTick = false; return true; } break; - case 'Private data': + case "Private data": if (clVars.pmsg.eventHatTick) { clVars.pmsg.eventHatTick = false; return true; } break; - case 'Direct data': + case "Direct data": if (clVars.direct.eventHatTick) { clVars.direct.eventHatTick = false; return true; } break; - case 'Status code': + case "Status code": if (clVars.statuscode.eventHatTick) { clVars.statuscode.eventHatTick = false; return true; } break; - case 'Global variables': + case "Global variables": if (clVars.gvar.eventHatTick) { clVars.gvar.eventHatTick = false; return true; } break; - case 'Private variables': + case "Private variables": if (clVars.pvar.eventHatTick) { clVars.pvar.eventHatTick = false; return true; @@ -2035,10 +2097,15 @@ // Run event switch (args.TYPE) { - case 'Global variables': - + case "Global variables": // Variable must exist - if (!Object.prototype.hasOwnProperty.call(clVars.gvar.varStates, String(args.VAR))) break; + if ( + !Object.prototype.hasOwnProperty.call( + clVars.gvar.varStates, + String(args.VAR) + ) + ) + break; if (clVars.gvar.varStates[String(args.VAR)].eventHatTick) { clVars.gvar.varStates[String(args.VAR)].eventHatTick = false; return true; @@ -2046,10 +2113,15 @@ break; - case 'Private variables': - + case "Private variables": // Variable must exist - if (!Object.prototype.hasOwnProperty.call(clVars.pvar.varStates, String(args.VAR))) break; + if ( + !Object.prototype.hasOwnProperty.call( + clVars.pvar.varStates, + String(args.VAR) + ) + ) + break; if (clVars.pvar.varStates[String(args.VAR)].eventHatTick) { clVars.pvar.varStates[String(args.VAR)].eventHatTick = false; return true; @@ -2063,61 +2135,64 @@ // Reporter - Returns a JSON-ified value. // toBeJSONified - String makeJSON(args) { - if (typeof (args.toBeJSONified) == "string") { + if (typeof args.toBeJSONified == "string") { try { JSON.parse(args.toBeJSONified); return String(args.toBeJSONified); } catch (err) { return "Not JSON!"; } - } else if (typeof (args.toBeJSONified) == "object") { + } else if (typeof args.toBeJSONified == "object") { return JSON.stringify(args.toBeJSONified); } else { return "Not JSON!"; - }; + } } // Boolean - Returns true if connected. getComState() { - return ((clVars.linkState.status == 2) && (clVars.socket != null)); + return clVars.linkState.status == 2 && clVars.socket != null; } // Boolean - Returns true if linked to rooms (other than "default") getRoomState() { - return ((clVars.socket != null) && (clVars.rooms.isLinked)); + return clVars.socket != null && clVars.rooms.isLinked; } // Boolean - Returns true if the connection was dropped. getComLostConnectionState() { - return ((clVars.linkState.status == 4) && (clVars.linkState.disconnectType == 2)); + return ( + clVars.linkState.status == 4 && clVars.linkState.disconnectType == 2 + ); } // Boolean - Returns true if the client failed to establish a connection. getComFailedConnectionState() { - return ((clVars.linkState.status == 4) && (clVars.linkState.disconnectType == 1)); + return ( + clVars.linkState.status == 4 && clVars.linkState.disconnectType == 1 + ); } // Boolean - Returns true if the username was set successfully. getUsernameState() { - return ((clVars.socket != null) && (clVars.username.accepted)); + return clVars.socket != null && clVars.username.accepted; } // Boolean - Returns true if there is new gmsg/pmsg/direct/statuscode data. // TYPE - String (menu datamenu) returnIsNewData(args) { - // Must be connected if (clVars.socket == null) return false; // Run event switch (args.TYPE) { - case 'Global data': + case "Global data": return clVars.gmsg.hasNew; - case 'Private data': + case "Private data": return clVars.pmsg.hasNew; - case 'Direct data': + case "Direct data": return clVars.direct.hasNew; - case 'Status code': + case "Status code": return clVars.statuscode.hasNew; } } @@ -2126,14 +2201,24 @@ // TYPE - String (menu varmenu), VAR - String (variable name) returnIsNewVarData(args) { switch (args.TYPE) { - case 'Global variables': - if (!Object.prototype.hasOwnProperty.call(clVars.gvar.varStates, String(args.VAR))) { + case "Global variables": + if ( + !Object.prototype.hasOwnProperty.call( + clVars.gvar.varStates, + String(args.VAR) + ) + ) { //console.warn(`[CloudLink] Global variable ${args.VAR} does not exist!`); return false; } return clVars.gvar.varStates[String(args.ID)].hasNew; - case 'Private variables': - if (!Object.prototype.hasOwnProperty.call(clVars.pvar.varStates, String(args.VAR))) { + case "Private variables": + if ( + !Object.prototype.hasOwnProperty.call( + clVars.pvar.varStates, + String(args.VAR) + ) + ) { //console.warn(`[CloudLink] Private variable ${args.VAR} does not exist!`); return false; } @@ -2144,7 +2229,12 @@ // Boolean - Returns true if a listener has a new value. // ID - String (listener ID) returnIsNewListener(args) { - if (!Object.prototype.hasOwnProperty.call(clVars.listeners.varStates, String(args.ID))) { + if ( + !Object.prototype.hasOwnProperty.call( + clVars.listeners.varStates, + String(args.ID) + ) + ) { //console.warn(`[CloudLink] Listener ID ${args.ID} does not exist!`); return false; } @@ -2154,24 +2244,21 @@ // Boolean - Returns true if a username/ID/UUID/object exists in the userlist. // ID - String (username or user object) checkForID(args) { - // Legacy ulist handling if (clVars.ulist.includes(args.ID)) return true; // New ulist handling if (clVars.linkState.identifiedProtocol > 2) { if (this.isValidJSON(args.ID)) { - return clVars.ulist.some(o => ( - (o.username === JSON.parse(args.ID).username) - && - (o.id == JSON.parse(args.ID).id) - )); + return clVars.ulist.some( + (o) => + o.username === JSON.parse(args.ID).username && + o.id == JSON.parse(args.ID).id + ); } else { - return clVars.ulist.some(o => ( - (o.username === String(args.ID)) - || - (o.id == args.ID) - )); + return clVars.ulist.some( + (o) => o.username === String(args.ID) || o.id == args.ID + ); } } else return false; } @@ -2184,7 +2271,7 @@ return true; } catch { return false; - }; + } } // Command - Establishes a connection to a server. @@ -2193,7 +2280,7 @@ if (clVars.socket != null) { //console.warn("[CloudLink] Already connected to a server."); return; - }; + } return newClient(args.IP); } @@ -2203,11 +2290,16 @@ if (clVars.socket != null) { //console.warn("[CloudLink] Already connected to a server."); return; - }; - if (!Object.prototype.hasOwnProperty.call(clVars.serverList, String(args.ID))) { + } + if ( + !Object.prototype.hasOwnProperty.call( + clVars.serverList, + String(args.ID) + ) + ) { //console.warn("[CloudLink] Not a valid server ID!"); return; - }; + } return newClient(clVars.serverList[String(args.ID)]["url"]); } @@ -2216,7 +2308,7 @@ if (clVars.socket == null) { //console.warn("[CloudLink] Already disconnected."); return; - }; + } //console.log("[CloudLink] Disconnecting..."); clVars.linkState.isAttemptingGracefulDisconnect = true; clVars.socket.close(1000, "Client going away"); @@ -2232,13 +2324,13 @@ if (clVars.username.attempted) { //console.warn("[CloudLink] Already attempting to set username!"); return; - }; + } // Prevent running if the username is already set. if (clVars.username.accepted) { //console.warn("[CloudLink] Already set username!"); return; - }; + } // Update state clVars.username.attempted = true; @@ -2251,7 +2343,6 @@ // Command - Prepares the next transmitted message to have a listener ID attached to it. // ID - String (listener ID) createListener(args) { - // Must be connected to set a username. if (clVars.socket == null) return; @@ -2265,7 +2356,7 @@ if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before creating a listener!"); return; - }; + } // Must be used once per packet if (clVars.listeners.enablerState) { @@ -2281,7 +2372,6 @@ // Command - Subscribes to various rooms on a server. // ROOMS - String (JSON Array or single string) linkToRooms(args) { - // Must be connected to set a username. if (clVars.socket == null) return; @@ -2295,19 +2385,19 @@ if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before linking to rooms!"); return; - }; + } // Prevent running if already linked. if (clVars.rooms.isLinked) { //console.warn("[CloudLink] Already linked to rooms!"); return; - }; + } // Prevent running if a room link is in progress. if (clVars.rooms.isAttemptingLink) { //console.warn("[CloudLink] Currently linking to rooms! Please wait!"); return; - }; + } clVars.rooms.isAttemptingLink = true; sendMessage({ cmd: "link", val: args.ROOMS, listener: "link" }); @@ -2316,7 +2406,6 @@ // Command - Specifies specific subscribed rooms to transmit messages to. // ROOMS - String (JSON Array or single string) selectRoomsInNextPacket(args) { - // Must be connected to user rooms. if (clVars.socket == null) return; @@ -2330,7 +2419,7 @@ if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before selecting rooms!"); return; - }; + } // Require once per packet if (clVars.rooms.enablerState) { @@ -2342,7 +2431,7 @@ if (!clVars.rooms.isLinked) { //console.warn("[CloudLink] Cannot use room selector while not linked to rooms!"); return; - }; + } clVars.rooms.enablerState = true; clVars.rooms.enablerValue = args.ROOMS; @@ -2350,7 +2439,6 @@ // Command - Unsubscribes from all rooms and re-subscribes to the the "default" room on the server. unlinkFromRooms() { - // Must be connected to user rooms. if (clVars.socket == null) return; @@ -2364,19 +2452,19 @@ if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before unjoining rooms!"); return; - }; + } // Prevent running if already unlinked. if (!clVars.rooms.isLinked) { //console.warn("[CloudLink] Already unlinked from rooms!"); return; - }; + } // Prevent running if a room unlink is in progress. if (clVars.rooms.isAttemptingUnlink) { //console.warn("[CloudLink] Currently unlinking from rooms! Please wait!"); return; - }; + } clVars.rooms.isAttemptingUnlink = true; sendMessage({ cmd: "unlink", val: "", listener: "unlink" }); @@ -2385,7 +2473,6 @@ // Command - Sends a gmsg value. // DATA - String sendGData(args) { - // Must be connected. if (clVars.socket == null) return; @@ -2395,7 +2482,6 @@ // Command - Sends a pmsg value. // DATA - String, ID - String (recipient ID) sendPData(args) { - // Must be connected. if (clVars.socket == null) return; @@ -2403,7 +2489,7 @@ if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before sending private messages!"); return; - }; + } sendMessage({ cmd: "pmsg", val: args.DATA, id: args.ID }); } @@ -2411,7 +2497,6 @@ // Command - Sends a gvar value. // DATA - String, VAR - String (variable name) sendGDataAsVar(args) { - // Must be connected. if (clVars.socket == null) return; @@ -2421,7 +2506,6 @@ // Command - Sends a pvar value. // DATA - String, VAR - String (variable name), ID - String (recipient ID) sendPDataAsVar(args) { - // Must be connected. if (clVars.socket == null) return; @@ -2429,7 +2513,7 @@ if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before sending private variables!"); return; - }; + } sendMessage({ cmd: "pvar", val: args.DATA, name: args.VAR, id: args.ID }); } @@ -2437,7 +2521,6 @@ // Command - Sends a raw-format command without specifying an ID. // CMD - String (command), DATA - String runCMDnoID(args) { - // Must be connected. if (clVars.socket == null) return; @@ -2447,7 +2530,6 @@ // Command - Sends a raw-format command with an ID. // CMD - String (command), DATA - String, ID - String (recipient ID) runCMD(args) { - // Must be connected. if (clVars.socket == null) return; @@ -2455,7 +2537,7 @@ if (!clVars.username.accepted) { //console.warn("[CloudLink] Username must be set before using this command!"); return; - }; + } sendMessage({ cmd: args.CMD, val: args.DATA, id: args.ID }); } @@ -2464,16 +2546,16 @@ // TYPE - String (menu datamenu) resetNewData(args) { switch (args.TYPE) { - case 'Global data': + case "Global data": clVars.gmsg.hasNew = false; break; - case 'Private data': + case "Private data": clVars.pmsg.hasNew = false; break; - case 'Direct data': + case "Direct data": clVars.direct.hasNew = false; break; - case 'Status code': + case "Status code": clVars.statuscode.hasNew = false; break; } @@ -2483,15 +2565,25 @@ // TYPE - String (menu varmenu), VAR - String (variable name) resetNewVarData(args) { switch (args.TYPE) { - case 'Global variables': - if (!Object.prototype.hasOwnProperty.call(clVars.gvar.varStates, String(args.VAR))) { + case "Global variables": + if ( + !Object.prototype.hasOwnProperty.call( + clVars.gvar.varStates, + String(args.VAR) + ) + ) { //console.warn(`[CloudLink] Global variable ${args.VAR} does not exist!`); return; } clVars.gvar.varStates[String(args.ID)].hasNew = false; break; - case 'Private variables': - if (!Object.prototype.hasOwnProperty.call(clVars.pvar.varStates, String(args.VAR))) { + case "Private variables": + if ( + !Object.prototype.hasOwnProperty.call( + clVars.pvar.varStates, + String(args.VAR) + ) + ) { //console.warn(`[CloudLink] Private variable ${args.VAR} does not exist!`); return false; } @@ -2502,7 +2594,12 @@ // Command - Resets the "returnIsNewListener" boolean state. // ID - Listener ID resetNewListener(args) { - if (!Object.prototype.hasOwnProperty.call(clVars.listeners.varStates, String(args.ID))) { + if ( + !Object.prototype.hasOwnProperty.call( + clVars.listeners.varStates, + String(args.ID) + ) + ) { //console.warn(`[CloudLink] Listener ID ${args.ID} does not exist!`); return; } @@ -2513,25 +2610,25 @@ // TYPE - String (menu allmenu) clearAllPackets(args) { switch (args.TYPE) { - case 'Global data': + case "Global data": clVars.gmsg.queue = []; break; - case 'Private data': + case "Private data": clVars.pmsg.queue = []; break; - case 'Direct data': + case "Direct data": clVars.direct.queue = []; break; - case 'Status code': + case "Status code": clVars.statuscode.queue = []; break; - case 'Global variables': + case "Global variables": clVars.gvar.queue = []; break; - case 'Private variables': + case "Private variables": clVars.pvar.queue = []; break; - case 'All data': + case "All data": clVars.gmsg.queue = []; clVars.pmsg.queue = []; clVars.direct.queue = []; @@ -2543,11 +2640,15 @@ } recentlyjoined() { - return makeValueScratchSafe(JSON.stringify(clVars?.recentlyJoinedUser ?? {})); + return makeValueScratchSafe( + JSON.stringify(clVars?.recentlyJoinedUser ?? {}) + ); } recentlyleft() { - return makeValueScratchSafe(JSON.stringify(clVars?.recentlyLeftUser ?? {})); + return makeValueScratchSafe( + JSON.stringify(clVars?.recentlyLeftUser ?? {}) + ); } } Scratch.extensions.register(new CloudLink()); From d985b7816b50296adfea47ed6130baba55e8ac24 Mon Sep 17 00:00:00 2001 From: "Mike J. Renaker / \"MikeDEV" Date: Fri, 10 Apr 2026 12:07:04 -0400 Subject: [PATCH 05/11] [Gemini] Address regressions for linter Addresses regressions outlined by https://github.com/TurboWarp/extensions/actions/runs/24251816434/job/70812946244?pr=2458 --- extensions/cloudlink.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/extensions/cloudlink.js b/extensions/cloudlink.js index c7eecfe0e2..edd208b24b 100644 --- a/extensions/cloudlink.js +++ b/extensions/cloudlink.js @@ -409,7 +409,7 @@ } // Compare the version string of the server to known compatible variants to configure clVars.linkState.identifiedProtocol. - async function setServerVersion(version) { + function setServerVersion(version) { //console.log(`[CloudLink] Server version: ${String(version)}`); clVars.server_version = version; @@ -457,7 +457,7 @@ if ( clVars.linkState.identifiedProtocol < 4 && !confirm( - `You have connected to an old CloudLink server, running version ${clVars.server_version}.\n\nFor your security and privacy, we recommend you disconnect from this server and connect to an up-to-date server.\n\nClick/tap \"OK\" to stay connected.` + `You have connected to an old CloudLink server, running version ${clVars.server_version}.\n\nFor your security and privacy, we recommend you disconnect from this server and connect to an up-to-date server.\n\nClick/tap "OK" to stay connected.` ) ) { // Close the connection if they choose "Cancel" @@ -474,7 +474,7 @@ } // CL-specific netcode needed to make the extension work - async function handleMessage(data) { + function handleMessage(data) { // Parse the message JSON let packet = {}; try { @@ -532,7 +532,7 @@ // Server 0.1.5 (at least) case "vers": window.clearTimeout(clVars.handshakeTimeout); - await setServerVersion(packet.val.val); + setServerVersion(packet.val.val); return; // Server 0.1.7 (at least) @@ -678,7 +678,7 @@ clVars.recentlyJoinedUser = packet.val; Scratch.vm.runtime.startHats("cloudlink_whenuserconnects"); break; - case "remove": + case "remove": { let index = -1; for (let i = 0; i < clVars.ulist.length; i++) { let user = clVars.ulist[i]; @@ -691,6 +691,7 @@ clVars.recentlyLeftUser = packet.val; Scratch.vm.runtime.startHats("cloudlink_whenuserdisconnects"); break; + } default: //console.warn(`[CloudLink] Unrecognised userlist mode: \"${packet.mode}\".`); break; @@ -702,7 +703,7 @@ case "server_version": window.clearTimeout(clVars.handshakeTimeout); - await setServerVersion(packet.val); + setServerVersion(packet.val); break; case "client_ip": @@ -754,6 +755,7 @@ // Establish a connection to the server //console.log("[CloudLink] Connecting to server:", url); try { + // eslint-disable-next-line extension/check-can-fetch clVars.socket = new WebSocket(url); } catch (e) { //console.warn("[CloudLink] An exception has occurred:", e); @@ -825,6 +827,7 @@ getInfo() { return { id: "cloudlink", + // eslint-disable-next-line extension/should-translate name: "CloudLink V4", blockIconURI: cl_block, menuIconURI: cl_icon, From 085f8f7f54c4818b787de9ec6b99de3cb89398ed Mon Sep 17 00:00:00 2001 From: "DangoCat[bot]" Date: Fri, 10 Apr 2026 16:08:27 +0000 Subject: [PATCH 06/11] [Automated] Format code --- extensions/cloudlink.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/cloudlink.js b/extensions/cloudlink.js index edd208b24b..9e4dd31bc8 100644 --- a/extensions/cloudlink.js +++ b/extensions/cloudlink.js @@ -678,7 +678,7 @@ clVars.recentlyJoinedUser = packet.val; Scratch.vm.runtime.startHats("cloudlink_whenuserconnects"); break; - case "remove": { + case "remove": { let index = -1; for (let i = 0; i < clVars.ulist.length; i++) { let user = clVars.ulist[i]; @@ -691,7 +691,7 @@ clVars.recentlyLeftUser = packet.val; Scratch.vm.runtime.startHats("cloudlink_whenuserdisconnects"); break; - } + } default: //console.warn(`[CloudLink] Unrecognised userlist mode: \"${packet.mode}\".`); break; From ba893abae981d56b1714adcdf7baec7a6fd08d26 Mon Sep 17 00:00:00 2001 From: "Mike J. Renaker / \"MikeDEV" Date: Fri, 10 Apr 2026 12:10:20 -0400 Subject: [PATCH 07/11] Resolve comment https://github.com/TurboWarp/extensions/pull/2458/changes/d985b7816b50296adfea47ed6130baba55e8ac24#r3061733361 --- extensions/cloudlink.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/cloudlink.js b/extensions/cloudlink.js index 9e4dd31bc8..c88ac5e252 100644 --- a/extensions/cloudlink.js +++ b/extensions/cloudlink.js @@ -643,7 +643,7 @@ clVars.ulist = String(packet.val).split(";"); // Get rid of blank entry at the end of the list - clVars.ulist.pop(clVars.ulist.length); + clVars.ulist.pop(); // Check if username has been set (since older servers don't implement statuscodes or listeners) if ( From 9e65bc88130f1373b3568d42e597f269689c77cb Mon Sep 17 00:00:00 2001 From: "Mike J. Renaker / \"MikeDEV" Date: Fri, 10 Apr 2026 12:17:19 -0400 Subject: [PATCH 08/11] Retain existing name metadata in the repo --- extensions/cloudlink.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/cloudlink.js b/extensions/cloudlink.js index c88ac5e252..4415e05802 100644 --- a/extensions/cloudlink.js +++ b/extensions/cloudlink.js @@ -1,4 +1,4 @@ -// Name: Cloudlink V4 Improved +// Name: Cloudlink V4 // ID: cloudlink // Description: A powerful WebSocket extension for Scratch. // By: MikeDEV From b85ef8dd499eaf375c8767771efb0f9303cf8774 Mon Sep 17 00:00:00 2001 From: "Mike J. Renaker / \"MikeDEV" Date: Fri, 10 Apr 2026 12:18:10 -0400 Subject: [PATCH 09/11] how did I miss that? --- extensions/cloudlink.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/cloudlink.js b/extensions/cloudlink.js index 4415e05802..5513c4e310 100644 --- a/extensions/cloudlink.js +++ b/extensions/cloudlink.js @@ -1,4 +1,4 @@ -// Name: Cloudlink V4 +// Name: CloudLink V4 // ID: cloudlink // Description: A powerful WebSocket extension for Scratch. // By: MikeDEV From b0d4615a7f5cacd6e1e916baad1c49de93100256 Mon Sep 17 00:00:00 2001 From: "Mike J. Renaker / \"MikeDEV" Date: Fri, 10 Apr 2026 12:20:41 -0400 Subject: [PATCH 10/11] Undo regression --- extensions/cloudlink.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extensions/cloudlink.js b/extensions/cloudlink.js index 5513c4e310..afcec9db29 100644 --- a/extensions/cloudlink.js +++ b/extensions/cloudlink.js @@ -2294,6 +2294,10 @@ //console.warn("[CloudLink] Already connected to a server."); return; } + // This is the only server that's listed and works. + if (Scratch.Cast.toNumber(args.ID) >= 1) { + args.ID = 7; + } if ( !Object.prototype.hasOwnProperty.call( clVars.serverList, From a0e6e6a251f49d23befe7ef3765b9910321e8d48 Mon Sep 17 00:00:00 2001 From: "Mike J. Renaker / \"MikeDEV" Date: Fri, 10 Apr 2026 12:24:52 -0400 Subject: [PATCH 11/11] Missed one --- extensions/cloudlink.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/cloudlink.js b/extensions/cloudlink.js index afcec9db29..7c9e729518 100644 --- a/extensions/cloudlink.js +++ b/extensions/cloudlink.js @@ -1315,7 +1315,7 @@ arguments: { ROOMS: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('["test"]'), + defaultValue: '["test"]', }, }, }, @@ -1327,7 +1327,7 @@ arguments: { ROOMS: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('["test"]'), + defaultValue: '["test"]', }, }, },