diff --git a/examples/real_time_updates/main.go b/examples/real_time_updates/main.go new file mode 100644 index 0000000..559c212 --- /dev/null +++ b/examples/real_time_updates/main.go @@ -0,0 +1,81 @@ +package main + +import ( + "context" + "fmt" + + "github.com/densestvoid/groupme" +) + +// This is not a real token. Please find yours by logging +// into the GroupMe development website: https://dev.groupme.com/ +var authorizationToken = "ABCD" + +// A short program that gets the gets the first 5 groups +// the user is part of, and then the first 10 messages of +// the first group in that list +func main() { + // Create a new client with your auth token + client := groupme.NewClient(authorizationToken) + + // Get the groups your user is part of + groups, err := client.IndexGroups( + context.Background(), + &groupme.GroupsQuery{ + Page: 0, + PerPage: 5, + Omit: "memberships", + }, + ) + + if err != nil { + fmt.Println(err) + return + } + + // fmt.Println(groups) + + // Get first 10 messages of the first group + if len(groups) == 0 { + fmt.Println("No groups") + } + + // messages, err := client.IndexMessages(context.Background(), groups[0].ID, &groupme.IndexMessagesQuery{ + // Limit: 10, + // }) + + // if err != nil { + // fmt.Println(err) + // } + + // fmt.Println(messages) + p := client.NewPushSubscription(context.Background()) + go p.Listen(context.Background()) + + client = groupme.NewClient(authorizationToken) + + a, _ := client.MyUser(context.Background()) + + p.SubscribeToUser(context.Background(), a.ID, authorizationToken) + + authorizationToken = "BCDF" + client = groupme.NewClient(authorizationToken) + a, _ = client.MyUser(context.Background()) + + p.SubscribeToUser(context.Background(), a.ID, authorizationToken) + + for { + select { + case msg := <-p.MessageChannel: + println(msg.Text) + break + + case like := <-p.LikeChannel: + println("Liked") + println(like.Message.ID.String()) + println(like.Message.Text) + break + } + } + +} diff --git a/go.mod b/go.mod index a4551d9..ca1a935 100755 --- a/go.mod +++ b/go.mod @@ -2,8 +2,11 @@ module github.com/densestvoid/groupme go 1.15 +replace github.com/karmanyaahm/wray => ../wray + require ( github.com/google/uuid v1.2.0 github.com/gorilla/mux v1.8.0 + github.com/karmanyaahm/wray v0.0.0-20160519030252-f36984f6648c github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index 8634fb4..a909b99 100755 --- a/go.sum +++ b/go.sum @@ -1,15 +1,29 @@ +github.com/autogrowsystems/wray v0.0.0-20160519030252-f36984f6648c/go.mod h1:druJ8QMeBCUmwJ7ZSFowx77dWxEWF3SYlQlsqZaLZQg= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/real_time.go b/real_time.go new file mode 100644 index 0000000..2566788 --- /dev/null +++ b/real_time.go @@ -0,0 +1,180 @@ +package groupme + +import ( + "context" + "encoding/json" + "errors" + "log" + "sync" + "time" + + "github.com/karmanyaahm/wray" +) + +const ( + pushServer = "https://push.groupme.com/faye" + userChannel = "/user/" + groupChannel = "/group/" + handshakeChannel = "/meta/handshake" + connectChannel = "/meta/connect" + subscribeChannel = "/meta/subscribe" +) + +var concur = sync.Mutex{} +var token string + +type fayeLogger struct{} + +func (l fayeLogger) Infof(f string, a ...interface{}) { + log.Printf("[INFO] : "+f, a...) +} +func (l fayeLogger) Errorf(f string, a ...interface{}) { + log.Printf("[ERROR] : "+f, a...) +} +func (l fayeLogger) Debugf(f string, a ...interface{}) { + // log.Printf("[DEBUG] : "+f, a...) +} +func (l fayeLogger) Warnf(f string, a ...interface{}) { + log.Printf("[WARN] : "+f, a...) +} + +func init() { + wray.RegisterTransports([]wray.Transport{&wray.HTTPTransport{}}) +} + +//LikeEvent returns events as they happen from GroupMe +type LikeEvent struct { + Message Message +} + +//PushSubscription manages real time subscription +type PushSubscription struct { + channel chan wray.Message + //Channel to return things + MessageChannel chan Message + LikeChannel chan LikeEvent + fayeClient *wray.FayeClient +} + +//NewPushSubscription creates and returns a push subscription object +func NewPushSubscription(context context.Context) PushSubscription { + + r := PushSubscription{ + channel: make(chan wray.Message), + MessageChannel: make(chan Message), + LikeChannel: make(chan LikeEvent), + } + + return r +} + +//Listen connects to GroupMe. Runs in Goroutine. +func (r *PushSubscription) Listen(context context.Context) { + r.fayeClient = wray.NewFayeClient(pushServer) + + r.fayeClient.SetLogger(fayeLogger{}) + + r.fayeClient.AddExtension(&authExtension{}) + //r.fayeClient.AddExtension(r.fayeClient) //verbose output + + go r.fayeClient.Listen() + + for { + msg := <-r.channel + data := msg.Data() + content, _ := data["subject"] + contentType := data["type"].(string) + + switch contentType { + case "line.create": + b, _ := json.Marshal(content) + + out := Message{} + json.Unmarshal(b, &out) + //fmt.Printf("%+v\n", out) //TODO + + r.MessageChannel <- out + break + case "like.create": + b, _ := json.Marshal(content.(map[string]interface{})["line"]) + + out := Message{} + //log.Println(string(b)) + err := json.Unmarshal(b, &out) + if err != nil { + log.Println(err) + } + outt := LikeEvent{Message: out} + //fmt.Printf("Like on %+v \n", outt.Message) + + r.LikeChannel <- outt + break + case "ping": + break + default: //TODO: see if any other types are returned + if len(contentType) == 0 || content == nil { + break + } + log.Println(contentType) + log.Fatalln(data) + + } + + } +} + +//SubscribeToUser to users +func (r *PushSubscription) SubscribeToUser(context context.Context, userID ID, authToken string) error { + concur.Lock() + defer concur.Unlock() + + if r.fayeClient == nil { + return errors.New("Not Listening") //TODO: Proper error + } + + token = authToken + r.fayeClient.WaitSubscribe(userChannel+userID.String(), r.channel) + + return nil +} + +//SubscribeToGroup to groups for typing notification +func (r *PushSubscription) SubscribeToGroup(context context.Context, groupID ID, authToken string) error { + concur.Lock() + defer concur.Unlock() + if r.fayeClient == nil { + return errors.New("Not Listening") //TODO: Proper error + } + + token = authToken + r.fayeClient.WaitSubscribe(groupChannel+groupID.String(), r.channel) + + return nil +} + +// Stop listening to GroupMe after completing all other actions scheduled first +func (r *PushSubscription) Stop(context context.Context) { + concur.Lock() + defer concur.Unlock() + + //TODO: stop listening +} + +type authExtension struct { +} + +// In does nothing in this extension, but is needed to satisy the interface +func (e *authExtension) In(msg wray.Message) { + if len(msg.Error()) > 0 { + log.Fatalln(msg.Error()) + } +} + +// Out adds the authentication token to the messages ext field +func (e *authExtension) Out(msg wray.Message) { + if msg.Channel() == subscribeChannel { + ext := msg.Ext() + ext["access_token"] = token + ext["timestamp"] = time.Now().Unix() + } +}