Thumbnail

rani/matterbridge.git

Clone URL: https://git.buni.party/rani/matterbridge.git

commit 15a0eede375dc180bea3e77b649426f10f011302 Author: rani <clagv.randomgames@gmail.com> Date: Wed Feb 18 19:29:43 2026 +0000 patch in xmpp replies diff --git a/bridge/config/config.go b/bridge/config/config.go index 744ebfb..efe3884 100644 --- a/bridge/config/config.go +++ b/bridge/config/config.go @@ -2128 +2128 @@ type Protocol struct {   UseTLS bool // IRC   UseDiscriminator bool // discord   UseFirstName bool // telegram - UseUserName bool // discord, matrix, mattermost - UsePerProtocolJID bool // xmpp + UseUserName bool // discord, matrix, mattermost + UsePerProtocolJID bool // xmpp   UseInsecureURL bool // telegram   UserName string // IRC   VerboseJoinPart bool // IRC diff --git a/bridge/xmpp/xmpp.go b/bridge/xmpp/xmpp.go index be97699..c0d585f 100644 --- a/bridge/xmpp/xmpp.go +++ b/bridge/xmpp/xmpp.go @@ -36 +37 @@ package bxmpp  import (   "crypto/sha1"   "encoding/hex" + "encoding/xml"   "fmt"   "strings"   "sync" @@ -156 +1643 @@ import (   "gosrc.io/xmpp/stanza"  )   +const xmppMsgCacheSize = 100 + +type xmppReply struct { + XMLName xml.Name `xml:"urn:xmpp:reply:0 reply"` + ID string `xml:"id,attr"` + To string `xml:"to,attr"` +} + +type xmppStanzaID struct { + XMLName xml.Name `xml:"urn:xmpp:sid:0 stanza-id"` + ID string `xml:"id,attr"` + By string `xml:"by,attr"` +} + +type xmppFallback struct { + XMLName xml.Name `xml:"urn:xmpp:fallback:0 fallback"` + For string `xml:"for,attr"` + Body *xmppFallbackBody `xml:"body"` +} + +type xmppFallbackBody struct { + Start int `xml:"start,attr"` + End int `xml:"end,attr"` +} + +type xmppCachedMessage struct { + ID string + Nick string + Body string +} + +func init() { + stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:reply:0", Local: "reply"}, xmppReply{}) + stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:sid:0", Local: "stanza-id"}, xmppStanzaID{}) + stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:fallback:0", Local: "fallback"}, xmppFallback{}) +} +  type Bxmpp struct {   *bridge.Config   @@ -296 +6711 @@ type Bxmpp struct {   rooms map[string]*mucRoom   connected bool   reconnectCh chan struct{} + + cacheMu sync.RWMutex + cacheRing []string + cacheMap map[string]xmppCachedMessage + cacheIdx int  }    type mucRoom struct { @@ -408 +8310 @@ type mucRoom struct {    func New(cfg *bridge.Config) bridge.Bridger {   return &Bxmpp{ - Config: cfg, - rooms: make(map[string]*mucRoom), + Config: cfg, + rooms: make(map[string]*mucRoom), + cacheRing: make([]string, 0, xmppMsgCacheSize), + cacheMap: make(map[string]xmppCachedMessage, xmppMsgCacheSize),   }  }   @@ -1636 +20816 @@ func (b *Bxmpp) handleMessage(s xmpp.Sender, p stanza.Packet) {   return   }   + text := msg.Body + event := "" + if strings.HasPrefix(text, "/me ") { + text = strings.TrimPrefix(text, "/me ") + event = config.EventUserAction + } + + replyID := b.getReplyID(msg, roomJID) + b.cacheMessage(replyID, nick, text) +   if nick == room.BotNick {   return   } @@ -17012 +2257 @@ func (b *Bxmpp) handleMessage(s xmpp.Sender, p stanza.Packet) {   return   }   - text := msg.Body - event := "" - if strings.HasPrefix(text, "/me ") { - text = strings.TrimPrefix(text, "/me ") - event = config.EventUserAction - } + text = b.handleQuote(msg, text)     rmsg := config.Message{   Username: nick, @@ -1887 +238106 @@ func (b *Bxmpp) handleMessage(s xmpp.Sender, p stanza.Packet) {   b.Remote <- rmsg  }   +func (b *Bxmpp) getReplyID(msg stanza.Message, roomJID string) string { + if msg.Type != stanza.MessageTypeGroupchat { + return "" + } + + var sid xmppStanzaID + if msg.Get(&sid) && sid.ID != "" { + if bareJID(sid.By) == roomJID { + return sid.ID + } + } + return "" +} + +func (b *Bxmpp) stripReplyFallback(msg stanza.Message, text string) string { + var fb xmppFallback + if !msg.Get(&fb) || fb.For != "urn:xmpp:reply:0" || fb.Body == nil { + return text + } + + return removeRuneRange(text, fb.Body.Start, fb.Body.End) +} + +func (b *Bxmpp) cacheMessage(id, nick, body string) { + if id == "" { + return + } + b.cacheMu.Lock() + defer b.cacheMu.Unlock() + + entry := xmppCachedMessage{ID: id, Nick: nick, Body: body} + + if _, exists := b.cacheMap[id]; exists { + b.cacheMap[id] = entry + return + } + + if len(b.cacheRing) < xmppMsgCacheSize { + b.cacheRing = append(b.cacheRing, id) + b.cacheMap[id] = entry + return + } + + evictID := b.cacheRing[b.cacheIdx] + delete(b.cacheMap, evictID) + + b.cacheRing[b.cacheIdx] = id + b.cacheMap[id] = entry + b.cacheIdx = (b.cacheIdx + 1) % xmppMsgCacheSize +} + +func (b *Bxmpp) findCachedMessage(id string) (xmppCachedMessage, bool) { + b.cacheMu.RLock() + defer b.cacheMu.RUnlock() + + msg, ok := b.cacheMap[id] + return msg, ok +} + +func (b *Bxmpp) handleQuote(msg stanza.Message, text string) string { + if b.GetBool("QuoteDisable") { + return text + } + + var reply xmppReply + if !msg.Get(&reply) || reply.ID == "" { + return text + } + b.Log.Debugf("XMPP reply details: id=%s to=%s", reply.ID, reply.To) + + cached, ok := b.findCachedMessage(reply.ID) + if !ok { + return text + } + + text = b.stripReplyFallback(msg, text)   + quoteMessage := cached.Body + quoteNick := cached.Nick + + format := b.GetString("quoteformat") + if format == "" { + format = "{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})" + } + + limit := b.GetInt("QuoteLengthLimit") + if limit != 0 { + runes := []rune(quoteMessage) + if len(runes) > limit { + quoteMessage = string(runes[:limit]) + "..." + } + } + + replacer := strings.NewReplacer( + "{MESSAGE}", text, + "{QUOTENICK}", quoteNick, + "{QUOTEMESSAGE}", quoteMessage, + ) + return replacer.Replace(format) +}    func (b *Bxmpp) ensureRoom(channel string) *mucRoom {   b.mu.Lock() @@ -3886 +53724 @@ func parseRoomNick(from string) (string, string) {   return parts[0], parts[1]  }   +func bareJID(jid string) string { + if i := strings.Index(jid, "/"); i >= 0 { + return jid[:i] + } + return jid +} + +func removeRuneRange(text string, start, end int) string { + if start < 0 || end <= start { + return text + } + runes := []rune(text) + if start > len(runes) || end > len(runes) { + return text + } + return string(append(runes[:start], runes[end:]...)) +} +  func (b *Bxmpp) setConnected(state bool) {   b.Lock()   b.connected = state diff --git a/scripts/outmessage.tengo b/scripts/outmessage.tengo index c2ce556..9a740f4 100644 --- a/scripts/outmessage.tengo +++ b/scripts/outmessage.tengo @@ -1212 +1213 @@ read-write:  text := import("text")  fmt := import("fmt")   -doQuote := func(reply, nick, ctx) { +doQuote_ := func(reply, nick, ctx) { if outProtocol == "xmpp" || outProtocol == "discord" { // split multiline context and prepend lines with a ">" ctx = text.join(text.split(ctx, "\n"), "\n> ") - - return "↪ " + nick + + prefix := "> ↪ " + if outProtocol == "discord" { prefix = "> -# ↪ " } + return prefix + nick + "\n> " + ctx + "\n" + reply } else if outProtocol == "irc" || outProtocol == "telegram" { @@ -286 +2922 @@ doQuote := func(reply, nick, ctx) { }  }   +// match $1 must be the reply +// match $2 must be the nick +// match $3 must be the context +doQuote := func(...reStrs) { + for reStr in reStrs { + re := text.re_compile(reStr) + if re.match(msgText) { + reply := re.replace(msgText, "$1") + nick := re.replace(msgText, "$2") + ctx := re.replace(msgText, "$3") + msgText = doQuote_(reply, nick, ctx) + break + } + } +} +  // start - strip irc colors  // if we're not sending to an irc bridge we strip the IRC colors  if inProtocol == "irc" && outProtocol != "irc" { @@ -4622 +6320 @@ if (inProtocol == "discord" && outProtocol != "discord") {  }    // format discord replies as quotes on xmpp -if (inProtocol == "discord" && outProtocol != "discord") { - re := text.re_compile(`(?ms)(.*?) \(in reply to (.*?): (.*?)\)$`) - if re.match(msgText) { - reply := re.replace(msgText, "$1") - nick := re.replace(msgText, "$2") - ctx := re.replace(msgText, "$3") - msgText = doQuote(reply, nick, ctx) - } +if (inProtocol == "discord") { + doQuote(`(?ms)(.*?) \(in reply to (.*?): (.*?)\)$`)  }   -if (inProtocol == "telegram" && outProtocol != "telegram") { - re := text.re_compile(`(?ms)(.*?) \(in reply to (.*?): <([^>]+)> (.*?)\)$`) - if re.match(msgText) { - reply := re.replace(msgText, "$1") - nick := re.replace(msgText, "$3") - ctx := re.replace(msgText, "$4") - msgText = doQuote(reply, nick, ctx) - } +if (inProtocol == "xmpp") { + doQuote( + `(?ms)(.*?) \(in reply to (.*?) \[b]: (.*?)\)$`, + `(?ms)(.*?) \(in reply to (.*?): (.*?)\)$` + ) +} + +if (inProtocol == "telegram") { + doQuote( + `(?ms)(.*?) \(in reply to lulbridge: <([^>]+)> (.*?)\)$`, + `(?ms)(.*?) \(in reply to (.*?): (.*?)\)$` + )  }