From 2b9281f22ae61a0cb107feb9c3c08cdd4f2960b5 Mon Sep 17 00:00:00 2001 From: Sumner Evans Date: Mon, 10 Feb 2025 15:40:40 -0700 Subject: [PATCH] connector: implement receiving read receipts Signed-off-by: Sumner Evans --- pkg/connector/client.go | 40 +++++++++++++++++++++++++------- pkg/linkedingo/types/realtime.go | 13 ++++++++++- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index b18bcab..113632d 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -121,6 +121,8 @@ func (l *LinkedInClient) onDecoratedEvent(ctx context.Context, decoratedEvent *t l.onRealtimeMessage(ctx, decoratedEvent.Payload.Data.DecoratedMessage.Result) case linkedingo.RealtimeEventTopicTypingIndicators: l.onRealtimeTypingIndicator(decoratedEvent) + case linkedingo.RealtimeEventTopicMessageSeenReceipts: + l.onRealtimeMessageSeenReceipts(ctx, decoratedEvent.Payload.Data.DecoratedSeenReceipt.Result) default: fmt.Printf("UNSUPPORTED %q %+v\n", decoratedEvent.Topic, decoratedEvent) } @@ -131,7 +133,7 @@ func (l *LinkedInClient) onRealtimeMessage(ctx context.Context, msg types.Messag meta := simplevent.EventMeta{ LogContext: func(c zerolog.Context) zerolog.Context { return c. - Stringer("backend_urn", msg.BackendURN). + Stringer("entity_urn", msg.EntityURN). Stringer("sender", msg.Sender.BackendURN) }, PortalKey: l.makePortalKey(msg.Conversation.EntityURN), @@ -147,8 +149,8 @@ func (l *LinkedInClient) onRealtimeMessage(ctx context.Context, msg types.Messag }) evt := simplevent.Message[types.Message]{ - ID: networkid.MessageID(msg.BackendURN.ID()), - TargetMessage: networkid.MessageID(msg.BackendURN.ID()), + ID: networkid.MessageID(msg.EntityURN.String()), + TargetMessage: networkid.MessageID(msg.EntityURN.String()), Data: msg, ConvertMessageFunc: l.convertToMatrix, ConvertEditFunc: l.convertEditToMatrix, @@ -161,7 +163,7 @@ func (l *LinkedInClient) onRealtimeMessage(ctx context.Context, msg types.Messag case types.MessageBodyRenderFormatRecalled: l.main.Bridge.QueueRemoteEvent(l.userLogin, &simplevent.MessageRemove{ EventMeta: meta.WithType(bridgev2.RemoteEventMessageRemove), - TargetMessage: networkid.MessageID(msg.BackendURN.ID()), + TargetMessage: networkid.MessageID(msg.EntityURN.String()), }) return case types.MessageBodyRenderFormatSystem: @@ -169,10 +171,6 @@ func (l *LinkedInClient) onRealtimeMessage(ctx context.Context, msg types.Messag log.Warn().Str("message_body_render_format", string(msg.MessageBodyRenderFormat)).Msg("Unknown render format") } l.main.Bridge.QueueRemoteEvent(l.userLogin, &evt) - l.main.Bridge.QueueRemoteEvent(l.userLogin, &simplevent.Typing{ - EventMeta: meta.WithType(bridgev2.RemoteEventTyping), - Type: bridgev2.TypingTypeText, - }) } func (l *LinkedInClient) onRealtimeTypingIndicator(decoratedEvent *types.DecoratedEvent) { @@ -196,6 +194,32 @@ func (l *LinkedInClient) onRealtimeTypingIndicator(decoratedEvent *types.Decorat }) } +func (l *LinkedInClient) onRealtimeMessageSeenReceipts(ctx context.Context, receipt types.SeenReceipt) { + log := zerolog.Ctx(ctx) + part, err := l.main.Bridge.DB.Message.GetLastPartByID(ctx, l.userLogin.ID, networkid.MessageID(receipt.Message.EntityURN.String())) + if err != nil { + log.Err(err).Msg("failed to get read message") + } else if part == nil { + log.Warn().Msg("couldn't find read message") + return + } + l.main.Bridge.QueueRemoteEvent(l.userLogin, &simplevent.Receipt{ + EventMeta: simplevent.EventMeta{ + Type: bridgev2.RemoteEventReadReceipt, + LogContext: func(c zerolog.Context) zerolog.Context { + return c. + Time("seen_at", receipt.SeenAt.Time). + Stringer("message_urn", receipt.Message.EntityURN). + Stringer("typing_participant_urn", receipt.SeenByParticipant.BackendURN) + }, + PortalKey: part.Room, + Sender: l.makeSender(receipt.SeenByParticipant), + Timestamp: receipt.SeenAt.Time, + }, + LastTarget: networkid.MessageID(receipt.Message.EntityURN.String()), + }) +} + func (l *LinkedInClient) getAvatar(img *types.VectorImage) (avatar bridgev2.Avatar) { avatar.ID = networkid.AvatarID(img.RootURL) avatar.Remove = img.RootURL == "" diff --git a/pkg/linkedingo/types/realtime.go b/pkg/linkedingo/types/realtime.go index 8487e22..77fef8b 100644 --- a/pkg/linkedingo/types/realtime.go +++ b/pkg/linkedingo/types/realtime.go @@ -34,6 +34,7 @@ type DecoratedEventData struct { Type string `json:"_type,omitempty"` DecoratedMessage *DecoratedMessage `json:"doDecorateMessageMessengerRealtimeDecoration,omitempty"` DecoratedTypingIndicator *DecoratedTypingIndicator `json:"doDecorateTypingIndicatorMessengerRealtimeDecoration,omitempty"` + DecoratedSeenReceipt *DecoratedSeenReceipt `json:"doDecorateSeenReceiptMessengerRealtimeDecoration,omitempty"` } // Conversation represents a com.linkedin.messenger.Conversation object @@ -95,7 +96,6 @@ type DecoratedMessage struct { // Message represents a com.linkedin.messenger.Message object. type Message struct { Body AttributedText `json:"body,omitempty"` - BackendURN URN `json:"backendUrn,omitempty"` DeliveredAt jsontime.UnixMilli `json:"deliveredAt,omitempty"` EntityURN URN `json:"entityUrn,omitempty"` Sender MessagingParticipant `json:"sender,omitempty"` @@ -115,3 +115,14 @@ type RealtimeTypingIndicator struct { TypingParticipant MessagingParticipant `json:"typingParticipant,omitempty"` Conversation Conversation `json:"conversation,omitempty"` } + +type DecoratedSeenReceipt struct { + Result SeenReceipt `json:"result,omitempty"` +} + +// SeenReceipt represents a com.linkedin.messenger.SeenReceipt object. +type SeenReceipt struct { + SeenAt jsontime.UnixMilli `json:"seenAt,omitempty"` + Message Message `json:"message,omitempty"` + SeenByParticipant MessagingParticipant `json:"seenByParticipant,omitempty"` +}