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{})
+}