Basic XMPP
This commit is contained in:
parent
b7f0776784
commit
225fc84f09
7 changed files with 401 additions and 30 deletions
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
||||
port, err := c.GetInt("port")
|
||||
irc.nick, err = c.GetString("nick")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if port == 0 {
|
||||
port = 6667
|
||||
|
||||
irc.server, err = c.GetString("server")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
274
connector/xmpp/xmpp.go
Normal file
274
connector/xmpp/xmpp.go
Normal file
|
@ -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
|
||||
}
|
||||
|
6
go.mod
6
go.mod
|
@ -2,4 +2,8 @@ module git.deuxfleurs.fr/Deuxfleurs/easybridge
|
|||
|
||||
go 1.13
|
||||
|
||||
require github.com/lrstanley/girc v0.0.0-20190801035559-4fc93959e1a7
|
||||
require (
|
||||
github.com/lrstanley/girc v0.0.0-20190801035559-4fc93959e1a7
|
||||
github.com/matterbridge/go-xmpp v0.0.0-20180131083630-7ec2b8b7def6
|
||||
github.com/mattn/go-xmpp v0.0.0-20200128155807-a86b6abcb3ad
|
||||
)
|
||||
|
|
4
go.sum
4
go.sum
|
@ -1,2 +1,6 @@
|
|||
github.com/lrstanley/girc v0.0.0-20190801035559-4fc93959e1a7 h1:BS9tqL0OCiOGuy/CYYk2gc33fxqaqh5/rhqMKu4tcYA=
|
||||
github.com/lrstanley/girc v0.0.0-20190801035559-4fc93959e1a7/go.mod h1:liX5MxHPrwgHaKowoLkYGwbXfYABh1jbZ6FpElbGF1I=
|
||||
github.com/matterbridge/go-xmpp v0.0.0-20180131083630-7ec2b8b7def6 h1:GDh7egrbDEzP41mScMt7Q/uPM2nJENh9LNFXjUOGts8=
|
||||
github.com/matterbridge/go-xmpp v0.0.0-20180131083630-7ec2b8b7def6/go.mod h1:ECDRehsR9TYTKCAsRS8/wLeOk6UUqDydw47ln7wG41Q=
|
||||
github.com/mattn/go-xmpp v0.0.0-20200128155807-a86b6abcb3ad h1:ntj2CDcRNjFht20llTwIwwguKa00u0UCLtF2J5+Gmxo=
|
||||
github.com/mattn/go-xmpp v0.0.0-20200128155807-a86b6abcb3ad/go.mod h1:Cs5mF0OsrRRmhkyOod//ldNPOwJsrBvJ+1WRspv0xoc=
|
||||
|
|
66
main.go
66
main.go
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"git.deuxfleurs.fr/Deuxfleurs/easybridge/connector"
|
||||
"git.deuxfleurs.fr/Deuxfleurs/easybridge/connector/irc"
|
||||
"git.deuxfleurs.fr/Deuxfleurs/easybridge/connector/xmpp"
|
||||
)
|
||||
|
||||
type TmpHandler struct{
|
||||
|
@ -35,17 +36,17 @@ func (h *TmpHandler) Event(e *connector.Event) {
|
|||
} else if e.Type == connector.EVENT_LEAVE {
|
||||
fmt.Printf("C E Leave %s %s\n", e.Author, e.Room)
|
||||
} else if e.Type == connector.EVENT_MESSAGE {
|
||||
fmt.Printf("C E Message %s %s %s\n", e.Author, e.Room, e.Message)
|
||||
if strings.Contains(e.Message, "ezbrexit") {
|
||||
fmt.Printf("we have to exit")
|
||||
fmt.Printf("C E Message %s %s %s\n", e.Author, e.Room, e.Text)
|
||||
if strings.Contains(e.Text, "ezbrexit") {
|
||||
fmt.Printf("we have to exit\n")
|
||||
h.exit <- true
|
||||
}
|
||||
} else if e.Type == connector.EVENT_ACTION {
|
||||
fmt.Printf("C E Action %s %s %s\n", e.Author, e.Room, e.Message)
|
||||
fmt.Printf("C E Action %s %s %s\n", e.Author, e.Room, e.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
func testIrc() {
|
||||
irc := &irc.IRC{}
|
||||
h := TmpHandler{
|
||||
exit: make(chan bool),
|
||||
|
@ -70,7 +71,7 @@ func main() {
|
|||
err = irc.Send(&connector.Event{
|
||||
Room: connector.RoomID("#ezbrtest@irc.ulminfo.fr"),
|
||||
Type: connector.EVENT_MESSAGE,
|
||||
Message: "EZBR TEST",
|
||||
Text: "EZBR TEST",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Send: %s", err)
|
||||
|
@ -80,7 +81,7 @@ func main() {
|
|||
err = irc.Send(&connector.Event{
|
||||
Recipient: connector.UserID("lx@irc.ulminfo.fr"),
|
||||
Type: connector.EVENT_MESSAGE,
|
||||
Message: "EZBR TEST direct message lol",
|
||||
Text: "EZBR TEST direct message lol",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Send: %s", err)
|
||||
|
@ -91,3 +92,54 @@ func main() {
|
|||
fmt.Printf("got exit signal\n")
|
||||
irc.Close()
|
||||
}
|
||||
|
||||
func testXmpp() {
|
||||
xmpp := &xmpp.XMPP{}
|
||||
h := TmpHandler{
|
||||
exit: make(chan bool),
|
||||
}
|
||||
xmpp.SetHandler(&h)
|
||||
|
||||
err := xmpp.Configure(connector.Configuration{
|
||||
"server": "jabber.fr",
|
||||
"jid": "ezbr@jabber.fr",
|
||||
"password": "azerty1234",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Connect: %s", err)
|
||||
}
|
||||
|
||||
err = xmpp.Join(connector.RoomID("ezbrtest@muc.linkmauve.fr"))
|
||||
if err != nil {
|
||||
log.Fatalf("Join: %s", err)
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(1)*time.Second)
|
||||
err = xmpp.Send(&connector.Event{
|
||||
Room: connector.RoomID("ezbrtest@muc.linkmauve.fr"),
|
||||
Type: connector.EVENT_MESSAGE,
|
||||
Text: "EZBR TEST",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Send: %s", err)
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(1)*time.Second)
|
||||
err = xmpp.Send(&connector.Event{
|
||||
Recipient: connector.UserID("alexis211@jabber.fr"),
|
||||
Type: connector.EVENT_MESSAGE,
|
||||
Text: "EZBR TEST direct message lol",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Send: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("waiting exit signal\n")
|
||||
<-h.exit
|
||||
fmt.Printf("got exit signal\n")
|
||||
xmpp.Close()
|
||||
}
|
||||
|
||||
func main() {
|
||||
testXmpp()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue