From 37392f6a318e2553e30ed6f3f7a74a83c44ee9c0 Mon Sep 17 00:00:00 2001 From: hiimgoodpack Date: Thu, 31 Dec 2020 12:07:08 -0500 Subject: [PATCH] Initialize project --- index.js | 330 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 20 ++++ 2 files changed, 350 insertions(+) create mode 100644 index.js create mode 100644 package.json diff --git a/index.js b/index.js new file mode 100644 index 0000000..f691385 --- /dev/null +++ b/index.js @@ -0,0 +1,330 @@ +/* + Unofficial brainlet interface functions + Copyright (C) 2020 hiimgoodpack + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +const validator = require("validator"); +const urllib = require("urllib"); +const WebSocket = require("ws"); + +const reportBug = ` + +You've also discovered something that shouldn't occur. +You can report it at https://git.hippoz.xyz/hiimgoodpack/brainlet-lib/issues`; + +const rateLimited = "You are being rate limited"; +module.exports = {}; + +// I didn't make the rules. This is what the server requires +module.exports.valid = { + username: (name) => { + if (validator.isEmpty(name)) + return "Empty username"; + if (!validator.isLength(name.trim(), { min: 3, max: 32 })) + return "Username length must be between 3 and 32 characters"; + if (!validator.isAlphanumeric(name)) + return "Username must use alphanumeric characters"; + }, + + email: (email) => { + if (validator.isEmpty(email)) + return "Empty email"; + if (!validator.isEmail(email)) + return "Invalid email"; + }, + + password: (password) => { + if (validator.isEmpty(password)) + return "Empty password"; + if (!validator.isLength(password, { min: 8, max: 128 })) + return "Password length must be between 8 and 128 characters"; + }, + + category: (category) => { + if (validator.isEmpty(category)) + return "Empty category name"; + if (!validator.isLength(category.trim(), { min: 3, max: 32 })) + return "Category name must be between 3 and 32 characters"; + } +}; + +/* + User format: + { + name; + email; + password; + token; + + permission; + role; + color; + _id; + } +*/ +// WARNING: You should trust the server being used! +module.exports.users = { + create: (server, user) => { + return new Promise((res, rej) => { + const validationError = + module.exports.valid.username(user.name) + || module.exports.valid.email(user.email) + || module.exports.valid.password(user.password); + if (validationError) { + rej(validationError); + return; + } + + const api = `http://${server}/api/v1/users/account/create`; + urllib.request(api, { + method: "POST", + data: { + username: user.name, + email: user.email, + password: user.password + } + }).then((result) => { + if (result.res.statusCode === 200) + res(); + else { + const rawData = result.res.data.toString(); + if (rawData === ratelimited) { + rej(`Error when creating account: ${rateLimited}`); + return; + } + const error = JSON.parse(rawData); + rej(`Error when creating account: ${error.errors[0].msg} (${error.message})`); + } + }).catch((error) => { + rej(`Error when sending request to ${api}: ${error.code}`); + }); + }); + }, + + login: (server, user) => { + return new Promise((res, rej) => { + const validationError = + module.exports.valid.username(user.name) + || module.exports.valid.password(user.password); + if (validationError) { + rej(validationError); + return; + } + + const api = `http://${server}/api/v1/users/token/create`; + urllib.request(api, { + method: "POST", + data: { + username: user.name, + password: user.password + } + }).then((result) => { + let newUser = Object.assign({}, user); + const rawData = result.res.data.toString(); + if (rawData === rateLimited) { + rej(`Error when logging into account: ${rateLimited}`); + return; + } + const data = JSON.parse(rawData); + if (result.res.statusCode !== 200) { + rej(`Error when logging into account: ${data.message}`); + return; + } + + newUser.token = data.token; + newUser.permission = data.user.permissionLevel; + newUser.role = data.user.role; + newUser.color = data.user.color; + newUser._id = data.user._id; + + res(newUser); + }).catch((error) => { + console.log(error) + rej(`Error when sending request to ${api}: ${error.code}`); + }); + }); + } +}; + +module.exports.categories = { + create: (server, token, name) => { + return new Promise((res, rej) => { + const validationError = module.exports.valid.category(name); + + const api = `http://${server}/api/v1/content/category/create`; + urllib.request(api, { + method: "POST", + headers: { + Cookie: `token=${token}` + }, + data: { + title: name + } + }).then((result) => { + if (result.res.statusCode === 200) + res(); + else { + const rawData = result.res.data.toString(); + if (rawData === rateLimited) { + rej(`Error when creating category: ${rateLimited}`); + return; + } + + const data = JSON.parse(rawData); + rej(`Error when creating category: ${data.message}`); + } + }).catch((error) => { + rej(`Error when sending request to ${api}: ${error.code}`); + }); + }); + }, + + list: (server, token, limit) => { + return new Promise((res, rej) => { + const api = `http://${server}/api/v1/content/category/list?count=${limit}` + urllib.request(api, { + method: "GET", + headers: { + Cookie: `token=${token}` + } + }).then((result) => { + const rawData = result.res.data.toString(); + if (rawData === rateLimited) { + rej(`Error while listing categories: ${rateLimited}`); + return; + } + const data = JSON.parse(rawData); + if (result.res.statusCode !== 200) { + rej(`Error while listing categories: ${data.message}`); + return; + } + res(data.categories); + }).catch((error) => { + rej(`Error while sending request to ${api}: ${error.code}`); + }); + }); + }, + + client: (server, token) => { + const socketURL = `ws://${server}/socket.io/?token=${token}&transport=websocket`; + return new Promise((res, rej) => { + const socket = new WebSocket(socketURL); + + let categories = {}; + let messageCallbacks = []; + let authenticated = false; + + let sharedData = { + connect: (categoriesID) => { + return new Promise((res, rej) => { + for (const categoryID of categoriesID) { + categories[categoryID] = { + updateCallback: res + } + } + const data = [ + "subscribe", + categoriesID + ]; + + socket.send(`42/gateway,${JSON.stringify(data)}`); + }); + }, + + send: (categoryID, message) => { + const data = [ + "message", + { + category: { + _id: categoryID + }, + content: message + } + ]; + + socket.send(`42/gateway,${JSON.stringify(data)}`); + }, + + onMessage: (callback) => { + messageCallbacks[messageCallbacks.length] = callback; + } + }; + + socket.on("message", (rawMessage) => { + const type = parseInt(rawMessage); + const contents = rawMessage.substr(type.toString().length); + + switch (type) { + // Authentication + case 0: { + socket.send("40/gateway,"); + if (authenticated) + console.error(`The server unexpectedly sent message type 0 multiple times${reportBug}`); + break; + } + + case 42: { + const data = JSON.parse(contents.substr(("/gateway,").length)); + const dataType = data[0]; + + switch (dataType) { + case "hello": { + socket.send("42/gateway,[\"yoo\"]"); + if (authenticated) + console.error(`The server unexpectedly sent hello through message type 42 multiple times${reportBug}`); + else { + authenticated = true; + res(sharedData); + } + break; + } + case "clientListUpdate": { + const categoryID = data[1].category._id; + const callback = categories[categoryID].updateCallback; + if (callback) { + categories[categoryID].updateCallback = null; + callback(categoryID); + } + break; + } + case "message": { + for (const callback in messageCallbacks) { + messageCallbacks[callback](data[1]); + } + } + } + break; + } + + // Ping + case 2: { + socket.send("3"); + break; + } + + // Things I don't care about + // More authentication messages + case 40: break; + + default: { + console.error(`Got sent unknown type ${type}\nMessage contents: ${rawMessage}`); + break; + } + } + }); + }); + } +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..4c29760 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "brainlet-lib", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://git.hippoz.xyz/hiimgoodpack/brainlet-lib.git" + }, + "author": "", + "license": "GPL-3.0-or-later", + "dependencies": { + "urllib": "^2.36.1", + "validator": "^13.5.2", + "ws": "^7.4.2" + } +}