2020-12-31 19:22:07 +02:00
|
|
|
/*
|
|
|
|
Unofficial brainlet command-line interface
|
|
|
|
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 brainlet = require("./brainlet-lib/index.js");
|
|
|
|
const readline = require("readline").createInterface({
|
|
|
|
input: process.stdin,
|
|
|
|
output: process.stdout
|
|
|
|
});
|
|
|
|
|
|
|
|
const ask = (question) => {
|
|
|
|
return new Promise((res, rej) => {
|
|
|
|
readline.question(question, res);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const catchErrors = (promise) => {
|
|
|
|
return new Promise((res, rej) => {
|
|
|
|
promise.then(res).catch((...errors) => {
|
|
|
|
console.error(...errors);
|
|
|
|
res(undefined);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const promiseSucceeded = async (promise) => {
|
|
|
|
return await new Promise((res, rej) => {
|
|
|
|
promise.then(() => {
|
|
|
|
res(true);
|
|
|
|
}).catch((...errors) => {
|
|
|
|
console.error(...errors);
|
|
|
|
res(false);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const getColorCode = ([r, g, b]) => {
|
|
|
|
return `\x1b[38;2;${r};${g};${b}m`;
|
|
|
|
};
|
|
|
|
const resetColorCode = "\x1b[0m";
|
|
|
|
const clearLine = "\x1b[2K";
|
|
|
|
|
|
|
|
const hexToRGB = (hex) => {
|
|
|
|
let decimal = parseInt(hex.substr(1), 16);
|
|
|
|
return [(decimal & 0xFF0000) >> 16, (decimal & 0xFF00) >> 8, decimal & 0xFF];
|
|
|
|
};
|
|
|
|
|
2020-12-31 23:27:05 +02:00
|
|
|
const format = {
|
|
|
|
bold: "\x1b[1m",
|
|
|
|
noBold: "\x1b[22m",
|
|
|
|
|
|
|
|
italics: "\x1b[3m",
|
|
|
|
noItalics: "\x1b[23m",
|
|
|
|
|
|
|
|
quote: "\x1b[7m\x1b[2m",
|
|
|
|
noQuote: "\x1b[27m\x1b[22m",
|
|
|
|
|
|
|
|
underline: "\x1b[4m",
|
|
|
|
noUnderline: "\x1b[24m"
|
|
|
|
};
|
|
|
|
const toState = (state) => {
|
|
|
|
let result = "";
|
|
|
|
result += format.noQuote;
|
|
|
|
result += state.bold ? format.bold : format.noBold;
|
|
|
|
result += state.italics ? format.italics : format.noItalics;
|
|
|
|
result += state.underline ? format.underline : format.noUnderline;
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
const toMarkdown = (text) => {
|
|
|
|
const defaultState = {
|
|
|
|
bold: false,
|
|
|
|
italics: false,
|
|
|
|
underline: false
|
|
|
|
};
|
|
|
|
|
|
|
|
let states = [Object.assign({}, defaultState)];
|
|
|
|
let result = "";
|
|
|
|
let quoteDepth = 0;
|
|
|
|
for (let i = 0; i < text.length; i++) {
|
|
|
|
let state = states[quoteDepth];
|
|
|
|
switch (text[i]) {
|
|
|
|
case "*": {
|
|
|
|
// If bold
|
|
|
|
if (text[i+1] === "*") {
|
|
|
|
state.bold = !state.bold;
|
|
|
|
result += state.bold ? format.bold : format.noBold;
|
|
|
|
i++;
|
|
|
|
} else {
|
|
|
|
state.italics = !state.italics;
|
|
|
|
result += state.italics ? format.italics : format.noItalics;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case ">": {
|
|
|
|
if (text[i+1] === " ") {
|
|
|
|
quoteDepth++;
|
|
|
|
result += toState(defaultState);
|
|
|
|
|
|
|
|
if (!states[quoteDepth])
|
|
|
|
states[quoteDepth] = Object.assign({}, defaultState);
|
|
|
|
result += (quoteDepth%2==1) ? format.quote : format.noQuote;
|
|
|
|
result += " | ";
|
|
|
|
result += toState(states[quoteDepth]);
|
|
|
|
result += (quoteDepth%2==1) ? format.quote : format.noQuote;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "_": {
|
|
|
|
if (text[i+1] === "_") {
|
|
|
|
state.underline = !state.underline;
|
|
|
|
result += state.underline ? format.underline : format.noUnderline;
|
|
|
|
i++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case "\n": {
|
|
|
|
if (quoteDepth != 0) {
|
|
|
|
quoteDepth = 0;
|
|
|
|
result += toState(states[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
default: {
|
|
|
|
result += text[i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
2020-12-31 19:22:07 +02:00
|
|
|
let user = {};
|
|
|
|
|
|
|
|
console.log(`Unofficial brainlet command-line interface
|
|
|
|
Copyright (C) 2020 hiimgoodpack
|
|
|
|
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
|
|
|
|
|
|
|
|
Warning: You should not connect to an untrusted server,
|
|
|
|
or a server with untrusted clients, as there may be some risks
|
|
|
|
with outputting arbritrary text ot the console.
|
|
|
|
`);
|
|
|
|
|
|
|
|
// Put in async function
|
|
|
|
(async() => {
|
|
|
|
const server = process.argv[2];
|
|
|
|
let prompt = 1;
|
|
|
|
let developerMode = 0;
|
|
|
|
let socket;
|
|
|
|
|
|
|
|
const useDeveloperMode = () => {
|
|
|
|
return new Promise((res, rej) => {
|
|
|
|
if (!developerMode) {
|
|
|
|
ask(`Warning: This command requires developer mode to be enabled.
|
|
|
|
Enabling developer mode may enable commands which can reveal private information or information
|
|
|
|
which can be used to log into your account.
|
|
|
|
|
|
|
|
Note: Every time the client is started, developer mode is disabled, even if you have enabled it previously in another session.
|
|
|
|
|
|
|
|
Are you sure you want to enable developer mode? [y/n] `).then((response) => {
|
|
|
|
if (response === "y") {
|
|
|
|
developerMode = true;
|
|
|
|
console.log("Developer mode has been enabled")
|
|
|
|
}
|
|
|
|
res();
|
|
|
|
});
|
|
|
|
} else
|
|
|
|
res();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
while (prompt) {
|
|
|
|
const input = await ask("> ");
|
|
|
|
switch (input) {
|
|
|
|
case "q":
|
|
|
|
case "quit": {
|
|
|
|
prompt = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case "signup": {
|
|
|
|
let newUser = {};
|
|
|
|
|
|
|
|
// Get username, email, and password
|
|
|
|
while (1) {
|
|
|
|
newUser.name = await ask("Username: ");
|
|
|
|
|
|
|
|
let error = brainlet.valid.username(newUser.name);
|
|
|
|
if (error)
|
|
|
|
console.error(error);
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
while (1) {
|
|
|
|
newUser.email = await ask("Email: ");
|
|
|
|
|
|
|
|
let error = brainlet.valid.email(newUser.email);
|
|
|
|
if (error)
|
|
|
|
console.error(error);
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
while (1) {
|
|
|
|
newUser.password = await ask("Password: ");
|
|
|
|
|
|
|
|
let error = brainlet.valid.password(newUser.password);
|
|
|
|
if (error)
|
|
|
|
console.error(error);
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
while (1) {
|
|
|
|
let confirmationPassword = await ask("Enter same password again: ");
|
|
|
|
if (confirmationPassword === newUser.password)
|
|
|
|
break;
|
|
|
|
else
|
|
|
|
console.error("Passwords do not match");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (await promiseSucceeded(brainlet.users.create(server, newUser))) {
|
|
|
|
console.log("Account successfully created. To log in, use the \"login\" command");
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case "login": {
|
|
|
|
// Get username and password
|
|
|
|
while (1) {
|
|
|
|
user.name = await ask("Username: ");
|
|
|
|
|
|
|
|
let error = brainlet.valid.username(user.name);
|
|
|
|
if (error)
|
|
|
|
console.error(error);
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
while (1) {
|
|
|
|
user.password = await ask("Password: ");
|
|
|
|
|
|
|
|
let error = brainlet.valid.password(user.password);
|
|
|
|
if (error)
|
|
|
|
console.error(error);
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
let newUser = await catchErrors(brainlet.users.login(server, user));
|
|
|
|
|
|
|
|
if (newUser) {
|
|
|
|
newUser.password = undefined;
|
|
|
|
newUser.email = undefined;
|
|
|
|
|
|
|
|
user = newUser;
|
|
|
|
console.log("Successfully logged in, starting connection");
|
|
|
|
|
|
|
|
socket = await brainlet.categories.client(server, user.token);
|
|
|
|
console.log("Connection started");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case "ls": {
|
|
|
|
if (!socket) {
|
|
|
|
console.error("You must log in before listing categories");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
const limit = 5;
|
|
|
|
let categories = await catchErrors(brainlet.categories.list(server, user.token, limit));
|
|
|
|
|
|
|
|
if (categories) {
|
|
|
|
if (categories === limit) {
|
|
|
|
console.error(`Warning: This may be an incomplete list of categories`);
|
|
|
|
}
|
|
|
|
console.log("CATEGORY ID | CATEGORY TITLE");
|
|
|
|
for (let category of categories) {
|
|
|
|
console.log(`${category._id} | ${category.title}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case "join": {
|
|
|
|
if (!socket) {
|
|
|
|
console.error("You must log in before joining a category");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
let categoryID = await ask("Category ID: ");
|
|
|
|
await catchErrors(socket.connect([categoryID]));
|
|
|
|
console.log(`Successfully joined category ${categoryID}.
|
|
|
|
Anything typed will be sent to other users, unless it is prefixed with '/'.
|
|
|
|
To leave this category, use '/quit'`);
|
|
|
|
|
|
|
|
const PS1 = `${getColorCode(hexToRGB(user.color))}${user.name}${resetColorCode} [message currently not sent]: `;
|
|
|
|
socket.onMessage((message) => {
|
|
|
|
if (message.category._id === categoryID) {
|
2020-12-31 23:27:05 +02:00
|
|
|
process.stdout.write(`\r${clearLine}${getColorCode(hexToRGB(message.author.color))}${message.author.username}${resetColorCode}: ${toMarkdown(message.content)}\n${PS1}`);
|
2020-12-31 19:22:07 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
let sendMessages = true;
|
|
|
|
while (sendMessages) {
|
|
|
|
const message = await ask(PS1);
|
|
|
|
switch (message) {
|
|
|
|
case "/leave": {
|
|
|
|
sendMessages = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default: {
|
|
|
|
if (message[0] === "/") {
|
|
|
|
console.error(`${getColorCode([0xdd, 0, 0])}Invalid command ${message}`);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-12-31 23:27:05 +02:00
|
|
|
socket.send(categoryID, message.replaceAll("/newline", "\n"));
|
2020-12-31 19:22:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case "dump": {
|
|
|
|
await useDeveloperMode();
|
|
|
|
if (developerMode) {
|
|
|
|
console.log(user);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
readline.close();
|
|
|
|
})();
|