commit 15a0eede375dc180bea3e77b649426f10f011302
Author: rani <clagv.randomgames@gmail.com>
Date: Wed Feb 18 19:29:43 2026 +0000
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) {
-
- return "↪ " + nick +
+ prefix := "> ↪ "
+ if outProtocol == "discord" { prefix = "> -# ↪ " }
+ return prefix + nick +
@@ -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 (.*?): (.*?)\)$`
+ )
}