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) }