From 6cc62ef1d073d868aa72b5ced8b9ffc818176e80 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Tue, 20 Oct 2026 21:48:31 +0100 Subject: [PATCH 1/2] PLAT-39414 connector: resolve Reddit user IDs without a directory search Clients pass the t2_ ID from search results when starting a DM, which Reddit's user directory never matches, so every new DM failed with "No Reddit user found". Look t2_ IDs up by profile and keep the directory search for usernames. Co-Authored-By: Claude Opus 5.5 --- pkg/connector/ids.go | 4 ++++ pkg/connector/membership.go | 3 +-- pkg/connector/startchat.go | 42 +++++++++++++++++++++++++------------ 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/pkg/connector/ids.go b/pkg/connector/ids.go index 55fbdd0..6627e45 100644 --- a/pkg/connector/ids.go +++ b/pkg/connector/ids.go @@ -25,6 +25,10 @@ func makeUserLoginID(matrixUserID id.UserID) networkid.UserLoginID { return networkid.UserLoginID(makeUserID(matrixUserID)) } +func isRedditUserID(userID networkid.UserID) bool { + return strings.HasPrefix(string(userID), "t2_") && len(userID) > 3 && strings.Trim(string(userID)[3:], "0123456789abcdefghijklmnopqrstuvwxyz") == "" +} + func userIDToMatrix(userID networkid.UserID, homeserver string) id.UserID { return id.UserID(fmt.Sprintf("@%s:%s", userID, matrixServerName(homeserver))) } diff --git a/pkg/connector/membership.go b/pkg/connector/membership.go index 763541b..148a6fb 100644 --- a/pkg/connector/membership.go +++ b/pkg/connector/membership.go @@ -3,7 +3,6 @@ package connector import ( "context" "errors" - "strings" "maunium.net/go/mautrix" "maunium.net/go/mautrix/bridgev2" @@ -55,7 +54,7 @@ func (r *RedditClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2 case *bridgev2.UserLogin: targetID = networkid.UserID(target.ID) } - if !strings.HasPrefix(string(targetID), "t2_") || strings.Trim(string(targetID)[3:], "0123456789abcdefghijklmnopqrstuvwxyz") != "" || len(targetID) <= 3 { + if !isRedditUserID(targetID) { return nil, permanentFailure(event.MessageStatusUnsupported, "only reddit accounts can be invited or removed") } if targetID == r.userID { diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index b85d182..4c91981 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -2,6 +2,7 @@ package connector import ( "context" + "errors" "strings" "go.mau.fi/util/ptr" @@ -18,9 +19,10 @@ var ( _ bridgev2.GroupCreatingNetworkAPI = (*RedditClient)(nil) ) -// ResolveIdentifier turns a Reddit username (with or without "u/" prefix) into -// a ghost + optional DM. It first searches Reddit's user directory, then — -// if createChat is set — opens (or finds) a DM with the resolved user. +// ResolveIdentifier turns a Reddit username (with or without "u/" prefix) or +// t2_ user ID into a ghost + optional DM. Usernames are looked up in Reddit's +// user directory and IDs by profile; if createChat is set, it then opens (or +// finds) a DM with the resolved user. func (r *RedditClient) ResolveIdentifier(ctx context.Context, identifier string, createChat bool) (*bridgev2.ResolveIdentifierResponse, error) { identifier = strings.TrimSpace(identifier) identifier = strings.TrimPrefix(identifier, "u/") @@ -28,18 +30,32 @@ func (r *RedditClient) ResolveIdentifier(ctx context.Context, identifier string, if identifier == "" { return nil, bridgev2.RespError(mautrix.MInvalidParam.WithMessage("Enter a Reddit username.")) } - results, err := r.remote().SearchUsers(ctx, identifier) - if err != nil { - return nil, wrapRedditRespError(err) - } var match *userDirectoryHit - for _, hit := range results.Results { - display := hit.DisplayName - if strings.EqualFold(stripUserPrefix(display), identifier) || strings.EqualFold(string(hit.UserID), "@"+identifier+":reddit.com") { - if match != nil && match.UserID != hit.UserID { - return nil, bridgev2.RespError(mautrix.MInvalidParam.WithMessage("Multiple Reddit users match %q.", identifier)) + if userID := networkid.UserID(identifier); isRedditUserID(userID) { + mxid := userIDToMatrix(userID, "") + profile, err := r.matrix().GetProfile(ctx, mxid) + if err == nil { + name := profile.DisplayName + if name == "" { + name = identifier + } + match = &userDirectoryHit{UserID: mxid, Name: name} + } else if !errors.Is(err, mautrix.MNotFound) { + return nil, wrapRedditRespError(err) + } + } else { + results, err := r.remote().SearchUsers(ctx, identifier) + if err != nil { + return nil, wrapRedditRespError(err) + } + for _, hit := range results.Results { + display := hit.DisplayName + if strings.EqualFold(stripUserPrefix(display), identifier) || strings.EqualFold(string(hit.UserID), "@"+identifier+":reddit.com") { + if match != nil && match.UserID != hit.UserID { + return nil, bridgev2.RespError(mautrix.MInvalidParam.WithMessage("Multiple Reddit users match %q.", identifier)) + } + match = &userDirectoryHit{UserID: hit.UserID, Name: display, Avatar: string(hit.AvatarURL)} } - match = &userDirectoryHit{UserID: hit.UserID, Name: display, Avatar: string(hit.AvatarURL)} } } if match == nil { From 50df6a9b6acfc4a0a0846a587df6e23ad42bf4ad Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Wed, 21 Oct 2026 20:36:33 +0100 Subject: [PATCH 2/2] PLAT-39414 connector: return members for newly created DMs Without members, bridgev2 refetched chat info before Reddit had set the new DM's chat type, so room creation failed with a 500 even though the DM was created. Return the user as joined and the other user as invited, with the DM name, when Reddit creates a new DM. Co-Authored-By: Claude Opus 5.5 --- pkg/connector/startchat.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index 4c91981..d9a027f 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -10,6 +10,7 @@ import ( "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/database" "maunium.net/go/mautrix/bridgev2/networkid" + "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" ) @@ -75,7 +76,7 @@ func (r *RedditClient) ResolveIdentifier(ctx context.Context, identifier string, if !createChat { return resp, nil } - roomID, _, err := r.remote().CreateOrGetDM(ctx, match.UserID) + roomID, created, err := r.remote().CreateOrGetDM(ctx, match.UserID) if err != nil { return nil, wrapRedditRespError(err) } @@ -86,6 +87,14 @@ func (r *RedditClient) ResolveIdentifier(ctx context.Context, identifier string, Type: ptr.Ptr(database.RoomTypeDM), }, } + if created { + members := &bridgev2.ChatMemberList{MemberMap: make(bridgev2.ChatMemberMap), OtherUserID: uid} + members.MemberMap.Set(bridgev2.ChatMember{EventSender: r.makeSender(userIDToMatrix(r.userID, "")), Membership: event.MembershipJoin}) + members.MemberMap.Set(bridgev2.ChatMember{EventSender: r.makeSender(match.UserID), Membership: event.MembershipInvite}) + resp.Chat.PortalInfo.Name = resp.UserInfo.Name + resp.Chat.PortalInfo.Members = members + resp.Chat.PortalInfo.CanBackfill = true + } return resp, nil }