forked from cwtch.im/cwtch
feature complete app
This commit is contained in:
parent
383828512a
commit
eacce2113c
198
app/cli/main.go
198
app/cli/main.go
|
@ -1,33 +1,151 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
||||
"fmt"
|
||||
app2 "git.mascherari.press/cwtch/app"
|
||||
"os"
|
||||
|
||||
"strings"
|
||||
"github.com/c-bata/go-prompt"
|
||||
)
|
||||
|
||||
|
||||
var app app2.Application
|
||||
|
||||
|
||||
func completer(d prompt.Document) []prompt.Suggest {
|
||||
|
||||
s := []prompt.Suggest{}
|
||||
|
||||
if d.FindStartOfPreviousWord() == 0 {
|
||||
s := []prompt.Suggest{
|
||||
{Text: "new-profile", Description: "create a new profile"},
|
||||
{Text: "load-profile", Description: "load a new profile"},
|
||||
{Text: "quit", Description: "quit cwtch"},
|
||||
{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"},
|
||||
}
|
||||
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
|
||||
}
|
||||
|
||||
w := d.CurrentLine()
|
||||
if strings.HasPrefix(w, "send") || strings.HasPrefix(w, "timeline") {
|
||||
s = []prompt.Suggest{}
|
||||
groups := app.Peer.Profile.Groups
|
||||
for _, group := range groups {
|
||||
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, "invite-to-group") {
|
||||
|
||||
if d.FindStartOfPreviousWordWithSpace() == 0 {
|
||||
s = []prompt.Suggest{}
|
||||
contacts := app.Peer.Profile.Contacts
|
||||
for _, contact := range contacts {
|
||||
s = append(s, prompt.Suggest{Text: contact.Onion, Description: contact.Name})
|
||||
}
|
||||
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
|
||||
} else {
|
||||
s = []prompt.Suggest{}
|
||||
groups := app.Peer.Profile.Groups
|
||||
for _, group := range groups {
|
||||
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.Profile.Groups
|
||||
for _, group := range groups {
|
||||
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() {
|
||||
|
||||
quit := false
|
||||
app := app2.Application{}
|
||||
profilefile := ""
|
||||
for !quit {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
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.Profile.Name
|
||||
}
|
||||
fmt.Printf("cwtch [%v]> ", profile)
|
||||
text, _ := reader.ReadString('\n')
|
||||
commands := strings.Split(text[0:len(text)-1], " ")
|
||||
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:len(text)], " ")
|
||||
history = append(history,text)
|
||||
switch commands[0] {
|
||||
case "quit":
|
||||
app.Peer.Save(profilefile)
|
||||
quit = true
|
||||
case "newprofile":
|
||||
case "new-profile":
|
||||
if len(commands) == 3 {
|
||||
err := app.NewProfile(commands[1], commands[2])
|
||||
if err == nil {
|
||||
|
@ -38,7 +156,7 @@ func main() {
|
|||
} else {
|
||||
fmt.Printf("Error creating NewProfile, usage: newprofile [name] [filename]\n")
|
||||
}
|
||||
case "loadprofile":
|
||||
case "load-profile":
|
||||
if len(commands) == 2 {
|
||||
err := app.SetProfile(commands[1])
|
||||
profilefile = commands[1]
|
||||
|
@ -55,7 +173,7 @@ func main() {
|
|||
if app.Peer != nil {
|
||||
fmt.Printf("Address cwtch:%v\n", app.Peer.Profile.Onion)
|
||||
} else {
|
||||
fmt.Printf("Profile needs to be set")
|
||||
fmt.Printf("Profile needs to be set\n")
|
||||
}
|
||||
case "invite":
|
||||
if len(commands) == 2 {
|
||||
|
@ -69,33 +187,75 @@ func main() {
|
|||
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":
|
||||
for _, c := range app.Peer.Profile.Contacts {
|
||||
fmt.Printf("Name: %v, Onion: %v, Trusted: %v\n", c.Name, c.Onion, c.Trusted)
|
||||
}
|
||||
case "groups":
|
||||
for gid, g := range app.Peer.Profile.Groups {
|
||||
fmt.Printf("Group Id: %v, Owner: %v\n", gid, g.Owner)
|
||||
fmt.Printf("Group Id: %v, Owner: %v Accepted:%v \n", gid, g.Owner, g.Accepted)
|
||||
}
|
||||
case "invitetogroup":
|
||||
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 {
|
||||
fmt.Printf("Error accepting invite, usage: accept-invite [groupid]\n")
|
||||
}
|
||||
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", err)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Error inviting peer to group, usage: invitetogroup [onion] [groupid]\n")
|
||||
}
|
||||
case "newgroup":
|
||||
case "new-group":
|
||||
if len(commands) == 2 {
|
||||
fmt.Printf("Setting up a new group on server:%v\n", commands[1])
|
||||
id, _ := app.Peer.Profile.StartGroup(commands[1])
|
||||
fmt.Printf("New Group [%v] created for server %v", id, commands[1])
|
||||
fmt.Printf("New Group [%v] created for server %v\n", id, commands[1])
|
||||
app.Peer.Save(profilefile)
|
||||
} else {
|
||||
fmt.Printf("Error inviting peer, usage: newgroup [server]\n")
|
||||
}
|
||||
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: send [groupid] [message]\n")
|
||||
}
|
||||
case "timeline":
|
||||
if len(commands) == 2 {
|
||||
group := app.Peer.Profile.GetGroupByGroupId(commands[1])
|
||||
if group == nil {
|
||||
fmt.Printf("Error: group does not exist\n")
|
||||
} else {
|
||||
for _, m := range group.Timeline.Messages {
|
||||
verified := "not-verified"
|
||||
if m.Verified {
|
||||
verified = "verified"
|
||||
}
|
||||
fmt.Printf("%v %v: %v [%s]\n", m.Timestamp, m.PeerID, m.Message, verified)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Error reading timeline from group, usage: timeline [groupid]\n")
|
||||
}
|
||||
case "save":
|
||||
app.Peer.Save(profilefile)
|
||||
}
|
||||
|
|
|
@ -58,6 +58,14 @@ func (t Timeline) Less(i, j int) bool {
|
|||
|
||||
func (t *Timeline) Insert(mi *Message) {
|
||||
t.lock.Lock()
|
||||
|
||||
for _,m := range t.Messages {
|
||||
if compareSignatures(m.Signature, mi.Signature) {
|
||||
t.lock.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
t.Messages = append(t.Messages, *mi)
|
||||
sort.Sort(t)
|
||||
t.lock.Unlock()
|
||||
|
|
|
@ -81,6 +81,12 @@ func (p *Profile) AddContact(onion string, profile PublicProfile) {
|
|||
|
||||
// VerifyMessage confirms the authenticity of a message given an onion, message and signature.
|
||||
func (p *Profile) VerifyGroupMessage(onion string, groupID string, message string, timestamp int32, signature []byte) bool {
|
||||
|
||||
if onion == p.Onion {
|
||||
m := message + groupID + strconv.Itoa(int(timestamp))
|
||||
return ed25519.Verify(p.Ed25519PublicKey, []byte(m), signature)
|
||||
}
|
||||
|
||||
contact, found := p.Contacts[onion]
|
||||
if found {
|
||||
m := message + groupID + strconv.Itoa(int(timestamp))
|
||||
|
|
|
@ -22,19 +22,28 @@ func NewConnectionsManager() *Manager {
|
|||
|
||||
func (m *Manager) ManagePeerConnection(host string, profile *model.Profile) {
|
||||
m.lock.Lock()
|
||||
|
||||
|
||||
_,exists := m.peerConnections[host]
|
||||
if !exists {
|
||||
ppc := NewPeerPeerConnection(host, profile)
|
||||
go ppc.Run()
|
||||
m.peerConnections[host] = ppc
|
||||
}
|
||||
m.lock.Unlock()
|
||||
|
||||
}
|
||||
|
||||
func (m *Manager) ManageServerConnection(host string, handler func(string, *protocol.GroupMessage)) {
|
||||
m.lock.Lock()
|
||||
|
||||
_,exists := m.serverConnections[host]
|
||||
if !exists {
|
||||
psc := NewPeerServerConnection(host)
|
||||
go psc.Run()
|
||||
psc.GroupMessageHandler = handler
|
||||
m.serverConnections[host] = psc
|
||||
}
|
||||
m.lock.Unlock()
|
||||
}
|
||||
|
||||
|
@ -48,6 +57,17 @@ func (m *Manager) GetPeers() map[string]ConnectionState {
|
|||
return rm
|
||||
}
|
||||
|
||||
|
||||
func (m *Manager) GetServers() map[string]ConnectionState {
|
||||
rm := make(map[string]ConnectionState)
|
||||
m.lock.Lock()
|
||||
for onion, psc := range m.serverConnections {
|
||||
rm[onion] = psc.GetState()
|
||||
}
|
||||
m.lock.Unlock()
|
||||
return rm
|
||||
}
|
||||
|
||||
func (m *Manager) GetPeerPeerConnectionForOnion(host string) (ppc *PeerPeerConnection) {
|
||||
m.lock.Lock()
|
||||
ppc = m.peerConnections[host]
|
||||
|
|
|
@ -46,6 +46,11 @@ func (cp *CwtchPeer) setup() {
|
|||
cp.PeerWithOnion(onion)
|
||||
}
|
||||
|
||||
for _,group := range cp.Profile.Groups {
|
||||
if group.Accepted || group.Owner == "self" {
|
||||
cp.JoinServer(group.GroupServer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewCwtchPeer(name string) *CwtchPeer {
|
||||
|
@ -87,6 +92,7 @@ func (cp *CwtchPeer) InviteOnionToGroup(onion string, groupid string) error {
|
|||
ppc := cp.connectionsManager.GetPeerPeerConnectionForOnion(onion)
|
||||
fmt.Printf("Got connection for group: %v - Sending Invite\n", ppc)
|
||||
ppc.SendGroupInvite(invite)
|
||||
return nil
|
||||
}
|
||||
return errors.New("group id could not be found")
|
||||
}
|
||||
|
@ -99,20 +105,46 @@ func (cp *CwtchPeer) JoinServer(onion string) {
|
|||
cp.connectionsManager.ManageServerConnection(onion, cp.ReceiveGroupMessage)
|
||||
}
|
||||
|
||||
func (cp *CwtchPeer) SendMessageToGroup(groupid string, message string) {
|
||||
func (cp *CwtchPeer) SendMessageToGroup(groupid string, message string) error {
|
||||
group := cp.Profile.GetGroupByGroupId(groupid)
|
||||
if group == nil {
|
||||
return errors.New("group does not exit")
|
||||
}
|
||||
psc := cp.connectionsManager.GetPeerServerConnectionForOnion(group.GroupServer)
|
||||
ct := cp.Profile.EncryptMessageToGroup(message, groupid)
|
||||
gm := &protocol.GroupMessage{
|
||||
Ciphertext: ct,
|
||||
}
|
||||
psc.SendGroupMessage(gm)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *CwtchPeer) GetPeers() map[string]connections.ConnectionState {
|
||||
return cp.connectionsManager.GetPeers()
|
||||
}
|
||||
|
||||
func (cp *CwtchPeer) GetServers() map[string]connections.ConnectionState {
|
||||
return cp.connectionsManager.GetServers()
|
||||
}
|
||||
|
||||
func (cp *CwtchPeer) AcceptInvite(groupID string) error {
|
||||
g := cp.Profile.GetGroupByGroupId(groupID)
|
||||
if g == nil {
|
||||
return errors.New("group invite does not exit")
|
||||
}
|
||||
g.Accepted = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *CwtchPeer) RejectInvite(groupID string) error {
|
||||
g := cp.Profile.GetGroupByGroupId(groupID)
|
||||
if g == nil {
|
||||
return errors.New("group invite does not exit")
|
||||
}
|
||||
g.Accepted = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *CwtchPeer) Listen() error {
|
||||
cwtchpeer := new(application.RicochetApplication)
|
||||
l, err := application.SetupOnion("127.0.0.1:9051", "tcp4", "", cp.Profile.OnionPrivateKey, 9878)
|
||||
|
|
Loading…
Reference in New Issue