deicidus
2 years ago
16 changed files with 536 additions and 1375 deletions
@ -1,10 +1,10 @@
|
||||
// Hook to add keyboard shortcuts to all inquirer prompts
|
||||
import inquirer from 'inquirer' |
||||
import InterruptedPrompt from 'inquirer-interrupted-prompt' |
||||
//import inquirer from 'inquirer'
|
||||
//import InterruptedPrompt from 'inquirer-interrupted-prompt'
|
||||
|
||||
InterruptedPrompt.replaceAllDefaults(inquirer) |
||||
//InterruptedPrompt.replaceAllDefaults(inquirer)
|
||||
|
||||
// Note that the above method can only detect one key per menu, apparently, and it can't tell you which one it detected in the callback.
|
||||
// Might be worth looking into a more flexible UI library that integrates keyboard shortcuts with its menu library.
|
||||
// Right now we are stuck with just using one key, the Escape key to go up a level in menus. Every menu must handle it or it can crash.
|
||||
// The inquirer-interrupted-prompt documentation shows using a prompt with an array of prompt objects as args, but it didn't work for me.
|
||||
// The inquirer-interrupted-prompt documentation shows using a prompt with an array of prompt objects as args, but it didn't work for me.
|
||||
|
@ -0,0 +1,232 @@
|
||||
// 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) |
||||
}) |
||||
}) |
||||
} |
Loading…
Reference in new issue