From e84baea73e683545c08d069a681798cdc29aaf0a Mon Sep 17 00:00:00 2001 From: Qucchia <45072410+qucchia@users.noreply.github.com> Date: Mon, 28 Jun 2021 13:12:32 +0200 Subject: [PATCH 1/7] Fix cloud bug --- src/Struct/CloudSession.js | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/Struct/CloudSession.js b/src/Struct/CloudSession.js index 6a29585..7275fe5 100644 --- a/src/Struct/CloudSession.js +++ b/src/Struct/CloudSession.js @@ -83,20 +83,27 @@ class CloudSession extends EventEmitter { _this.connect(); }); - connection.on("message", function (chunk) { - let json = JSON.parse(chunk); - - _this._client._debugLog("CloudData: Received message: " + json); - - if (json.method === "set") { - _this._variables[json.name] = new CloudVariable(_this._client, { - name: json.name, - value: json.value - }); - - _this.emit("set", _this._variables[json.name]); - } else { - _this._client._debugLog("CloudData: Method not supported: " + json.method); + connection.on("message", function (chunks) { + let chunksArr = chunks.split("\n"); + for (let i = 0; i < chunksArr.length; i++) { + let chunk = chunksArr[i]; + if (!chunk) { + continue; + } + let json = JSON.parse(chunk); + + _this._client._debugLog("CloudData: Received message: " + json); + + if (json.method === "set") { + _this._variables[json.name] = new CloudVariable(_this._client, { + name: json.name, + value: json.value + }); + + _this.emit("set", _this._variables[json.name]); + } else { + _this._client._debugLog("CloudData: Method not supported: " + json.method); + } } }); }); From dea95eb0c7b99fffa97c2d402eeb54509377750d Mon Sep 17 00:00:00 2001 From: Qucchia <45072410+qucchia@users.noreply.github.com> Date: Wed, 30 Jun 2021 19:52:08 +0200 Subject: [PATCH 2/7] Update README.md --- README.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9331e80..f0f62dc 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,20 @@ This package is a nodejs promise-based client to connect with the Scratch web and cloud servers. This package is based off https://github.com/trumank/scratch-api which the developer has discontinued. +# Installation + +In the folder you want to use the client in, clone the repository: +```bash +git clone https://github.com/qucchia/node-scratch-client/ +``` + +Once that's done, you can import the module using `require("./node-scratch-client/index.js")`. + +# Examples + Full project manipulation: ```js -// Import the module with: -const scratch = require("node-scratch-client"); +const scratch = require("./node-scratch-client/index.js"); // Initiate client const Client = new scratch.Client({ @@ -26,7 +36,7 @@ Client.login().then(() => { Cloud server connection: ```js -const scratch = require("node-scratch-client"); +const scratch = require("./node-scratch-client/index.js"); const Client = new scratch.Client({ username: "ceebeee", From 9a3f8673e5a6b78780f08a8c246944ae81814987 Mon Sep 17 00:00:00 2001 From: Qucchia <45072410+qucchia@users.noreply.github.com> Date: Wed, 30 Jun 2021 19:53:23 +0200 Subject: [PATCH 3/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f0f62dc..f13ebd6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # node-scratch-client -This package is a nodejs promise-based client to connect with the Scratch web and cloud servers. This package is based off https://github.com/trumank/scratch-api which the developer has discontinued. +This fork of [node-scratch-client](https://github.com/edqx/node-scratch-client/) intends to fix a few bugs that can cause errors during its use. # Installation From 0d06be25e76bf8e9870fccf7d8bb5adffb7e8e91 Mon Sep 17 00:00:00 2001 From: Qucchia <45072410+qucchia@users.noreply.github.com> Date: Thu, 1 Jul 2021 09:21:31 +0200 Subject: [PATCH 4/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f13ebd6..c521ea0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # node-scratch-client -This fork of [node-scratch-client](https://github.com/edqx/node-scratch-client/) intends to fix a few bugs that can cause errors during its use. +The Scratch development team is always changing the Scratch API, and we need to catch up with it. This fork of [node-scratch-client](https://github.com/edqx/node-scratch-client/) intends to keep up-to-date with the latest Scratch API as quickly as possible. # Installation From 0053296de0b5a36d582122423c13650a1a55f2a8 Mon Sep 17 00:00:00 2001 From: Qucchia <45072410+qucchia@users.noreply.github.com> Date: Thu, 1 Jul 2021 09:28:50 +0200 Subject: [PATCH 5/7] Fix getComments() by parsing XML --- src/Struct/Project.js | 115 +++++++++++++++++++++++++++++++++++------- 1 file changed, 97 insertions(+), 18 deletions(-) diff --git a/src/Struct/Project.js b/src/Struct/Project.js index f8f499e..b76f286 100644 --- a/src/Struct/Project.js +++ b/src/Struct/Project.js @@ -1,5 +1,6 @@ const request = require("../request.js"); const fs = require("fs"); +var parse = require('xml-parser-xo'); const IncompleteUser = require("./IncompleteUser.js"); const Studio = require("./Studio.js"); @@ -45,6 +46,39 @@ class Project { this.isRemix = !!raw.remix.parents; } + removeWhitespace(json) { + function trimElements(element) { + if (element.children) { + element.children = element.children.map((child) => { + if (child.type === "Element") { + trimElements(child); + } else if (child.type === "Text") { + child.content = child.content.trim(); + } + return child; + }); + } + } + + function deleteEmptyElements(element) { + if (element.children) { + element.children = element.children.filter((child) => { + if (child.type === "Element") { + deleteEmptyElements(child); + return true; + } else if (child.type === "Text") { + return child.content.length > 0; + } else if (child.type === "Comment") { + return false; + } + }); + } + } + + trimElements(json); + deleteEmptyElements(json); + } + love() { let _this = this; @@ -261,44 +295,89 @@ class Project { let _this = this; let all = []; + function parseComments(xml) { + let json = parse('' + xml + ''); + + _this.removeWhitespace(json); + + json.root.children.forEach(child => { + if (child.type === "Element" && child.name === "li") { + let commentData = { + parent_id: null + }; + child.children.forEach(child => { + if (child.type === "Element" && child.name === "div") { + commentData.id = child.attributes["data-comment-id"]; + child.children.forEach(child => { + if (child.type === "Element" && child.name === "div" && child.attributes.class === "info") { + child.children.forEach(child => { + if (child.type === "Element" && child.name === "div") { + if (child.attributes.class === "name") { + } else if (child.attributes.class === "content") { + commentData.content = child.children[0].content; + } else { + child.children.forEach(child => { + if (child.type === "Element" && child.name === "span") { + commentData.datetime_created = child.attributes.title; + commentData.datetime_modified = child.attributes.title; + }else if (child.type === "Element" && child.name === "a") { + commentData.commentee_id = child.attributes["data-commentee-id"]; + } + }); + } + } + }) + } + }) + } + if (child.type === "Element" && child.name === "ul") { + let replyCount = 0; + child.children.forEach(child => { + if (child.type === "Element" && child.name === "li") { + replyCount += 1; + } + }); + commentData.reply_count = replyCount; + } + }) + all.push(new ProjectComment(_this._client, _this, commentData)); + } + }); + } + if (opt.fetchAll) { return new Promise((resolve, reject) => { - (function loop(rCount) { - let query = "limit=40&offset=" + rCount; + (function loop(pageCount) { + let query = "page=" + pageCount; request({ - hostname: "api.scratch.mit.edu", - path: "/projects/" + _this.id + "/comments?" + query, + hostname: "scratch.mit.edu", + path: "/site-api/comments/project/" + _this.id + "/?" + query, method: "GET", csrftoken: _this._client.session.csrftoken }).then(response => { - let json = JSON.parse(response.body); + parseComments(response.body); - JSON.parse(response.body).forEach(comment => { - all.push(new Comment(_this._client, _this, comment)); - }); - - if (json.length === 40) { - loop(rCount + 40); + if (json.root.children.length === 40) { + loop(pageCount + 1); } else { resolve(all); } }).catch(reject); - })(0); + })(1); }); } else { return new Promise((resolve, reject) => { - let query = "limit=" + (opt.limit || 20) + "&offset=" + (opt.offset || 0); + let query = "page=" + opt.page; request({ - hostname: "api.scratch.mit.edu", - path: "/projects/" + _this.id + "/comments/?" + query, + hostname: "scratch.mit.edu", + path: "/site-api/comments/project/" + _this.id + "/?" + query, method: "GET", csrftoken: _this._client.session.csrftoken }).then(response => { - resolve(JSON.parse(response.body).map(comment => { - return new ProjectComment(comment); - })); + parseComments(response.body); + resolve(all); }).catch(reject); }); } From 901fa482568fe47434856c2724f4655beeb50140 Mon Sep 17 00:00:00 2001 From: Qucchia <45072410+qucchia@users.noreply.github.com> Date: Thu, 1 Jul 2021 09:29:26 +0200 Subject: [PATCH 6/7] Update ProjectComment constructor --- src/Struct/ProjectComment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Struct/ProjectComment.js b/src/Struct/ProjectComment.js index 06316f6..71bf810 100644 --- a/src/Struct/ProjectComment.js +++ b/src/Struct/ProjectComment.js @@ -18,7 +18,7 @@ class ProjectComment { this.visible = raw.visibility === "visible"; - this.author = new CommentAuthor(Client, raw.author); + this.author = project.author; } delete() { From 7081dabd300ea4a71b7592d8da4ddd5ad7f1f530 Mon Sep 17 00:00:00 2001 From: Qucchia <45072410+qucchia@users.noreply.github.com> Date: Thu, 8 Jul 2021 07:54:07 +0000 Subject: [PATCH 7/7] Allow multiple Cloud sessions --- .replit | 2 ++ src/Struct/CloudSession.js | 4 ++-- src/Struct/CloudVariable.js | 5 +++-- src/Struct/Session.js | 8 +++++--- 4 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 .replit diff --git a/.replit b/.replit new file mode 100644 index 0000000..d409ef8 --- /dev/null +++ b/.replit @@ -0,0 +1,2 @@ +language = "nodejs" +run = "npm start" \ No newline at end of file diff --git a/src/Struct/CloudSession.js b/src/Struct/CloudSession.js index 7275fe5..de0c4c3 100644 --- a/src/Struct/CloudSession.js +++ b/src/Struct/CloudSession.js @@ -32,7 +32,7 @@ class CloudSession extends EventEmitter { } setVariable(name, value) { - this._variables[name].set(value); + this._send("set", { name, value }); } _send(method, options) { @@ -98,7 +98,7 @@ class CloudSession extends EventEmitter { _this._variables[json.name] = new CloudVariable(_this._client, { name: json.name, value: json.value - }); + }, this); _this.emit("set", _this._variables[json.name]); } else { diff --git a/src/Struct/CloudVariable.js b/src/Struct/CloudVariable.js index 05fd749..5da0021 100644 --- a/src/Struct/CloudVariable.js +++ b/src/Struct/CloudVariable.js @@ -1,6 +1,7 @@ class CloudVariable { - constructor (Client, raw) { + constructor (Client, raw, CloudSession) { this._client = Client; + this._cloudsession = CloudSession; this.name = raw.name; this.value = raw.value; @@ -9,7 +10,7 @@ class CloudVariable { set(value) { this.value = value; - this._client.session.cloudsession._send("set", { + this._cloudsession._send("set", { name: this.name, value: this.value }); diff --git a/src/Struct/Session.js b/src/Struct/Session.js index e3bee68..728deb9 100644 --- a/src/Struct/Session.js +++ b/src/Struct/Session.js @@ -17,15 +17,17 @@ class Session { this.authorized = basic.authorized || null; - this.cloudsession = null; + this.cloudsessions = []; this.permission = null; } createCloudSession(project) { - this.cloudsession = new CloudSession(this._client, this.username, project); + let cloudsession = new CloudSession(this._client, this.username, project); - return this.cloudsession; + this.cloudsessions.push(cloudsession); + + return cloudsession; } getBackpack() {