diff --git a/circleci/main.go b/circleci/main.go index 89d4bee..2c585fa 100644 --- a/circleci/main.go +++ b/circleci/main.go @@ -7,7 +7,7 @@ import ( libHTTP "dev.sum7.eu/genofire/golang-lib/http" "github.com/bdlm/log" - xmpp "github.com/mattn/go-xmpp" + "gosrc.io/xmpp" "dev.sum7.eu/genofire/hook2xmpp/runtime" ) @@ -30,7 +30,7 @@ func (r requestBody) String() string { } func init() { - runtime.HookRegister[hookType] = func(client *xmpp.Client, hooks []runtime.Hook) func(w http.ResponseWriter, r *http.Request) { + runtime.HookRegister[hookType] = func(client xmpp.Sender, hooks []runtime.Hook) func(w http.ResponseWriter, r *http.Request) { log.WithField("type", hookType).Info("loaded") return func(w http.ResponseWriter, r *http.Request) { logger := log.WithField("type", hookType) @@ -47,12 +47,14 @@ func init() { }) ok := false + msg := request.String() + for _, hook := range hooks { if request.Payload.VCSURL != hook.Secret { continue } logger.Infof("run hook") - runtime.Notify(client, hook, request.String()) + runtime.Notify(client, hook, msg, msg) ok = true } if !ok { diff --git a/config_example.toml b/config_example.toml index a6a7ab1..e2ddfba 100644 --- a/config_example.toml +++ b/config_example.toml @@ -1,13 +1,16 @@ log_level = 50 webserver_bind = ":8080" -[xmpp] -host = "fireorbit.de" -username = "bot@fireorbit.de" -password = "example" startup_notify_user = ["user@fireorbit.de"] startup_notify_muc = [] +nickname = "logbot" + +[xmpp] +address = "fireorbit.de" +jid = "bot@fireorbit.de" +password = "example" + # suported hooks are, which could be declared multiple times with different `secrets` (see [[hooks.grafana]]): [[hooks.grafana]] [[hooks.prometheus]] diff --git a/git/main.go b/git/main.go index ddaa962..738d47b 100644 --- a/git/main.go +++ b/git/main.go @@ -7,8 +7,8 @@ import ( libHTTP "dev.sum7.eu/genofire/golang-lib/http" "github.com/bdlm/log" - xmpp "github.com/mattn/go-xmpp" "github.com/mitchellh/mapstructure" + "gosrc.io/xmpp" "dev.sum7.eu/genofire/hook2xmpp/runtime" ) @@ -21,7 +21,7 @@ var eventHeader = map[string]string{ const hookType = "git" func init() { - runtime.HookRegister[hookType] = func(client *xmpp.Client, hooks []runtime.Hook) func(w http.ResponseWriter, r *http.Request) { + runtime.HookRegister[hookType] = func(client xmpp.Sender, hooks []runtime.Hook) func(w http.ResponseWriter, r *http.Request) { log.WithField("type", hookType).Info("loaded") return func(w http.ResponseWriter, r *http.Request) { logger := log.WithField("type", hookType) @@ -62,12 +62,14 @@ func init() { }) ok := false + msg := request.String(event) + for _, hook := range hooks { if secret != hook.Secret { continue } logger.Infof("run hook") - runtime.Notify(client, hook, request.String(event)) + runtime.Notify(client, hook, msg, msg) ok = true } if !ok { diff --git a/gitlab/main.go b/gitlab/main.go index a0bc260..ce27460 100644 --- a/gitlab/main.go +++ b/gitlab/main.go @@ -10,7 +10,7 @@ import ( libHTTP "dev.sum7.eu/genofire/golang-lib/http" "github.com/bdlm/log" - xmpp "github.com/mattn/go-xmpp" + "gosrc.io/xmpp" "dev.sum7.eu/genofire/hook2xmpp/runtime" ) @@ -24,7 +24,7 @@ var eventHeader = map[string]string{ const hookType = "gitlab" func init() { - runtime.HookRegister[hookType] = func(client *xmpp.Client, hooks []runtime.Hook) func(w http.ResponseWriter, r *http.Request) { + runtime.HookRegister[hookType] = func(client xmpp.Sender, hooks []runtime.Hook) func(w http.ResponseWriter, r *http.Request) { log.WithField("type", hookType).Info("loaded") return func(w http.ResponseWriter, r *http.Request) { event := r.Header.Get("X-Gitlab-Event") @@ -131,7 +131,7 @@ func init() { continue } logger.Infof("run hook") - runtime.Notify(client, hook, msg) + runtime.Notify(client, hook, msg, msg) ok = true } if !ok { diff --git a/grafana/main.go b/grafana/main.go index 053c108..eb7b15c 100644 --- a/grafana/main.go +++ b/grafana/main.go @@ -6,7 +6,7 @@ import ( libHTTP "dev.sum7.eu/genofire/golang-lib/http" "github.com/bdlm/log" - xmpp "github.com/mattn/go-xmpp" + "gosrc.io/xmpp" "dev.sum7.eu/genofire/hook2xmpp/runtime" ) @@ -39,7 +39,7 @@ func (r requestBody) String() string { } func init() { - runtime.HookRegister[hookType] = func(client *xmpp.Client, hooks []runtime.Hook) func(w http.ResponseWriter, r *http.Request) { + runtime.HookRegister[hookType] = func(client xmpp.Sender, hooks []runtime.Hook) func(w http.ResponseWriter, r *http.Request) { log.WithField("type", hookType).Info("loaded") return func(w http.ResponseWriter, r *http.Request) { logger := log.WithField("type", hookType) @@ -65,12 +65,14 @@ func init() { }) ok = false + msg := request.String() + for _, hook := range hooks { if secret != hook.Secret { continue } - runtime.Notify(client, hook, request.String()) + runtime.Notify(client, hook, msg, msg) if request.ImageURL != "" { runtime.NotifyImage(client, hook, request.ImageURL, request.String()) } else { diff --git a/main.go b/main.go index fdbcbcf..231b06f 100644 --- a/main.go +++ b/main.go @@ -9,7 +9,7 @@ import ( "dev.sum7.eu/genofire/golang-lib/file" "github.com/bdlm/log" - "github.com/mattn/go-xmpp" + "gosrc.io/xmpp" _ "dev.sum7.eu/genofire/hook2xmpp/circleci" _ "dev.sum7.eu/genofire/hook2xmpp/git" @@ -19,39 +19,36 @@ import ( "dev.sum7.eu/genofire/hook2xmpp/runtime" ) +var config = runtime.Config{} + func main() { configFile := "config.conf" flag.StringVar(&configFile, "config", configFile, "path of configuration file") flag.Parse() - config := &runtime.Config{} - - if err := file.ReadTOML(configFile, config); err != nil { + if err := file.ReadTOML(configFile, &config); err != nil { log.WithField("tip", "maybe call me with: hook2xmpp--config /etc/hook2xmpp.conf").Panicf("error on read config: %s", err) } log.SetLevel(config.LogLevel) - // load config - options := xmpp.Options{ - Host: config.XMPP.Host, - User: config.XMPP.Username, - Resource: config.XMPP.Resource, - Password: config.XMPP.Password, - StartTLS: config.XMPP.StartTLS, - NoTLS: config.XMPP.NoTLS, - Debug: config.XMPP.Debug, - Session: config.XMPP.Session, - Status: config.XMPP.Status, - StatusMessage: config.XMPP.StatusMessage, - } - client, err := options.NewClient() + router := xmpp.NewRouter() + var err error + client, err := xmpp.NewClient(xmpp.Config{ + Address: config.XMPP.Address, + Jid: config.XMPP.JID, + Password: config.XMPP.Password, + }, router) + if err != nil { log.Panicf("error on startup xmpp client: %s", err) } - go runtime.Start(client) - + cm := xmpp.NewStreamManager(client, postStartup) + go func() { + err := cm.Run() + log.Panic("closed connection:", err) + }() for hookType, getHandler := range runtime.HookRegister { hooks, ok := config.Hooks[hookType] if ok { @@ -68,45 +65,14 @@ func main() { } }() - var mucs []string - for _, muc := range config.StartupNotifyMuc { - mucs = append(mucs, muc) - client.JoinMUCNoHistory(muc, config.Nickname) - } - for _, hooks := range config.Hooks { - for _, hook := range hooks { - for _, muc := range hook.NotifyMuc { - mucs = append(mucs, muc) - client.JoinMUCNoHistory(muc, config.Nickname) - } - } - } - - notify := func(msg string) { - for _, muc := range config.StartupNotifyMuc { - client.SendHtml(xmpp.Chat{Remote: muc, Type: "groupchat", Text: msg}) - } - for _, user := range config.StartupNotifyUser { - client.SendHtml(xmpp.Chat{Remote: user, Type: "chat", Text: msg}) - } - } - - log.Infof("started hock2xmpp with %s", client.JID()) - notify("startup of hock2xmpp") - // Wait for system signal sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) sig := <-sigs - notify("stopped of hock2xmpp") - - for _, muc := range mucs { - client.LeaveMUC(muc) - } + closeXMPP(client) srv.Close() - client.Close() log.Infof("closed by receiving: %s", sig) } diff --git a/prometheus/main.go b/prometheus/main.go index 116bc9f..0867d9d 100644 --- a/prometheus/main.go +++ b/prometheus/main.go @@ -8,8 +8,8 @@ import ( libHTTP "dev.sum7.eu/genofire/golang-lib/http" "github.com/bdlm/log" - xmpp "github.com/mattn/go-xmpp" "github.com/prometheus/alertmanager/notify/webhook" + "gosrc.io/xmpp" "dev.sum7.eu/genofire/hook2xmpp/runtime" ) @@ -17,7 +17,7 @@ import ( const hookType = "prometheus" func init() { - runtime.HookRegister[hookType] = func(client *xmpp.Client, hooks []runtime.Hook) func(w http.ResponseWriter, r *http.Request) { + runtime.HookRegister[hookType] = func(client xmpp.Sender, hooks []runtime.Hook) func(w http.ResponseWriter, r *http.Request) { log.WithField("type", hookType).Info("loaded") return func(w http.ResponseWriter, r *http.Request) { logger := log.WithField("type", hookType) @@ -51,7 +51,7 @@ func init() { continue } logger.Infof("run hook") - runtime.Notify(client, hook, content) + runtime.Notify(client, hook, content, content) ok = true } if !ok { diff --git a/runtime/config.go b/runtime/config.go index cb470a2..0295d97 100644 --- a/runtime/config.go +++ b/runtime/config.go @@ -7,16 +7,9 @@ type Config struct { WebserverBind string `toml:"webserver_bind"` XMPP struct { - Host string `toml:"host"` - Username string `toml:"username"` - Resource string `toml:"resource"` - Password string `toml:"password"` - Debug bool `toml:"debug"` - NoTLS bool `toml:"no_tls"` - StartTLS bool `toml:"start_tls"` - Session bool `toml:"session"` - Status string `toml:"status"` - StatusMessage string `toml:"status_message"` + Address string `toml:"address"` + JID string `toml:"jid"` + Password string `toml:"password"` } `toml:"xmpp"` Nickname string `toml:"nickname"` diff --git a/runtime/hooks.go b/runtime/hooks.go index 83cddf6..dca595a 100644 --- a/runtime/hooks.go +++ b/runtime/hooks.go @@ -3,10 +3,10 @@ package runtime import ( "net/http" - xmpp "github.com/mattn/go-xmpp" + "gosrc.io/xmpp" ) -type HookHandler func(*xmpp.Client, []Hook) func(http.ResponseWriter, *http.Request) +type HookHandler func(xmpp.Sender, []Hook) func(http.ResponseWriter, *http.Request) var HookRegister map[string]HookHandler diff --git a/runtime/xmpp.go b/runtime/xmpp.go index f06462e..e2c05fa 100644 --- a/runtime/xmpp.go +++ b/runtime/xmpp.go @@ -1,51 +1,70 @@ package runtime import ( - "fmt" - "github.com/bdlm/log" - xmpp "github.com/mattn/go-xmpp" + + "gosrc.io/xmpp" + "gosrc.io/xmpp/stanza" ) -func Start(client *xmpp.Client) { - for { - m, err := client.Recv() - if err != nil { - continue - } - switch v := m.(type) { - case xmpp.Chat: - if v.Type == "chat" { - log.Debugf("from %s: %s", v.Remote, v.Text) - } - if v.Type == "groupchat" { - } - case xmpp.Presence: - // do nothing - } +func NotifyImage(client xmpp.Sender, hook Hook, url string, desc string) { + msg := stanza.Message{ + Attrs: stanza.Attrs{Type: stanza.MessageTypeGroupchat}, + Body: url, + Extensions: []stanza.MsgExtension{ + stanza.OOB{URL: url, Desc: desc}, + }, } -} -func NotifyImage(client *xmpp.Client, hook Hook, url string, desc string) { - msg := fmt.Sprintf(` - %s - - %s - %s - - `, url, url, desc) for _, muc := range hook.NotifyMuc { - client.SendOrg(fmt.Sprintf(msg, muc, "groupchat")) + msg.To = muc + if err := client.Send(msg); err != nil { + log.WithFields(map[string]interface{}{ + "muc": muc, + "url": url, + }).Errorf("error on image notify: %s", err) + } } + + msg.Type = stanza.MessageTypeChat for _, user := range hook.NotifyUser { - client.SendOrg(fmt.Sprintf(msg, user, "chat")) + msg.To = user + if err := client.Send(msg); err != nil { + log.WithFields(map[string]interface{}{ + "user": user, + "url": url, + }).Errorf("error on image notify: %s", err) + } } } -func Notify(client *xmpp.Client, hook Hook, msg string) { + +func Notify(client xmpp.Sender, hook Hook, text, html string) { + msg := stanza.Message{ + Attrs: stanza.Attrs{Type: stanza.MessageTypeGroupchat}, + Body: text, + Extensions: []stanza.MsgExtension{ + stanza.HTML{Body: stanza.HTMLBody{InnerXML: html}}, + }, + } + for _, muc := range hook.NotifyMuc { - client.SendHtml(xmpp.Chat{Remote: muc, Type: "groupchat", Text: msg}) + msg.To = muc + if err := client.Send(msg); err != nil { + log.WithFields(map[string]interface{}{ + "muc": muc, + "text": text, + }).Errorf("error on notify: %s", err) + } } + + msg.Type = stanza.MessageTypeChat for _, user := range hook.NotifyUser { - client.SendHtml(xmpp.Chat{Remote: user, Type: "chat", Text: msg}) + msg.To = user + if err := client.Send(msg); err != nil { + log.WithFields(map[string]interface{}{ + "user": user, + "text": text, + }).Errorf("error on notify: %s", err) + } } } diff --git a/xmpp.go b/xmpp.go new file mode 100644 index 0000000..d2188af --- /dev/null +++ b/xmpp.go @@ -0,0 +1,89 @@ +package main + +import ( + "github.com/bdlm/log" + "gosrc.io/xmpp" + "gosrc.io/xmpp/stanza" +) + +var mucs []string + +func notify(c xmpp.Sender, text string) { + msg := stanza.Message{ + Attrs: stanza.Attrs{Type: stanza.MessageTypeGroupchat}, + Body: text, + } + + for _, muc := range config.StartupNotifyMuc { + msg.To = muc + if err := c.Send(msg); err != nil { + log.WithFields(map[string]interface{}{ + "muc": muc, + "msg": text, + }).Errorf("error on startup notify: %s", err) + } + } + + msg.Type = stanza.MessageTypeChat + for _, user := range config.StartupNotifyUser { + msg.To = user + if err := c.Send(msg); err != nil { + log.WithFields(map[string]interface{}{ + "user": user, + "msg": text, + }).Errorf("error on startup notify: %s", err) + } + } + log.Infof("notify: %s", text) +} + +func joinMUC(c xmpp.Sender, to, nick string) error { + + toJID, err := xmpp.NewJid(to) + if err != nil { + return err + } + toJID.Resource = nick + jid := toJID.Full() + + mucs = append(mucs, jid) + + return c.Send(stanza.Presence{Attrs: stanza.Attrs{To: jid}, + Extensions: []stanza.PresExtension{ + stanza.MucPresence{ + History: stanza.History{MaxStanzas: stanza.NewNullableInt(0)}, + }}, + }) + +} + +func postStartup(c xmpp.Sender) { + for _, muc := range config.StartupNotifyMuc { + if err := joinMUC(c, muc, config.Nickname); err != nil { + log.WithField("muc", muc).Errorf("error on joining muc: %s", err) + } + } + for _, hooks := range config.Hooks { + for _, hook := range hooks { + for _, muc := range hook.NotifyMuc { + if err := joinMUC(c, muc, config.Nickname); err != nil { + log.WithField("muc", muc).Errorf("error on joining muc: %s", err) + } + } + } + } + notify(c, "started hock2xmpp") +} + +func closeXMPP(c xmpp.Sender) { + notify(c, "stopped of hock2xmpp") + + for _, muc := range mucs { + if err := c.Send(stanza.Presence{Attrs: stanza.Attrs{ + To: muc, + Type: stanza.PresenceTypeUnavailable, + }}); err != nil { + log.WithField("muc", muc).Errorf("error on leaving muc: %s", err) + } + } +}