From 12a23984573f444a859e85a0447a7bb8aa0d1feb Mon Sep 17 00:00:00 2001 From: Martin/Geno Date: Wed, 7 Aug 2019 12:27:18 +0200 Subject: [PATCH] add simple mqtt support --- config.go | 4 ++- config.toml | 6 ++++ config_example.toml | 70 ++++++++------------------------------------- main.go | 24 ++++++++++++++-- mqtt/bot.go | 70 +++++++++++++++++++++++++++++++++++++++++++++ mqtt/config.go | 8 ++++++ mqtt/schalter.go | 5 ++++ mqtt/service.go | 38 ++++++++++++++++++++++++ schalter/bot.go | 7 +++-- schalter/main.go | 23 +++++++++++---- 10 files changed, 185 insertions(+), 70 deletions(-) create mode 100644 mqtt/bot.go create mode 100644 mqtt/config.go create mode 100644 mqtt/schalter.go create mode 100644 mqtt/service.go diff --git a/config.go b/config.go index 213ab75..0539985 100644 --- a/config.go +++ b/config.go @@ -3,6 +3,7 @@ package main import ( "github.com/bdlm/std/logger" + "dev.sum7.eu/ccchb/ccchatbot/mqtt" "dev.sum7.eu/ccchb/ccchatbot/schalter" ) @@ -15,5 +16,6 @@ type Config struct { Password string `toml:"password"` } `toml:"xmpp"` - Schalter schalter.Schalter `toml:"schalter"` + Schalter *schalter.Schalter `toml:"schalter"` + MQTT *mqtt.Config `toml:"mqtt"` } diff --git a/config.toml b/config.toml index 21f37bb..b728413 100644 --- a/config.toml +++ b/config.toml @@ -9,3 +9,9 @@ password = "test" url = "https://schalter.ccchb.de/spaceapi.json" interval = 5000000000 mucs = ["ffhb_events@conference.chat.sum7.eu","#ccchb@irc.hackint.org"] + +[mqtt] +broker = "" +client_id = "" +username = "" +password = "" diff --git a/config_example.toml b/config_example.toml index e2ddfba..aac6c29 100644 --- a/config_example.toml +++ b/config_example.toml @@ -1,64 +1,18 @@ log_level = 50 -webserver_bind = ":8080" -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]] -[[hooks.git]] -[[hooks.gitlab]] -[[hooks.circleci]] - -# every hook could have following attributes: -secret = "" -notify_muc = [] -notify_user = [] - -# for handling webhooks from prometheus alertmanager - -[[hooks.prometheus]] - -# for handling webhooks from grafana -# at http://localhost:8080/grafana -# for image support you have to enable `external_image_storage` (e.g. `provider = local`) -# see more at http://docs.grafana.org/installation/configuration/#external-image-storage -[[hooks.grafana]] -secret = "dev.sum7.eu-aShared-Secret" -notify_muc = ["monitoring@conference.chat.sum7.eu"] - -[[hooks.grafana]] -secret = "dev.sum7.eu-aShared-Secret-for important messages" -notify_user = ["user@fireorbit.de"] - - -# for handling webhooks from git software (e.g. gitea, gogs, github) -# at http://localhost:8080/git -[[hooks.git]] -secret = "github-FreifunkBremen-yanic-aShared-Secret" -notify_muc = [] -notify_user = ["user@fireorbit.de"] - -# for handling webhooks from gitlab -# at http://localhost:8080/gitlab -[[hooks.gitlab]] -secret = "dev.sum7.eu-aShared-Secret" -notify_muc = [] -notify_user = ["user@fireorbit.de"] - -# for handling webhooks from circleci -# at http://localhost:8080/circleci -[[hooks.circleci]] -secret = "dev.sum7.eu-aShared-Secret" -notify_muc = [] -notify_user = ["user@fireorbit.de"] +jid = "user@example.org" +password = "password" +[schalter] +url = "https://schalter.ccchb.de/spaceapi.json" +interval = 5000000000 +users = ["annoying@exmaple.com"] +mucs = ["#ccchb@irc.hackint.org"] +[mqtt] +broker = "" +client_id = "" +username = "" +password = "" diff --git a/main.go b/main.go index 9e5fe1f..deea2b6 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "gosrc.io/xmpp" "gosrc.io/xmpp/stanza" + "dev.sum7.eu/ccchb/ccchatbot/mqtt" "dev.sum7.eu/ccchb/ccchatbot/runtime" ) @@ -27,6 +28,11 @@ func main() { log.SetLevel(config.LogLevel) + var mqttService *mqtt.Service + if config.MQTT != nil { + mqttService = mqtt.Connect(config.MQTT) + } + router := xmpp.NewRouter() router.HandleFunc("presence", handlePresence) router.HandleFunc("message", func(s xmpp.Sender, p stanza.Packet) { @@ -35,9 +41,15 @@ func main() { log.Errorf("ignoring wrong routed packet: %T", p) return } - if err := config.Schalter.HandleBotMessage(s, msg); err != nil { - log.Debugf("bot could not handle message: %s", err) + if ok := config.Schalter.HandleBotMessage(s, msg); ok { + return } + if mqttService != nil { + if ok := mqttService.HandleBotMessage(s, msg); ok { + return + } + } + log.Debugf("no bot has handle message of %s: %s", msg.From, msg.Body) }) var err error @@ -47,6 +59,10 @@ func main() { Password: config.XMPP.Password, }, router) + config.Schalter.ChangeEvent = append(config.Schalter.ChangeEvent, config.Schalter.XMPPChangeEvent(client)) + if mqttService != nil { + config.Schalter.ChangeEvent = append(config.Schalter.ChangeEvent, mqttService.HandleSchalterStateChange) + } if err != nil { log.Panicf("error on startup xmpp client: %s", err) } @@ -64,6 +80,10 @@ func main() { signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) sig := <-sigs + if mqttService != nil { + mqttService.Close() + } + config.Schalter.Close() runtime.LeaveAllMUCs(client) diff --git a/mqtt/bot.go b/mqtt/bot.go new file mode 100644 index 0000000..b85685d --- /dev/null +++ b/mqtt/bot.go @@ -0,0 +1,70 @@ +package mqtt + +import ( + "fmt" + + "github.com/mattn/go-shellwords" + "gosrc.io/xmpp" + "gosrc.io/xmpp/stanza" +) + +var botCommands map[string]func(*Service, xmpp.Sender, stanza.Attrs, []string) bool + +func (s *Service) HandleBotMessage(c xmpp.Sender, msg stanza.Message) bool { + msgParts, err := shellwords.Parse(msg.Body) + if err != nil { + return false + } + if len(msgParts) <= 0 || msgParts[0][0] != '.' { + return false + } + command := msgParts[0][1:] + + if f, ok := botCommands[command]; ok { + attrs := stanza.Attrs{To: msg.From, Type: msg.Type} + if msg.Type == stanza.MessageTypeGroupchat { + jid, _ := xmpp.NewJid(msg.From) + attrs.To = jid.Bare() + } + return f(s, c, attrs, msgParts[1:]) + } + return false +} + +func init() { + botCommands = make(map[string]func(*Service, xmpp.Sender, stanza.Attrs, []string) bool) + + botCommands["sub"] = func(s *Service, c xmpp.Sender, replyAttrs stanza.Attrs, leftCommands []string) bool { + c.Send(stanza.Message{ + Attrs: replyAttrs, + Body: "not supported yet:\n.sub ", + }) + return true + } + botCommands["unsub"] = func(s *Service, c xmpp.Sender, replyAttrs stanza.Attrs, leftCommands []string) bool { + c.Send(stanza.Message{ + Attrs: replyAttrs, + Body: "not supported yet:\n.unsub ", + }) + return true + } + botCommands["pub"] = func(s *Service, c xmpp.Sender, replyAttrs stanza.Attrs, leftCommands []string) bool { + if len(leftCommands) != 2 { + c.Send(stanza.Message{ + Attrs: replyAttrs, + Body: "wrong arguments: need format:\n.pub ", + }) + return true + } + topic := leftCommands[0] + payload := leftCommands[1] + + token := s.client.Publish(topic, 0, false, payload) + token.Wait() + c.Send(stanza.Message{ + Attrs: replyAttrs, + Body: fmt.Sprintf("published in %s : %s", topic, payload), + }) + return true + } +} diff --git a/mqtt/config.go b/mqtt/config.go new file mode 100644 index 0000000..49d008c --- /dev/null +++ b/mqtt/config.go @@ -0,0 +1,8 @@ +package mqtt + +type Config struct { + Broker string `toml:"broker"` + ClientID string `toml:"client_id"` + Username string `toml:"username"` + Password string `toml:"passoword"` +} diff --git a/mqtt/schalter.go b/mqtt/schalter.go new file mode 100644 index 0000000..961f8af --- /dev/null +++ b/mqtt/schalter.go @@ -0,0 +1,5 @@ +package mqtt + +func (s *Service) HandleSchalterStateChange(open bool) { + s.client.Publish("ccchb/schalter", 0, true, open) +} diff --git a/mqtt/service.go b/mqtt/service.go new file mode 100644 index 0000000..d1a5e7e --- /dev/null +++ b/mqtt/service.go @@ -0,0 +1,38 @@ +package mqtt + +import ( + "github.com/bdlm/log" + MQTT "github.com/eclipse/paho.mqtt.golang" +) + +type Service struct { + config *Config + client MQTT.Client +} + +func Connect(c *Config) *Service { + if c.Broker == "" { + log.Warn("no broker given, starting without mqtt service") + return nil + } + optsPub := MQTT.NewClientOptions() + optsPub.AddBroker(c.Broker) + optsPub.SetClientID(c.ClientID) + if c.Username != "" { + optsPub.SetUsername(c.Username) + } + if c.Password != "" { + optsPub.SetPassword(c.Password) + } + + clientPub := MQTT.NewClient(optsPub) + if tokenPub := clientPub.Connect(); tokenPub.Wait() && tokenPub.Error() != nil { + log.Panic(tokenPub.Error()) + } + + return &Service{config: c, client: clientPub} +} + +func (s *Service) Close() { + s.client.Disconnect(250) +} diff --git a/schalter/bot.go b/schalter/bot.go index 2f143d0..de9f624 100644 --- a/schalter/bot.go +++ b/schalter/bot.go @@ -7,7 +7,7 @@ import ( "gosrc.io/xmpp/stanza" ) -func (s *Schalter) HandleBotMessage(c xmpp.Sender, msg stanza.Message) error { +func (s *Schalter) HandleBotMessage(c xmpp.Sender, msg stanza.Message) bool { if msg.Body == ".status" { jid, _ := xmpp.NewJid(msg.From) reply := stanza.Message{ @@ -20,7 +20,8 @@ func (s *Schalter) HandleBotMessage(c xmpp.Sender, msg stanza.Message) error { reply.To = jid.Bare() reply.Body = fmt.Sprintf("%s: %s", jid.Resource, reply.Body) } - return c.Send(reply) + c.Send(reply) + return true } - return fmt.Errorf("not handled by this bot: %v", msg) + return false } diff --git a/schalter/main.go b/schalter/main.go index adfe302..10c695e 100644 --- a/schalter/main.go +++ b/schalter/main.go @@ -23,6 +23,8 @@ type Schalter struct { Interval time.Duration `toml:"interval"` + ChangeEvent []func(bool) + state bool spaceName string worker *worker.Worker @@ -61,19 +63,28 @@ func (s *Schalter) Start(c xmpp.Sender) { func (s *Schalter) run(c xmpp.Sender) func() { return func() { if s.fetchState() { - s.updatePresence(c) - text := fmt.Sprintf("%s changed to closed", s.spaceName) - if s.state { - text = fmt.Sprintf("%s changed to open", s.spaceName) + for _, f := range s.ChangeEvent { + f(s.state) } - runtime.SendText(c, s.Users, s.MUCs, text, text) - log.Infof("worker detect changes of status: %s", text) + + log.Infof("worker detect changes of status: %s", s.stateString()) } else { log.Debug("worker run, but no changes detected") } } } +func (s *Schalter) XMPPChangeEvent(c xmpp.Sender) func(bool) { + return func(state bool) { + s.updatePresence(c) + text := fmt.Sprintf("%s changed to closed", s.spaceName) + if state { + text = fmt.Sprintf("%s changed to open", s.spaceName) + } + runtime.SendText(c, s.Users, s.MUCs, text, text) + } +} + func (s *Schalter) Close() { if s.worker != nil { s.worker.Close()