209 lines
5.0 KiB
Go
209 lines
5.0 KiB
Go
package groupme
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"log"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/karmanyaahm/wray"
|
|
)
|
|
|
|
const (
|
|
PushServer = "https://push.groupme.com/faye"
|
|
userChannel = "/user/"
|
|
groupChannel = "/group/"
|
|
dmChannel = "/direct_message/"
|
|
subscribeChannel = "/meta/subscribe"
|
|
)
|
|
|
|
var (
|
|
ErrHandlerNotFound = errors.New("Handler not found")
|
|
ErrListenerNotStarted = errors.New("GroupMe listener not started")
|
|
)
|
|
|
|
var concur = sync.Mutex{}
|
|
var token string
|
|
|
|
func init() {
|
|
wray.RegisterTransports([]wray.Transport{&wray.HTTPTransport{}})
|
|
}
|
|
|
|
type HandlerAll interface {
|
|
Handler
|
|
|
|
//of self
|
|
HandlerText
|
|
HandlerLike
|
|
HandlerMembership
|
|
|
|
//of group
|
|
HandleGroupTopic
|
|
HandleGroupAvatar
|
|
HandleGroupName
|
|
HandleGroupLikeIcon
|
|
|
|
//of group members
|
|
HandleMemberNewNickname
|
|
HandleMemberNewAvatar
|
|
HandleMembers
|
|
}
|
|
type Handler interface {
|
|
HandleError(error)
|
|
}
|
|
type HandlerText interface {
|
|
HandleTextMessage(Message)
|
|
}
|
|
type HandlerLike interface {
|
|
HandleLike(Message)
|
|
}
|
|
type HandlerMembership interface {
|
|
HandleJoin(ID)
|
|
}
|
|
|
|
//Group Handlers
|
|
type HandleGroupTopic interface {
|
|
HandleGroupTopic(group ID, newTopic string)
|
|
}
|
|
|
|
type HandleGroupName interface {
|
|
HandleGroupName(group ID, newName string)
|
|
}
|
|
type HandleGroupAvatar interface {
|
|
HandleGroupAvatar(group ID, newAvatar string)
|
|
}
|
|
type HandleGroupLikeIcon interface {
|
|
HandleLikeIcon(group ID, PackID, PackIndex int, Type string)
|
|
}
|
|
|
|
//Group member handlers
|
|
type HandleMemberNewNickname interface {
|
|
HandleNewNickname(group ID, user ID, newName string)
|
|
}
|
|
|
|
type HandleMemberNewAvatar interface {
|
|
HandleNewAvatarInGroup(group ID, user ID, avatarURL string)
|
|
}
|
|
type HandleMembers interface {
|
|
//HandleNewMembers returns only partial member with id and nickname; added is false if removing
|
|
HandleMembers(group ID, members []Member, added bool)
|
|
}
|
|
|
|
type PushMessage interface {
|
|
Channel() string
|
|
Data() map[string]interface{}
|
|
Ext() map[string]interface{}
|
|
Error() string
|
|
}
|
|
|
|
type FayeClient interface {
|
|
//Listen starts a blocking listen loop
|
|
Listen()
|
|
//WaitSubscribe is a blocking/synchronous subscribe method
|
|
WaitSubscribe(channel string, msgChannel chan PushMessage)
|
|
}
|
|
|
|
//PushSubscription manages real time subscription
|
|
type PushSubscription struct {
|
|
channel chan PushMessage
|
|
fayeClient FayeClient
|
|
handlers []Handler
|
|
LastConnected int64
|
|
}
|
|
|
|
//NewPushSubscription creates and returns a push subscription object
|
|
func NewPushSubscription(context context.Context) PushSubscription {
|
|
|
|
r := PushSubscription{
|
|
channel: make(chan PushMessage),
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
func (r *PushSubscription) AddHandler(h Handler) {
|
|
r.handlers = append(r.handlers, h)
|
|
}
|
|
|
|
//AddFullHandler is the same as AddHandler except it ensures the interface implements everything
|
|
func (r *PushSubscription) AddFullHandler(h HandlerAll) {
|
|
r.handlers = append(r.handlers, h)
|
|
}
|
|
|
|
var RealTimeHandlers map[string]func(r *PushSubscription, channel string, data ...interface{})
|
|
var RealTimeSystemHandlers map[string]func(r *PushSubscription, channel string, id ID, rawData []byte)
|
|
|
|
//Listen connects to GroupMe. Runs in Goroutine.
|
|
func (r *PushSubscription) StartListening(context context.Context, client FayeClient) {
|
|
r.fayeClient = client
|
|
|
|
go r.fayeClient.Listen()
|
|
|
|
go func() {
|
|
for msg := range r.channel {
|
|
r.LastConnected = time.Now().Unix()
|
|
data := msg.Data()
|
|
content := data["subject"]
|
|
contentType := data["type"].(string)
|
|
channel := msg.Channel()
|
|
|
|
handler, ok := RealTimeHandlers[contentType]
|
|
if !ok {
|
|
if contentType == "ping" ||
|
|
len(contentType) == 0 ||
|
|
content == nil {
|
|
continue
|
|
}
|
|
log.Println("Unable to handle GroupMe message type", contentType)
|
|
}
|
|
|
|
handler(r, channel, content)
|
|
}
|
|
}()
|
|
}
|
|
|
|
//SubscribeToUser to users
|
|
func (r *PushSubscription) SubscribeToUser(context context.Context, id ID, authToken string) error {
|
|
return r.subscribeWithPrefix(userChannel, context, id, authToken)
|
|
}
|
|
|
|
//SubscribeToGroup to groups for typing notification
|
|
func (r *PushSubscription) SubscribeToGroup(context context.Context, id ID, authToken string) error {
|
|
return r.subscribeWithPrefix(groupChannel, context, id, authToken)
|
|
}
|
|
|
|
//SubscribeToDM to users
|
|
func (r *PushSubscription) SubscribeToDM(context context.Context, id ID, authToken string) error {
|
|
id = ID(strings.Replace(id.String(), "+", "_", 1))
|
|
return r.subscribeWithPrefix(dmChannel, context, id, authToken)
|
|
}
|
|
|
|
func (r *PushSubscription) subscribeWithPrefix(prefix string, context context.Context, groupID ID, authToken string) error {
|
|
concur.Lock()
|
|
defer concur.Unlock()
|
|
if r.fayeClient == nil {
|
|
return ErrListenerNotStarted
|
|
}
|
|
|
|
token = authToken
|
|
r.fayeClient.WaitSubscribe(prefix+groupID.String(), r.channel)
|
|
|
|
return nil
|
|
}
|
|
|
|
//Connected check if connected
|
|
func (r *PushSubscription) Connected() bool {
|
|
return r.LastConnected+30 >= time.Now().Unix()
|
|
}
|
|
|
|
// Out adds the authentication token to the messages ext field
|
|
func OutMsgProc(msg PushMessage) {
|
|
if msg.Channel() == subscribeChannel {
|
|
ext := msg.Ext()
|
|
ext["access_token"] = token
|
|
ext["timestamp"] = time.Now().Unix()
|
|
}
|
|
}
|