Initialize project
This commit is contained in:
parent
0655175a62
commit
37392f6a31
2 changed files with 350 additions and 0 deletions
330
index.js
Normal file
330
index.js
Normal 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
20
package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue