// Require the needed libs const blessed = require('neo-blessed'); const Hyperswarm = require('hyperswarm') const crypto = require('hypercore-crypto') const b4a = require('b4a') const readline = require('readline') const fs = require("fs"); // Import our commands - You must add this line. // Otherwise there is no access to the command functions const { login } = require('./commands/login'); const { execute } = require('./commands/exec'); const { stop } = require('./commands/stop'); const { start } = require('./commands/start'); const { restart } = require('./commands/restart'); const { stats } = require('./commands/stats'); const { changeDir } = require('./commands/cd'); const { AIRequest } = require('./commands/AI'); // Generate a random number, this is used when generating a anon name let rand = Math.floor(Math.random() * 99999).toString(); // Storage for our clients information let USERPWD = "/" let DAPI_KEY let LOGGEDIN = false let MYKEY = [] let conns = [] let connectedUsers = []; let USERNAME = ["anon" + rand] let DISCORD_USERID = [] // Sleep function used when closing connections. function sleep(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } // Adding a user to a peer and user MAP to keep track of peers. function addUser(user, peerId) { connectedUsers.push({ name: user, peerId: peerId }); sidebarBox.setContent(connectedUsers.map(user => `${user.peerId}`).join("\n")); screen.render(); } // Removing a user to a peer and user MAP to keep track of peers. // TODO: Get this to work properly function removeUser(peerId) { connectedUsers = connectedUsers.filter(user => user.peerId !== peerId); sidebarBox.setContent("Peers since connection: \n" + connectedUsers.map(user => `${user.peerId}`).join("\n")); screen.render(); } // Create the screen for blessed const screen = blessed.screen({ smartCSR: true, fastCSR: true }); // Quit on Escape, q, or Control-C. screen.key(['escape', 'q', 'C-c'], function (ch, key) { console.log("Sending close message...") for (let conn of conns) { conn.write(`CLOSED: ${publicKey.toString('hex')}`) } (async () => { await sleep(2000) process.exit() })(); }); // When users press ESC, you may press t or enter to go back // to the chat input box screen.key(['t', 'enter'], function (ch, key) { stdinBox.focus(); }); // When users press ESC, you may press p to go to the mainbox // This lets users scroll the chat feed. screen.key(['p'], function (ch, key) { sidebarBox.focus(); }); // Debug ONLY - Use this to capture keypress events // screen.on('keypress', function(ch, key){ // console.log(JSON.stringify(key)); // }); // Creating the mainbox let mainBox = blessed.box({ parent: screen, top: 0, left: 0, width: '80%', height: '80%', border: { type: 'line' }, style: { fg: 'white', bg: 'black' }, keys: true, vi: true, alwaysScroll: true, scrollable: true, scrollbar: { style: { bg: 'yellow' } } }); // A function to update the mainbox scroll per message. async function updateScroll() { mainBox.scrollTo(mainBox.getScrollHeight()); } // Create the STDIN box for chat input and command input const stdinBox = blessed.textbox({ bottom: '0', left: '0', width: '80%', height: '21%', border: { type: 'line' }, style: { fg: 'white', bg: 'black', border: { fg: '#f0f0f0' }, }, inputOnFocus: true, input: true }); // Sidebar to display all peers that have been seen let sidebarBox = blessed.box({ parent: screen, top: '0', right: '0', width: '20%', height: '100%', border: { type: 'line' }, style: { fg: 'white', bg: 'black' }, keys: true, vi: true, alwaysScroll: true, scrollable: true, scrollbar: { style: { bg: 'yellow' } } }); // Setting the sidebar label. sidebarBox.setLabel("Peers since connection: 0") // Replacing the console.log function with our own // This sends all console.log events to the main window. const originalLog = console.log; console.log = (...args) => { mainBox.setContent(mainBox.getContent() + `\n${args.join(' ')}`); updateScroll() stdinBox.clearValue(); screen.render() } // Generate a random public key const publicKey = crypto.randomBytes(32) // Create the swarm and pass in the public key const swarm = new Hyperswarm() // Set up our commands const commandDir = __dirname + '/commands/'; const commandFiles = fs.readdirSync(commandDir); const commands = {} // For each command lets add it to a var for (const file of commandFiles) { const commandName = file.split(".")[0] require(`${commandDir}/${file}`) const command = require(`${commandDir}/${file}`); commands[commandName] = command; } // The command handler async function handleCommand(input) { if (input.startsWith("!") || input.startsWith(">")) { const command = input.split(" ") if (!command) return consoile.log("Please either send a message or enter a command.") switch (command[0]) { case "!": AIRequest(command.slice(1).join(" ")) break; case ">cd": USERPWD = await changeDir(command[1], USERPWD); console.log(USERPWD) break; case ">stats": stats(MYKEY[0]) break; case ">": execute(MYKEY[0], command.slice(1).join(" "), USERPWD, conns, USERNAME); break; case ">restart": restart(MYKEY[0]) break; case ">stop": stop(MYKEY[0]) break; case ">start": start(MYKEY[0]) break; case ">login": login(command[1], conns, MYKEY, USERNAME, DISCORD_USERID) break; case ">exit": console.log("Sending close message...") for (let conn of conns) { conn.write(`CLOSED: ${publicKey.toString('hex')}`) } await sleep(2000) process.exit() break default: console.log("Command not found.") } } else { if (DISCORD_USERID.length !== 0) { for (const conn of conns) conn.write(`${DISCORD_USERID[0]}: ${input}`) } else { for (const conn of conns) conn.write(`${USERNAME}: ${input}`) } if (DISCORD_USERID.length !== 0) { console.log(`${DISCORD_USERID[0]}: ${input}`) } else { console.log(`${USERNAME}: ${input}`) } } } swarm.on('connection', conn => { process.on('SIGINT', async () => { console.log("Sending close message...") for (let conn of conns) { conn.write(`CLOSED: ${publicKey.toString('hex')}`) } await sleep(2000) process.exit() }) // Remote Connection Name const name = b4a.toString(conn.remotePublicKey, 'hex') console.log(`* got a connection from ${name} (${USERNAME[0]}) *`) // Add the user to the MAP addUser(USERNAME[0], name) // Update the sidebar sidebarBox.setLabel("Peers since connection: " + connectedUsers.length) // Render the changes screen.render() // Add the connection to the conn list conns.push(conn) // IF the connection is closed, remove a connection conn.once('close', () => conns.splice(conns.indexOf(conn), 1)) // Handle data as it comes in from the peer stream conn.on('data', data => { // If data shows that a peer has left, lets handle it and remove their key if (data.toString().startsWith('CLOSED:')) { // Extract the key from the message string const key = data.toString().split(':')[1].trim(); removeUser(key) console.log(`Removing peer ${key}`); // Wait 5 seconds, remove the peer from the connections // This stops timeouts which crashes all peers. (async () => { await sleep(5000) conns = conns.filter(c => c !== conn); conn.destroy(); })(); } else { // If there is no actions detected, update chat. console.log(`${data}`) } // Use the USERNAME if it has been set, otherwise use the public key }) }) swarm.on('error', (err) => { console.log('Error connecting to peer:', err); }); // Join a common topic const topic = process.argv[2] ? b4a.from(process.argv[2], 'hex') : crypto.randomBytes(32) // Join the topic with a timeout setTimeout(() => { const discovery = swarm.join(topic, { lookup: true, announce: true, timeout: 300000 }); // The flushed promise will resolve when the topic has been fully announced to the DHT discovery.flushed().then(() => { mainBox.setLabel("Topic: " + b4a.toString(topic, 'hex') + " (Share to connect)") stdinBox.setLabel("To login: >login [TOKEN] | To quit: >exit") screen.render() }) }, 1000); // Append the boxes to the screen screen.append(mainBox); screen.append(stdinBox); screen.append(sidebarBox); // Handle input in the stdinBox stdinBox.on('submit', (input) => { // handle the input here // for example : handleCommand(input); // clear the input field stdinBox.focus(); }); stdinBox.focus(); // Render the screen screen.render()