Handle updates of user status
This commit is contained in:
parent
bcf222b53d
commit
dbe87fafa8
|
@ -1,10 +1,10 @@
|
||||||
package telegram
|
package telegram
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"dev.narayana.im/narayana/telegabber/config"
|
"dev.narayana.im/narayana/telegabber/config"
|
||||||
"dev.narayana.im/narayana/telegabber/persistence"
|
"dev.narayana.im/narayana/telegabber/persistence"
|
||||||
|
@ -23,6 +23,16 @@ var logConstants = map[string]int32{
|
||||||
":all": 1023,
|
":all": 1023,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type cacheStruct struct {
|
||||||
|
chats map[int64]*client.Chat
|
||||||
|
users map[int32]*client.User
|
||||||
|
}
|
||||||
|
|
||||||
|
var cache = cacheStruct{
|
||||||
|
chats: map[int64]*client.Chat{},
|
||||||
|
users: map[int32]*client.User{},
|
||||||
|
}
|
||||||
|
|
||||||
func stringToLogConstant(c string) int32 {
|
func stringToLogConstant(c string) int32 {
|
||||||
level, ok := logConstants[c]
|
level, ok := logConstants[c]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -44,10 +54,14 @@ type Client struct {
|
||||||
jid string
|
jid string
|
||||||
Session *persistence.Session
|
Session *persistence.Session
|
||||||
|
|
||||||
ready chan bool
|
locks clientLocks
|
||||||
online bool
|
online bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type clientLocks struct {
|
||||||
|
authorizationReady sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
// NewClient instantiates a Telegram App
|
// NewClient instantiates a Telegram App
|
||||||
func NewClient(conf config.TelegramConfig, jid string, component *xmpp.Component, session *persistence.Session) (*Client, error) {
|
func NewClient(conf config.TelegramConfig, jid string, component *xmpp.Component, session *persistence.Session) (*Client, error) {
|
||||||
logVerbosity := client.WithLogVerbosity(&client.SetLogVerbosityLevelRequest{
|
logVerbosity := client.WithLogVerbosity(&client.SetLogVerbosityLevelRequest{
|
||||||
|
@ -88,21 +102,6 @@ func NewClient(conf config.TelegramConfig, jid string, component *xmpp.Component
|
||||||
jid: jid,
|
jid: jid,
|
||||||
Session: session,
|
Session: session,
|
||||||
logVerbosity: logVerbosity,
|
logVerbosity: logVerbosity,
|
||||||
ready: make(chan bool),
|
locks: clientLocks{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateHandler(tdlibClient *client.Client) {
|
|
||||||
listener := tdlibClient.GetListener()
|
|
||||||
defer listener.Close()
|
|
||||||
|
|
||||||
for update := range listener.Updates {
|
|
||||||
if update.GetClass() == client.ClassUpdate {
|
|
||||||
fmt.Printf("%#v\n", update)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) ProcessOutgoingMessage(chatID int, text string, messageID int) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
|
@ -100,6 +100,8 @@ func (c *Client) Connect() error {
|
||||||
Password: make(chan string, 1),
|
Password: make(chan string, 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.locks.authorizationReady.Add(1)
|
||||||
|
|
||||||
go c.interactor()
|
go c.interactor()
|
||||||
|
|
||||||
c.authorizer.TdlibParameters <- c.parameters
|
c.authorizer.TdlibParameters <- c.parameters
|
||||||
|
@ -111,9 +113,9 @@ func (c *Client) Connect() error {
|
||||||
|
|
||||||
c.client = tdlibClient
|
c.client = tdlibClient
|
||||||
c.online = true
|
c.online = true
|
||||||
c.ready <- true
|
c.locks.authorizationReady.Done()
|
||||||
|
|
||||||
go updateHandler(c.client)
|
go c.updateHandler()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -141,6 +143,7 @@ func (c *Client) interactor() {
|
||||||
|
|
||||||
stateType := state.AuthorizationStateType()
|
stateType := state.AuthorizationStateType()
|
||||||
log.Infof("Telegram authorization state: %#v", stateType)
|
log.Infof("Telegram authorization state: %#v", stateType)
|
||||||
|
log.Debugf("%#v", state)
|
||||||
|
|
||||||
switch stateType {
|
switch stateType {
|
||||||
case client.TypeAuthorizationStateWaitPhoneNumber:
|
case client.TypeAuthorizationStateWaitPhoneNumber:
|
||||||
|
@ -159,7 +162,7 @@ func (c *Client) interactor() {
|
||||||
case client.TypeAuthorizationStateReady:
|
case client.TypeAuthorizationStateReady:
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
<-c.ready
|
c.locks.authorizationReady.Wait()
|
||||||
|
|
||||||
log.Warn("Authorization successful!")
|
log.Warn("Authorization successful!")
|
||||||
|
|
||||||
|
@ -178,7 +181,7 @@ func (c *Client) interactor() {
|
||||||
log.Error("Could not retrieve chats")
|
log.Error("Could not retrieve chats")
|
||||||
}
|
}
|
||||||
|
|
||||||
gateway.SendPresence(c.xmpp, nil, c.jid, gateway.SPStatus("Logged in "+c.Session.Login))
|
gateway.SendPresence(c.xmpp, c.jid, gateway.SPStatus("Logged in "+c.Session.Login))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
38
telegram/handlers.go
Normal file
38
telegram/handlers.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package telegram
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/zelenin/go-tdlib/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
func uhOh() {
|
||||||
|
log.Fatal("Update type mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) updateHandler() {
|
||||||
|
listener := c.client.GetListener()
|
||||||
|
defer listener.Close()
|
||||||
|
|
||||||
|
for update := range listener.Updates {
|
||||||
|
if update.GetClass() == client.ClassUpdate {
|
||||||
|
switch update.GetType() {
|
||||||
|
case client.TypeUpdateUser:
|
||||||
|
typedUpdate, ok := update.(*client.UpdateUser)
|
||||||
|
if !ok {
|
||||||
|
uhOh()
|
||||||
|
}
|
||||||
|
c.updateUser(typedUpdate)
|
||||||
|
default:
|
||||||
|
// log only handled types
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("%#v", update)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) updateUser(update *client.UpdateUser) {
|
||||||
|
cache.users[update.User.Id] = update.User
|
||||||
|
c.processStatusUpdate(update.User.Id, &update.User.Status)
|
||||||
|
}
|
170
telegram/utils.go
Normal file
170
telegram/utils.go
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
package telegram
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"dev.narayana.im/narayana/telegabber/xmpp/gateway"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/soheilhy/args"
|
||||||
|
"github.com/zelenin/go-tdlib/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errOffline = errors.New("TDlib instance is offline")
|
||||||
|
|
||||||
|
// GetContactByUsername resolves username to user id retrieves user and chat information
|
||||||
|
func (c *Client) GetContactByUsername(username string) (*client.Chat, *client.User, error) {
|
||||||
|
if !c.online {
|
||||||
|
return nil, nil, errOffline
|
||||||
|
}
|
||||||
|
|
||||||
|
chat, err := c.client.SearchPublicChat(&client.SearchPublicChatRequest{
|
||||||
|
Username: username,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.GetContactByID(int32(chat.Id), chat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContactByID gets user and chat information from cache (or tries to retrieve it, if missing)
|
||||||
|
func (c *Client) GetContactByID(id int32, chat *client.Chat) (*client.Chat, *client.User, error) {
|
||||||
|
if !c.online {
|
||||||
|
return nil, nil, errOffline
|
||||||
|
}
|
||||||
|
|
||||||
|
var user *client.User
|
||||||
|
var cacheChat *client.Chat
|
||||||
|
var ok bool
|
||||||
|
var err error
|
||||||
|
|
||||||
|
user, ok = cache.users[id]
|
||||||
|
if !ok && id > 0 {
|
||||||
|
user, err = c.client.GetUser(&client.GetUserRequest{
|
||||||
|
UserId: id,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.users[id] = user
|
||||||
|
}
|
||||||
|
|
||||||
|
chatID := int64(id)
|
||||||
|
cacheChat, ok = cache.chats[chatID]
|
||||||
|
if !ok {
|
||||||
|
if chat == nil {
|
||||||
|
cacheChat, err = c.client.GetChat(&client.GetChatRequest{
|
||||||
|
ChatId: chatID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.chats[chatID] = cacheChat
|
||||||
|
} else {
|
||||||
|
cache.chats[chatID] = chat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if chat == nil {
|
||||||
|
chat = cacheChat
|
||||||
|
}
|
||||||
|
|
||||||
|
return chat, user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) processStatusUpdate(chatID int32, status *client.UserStatus, args ...args.V) error {
|
||||||
|
if !c.online {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"chat_id": chatID,
|
||||||
|
}).Info("Status update for")
|
||||||
|
|
||||||
|
chat, user, err := c.GetContactByID(chatID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var photo string
|
||||||
|
if chat != nil && chat.Photo != nil {
|
||||||
|
path := chat.Photo.Small.Local.Path
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err == nil {
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
hash := sha1.New()
|
||||||
|
_, err = io.Copy(hash, file)
|
||||||
|
if err == nil {
|
||||||
|
photo = string(hash.Sum(nil))
|
||||||
|
} else {
|
||||||
|
log.Errorf("Error calculating hash: %v", path)
|
||||||
|
}
|
||||||
|
} else if path != "" {
|
||||||
|
log.Errorf("Photo does not exist: %v", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if status == nil && user != nil {
|
||||||
|
status = &user.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
var show, textStatus string
|
||||||
|
if status == nil {
|
||||||
|
show = "chat"
|
||||||
|
if chat.Title != "" {
|
||||||
|
textStatus = chat.Title
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (*status).UserStatusType() {
|
||||||
|
case client.TypeUserStatusOnline:
|
||||||
|
textStatus = "Online"
|
||||||
|
case client.TypeUserStatusRecently:
|
||||||
|
show, textStatus = "dnd", "Last seen recently"
|
||||||
|
case client.TypeUserStatusLastWeek:
|
||||||
|
show, textStatus = "unavailable", "Last seen last week"
|
||||||
|
case client.TypeUserStatusLastMonth:
|
||||||
|
show, textStatus = "unavailable", "Last seen last month"
|
||||||
|
case client.TypeUserStatusEmpty:
|
||||||
|
show, textStatus = "unavailable", "Last seen a long time ago"
|
||||||
|
case client.TypeUserStatusOffline:
|
||||||
|
offlineStatus, ok := (*status).(*client.UserStatusOffline)
|
||||||
|
if !ok {
|
||||||
|
log.Fatal("Status type changed before conversion!")
|
||||||
|
}
|
||||||
|
// this will stop working in 2038 O\
|
||||||
|
elapsed := time.Now().Unix() - int64(offlineStatus.WasOnline)
|
||||||
|
if elapsed < 3600 {
|
||||||
|
show = "away"
|
||||||
|
} else {
|
||||||
|
show = "xa"
|
||||||
|
}
|
||||||
|
// TODO: timezone
|
||||||
|
textStatus = time.Unix(int64(offlineStatus.WasOnline), 0).Format("Last seen at 15:03 02/01/2006")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gateway.SendPresence(
|
||||||
|
c.xmpp,
|
||||||
|
c.jid,
|
||||||
|
gateway.SPFrom(strconv.Itoa(int(chatID))),
|
||||||
|
gateway.SPShow(show),
|
||||||
|
gateway.SPStatus(textStatus),
|
||||||
|
gateway.SPPhoto(photo),
|
||||||
|
gateway.SPImmed(gateway.SPImmed.Get(args)),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ProcessOutgoingMessage(chatID int, text string, messageID int) {
|
||||||
|
// TODO
|
||||||
|
}
|
|
@ -17,7 +17,6 @@ const pollingInterval time.Duration = 1e7
|
||||||
|
|
||||||
var tgConf config.TelegramConfig
|
var tgConf config.TelegramConfig
|
||||||
var sessions map[string]*telegram.Client
|
var sessions map[string]*telegram.Client
|
||||||
var queue gateway.Queue
|
|
||||||
var db persistence.SessionsYamlDB
|
var db persistence.SessionsYamlDB
|
||||||
|
|
||||||
// NewComponent starts a new component and wraps it in
|
// NewComponent starts a new component and wraps it in
|
||||||
|
@ -30,8 +29,6 @@ func NewComponent(conf config.XMPPConfig, tc config.TelegramConfig) (*xmpp.Strea
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
queue = make(gateway.Queue)
|
|
||||||
|
|
||||||
tgConf = tc
|
tgConf = tc
|
||||||
|
|
||||||
options := xmpp.ComponentOptions{
|
options := xmpp.ComponentOptions{
|
||||||
|
@ -69,7 +66,7 @@ func heartbeat(component *xmpp.Component) {
|
||||||
|
|
||||||
for jid := range sessions {
|
for jid := range sessions {
|
||||||
for {
|
for {
|
||||||
err = gateway.SendPresence(component, queue, jid, probeType)
|
err = gateway.SendPresence(component, jid, probeType)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -80,12 +77,12 @@ func heartbeat(component *xmpp.Component) {
|
||||||
log.Info("Starting heartbeat queue")
|
log.Info("Starting heartbeat queue")
|
||||||
|
|
||||||
for {
|
for {
|
||||||
for key, presence := range queue {
|
for key, presence := range gateway.Queue {
|
||||||
err = component.Send(presence)
|
err = component.Send(presence)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gateway.LogBadPresence(err, presence)
|
gateway.LogBadPresence(err, presence)
|
||||||
} else {
|
} else {
|
||||||
delete(queue, key)
|
delete(gateway.Queue, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
time.Sleep(60e9)
|
time.Sleep(60e9)
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Queue stores presences to send later
|
// Queue stores presences to send later
|
||||||
type Queue map[string]*stanza.Presence
|
var Queue = make(map[string]*stanza.Presence)
|
||||||
|
|
||||||
// Jid stores the component's JID object
|
// Jid stores the component's JID object
|
||||||
var Jid *xmpp.Jid
|
var Jid *xmpp.Jid
|
||||||
|
@ -101,32 +101,47 @@ func newPresence(bareJid string, to string, args ...args.V) stanza.Presence {
|
||||||
}}
|
}}
|
||||||
|
|
||||||
if SPType.IsSet(args) {
|
if SPType.IsSet(args) {
|
||||||
presence.Attrs.Type = stanza.StanzaType(SPType.Get(args))
|
t := SPType.Get(args)
|
||||||
|
if t != "" {
|
||||||
|
presence.Attrs.Type = stanza.StanzaType(t)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if SPShow.IsSet(args) {
|
if SPShow.IsSet(args) {
|
||||||
presence.Show = stanza.PresenceShow(SPShow.Get(args))
|
show := SPShow.Get(args)
|
||||||
|
if show != "" {
|
||||||
|
presence.Show = stanza.PresenceShow(show)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if SPStatus.IsSet(args) {
|
if SPStatus.IsSet(args) {
|
||||||
presence.Status = SPStatus.Get(args)
|
status := SPStatus.Get(args)
|
||||||
|
if status != "" {
|
||||||
|
presence.Status = status
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if SPNickname.IsSet(args) {
|
if SPNickname.IsSet(args) {
|
||||||
presence.Extensions = append(presence.Extensions, extensions.PresenceNickExtension{
|
nickname := SPNickname.Get(args)
|
||||||
Text: SPNickname.Get(args),
|
if nickname != "" {
|
||||||
})
|
presence.Extensions = append(presence.Extensions, extensions.PresenceNickExtension{
|
||||||
|
Text: nickname,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if SPPhoto.IsSet(args) {
|
if SPPhoto.IsSet(args) {
|
||||||
presence.Extensions = append(presence.Extensions, extensions.PresenceXVCardUpdateExtension{
|
photo := SPPhoto.Get(args)
|
||||||
Photo: extensions.PresenceXVCardUpdatePhoto{
|
if photo != "" {
|
||||||
Text: SPPhoto.Get(args),
|
presence.Extensions = append(presence.Extensions, extensions.PresenceXVCardUpdateExtension{
|
||||||
},
|
Photo: extensions.PresenceXVCardUpdatePhoto{
|
||||||
})
|
Text: photo,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return presence
|
return presence
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendPresence creates and sends a presence stanza
|
// SendPresence creates and sends a presence stanza
|
||||||
func SendPresence(component *xmpp.Component, queue Queue, to string, args ...args.V) error {
|
func SendPresence(component *xmpp.Component, to string, args ...args.V) error {
|
||||||
var logFrom string
|
var logFrom string
|
||||||
bareJid := Jid.Bare()
|
bareJid := Jid.Bare()
|
||||||
if SPFrom.IsSet(args) {
|
if SPFrom.IsSet(args) {
|
||||||
|
@ -161,7 +176,7 @@ func SendPresence(component *xmpp.Component, queue Queue, to string, args ...arg
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
queue[presence.From+presence.To] = &presence
|
Queue[presence.From+presence.To] = &presence
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
Loading…
Reference in a new issue