package gothings import ( "cwtch.im/ui/go/constants" "cwtch.im/ui/go/cwutil" "cwtch.im/cwtch/model" "cwtch.im/ui/go/characters" "cwtch.im/ui/go/gobjects" "cwtch.im/ui/go/the" "encoding/base32" "fmt" "github.com/therecipe/qt/core" "log" "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"` // messages pane stuff _ func(handle, from, displayName, message, image string, mID uint, fromMe bool, ts string) `signal:"AppendMessage"` _ func() `signal:"ClearMessages"` _ func() `signal:"ResetMessagePane"` _ func(mID uint) `signal:"Acknowledged"` // profile-area stuff _ func(name, onion, image string) `signal:"UpdateMyProfile"` _ func(status int, str string) `signal:"TorStatus"` // other stuff i can't ontologize atm _ func(str string) `signal:"InvokePopup"` _ func(name, server, invitation string) `signal:"SupplyGroupSettings"` // signals emitted from the ui (written in go, below) _ func(message string, mid uint) `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() `signal:"requestGroupSettings,auto"` } func (this *GrandCentralDispatcher) sendMessage(message string, mID uint) { 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 { the.Peer.GetGroup(this.CurrentOpenConversation()).Accepted = true the.CwtchApp.SaveProfile(the.Peer) c := this.UIState.GetContact(this.CurrentOpenConversation()) c.Trusted = true this.UIState.UpdateContact(c.Handle) } the.Peer.SendMessageToGroup(this.CurrentOpenConversation(), message) return } // 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()) } select { // 1 weird trick to do a non-blocking send. this means the user can only send a limited number of messages // before the channel buffer fills. TODO: stop the user from sending if the buffer is full case this.OutgoingMessages <- gobjects.Letter{this.CurrentOpenConversation(), message, mID}: default: } this.UIState.AddMessage(&gobjects.Message{ this.CurrentOpenConversation(), "me", "", message, "", true, int(mID), time.Now(), }) } func (this *GrandCentralDispatcher) loadMessagesPane(handle string) { this.ClearMessages() this.SetCurrentOpenConversation(handle) c := this.UIState.GetContact(handle) c.Badge = 0 this.UIState.UpdateContact(handle) if len(handle) == 32 { // LOAD GROUP log.Printf("LOADING GROUP %s", handle) tl := the.Peer.GetGroup(handle).GetTimeline() log.Printf("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 name, exists = the.Peer.GetProfile().GetCustomAttribute(tl[i].PeerID + "_name") if !exists || name == "" { name = tl[i].PeerID[:16] + "..." } this.AppendMessage( handle, tl[i].PeerID, name, tl[i].Message, cwutil.RandomProfileImage(tl[i].PeerID), 0, tl[i].PeerID == the.Peer.GetProfile().Onion, tl[i].Timestamp.Format(constants.TIME_FORMAT), ) } return } // ELSE LOAD CONTACT 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), uint(messages[i].MessageID), messages[i].FromMe, messages[i].Timestamp.Format(constants.TIME_FORMAT), ) } } func (this *GrandCentralDispatcher) requestGroupSettings() { log.Printf("requestGroupSettings()") group := the.Peer.GetGroup(this.CurrentOpenConversation()) nick, _ := group.GetAttribute("nick") invite, _ := the.Peer.ExportGroup(this.CurrentOpenConversation()) this.SupplyGroupSettings(nick, group.GroupServer, invite) } func (this *GrandCentralDispatcher) broadcast(signal string) { switch signal { default: log.Printf("unhandled broadcast signal: %v", signal) case "ResetMessagePane": this.ResetMessagePane() } } func (this *GrandCentralDispatcher) importString(str string) { if len(str) < 5 { log.Printf("ignoring short string") return } log.Printf("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 groupID, err := the.Peer.ImportGroup(str) if err != nil { this.InvokePopup("not a valid group invite") return } group := the.Peer.GetGroup(groupID) the.Peer.JoinServer(group.GroupServer) the.CwtchApp.SaveProfile(the.Peer) this.UIState.AddContact(&gobjects.Contact{ groupID[:12], groupID, cwutil.RandomGroupImage(groupID), group.GroupServer, 0, 0, true, }) fmt.Printf("imported groupid=%s server=%s", groupID, group.GroupServer) 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.Printf("%v", err) this.InvokePopup("bad format. missing characters?") return } _, exists := the.Peer.GetProfile().GetCustomAttribute(name + "_onion") if exists { this.InvokePopup("can't re-use names :(") return } _, exists = the.Peer.GetProfile().GetCustomAttribute(onion + "_name") if exists { this.InvokePopup("already have this contact") return //TODO: bring them to the duplicate } this.UIState.AddContact(&gobjects.Contact{ onion, name, cwutil.RandomProfileImage(onion), "", 0, 0, true, }) } func (this *GrandCentralDispatcher) popup(str string) { this.InvokePopup(str) } func (this *GrandCentralDispatcher) updateNick(nick string) { the.Peer.GetProfile().Name = nick the.CwtchApp.SaveProfile(the.Peer) } 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{ groupID, groupName, cwutil.RandomGroupImage(groupID), server, 0, 0, true, }) group := the.Peer.GetGroup(groupID) group.SetAttribute("nick", groupName) the.CwtchApp.SaveProfile(the.Peer) the.Peer.JoinServer(server) group.NewMessage = make(chan model.Message) go characters.CwtchListener(this.UIState.AddMessage, group.GroupID, group.NewMessage) }