aboutsummaryrefslogtreecommitdiff
path: root/connector
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
parentb7f0776784b6e5fe0f0a028c2913f58e995afa73 (diff)
downloadeasybridge-225fc84f097aa615239df6deece647a19794234a.tar.gz
easybridge-225fc84f097aa615239df6deece647a19794234a.zip
Basic XMPP
Diffstat (limited to 'connector')
-rw-r--r--connector/config.go34
-rw-r--r--connector/connector.go2
-rw-r--r--connector/irc/irc.go43
-rw-r--r--connector/xmpp/xmpp.go274
4 files changed, 332 insertions, 21 deletions
diff --git a/connector/config.go b/connector/config.go
index d719b49..e0fcf17 100644
--- a/connector/config.go
+++ b/connector/config.go
@@ -1,21 +1,45 @@
package connector
import (
+ "fmt"
"strconv"
+ "strings"
)
type Configuration map[string]string
-func (c Configuration) GetString(k string) string {
+func (c Configuration) GetString(k string, deflt ...string) (string, error) {
if ss, ok := c[k]; ok {
- return ss
+ return ss, nil
}
- return ""
+ if len(deflt) > 0 {
+ return deflt[0], nil
+ }
+ return "", fmt.Errorf("Missing configuration key: %s", k)
}
-func (c Configuration) GetInt(k string) (int, error) {
+func (c Configuration) GetInt(k string, deflt ...int) (int, error) {
if ss, ok := c[k]; ok {
return strconv.Atoi(ss)
}
- return 0, nil
+ if len(deflt) > 0 {
+ return deflt[0], nil
+ }
+ return 0, fmt.Errorf("Missing configuration key: %s", k)
+}
+
+func (c Configuration) GetBool(k string, deflt ...bool) (bool, error) {
+ if ss, ok := c[k]; ok {
+ if strings.EqualFold(ss, "true") {
+ return true, nil
+ } else if strings.EqualFold(ss, "false") {
+ return false, nil
+ } else {
+ return false, fmt.Errorf("Invalid value: %s", ss)
+ }
+ }
+ if len(deflt) > 0 {
+ return deflt[0], nil
+ }
+ return false, fmt.Errorf("Missing configuration key: %s", k)
}
diff --git a/connector/connector.go b/connector/connector.go
index 9894bfc..92b1adc 100644
--- a/connector/connector.go
+++ b/connector/connector.go
@@ -106,7 +106,7 @@ type Event struct {
Room RoomID
// Message text or action text
- Message string
+ Text string
// Attached files such as images
Attachements map[string]MediaObject
diff --git a/connector/irc/irc.go b/connector/irc/irc.go
index b9c58ad..671d19a 100644
--- a/connector/irc/irc.go
+++ b/connector/irc/irc.go
@@ -12,11 +12,11 @@ import (
)
// User id format: nickname@server
+
// Room id format: #room_name@server
type IRC struct {
handler Handler
- config Configuration
connected bool
timeout int
@@ -40,17 +40,26 @@ func (irc *IRC) Configure(c Configuration) error {
irc.Close()
}
- irc.config = c
+ var err error
- irc.nick = c.GetString("nick")
- irc.server = c.GetString("server")
+ irc.nick, err = c.GetString("nick")
+ if err != nil {
+ return err
+ }
- port, err := c.GetInt("port")
+ irc.server, err = c.GetString("server")
if err != nil {
return err
}
- if port == 0 {
- port = 6667
+
+ port, err := c.GetInt("port", 6666)
+ if err != nil {
+ return err
+ }
+
+ ssl, err := c.GetBool("ssl", true)
+ if err != nil {
+ return err
}
client := girc.New(girc.Config{
@@ -59,7 +68,7 @@ func (irc *IRC) Configure(c Configuration) error {
Nick: irc.nick,
User: irc.nick,
Out: os.Stderr,
- SSL: true,
+ SSL: ssl,
})
client.Handlers.Add(girc.CONNECTED, irc.ircConnected)
@@ -83,7 +92,7 @@ func (irc *IRC) Configure(c Configuration) error {
return nil
}
}
- return fmt.Errorf("Failed to conncect after 42s attempting")
+ return fmt.Errorf("Failed to connect after 42s attempting")
}
func (irc *IRC) User() UserID {
@@ -184,9 +193,9 @@ func (irc *IRC) Send(event *Event) error {
}
if event.Type == EVENT_MESSAGE {
- irc.conn.Cmd.Message(dest, event.Message)
+ irc.conn.Cmd.Message(dest, event.Text)
} else if event.Type == EVENT_ACTION {
- irc.conn.Cmd.Action(dest, event.Message)
+ irc.conn.Cmd.Action(dest, event.Text)
} else {
return fmt.Errorf("Invalid event type")
}
@@ -194,8 +203,9 @@ func (irc *IRC) Send(event *Event) error {
}
func (irc *IRC) Close() {
- irc.conn.Close()
+ conn := irc.conn
irc.conn = nil
+ conn.Close()
}
func (irc *IRC) connectLoop(c *girc.Client) {
@@ -206,10 +216,13 @@ func (irc *IRC) connectLoop(c *girc.Client) {
}
if err := c.Connect(); err != nil {
irc.connected = false
- fmt.Printf("IRC failed to connect / disconnected: %s", err)
- fmt.Printf("Retrying in %ds", irc.timeout)
+ fmt.Printf("IRC failed to connect / disconnected: %s\n", err)
+ fmt.Printf("Retrying in %ds\n", irc.timeout)
time.Sleep(time.Duration(irc.timeout) * time.Second)
irc.timeout *= 2
+ if irc.timeout > 600 {
+ irc.timeout = 600
+ }
} else {
return
}
@@ -226,7 +239,7 @@ func (irc *IRC) ircPrivmsg(c *girc.Client, e girc.Event) {
ev := &Event{
Type: EVENT_MESSAGE,
Author: UserID(e.Source.Name + "@" + irc.server),
- Message: e.Last(),
+ Text: e.Last(),
}
if e.IsFromChannel() {
ev.Room = RoomID(e.Params[0] + "@" + irc.server)
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
+}
+