aboutsummaryrefslogtreecommitdiff
path: root/connector/xmpp
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2020-02-16 17:53:31 +0100
committerAlex Auvolat <alex@adnab.me>2020-02-16 17:53:31 +0100
commit225fc84f097aa615239df6deece647a19794234a (patch)
tree14ce2ea0a61993c56da21f8f0c3f157177717bbb /connector/xmpp
parentb7f0776784b6e5fe0f0a028c2913f58e995afa73 (diff)
downloadeasybridge-225fc84f097aa615239df6deece647a19794234a.tar.gz
easybridge-225fc84f097aa615239df6deece647a19794234a.zip
Basic XMPP
Diffstat (limited to 'connector/xmpp')
-rw-r--r--connector/xmpp/xmpp.go274
1 files changed, 274 insertions, 0 deletions
diff --git a/connector/xmpp/xmpp.go b/connector/xmpp/xmpp.go
new file mode 100644
index 0000000..2b2977e
--- /dev/null
+++ b/connector/xmpp/xmpp.go
@@ -0,0 +1,274 @@
+package xmpp
+
+import (
+ "log"
+ "time"
+ //"os"
+ "strings"
+ "fmt"
+ "crypto/tls"
+
+ . "git.deuxfleurs.fr/Deuxfleurs/easybridge/connector"
+
+ gxmpp "github.com/mattn/go-xmpp"
+)
+
+// User id format: username@server (= JID)
+// OR: nickname@room_name@muc_server
+
+// Room id format: room_name@muc_server (= MUC ID)
+
+type XMPP struct {
+ handler Handler
+
+ connectorLoopNum int
+ connected bool
+ timeout int
+
+ server string
+ port int
+ ssl bool
+ jid string
+ jid_localpart string
+ password string
+ nickname string
+
+ conn *gxmpp.Client
+}
+
+func (xm *XMPP) SetHandler(h Handler) {
+ xm.handler = h
+}
+
+func(xm *XMPP) Protocol() string {
+ return "xmpp"
+}
+
+func (xm *XMPP) Configure(c Configuration) error {
+ if xm.conn != nil {
+ xm.Close()
+ }
+
+ // Parse and validate configuration
+ var err error
+
+ xm.server, err = c.GetString("server")
+ if err != nil {
+ return err
+ }
+
+ xm.port, err = c.GetInt("port", 5222)
+ if err != nil {
+ return err
+ }
+
+ xm.ssl, err = c.GetBool("ssl", true)
+ if err != nil {
+ return err
+ }
+
+ xm.jid, err = c.GetString("jid")
+ if err != nil {
+ return err
+ }
+ jid_parts := strings.Split(xm.jid, "@")
+ if len(jid_parts) != 2 {
+ return fmt.Errorf("Invalid JID: %s", xm.jid)
+ }
+ if jid_parts[1] != xm.server {
+ return fmt.Errorf("JID %s not on server %s", xm.jid, xm.server)
+ }
+ xm.jid_localpart = jid_parts[0]
+ xm.nickname = xm.jid_localpart
+
+ xm.password, err = c.GetString("password")
+ if err != nil {
+ return err
+ }
+
+ // Try to connect
+ xm.connectorLoopNum += 1
+ go xm.connectLoop(xm.connectorLoopNum)
+
+ for i := 0; i < 42; i++ {
+ time.Sleep(time.Duration(1)*time.Second)
+ if xm.connected {
+ return nil
+ }
+ }
+ return fmt.Errorf("Failed to connect after 42s attempting")
+}
+
+func (xm *XMPP) connectLoop(num int) {
+ xm.timeout = 10
+ for {
+ if xm.connectorLoopNum != num {
+ return
+ }
+ tc := &tls.Config{
+ ServerName: strings.Split(xm.jid, "@")[1],
+ InsecureSkipVerify: true,
+ }
+ options := gxmpp.Options{
+ Host: xm.server,
+ User: xm.jid,
+ Password: xm.password,
+ NoTLS: true,
+ StartTLS: xm.ssl,
+ Session: true,
+ TLSConfig: tc,
+ }
+ var err error
+ xm.conn, err = options.NewClient()
+ if err != nil {
+ xm.connected = false
+ fmt.Printf("XMPP failed to connect / disconnected: %s\n", err)
+ fmt.Printf("Retrying in %ds\n", xm.timeout)
+ time.Sleep(time.Duration(xm.timeout) * time.Second)
+ xm.timeout *= 2
+ if xm.timeout > 600 {
+ xm.timeout = 600
+ }
+ } else {
+ xm.connected = true
+ xm.timeout = 10
+ err = xm.handleXMPP()
+ if err != nil {
+ xm.connected = false
+ fmt.Printf("XMPP disconnected: %s\n", err)
+ fmt.Printf("Reconnecting.\n")
+ }
+ }
+ }
+}
+
+func (xm *XMPP) xmppKeepAlive() chan bool {
+ done := make(chan bool)
+ go func() {
+ ticker := time.NewTicker(90 * time.Second)
+ defer ticker.Stop()
+ for {
+ select {
+ case <-ticker.C:
+ if err := xm.conn.PingC2S("", ""); err != nil {
+ log.Printf("PING failed %#v\n", err)
+ }
+ case <-done:
+ return
+ }
+ }
+ }()
+ return done
+}
+
+func (xm *XMPP) handleXMPP() error {
+ done := xm.xmppKeepAlive()
+ defer close(done)
+
+ for {
+ m, err := xm.conn.Recv()
+ if err != nil {
+ return err
+ }
+
+ switch v := m.(type) {
+ case gxmpp.Chat:
+ log.Printf("== Receiving %#v\n", v)
+
+ if v.Text == "" || v.Remote == xm.jid {
+ continue
+ }
+
+ event := &Event{
+ Type: EVENT_MESSAGE,
+ Text: v.Text,
+ }
+ log.Printf("Remote: %s\n", v.Remote)
+
+ if strings.HasPrefix(event.Text, "/me ") {
+ event.Type = EVENT_ACTION
+ event.Text = strings.Replace(event.Text, "/me ", "", 1)
+ }
+
+ if v.Type == "chat" {
+ remote_jid := strings.Split(v.Remote, "/")[0]
+ event.Author = UserID(remote_jid)
+ xm.handler.Event(event)
+ }
+ if v.Type == "groupchat" {
+ remote := strings.Split(v.Remote, "/")
+ if len(remote) != 2 {
+ log.Printf("Invalid remote: %s\n", v.Remote)
+ continue
+ }
+ event.Room = RoomID(remote[0])
+ event.Author = UserID(remote[1] + "@" + remote[0])
+
+ if strings.Contains(v.Text, "has set the subject to:") {
+ // TODO
+ continue
+ }
+
+ xm.handler.Event(event)
+ }
+ case gxmpp.Presence:
+ // Do nothing.
+ }
+ }
+}
+
+func (xm *XMPP) User() UserID {
+ return UserID(xm.jid)
+}
+
+func (xm *XMPP) SetUserInfo(info *UserInfo) error {
+ //TODO
+ return fmt.Errorf("Not implemented")
+}
+
+func (xm *XMPP) SetRoomInfo(roomId RoomID, info *RoomInfo) error {
+ // TODO
+ return fmt.Errorf("Not implemented")
+}
+
+func (xm *XMPP) Join(roomId RoomID) error {
+ fmt.Printf("Join %s with nick %s\n", roomId, xm.nickname)
+ _, err := xm.conn.JoinMUCNoHistory(string(roomId), xm.nickname)
+ return err
+}
+
+func (xm *XMPP) Invite(userId UserID, roomId RoomID) error {
+ // TODO
+ return fmt.Errorf("Not implemented")
+}
+
+func (xm *XMPP) Leave(roomId RoomID) {
+ // TODO
+}
+
+func (xm *XMPP) Send(event *Event) error {
+ if len(event.Recipient) > 0 {
+ xm.conn.Send(gxmpp.Chat{
+ Type: "chat",
+ Remote: string(event.Recipient),
+ Text: event.Text,
+ })
+ return nil
+ } else if len(event.Room) > 0 {
+ xm.conn.Send(gxmpp.Chat{
+ Type: "groupchat",
+ Remote: string(event.Room),
+ Text: event.Text,
+ })
+ return nil
+ } else {
+ return fmt.Errorf("Invalid event")
+ }
+}
+
+func (xm *XMPP) Close() {
+ xm.conn.Close()
+ xm.conn = nil
+ xm.connectorLoopNum += 1
+}
+