sshChat-CLI/sshChat.js

351 lines
9.6 KiB
JavaScript
Raw Normal View History

2023-01-11 21:27:33 +00:00
// Require the needed libs
2023-01-16 03:59:58 +00:00
const blessed = require('neo-blessed');
2023-01-11 20:48:57 +00:00
const Hyperswarm = require('hyperswarm')
const crypto = require('hypercore-crypto')
const b4a = require('b4a')
const readline = require('readline')
const fs = require("fs");
2023-01-11 21:27:33 +00:00
2023-01-16 03:59:58 +00:00
// Import our commands - You must add this line.
// Otherwise there is no access to the command functions
2023-01-11 20:48:57 +00:00
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');
2023-01-16 03:59:58 +00:00
// Generate a random number, this is used when generating a anon name
2023-01-11 20:48:57 +00:00
let rand = Math.floor(Math.random() * 99999).toString();
2023-01-16 03:59:58 +00:00
// Storage for our clients information
2023-01-11 20:48:57 +00:00
let USERPWD = "/"
let DAPI_KEY
let LOGGEDIN = false
let MYKEY = []
let conns = []
2023-01-12 18:35:30 +00:00
let connectedUsers = [];
2023-01-16 03:59:58 +00:00
let USERNAME = ["anon" + rand]
2023-01-13 04:51:24 +00:00
let DISCORD_USERID = []
2023-01-16 03:59:58 +00:00
// Sleep function used when closing connections.
2023-01-11 20:48:57 +00:00
function sleep(ms) {
2023-01-12 18:35:30 +00:00
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
2023-01-11 20:48:57 +00:00
}
2023-01-16 03:59:58 +00:00
// Adding a user to a peer and user MAP to keep track of peers.
2023-01-12 18:35:30 +00:00
function addUser(user, peerId) {
connectedUsers.push({ name: user, peerId: peerId });
2023-01-13 01:24:24 +00:00
sidebarBox.setContent(connectedUsers.map(user => `${user.peerId}`).join("\n"));
2023-01-12 18:35:30 +00:00
screen.render();
}
2023-01-16 03:59:58 +00:00
// Removing a user to a peer and user MAP to keep track of peers.
// TODO: Get this to work properly
2023-01-12 18:35:30 +00:00
function removeUser(peerId) {
connectedUsers = connectedUsers.filter(user => user.peerId !== peerId);
2023-01-13 01:24:24 +00:00
sidebarBox.setContent("Peers since connection: \n" + connectedUsers.map(user => `${user.peerId}`).join("\n"));
2023-01-12 18:35:30 +00:00
screen.render();
}
2023-01-16 03:59:58 +00:00
// Create the screen for blessed
2023-01-12 18:35:30 +00:00
const screen = blessed.screen({
smartCSR: true,
fastCSR: true
});
2023-01-16 03:03:13 +00:00
// 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()
})();
});
2023-01-16 03:59:58 +00:00
// When users press ESC, you may press t or enter to go back
// to the chat input box
2023-01-16 03:03:13 +00:00
screen.key(['t', 'enter'], function (ch, key) {
stdinBox.focus();
});
2023-01-16 03:59:58 +00:00
// 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) {
2023-01-16 03:14:24 +00:00
sidebarBox.focus();
});
2023-01-16 03:59:58 +00:00
// Debug ONLY - Use this to capture keypress events
// screen.on('keypress', function(ch, key){
// console.log(JSON.stringify(key));
// });
2023-01-16 03:59:58 +00:00
// Creating the mainbox
2023-01-12 18:35:30 +00:00
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'
}
}
});
2023-01-16 03:59:58 +00:00
// A function to update the mainbox scroll per message.
2023-01-12 18:35:30 +00:00
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
});
2023-01-16 03:14:24 +00:00
2023-01-16 03:59:58 +00:00
// Sidebar to display all peers that have been seen
2023-01-16 03:14:24 +00:00
let sidebarBox = blessed.box({
parent: screen,
2023-01-12 18:35:30 +00:00
top: '0',
right: '0',
width: '20%',
height: '100%',
border: {
type: 'line'
},
style: {
fg: 'white',
2023-01-16 03:14:24 +00:00
bg: 'black'
},
keys: true,
vi: true,
alwaysScroll: true,
scrollable: true,
scrollbar: {
style: {
bg: 'yellow'
}
2023-01-12 18:35:30 +00:00
}
});
2023-01-16 03:59:58 +00:00
// Setting the sidebar label.
2023-01-13 01:13:16 +00:00
sidebarBox.setLabel("Peers since connection: 0")
2023-01-12 18:35:30 +00:00
2023-01-16 03:59:58 +00:00
// Replacing the console.log function with our own
// This sends all console.log events to the main window.
2023-01-12 18:35:30 +00:00
const originalLog = console.log;
console.log = (...args) => {
mainBox.setContent(mainBox.getContent() + `\n${args.join(' ')}`);
updateScroll()
stdinBox.clearValue();
screen.render()
2023-01-12 18:35:30 +00:00
2023-01-11 20:48:57 +00:00
}
// Generate a random public key
const publicKey = crypto.randomBytes(32)
// Create the swarm and pass in the public key
const swarm = new Hyperswarm()
2023-01-16 03:59:58 +00:00
// Set up our commands
const commandDir = __dirname + '/commands/';
2023-01-11 20:48:57 +00:00
const commandFiles = fs.readdirSync(commandDir);
const commands = {}
2023-01-16 03:59:58 +00:00
// For each command lets add it to a var
2023-01-11 20:48:57 +00:00
for (const file of commandFiles) {
2023-01-12 18:35:30 +00:00
const commandName = file.split(".")[0]
require(`${commandDir}/${file}`)
const command = require(`${commandDir}/${file}`);
commands[commandName] = command;
2023-01-11 20:48:57 +00:00
}
2023-01-16 03:59:58 +00:00
// The command handler
2023-01-11 20:48:57 +00:00
async function handleCommand(input) {
2023-01-12 18:35:30 +00:00
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);
2023-01-12 18:35:30 +00:00
break;
case ">restart":
restart(MYKEY[0])
break;
case ">stop":
stop(MYKEY[0])
break;
case ">start":
start(MYKEY[0])
break;
case ">login":
2023-01-13 04:51:24 +00:00
login(command[1], conns, MYKEY, USERNAME, DISCORD_USERID)
2023-01-12 18:35:30 +00:00
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.")
2023-01-11 21:53:49 +00:00
}
2023-01-12 18:35:30 +00:00
} else {
if (DISCORD_USERID.length !== 0) {
for (const conn of conns)
conn.write(`${DISCORD_USERID[0]}: ${input}`)
} else {
2023-01-13 04:51:24 +00:00
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}`)
}
}
2023-01-11 20:48:57 +00:00
}
swarm.on('connection', conn => {
2023-01-12 18:35:30 +00:00
process.on('SIGINT', async () => {
console.log("Sending close message...")
for (let conn of conns) {
conn.write(`CLOSED: ${publicKey.toString('hex')}`)
}
2023-01-11 20:48:57 +00:00
2023-01-12 18:35:30 +00:00
await sleep(2000)
process.exit()
2023-01-11 20:48:57 +00:00
2023-01-12 18:35:30 +00:00
})
2023-01-16 03:59:58 +00:00
// Remote Connection Name
2023-01-12 18:35:30 +00:00
const name = b4a.toString(conn.remotePublicKey, 'hex')
console.log(`* got a connection from ${name} (${USERNAME[0]}) *`)
2023-01-16 03:59:58 +00:00
// Add the user to the MAP
2023-01-12 18:35:30 +00:00
addUser(USERNAME[0], name)
2023-01-16 03:59:58 +00:00
// Update the sidebar
2023-01-13 01:24:24 +00:00
sidebarBox.setLabel("Peers since connection: " + connectedUsers.length)
2023-01-16 03:59:58 +00:00
// Render the changes
2023-01-12 18:35:30 +00:00
screen.render()
2023-01-16 03:59:58 +00:00
// Add the connection to the conn list
2023-01-12 18:35:30 +00:00
conns.push(conn)
2023-01-16 03:59:58 +00:00
// IF the connection is closed, remove a connection
2023-01-12 18:35:30 +00:00
conn.once('close', () => conns.splice(conns.indexOf(conn), 1))
2023-01-16 03:59:58 +00:00
// Handle data as it comes in from the peer stream
2023-01-12 18:35:30 +00:00
conn.on('data', data => {
2023-01-16 03:59:58 +00:00
// If data shows that a peer has left, lets handle it and remove their key
2023-01-12 18:35:30 +00:00
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}`);
2023-01-11 20:48:57 +00:00
2023-01-16 03:59:58 +00:00
// Wait 5 seconds, remove the peer from the connections
// This stops timeouts which crashes all peers.
(async () => {
2023-01-12 18:35:30 +00:00
await sleep(5000)
conns = conns.filter(c => c !== conn);
conn.destroy();
})();
} else {
2023-01-16 03:59:58 +00:00
// If there is no actions detected, update chat.
2023-01-12 18:35:30 +00:00
console.log(`${data}`)
}
// Use the USERNAME if it has been set, otherwise use the public key
})
2023-01-11 20:48:57 +00:00
})
swarm.on('error', (err) => {
2023-01-12 18:35:30 +00:00
console.log('Error connecting to peer:', err);
2023-01-11 20:48:57 +00:00
});
// Join a common topic
const topic = process.argv[2] ? b4a.from(process.argv[2], 'hex') : crypto.randomBytes(32)
2023-01-16 03:59:58 +00:00
// Join the topic with a timeout
2023-01-11 20:48:57 +00:00
setTimeout(() => {
2023-01-12 18:35:30 +00:00
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)")
2023-01-12 18:55:20 +00:00
stdinBox.setLabel("To login: >login [TOKEN] | To quit: >exit")
2023-01-12 18:35:30 +00:00
screen.render()
})
2023-01-11 20:48:57 +00:00
}, 1000);
2023-01-12 18:35:30 +00:00
// 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
2023-01-16 03:03:13 +00:00
screen.render()