From 506f42f93b300b4ffb12c79fb2ee395d11a687f6 Mon Sep 17 00:00:00 2001 From: Skip R Date: Thu, 11 Dec 2025 19:17:57 -0800 Subject: [PATCH] bridge basic emoji reactions from gateway to matrix --- pkg/connector/client.go | 12 +++-- pkg/connector/handlediscord.go | 89 ++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 4 deletions(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 5fd5f08..4d3e333 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -234,14 +234,18 @@ func makeChannelAvatar(ch *discordgo.Channel) *bridgev2.Avatar { } } -func (d *DiscordClient) makeEventSender(user *discordgo.User) bridgev2.EventSender { +func (d *DiscordClient) makeEventSenderWithID(userID string) bridgev2.EventSender { return bridgev2.EventSender{ - IsFromMe: user.ID == d.Session.State.User.ID, - SenderLogin: networkid.UserLoginID(user.ID), - Sender: networkid.UserID(user.ID), + IsFromMe: userID == d.Session.State.User.ID, + SenderLogin: networkid.UserLoginID(userID), + Sender: networkid.UserID(userID), } } +func (d *DiscordClient) makeEventSender(user *discordgo.User) bridgev2.EventSender { + return d.makeEventSenderWithID(user.ID) +} + func (d *DiscordClient) syncChannel(_ context.Context, ch *discordgo.Channel, selfIsInChannel bool) { isGroup := len(ch.RecipientIDs) > 1 diff --git a/pkg/connector/handlediscord.go b/pkg/connector/handlediscord.go index 7aaaf9d..1c68c16 100644 --- a/pkg/connector/handlediscord.go +++ b/pkg/connector/handlediscord.go @@ -18,6 +18,7 @@ package connector import ( "context" + "fmt" "runtime/debug" "github.com/bwmarrin/discordgo" @@ -86,6 +87,79 @@ func (d *DiscordClient) wrapDiscordMessage(evt *discordgo.MessageCreate) Discord } } +type DiscordReaction struct { + *DiscordEventMeta + Reaction *discordgo.MessageReaction + Client *DiscordClient +} + +func (r *DiscordReaction) GetSender() bridgev2.EventSender { + return r.Client.makeEventSenderWithID(r.Reaction.UserID) +} + +func (r *DiscordReaction) GetTargetMessage() networkid.MessageID { + return networkid.MessageID(r.Reaction.MessageID) +} + +func (r *DiscordReaction) GetRemovedEmojiID() networkid.EmojiID { + return networkid.EmojiID(r.Reaction.Emoji.Name) +} + +var ( + _ bridgev2.RemoteReaction = (*DiscordReaction)(nil) + _ bridgev2.RemoteReactionRemove = (*DiscordReaction)(nil) + _ bridgev2.RemoteReactionWithExtraContent = (*DiscordReaction)(nil) +) + +func (r *DiscordReaction) GetReactionEmoji() (string, networkid.EmojiID) { + // name is either a grapheme cluster consisting of a Unicode emoji, or the + // name of a custom emoji. + name := r.Reaction.Emoji.Name + return name, networkid.EmojiID(name) +} + +func (r *DiscordReaction) GetReactionExtraContent() map[string]any { + extra := make(map[string]any) + + reaction := r.Reaction + emoji := reaction.Emoji + + if emoji.ID != "" { + // The emoji is a custom emoji. + + extra["fi.mau.discord.reaction"] = map[string]any{ + "id": emoji.ID, + "name": emoji.Name, + // FIXME Handle custom emoji. + // "mxc": reaction, + } + + wrappedShortcode := fmt.Sprintf(":%s:", reaction.Emoji.Name) + extra["com.beeper.reaction.shortcode"] = wrappedShortcode + } + + return extra +} + +func (d *DiscordClient) wrapDiscordReaction(reaction *discordgo.MessageReaction, beingAdded bool) DiscordReaction { + evtType := bridgev2.RemoteEventReaction + if !beingAdded { + evtType = bridgev2.RemoteEventReactionRemove + } + + return DiscordReaction{ + DiscordEventMeta: &DiscordEventMeta{ + Type: evtType, + PortalKey: networkid.PortalKey{ + ID: networkid.PortalID(reaction.ChannelID), + Receiver: d.UserLogin.ID, + }, + }, + Reaction: reaction, + Client: d, + } +} + func (d *DiscordClient) handleDiscordEvent(rawEvt any) { if d.UserLogin == nil { // Our event handlers are able to assume that a UserLogin is available. @@ -97,6 +171,13 @@ func (d *DiscordClient) handleDiscordEvent(rawEvt any) { return } + if d.Session == nil || d.Session.State == nil || d.Session.State.User == nil { + // Our event handlers are able to assume that we've fully connected to the + // gateway. + d.UserLogin.Log.Debug().Msg("Dropping Discord event received before READY or RESUMED") + return + } + defer func() { err := recover() if err != nil { @@ -115,6 +196,14 @@ func (d *DiscordClient) handleDiscordEvent(rawEvt any) { case *discordgo.MessageCreate: wrappedEvt := d.wrapDiscordMessage(evt) d.UserLogin.Bridge.QueueRemoteEvent(d.UserLogin, &wrappedEvt) + case *discordgo.MessageReactionAdd: + wrappedEvt := d.wrapDiscordReaction(evt.MessageReaction, true) + d.UserLogin.Bridge.QueueRemoteEvent(d.UserLogin, &wrappedEvt) + case *discordgo.MessageReactionRemove: + wrappedEvt := d.wrapDiscordReaction(evt.MessageReaction, false) + d.UserLogin.Bridge.QueueRemoteEvent(d.UserLogin, &wrappedEvt) + // TODO case *discordgo.MessageReactionRemoveAll: + // TODO case *discordgo.MessageReactionRemoveEmoji: (needs impl. in discordgo) case *discordgo.PresenceUpdate: return case *discordgo.Event: