Initialize project

This commit is contained in:
hiimgoodpack 2020-12-31 12:07:08 -05:00
parent 0655175a62
commit 37392f6a31
Signed by: hiimgoodpack
GPG key ID: 4E0E62733C14AE69
2 changed files with 350 additions and 0 deletions

330
index.js Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}
});
});
}
};

20
package.json Normal file
View file

@ -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"
}
}