diff options
Diffstat (limited to 'connector/xmpp/xmpp.go')
-rw-r--r-- | connector/xmpp/xmpp.go | 274 |
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 +} + |