2018-10-23 18:52:13 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
libpeer "cwtch.im/cwtch/peer"
|
2018-10-25 00:13:03 +00:00
|
|
|
"cwtch.im/cwtch/peer/connections"
|
2018-10-23 18:52:13 +00:00
|
|
|
"encoding/base32"
|
2018-10-25 00:13:03 +00:00
|
|
|
"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"
|
2018-10-23 18:52:13 +00:00
|
|
|
"strings"
|
2018-10-25 00:13:03 +00:00
|
|
|
"time"
|
|
|
|
"strconv"
|
|
|
|
"git.openprivacy.ca/openprivacy/asaur"
|
2018-10-28 02:49:14 +00:00
|
|
|
"git.openprivacy.ca/openprivacy/libricochet-go/application"
|
|
|
|
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
|
|
|
"log"
|
|
|
|
"cwtch.im/cwtch/model"
|
|
|
|
"encoding/hex"
|
2018-10-30 18:43:51 +00:00
|
|
|
"cwtch.im/cwtch/app"
|
2018-10-28 02:49:14 +00:00
|
|
|
)
|
2018-10-23 18:52:13 +00:00
|
|
|
|
|
|
|
var gcd *GrandCentralDispatcher
|
2018-10-25 00:13:03 +00:00
|
|
|
|
2018-10-23 18:52:13 +00:00
|
|
|
type ContactManager map[string]*Contact
|
2018-10-25 00:13:03 +00:00
|
|
|
|
2018-10-23 18:52:13 +00:00
|
|
|
var contactMgr ContactManager
|
|
|
|
var peer libpeer.CwtchPeer
|
2018-10-25 00:13:03 +00:00
|
|
|
var outgoingMessages chan Message
|
2018-10-23 18:52:13 +00:00
|
|
|
|
2018-10-28 02:49:14 +00:00
|
|
|
// need this to translate from the message IDs the UI uses to the ones the acknowledgement system uses on the wire
|
|
|
|
var acknowledgementIDs map[uint32]uint
|
|
|
|
|
2018-10-23 18:52:13 +00:00
|
|
|
type Contact struct {
|
|
|
|
Messages []Message
|
2018-10-25 00:13:03 +00:00
|
|
|
Unread int
|
|
|
|
Status connections.ConnectionState
|
2018-10-23 18:52:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (this *Contact) AddMessage(m Message) {
|
|
|
|
this.Messages = append(this.Messages, m)
|
|
|
|
}
|
|
|
|
|
|
|
|
type Message struct {
|
|
|
|
With, Message string
|
2018-10-25 00:13:03 +00:00
|
|
|
FromMe bool
|
2018-10-28 02:49:14 +00:00
|
|
|
MessageID uint
|
|
|
|
Timestamp time.Time
|
2018-10-23 18:52:13 +00:00
|
|
|
}
|
|
|
|
|
2018-10-29 18:00:21 +00:00
|
|
|
func DeliverMessageToUI(handle, from, displayname, message string, mID uint, fromMe bool, ts time.Time) {
|
|
|
|
_, found := contactMgr[handle]
|
2018-10-23 18:52:13 +00:00
|
|
|
if !found {
|
2018-10-29 18:00:21 +00:00
|
|
|
contactMgr[handle] = &Contact{[]Message{}, 0, 0}
|
2018-10-23 18:52:13 +00:00
|
|
|
}
|
|
|
|
|
2018-10-29 18:00:21 +00:00
|
|
|
contactMgr[handle].AddMessage(Message{from, message, fromMe, mID, ts})
|
|
|
|
if gcd.currentOpenConversation == handle {
|
2018-10-23 18:52:13 +00:00
|
|
|
if fromMe {
|
|
|
|
from = "me"
|
|
|
|
}
|
2018-10-29 18:00:21 +00:00
|
|
|
gcd.AppendMessage(from, message, displayname, mID, ts.Format(TIME_FORMAT), randomProfileImage(from))
|
2018-10-23 18:52:13 +00:00
|
|
|
} else {
|
2018-10-29 18:00:21 +00:00
|
|
|
contactMgr[handle].Unread++
|
|
|
|
gcd.SetUnread(handle, contactMgr[handle].Unread)
|
2018-10-23 18:52:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
GrandCentralDispatcher_QmlRegisterType2("CustomQmlTypes", 1, 0, "GrandCentralDispatcher")
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
contactMgr = make(ContactManager)
|
2018-10-28 02:49:14 +00:00
|
|
|
acknowledgementIDs = make(map[uint32]uint)
|
2018-10-23 18:52:13 +00:00
|
|
|
|
|
|
|
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)
|
2018-10-29 18:00:21 +00:00
|
|
|
view.SetMinimumHeight(280)
|
|
|
|
view.SetMinimumWidth(300)
|
|
|
|
view.SetTitle("cwtch")
|
2018-10-23 18:52:13 +00:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
2018-10-25 00:13:03 +00:00
|
|
|
outgoingMessages = make(chan Message, 1000)
|
|
|
|
go postmanPat()
|
|
|
|
|
2018-10-23 18:52:13 +00:00
|
|
|
initialize(view)
|
|
|
|
view.Show()
|
2018-10-25 00:13:03 +00:00
|
|
|
go torStatusPoller()
|
2018-10-23 18:52:13 +00:00
|
|
|
go presencePoller()
|
2018-10-28 02:49:14 +00:00
|
|
|
go groupPoller()
|
2018-10-23 18:52:13 +00:00
|
|
|
go ricochetListener()
|
|
|
|
widgets.QApplication_Exec()
|
|
|
|
}
|
|
|
|
|
2018-10-28 02:49:14 +00:00
|
|
|
func groupPoller() {
|
2018-10-23 18:52:13 +00:00
|
|
|
for {
|
2018-10-28 02:49:14 +00:00
|
|
|
time.Sleep(time.Second * 4)
|
|
|
|
|
|
|
|
servers := peer.GetServers()
|
|
|
|
groups := peer.GetGroups()
|
|
|
|
for i := range groups {
|
|
|
|
group := peer.GetGroup(groups[i])
|
2018-10-29 18:00:21 +00:00
|
|
|
//log.Printf("setting group %s to status %d", groups[i], int(servers[group.GroupServer]))
|
2018-10-28 02:49:14 +00:00
|
|
|
gcd.SetConnectionStatus(groups[i], int(servers[group.GroupServer]))
|
|
|
|
}
|
2018-10-23 18:52:13 +00:00
|
|
|
}
|
|
|
|
}
|
2018-10-29 18:00:21 +00:00
|
|
|
|
2018-10-23 18:52:13 +00:00
|
|
|
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()
|
2018-10-28 02:49:14 +00:00
|
|
|
gcd.AddContact(c.Name, contacts[i], "", randomProfileImage(contacts[i]), "0", c.Trusted)
|
2018-10-23 18:52:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-25 00:13:03 +00:00
|
|
|
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"])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-28 02:49:14 +00:00
|
|
|
func cwtchListener(groupID string, channel chan model.Message) {
|
|
|
|
for {
|
|
|
|
m := <-channel
|
2018-10-29 18:00:21 +00:00
|
|
|
log.Printf("GROUPMSG %s %s", m.Message, m.PeerID)
|
|
|
|
name := m.PeerID
|
|
|
|
if name == peer.GetProfile().Onion {
|
|
|
|
name = "me"
|
|
|
|
} else {
|
|
|
|
var exists bool // lol this is a golang antifeature
|
|
|
|
name, exists = peer.GetProfile().GetCustomAttribute(m.PeerID + "_name")
|
|
|
|
if !exists {
|
|
|
|
name = ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if name == "" {
|
|
|
|
name = m.PeerID[:16] + "..."
|
|
|
|
}
|
|
|
|
DeliverMessageToUI(groupID, m.PeerID, name, m.Message, 0, m.PeerID == peer.GetProfile().Onion, m.Timestamp)
|
|
|
|
peer.Save()
|
2018-10-23 18:52:13 +00:00
|
|
|
}
|
2018-10-28 02:49:14 +00:00
|
|
|
}
|
2018-10-23 18:52:13 +00:00
|
|
|
|
2018-10-28 02:49:14 +00:00
|
|
|
func ricochetListener() {
|
2018-10-23 18:52:13 +00:00
|
|
|
err := peer.Listen()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("error listening for connections: %v\n", err)
|
|
|
|
gcd.InvokePopup("error handling network connection")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-25 00:13:03 +00:00
|
|
|
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)
|
2018-10-28 02:49:14 +00:00
|
|
|
connection.DoOnChannel("im.ricochet.chat", channels.Outbound, func(channel *channels.Channel) {
|
|
|
|
chatchannel, ok := channel.Handler.(*channels.ChatChannel)
|
|
|
|
if ok {
|
|
|
|
log.Printf("Sending packet")
|
|
|
|
acknowledgementIDs[chatchannel.SendMessage(m.Message)] = m.MessageID
|
|
|
|
}
|
|
|
|
})
|
2018-10-25 00:13:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-23 18:52:13 +00:00
|
|
|
func initialize(view *quick.QQuickView) {
|
2018-10-30 19:48:37 +00:00
|
|
|
var err error
|
2018-10-23 18:52:13 +00:00
|
|
|
//TODO: this section is ported over and has a lot of printf errors, need to show them in the ui
|
|
|
|
var dirname, filename string
|
2018-10-29 18:00:21 +00:00
|
|
|
if os.Getenv("CWTCH_FOLDER") != "" {
|
|
|
|
dirname = os.Getenv("CWTCH_FOLDER")
|
|
|
|
filename = path.Join(dirname, "keep-this-file-private")
|
2018-10-23 18:52:13 +00:00
|
|
|
} else {
|
|
|
|
usr, err := user.Current()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("\nerror: could not load current user: %v\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2018-10-29 18:00:21 +00:00
|
|
|
dirname = path.Join(usr.HomeDir, ".cwtch")
|
|
|
|
filename = path.Join(dirname, "keep-this-file-private")
|
2018-10-23 18:52:13 +00:00
|
|
|
}
|
|
|
|
|
2018-10-30 19:48:37 +00:00
|
|
|
/*_, err := app2.NewApp(dirname, "/data/data/org.qtproject.example.go/lib/libtor.so")
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("ERROR CREATING CWTCH APP: %v", err)
|
|
|
|
}
|
|
|
|
time.Sleep(time.Second * 10)
|
|
|
|
*/
|
2018-10-23 18:52:13 +00:00
|
|
|
os.MkdirAll(dirname, 0700)
|
2018-10-30 18:43:51 +00:00
|
|
|
|
2018-10-30 19:50:41 +00:00
|
|
|
_, err = app.NewApp("/data/data/org.qtproject.example.go/files/", "/data/data/org.qtproject.example.go/lib/libtor.so")
|
2018-10-30 18:43:51 +00:00
|
|
|
log.Printf("starting Swtch app: err: %v\n", err)
|
|
|
|
|
2018-10-23 18:52:13 +00:00
|
|
|
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))
|
|
|
|
|
2018-10-28 02:49:14 +00:00
|
|
|
aif := application.ApplicationInstanceFactory{}
|
|
|
|
aif.Init()
|
|
|
|
app := new(application.RicochetApplication)
|
|
|
|
aif.AddHandler("im.ricochet.chat", func(rai *application.ApplicationInstance) func() channels.Handler {
|
|
|
|
ccl := new(ChatChannelListener)
|
|
|
|
ccl.Init(rai, app)
|
|
|
|
return func() channels.Handler {
|
|
|
|
chat := new(channels.ChatChannel)
|
|
|
|
chat.Handler = ccl
|
|
|
|
return chat
|
|
|
|
}
|
|
|
|
})
|
|
|
|
peer.SetApplicationInstanceFactory(aif)
|
|
|
|
|
2018-10-23 18:52:13 +00:00
|
|
|
contacts := peer.GetContacts()
|
|
|
|
for i := range contacts {
|
|
|
|
attr, _ := peer.GetProfile().GetCustomAttribute(contacts[i] + "_name")
|
|
|
|
contactMgr[contacts[i]] = &Contact{[]Message{}, 0, 0}
|
2018-10-28 02:49:14 +00:00
|
|
|
gcd.AddContact(attr, contacts[i], "", randomProfileImage(contacts[i]), "0", peer.GetContact(contacts[i]).Trusted)
|
|
|
|
}
|
|
|
|
|
|
|
|
groups := peer.GetGroups()
|
|
|
|
for i := range groups {
|
|
|
|
group := peer.GetGroup(groups[i])
|
|
|
|
group.NewMessage = make(chan model.Message)
|
|
|
|
go cwtchListener(groups[i], group.NewMessage)
|
|
|
|
peer.JoinServer(group.GroupServer)
|
|
|
|
//TODO: base the profileImage off the groupid, not the server. probably by decoding it and then re-encoding it b32
|
|
|
|
gcd.AddContact(group.GroupID[:12], group.GroupID, group.GroupServer, randomGroupImage(groups[i]), "0", group.Accepted)
|
|
|
|
log.Printf("GROUP %s@%s", group.GroupID, group.GroupServer)
|
2018-10-23 18:52:13 +00:00
|
|
|
}
|
2018-10-30 18:43:51 +00:00
|
|
|
|
2018-10-23 18:52:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// temporary until we do real picture selection
|
|
|
|
func randomProfileImage(onion string) string {
|
2018-10-29 18:00:21 +00:00
|
|
|
//TODO: this is a hack, fix ever passing this in
|
|
|
|
if onion == "me" {
|
|
|
|
onion = peer.GetProfile().Onion
|
|
|
|
}
|
|
|
|
|
2018-10-25 00:13:03 +00:00
|
|
|
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"}
|
2018-10-23 18:52:13 +00:00
|
|
|
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"
|
|
|
|
}
|
2018-10-25 00:13:03 +00:00
|
|
|
return "qrc:/qml/images/profiles/" + choices[int(barr[33])%len(choices)] + ".png"
|
|
|
|
}
|
2018-10-28 02:49:14 +00:00
|
|
|
|
|
|
|
func randomGroupImage(onion string) string {
|
|
|
|
choices := []string{"001-borobudur","002-opera-house","003-burj-al-arab","004-chrysler","005-acropolis","006-empire-state-building","007-temple","008-indonesia-1","009-new-zealand","010-notre-dame","011-space-needle","012-seoul","013-mosque","014-milan","015-statue","016-pyramid","017-cologne","018-brandenburg-gate","019-berlin-cathedral","020-hungarian-parliament","021-buckingham","022-thailand","023-independence","024-angkor-wat","025-vaticano","026-christ-the-redeemer","027-colosseum","028-golden-gate-bridge","029-sphinx","030-statue-of-liberty","031-cradle-of-humankind","032-istanbul","033-london-eye","034-sagrada-familia","035-tower-bridge","036-burj-khalifa","037-washington","038-big-ben","039-stonehenge","040-white-house","041-ahu-tongariki","042-capitol","043-eiffel-tower","044-church-of-the-savior-on-spilled-blood","045-arc-de-triomphe","046-windmill","047-louvre","048-torii-gate","049-petronas","050-matsumoto-castle","051-fuji","052-temple-of-heaven","053-pagoda","054-chichen-itza","055-forbidden-city","056-merlion","057-great-wall-of-china","058-taj-mahal","059-pisa","060-indonesia"}
|
|
|
|
barr, err := hex.DecodeString(onion)
|
|
|
|
if err != nil || len(barr) == 0 {
|
|
|
|
fmt.Printf("error: %v %v %v\n", onion, err, barr)
|
|
|
|
return "qrc:/qml/images/extra/openprivacy.png"
|
|
|
|
}
|
|
|
|
return "qrc:/qml/images/servers/" + choices[int(barr[0])%len(choices)] + ".png"
|
|
|
|
}
|