From 4bdb0de5596a026cf47178ff5351d737f7fb897c Mon Sep 17 00:00:00 2001 From: Skip R Date: Thu, 5 Feb 2026 21:05:04 -0800 Subject: [PATCH] discordid,connector: remember which guilds were bridged --- pkg/connector/client.go | 26 ++++++++++++++++++- pkg/connector/provisioning.go | 49 ++++++++++++++++++++++++++++++++--- pkg/discordid/dbmeta.go | 1 + 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 9ed9797..718f171 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "io" + "maps" "net/http" "slices" "sync" @@ -276,8 +277,30 @@ func (d *DiscordClient) syncGuildSpace(_ context.Context, guild *discordgo.Guild }) } +// bridgedGuildIDs returns a set of guild IDs that should be bridged. Note that +// presence in the returned set does not imply that the rooms for the guild have +// already been created. +func (d *DiscordClient) bridgedGuildIDs() map[string]struct{} { + meta := d.UserLogin.Metadata.(*discordid.UserLoginMetadata) + bridgingGuildIDs := map[string]struct{}{} + + // guilds that were bridged via the provisioning api + for guildID, bridged := range meta.BridgedGuildIDs { + if bridged { + bridgingGuildIDs[guildID] = struct{}{} + } + } + + // guilds that were declared in the configuration file + for _, guildID := range d.connector.Config.Guilds.BridgingGuildIDs { + bridgingGuildIDs[guildID] = struct{}{} + } + + return bridgingGuildIDs +} + func (d *DiscordClient) syncGuilds(ctx context.Context) { - guildIDs := d.connector.Config.Guilds.BridgingGuildIDs + guildIDs := slices.Sorted(maps.Keys(d.bridgedGuildIDs())) for _, guildID := range guildIDs { log := zerolog.Ctx(ctx).With(). @@ -298,6 +321,7 @@ func (d *DiscordClient) bridgeGuild(ctx context.Context, guildID string) error { guild, err := d.Session.State.Guild(guildID) if errors.Is(err, discordgo.ErrStateNotFound) || guild == nil { log.Err(err).Msg("Couldn't find guild, user isn't a member?") + // TODO likely left/kicked/banned from guild; nuke the portals return errors.New("couldn't find guild in state") } diff --git a/pkg/connector/provisioning.go b/pkg/connector/provisioning.go index 0b2d56e..07e9887 100644 --- a/pkg/connector/provisioning.go +++ b/pkg/connector/provisioning.go @@ -117,19 +117,33 @@ func (p *ProvisioningAPI) makeHandler(handler func(http.ResponseWriter, *http.Re } func (p *ProvisioningAPI) guildsList(w http.ResponseWriter, r *http.Request, login *bridgev2.UserLogin, client *DiscordClient) { + ctx := r.Context() p.log.Info().Str("login_id", discordid.ParseUserLoginID(login.ID)).Msg("guilds list requested via provisioning api") var resp respGuildsList resp.Guilds = []guildEntry{} for _, guild := range client.Session.State.Guilds { + portalKey := client.guildPortalKeyFromID(guild.ID) + portal, err := p.connector.Bridge.GetExistingPortalByKey(ctx, portalKey) + if err != nil { + p.log.Err(err). + Str("guild_id", guild.ID). + Msg("Failed to get guild portal for provisioning list") + } + + mxid := "" + if portal != nil { + mxid = portal.MXID.String() + } + resp.Guilds = append(resp.Guilds, guildEntry{ ID: guild.ID, Name: guild.Name, AvatarURL: discordgo.EndpointGuildIcon(guild.ID, guild.Icon), + MXID: mxid, BridgingMode: "everything", - - Available: !guild.Unavailable, + Available: !guild.Unavailable, }) } @@ -148,10 +162,27 @@ func (p *ProvisioningAPI) bridgeGuild(w http.ResponseWriter, r *http.Request, lo Str("guild_id", guildID). Msg("requested to bridge guild via provisioning api") - // TODO detect guild already bridged + meta := login.Metadata.(*discordid.UserLoginMetadata) + + if meta.BridgedGuildIDs == nil { + meta.BridgedGuildIDs = map[string]bool{} + } + _, alreadyBridged := meta.BridgedGuildIDs[guildID] + meta.BridgedGuildIDs[guildID] = true + + if err := login.Save(r.Context()); err != nil { + p.log.Err(err).Msg("Failed to save login after guild bridge request") + mautrix.MUnknown.WithMessage("failed to save login: %v", err).Write(w) + return + } + go client.bridgeGuild(context.TODO(), guildID) - exhttp.WriteJSONResponse(w, 201, nil) + responseStatus := 201 + if alreadyBridged { + responseStatus = 200 + } + exhttp.WriteJSONResponse(w, responseStatus, nil) } func (p *ProvisioningAPI) unbridgeGuild(w http.ResponseWriter, r *http.Request, login *bridgev2.UserLogin, client *DiscordClient) { @@ -166,6 +197,16 @@ func (p *ProvisioningAPI) unbridgeGuild(w http.ResponseWriter, r *http.Request, Str("guild_id", guildID). Msg("requested to unbridge guild via provisioning api") + meta := login.Metadata.(*discordid.UserLoginMetadata) + if meta.BridgedGuildIDs != nil { + delete(meta.BridgedGuildIDs, guildID) + } + if err := login.Save(r.Context()); err != nil { + p.log.Err(err).Msg("Failed to save login after guild unbridge request") + mautrix.MUnknown.WithMessage("failed to save login: %v", err).Write(w) + return + } + ctx := context.TODO() portalKey := client.guildPortalKeyFromID(guildID) diff --git a/pkg/discordid/dbmeta.go b/pkg/discordid/dbmeta.go index 1f2b939..a5eb9c0 100644 --- a/pkg/discordid/dbmeta.go +++ b/pkg/discordid/dbmeta.go @@ -30,4 +30,5 @@ type PortalMetadata struct { type UserLoginMetadata struct { Token string `json:"token"` HeartbeatSession discordgo.HeartbeatSession `json:"heartbeat_session"` + BridgedGuildIDs map[string]bool `json:"bridged_guild_ids,omitempty"` }