Official cwtch.im peer and server implementations. https://cwtch.im
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.

main.go 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. package main
  2. import (
  3. app2 "cwtch.im/cwtch/app"
  4. "cwtch.im/cwtch/event"
  5. peer2 "cwtch.im/cwtch/peer"
  6. "bytes"
  7. "cwtch.im/cwtch/model"
  8. "fmt"
  9. "git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
  10. "git.openprivacy.ca/openprivacy/libricochet-go/log"
  11. "github.com/c-bata/go-prompt"
  12. "golang.org/x/crypto/ssh/terminal"
  13. "os"
  14. "os/user"
  15. "path"
  16. "strings"
  17. "syscall"
  18. "time"
  19. )
  20. var app app2.Application
  21. var peer peer2.CwtchPeer
  22. var group *model.Group
  23. var groupFollowBreakChan chan bool
  24. var prmpt string
  25. var suggestionsBase = []prompt.Suggest{
  26. {Text: "/new-profile", Description: "create a new profile"},
  27. {Text: "/load-profiles", Description: "loads profiles with a password"},
  28. {Text: "/list-profiles", Description: "list active profiles"},
  29. {Text: "/select-profile", Description: "selects an active profile to use"},
  30. {Text: "/help", Description: "print list of commands"},
  31. {Text: "/quit", Description: "quit cwtch"},
  32. }
  33. var suggestionsSelectedProfile = []prompt.Suggest{
  34. {Text: "/info", Description: "show user info"},
  35. {Text: "/list-contacts", Description: "retrieve a list of contacts"},
  36. {Text: "/list-groups", Description: "retrieve a list of groups"},
  37. {Text: "/new-group", Description: "create a new group on a server"},
  38. {Text: "/select-group", Description: "selects a group to follow"},
  39. {Text: "/unselect-group", Description: "stop following the current group"},
  40. {Text: "/invite", Description: "invite a new contact"},
  41. {Text: "/invite-to-group", Description: "invite an existing contact to join an existing group"},
  42. {Text: "/accept-invite", Description: "accept the invite of a group"},
  43. /*{Text: "/list-servers", Description: "retrieve a list of servers and their connection status"},
  44. {Text: "/list-peers", Description: "retrieve a list of peers and their connection status"},*/
  45. {Text: "/export-group", Description: "export a group invite: prints as a string"},
  46. {Text: "/trust", Description: "trust a peer"},
  47. {Text: "/block", Description: "block a peer - you will no longer see messages or connect to this peer"},
  48. }
  49. var suggestions = suggestionsBase
  50. var usages = map[string]string{
  51. "/new-profile": "/new-profile [name]",
  52. "/load-profiles": "/load-profiles",
  53. "/list-profiles": "",
  54. "/select-profile": "/select-profile [onion]",
  55. "/quit": "",
  56. /* "/list-servers": "",
  57. "/list-peers": "",*/
  58. "/list-contacts": "",
  59. "/list-groups": "",
  60. "/select-group": "/select-group [groupid]",
  61. "/unselect-group": "",
  62. "/export-group": "/export-group [groupid]",
  63. "/info": "",
  64. "/send": "/send [groupid] [message]",
  65. "/timeline": "/timeline [groupid]",
  66. "/accept-invite": "/accept-invite [groupid]",
  67. "/invite": "/invite [peerid]",
  68. "/invite-to-group": "/invite-to-group [groupid] [peerid]",
  69. "/new-group": "/new-group [server]",
  70. "/help": "",
  71. "/trust": "/trust [peerid]",
  72. "/block": "/block [peerid]",
  73. }
  74. func printMessage(m model.Message) {
  75. p := peer.GetContact(m.PeerID)
  76. name := "unknown"
  77. if p != nil {
  78. name = p.Name
  79. } else if peer.GetProfile().Onion == m.PeerID {
  80. name = peer.GetProfile().Name
  81. }
  82. fmt.Printf("%v %v (%v): %v\n", m.Timestamp, name, m.PeerID, m.Message)
  83. }
  84. func startGroupFollow() {
  85. groupFollowBreakChan = make(chan bool)
  86. go func() {
  87. for {
  88. l := len(group.Timeline.GetMessages())
  89. select {
  90. case <-time.After(1 * time.Second):
  91. if group == nil {
  92. return
  93. }
  94. gms := group.Timeline.GetMessages()
  95. if len(gms) != l {
  96. fmt.Printf("\n")
  97. for ; l < len(gms); l++ {
  98. printMessage(gms[l])
  99. }
  100. fmt.Printf(prmpt)
  101. }
  102. case <-groupFollowBreakChan:
  103. return
  104. }
  105. }
  106. }()
  107. }
  108. func stopGroupFollow() {
  109. if group != nil {
  110. groupFollowBreakChan <- true
  111. group = nil
  112. }
  113. }
  114. func completer(d prompt.Document) []prompt.Suggest {
  115. var s []prompt.Suggest
  116. if d.FindStartOfPreviousWord() == 0 {
  117. return prompt.FilterHasPrefix(suggestions, d.GetWordBeforeCursor(), true)
  118. }
  119. w := d.CurrentLine()
  120. // Suggest a profile id
  121. if strings.HasPrefix(w, "/select-profile") {
  122. s = []prompt.Suggest{}
  123. peerlist := app.ListPeers()
  124. for onion, peername := range peerlist {
  125. s = append(s, prompt.Suggest{Text: onion, Description: peername})
  126. }
  127. }
  128. if peer == nil {
  129. return s
  130. }
  131. // Suggest groupid
  132. if /*strings.HasPrefix(w, "send") || strings.HasPrefix(w, "timeline") ||*/ strings.HasPrefix(w, "/export-group") || strings.HasPrefix(w, "/select-group") {
  133. s = []prompt.Suggest{}
  134. groups := peer.GetGroups()
  135. for _, groupID := range groups {
  136. group := peer.GetGroup(groupID)
  137. s = append(s, prompt.Suggest{Text: group.GroupID, Description: "Group owned by " + group.Owner + " on " + group.GroupServer})
  138. }
  139. return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
  140. }
  141. // Suggest unaccepted group
  142. if strings.HasPrefix(w, "/accept-invite") {
  143. s = []prompt.Suggest{}
  144. groups := peer.GetGroups()
  145. for _, groupID := range groups {
  146. group := peer.GetGroup(groupID)
  147. if group.Accepted == false {
  148. s = append(s, prompt.Suggest{Text: group.GroupID, Description: "Group owned by " + group.Owner + " on " + group.GroupServer})
  149. }
  150. }
  151. return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
  152. }
  153. // suggest groupid AND peerid
  154. if strings.HasPrefix(w, "/invite-to-group") {
  155. if d.FindStartOfPreviousWordWithSpace() == 0 {
  156. s = []prompt.Suggest{}
  157. groups := peer.GetGroups()
  158. for _, groupID := range groups {
  159. group := peer.GetGroup(groupID)
  160. if group.Owner == "self" {
  161. s = append(s, prompt.Suggest{Text: group.GroupID, Description: "Group owned by " + group.Owner + " on " + group.GroupServer})
  162. }
  163. }
  164. return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
  165. }
  166. s = []prompt.Suggest{}
  167. contacts := peer.GetContacts()
  168. for _, onion := range contacts {
  169. contact := peer.GetContact(onion)
  170. s = append(s, prompt.Suggest{Text: contact.Onion, Description: contact.Name})
  171. }
  172. return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
  173. }
  174. // Suggest contact onion / peerid
  175. if strings.HasPrefix(w, "/block") || strings.HasPrefix(w, "/trust") || strings.HasPrefix(w, "/invite") {
  176. s = []prompt.Suggest{}
  177. contacts := peer.GetContacts()
  178. for _, onion := range contacts {
  179. contact := peer.GetContact(onion)
  180. s = append(s, prompt.Suggest{Text: contact.Onion, Description: contact.Name})
  181. }
  182. return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
  183. }
  184. return s
  185. }
  186. func handleAppEvents(em event.Manager) {
  187. queue := event.NewEventQueue(100)
  188. em.Subscribe(event.NewPeer, queue.EventChannel)
  189. em.Subscribe(event.PeerError, queue.EventChannel)
  190. for {
  191. ev := queue.Next()
  192. switch ev.EventType {
  193. case event.NewPeer:
  194. onion := ev.Data[event.Identity]
  195. p := app.GetPeer(onion)
  196. app.LaunchPeers()
  197. fmt.Printf("\nLoaded profile %v (%v)\n", p.GetProfile().Name, p.GetProfile().Onion)
  198. suggestions = append(suggestionsBase, suggestionsSelectedProfile...)
  199. profiles := app.ListPeers()
  200. fmt.Printf("\n%v profiles active now\n", len(profiles))
  201. fmt.Printf("You should run `select-profile` to use a profile or `list-profiles` to view loaded profiles\n")
  202. case event.PeerError:
  203. err := ev.Data[event.Error]
  204. fmt.Printf("\nError creating profile: %v\n", err)
  205. }
  206. }
  207. }
  208. func main() {
  209. cwtch :=
  210. `
  211. #, #'
  212. @@@@@@:
  213. @@@@@@.
  214. @'@@+#' @@@@+
  215. ''''''@ #+@ :
  216. @''''+;+' . '
  217. @''@' :+' , ; ##, +'
  218. ,@@ ;' #'#@''. #''@''#
  219. # ''''''#:,,#'''''@
  220. : @''''@ :+'''@
  221. ' @;+'@ @'#
  222. .:# '#..# '# @
  223. @@@@@@
  224. @@@@@@
  225. '@@@@
  226. @# . .
  227. +++, #'@+'@
  228. ''', ''''''#
  229. .#+# ''', @'''+,
  230. @''# ''', .#@
  231. :; '@''# .;. ''', ' : ;. ,
  232. @+'''@ '+'+ @++ @+'@+''''+@ #+'''#: ''';#''+@ @@@@ @@@@@@@@@ :@@@@#
  233. #''''''# +''. +'': +'''''''''+ @'''''''# '''+'''''@ @@@@ @@@@@@@@@@@@@@@@:
  234. @'''@@'''@ @''# ,'''@ ''+ @@''+#+ :'''@@+''' ''''@@'''' @@@@ @@@@@@@@@@@@@@@@@
  235. '''# @''# +''@ @'''# ;''@ +''+ @''@ ,+'', '''@ #'''. @@@@ @@@@ '@@@# @@@@
  236. ;''' @@; '''# #'@'' @''@ @''+ +''# .@@ ''', '''. @@@@ @@@ @@@ .@@@
  237. @''# #'' ''#''#@''. #''# '''. '''. +'', @@@@ @@@ @@@ @@@
  238. @''# @''@'' #'@+'+ #''# '''. ''', +'', +@@@.@@@ @@@@ @@@, @@@ ,@@@
  239. ;''+ @, +''@'# @'+''@ @''# +''; '+ ''', +'', @@@@@@@@# @@@@ @@@. .@@@ .@@@
  240. '''# ++'+ ''''@ ,''''# #''' @''@ '@''+ ''', ''', @@@@@@@@: @@@@ @@@; .@@@' ;@@@
  241. @'''@@'''@ #'''. +'''' ;'''#@ :'''#@+''+ ''', ''', @@@@@@# @@@@ @@@+ ,@@@. @@@@
  242. #''''''# @''+ @''+ +'''' @'''''''# ''', ''', #@@@. @@@@ @@@+ @@@ @@@@
  243. @+''+@ '++@ ;++@ '#''@ ##'''@: +++, +++, :@ @@@@ @@@' @@@ '@@@
  244. :' ' '`
  245. fmt.Printf("%v\n\n", cwtch)
  246. quit := false
  247. usr, err := user.Current()
  248. if err != nil {
  249. log.Errorf("\nError: could not load current user: %v\n", err)
  250. os.Exit(1)
  251. }
  252. acn, err := connectivity.StartTor(path.Join(usr.HomeDir, ".cwtch"), "")
  253. if err != nil {
  254. log.Errorf("\nError connecting to Tor: %v\n", err)
  255. os.Exit(1)
  256. }
  257. app = app2.NewApp(acn, path.Join(usr.HomeDir, ".cwtch"))
  258. go handleAppEvents(app.GetPrimaryBus())
  259. if err != nil {
  260. log.Errorf("Error initializing application: %v", err)
  261. os.Exit(1)
  262. }
  263. log.SetLevel(log.LevelDebug)
  264. fmt.Printf("\nWelcome to Cwtch!\n")
  265. fmt.Printf("If this if your first time you should create a profile by running `/new-profile`\n")
  266. fmt.Printf("`/load-profiles` will prompt you for a password and load profiles from storage\n")
  267. fmt.Printf("`/help` will show you other available commands\n")
  268. fmt.Printf("There is full [TAB] completion support\n\n")
  269. var history []string
  270. for !quit {
  271. prmpt = "cwtch> "
  272. if group != nil {
  273. prmpt = fmt.Sprintf("cwtch %v (%v) [%v] say> ", peer.GetProfile().Name, peer.GetProfile().Onion, group.GroupID)
  274. } else if peer != nil {
  275. prmpt = fmt.Sprintf("cwtch %v (%v)> ", peer.GetProfile().Name, peer.GetProfile().Onion)
  276. }
  277. text := prompt.Input(prmpt, completer, prompt.OptionSuggestionBGColor(prompt.Purple),
  278. prompt.OptionDescriptionBGColor(prompt.White),
  279. prompt.OptionPrefixTextColor(prompt.White),
  280. prompt.OptionInputTextColor(prompt.Purple),
  281. prompt.OptionHistory(history))
  282. commands := strings.Split(text[0:], " ")
  283. history = append(history, text)
  284. if peer == nil {
  285. if commands[0] != "/help" && commands[0] != "/quit" && commands[0] != "/new-profile" && commands[0] != "/load-profiles" && commands[0] != "/select-profile" && commands[0] != "/list-profiles" {
  286. fmt.Printf("Profile needs to be set\n")
  287. continue
  288. }
  289. }
  290. // Send
  291. if group != nil && !strings.HasPrefix(commands[0], "/") {
  292. err := peer.SendMessageToGroup(group.GroupID, text)
  293. if err != nil {
  294. fmt.Printf("Error sending message: %v\n", err)
  295. }
  296. }
  297. switch commands[0] {
  298. case "/quit":
  299. quit = true
  300. case "/new-profile":
  301. if len(commands) == 2 {
  302. name := strings.Trim(commands[1], " ")
  303. if name == "" {
  304. fmt.Printf("Error creating profile, usage: %v\n", usages[commands[0]])
  305. break
  306. }
  307. fmt.Print("** WARNING: PASSWORDS CANNOT BE RECOVERED! **\n")
  308. password := ""
  309. failcount := 0
  310. for ; failcount < 3; failcount++ {
  311. fmt.Print("Enter a password to encrypt the profile: ")
  312. bytePassword, _ := terminal.ReadPassword(int(syscall.Stdin))
  313. if string(bytePassword) == "" {
  314. fmt.Print("\nBlank password not allowed.")
  315. continue
  316. }
  317. fmt.Print("\nRe-enter password: ")
  318. bytePassword2, _ := terminal.ReadPassword(int(syscall.Stdin))
  319. if bytes.Equal(bytePassword, bytePassword2) {
  320. password = string(bytePassword)
  321. break
  322. } else {
  323. fmt.Print("\nPASSWORDS DIDN'T MATCH! Try again.\n")
  324. }
  325. }
  326. if failcount >= 3 {
  327. fmt.Printf("Error creating profile for %v: Your password entries must match!\n", name)
  328. } else {
  329. app.CreatePeer(name, password)
  330. }
  331. } else {
  332. fmt.Printf("Error creating New Profile, usage: %s\n", usages[commands[0]])
  333. }
  334. case "/load-profiles":
  335. fmt.Print("Enter a password to decrypt the profile: ")
  336. bytePassword, err := terminal.ReadPassword(int(syscall.Stdin))
  337. if err != nil {
  338. fmt.Printf("\nError loading profiles: %v\n", err)
  339. continue
  340. }
  341. app.LoadProfiles(string(bytePassword))
  342. if err == nil {
  343. } else {
  344. fmt.Printf("\nError loading profiles: %v\n", err)
  345. }
  346. case "/list-profiles":
  347. peerlist := app.ListPeers()
  348. for onion, peername := range peerlist {
  349. fmt.Printf(" %v\t%v\n", onion, peername)
  350. }
  351. case "/select-profile":
  352. if len(commands) == 2 {
  353. p := app.GetPeer(commands[1])
  354. if p == nil {
  355. fmt.Printf("Error: profile '%v' does not exist\n", commands[1])
  356. } else {
  357. stopGroupFollow()
  358. peer = p
  359. suggestions = append(suggestionsBase, suggestionsSelectedProfile...)
  360. }
  361. // Auto cwtchPeer / Join Server
  362. // TODO There are some privacy implications with this that we should
  363. // think over.
  364. for _, name := range p.GetProfile().GetContacts() {
  365. profile := p.GetContact(name)
  366. if profile.Trusted && !profile.Blocked {
  367. p.PeerWithOnion(profile.Onion)
  368. }
  369. }
  370. for _, groupid := range p.GetGroups() {
  371. group := p.GetGroup(groupid)
  372. if group.Accepted || group.Owner == "self" {
  373. p.JoinServer(group.GroupServer)
  374. }
  375. }
  376. } else {
  377. fmt.Printf("Error selecting profile, usage: %s\n", usages[commands[0]])
  378. }
  379. case "/info":
  380. if peer != nil {
  381. fmt.Printf("Address cwtch:%v\n", peer.GetProfile().Onion)
  382. } else {
  383. fmt.Printf("Profile needs to be set\n")
  384. }
  385. case "/invite":
  386. if len(commands) == 2 {
  387. fmt.Printf("Inviting cwtch:%v\n", commands[1])
  388. peer.PeerWithOnion(commands[1])
  389. } else {
  390. fmt.Printf("Error inviting peer, usage: %s\n", usages[commands[0]])
  391. }
  392. /*case "/list-peers":
  393. peers := peer.GetPeers()
  394. for p, s := range peers {
  395. fmt.Printf("Name: %v Status: %v\n", p, connections.ConnectionStateName[s])
  396. }
  397. case "/list-servers":
  398. servers := peer.GetServers()
  399. for s, st := range servers {
  400. fmt.Printf("Name: %v Status: %v\n", s, connections.ConnectionStateName[st])
  401. }*/
  402. case "/list-contacts":
  403. contacts := peer.GetContacts()
  404. for _, onion := range contacts {
  405. c := peer.GetContact(onion)
  406. fmt.Printf("Name: %v Onion: %v Trusted: %v\n", c.Name, c.Onion, c.Trusted)
  407. }
  408. case "/list-groups":
  409. for _, gid := range peer.GetGroups() {
  410. g := peer.GetGroup(gid)
  411. fmt.Printf("Group Id: %v Owner: %v Accepted:%v\n", gid, g.Owner, g.Accepted)
  412. }
  413. case "/trust":
  414. if len(commands) == 2 {
  415. peer.TrustPeer(commands[1])
  416. } else {
  417. fmt.Printf("Error trusting peer, usage: %s\n", usages[commands[0]])
  418. }
  419. case "/block":
  420. if len(commands) == 2 {
  421. peer.BlockPeer(commands[1])
  422. } else {
  423. fmt.Printf("Error blocking peer, usage: %s\n", usages[commands[0]])
  424. }
  425. case "/accept-invite":
  426. if len(commands) == 2 {
  427. groupID := commands[1]
  428. err := peer.AcceptInvite(groupID)
  429. if err != nil {
  430. fmt.Printf("Error: %v\n", err)
  431. } else {
  432. group := peer.GetGroup(groupID)
  433. if group == nil {
  434. fmt.Printf("Error: group does not exist\n")
  435. } else {
  436. peer.JoinServer(group.GroupServer)
  437. }
  438. }
  439. } else {
  440. fmt.Printf("Error accepting invite, usage: %s\n", usages[commands[0]])
  441. }
  442. case "/invite-to-group":
  443. if len(commands) == 3 {
  444. fmt.Printf("Inviting %v to %v\n", commands[1], commands[2])
  445. err := peer.InviteOnionToGroup(commands[2], commands[1])
  446. if err != nil {
  447. fmt.Printf("Error: %v\n", err)
  448. }
  449. } else {
  450. fmt.Printf("Error inviting peer to group, usage: %s\n", usages[commands[0]])
  451. }
  452. case "/new-group":
  453. if len(commands) == 2 && commands[1] != "" {
  454. fmt.Printf("Setting up a new group on server:%v\n", commands[1])
  455. id, _, err := peer.StartGroup(commands[1])
  456. if err == nil {
  457. fmt.Printf("New Group [%v] created for server %v\n", id, commands[1])
  458. group := peer.GetGroup(id)
  459. if group == nil {
  460. fmt.Printf("Error: group does not exist\n")
  461. } else {
  462. peer.JoinServer(group.GroupServer)
  463. }
  464. } else {
  465. fmt.Printf("Error creating new group: %v", err)
  466. }
  467. } else {
  468. fmt.Printf("Error creating a new group, usage: %s\n", usages[commands[0]])
  469. }
  470. case "/select-group":
  471. if len(commands) == 2 {
  472. g := peer.GetGroup(commands[1])
  473. if g == nil {
  474. fmt.Printf("Error: group %s not found!\n", commands[1])
  475. } else {
  476. stopGroupFollow()
  477. group = g
  478. fmt.Printf("--------------- %v ---------------\n", group.GroupID)
  479. gms := group.Timeline.GetMessages()
  480. max := 20
  481. if len(gms) < max {
  482. max = len(gms)
  483. }
  484. for i := len(gms) - max; i < len(gms); i++ {
  485. printMessage(gms[i])
  486. }
  487. fmt.Printf("------------------------------\n")
  488. startGroupFollow()
  489. }
  490. } else {
  491. fmt.Printf("Error selecting a group, usage: %s\n", usages[commands[0]])
  492. }
  493. case "/unselect-group":
  494. stopGroupFollow()
  495. case "/export-group":
  496. if len(commands) == 2 {
  497. group := peer.GetGroup(commands[1])
  498. if group == nil {
  499. fmt.Printf("Error: group does not exist\n")
  500. } else {
  501. invite, _ := peer.ExportGroup(commands[1])
  502. fmt.Printf("Invite: %v\n", invite)
  503. }
  504. } else {
  505. fmt.Printf("Error exporting group, usage: %s\n", usages[commands[0]])
  506. }
  507. case "/import-group":
  508. if len(commands) == 2 {
  509. groupID, err := peer.ImportGroup(commands[1])
  510. if err != nil {
  511. fmt.Printf("Error importing group: %v\n", err)
  512. } else {
  513. fmt.Printf("Imported group: %s\n", groupID)
  514. }
  515. } else {
  516. fmt.Printf("%v", commands)
  517. fmt.Printf("Error importing group, usage: %s\n", usages[commands[0]])
  518. }
  519. case "/help":
  520. for _, command := range suggestions {
  521. fmt.Printf("%-18s%-56s%s\n", command.Text, command.Description, usages[command.Text])
  522. }
  523. case "/sendlots":
  524. if len(commands) == 2 {
  525. group := peer.GetGroup(commands[1])
  526. if group == nil {
  527. fmt.Printf("Error: group does not exist\n")
  528. } else {
  529. for i := 0; i < 100; i++ {
  530. fmt.Printf("Sending message: %v\n", i)
  531. err := peer.SendMessageToGroup(commands[1], fmt.Sprintf("this is message %v", i))
  532. if err != nil {
  533. fmt.Printf("could not send message %v because %v\n", i, err)
  534. }
  535. }
  536. fmt.Printf("Waiting 5 seconds for message to process...\n")
  537. time.Sleep(time.Second * 5)
  538. timeline := group.GetTimeline()
  539. totalLatency := time.Duration(0)
  540. maxLatency := time.Duration(0)
  541. totalMessages := 0
  542. for i := 0; i < 100; i++ {
  543. found := false
  544. for _, m := range timeline {
  545. if m.Message == fmt.Sprintf("this is message %v", i) && m.PeerID == peer.GetProfile().Onion {
  546. found = true
  547. latency := m.Received.Sub(m.Timestamp)
  548. fmt.Printf("Latency for Message %v was %v\n", i, latency)
  549. totalLatency = totalLatency + latency
  550. if maxLatency < latency {
  551. maxLatency = latency
  552. }
  553. totalMessages++
  554. }
  555. }
  556. if !found {
  557. fmt.Printf("message %v was never received\n", i)
  558. }
  559. }
  560. fmt.Printf("Average Latency for %v messages was: %vms\n", totalMessages, time.Duration(int64(totalLatency)/int64(totalMessages)))
  561. fmt.Printf("Max Latency for %v messages was: %vms\n", totalMessages, maxLatency)
  562. }
  563. }
  564. }
  565. }
  566. app.Shutdown()
  567. acn.Close()
  568. os.Exit(0)
  569. }