package gothings import ( "cwtch.im/cwtch/event" "cwtch.im/ui/go/constants" "cwtch.im/ui/go/cwutil" "cwtch.im/ui/go/gobjects" "cwtch.im/ui/go/the" "encoding/base32" "git.openprivacy.ca/openprivacy/libricochet-go/log" "github.com/therecipe/qt/core" "strings" "time" ) type GrandCentralDispatcher struct { core.QObject OutgoingMessages chan gobjects.Letter UIState InterfaceState _ string `property:"currentOpenConversation"` _ float32 `property:"themeScale"` // contact list stuff _ func(handle, displayName, image, server string, badge, status int, trusted bool) `signal:"AddContact"` _ func(handle, displayName, image, server string, badge, status int, trusted bool) `signal:"UpdateContact"` _ func(handle, key, value string) `signal:"UpdateContactAttribute"` // messages pane stuff _ func(handle, from, displayName, message, image string, mID string, fromMe bool, ts string, ackd bool, error bool) `signal:"AppendMessage"` _ func() `signal:"ClearMessages"` _ func() `signal:"ResetMessagePane"` _ func(mID string) `signal:"Acknowledged"` _ func(title string) `signal:"SetToolbarTitle"` _ func(signature string, err string) `signal:"GroupSendError"` // profile-area stuff _ func(name, onion, image string) `signal:"UpdateMyProfile"` _ func(status int, str string) `signal:"TorStatus"` // settings helpers _ func(str string) `signal:"InvokePopup"` _ func(groupID, name, server, invitation string, accepted bool, addrbooknames, addrbookaddrs []string) `signal:"SupplyGroupSettings"` _ func(onion, nick string) `signal:"SupplyPeerSettings"` // signals emitted from the ui (written in go, below) _ func(message string, mid string) `signal:"sendMessage,auto"` _ func(onion string) `signal:"loadMessagesPane,auto"` _ func(signal string) `signal:"broadcast,auto"` // convenience relay signal _ func(str string) `signal:"importString,auto"` _ func(str string) `signal:"popup,auto"` _ func(nick string) `signal:"updateNick,auto"` _ func(server, groupName string) `signal:"createGroup,auto"` _ func(groupID string) `signal:"leaveGroup,auto"` _ func(groupID string) `signal:"acceptGroup,auto"` _ func(groupID string) `signal:"requestGroupSettings,auto"` _ func(groupID, nick string) `signal:"saveGroupSettings,auto"` _ func() `signal:"requestPeerSettings,auto"` _ func(onion, nick string) `signal:"savePeerSettings,auto"` _ func(onion, groupID string) `signal:"inviteToGroup,auto"` _ func(onion, key, nick string) `signal:"setAttribute,auto"` } func (this *GrandCentralDispatcher) sendMessage(message string, mID string) { if len(message) > 65530 { this.InvokePopup("message is too long") return } if this.CurrentOpenConversation() == "" { this.InvokePopup("ui error") return } if len(this.CurrentOpenConversation()) == 32 { // SEND TO GROUP if !the.Peer.GetGroup(this.CurrentOpenConversation()).Accepted { err := the.Peer.AcceptInvite(this.CurrentOpenConversation()) if err != nil { log.Errorf("tried to mark a nonexistent group as existed. bad!") return } c := this.UIState.GetContact(this.CurrentOpenConversation()) c.Trusted = true this.UIState.UpdateContact(c.Handle) } var err error mID,err = the.Peer.SendMessageToGroupTracked(this.CurrentOpenConversation(), message) this.UIState.AddMessage(&gobjects.Message{ this.CurrentOpenConversation(), "me", "", message, "", true, mID, time.Now(), false, false, }) if err != nil { this.InvokePopup("failed to send message " +err.Error()) return } } else { // TODO: require explicit invite accept/reject instead of implicitly trusting on send if !this.UIState.GetContact(this.CurrentOpenConversation()).Trusted { this.UIState.GetContact(this.CurrentOpenConversation()).Trusted = true this.UIState.UpdateContact(this.CurrentOpenConversation()) } to := this.CurrentOpenConversation(); the.Peer.PeerWithOnion(to) mID = the.Peer.SendMessageToPeer(to, message) this.UIState.AddMessage(&gobjects.Message{ to, "me", "", message, "", true, mID, time.Now(), false, false, }) ackID := new(the.AckId) ackID.ID = mID ackID.Ack = false the.AcknowledgementIDs[to] = append(the.AcknowledgementIDs[to], ackID) } } func (this *GrandCentralDispatcher) loadMessagesPane(handle string) { go this.loadMessagesPaneHelper(handle) } func (this *GrandCentralDispatcher) loadMessagesPaneHelper(handle string) { this.ClearMessages() this.SetCurrentOpenConversation(handle) c := this.UIState.GetContact(handle) if c == nil { this.UIState.AddContact(&gobjects.Contact{ handle, handle, cwutil.RandomProfileImage(handle), "", 0, 0, false, }) } else { c.Badge = 0 this.UIState.UpdateContact(handle) } if len(handle) == 32 { // LOAD GROUP log.Debugf("LOADING GROUP %s", handle) group := the.Peer.GetGroup(handle) tl := group.GetTimeline() nick, _ := group.GetAttribute("nick") if nick == "" { this.SetToolbarTitle(handle) } else { this.SetToolbarTitle(nick) } log.Debugf("messages: %d", len(tl)) for i := range tl { if tl[i].PeerID == the.Peer.GetProfile().Onion { handle = "me" } else { handle = tl[i].PeerID } var name string var exists bool ctc := the.Peer.GetContact(tl[i].PeerID) if ctc != nil { name, exists = ctc.GetAttribute("nick") if !exists || name == "" { name = tl[i].PeerID[:16] + "..." } } this.AppendMessage( handle, tl[i].PeerID, name, tl[i].Message, cwutil.RandomProfileImage(tl[i].PeerID), string(tl[i].Signature), tl[i].PeerID == the.Peer.GetProfile().Onion, tl[i].Timestamp.Format(constants.TIME_FORMAT), tl[i].Received.Equal(time.Unix(0,0)) == false, // If the received timestamp is epoch, we have not yet received this message through an active server false, ) } return } // ELSE LOAD CONTACT contact, _ := the.Peer.GetProfile().GetContact(handle) if contact != nil { nick, _ := contact.GetAttribute("nick") if nick == "" { this.SetToolbarTitle(handle) } else { this.SetToolbarTitle(nick) } } messages := this.UIState.GetMessages(handle) for i := range messages { from := messages[i].From if messages[i].FromMe { from = "me" } this.AppendMessage( messages[i].Handle, from, messages[i].DisplayName, messages[i].Message, cwutil.RandomProfileImage(handle), messages[i].MessageID, messages[i].FromMe, messages[i].Timestamp.Format(constants.TIME_FORMAT), false, messages[i].Error, ) for _,id := range the.AcknowledgementIDs[messages[i].Handle] { if id.ID == messages[i].MessageID && id.Ack && !id.Error { this.Acknowledged(id.ID) } } } } func (this *GrandCentralDispatcher) requestPeerSettings() { contact := the.Peer.GetContact(this.CurrentOpenConversation()) if contact == nil { log.Errorf("error: requested settings for unknown contact %v?", this.CurrentOpenConversation()) this.SupplyPeerSettings(this.CurrentOpenConversation(), this.CurrentOpenConversation()) return } name, exists := contact.GetAttribute("nick") if !exists { log.Errorf("error: couldn't find contact %v", this.CurrentOpenConversation()) this.SupplyPeerSettings(this.CurrentOpenConversation(), this.CurrentOpenConversation()) return } this.SupplyPeerSettings(contact.Onion, name) } func (this *GrandCentralDispatcher) savePeerSettings(onion, nick string) { contact := the.Peer.GetContact(onion) if contact == nil { log.Errorf("error: tried to save settings for unknown peer %v", onion) return } contact.SetAttribute("nick", nick) the.CwtchApp.EventBus().Publish(event.NewEvent(event.SetPeerAttribute, map[event.Field]string{ event.RemotePeer: onion, event.Key: "nick", event.Data: nick, })) this.UIState.contacts[onion].DisplayName = nick this.UIState.UpdateContact(onion) } func (this *GrandCentralDispatcher) requestGroupSettings(groupID string) { group := the.Peer.GetGroup(groupID) if group == nil { log.Errorf("couldn't find group %v", groupID) return } nick, _ := group.GetAttribute("nick") invite, _ := the.Peer.ExportGroup(groupID) contactaddrs := the.Peer.GetContacts() contactnames := make([]string, len(contactaddrs)) for i, contact := range contactaddrs { name, hasname := the.Peer.GetContact(contact).GetAttribute("nick") if hasname { contactnames[i] = name } else { contactnames[i] = contact } } this.SupplyGroupSettings(group.GroupID, nick, group.GroupServer, invite, group.Accepted, contactnames, contactaddrs) } func (this *GrandCentralDispatcher) saveGroupSettings(groupID, nick string) { group := the.Peer.GetGroup(groupID) if group == nil { log.Errorf("couldn't find group %v", groupID) return } group.SetAttribute("nick", nick) the.CwtchApp.EventBus().Publish(event.NewEvent(event.SetGroupAttribute, map[event.Field]string{ event.GroupID: groupID, event.Key: "nick", event.Data: nick, })) this.UIState.contacts[groupID].DisplayName = nick this.UIState.UpdateContact(groupID) } func (this *GrandCentralDispatcher) broadcast(signal string) { switch signal { default: log.Debugf("unhandled broadcast signal: %v", signal) case "ResetMessagePane": this.ResetMessagePane() } } func (this *GrandCentralDispatcher) importString(str string) { if len(str) < 5 { log.Debugf("ignoring short string") return } log.Debugf("importing: %s\n", str) onion := str name := onion str = strings.TrimSpace(str) //eg: torv3JFDWkXExBsZLkjvfkkuAxHsiLGZBk0bvoeJID9ItYnU=EsEBCiBhOWJhZDU1OTQ0NWI3YmM2N2YxYTM5YjkzMTNmNTczNRIgpHeNaG+6jy750eDhwLO39UX4f2xs0irK/M3P6mDSYQIaOTJjM2ttb29ibnlnaGoyenc2cHd2N2Q1N3l6bGQ3NTNhdW8zdWdhdWV6enB2ZmFrM2FoYzRiZHlkCiJAdVSSVgsksceIfHe41OJu9ZFHO8Kwv3G6F5OK3Hw4qZ6hn6SiZjtmJlJezoBH0voZlCahOU7jCOg+dsENndZxAA== if str[0:5] == "torv3" { // GROUP INVITE _, err := the.Peer.ImportGroup(str) if err != nil { this.InvokePopup("not a valid group invite") return } return } if strings.Contains(str, " ") { // usually people prepend spaces and we don't want it going into the name (use ~ for that) parts := strings.Split(strings.TrimSpace(str), " ") str = parts[len(parts)-1] } if strings.Contains(str, "~") { parts := strings.Split(str, "~") onion = parts[len(parts)-1] name = strings.Join(parts[:len(parts)-1], " ") } if len(onion) != 56 { this.InvokePopup("invalid format") return } name = strings.TrimSpace(name) if name == "" { this.InvokePopup("empty name") return } if len(name) > 32 { name = name[:32] //TODO: better strategy for long names? } _, err := base32.StdEncoding.DecodeString(strings.ToUpper(onion[:56])) if err != nil { log.Debugln(err) this.InvokePopup("bad format. missing characters?") return } checkc := the.Peer.GetContact(onion) if checkc != nil { deleted,_ := checkc.GetAttribute("deleted") if deleted != "deleted" { this.InvokePopup("already have this contact") return //TODO: bring them to the duplicate } this.SetAttribute(onion, "deleted", "") } this.UIState.AddContact(&gobjects.Contact{ Handle: onion, DisplayName: name, Image: cwutil.RandomProfileImage(onion), Trusted: true, }) } func (this *GrandCentralDispatcher) popup(str string) { this.InvokePopup(str) } func (this *GrandCentralDispatcher) updateNick(nick string) { the.Peer.GetProfile().Name = nick the.CwtchApp.EventBus().Publish(event.NewEvent(event.SetProfileName, map[event.Field]string{ event.ProfileName: nick, })) } func (this *GrandCentralDispatcher) createGroup(server, groupName string) { groupID, _, err := the.Peer.StartGroup(server) if err != nil { this.popup("group creation failed :(") return } this.UIState.AddContact(&gobjects.Contact{ Handle: groupID, DisplayName: groupName, Image: cwutil.RandomGroupImage(groupID), Server: server, Trusted: true, }) group := the.Peer.GetGroup(groupID) group.SetAttribute("nick", groupName) the.CwtchApp.EventBus().Publish(event.NewEvent(event.SetGroupAttribute, map[event.Field]string{ event.GroupID: groupID, event.Key: "nick", event.Data: groupName, })) the.Peer.JoinServer(server) } func (this *GrandCentralDispatcher) inviteToGroup(onion, groupID string) { err := the.Peer.InviteOnionToGroup(onion, groupID) if err != nil { log.Errorf("inviting %v to %v: %v", onion, groupID, err) } } func (this *GrandCentralDispatcher) leaveGroup(groupID string) { the.CwtchApp.EventBus().Publish(event.NewEvent(event.SetGroupAttribute, map[event.Field]string{ event.GroupID: groupID, event.Key: "deleted", event.Data: "deleted", })) this.UIState.UpdateContactAttribute(groupID, "deleted", "deleted") } func (this *GrandCentralDispatcher) acceptGroup(groupID string) { if the.Peer.GetGroup(groupID) != nil { the.Peer.AcceptInvite(groupID) the.Peer.JoinServer(the.Peer.GetGroup(groupID).GroupServer) this.UIState.UpdateContact(groupID) } } func (this *GrandCentralDispatcher) setAttribute(onion, key, value string) { pp,_ := the.Peer.GetProfile().GetContact(onion) if pp != nil { pp.SetAttribute(key, value) the.CwtchApp.EventBus().Publish(event.NewEvent(event.SetPeerAttribute, map[event.Field]string{ event.RemotePeer: onion, event.Key: key, event.Data: value, })) this.UIState.UpdateContactAttribute(onion, key, value) } }