Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pkg/connector/ids.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
}
Expand Down
3 changes: 1 addition & 2 deletions pkg/connector/membership.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package connector
import (
"context"
"errors"
"strings"

"maunium.net/go/mautrix"
"maunium.net/go/mautrix/bridgev2"
Expand Down Expand Up @@ -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 {
Expand Down
53 changes: 39 additions & 14 deletions pkg/connector/startchat.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package connector

import (
"context"
"errors"
"strings"

"go.mau.fi/util/ptr"
"maunium.net/go/mautrix"
"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"
)

Expand All @@ -18,28 +20,43 @@ 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/")
identifier = strings.TrimPrefix(identifier, "/u/")
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪️ Nit · Usernames shaped like t2_ IDs no longer resolve through the directory

Any identifier matching t2_[0-9a-z]+ now goes only to GetProfile and skips SearchUsers. Reddit usernames can contain underscores and are case-insensitive, so a real account named like t2_abc typed as u/t2_abc is looked up as an ID. If no account has that ID the user gets 'No Reddit user found', and if one does they get the wrong person. Before this change SearchUsers matched it by display name. Fall back to the directory search when GetProfile returns MNotFound.

Found by Indent Review Agent

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 {
Expand All @@ -59,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)
}
Expand All @@ -70,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
}

Expand Down
Loading