An interactive command-line interface (CLI) tool to help you install, use, and administer an AO instance.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

233 lines
7.2 KiB

// AO shadowchat feature menu including bootstrap network server list browser, chatroom list on each server, and chatroom interface
// Called shadowchat because no record is kept of the chat messages, and all connections happen E2E over tor
// As this feature gets build, sensible standards must be developed around when tor addresses change hands, when users authenticate, etc
import { aoEnv } from './settings.js'
import { isLoggedIn } from './session.js'
import { startPublicBootstrap } from './bootstrap.js'
import { headerStyle } from './styles.js'
import { sleep } from './util.js'
import { askQuestionText, promptMenu } from './welcome.js'
import { AO_DEFAULT_HOSTNAME, startSocketListeners, socketStatus, socket, shadowchat } from './api.js'
import { fileURLToPath } from 'url'
import path from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
// Prints a menu that allows you to join the global AO chatrooms
export default async function chatMenu() {
let answers = {}
const PUBLIC_BOOTSTRAP_ENABLED = aoEnv('PUBLIC_BOOTSTRAP_ENABLED')
if(PUBLIC_BOOTSTRAP_ENABLED) {
// They previously enabled public bootstrapping, so check to make sure it is working and then hide the option
// todo: start and then verify functioning of p2p boostrap method here
// if it's already started don't start it again
//if(!publicBootstrapStarted) {
console.log("\nBootstrapping public AO swarm...")
startPublicBootstrap()
console.log("Bootstrapped (just kidding)")
//}
//answers['chat_menu'] = 'Enable p2p bootstrap'
}
const chatChoices = [
{ title: 'Join public chatroom', value: 'browse_chatrooms', short: 'public chatrooms' },
{ title: 'Join chatroom', value: 'join_chat', short: 'join chat' },
'Address Book',
'Back to AO Menu',
]
const answer = await promptMenu(chatChoices, 'AO Public Chatrooms')
switch(answer) {
case 'browse_chatrooms':
const loggedIn = isLoggedIn()
if(!isLoggedIn) {
console.log('Please start AO server and log in first.')
return true
}
console.log('Logged in as:', aoEnv('AO_CLI_SESSION_USERNAME'))
const onAuthenticated = () => {
//console.log('Websocket connected and authenticated.')
}
const onEvent = (event) => {
console.log('\nEvent received on websocket:', event) // todo: still receiving excess task-resets every 5 minutes
}
if(socketStatus !== 'authenticationSuccess') {
await startSocketListeners(onAuthenticated, onEvent)
await sleep(400)
}
console.log('socketStatus is', socketStatus)
if(socketStatus !== 'authenticationSuccess') {
console.log('Failed to connect to websocket. Make sure your AO server is running, and if you have a custom config set your target server in ao-cli.')
return true
}
console.log('Websocket successfully connected to listen for events.')
while(await browseChatrooms()) {}
break
case 'join_chat':
console.log('Not yet implemented')
break
case 'Address Book':
console.log('The point of this address book is to make it possible to type short, one-word names and have them resolve to tor addresses.')
console.log('Name a piece of data by saying name=data. For example, doge=5uc41o1...onion. Then \'doge\' will return the .onion address.')
console.log('Querying with any synonym in a chain will return the final meanings they all point to.')
console.log('Keys can have multiple values.')
break
default:
return false
}
return true
}
async function getLocalRooms() {
return new Promise((resolve, reject) => {
socket.on('rooms_list', event => {
socket.off('rooms_list')
resolve(event)
})
socket.emit('get_rooms')
})
}
async function browseChatrooms() {
if(socketStatus !== 'authenticationSuccess') {
console.log('Websocket is not connected, going back.')
return false
}
console.log('Getting list of chatrooms...')
const chatrooms = await getLocalRooms()
// Get list of all servers and display
// todo: check if websocket can work without authentication
// For each server, request its list of channels
console.log('Chatrooms on this server:', chatrooms)
//await chatInRoom('HUB')
return await chatroomScreen('HUB')
// Display the list as a menu with a heading for each server
}
async function chatInRoom(room) {
console.log('Joining room...')
socket.emit('join_room', room)
return new Promise(async (resolve, reject) => {
const leaveChatroom = () => {
console.log('Leaving room...')
socket.emit('leave_room', room)
socket.off('chat')
resolve()
}
socket.on('chat', event => {
const chatLine = event.name ? event.name + ': ' + event.message : 'Anon:' + event.message
console.log(/*new Date().toLocaleTimeString(), */'\n' + chatLine)
})
const username = aoEnv('AO_CLI_SESSION_USERNAME')
let message
do {
message = await askQuestionText('> ')
switch(message.toLowerCase()) {
case 'exit':
case 'leave':
case 'back':
case 'quit':
case false:
resolve(false)
default:
//socket.emit('chat', room, message, username || undefined)
shadowchat(room, message, username || undefined)
}
} while(message !== 'exit')
})
}
/*async function chatroomScreen(room) {
var ui = new inquirer.ui.BottomBar()
ui.render()
console.log(ui)
// pipe a Stream to the log zone
//outputStream.pipe(ui.log)
// Or simply write output
ui.log.write('something just happened.');
ui.log.write('Almost over, standby!');
// During processing, update the bottom bar content to display a loader
// or output a progress bar, etc
ui.updateBottomBar('new bottom bar content');
}*/
import blessed from 'blessed'
export async function chatroomScreen(room) {
const screen = blessed.screen({
smartCSR: true,
ignoreLocked: ['C-c'],
dockBorders: true,
title: room
})
const chatlog = blessed.log({
top: 0,
left: 0,
bottom: 2,
width: '100%',
content: '{bold}' + room + '{/bold}',
tags: true,
scrollable: true,
border: {
type: 'line'
},
style: {
fg: 'white',
bg: 'black',
border: {
fg: '#f0f0f0'
},
}
})
screen.append(chatlog);
const input = blessed.textbox({
bottom: 0,
left: 0,
right: 0,
height: 3,
content: 'Type message here',
inputOnFocus: true,
border: {
type: 'line'
},
style: {
fg: 'white',
bg: 'blue',
border: {
fg: '#f0f0f0'
},
}
})
input.on('submit', (event) => {
chatlog.log('new submission:', event)
input.clearValue()
input.focus()
})
input.on('keypress', (event) => {
chatlog.add('got keypress:', event)
})
screen.append(input)
//screen.focusPop()
chatlog.add('screen.focus:', screen.focus)
input.focus()
screen.render()
return new Promise(async (resolve, reject) => {
//screen.key(['C-c'], () => process.exit(0))
screen.key(['escape', 'C-c'], function(ch, key) {
chatlog.log('Exiting chatroom...')
screen.destroy()
resolve(false)
})
})
}