2021-01-29 04:47:48 +00:00
|
|
|
package groupme
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
2021-03-04 06:21:23 +00:00
|
|
|
"fmt"
|
2021-01-29 04:47:48 +00:00
|
|
|
"log"
|
2021-03-04 06:21:23 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2021-01-29 04:47:48 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/karmanyaahm/wray"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
pushServer = "https://push.groupme.com/faye"
|
|
|
|
userChannel = "/user/"
|
|
|
|
groupChannel = "/group/"
|
2021-03-04 06:21:23 +00:00
|
|
|
dmChannel = "/direct_message/"
|
2021-01-29 04:47:48 +00:00
|
|
|
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{}) {
|
2021-03-04 06:21:23 +00:00
|
|
|
log.Printf("[DEBUG] : "+f, a...)
|
2021-01-29 04:47:48 +00:00
|
|
|
}
|
|
|
|
func (l fayeLogger) Warnf(f string, a ...interface{}) {
|
|
|
|
log.Printf("[WARN] : "+f, a...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
wray.RegisterTransports([]wray.Transport{&wray.HTTPTransport{}})
|
|
|
|
}
|
|
|
|
|
2021-03-04 06:21:23 +00:00
|
|
|
type HandlerAll interface {
|
|
|
|
Handler
|
|
|
|
HandlerText
|
|
|
|
HandlerLike
|
|
|
|
HandlerMembership
|
|
|
|
HandleGroupMembership
|
|
|
|
HandleGroupMetadata
|
2021-01-29 04:47:48 +00:00
|
|
|
}
|
2021-02-22 03:53:43 +00:00
|
|
|
type Handler interface {
|
|
|
|
HandleError(error)
|
|
|
|
}
|
|
|
|
type HandlerText interface {
|
|
|
|
HandleTextMessage(Message)
|
|
|
|
}
|
|
|
|
type HandlerLike interface {
|
2021-03-04 06:21:23 +00:00
|
|
|
HandleLike(messageID ID, favBy []string)
|
2021-02-22 03:53:43 +00:00
|
|
|
}
|
2021-03-01 15:06:01 +00:00
|
|
|
type HandlerMembership interface {
|
|
|
|
HandleJoin(ID)
|
|
|
|
}
|
2021-02-22 03:53:43 +00:00
|
|
|
|
2021-03-04 06:21:23 +00:00
|
|
|
type HandleGroupMetadata interface {
|
|
|
|
HandleGroupTopic(group ID, newTopic string)
|
|
|
|
HandleGroupName(group ID, newName string)
|
|
|
|
HandleGroupAvatar(group ID, newAvatar string)
|
|
|
|
HandleLikeIcon(group ID, PackID, PackIndex int, Type string)
|
|
|
|
}
|
|
|
|
|
|
|
|
type HandleGroupMembership interface {
|
|
|
|
HandleNewNickname(group ID, user ID, newName string)
|
|
|
|
HandleNewAvatarInGroup(group ID, user ID, avatarURL string)
|
|
|
|
}
|
|
|
|
|
2021-01-29 04:47:48 +00:00
|
|
|
//PushSubscription manages real time subscription
|
|
|
|
type PushSubscription struct {
|
2021-03-04 06:21:23 +00:00
|
|
|
channel chan wray.Message
|
|
|
|
fayeClient *wray.FayeClient
|
|
|
|
handlers []Handler
|
|
|
|
LastConnected int64
|
2021-01-29 04:47:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//NewPushSubscription creates and returns a push subscription object
|
|
|
|
func NewPushSubscription(context context.Context) PushSubscription {
|
|
|
|
|
|
|
|
r := PushSubscription{
|
2021-02-22 03:53:43 +00:00
|
|
|
channel: make(chan wray.Message),
|
2021-01-29 04:47:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2021-02-22 03:53:43 +00:00
|
|
|
func (r *PushSubscription) AddHandler(h Handler) {
|
|
|
|
r.handlers = append(r.handlers, h)
|
|
|
|
}
|
|
|
|
|
2021-03-04 06:21:23 +00:00
|
|
|
//AddFullHandler is the same as AddHandler except to ensure interface implements everything
|
|
|
|
func (r *PushSubscription) AddFullHandler(h HandlerAll) {
|
|
|
|
r.handlers = append(r.handlers, h)
|
|
|
|
}
|
|
|
|
|
|
|
|
type systemMessage struct {
|
|
|
|
Event struct {
|
|
|
|
Kind string `json:"type"`
|
|
|
|
Data interface{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-29 04:47:48 +00:00
|
|
|
//Listen connects to GroupMe. Runs in Goroutine.
|
2021-02-22 03:53:43 +00:00
|
|
|
func (r *PushSubscription) StartListening(context context.Context) {
|
2021-01-29 04:47:48 +00:00
|
|
|
r.fayeClient = wray.NewFayeClient(pushServer)
|
|
|
|
|
|
|
|
r.fayeClient.SetLogger(fayeLogger{})
|
|
|
|
|
|
|
|
r.fayeClient.AddExtension(&authExtension{})
|
|
|
|
//r.fayeClient.AddExtension(r.fayeClient) //verbose output
|
|
|
|
|
|
|
|
go r.fayeClient.Listen()
|
|
|
|
|
2021-02-22 03:53:43 +00:00
|
|
|
go func() {
|
2021-03-04 06:21:23 +00:00
|
|
|
for msg := range r.channel {
|
|
|
|
r.LastConnected = time.Now().Unix()
|
2021-02-22 03:53:43 +00:00
|
|
|
data := msg.Data()
|
|
|
|
content, _ := data["subject"]
|
|
|
|
contentType := data["type"].(string)
|
2021-03-04 06:21:23 +00:00
|
|
|
channel := msg.Channel()
|
|
|
|
|
|
|
|
if strings.HasPrefix(channel, groupChannel) || strings.HasPrefix(channel, dmChannel) {
|
|
|
|
r.chatEvent(contentType, content)
|
|
|
|
}
|
2021-02-22 03:53:43 +00:00
|
|
|
|
|
|
|
switch contentType {
|
2021-03-04 06:21:23 +00:00
|
|
|
case "line.create":
|
2021-02-22 03:53:43 +00:00
|
|
|
b, _ := json.Marshal(content)
|
|
|
|
out := Message{}
|
2021-03-04 06:21:23 +00:00
|
|
|
_ = json.Unmarshal(b, &out)
|
|
|
|
|
|
|
|
if out.UserID.String() == "system" {
|
|
|
|
event := systemMessage{}
|
|
|
|
err := json.Unmarshal(b, &event)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
r.systemEvent(out.GroupID, event)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2021-02-22 03:53:43 +00:00
|
|
|
for _, h := range r.handlers {
|
|
|
|
if h, ok := h.(HandlerText); ok {
|
|
|
|
h.HandleTextMessage(out)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break
|
|
|
|
case "like.create":
|
2021-03-04 06:21:23 +00:00
|
|
|
//should be an associated chatEvent
|
2021-03-01 15:06:01 +00:00
|
|
|
break
|
|
|
|
case "membership.create":
|
|
|
|
c, _ := content.(map[string]interface{})
|
|
|
|
id, _ := c["id"].(string)
|
|
|
|
|
|
|
|
for _, h := range r.handlers {
|
|
|
|
if h, ok := h.(HandlerMembership); ok {
|
|
|
|
h.HandleJoin(ID(id))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-29 04:47:48 +00:00
|
|
|
break
|
2021-02-22 03:53:43 +00:00
|
|
|
case "ping":
|
|
|
|
break
|
|
|
|
default: //TODO: see if any other types are returned
|
|
|
|
if len(contentType) == 0 || content == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
log.Println(contentType)
|
2021-03-01 15:06:01 +00:00
|
|
|
b, _ := json.Marshal(content)
|
2021-03-04 06:21:23 +00:00
|
|
|
log.Fatalln(string(b))
|
2021-02-22 03:53:43 +00:00
|
|
|
|
2021-01-29 04:47:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2021-02-22 03:53:43 +00:00
|
|
|
}()
|
2021-01-29 04:47:48 +00:00
|
|
|
}
|
|
|
|
|
2021-03-04 06:21:23 +00:00
|
|
|
func (r *PushSubscription) chatEvent(contentType string, content interface{}) {
|
|
|
|
switch contentType {
|
|
|
|
case "favorite":
|
|
|
|
b, ok := content.(map[string]interface{})["line"].(Message)
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
log.Println(content)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, h := range r.handlers {
|
|
|
|
if h, ok := h.(HandlerLike); ok {
|
|
|
|
h.HandleLike(b.UserID, b.FavoritedBy)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
default: //TODO: see if any other types are returned
|
|
|
|
println("HEHE")
|
|
|
|
log.Println(contentType)
|
|
|
|
b, _ := json.Marshal(content)
|
|
|
|
log.Fatalln(string(b))
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *PushSubscription) systemEvent(groupID ID, msg systemMessage) {
|
|
|
|
kind := msg.Event.Kind
|
|
|
|
b, _ := json.Marshal(msg.Event.Data)
|
|
|
|
switch kind {
|
|
|
|
case "membership.nickname_changed":
|
|
|
|
data := struct {
|
|
|
|
Name string
|
|
|
|
User struct {
|
|
|
|
ID int
|
|
|
|
}
|
|
|
|
}{}
|
|
|
|
_ = json.Unmarshal(b, &data)
|
|
|
|
|
|
|
|
for _, h := range r.handlers {
|
|
|
|
if h, ok := h.(HandleGroupMembership); ok {
|
|
|
|
h.HandleNewNickname(groupID, ID(strconv.Itoa(data.User.ID)), data.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
case "membership.avatar_changed":
|
|
|
|
data := struct {
|
|
|
|
AvatarURL string `json:"avatar_url"`
|
|
|
|
User struct {
|
|
|
|
ID int
|
|
|
|
}
|
|
|
|
}{}
|
|
|
|
_ = json.Unmarshal(b, &data)
|
|
|
|
|
|
|
|
for _, h := range r.handlers {
|
|
|
|
if h, ok := h.(HandleGroupMembership); ok {
|
|
|
|
h.HandleNewAvatarInGroup(groupID, ID(strconv.Itoa(data.User.ID)), data.AvatarURL)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
case "group.name_change":
|
|
|
|
data := struct {
|
|
|
|
Name string
|
|
|
|
}{}
|
|
|
|
_ = json.Unmarshal(b, &data)
|
|
|
|
|
|
|
|
for _, h := range r.handlers {
|
|
|
|
if h, ok := h.(HandleGroupMetadata); ok {
|
|
|
|
h.HandleGroupName(groupID, data.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
case "group.topic_change":
|
|
|
|
data := struct {
|
|
|
|
Topic string
|
|
|
|
}{}
|
|
|
|
_ = json.Unmarshal(b, &data)
|
|
|
|
|
|
|
|
for _, h := range r.handlers {
|
|
|
|
if h, ok := h.(HandleGroupMetadata); ok {
|
|
|
|
h.HandleGroupTopic(groupID, data.Topic)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
case "group.avatar_change":
|
|
|
|
data := struct {
|
|
|
|
AvatarURL string `json:"avatar_url"`
|
|
|
|
}{}
|
|
|
|
_ = json.Unmarshal(b, &data)
|
|
|
|
|
|
|
|
for _, h := range r.handlers {
|
|
|
|
if h, ok := h.(HandleGroupMetadata); ok {
|
|
|
|
h.HandleGroupAvatar(groupID, data.AvatarURL)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
case "group.like_icon_set":
|
|
|
|
data := struct {
|
|
|
|
LikeIcon struct {
|
|
|
|
PackID int `json:"pack_id"`
|
|
|
|
PackIndex int `json:"pack_index"`
|
|
|
|
Type string
|
|
|
|
} `json:"like_icon"`
|
|
|
|
}{}
|
|
|
|
_ = json.Unmarshal(b, &data)
|
|
|
|
|
|
|
|
for _, h := range r.handlers {
|
|
|
|
if h, ok := h.(HandleGroupMetadata); ok {
|
|
|
|
h.HandleLikeIcon(groupID, data.LikeIcon.PackID, data.LikeIcon.PackIndex, data.LikeIcon.Type)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
case "group.like_icon_removed":
|
|
|
|
for _, h := range r.handlers {
|
|
|
|
if h, ok := h.(HandleGroupMetadata); ok {
|
|
|
|
h.HandleLikeIcon(groupID, 0, 0, "")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
log.Println(kind)
|
|
|
|
log.Fatalln(string(b))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-29 04:47:48 +00:00
|
|
|
//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
|
|
|
|
}
|
|
|
|
|
2021-03-04 06:21:23 +00:00
|
|
|
//Connected check if connected
|
|
|
|
func (r *PushSubscription) Connected() bool {
|
|
|
|
return r.LastConnected+30 >= time.Now().Unix()
|
|
|
|
}
|
|
|
|
|
2021-01-29 04:47:48 +00:00
|
|
|
// 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) {
|
2021-03-04 06:21:23 +00:00
|
|
|
println(msg.Channel())
|
2021-01-29 04:47:48 +00:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|