Skip to content

Commit 2b2c771

Browse files
committed
[WIP] Reactions
Only XMPP<->Discord at the moment.
1 parent f8cb9ab commit 2b2c771

7 files changed

Lines changed: 95 additions & 14 deletions

File tree

bridge/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const (
3030
EventUserTyping = "user_typing"
3131
EventGetChannelMembers = "get_channel_members"
3232
EventNoticeIRC = "notice_irc"
33+
EventReaction = "reaction"
3334
)
3435

3536
const ParentIDNotFound = "msg-parent-not-found"
@@ -46,6 +47,7 @@ type Message struct {
4647
Gateway string `json:"gateway"`
4748
ParentID string `json:"parent_id"`
4849
ParentText string `json:"parent_text"`
50+
Reactions []string `json:"reactions"`
4951
Timestamp time.Time `json:"timestamp"`
5052
ID string `json:"id"`
5153
Extra map[string][]interface{}

bridge/discord/discord.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ func (b *Bdiscord) Connect() error {
226226
b.c.AddHandler(b.messageCreate)
227227
b.c.AddHandler(b.messageTyping)
228228
b.c.AddHandler(b.messageUpdate)
229+
b.c.AddHandler(b.messageReaction)
229230
b.c.AddHandler(b.messageDelete)
230231
b.c.AddHandler(b.messageDeleteBulk)
231232
b.c.AddHandler(b.memberAdd)
@@ -278,7 +279,7 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
278279

279280
// Use webhook to send the message
280281
useWebhooks := b.shouldMessageUseWebhooks(&msg)
281-
if useWebhooks && msg.Event != config.EventMsgDelete && msg.ParentID == "" {
282+
if useWebhooks && msg.Event != config.EventReaction && msg.Event != config.EventMsgDelete && msg.ParentID == "" {
282283
return b.handleEventWebhook(&msg, channelID)
283284
}
284285

@@ -289,6 +290,22 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
289290
func (b *Bdiscord) handleEventBotUser(msg *config.Message, channelID string) (string, error) {
290291
b.Log.Debugf("Broadcasting using token (API)")
291292

293+
if msg.Event == config.EventReaction {
294+
// this is a reaction, not a message
295+
b.Log.Debugf("Sending reactions: %#v", msg.Reactions)
296+
for _, reaction := range msg.Reactions {
297+
// TODO: should we verify that reaction is, in fact, a single emoji?
298+
// Also: Discord has server-private emojis named like "hello:1234567654321"; can these be bridged in some generic way?
299+
b.Log.Debugf("MessageReactionAdd(%#v, %#v, %#v)", channelID, msg.ParentID, reaction)
300+
err := b.c.MessageReactionAdd(channelID, msg.ParentID, reaction)
301+
if err != nil {
302+
return "", err
303+
}
304+
}
305+
// reactions don't get an ID string
306+
return "", nil
307+
}
308+
292309
// Delete message
293310
if msg.Event == config.EventMsgDelete {
294311
if msg.ID == "" {

bridge/discord/handlers.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,34 @@ import (
88
"github.com/matterbridge-org/matterbridge/bridge/config"
99
)
1010

11+
func (b *Bdiscord) messageReaction(s *discordgo.Session, m *discordgo.MessageReactionAdd) { //nolint:unparam
12+
b.Log.Debugf("Got a Discord Reaction:i %#v", m)
13+
b.Log.Debugf("emoji = %#v", m.Emoji)
14+
15+
if m.GuildID != b.guildID {
16+
b.Log.Debugf("Ignoring messageDelete because it originates from a different guild")
17+
return
18+
}
19+
20+
var reaction string
21+
if m.Emoji.ID != "" {
22+
// Discord has server-private emojis named with an ID
23+
// Bridged these with a generic, neutral emoji.
24+
// XXX chosing the neutralest of emojis might be .. hard.
25+
reaction = "⬛"
26+
} else {
27+
reaction = m.Emoji.Name
28+
}
29+
30+
// TODO: we need to bridge m.Emoji.User.Username or m.UserID
31+
rmsg := config.Message{Account: b.Account, Event: config.EventReaction, ParentID: m.MessageID, Reactions: []string{reaction}}
32+
rmsg.Channel = b.getChannelName(m.ChannelID)
33+
34+
b.Log.Debugf("<= Sending reaction from %s to gateway", b.Account)
35+
b.Log.Debugf("<= Message is %#v", rmsg)
36+
b.Remote <- rmsg
37+
}
38+
1139
func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) { //nolint:unparam
1240
if m.GuildID != b.guildID {
1341
b.Log.Debugf("Ignoring messageDelete because it originates from a different guild")

bridge/xmpp/xmpp.go

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ type Bxmpp struct {
3434
xc *xmpp.Client
3535
xmppMap map[string]string
3636
connected bool
37-
stanzaIDs *lru.Cache[string, string]
38-
replyHeaders *lru.Cache[string, xmpp.Reply]
37+
stanzaIDs *lru.Cache[string, string] // stanzaID -> ID
38+
replyHeaders *lru.Cache[string, xmpp.Reply] // ID -> Reply{stanzaID, to}
3939
sync.RWMutex
4040

4141
avatarAvailability map[string]bool
@@ -158,9 +158,27 @@ func (b *Bxmpp) Send(msg config.Message) (string, error) {
158158

159159
// XEP-0461: populate reply fields if this message is a reply.
160160
var reply *xmpp.Reply
161+
var reactions *xmpp.Reactions
161162
if msg.ParentValid() {
162-
if _reply, ok := b.replyHeaders.Get(msg.ParentID); ok {
163-
reply = &_reply
163+
// either a Reaction or a Reply
164+
//
165+
if msg.Event == config.EventReaction {
166+
b.Log.Debugf("relaying a Reaction; the reactions are %#v", msg.Reactions)
167+
if _reply, ok := b.replyHeaders.Get(msg.ParentID); ok {
168+
reactions = &xmpp.Reactions{
169+
ID: _reply.ID,
170+
Reactions: msg.Reactions,
171+
}
172+
}
173+
174+
// XXX TODO: XEP-0444 says an update requires a *full* update for all reactions from a given user.
175+
// since the bridge is the source of ALL reactions for all users on the other side,
176+
// it needs to track what has been sent and *resend all of them*
177+
// also it's going to lose past reactions messages...
178+
} else {
179+
if _reply, ok := b.replyHeaders.Get(msg.ParentID); ok {
180+
reply = &_reply
181+
}
164182
}
165183
}
166184

@@ -169,11 +187,12 @@ func (b *Bxmpp) Send(msg config.Message) (string, error) {
169187
// Generate a dummy ID because to avoid collision with other internal messages
170188
msgID := xid.New().String()
171189
if _, err := b.xc.Send(xmpp.Chat{
172-
Type: "groupchat",
173-
Remote: msg.Channel + "@" + b.GetString("Muc"),
174-
Text: msg.Username + msg.Text,
175-
OriginID: msgID,
176-
Reply: reply,
190+
Type: "groupchat",
191+
Remote: msg.Channel + "@" + b.GetString("Muc"),
192+
Text: msg.Username + msg.Text,
193+
OriginID: msgID,
194+
Reply: reply,
195+
Reactions: reactions,
177196
}); err != nil {
178197
return "", err
179198
}
@@ -362,6 +381,17 @@ func (b *Bxmpp) handleXMPP() error {
362381
parentText = v.Reply.Quote
363382
}
364383

384+
// If there was a <reactions> // ....
385+
var reactions []string
386+
if v.Reactions != nil {
387+
if _parentID, ok := b.stanzaIDs.Get(v.Reactions.ID); ok {
388+
parentID = _parentID
389+
b.Log.Debugf("Got reactions: %#v", v.Reactions)
390+
reactions = append(reactions, v.Reactions.Reactions...) // XXX is the append() necessary?
391+
event = config.EventReaction
392+
}
393+
}
394+
365395
rmsg := config.Message{
366396
Username: b.parseNick(v.Remote),
367397
Text: v.Text,
@@ -373,6 +403,7 @@ func (b *Bxmpp) handleXMPP() error {
373403
Event: event,
374404
ParentID: parentID,
375405
ParentText: parentText,
406+
Reactions: reactions,
376407
}
377408

378409
// Check if we have an action event.
@@ -499,7 +530,7 @@ func (b *Bxmpp) skipMessage(message xmpp.Chat) bool {
499530
}
500531

501532
// skip empty messages
502-
if message.Text == "" {
533+
if message.Text == "" && message.Reactions == nil {
503534
return true
504535
}
505536

gateway/gateway.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,9 @@ func (gw *Gateway) ignoreTextEmpty(msg *config.Message) bool {
291291
if msg.Text != "" {
292292
return false
293293
}
294+
if msg.Event == config.EventReaction {
295+
return false
296+
}
294297
if msg.Event == config.EventUserTyping {
295298
return false
296299
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,4 @@ require (
159159

160160
go 1.25.0
161161

162-
replace github.com/xmppo/go-xmpp => github.com/sh4sh/go-xmpp v0.0.0-20260306054259-9e62a00d8f50
162+
replace github.com/xmppo/go-xmpp => github.com/sh4sh/go-xmpp v0.0.0-20260308194659-485ad27aa55a

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,8 +313,8 @@ github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj
313313
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
314314
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
315315
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
316-
github.com/sh4sh/go-xmpp v0.0.0-20260306054259-9e62a00d8f50 h1:rhsp1fFzf4hnYUY4E83bpzDprbGyg7oXU0CHj5rwWAk=
317-
github.com/sh4sh/go-xmpp v0.0.0-20260306054259-9e62a00d8f50/go.mod h1:U1T7Fv9GY+8pJpOwEpNII37JLngt31TiRHzHV3m66tc=
316+
github.com/sh4sh/go-xmpp v0.0.0-20260308194659-485ad27aa55a h1:/Id+09ZFYZGdBqpVqMUTrRznLUpD66dOunIWeRiqMTQ=
317+
github.com/sh4sh/go-xmpp v0.0.0-20260308194659-485ad27aa55a/go.mod h1:U1T7Fv9GY+8pJpOwEpNII37JLngt31TiRHzHV3m66tc=
318318
github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4 h1:zwQ1HBo5FYwn1ksMd19qBCKO8JAWE9wmHivEpkw/DvE=
319319
github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4/go.mod h1:vt2jWY/3Qw1bIzle5thrJWucsLuuX9iUNnp20CqCciI=
320320
github.com/shazow/ssh-chat v1.10.1 h1:ePS+ngEYqm+yUuXegDPutysqLV2WoI22XDOeRgI6CE0=

0 commit comments

Comments
 (0)