2019-10-22 16:36:54 +00:00
|
|
|
package xmpp
|
|
|
|
|
|
|
|
import (
|
2019-11-14 20:11:04 +00:00
|
|
|
"encoding/xml"
|
2019-11-12 15:50:25 +00:00
|
|
|
"github.com/pkg/errors"
|
2019-11-16 18:44:13 +00:00
|
|
|
"time"
|
2019-11-12 15:50:25 +00:00
|
|
|
|
2019-10-22 16:36:54 +00:00
|
|
|
"dev.narayana.im/narayana/telegabber/config"
|
2019-11-10 23:50:50 +00:00
|
|
|
"dev.narayana.im/narayana/telegabber/persistence"
|
2019-11-07 21:09:53 +00:00
|
|
|
"dev.narayana.im/narayana/telegabber/telegram"
|
2019-10-22 16:36:54 +00:00
|
|
|
|
2019-11-12 15:50:25 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
2019-11-14 20:11:04 +00:00
|
|
|
"github.com/soheilhy/args"
|
2019-10-22 16:36:54 +00:00
|
|
|
"gosrc.io/xmpp"
|
2019-11-14 20:11:04 +00:00
|
|
|
"gosrc.io/xmpp/stanza"
|
2019-10-22 16:36:54 +00:00
|
|
|
)
|
|
|
|
|
2019-11-18 19:01:45 +00:00
|
|
|
const pollingInterval time.Duration = 1e7
|
|
|
|
|
2019-11-03 22:15:43 +00:00
|
|
|
var jid *xmpp.Jid
|
|
|
|
var tgConf config.TelegramConfig
|
2019-11-07 21:09:53 +00:00
|
|
|
var sessions map[string]telegram.Client
|
2019-11-14 20:11:04 +00:00
|
|
|
var queue map[string]*stanza.Presence
|
2019-11-10 23:50:50 +00:00
|
|
|
var db persistence.SessionsYamlDB
|
2019-11-03 22:15:43 +00:00
|
|
|
|
2019-10-29 01:23:57 +00:00
|
|
|
// NewComponent starts a new component and wraps it in
|
|
|
|
// a stream manager that you should start yourself
|
2019-11-03 22:15:43 +00:00
|
|
|
func NewComponent(conf config.XMPPConfig, tc config.TelegramConfig) (*xmpp.StreamManager, error) {
|
|
|
|
var err error
|
2019-11-10 23:50:50 +00:00
|
|
|
|
2019-11-03 22:15:43 +00:00
|
|
|
jid, err = xmpp.NewJid(conf.Jid)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
tgConf = tc
|
|
|
|
|
2019-11-12 15:50:25 +00:00
|
|
|
err = loadSessions(conf.Db)
|
2019-11-10 23:50:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-10-22 16:36:54 +00:00
|
|
|
options := xmpp.ComponentOptions{
|
|
|
|
Address: conf.Host + ":" + conf.Port,
|
|
|
|
Domain: conf.Jid,
|
|
|
|
Secret: conf.Password,
|
|
|
|
Name: "telegabber",
|
|
|
|
}
|
|
|
|
|
|
|
|
router := xmpp.NewRouter()
|
2019-11-03 22:15:43 +00:00
|
|
|
router.HandleFunc("iq", HandleIq)
|
|
|
|
router.HandleFunc("presence", HandlePresence)
|
2019-10-22 16:36:54 +00:00
|
|
|
router.HandleFunc("message", HandleMessage)
|
|
|
|
|
|
|
|
component, err := xmpp.NewComponent(options, router)
|
|
|
|
if err != nil {
|
2019-11-03 22:15:43 +00:00
|
|
|
return nil, err
|
2019-10-22 16:36:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cm := xmpp.NewStreamManager(component, nil)
|
|
|
|
|
2019-11-16 18:44:13 +00:00
|
|
|
go heartbeat(component)
|
2019-11-12 15:50:25 +00:00
|
|
|
|
2019-11-03 22:15:43 +00:00
|
|
|
return cm, nil
|
2019-10-22 16:36:54 +00:00
|
|
|
}
|
2019-11-12 15:50:25 +00:00
|
|
|
|
2019-11-16 18:44:13 +00:00
|
|
|
func logPresence(err error, presence *stanza.Presence) {
|
|
|
|
log.WithFields(log.Fields{
|
|
|
|
"presence": *presence,
|
|
|
|
}).Error(errors.Wrap(err, "Couldn't send presence"))
|
|
|
|
}
|
|
|
|
|
|
|
|
func heartbeat(component *xmpp.Component) {
|
|
|
|
var err error
|
2019-11-14 20:11:04 +00:00
|
|
|
probeType := SPType("probe")
|
2019-11-16 18:44:13 +00:00
|
|
|
|
2019-11-14 20:11:04 +00:00
|
|
|
for jid := range sessions {
|
2019-11-18 19:01:45 +00:00
|
|
|
for {
|
|
|
|
err = sendPresence(component, jid, probeType)
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
time.Sleep(pollingInterval)
|
|
|
|
}
|
2019-11-14 20:11:04 +00:00
|
|
|
}
|
2019-11-16 18:44:13 +00:00
|
|
|
|
2019-11-18 19:01:45 +00:00
|
|
|
log.Info("Starting heartbeat queue")
|
|
|
|
|
2019-11-16 18:44:13 +00:00
|
|
|
for {
|
|
|
|
for key, presence := range queue {
|
|
|
|
err = component.Send(presence)
|
|
|
|
if err != nil {
|
|
|
|
logPresence(err, presence)
|
|
|
|
} else {
|
|
|
|
delete(queue, key)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
time.Sleep(60e9)
|
|
|
|
}
|
2019-11-12 15:50:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func loadSessions(dbPath string) error {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
sessions = make(map[string]telegram.Client)
|
|
|
|
|
|
|
|
db, err = persistence.LoadSessions(dbPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
db.Transaction(func() bool {
|
|
|
|
for jid, session := range db.Data.Sessions {
|
|
|
|
getTelegramInstance(jid, &session)
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}, persistence.SessionMarshaller)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getTelegramInstance(jid string, savedSession *persistence.Session) (telegram.Client, bool) {
|
|
|
|
session, ok := sessions[jid]
|
|
|
|
if !ok {
|
|
|
|
session, err := telegram.NewClient(tgConf, jid, savedSession)
|
|
|
|
if err != nil {
|
|
|
|
log.Error(errors.Wrap(err, "TDlib initialization failure"))
|
|
|
|
return session, false
|
|
|
|
}
|
|
|
|
sessions[jid] = session
|
|
|
|
}
|
|
|
|
|
|
|
|
return session, true
|
|
|
|
}
|
2019-11-14 20:11:04 +00:00
|
|
|
|
|
|
|
// SPFrom is a Telegram user id
|
|
|
|
var SPFrom = args.NewString()
|
|
|
|
|
|
|
|
// SPType is a presence type
|
|
|
|
var SPType = args.NewString()
|
|
|
|
|
|
|
|
// SPShow is a availability status
|
|
|
|
var SPShow = args.NewString()
|
|
|
|
|
|
|
|
// SPStatus is a verbose status
|
|
|
|
var SPStatus = args.NewString()
|
|
|
|
|
|
|
|
// SPNickname is a XEP-0172 nickname
|
|
|
|
var SPNickname = args.NewString()
|
|
|
|
|
|
|
|
// SPPhoto is a XEP-0153 hash of avatar in vCard
|
|
|
|
var SPPhoto = args.NewString()
|
|
|
|
|
|
|
|
// SPImmed skips queueing
|
|
|
|
var SPImmed = args.NewBool(args.Default(true))
|
|
|
|
|
|
|
|
func newPresence(bareJid string, to string, args ...args.V) stanza.Presence {
|
|
|
|
var presenceFrom string
|
|
|
|
if SPFrom.IsSet(args) {
|
|
|
|
presenceFrom = SPFrom.Get(args) + "@" + bareJid
|
|
|
|
} else {
|
|
|
|
presenceFrom = bareJid
|
|
|
|
}
|
|
|
|
|
|
|
|
presence := stanza.Presence{Attrs: stanza.Attrs{
|
|
|
|
From: presenceFrom,
|
|
|
|
To: to,
|
|
|
|
}}
|
|
|
|
|
|
|
|
if SPType.IsSet(args) {
|
|
|
|
presence.Attrs.Type = stanza.StanzaType(SPType.Get(args))
|
|
|
|
}
|
|
|
|
if SPShow.IsSet(args) {
|
|
|
|
presence.Show = stanza.PresenceShow(SPShow.Get(args))
|
|
|
|
}
|
|
|
|
if SPStatus.IsSet(args) {
|
|
|
|
presence.Status = SPStatus.Get(args)
|
|
|
|
}
|
|
|
|
if SPNickname.IsSet(args) {
|
|
|
|
presence.Extensions = append(presence.Extensions, PresenceNickExtension{
|
|
|
|
Text: SPNickname.Get(args),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if SPPhoto.IsSet(args) {
|
|
|
|
presence.Extensions = append(presence.Extensions, PresenceXVCardUpdateExtension{
|
|
|
|
Photo: PresenceXVCardUpdatePhoto{
|
|
|
|
Text: SPPhoto.Get(args),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return presence
|
|
|
|
}
|
|
|
|
|
2019-11-18 19:01:45 +00:00
|
|
|
func sendPresence(component *xmpp.Component, to string, args ...args.V) error {
|
2019-11-14 20:11:04 +00:00
|
|
|
var logFrom string
|
|
|
|
bareJid := jid.Bare()
|
|
|
|
if SPFrom.IsSet(args) {
|
|
|
|
logFrom = SPFrom.Get(args)
|
|
|
|
} else {
|
|
|
|
logFrom = bareJid
|
|
|
|
}
|
|
|
|
|
|
|
|
log.WithFields(log.Fields{
|
|
|
|
"type": SPType.Get(args),
|
|
|
|
"from": logFrom,
|
|
|
|
"to": to,
|
|
|
|
}).Info("Got presence")
|
|
|
|
|
|
|
|
presence := newPresence(bareJid, to, args...)
|
|
|
|
|
|
|
|
// explicit check, as marshalling is expensive
|
|
|
|
if log.GetLevel() == log.DebugLevel {
|
|
|
|
log.Debug(xml.Marshal(presence))
|
|
|
|
}
|
|
|
|
|
|
|
|
immed := SPImmed.Get(args)
|
|
|
|
if immed {
|
2019-11-16 18:44:13 +00:00
|
|
|
err := component.Send(presence)
|
|
|
|
if err != nil {
|
|
|
|
logPresence(err, &presence)
|
2019-11-18 19:01:45 +00:00
|
|
|
return err
|
2019-11-16 18:44:13 +00:00
|
|
|
}
|
2019-11-14 20:11:04 +00:00
|
|
|
} else {
|
|
|
|
queue[presence.From+presence.To] = &presence
|
|
|
|
}
|
2019-11-18 19:01:45 +00:00
|
|
|
|
|
|
|
return nil
|
2019-11-14 20:11:04 +00:00
|
|
|
}
|