package main import ( libpeer "cwtch.im/cwtch/peer" "cwtch.im/cwtch/peer/connections" "encoding/base32" "fmt" "github.com/sethvargo/go-diceware/diceware" "github.com/therecipe/qt/core" "github.com/therecipe/qt/quick" "github.com/therecipe/qt/quickcontrols2" "github.com/therecipe/qt/widgets" "os" "os/user" "path" "strings" "time" "strconv" "git.openprivacy.ca/openprivacy/asaur" ) var gcd *GrandCentralDispatcher type ContactManager map[string]*Contact var contactMgr ContactManager var peer libpeer.CwtchPeer var outgoingMessages chan Message type Contact struct { Messages []Message Unread int Status connections.ConnectionState } func (this *Contact) AddMessage(m Message) { this.Messages = append(this.Messages, m) } type Message struct { With, Message string FromMe bool } func DeliverMessageToUI(from, message string, fromMe bool) { _, found := contactMgr[from] if !found { contactMgr[from] = &Contact{[]Message{}, 0, 0} } contactMgr[from].AddMessage(Message{from, message, fromMe}) if gcd.currentOpenConversation == from { if fromMe { from = "me" } gcd.AppendMessage(from, message) } else { contactMgr[from].Unread++ gcd.SetUnread(from, contactMgr[from].Unread) } } func init() { GrandCentralDispatcher_QmlRegisterType2("CustomQmlTypes", 1, 0, "GrandCentralDispatcher") } func main() { contactMgr = make(ContactManager) core.QCoreApplication_SetAttribute(core.Qt__AA_EnableHighDpiScaling, true) widgets.NewQApplication(len(os.Args), os.Args) quickcontrols2.QQuickStyle_SetStyle("Universe") view := quick.NewQQuickView(nil) view.SetResizeMode(quick.QQuickView__SizeRootObjectToView) view.SetTitle("bounce") gcd = NewGrandCentralDispatcher(nil) view.RootContext().SetContextProperty("gcd", gcd) if len(os.Args) == 2 && os.Args[1] == "local" { view.SetSource(core.QUrl_FromLocalFile("./qml/main.qml")) } else { view.SetSource(core.NewQUrl3("qrc:/qml/main.qml", 0)) } outgoingMessages = make(chan Message, 1000) go postmanPat() initialize(view) view.Show() go torStatusPoller() go presencePoller() go ricochetListener() go alice() widgets.QApplication_Exec() } func alice() { i := 0 for { time.Sleep(time.Second * 3) //words, _ := diceware.Generate(3) //DeliverMessageToUI("f76b5vtleqx2puhwgkords34gs6crgbjqud6sebfzwtlrq4ngbqgcsyd", strings.Join(words, " "), i % 3 == 0) i++ } } func presencePoller() { // TODO: make this subscribe-able in ricochet time.Sleep(time.Second * 4) for { contacts := peer.GetContacts() for i := range contacts { _, found := contactMgr[contacts[i]] if !found { // new contact has attempted to connect with us, treat it as an invite contactMgr[contacts[i]] = &Contact{[]Message{}, 0, -1} c, _ := peer.GetProfile().GetContact(contacts[i]) peer.GetProfile().SetCustomAttribute(contacts[i]+"_name", c.Name) peer.GetProfile().SetCustomAttribute(c.Name+"_onion", contacts[i]) peer.Save() gcd.AddContact(c.Name, contacts[i], randomProfileImage(contacts[i]), "0", c.Trusted) } c, found := peer.GetPeers()[contacts[i]] if !found && contactMgr[contacts[i]].Status != -2 { //log.Printf("setting %v to -2", contacts[i]) contactMgr[contacts[i]].Status = -2 gcd.SetConnectionStatus(contacts[i], -2) } else if contactMgr[contacts[i]].Status != c { //log.Printf("was: %v", contactMgr[contacts[i]].Status) contactMgr[contacts[i]].Status = c //log.Printf("setting %v to status %v", contacts[i], c) gcd.SetConnectionStatus(contacts[i], int(c)) //log.Printf("now: %v", contactMgr[contacts[i]].Status) } } time.Sleep(time.Second * 4) } } func torStatusPoller() { for { time.Sleep(time.Second) //todo: this should use a config manager //todo: also, try dialing the proxy to differentiate tor not running vs control port not configured rawStatus, err := asaur.GetInfo("localhost:9051", "tcp4", "", "status/bootstrap-phase") if err != nil { gcd.TorStatus(0, "can't find tor. is it running? is the controlport configured?") continue } status := asaur.ParseBootstrapPhase(rawStatus) progress, _ := strconv.Atoi(status["PROGRESS"]) if status["TAG"] == "done" { gcd.TorStatus(3, "tor appears to be running just fine!") continue } if progress == 0 { gcd.TorStatus(1, "tor is trying to start up") continue } gcd.TorStatus(2, status["SUMMARY"]) //qCwtchApp.SetTorStatusProgress(progress) //qCwtchApp.SetTorStatusSummary(status["SUMMARY"]) //if status["TAG"] == "done" { } } func ricochetListener() { processData := func(onion string, data []byte) []byte { /* _, exists := peer.GetProfile().GetCustomAttribute(onion + "_name") if !exists { for peer.GetContact(onion) == nil { time.Sleep(time.Millisecond * 30) } name := peer.GetContact(onion).Name fmt.Printf("adding new untrusted contact %v <%v>\n", name, onion) peer.GetProfile().SetCustomAttribute(onion+"_name", name) peer.GetProfile().SetCustomAttribute(name+"_onion", onion) peer.Save() gcd.AddContact(name, onion, randomProfileImage(onion), "0", false) } */ DeliverMessageToUI(onion, string(data), false) return nil } peer.SetPeerDataHandler(processData) fmt.Fprintf(os.Stderr, "waiting for messages...\n") err := peer.Listen() if err != nil { fmt.Printf("error listening for connections: %v\n", err) gcd.InvokePopup("error handling network connection") } } func postmanPat() { postOffice := make(map[string]chan Message) for { m := <-outgoingMessages _, found := postOffice[m.With] if !found { postOffice[m.With] = make(chan Message, 100) go andHisBlackAndWhiteCat(postOffice[m.With]) } postOffice[m.With] <- m } } func andHisBlackAndWhiteCat(incomingMessages chan Message) { for { m := <-incomingMessages connection := peer.PeerWithOnion(m.With) connection.SendPacket([]byte(m.Message)) } } func initialize(view *quick.QQuickView) { //TODO: this section is ported over and has a lot of printf errors, need to show them in the ui var dirname, filename string if os.Getenv("SENDAFRIEND_FOLDER") != "" { dirname = os.Getenv("SENDAFRIEND_FOLDER") filename = path.Join(dirname, "identity.private") } else { usr, err := user.Current() if err != nil { fmt.Printf("\nerror: could not load current user: %v\n", err) os.Exit(1) } dirname = path.Join(usr.HomeDir, ".sendafriend") filename = path.Join(dirname, "identity.private") } os.MkdirAll(dirname, 0700) var err error peer, err = libpeer.LoadCwtchPeer(filename, "be gay do crime") if err != nil { fmt.Println("couldn't load your config file, attempting to create a new one now") names, err := diceware.Generate(1) peer, err = libpeer.NewCwtchPeer(names[0], "be gay do crime", filename) if err != nil { fmt.Println("couldn't create one either :( exiting") os.Exit(1) } peer.Save() } gcd.UpdateMyProfile(peer.GetProfile().Name, peer.GetProfile().Onion, randomProfileImage(peer.GetProfile().Onion)) contacts := peer.GetContacts() for i := range contacts { attr, _ := peer.GetProfile().GetCustomAttribute(contacts[i] + "_name") contactMgr[contacts[i]] = &Contact{[]Message{}, 0, 0} gcd.AddContact(attr, contacts[i], randomProfileImage(contacts[i]), "0", peer.GetContact(contacts[i]).Trusted) } } // temporary until we do real picture selection func randomProfileImage(onion string) string { choices := []string{"001-centaur", "002-kraken", "003-dinosaur", "004-tree-1", "005-hand", "006-echidna", "007-robot", "008-mushroom", "009-harpy", "010-phoenix", "011-dragon-1", "012-devil", "013-troll", "014-alien", "015-minotaur", "016-madre-monte", "017-satyr", "018-karakasakozou", "019-pirate", "020-werewolf", "021-scarecrow", "022-valkyrie", "023-curupira", "024-loch-ness-monster", "025-tree", "026-cerberus", "027-gryphon", "028-mermaid", "029-vampire", "030-goblin", "031-yeti", "032-leprechaun", "033-medusa", "034-chimera", "035-elf", "036-hydra", "037-cyclops", "038-pegasus", "039-narwhal", "040-woodcutter", "041-zombie", "042-dragon", "043-frankenstein", "044-witch", "045-fairy", "046-genie", "047-pinocchio", "048-ghost", "049-wizard", "050-unicorn"} barr, err := base32.StdEncoding.DecodeString(strings.ToUpper(onion)) if err != nil || len(barr) != 35 { fmt.Printf("error: %v %v %v\n", onion, err, barr) return "qrc:/qml/images/extra/openprivacy.png" } return "qrc:/qml/images/profiles/" + choices[int(barr[33])%len(choices)] + ".png" }