From bfebeeb7e5d1f22eb1bcb5d4beb031a0650ccc0a Mon Sep 17 00:00:00 2001 From: Skip R Date: Thu, 8 Jan 2026 16:11:26 -0800 Subject: [PATCH] handlematrix: bridge outgoing typing events --- pkg/connector/client.go | 16 ++++++++--- pkg/connector/handlematrix.go | 53 ++++++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 13eae65..92c0dab 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -23,6 +23,7 @@ import ( "io" "net/http" "slices" + "sync" "time" "github.com/bwmarrin/discordgo" @@ -36,11 +37,15 @@ import ( ) type DiscordClient struct { - connector *DiscordConnector - usersFromReady map[string]*discordgo.User - UserLogin *bridgev2.UserLogin - Session *discordgo.Session + connector *DiscordConnector + usersFromReady map[string]*discordgo.User + UserLogin *bridgev2.UserLogin + Session *discordgo.Session + hasBegunSyncing bool + + markedOpened map[string]time.Time + markedOpenedLock sync.Mutex } func (d *DiscordConnector) LoadUserLogin(ctx context.Context, login *bridgev2.UserLogin) error { @@ -73,6 +78,7 @@ var _ bridgev2.NetworkAPI = (*DiscordClient)(nil) // nil may be passed for meta, especially during provisioning where we need to // connect to the Discord gateway, but don't have a UserLogin yet. func (d *DiscordClient) SetUp(ctx context.Context, meta *UserLoginMetadata) { + // TODO: Turn this into a factory function like `NewDiscordClient`. log := zerolog.Ctx(ctx) // We'll have UserLogin metadata if this UserLogin is being loaded from the @@ -85,6 +91,8 @@ func (d *DiscordClient) SetUp(ctx context.Context, meta *UserLoginMetadata) { meta.HeartbeatSession.BumpLastUsed() d.Session.HeartbeatSession = meta.HeartbeatSession } + + d.markedOpened = make(map[string]time.Time) } func (d *DiscordClient) Connect(ctx context.Context) { diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index 5df997b..5335a87 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -18,8 +18,10 @@ package connector import ( "context" + "time" "github.com/bwmarrin/discordgo" + "github.com/rs/zerolog" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/database" "maunium.net/go/mautrix/bridgev2/networkid" @@ -147,7 +149,7 @@ func (d *DiscordClient) HandleMatrixReadReceipt(ctx context.Context, msg *bridge } } - // TODO: Support guilds. + // TODO: Support guilds and threads. channelID := string(msg.Portal.ID) resp, err := d.Session.ChannelMessageAckNoToken(channelID, targetMessageID, discordgo.WithChannelReferer("", channelID)) if err != nil { @@ -164,7 +166,50 @@ func (d *DiscordClient) HandleMatrixReadReceipt(ctx context.Context, msg *bridge return nil } -func (d *DiscordClient) HandleMatrixTyping(ctx context.Context, msg *bridgev2.MatrixTyping) error { - //TODO implement me - panic("implement me") +func (d *DiscordClient) viewingChannel(ctx context.Context, portal *bridgev2.Portal) error { + // TODO: When guilds are supported, explicitly bail out if this method is called with a guild channel. + d.markedOpenedLock.Lock() + defer d.markedOpenedLock.Unlock() + + channelID := string(portal.ID) + log := zerolog.Ctx(ctx).With(). + Str("channel_id", channelID).Logger() + + lastMarkedOpenedTs := d.markedOpened[channelID] + if lastMarkedOpenedTs.IsZero() { + d.markedOpened[channelID] = time.Now() + + err := d.Session.MarkViewing(channelID) + + if err != nil { + log.Error().Err(err).Msg("Failed to mark user as viewing channel") + return err + } + + log.Trace().Msg("Marked channel as being viewed") + } else { + log.Trace().Str("channel_id", channelID). + Msg("Already marked channel as viewed, not doing so") + } + + return nil +} + +func (d *DiscordClient) HandleMatrixTyping(ctx context.Context, msg *bridgev2.MatrixTyping) error { + log := zerolog.Ctx(ctx) + + // Don't mind if this fails. + _ = d.viewingChannel(ctx, msg.Portal) + + channelID := string(msg.Portal.ID) + // TODO: Support guilds and threads properly when sending the referer. + err := d.Session.ChannelTyping(channelID, discordgo.WithChannelReferer("", channelID)) + + if err != nil { + log.Warn().Err(err).Msg("Failed to mark user as typing") + return err + } + + log.Debug().Msg("Marked user as typing") + return nil }