diff --git a/Makefile b/Makefile index 9a4145d..d53a133 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ all: go build -o telegabber test: - go test -v ./config ./ ./telegram + go test -v ./config ./ ./telegram ./xmpp lint: $(GOPATH)/bin/golint ./... diff --git a/go.mod b/go.mod index 461d29e..7f0bbc9 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/pkg/errors v0.8.1 github.com/santhosh-tekuri/jsonschema v1.2.4 github.com/sirupsen/logrus v1.4.2 + github.com/soheilhy/args v0.0.0-20150720134047-6bcf4c78e87e github.com/zelenin/go-tdlib v0.1.0 gopkg.in/yaml.v2 v2.2.4 gosrc.io/xmpp v0.1.3 diff --git a/go.sum b/go.sum index 95bb95a..519b0eb 100644 --- a/go.sum +++ b/go.sum @@ -61,6 +61,8 @@ github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHi github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/soheilhy/args v0.0.0-20150720134047-6bcf4c78e87e h1:Xmvww1DnnCj49ZNwQAw069Kc6X3Q0MUd9OiFQ092yQQ= +github.com/soheilhy/args v0.0.0-20150720134047-6bcf4c78e87e/go.mod h1:RUak+ZC0a3E2NDNxZmA34Hk4Jm9nJMCSKLpKeB06clM= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/xmpp/component.go b/xmpp/component.go index a341a2e..3e85f28 100644 --- a/xmpp/component.go +++ b/xmpp/component.go @@ -1,6 +1,7 @@ package xmpp import ( + "encoding/xml" "github.com/pkg/errors" "dev.narayana.im/narayana/telegabber/config" @@ -8,12 +9,15 @@ import ( "dev.narayana.im/narayana/telegabber/telegram" log "github.com/sirupsen/logrus" + "github.com/soheilhy/args" "gosrc.io/xmpp" + "gosrc.io/xmpp/stanza" ) var jid *xmpp.Jid var tgConf config.TelegramConfig var sessions map[string]telegram.Client +var queue map[string]*stanza.Presence var db persistence.SessionsYamlDB // NewComponent starts a new component and wraps it in @@ -52,13 +56,16 @@ func NewComponent(conf config.XMPPConfig, tc config.TelegramConfig) (*xmpp.Strea cm := xmpp.NewStreamManager(component, nil) - go maintenance() + go maintenance(component) return cm, nil } -func maintenance() { - // TODO +func maintenance(component *xmpp.Component) { + probeType := SPType("probe") + for jid := range sessions { + sendPresence(component, jid, probeType) + } } func loadSessions(dbPath string) error { @@ -95,3 +102,92 @@ func getTelegramInstance(jid string, savedSession *persistence.Session) (telegra return session, true } + +// 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 +} + +func sendPresence(component *xmpp.Component, to string, args ...args.V) { + 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 { + component.Send(presence) + } else { + queue[presence.From+presence.To] = &presence + } +} diff --git a/xmpp/component_test.go b/xmpp/component_test.go new file mode 100644 index 0000000..b540ad4 --- /dev/null +++ b/xmpp/component_test.go @@ -0,0 +1,54 @@ +package xmpp + +import ( + "encoding/xml" + "testing" + + "gosrc.io/xmpp/stanza" +) + +func testPresence(t *testing.T, presence stanza.Presence, reference string) { + byteXML, err := xml.Marshal(presence) + if err != nil { + t.Errorf("XML parse error: %v", err) + } + xmlText := string(byteXML) + if xmlText != reference { + t.Errorf("%v does not match %v", xmlText, reference) + } +} + +func TestPresenceFrom(t *testing.T) { + presence := newPresence("from@test", "to@test", SPFrom("test")) + testPresence(t, presence, "") +} + +func TestPresenceNoFrom(t *testing.T) { + presence := newPresence("from@test", "to@test") + testPresence(t, presence, "") +} + +func TestPresenceType(t *testing.T) { + presence := newPresence("from@test", "to@test", SPType("subscribe")) + testPresence(t, presence, "") +} + +func TestPresenceShow(t *testing.T) { + presence := newPresence("from@test", "to@test", SPShow("dnd")) + testPresence(t, presence, "dnd") +} + +func TestPresenceStatus(t *testing.T) { + presence := newPresence("from@test", "to@test", SPStatus("cooking")) + testPresence(t, presence, "cooking") +} + +func TestPresenceNickname(t *testing.T) { + presence := newPresence("from@test", "to@test", SPNickname("Ishmael")) + testPresence(t, presence, "Ishmael") +} + +func TestPresencePhoto(t *testing.T) { + presence := newPresence("from@test", "to@test", SPPhoto("01b87fcd030b72895ff8e88db57ec525450f000d")) + testPresence(t, presence, "01b87fcd030b72895ff8e88db57ec525450f000d") +} diff --git a/xmpp/extensions.go b/xmpp/extensions.go new file mode 100644 index 0000000..2270431 --- /dev/null +++ b/xmpp/extensions.go @@ -0,0 +1,49 @@ +package xmpp + +import ( + "encoding/xml" + + "gosrc.io/xmpp/stanza" +) + +// PresenceNickExtension is from XEP-0172 +type PresenceNickExtension struct { + XMLName xml.Name `xml:"http://jabber.org/protocol/nick nick"` + Text string `xml:",chardata"` +} + +// PresenceXVCardUpdateExtension is from XEP-0153 +type PresenceXVCardUpdateExtension struct { + XMLName xml.Name `xml:"vcard-temp:x:update x"` + Photo PresenceXVCardUpdatePhoto +} + +// PresenceXVCardUpdatePhoto is from XEP-0153 +type PresenceXVCardUpdatePhoto struct { + XMLName xml.Name `xml:"photo"` + Text string `xml:",chardata"` +} + +// Namespace is a namespace! +func (c PresenceNickExtension) Namespace() string { + return c.XMLName.Space +} + +// Namespace is a namespace! +func (c PresenceXVCardUpdateExtension) Namespace() string { + return c.XMLName.Space +} + +func init() { + // presence nick + stanza.TypeRegistry.MapExtension(stanza.PKTPresence, xml.Name{ + "http://jabber.org/protocol/nick", + "nick", + }, PresenceNickExtension{}) + + // presence vcard update + stanza.TypeRegistry.MapExtension(stanza.PKTPresence, xml.Name{ + "vcard-temp:x:update", + "x", + }, PresenceXVCardUpdateExtension{}) +}