diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 2e80b72..728ac85 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -34,7 +34,7 @@ func NewClient(userLogin *bridgev2.UserLogin, client *gchatmeow.Client) *GChatCl userLogin: userLogin, client: client, users: map[string]*proto.User{}, - msgConv: msgconv.NewMessageConverter(client), + msgConv: msgconv.NewMessageConverter(userLogin.Bridge, client), } } diff --git a/pkg/gchatmeow/proto/extra.go b/pkg/gchatmeow/proto/extra.go new file mode 100644 index 0000000..93a954a --- /dev/null +++ b/pkg/gchatmeow/proto/extra.go @@ -0,0 +1,3 @@ +package proto + +type MetadataAssociatedValue = isAnnotation_Metadata diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 726a184..3ec5261 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -13,7 +13,6 @@ func (mc *MessageConverter) ToGChat( ctx context.Context, content *event.MessageEventContent, ) (string, []*proto.Annotation) { - parser := &matrixfmt.HTMLParser{} - body, annotations := matrixfmt.Parse(ctx, parser, content) + body, annotations := matrixfmt.Parse(ctx, mc.matrixFmtParams, content) return body, annotations } diff --git a/pkg/msgconv/gchatfmt/utils.go b/pkg/msgconv/gchatfmt/utils.go index 9781867..72e4513 100644 --- a/pkg/msgconv/gchatfmt/utils.go +++ b/pkg/msgconv/gchatfmt/utils.go @@ -14,5 +14,15 @@ func MakeAnnotation(start, length int32, format proto.FormatMetadata_FormatType) }, }, } +} + +func MakeAnnotationFromMetadata(typ proto.AnnotationType, start, length int32, metadata proto.MetadataAssociatedValue) *proto.Annotation { + return &proto.Annotation{ + Type: typ, + StartIndex: start, + Length: length, + ChipRenderType: proto.Annotation_DO_NOT_RENDER, + Metadata: metadata, + } } diff --git a/pkg/msgconv/matrixfmt/html.go b/pkg/msgconv/matrixfmt/html.go index d3974c0..2101a2a 100644 --- a/pkg/msgconv/matrixfmt/html.go +++ b/pkg/msgconv/matrixfmt/html.go @@ -4,11 +4,13 @@ import ( "context" "fmt" "math" + "slices" "strconv" "strings" "golang.org/x/net/html" "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" "go.mau.fi/mautrix-googlechat/pkg/gchatmeow" ) @@ -228,7 +230,7 @@ func (ctx Context) WithWhitespace() Context { // HTMLParser is a somewhat customizable Matrix HTML parser. type HTMLParser struct { - // GetUUIDFromMXID func(context.Context, id.UserID) uuid.UUID + GetUIDFromMXID func(context.Context, id.UserID) string } // TaggedString is a string that also contains a HTML tag. @@ -345,6 +347,21 @@ func (parser *HTMLParser) linkToString(node *html.Node, ctx Context) *EntityStri if len(href) == 0 { return str } + parsedMatrix, err := id.ParseMatrixURIOrMatrixToURL(href) + if err == nil && parsedMatrix != nil && parsedMatrix.Sigil1 == '@' { + mxid := parsedMatrix.UserID() + if ctx.AllowedMentions != nil && !slices.Contains(ctx.AllowedMentions.UserIDs, mxid) { + // Mention not allowed, use name as-is + return str + } + u := parser.GetUIDFromMXID(ctx.Ctx, mxid) + if u == "" { + return str + } + return NewEntityString("@" + str.String.String()).Format(Mention{ + ID: u, + }) + } if str.String.String() == href { return str } diff --git a/pkg/msgconv/matrixfmt/tags.go b/pkg/msgconv/matrixfmt/tags.go index 8f45c26..d39d7e2 100644 --- a/pkg/msgconv/matrixfmt/tags.go +++ b/pkg/msgconv/matrixfmt/tags.go @@ -9,7 +9,30 @@ import ( type BodyRangeValue interface { String() string Format(message string) string - Proto() proto.FormatMetadata_FormatType + Proto() proto.MetadataAssociatedValue +} + +type Mention struct { + ID string +} + +func (m Mention) String() string { + return fmt.Sprintf("Mention{ID: (%s)}", m.ID) +} + +func (m Mention) Proto() proto.MetadataAssociatedValue { + return &proto.Annotation_UserMentionMetadata{ + UserMentionMetadata: &proto.UserMentionMetadata{ + Type: proto.UserMentionMetadata_MENTION, + Id: &proto.UserId{ + Id: m.ID, + }, + }, + } +} + +func (m Mention) Format(message string) string { + return message } type Style int @@ -27,8 +50,12 @@ const ( StyleFontColor ) -func (s Style) Proto() proto.FormatMetadata_FormatType { - return proto.FormatMetadata_FormatType(s) +func (s Style) Proto() proto.MetadataAssociatedValue { + return &proto.Annotation_FormatMetadata{ + FormatMetadata: &proto.FormatMetadata{ + FormatType: proto.FormatMetadata_FormatType(s), + }, + } } func (s Style) String() string { diff --git a/pkg/msgconv/matrixfmt/tree.go b/pkg/msgconv/matrixfmt/tree.go index 70a58b2..096cda8 100644 --- a/pkg/msgconv/matrixfmt/tree.go +++ b/pkg/msgconv/matrixfmt/tree.go @@ -63,7 +63,13 @@ func (b BodyRange) TruncateEnd(maxEnd int) *BodyRange { } func (b BodyRange) Proto() *proto.Annotation { - return gchatfmt.MakeAnnotation(int32(b.Start), int32(b.Length), b.Value.Proto()) + metadata := b.Value.Proto() + typ := proto.AnnotationType_FORMAT_DATA + _, ok := metadata.(*proto.Annotation_UserMentionMetadata) + if ok { + typ = proto.AnnotationType_USER_MENTION + } + return gchatfmt.MakeAnnotationFromMetadata(typ, int32(b.Start), int32(b.Length), b.Value.Proto()) } // LinkedRangeTree is a linked tree of formatting entities. diff --git a/pkg/msgconv/msgconv.go b/pkg/msgconv/msgconv.go index 94846f0..d72c2ae 100644 --- a/pkg/msgconv/msgconv.go +++ b/pkg/msgconv/msgconv.go @@ -1,15 +1,51 @@ package msgconv import ( + "context" + "go.mau.fi/mautrix-googlechat/pkg/gchatmeow" + "go.mau.fi/mautrix-googlechat/pkg/msgconv/matrixfmt" + "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/id" +) + +type contextKey int + +const ( + contextKeyPortal contextKey = iota + contextKeyClient + contextKeyIntent ) type MessageConverter struct { client *gchatmeow.Client + + matrixFmtParams *matrixfmt.HTMLParser } -func NewMessageConverter(client *gchatmeow.Client) *MessageConverter { +func NewMessageConverter(br *bridgev2.Bridge, client *gchatmeow.Client) *MessageConverter { return &MessageConverter{ client: client, + + matrixFmtParams: &matrixfmt.HTMLParser{ + GetUIDFromMXID: func(ctx context.Context, userID id.UserID) string { + parsed, ok := br.Matrix.ParseGhostMXID(userID) + if ok { + return string(parsed) + } + user, _ := br.GetExistingUserByMXID(ctx, userID) + if user != nil { + preferredLogin, _, _ := getPortal(ctx).FindPreferredLogin(ctx, user, true) + if preferredLogin != nil { + return string(preferredLogin.ID) + } + } + return "" + }, + }, } } + +func getPortal(ctx context.Context) *bridgev2.Portal { + return ctx.Value(contextKeyPortal).(*bridgev2.Portal) +}