diff --git a/pkg/connector/handlegchat.go b/pkg/connector/handlegchat.go index d3120a7..416ce28 100644 --- a/pkg/connector/handlegchat.go +++ b/pkg/connector/handlegchat.go @@ -133,6 +133,7 @@ func (c *GChatClient) handleReaction(ctx context.Context, evt *proto.Event) { Sender: networkid.UserID(sender), }, }, + EmojiID: networkid.EmojiID(reaction.Emoji.GetUnicode()), Emoji: reaction.Emoji.GetUnicode(), TargetMessage: networkid.MessageID(messageId), }) diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index ad35162..a4ccf44 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -2,6 +2,7 @@ package connector import ( "context" + "time" "google.golang.org/protobuf/encoding/prototext" "maunium.net/go/mautrix/bridgev2" @@ -13,6 +14,10 @@ import ( "go.mau.fi/mautrix-googlechat/pkg/gchatmeow/proto" ) +var ( + _ bridgev2.ReactionHandlingNetworkAPI = (*GChatClient)(nil) +) + func portalToGroupId(portal *bridgev2.Portal) (*proto.GroupId, error) { groupId := &proto.GroupId{} err := prototext.Unmarshal([]byte(portal.ID), groupId) @@ -85,6 +90,7 @@ func (c *GChatClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.Mat } var msgID string + var timestamp int64 if msg.ThreadRoot != nil { threadId := ptr.Ptr(string(msg.ThreadRoot.ID)) @@ -112,6 +118,7 @@ func (c *GChatClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.Mat return nil, err } msgID = *res.Message.Id.MessageId + timestamp = *res.Message.CreateTime } else { req := &proto.CreateTopicRequest{ GroupId: groupId, @@ -124,13 +131,75 @@ func (c *GChatClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.Mat return nil, err } msgID = *res.Topic.Id.TopicId + timestamp = *res.Topic.CreateTimeUsec } msg.AddPendingToIgnore(networkid.TransactionID(msgID)) return &bridgev2.MatrixMessageResponse{ DB: &database.Message{ - ID: networkid.MessageID(msgID), + ID: networkid.MessageID(msgID), + Timestamp: time.UnixMicro(timestamp), }, RemovePending: networkid.TransactionID(msgID), }, nil } + +func (c *GChatClient) PreHandleMatrixReaction(_ context.Context, msg *bridgev2.MatrixReaction) (bridgev2.MatrixReactionPreResponse, error) { + emoji := msg.Content.RelatesTo.Key + return bridgev2.MatrixReactionPreResponse{ + SenderID: networkid.UserID(c.userLogin.ID), + EmojiID: networkid.EmojiID(emoji), + Emoji: emoji, + }, nil +} + +func (c *GChatClient) HandleMatrixReaction(ctx context.Context, msg *bridgev2.MatrixReaction) (*database.Reaction, error) { + return nil, c.doHandleMatrixReaction(ctx, msg.Portal, + string(msg.TargetMessage.ThreadRoot), + string(msg.TargetMessage.ID), msg.PreHandleResp.Emoji, proto.UpdateReactionRequest_ADD) +} + +func (c *GChatClient) HandleMatrixReactionRemove(ctx context.Context, msg *bridgev2.MatrixReactionRemove) error { + dbMsg, err := c.userLogin.Bridge.DB.Message.GetLastPartByID(ctx, c.userLogin.ID, msg.TargetReaction.MessageID) + if err != nil { + return err + } + var topicId string + if dbMsg != nil { + topicId = string(dbMsg.ThreadRoot) + } + return c.doHandleMatrixReaction(ctx, msg.Portal, + topicId, + string(msg.TargetReaction.MessageID), string(msg.TargetReaction.EmojiID), proto.UpdateReactionRequest_REMOVE) +} + +func (c *GChatClient) doHandleMatrixReaction(ctx context.Context, portal *bridgev2.Portal, topicId, messageId, emoji string, typ proto.UpdateReactionRequest_ReactionUpdateType) error { + groupId, err := portalToGroupId(portal) + if err != nil { + return err + } + + if topicId == "" { + topicId = messageId + } + _, err = c.client.UpdateReaction(ctx, &proto.UpdateReactionRequest{ + MessageId: &proto.MessageId{ + ParentId: &proto.MessageParentId{ + Parent: &proto.MessageParentId_TopicId{ + TopicId: &proto.TopicId{ + GroupId: groupId, + TopicId: &topicId, + }, + }, + }, + MessageId: &messageId, + }, + Emoji: &proto.Emoji{ + Content: &proto.Emoji_Unicode{ + Unicode: emoji, + }, + }, + Type: typ.Enum(), + }) + return err +} diff --git a/pkg/gchatmeow/api.go b/pkg/gchatmeow/api.go index a74faea..3e95483 100644 --- a/pkg/gchatmeow/api.go +++ b/pkg/gchatmeow/api.go @@ -160,6 +160,12 @@ func (c *Client) CatchUpGroup(ctx context.Context, request *proto.CatchUpGroupRe return response, c.gcRequest(ctx, "catch_up_group", request, response) } +func (c *Client) UpdateReaction(ctx context.Context, request *proto.UpdateReactionRequest) (*proto.UpdateReactionResponse, error) { + request.RequestHeader = c.gcRequestHeader + response := &proto.UpdateReactionResponse{} + return response, c.gcRequest(ctx, "update_reaction", request, response) +} + func (c *Client) UploadFile(ctx context.Context, data []byte, groupId string, fileName string, mimeType string) (*proto.UploadMetadata, error) { headers := http.Header{ "x-goog-upload-protocol": {"resumable"},