274 lines
5.1 KiB
Go
274 lines
5.1 KiB
Go
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
|
|
}
|
|
|