Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .replit
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
language = "nodejs"
run = "npm start"
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
# 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.
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

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({
Expand All @@ -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",
Expand Down
37 changes: 22 additions & 15 deletions src/Struct/CloudSession.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class CloudSession extends EventEmitter {
}

setVariable(name, value) {
this._variables[name].set(value);
this._send("set", { name, value });
}

_send(method, options) {
Expand Down Expand Up @@ -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);

_this.emit("set", _this._variables[json.name]);
} else {
_this._client._debugLog("CloudData: Method not supported: " + json.method);
}
}
});
});
Expand Down
5 changes: 3 additions & 2 deletions src/Struct/CloudVariable.js
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
});
Expand Down
115 changes: 97 additions & 18 deletions src/Struct/Project.js
Original file line number Diff line number Diff line change
@@ -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");
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -261,44 +295,89 @@ class Project {
let _this = this;
let all = [];

function parseComments(xml) {
let json = parse('<?xml version="1.0" encoding="utf-8"?><all>' + xml + '</all></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);
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/Struct/ProjectComment.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class ProjectComment {

this.visible = raw.visibility === "visible";

this.author = new CommentAuthor(Client, raw.author);
this.author = project.author;
}

delete() {
Expand Down
8 changes: 5 additions & 3 deletions src/Struct/Session.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down