Handle updates of user status

This commit is contained in:
bodqhrohro 2019-11-29 02:51:41 +02:00
parent bcf222b53d
commit dbe87fafa8
6 changed files with 264 additions and 42 deletions

View file

@ -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
}

View file

@ -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
View 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
View 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
}

View file

@ -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)

View file

@ -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