/* 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 . */ const brainlet = require("./brainlet-lib/index.js"); const replaceAll = require("replaceall"); 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]; }; 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; }; let user = {}; console.log(`Unofficial brainlet command-line interface Copyright (C) 2020 hiimgoodpack License GPLv3+: GNU GPL version 3 or later 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) { process.stdout.write(`\r${clearLine}${getColorCode(hexToRGB(message.author.color))}${message.author.username}${resetColorCode}: ${toMarkdown(message.content)}\n${PS1}`); } }); 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; } socket.send(categoryID, replaceAll("/newline", "\n", message)); } } } break; } case "dump": { await useDeveloperMode(); if (developerMode) { console.log(user); } break; } } } readline.close(); })();