deicidus
3 years ago
16 changed files with 536 additions and 1375 deletions
@ -1,10 +1,10 @@ |
|||||||
// Hook to add keyboard shortcuts to all inquirer prompts
|
// Hook to add keyboard shortcuts to all inquirer prompts
|
||||||
import inquirer from 'inquirer' |
//import inquirer from 'inquirer'
|
||||||
import InterruptedPrompt from 'inquirer-interrupted-prompt' |
//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.
|
// 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.
|
// 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.
|
// 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