cwtch/app/cli/main.go

445 lines
15 KiB
Go

package main
import (
app2 "cwtch.im/cwtch/app"
"fmt"
"github.com/c-bata/go-prompt"
"strings"
"time"
"bytes"
"golang.org/x/crypto/ssh/terminal"
"os"
"syscall"
)
var app app2.Application
var suggestions = []prompt.Suggest{
{Text: "new-profile", Description: "create a new profile"},
{Text: "load-profile", Description: "load a new profile"},
{Text: "quit", Description: "quit cwtch"},
{Text: "info", Description: "show user info"},
{Text: "servers", Description: "retrieve a list of servers and their connection status"},
{Text: "peers", Description: "retrieve a list of peers and their connection status"},
{Text: "contacts", Description: "retrieve a list of contacts"},
{Text: "groups", Description: "retrieve a list of groups"},
{Text: "send", Description: "send a message to a group"},
{Text: "timeline", Description: "read the timeline of a given group"},
{Text: "accept-invite", Description: "accept the invite of a group"},
{Text: "invite", Description: "invite a new contact"},
{Text: "invite-to-group", Description: "invite an existing contact to join an existing group"},
{Text: "new-group", Description: "create a new group"},
{Text: "help", Description: "print list of commands"},
{Text: "trust", Description: "trust a peer"},
{Text: "block", Description: "block a peer - you will no longer see messages or connect to this peer"},
}
var usages = map[string]string{
"new-profile": "new-profile [name] [filename]",
"load-profile": "load-profile [filename]",
"quit": "",
"servers": "",
"peers": "",
"contacts": "",
"groups": "",
"info": "",
"send": "send [groupid] [message]",
"timeline": "timeline [groupid]",
"accept-invite": "accept-invite [groupid]",
"invite": "invite [peerid]",
"invite-to-group": "invite-to-group [peerid] [groupid]",
"new-group": "new-group [server]",
"help": "",
"trust": "trust [peerid]",
"block": "block [peerid]",
}
func completer(d prompt.Document) []prompt.Suggest {
var s []prompt.Suggest
if d.FindStartOfPreviousWord() == 0 {
return prompt.FilterHasPrefix(suggestions, d.GetWordBeforeCursor(), true)
}
w := d.CurrentLine()
if strings.HasPrefix(w, "send") || strings.HasPrefix(w, "timeline") {
s = []prompt.Suggest{}
groups := app.Peer.GetGroups()
for _, groupID := range groups {
group := app.Peer.GetGroup(groupID)
s = append(s, prompt.Suggest{Text: group.GroupID, Description: "Group owned by " + group.Owner + " on " + group.GroupServer})
}
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
}
if strings.HasPrefix(w, "block") || strings.HasPrefix(w, "trust") {
s = []prompt.Suggest{}
contacts := app.Peer.GetContacts()
for _, onion := range contacts {
contact := app.Peer.GetContact(onion)
s = append(s, prompt.Suggest{Text: contact.Onion, Description: contact.Name})
}
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
}
if strings.HasPrefix(w, "invite-to-group") {
if d.FindStartOfPreviousWordWithSpace() == 0 {
s = []prompt.Suggest{}
contacts := app.Peer.GetContacts()
for _, onion := range contacts {
contact := app.Peer.GetContact(onion)
s = append(s, prompt.Suggest{Text: contact.Onion, Description: contact.Name})
}
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
}
s = []prompt.Suggest{}
groups := app.Peer.GetGroups()
for _, groupID := range groups {
group := app.Peer.GetGroup(groupID)
if group.Owner == "self" {
s = append(s, prompt.Suggest{Text: group.GroupID, Description: "Group owned by " + group.Owner + " on " + group.GroupServer})
}
}
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
}
if strings.HasPrefix(w, "accept-invite") {
s = []prompt.Suggest{}
groups := app.Peer.GetGroups()
for _, groupID := range groups {
group := app.Peer.GetGroup(groupID)
if group.Accepted == false {
s = append(s, prompt.Suggest{Text: group.GroupID, Description: "Group owned by " + group.Owner + " on " + group.GroupServer})
}
}
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
}
return s
}
func main() {
cwtch :=
`
#, #'
@@@@@@:
@@@@@@.
@'@@+#' @@@@+
''''''@ #+@ :
@''''+;+' . '
@''@' :+' , ; ##, +'
,@@ ;' #'#@''. #''@''#
# ''''''#:,,#'''''@
: @''''@ :+'''@
' @;+'@ @'#
.:# '#..# '# @
@@@@@@
@@@@@@
'@@@@
@# . .
+++, #'@+'@
''', ''''''#
.#+# ''', @'''+,
@''# ''', .#@
:; '@''# .;. ''', ' : ;. ,
@+'''@ '+'+ @++ @+'@+''''+@ #+'''#: ''';#''+@ @@@@ @@@@@@@@@ :@@@@#
#''''''# +''. +'': +'''''''''+ @'''''''# '''+'''''@ @@@@ @@@@@@@@@@@@@@@@:
@'''@@'''@ @''# ,'''@ ''+ @@''+#+ :'''@@+''' ''''@@'''' @@@@ @@@@@@@@@@@@@@@@@
'''# @''# +''@ @'''# ;''@ +''+ @''@ ,+'', '''@ #'''. @@@@ @@@@ '@@@# @@@@
;''' @@; '''# #'@'' @''@ @''+ +''# .@@ ''', '''. @@@@ @@@ @@@ .@@@
@''# #'' ''#''#@''. #''# '''. '''. +'', @@@@ @@@ @@@ @@@
@''# @''@'' #'@+'+ #''# '''. ''', +'', +@@@.@@@ @@@@ @@@, @@@ ,@@@
;''+ @, +''@'# @'+''@ @''# +''; '+ ''', +'', @@@@@@@@# @@@@ @@@. .@@@ .@@@
'''# ++'+ ''''@ ,''''# #''' @''@ '@''+ ''', ''', @@@@@@@@: @@@@ @@@; .@@@' ;@@@
@'''@@'''@ #'''. +'''' ;'''#@ :'''#@+''+ ''', ''', @@@@@@# @@@@ @@@+ ,@@@. @@@@
#''''''# @''+ @''+ +'''' @'''''''# ''', ''', #@@@. @@@@ @@@+ @@@ @@@@
@+''+@ '++@ ;++@ '#''@ ##'''@: +++, +++, :@ @@@@ @@@' @@@ '@@@
:' ' '`
fmt.Printf("%v\n\n", cwtch)
quit := false
app = app2.Application{}
profilefile := ""
var history []string
for !quit {
profile := "unset"
if app.Peer != nil {
profile = app.Peer.GetProfile().Name
}
prmpt := fmt.Sprintf("cwtch [%v]> ", profile)
text := prompt.Input(prmpt, completer, prompt.OptionSuggestionBGColor(prompt.Purple),
prompt.OptionDescriptionBGColor(prompt.White),
prompt.OptionHistory(history))
commands := strings.Split(text[0:], " ")
history = append(history, text)
switch commands[0] {
case "quit":
app.Peer.Save(profilefile)
quit = true
case "new-profile":
if len(commands) == 3 {
fmt.Print("** WARNING: PASSWORDS CANNOT BE RECOVERED! **\n")
password := ""
failcount := 0
for ; failcount < 3; failcount++ {
fmt.Print("Enter a password to encrypt the profile: ")
bytePassword, _ := terminal.ReadPassword(int(syscall.Stdin))
if string(bytePassword) == "" {
fmt.Print("\nBlank password not allowed.")
continue
}
fmt.Print("\nRe-enter password: ")
bytePassword2, _ := terminal.ReadPassword(int(syscall.Stdin))
if bytes.Equal(bytePassword, bytePassword2) {
password = string(bytePassword)
break
} else {
fmt.Print("\nPASSWORDS DIDN'T MATCH! Try again.\n")
}
}
if failcount >= 3 {
fmt.Printf("Error creating profile for %v: Your password entries must match!\n", commands[1])
} else {
err := app.NewProfile(commands[1], commands[2], password)
profilefile = commands[2]
if err == nil {
fmt.Printf("\nNew profile created for %v\n", commands[1])
} else {
fmt.Printf("\nError creating profile for %v: %v\n", commands[1], err)
}
}
} else {
fmt.Printf("Error creating NewProfile, usage: %s\n", usages["new-profile"])
}
case "load-profile":
if len(commands) == 2 {
fmt.Print("Enter a password to decrypt the profile: ")
bytePassword, err := terminal.ReadPassword(int(syscall.Stdin))
err = app.SetProfile(commands[1], string(bytePassword))
if err == nil {
fmt.Printf("\nLoaded profile for %v\n", commands[1])
profilefile = commands[1]
} else {
fmt.Printf("Error loading profile for %v: %v\n", commands[1], err)
}
} else {
fmt.Printf("Error Loading profile, usage: %s\n", usages["load-profile"])
}
case "info":
if app.Peer != nil {
fmt.Printf("Address cwtch:%v\n", app.Peer.GetProfile().Onion)
} else {
fmt.Printf("Profile needs to be set\n")
}
case "invite":
if len(commands) == 2 {
fmt.Printf("Inviting cwtch:%v\n", commands[1])
app.PeerRequest(commands[1])
} else {
fmt.Printf("Error inviting peer, usage: %s\n", usages["invite"])
}
case "peers":
peers := app.Peer.GetPeers()
for p, s := range peers {
fmt.Printf("Name: %v Status: %v\n", p, s)
}
case "servers":
servers := app.Peer.GetServers()
for s, st := range servers {
fmt.Printf("Name: %v Status: %v\n", s, st)
}
case "contacts":
contacts := app.Peer.GetContacts()
for _, onion := range contacts {
c := app.Peer.GetContact(onion)
fmt.Printf("Name: %v Onion: %v Trusted: %v\n", c.Name, c.Onion, c.Trusted)
}
case "groups":
for _, gid := range app.Peer.GetGroups() {
g := app.Peer.GetGroup(gid)
fmt.Printf("Group Id: %v Owner: %v Accepted:%v\n", gid, g.Owner, g.Accepted)
}
case "trust":
if len(commands) == 2 {
app.Peer.TrustPeer(commands[1])
} else {
fmt.Printf("Error trusting peer, usage: %s\n", usages["trust"])
}
case "block":
if len(commands) == 2 {
app.Peer.BlockPeer(commands[1])
} else {
fmt.Printf("Error blocking peer, usage: %s\n", usages["trust"])
}
case "accept-invite":
if len(commands) == 2 {
groupID := commands[1]
err := app.Peer.AcceptInvite(groupID)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
app.Peer.Save(profilefile)
group := app.Peer.GetGroup(groupID)
if group == nil {
fmt.Printf("Error: group does not exist\n")
} else {
app.Peer.JoinServer(group.GroupServer)
}
}
} else {
fmt.Printf("Error accepting invite, usage: %s\n", usages["accept-invite"])
}
case "invite-to-group":
if len(commands) == 3 {
fmt.Printf("Inviting %v to %v\n", commands[1], commands[2])
err := app.Peer.InviteOnionToGroup(commands[1], commands[2])
if err != nil {
fmt.Printf("Error: %v\n", err)
}
} else {
fmt.Printf("Error inviting peer to group, usage: %s\n", usages["invite-to-group"])
}
case "new-group":
if len(commands) == 2 && commands[1] != "" {
fmt.Printf("Setting up a new group on server:%v\n", commands[1])
id, _, err := app.Peer.StartGroup(commands[1])
if err == nil {
fmt.Printf("New Group [%v] created for server %v\n", id, commands[1])
app.Peer.Save(profilefile)
group := app.Peer.GetGroup(id)
if group == nil {
fmt.Printf("Error: group does not exist\n")
} else {
app.Peer.JoinServer(group.GroupServer)
}
} else {
fmt.Printf("Error creating new group: %v", err)
}
} else {
fmt.Printf("Error creating a new group, usage: %s\n", usages["new-group"])
}
case "send":
if len(commands) > 2 {
message := strings.Join(commands[2:], " ")
err := app.Peer.SendMessageToGroup(commands[1], message)
if err != nil {
fmt.Printf("Error: %v\n", err)
}
} else {
fmt.Printf("Error sending message to group, usage: %s\n", usages["send"])
}
case "timeline":
if len(commands) == 2 {
group := app.Peer.GetGroup(commands[1])
if group == nil {
fmt.Printf("Error: group does not exist\n")
} else {
timeline := group.GetTimeline()
for _, m := range timeline {
verified := "not-verified"
if m.Verified {
verified = "verified"
}
p := app.Peer.GetContact(m.PeerID)
name := "unknown"
if p != nil {
name = p.Name
} else if app.Peer.GetProfile().Onion == m.PeerID {
name = app.Peer.GetProfile().Name
}
fmt.Printf("%v %v (%v): %v [%s]\n", m.Timestamp, name, m.PeerID, m.Message, verified)
}
}
} else {
fmt.Printf("Error reading timeline from group, usage: %s\n", usages["timeline"])
}
case "export-group":
if len(commands) == 2 {
group := app.Peer.GetGroup(commands[1])
if group == nil {
fmt.Printf("Error: group does not exist\n")
} else {
invite, _ := app.Peer.ExportGroup(commands[1])
fmt.Printf("Invite: %v\n", invite)
}
} else {
fmt.Printf("Error reading timeline from group, usage: %s\n", usages["timeline"])
}
case "save":
app.Peer.Save(profilefile)
case "help":
for _, command := range suggestions {
fmt.Printf("%-18s%-56s%s\n", command.Text, command.Description, usages[command.Text])
}
case "sendlots":
if len(commands) == 2 {
group := app.Peer.GetGroup(commands[1])
if group == nil {
fmt.Printf("Error: group does not exist\n")
} else {
for i := 0; i < 100; i++ {
fmt.Printf("Sending message: %v\n", i)
err := app.Peer.SendMessageToGroup(commands[1], fmt.Sprintf("this is message %v", i))
if err != nil {
fmt.Printf("could not send message %v because %v\n", i, err)
}
}
fmt.Printf("Waiting 5 seconds for message to process...\n")
time.Sleep(time.Second * 5)
timeline := group.GetTimeline()
totalLatency := time.Duration(0)
maxLatency := time.Duration(0)
totalMessages := 0
for i := 0; i < 100; i++ {
found := false
for _, m := range timeline {
if m.Message == fmt.Sprintf("this is message %v", i) && m.PeerID == app.Peer.GetProfile().Onion {
found = true
latency := m.Received.Sub(m.Timestamp)
fmt.Printf("Latency for Message %v was %v\n", i, latency)
totalLatency = totalLatency + latency
if maxLatency < latency {
maxLatency = latency
}
totalMessages++
}
}
if !found {
fmt.Printf("message %v was never received\n", i)
}
}
fmt.Printf("Average Latency for %v messages was: %vms\n", totalMessages, time.Duration(int64(totalLatency)/int64(totalMessages)))
fmt.Printf("Max Latency for %v messages was: %vms\n", totalMessages, maxLatency)
}
}
}
if profilefile != "" {
if app.Peer != nil {
app.Peer.Save(profilefile)
}
}
}
if app.TorManager != nil {
fmt.Println("Shutting down Tor process...")
app.TorManager.Shutdown()
}
os.Exit(0)
}