package xmpp import ( "strings" xmpp_client "dev.sum7.eu/genofire/yaja/client" xmpp "dev.sum7.eu/genofire/yaja/xmpp" "dev.sum7.eu/genofire/yaja/xmpp/base" log "github.com/sirupsen/logrus" "dev.sum7.eu/genofire/logmania/bot" "dev.sum7.eu/genofire/logmania/database" "dev.sum7.eu/genofire/logmania/lib" "dev.sum7.eu/genofire/logmania/notify" ) const ( proto = "xmpp" protoGroup = "xmpp-muc" nickname = "logmania" ) var logger = log.WithField("notify", proto) type Notifier struct { notify.Notifier defaults []*database.Notify client *xmpp_client.Client channels map[string]bool formatter log.Formatter } func Init(config *lib.NotifyConfig, db *database.DB, bot *bot.Bot) notify.Notifier { channels := make(map[string]bool) client, err := xmpp_client.NewClient(xmppbase.NewJID(config.XMPP.JID), config.XMPP.Password) if err != nil { logger.Error(err) return nil } go func() { for { if err := client.Start(); err != nil { log.Warn("close connection, try reconnect") client.Connect(config.XMPP.Password) } else { log.Warn("closed connection") return } } }() go func() { for { element, more := client.Recv() if !more { log.Warn("could not receive new message, try later") continue } switch element.(type) { case *xmpp.PresenceClient: pres := element.(*xmpp.PresenceClient) sender := pres.From logPres := logger.WithField("from", sender.Full()) switch pres.Type { case xmpp.PresenceTypeSubscribe: logPres.Debugf("recv presence subscribe") pres.Type = xmpp.PresenceTypeSubscribed pres.To = sender pres.From = nil client.Send(pres) logPres.Debugf("accept new subscribe") pres.Type = xmpp.PresenceTypeSubscribe pres.ID = "" client.Send(pres) logPres.Info("request also subscribe") case xmpp.PresenceTypeSubscribed: logPres.Info("recv presence accepted subscribe") case xmpp.PresenceTypeUnsubscribe: logPres.Info("recv presence remove subscribe") case xmpp.PresenceTypeUnsubscribed: logPres.Info("recv presence removed subscribe") case xmpp.PresenceTypeUnavailable: logPres.Debug("recv presence unavailable") case "": logPres.Debug("recv empty presence, maybe from joining muc") continue default: logPres.Warnf("recv presence unsupported: %s -> %s", pres.Type, xmpp.XMLChildrenString(pres)) } case *xmpp.MessageClient: msg := element.(*xmpp.MessageClient) from := msg.From.Bare().String() if msg.Type == xmpp.MessageTypeGroupchat { from = protoGroup + ":" + from } else { from = proto + ":" + from } bot.Handle(func(answer string) { to := msg.From if msg.Type == xmpp.MessageTypeGroupchat && !to.IsBare() { to = to.Bare() } err := client.Send(&xmpp.MessageClient{ Type: msg.Type, To: to, Body: answer, }) if err != nil { logger.Error("xmpp to ", msg.From.String(), " error:", err) } }, from, msg.Body) } } }() for toAddr, toAddresses := range db.NotifiesByAddress { if toAddresses.Protocol == protoGroup { toJID := xmppbase.NewJID(toAddresses.To) toJID.Resource = nickname err := client.Send(&xmpp.PresenceClient{ To: toJID, }) if err != nil { logger.Error("xmpp could not join ", toJID.String(), " error:", err) } else { channels[toAddr] = true } } } logger.WithField("jid", config.XMPP.JID).Info("startup") var defaults []*database.Notify for to, muc := range config.XMPP.Defaults { def := &database.Notify{ Protocol: proto, To: to, } if muc { def.Protocol = protoGroup } defaults = append(defaults, def) } return &Notifier{ channels: channels, defaults: defaults, client: client, formatter: &log.TextFormatter{ DisableTimestamp: true, }, } } func (n *Notifier) Default() []*database.Notify { return n.defaults } func (n *Notifier) Send(e *log.Entry, to *database.Notify) bool { textByte, err := n.formatter.Format(e) if err != nil { logger.Error("during format notify", err) return false } text := strings.TrimRight(to.RunReplace(string(textByte)), "\n") if to.Protocol == protoGroup { if _, ok := n.channels[to.To]; ok { toJID := xmppbase.NewJID(to.To) toJID.Resource = nickname err := n.client.Send(&xmpp.PresenceClient{ To: toJID, }) if err != nil { logger.Error("xmpp could not join ", toJID.String(), " error:", err) } else { n.channels[to.To] = true } } err := n.client.Send(&xmpp.MessageClient{ Type: xmpp.MessageTypeGroupchat, To: xmppbase.NewJID(to.To), Body: text, }) if err != nil { logger.Error("xmpp to ", to.To, " error:", err) } return true } if to.Protocol == proto { err := n.client.Send(&xmpp.MessageClient{ Type: xmpp.MessageTypeChat, To: xmppbase.NewJID(to.To), Body: text, }) if err != nil { logger.Error("xmpp to ", to, " error:", err) } return true } return false } func (n *Notifier) Close() { for jid := range n.channels { toJID := xmppbase.NewJID(jid) toJID.Resource = nickname err := n.client.Send(&xmpp.PresenceClient{ To: toJID, Type: xmpp.PresenceTypeUnavailable, }) if err != nil { logger.Error("xmpp could not leave ", toJID.String(), " error:", err) } } n.client.Close() } func init() { notify.AddNotifier(Init) }