Skip to content

Commit

Permalink
Strict typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
webdev03 committed Oct 5, 2023
1 parent 6dc443d commit be26726
Show file tree
Hide file tree
Showing 11 changed files with 105 additions and 57 deletions.
4 changes: 2 additions & 2 deletions src/Consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ interface SessionJSON {
};
}

interface Session {
type Session = {
csrfToken: string;
token: string;
cookieSet: string;
sessionJSON: SessionJSON;
}
} | undefined;

const UserAgent = "Mozilla/5.0";

Expand Down
57 changes: 35 additions & 22 deletions src/ScratchSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,20 @@ type Message = PartialMessage & ({
* Manages a Scratch session.
*/
class ScratchSession {
username: string;
csrfToken: string;
token: string;
cookieSet: string;
sessionJSON: SessionJSON;
auth?: {
username: string;
csrfToken: string;
token: string;
cookieSet: string;
sessionJSON: SessionJSON;
}

/**
* Sets up the ScratchSession to use authenticated functions.
* @param user The username of the user you want to log in to.
* @param pass The password of the user you want to log in to.
*/
async init(user: string, pass: string) {
this.username = user;
// a lot of this code is taken from
// https://github.com/CubeyTheCube/scratchclient/blob/main/scratchclient/ScratchSession.py
// thanks!
Expand All @@ -94,20 +95,22 @@ class ScratchSession {
throw new Error("Login failed.");
}

this.csrfToken = /scratchcsrftoken=(.*?);/gm.exec(
loginReq.headers.get("set-cookie")
)[1];
this.token = /"(.*)"/gm.exec(loginReq.headers.get("set-cookie"))[1];
this.cookieSet =
const setCookie = loginReq.headers.get("set-cookie");
if(!setCookie) throw Error("Something went wrong");
const csrfToken = /scratchcsrftoken=(.*?);/gm.exec(
setCookie
)![1];
const token = /"(.*)"/gm.exec(setCookie)![1];
const cookieSet =
"scratchcsrftoken=" +
this.csrfToken +
csrfToken +
";scratchlanguage=en;scratchsessionsid=" +
this.token +
token +
";";
const sessionFetch = await fetch("https://scratch.mit.edu/session", {
method: "GET",
headers: {
Cookie: this.cookieSet,
Cookie: cookieSet,
"User-Agent": UserAgent,
Referer: "https://scratch.mit.edu/",
Host: "scratch.mit.edu",
Expand All @@ -118,7 +121,14 @@ class ScratchSession {
"Accept-Encoding": "gzip, deflate, br"
}
});
this.sessionJSON = (await sessionFetch.json()) as SessionJSON;
const sessionJSON = (await sessionFetch.json()) as SessionJSON;
this.auth = {
username: user,
csrfToken,
token,
cookieSet,
sessionJSON
}
}

/**
Expand All @@ -131,14 +141,15 @@ class ScratchSession {
* await session.uploadToAssets(fs.readFileSync("photo.png"), "png"); // returns URL to image
*/
async uploadToAssets(buffer: Buffer, fileExtension: string) {
if(!this.auth) throw Error("You must be logged in to use this");
const md5hash = createHash("md5").update(buffer).digest("hex");
const upload = await fetch(
`https://assets.scratch.mit.edu/${md5hash}.${fileExtension}`,
{
method: "POST",
body: buffer,
headers: {
Cookie: this.cookieSet,
Cookie: this.auth.cookieSet,
"User-Agent": UserAgent,
Referer: "https://scratch.mit.edu/",
Host: "assets.scratch.mit.edu",
Expand Down Expand Up @@ -210,11 +221,12 @@ class ScratchSession {
* @param offset The offset of messages
*/
async getMessages(limit: number = 40, offset: number = 0) {
const request = await fetch(`https://api.scratch.mit.edu/users/${this.username}/messages?limit=${limit}&offset=${offset}`, {
if(!this.auth) throw Error("You must be logged in to use this");
const request = await fetch(`https://api.scratch.mit.edu/users/${this.auth.username}/messages?limit=${limit}&offset=${offset}`, {
headers: {
Origin: "https://scratch.mit.edu",
Referer: "https://scratch.mit.edu/",
"X-Token": this.sessionJSON.user.token
"X-Token": this.auth.sessionJSON.user.token
}
});
if(!request.ok) throw Error(`Request failed with status ${request.status}`);
Expand All @@ -225,14 +237,14 @@ class ScratchSession {
* Logs out of Scratch.
*/
async logout() {
if (!this.csrfToken || !this.token) return;
if(!this.auth) throw Error("You must be logged in to use this");
const logoutFetch = await fetch(
"https://scratch.mit.edu/accounts/logout/",
{
method: "POST",
body: `csrfmiddlewaretoken=${this.csrfToken}`,
body: `csrfmiddlewaretoken=${this.auth.csrfToken}`,
headers: {
Cookie: this.cookieSet,
Cookie: this.auth.cookieSet,
"User-Agent": UserAgent,
accept: "application/json",
Referer: "https://scratch.mit.edu/",
Expand All @@ -246,7 +258,8 @@ class ScratchSession {
);
if (!logoutFetch.ok) {
throw new Error(`Error in logging out. ${logoutFetch.status}`);
}
};
this.auth = undefined;
}
}

Expand Down
11 changes: 6 additions & 5 deletions src/classes/CloudConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import events from "node:events";
class CloudConnection extends events.EventEmitter {
id: number;
session: Session;
server: string;
connection: WebSocket;
connection!: WebSocket;
open: boolean = false;
queue: Array<{
user: string;
Expand All @@ -29,11 +28,11 @@ class CloudConnection extends events.EventEmitter {
super();
this.id = id;
this.session = session;

this.connect();
}

private connect() {
if(!this.session) throw Error("You need to be logged in")
this.open = false;
this.connection = new WebSocket("wss://clouddata.scratch.mit.edu", {
headers: {
Expand All @@ -52,6 +51,7 @@ class CloudConnection extends events.EventEmitter {
}
});
this.connection.on("open", () => {
if(!this.session) throw Error("You need to be logged in")
this.open = true;
this.send({
method: "handshake",
Expand Down Expand Up @@ -90,6 +90,7 @@ class CloudConnection extends events.EventEmitter {
* @param value The value to set the variable to.
*/
setVariable(variable: string, value: number | string) {
if(!this.session) throw Error("You need to be logged in")
const varname = variable.startsWith("☁ ")
? variable.substring(2)
: variable;
Expand All @@ -116,9 +117,9 @@ class CloudConnection extends events.EventEmitter {
/**
* Gets a cloud variable.
* @param variable The variable name to get.
* @returns {string} The value of the variable in string format.
* @returns The value of the variable in string format if it exists.
*/
getVariable(variable: string): string {
getVariable(variable: string) {
const varname = variable.startsWith("☁ ")
? variable.substring(2)
: variable;
Expand Down
37 changes: 21 additions & 16 deletions src/classes/Profile.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Session, UserAgent } from "../Consts";
import { parse } from "node-html-parser";
import ScratchSession from "../ScratchSession";

interface UserAPIResponse {
id: number;
Expand Down Expand Up @@ -46,9 +47,9 @@ interface ProfileComment {
class Profile {
user: string;
session: Session;
constructor(session: Session, username: string) {
constructor(session: ScratchSession, username: string) {
this.user = username;
this.session = session;
this.session = session.auth;
}

/**
Expand All @@ -58,7 +59,7 @@ class Profile {
*/
async getStatus() {
const dom = parse(await this.getUserHTML());
return dom.querySelector(".group").innerHTML.trim() as
return dom.querySelector(".group")!.innerHTML.trim() as
| "Scratcher"
| "New Scratcher"
| "Scratch Team";
Expand All @@ -68,6 +69,7 @@ class Profile {
* Follow the user
*/
async follow() {
if(!this.session) throw Error("You need to be logged in")
const request = await fetch(
`https://scratch.mit.edu/site-api/users/followers/${this.user}/add/?usernames=${this.session.sessionJSON.user.username}`,
{
Expand All @@ -92,6 +94,7 @@ class Profile {
* Unfollow the user
*/
async unfollow() {
if(!this.session) throw Error("You need to be logged in")
const request = await fetch(
`https://scratch.mit.edu/site-api/users/followers/${this.user}/remove/?usernames=${this.session.sessionJSON.user.username}`,
{
Expand All @@ -117,6 +120,7 @@ class Profile {
* @param id The comment ID, for example 12345, *not* comment-12345.
*/
async deleteComment(id: string | number) {
if(!this.session) throw Error("You need to be logged in")
const delFetch = await fetch(
`https://scratch.mit.edu/site-api/comments/user/${this.user}/del/`,
{
Expand Down Expand Up @@ -199,38 +203,38 @@ class Profile {
for (let elID in items) {
const element = items[elID];
if (typeof element == "function") break;
const commentID = element.querySelector(".comment").id;
const commentID = element.querySelector(".comment")!.id;
const commentPoster = element
.querySelector(".comment")
.querySelector(".comment")!
.getElementsByTagName("a")[0]
.getAttribute("data-comment-user");
.getAttribute("data-comment-user")!;
const commentContent = element
.querySelector(".comment")
.querySelector(".info")
.querySelector(".content")
.querySelector(".comment")!
.querySelector(".info")!
.querySelector(".content")!
.innerHTML.trim();

// get replies
let replies: ProfileCommentReply[] = [];
let replyList = element
.querySelector(".replies")
.querySelector(".replies")!
.querySelectorAll(".reply");
for (let replyID in replyList) {
const reply = replyList[replyID];
if (reply.tagName === "A") continue;
if (typeof reply === "function") continue;
if (typeof reply === "number") continue;
const commentID = reply.querySelector(".comment").id;
const commentID = reply.querySelector(".comment")!.id;
const commentPoster = reply
.querySelector(".comment")
.querySelector(".comment")!
.getElementsByTagName("a")[0]
.getAttribute("data-comment-user");
.getAttribute("data-comment-user")!;

// regex here developed at https://scratch.mit.edu/discuss/post/5983094/
const commentContent = reply
.querySelector(".comment")
.querySelector(".info")
.querySelector(".content")
.querySelector(".comment")!
.querySelector(".info")!
.querySelector(".content")!
.textContent.trim()
.replace(/\n+/gm, "")
.replace(/\s+/gm, " ");
Expand Down Expand Up @@ -260,6 +264,7 @@ class Profile {
* Toggle the comments section on the profile
*/
async toggleComments() {
if(!this.session) throw Error("You need to be logged in")
const request = await fetch(`https://scratch.mit.edu/site-api/comments/user/${this.user}/toggle-comments/`, {
method: "POST",
headers: {
Expand Down
Loading

0 comments on commit be26726

Please sign in to comment.