From 50f47415e3d4e7c29e44f230150d0ae378110474 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 28 Feb 2024 17:01:06 +0100 Subject: [PATCH 1/7] feat(federation): Federate unread information to proxies Signed-off-by: Joas Schilling --- lib/Federation/BackendNotifier.php | 6 ++++++ .../CloudFederationProviderTalk.php | 20 +++++++++++++++++-- .../TalkV1/Notifier/MessageSentListener.php | 19 ++++++++++++------ lib/Service/ParticipantService.php | 8 ++++++++ lib/Service/RoomFormatter.php | 8 +++++++- 5 files changed, 52 insertions(+), 9 deletions(-) diff --git a/lib/Federation/BackendNotifier.php b/lib/Federation/BackendNotifier.php index 487722c90cd..975829ff03a 100644 --- a/lib/Federation/BackendNotifier.php +++ b/lib/Federation/BackendNotifier.php @@ -278,6 +278,8 @@ public function sendRoomModifiedUpdate( /** * Send information to remote participants that a message was posted * Sent from Host server to Remote participant server + * + * @param array{unreadMessages: int, unreadMention: bool, unreadMentionDirect: bool} $unreadInfo */ public function sendMessageUpdate( string $remoteServer, @@ -286,6 +288,7 @@ public function sendMessageUpdate( string $accessToken, string $localToken, array $messageData, + array $unreadInfo, ): void { $remote = $this->prepareRemoteUrl($remoteServer); @@ -299,6 +302,7 @@ public function sendMessageUpdate( 'sharedSecret' => $accessToken, 'remoteToken' => $localToken, 'messageData' => $messageData, + 'unreadInfo' => $unreadInfo, ], ); @@ -324,6 +328,8 @@ public function sendUpdateDataToRemote(string $remote, array $data, int $try): v } protected function sendUpdateToRemote(string $remote, ICloudFederationNotification $notification, int $try = 0): void { + \OC::$server->getLogger()->error('sendUpdateToRemote'); + \OC::$server->getLogger()->error(json_encode($notification->getMessage())); $response = $this->federationProviderManager->sendNotification($remote, $notification); if (!is_array($response)) { $this->jobList->add(RetryJob::class, diff --git a/lib/Federation/CloudFederationProviderTalk.php b/lib/Federation/CloudFederationProviderTalk.php index 525784e2b35..0ff47da250b 100644 --- a/lib/Federation/CloudFederationProviderTalk.php +++ b/lib/Federation/CloudFederationProviderTalk.php @@ -32,6 +32,7 @@ use OCA\Talk\Events\AAttendeeRemovedEvent; use OCA\Talk\Events\ARoomModifiedEvent; use OCA\Talk\Events\AttendeesAddedEvent; +use OCA\Talk\Exceptions\ParticipantNotFoundException; use OCA\Talk\Exceptions\RoomNotFoundException; use OCA\Talk\Manager; use OCA\Talk\Model\Attendee; @@ -309,7 +310,7 @@ private function roomModified(int $remoteAttendeeId, array $notification): array /** * @param int $remoteAttendeeId - * @param array{remoteServerUrl: string, sharedSecret: string, remoteToken: string, messageData: array{remoteMessageId: int, actorType: string, actorId: string, actorDisplayName: string, messageType: string, systemMessage: string, expirationDatetime: string, message: string, messageParameter: string}} $notification + * @param array{remoteServerUrl: string, sharedSecret: string, remoteToken: string, messageData: array{remoteMessageId: int, actorType: string, actorId: string, actorDisplayName: string, messageType: string, systemMessage: string, expirationDatetime: string, message: string, messageParameter: string}, unreadInfo: array{unreadMessages: int, unreadMention: bool, unreadMentionDirect: bool}} $notification * @return array * @throws ActionNotSupportedException * @throws AuthenticationFailedException @@ -338,7 +339,9 @@ private function messagePosted(int $remoteAttendeeId, array $notification): arra $message->setActorDisplayName($notification['messageData']['actorDisplayName']); $message->setMessageType($notification['messageData']['messageType']); $message->setSystemMessage($notification['messageData']['systemMessage']); - $message->setExpirationDateTime(new \DateTimeImmutable($notification['messageData']['expirationDatetime'])); + if ($notification['messageData']['expirationDatetime']) { + $message->setExpirationDateTime(new \DateTimeImmutable($notification['messageData']['expirationDatetime'])); + } $message->setMessage($notification['messageData']['message']); $message->setMessageParameters($notification['messageData']['messageParameter']); $this->proxyCacheMessagesMapper->insert($message); @@ -351,6 +354,19 @@ private function messagePosted(int $remoteAttendeeId, array $notification): arra } } + try { + $participant = $this->participantService->getParticipant($room, $invite->getUserId(), false); + } catch (ParticipantNotFoundException) { + throw new ShareNotFound(); + } + + $this->participantService->updateUnreadInfoForProxyParticipant( + $participant, + $notification['unreadInfo']['unreadMessages'], + $notification['unreadInfo']['unreadMention'], + $notification['unreadInfo']['unreadMentionDirect'], + ); + return []; } diff --git a/lib/Federation/Proxy/TalkV1/Notifier/MessageSentListener.php b/lib/Federation/Proxy/TalkV1/Notifier/MessageSentListener.php index 3c7dc3a854f..e6920cc0654 100644 --- a/lib/Federation/Proxy/TalkV1/Notifier/MessageSentListener.php +++ b/lib/Federation/Proxy/TalkV1/Notifier/MessageSentListener.php @@ -48,6 +48,7 @@ public function __construct( protected ICloudIdManager $cloudIdManager, protected MessageParser $messageParser, protected IFactory $l10nFactory, + protected ChatManager $chatManager, ) { } @@ -90,15 +91,20 @@ public function handle(Event $event): void { 'messageParameter' => json_encode($chatMessage->getMessageParameters()), ]; - $notifiedServers = []; $participants = $this->participantService->getParticipantsByActorType($event->getRoom(), Attendee::ACTOR_FEDERATED_USERS); foreach ($participants as $participant) { - $cloudId = $this->cloudIdManager->resolveCloudId($participant->getAttendee()->getActorId()); + $attendee = $participant->getAttendee(); + $cloudId = $this->cloudIdManager->resolveCloudId($attendee->getActorId()); - if (isset($notifiedServers[$cloudId->getRemote()])) { - continue; - } - $notifiedServers[$cloudId->getRemote()] = true; + $lastReadMessage = $attendee->getLastReadMessage(); + $lastMention = $attendee->getLastMentionMessage(); + $lastMentionDirect = $attendee->getLastMentionDirect(); + + $unreadInfo = [ + 'unreadMessages' => $this->chatManager->getUnreadCount($event->getRoom(), $lastReadMessage), + 'unreadMention' => $lastMention !== 0 && $lastReadMessage < $lastMention, + 'unreadMentionDirect' => $lastMentionDirect !== 0 && $lastReadMessage < $lastMentionDirect + ]; $this->backendNotifier->sendMessageUpdate( $cloudId->getRemote(), @@ -106,6 +112,7 @@ public function handle(Event $event): void { $participant->getAttendee()->getAccessToken(), $event->getRoom()->getToken(), $messageData, + $unreadInfo, ); } } diff --git a/lib/Service/ParticipantService.php b/lib/Service/ParticipantService.php index c16510c5985..6d0711ed6c3 100644 --- a/lib/Service/ParticipantService.php +++ b/lib/Service/ParticipantService.php @@ -249,6 +249,14 @@ public function updateLastReadMessage(Participant $participant, int $lastReadMes $this->attendeeMapper->update($attendee); } + public function updateUnreadInfoForProxyParticipant(Participant $participant, int $unreadMessageCount, bool $hasMention, bool $hadDirectMention): void { + $attendee = $participant->getAttendee(); + $attendee->setLastReadMessage($unreadMessageCount); + $attendee->setLastMentionMessage($hasMention ? 1 : 0); + $attendee->setLastMentionDirect($hadDirectMention ? 1 : 0); + $this->attendeeMapper->update($attendee); + } + public function updateFavoriteStatus(Participant $participant, bool $isFavorite): void { $attendee = $participant->getAttendee(); $attendee->setFavorite($isFavorite); diff --git a/lib/Service/RoomFormatter.php b/lib/Service/RoomFormatter.php index bffb0d976e1..bc4cc6204a7 100644 --- a/lib/Service/RoomFormatter.php +++ b/lib/Service/RoomFormatter.php @@ -287,7 +287,13 @@ public function formatRoomV4( if ($attendee->getActorType() === Attendee::ACTOR_USERS) { $currentUser = $this->userManager->get($attendee->getActorId()); - if ($currentUser instanceof IUser) { + if ($room->getRemoteServer() !== '') { + // For proxy conversations the information is the real counter, + // not the message ID requiring math afterward. + $roomData['unreadMessages'] = $attendee->getLastReadMessage(); + $roomData['unreadMention'] = (bool) $attendee->getLastMentionMessage(); + $roomData['unreadMentionDirect'] = (bool) $attendee->getLastMentionDirect(); + } elseif ($currentUser instanceof IUser) { $lastReadMessage = $attendee->getLastReadMessage(); if ($lastReadMessage === -1) { /* From 88101baeee7db6611bc932f915f7447cd0b5cc62 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 28 Feb 2024 18:07:18 +0100 Subject: [PATCH 2/7] feat(federation): Proxy setReadMarker and return updated conversation Signed-off-by: Joas Schilling --- docs/chat.md | 4 ++ lib/Controller/ChatController.php | 33 ++++++++++--- .../TalkV1/Controller/ChatController.php | 48 +++++++++++++++++++ lib/Service/RoomFormatter.php | 7 +++ openapi-full.json | 5 +- openapi.json | 5 +- tests/php/Controller/ChatControllerTest.php | 4 ++ 7 files changed, 97 insertions(+), 9 deletions(-) diff --git a/docs/chat.md b/docs/chat.md index 68afb61d254..a5ceb4664d1 100644 --- a/docs/chat.md +++ b/docs/chat.md @@ -426,6 +426,10 @@ See [OCP\RichObjectStrings\Definitions](https://github.com/nextcloud/server/blob + `404 Not Found` When the room could not be found for the participant, or the participant is a guest. + - Data in case of `200 OK`: + + **Without** `federation-v1` capability empty + + **With** `federation-v1` capability, see array definition in [Get user´s conversations](conversation.md#get-user-s-conversations) + - Header: | field | type | Description | diff --git a/lib/Controller/ChatController.php b/lib/Controller/ChatController.php index 0c92b6eabda..d02e4c765ea 100644 --- a/lib/Controller/ChatController.php +++ b/lib/Controller/ChatController.php @@ -55,6 +55,7 @@ use OCA\Talk\Service\BotService; use OCA\Talk\Service\ParticipantService; use OCA\Talk\Service\ReminderService; +use OCA\Talk\Service\RoomFormatter; use OCA\Talk\Service\SessionService; use OCA\Talk\Share\Helper\FilesMetadataCache; use OCA\Talk\Share\RoomShareProvider; @@ -90,6 +91,7 @@ * @psalm-import-type TalkChatMessage from ResponseDefinitions * @psalm-import-type TalkChatMessageWithParent from ResponseDefinitions * @psalm-import-type TalkChatReminder from ResponseDefinitions + * @psalm-import-type TalkRoom from ResponseDefinitions */ class ChatController extends AEnvironmentAwareController { /** @var string[] */ @@ -102,6 +104,7 @@ public function __construct( private IUserManager $userManager, private IAppManager $appManager, private ChatManager $chatManager, + private RoomFormatter $roomFormatter, private ReactionManager $reactionManager, private ParticipantService $participantService, private SessionService $sessionService, @@ -1045,19 +1048,35 @@ public function clearHistory(): DataResponse { * * @param int $lastReadMessage ID if the last read message * @psalm-param non-negative-int $lastReadMessage - * @return DataResponse, array{X-Chat-Last-Common-Read?: numeric-string}> + * @return DataResponse * * 200: Read marker set successfully */ - #[NoAdminRequired] - #[RequireParticipant] + #[FederationSupported] + #[PublicPage] + #[RequireAuthenticatedParticipant] public function setReadMarker(int $lastReadMessage): DataResponse { + if ($this->room->getRemoteServer() !== '') { + /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\ChatController $proxy */ + $proxy = \OCP\Server::get(\OCA\Talk\Federation\Proxy\TalkV1\Controller\ChatController::class); + return $proxy->setReadMarker($this->room, $this->participant, $this->getResponseFormat(), $lastReadMessage); + } + $this->participantService->updateLastReadMessage($this->participant, $lastReadMessage); - $headers = []; - if ($this->participant->getAttendee()->getReadPrivacy() === Participant::PRIVACY_PUBLIC) { - $headers = ['X-Chat-Last-Common-Read' => (string) $this->chatManager->getLastCommonReadMessage($this->room)]; + $attendee = $this->participant->getAttendee(); + + $headers = $lastCommonRead = []; + if ($attendee->getReadPrivacy() === Participant::PRIVACY_PUBLIC) { + $lastCommonRead[$this->room->getId()] = $this->chatManager->getLastCommonReadMessage($this->room); + $headers = ['X-Chat-Last-Common-Read' => (string) $lastCommonRead[$this->room->getId()]]; } - return new DataResponse([], Http::STATUS_OK, $headers); + + return new DataResponse($this->roomFormatter->formatRoom( + $this->getResponseFormat(), + $lastCommonRead, + $this->room, + $this->participant, + ), Http::STATUS_OK, $headers); } /** diff --git a/lib/Federation/Proxy/TalkV1/Controller/ChatController.php b/lib/Federation/Proxy/TalkV1/Controller/ChatController.php index 6e4494eb582..28dff03b778 100644 --- a/lib/Federation/Proxy/TalkV1/Controller/ChatController.php +++ b/lib/Federation/Proxy/TalkV1/Controller/ChatController.php @@ -32,6 +32,8 @@ use OCA\Talk\Participant; use OCA\Talk\ResponseDefinitions; use OCA\Talk\Room; +use OCA\Talk\Service\ParticipantService; +use OCA\Talk\Service\RoomFormatter; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; use OCP\ICache; @@ -40,6 +42,7 @@ /** * @psalm-import-type TalkChatMentionSuggestion from ResponseDefinitions * @psalm-import-type TalkChatMessageWithParent from ResponseDefinitions + * @psalm-import-type TalkRoom from ResponseDefinitions */ class ChatController { protected ?ICache $proxyCacheMessages; @@ -47,6 +50,8 @@ class ChatController { public function __construct( protected ProxyRequest $proxy, protected UserConverter $userConverter, + protected ParticipantService $participantService, + protected RoomFormatter $roomFormatter, ICacheFactory $cacheFactory, ) { $this->proxyCacheMessages = $cacheFactory->isAvailable() ? $cacheFactory->createDistributed('talk/pcm/') : null; @@ -347,6 +352,49 @@ public function deleteMessage(Room $room, Participant $participant, int $message ); } + /** + * @see \OCA\Talk\Controller\ChatController::setReadMarker() + * + * @param 'json'|'xml' $responseFormat + * @return DataResponse + * @throws CannotReachRemoteException + * + * 200: List of mention suggestions returned + */ + public function setReadMarker(Room $room, Participant $participant, string $responseFormat, int $lastReadMessage): DataResponse { + $proxy = $this->proxy->post( + $participant->getAttendee()->getInvitedCloudId(), + $participant->getAttendee()->getAccessToken(), + $room->getRemoteServer() . '/ocs/v2.php/apps/spreed/api/v1/chat/' . $room->getRemoteToken() . '/read', + [ + 'lastReadMessage' => $lastReadMessage, + ], + ); + + /** @var TalkRoom $data */ + $data = $this->proxy->getOCSData($proxy); + + $this->participantService->updateUnreadInfoForProxyParticipant( + $participant, + $data['unreadMessages'], + $data['unreadMention'], + $data['unreadMentionDirect'], + ); + + $headers = $lastCommonRead = []; + if ($proxy->getHeader('X-Chat-Last-Common-Read')) { + $lastCommonRead[$room->getId()] = (int) $proxy->getHeader('X-Chat-Last-Common-Read'); + $headers['X-Chat-Last-Common-Read'] = (string) $lastCommonRead[$room->getId()]; + } + + return new DataResponse($this->roomFormatter->formatRoom( + $responseFormat, + $lastCommonRead, + $room, + $participant, + ), Http::STATUS_OK, $headers); + } + /** * @see \OCA\Talk\Controller\ChatController::mentions() * diff --git a/lib/Service/RoomFormatter.php b/lib/Service/RoomFormatter.php index bc4cc6204a7..41c3e6aaf69 100644 --- a/lib/Service/RoomFormatter.php +++ b/lib/Service/RoomFormatter.php @@ -329,6 +329,13 @@ public function formatRoomV4( && $currentParticipant->hasModeratorPermissions(false) && $this->talkConfig->canUserEnableSIP($currentUser); } + } elseif ($attendee->getActorType() === Attendee::ACTOR_FEDERATED_USERS) { + $lastReadMessage = $attendee->getLastReadMessage(); + $lastMention = $attendee->getLastMentionMessage(); + $lastMentionDirect = $attendee->getLastMentionDirect(); + $roomData['unreadMessages'] = $this->chatManager->getUnreadCount($room, $lastReadMessage); + $roomData['unreadMention'] = $lastMention !== 0 && $lastReadMessage < $lastMention; + $roomData['unreadMentionDirect'] = $lastMentionDirect !== 0 && $lastReadMessage < $lastMentionDirect; } else { $roomData['lastReadMessage'] = $attendee->getLastReadMessage(); } diff --git a/openapi-full.json b/openapi-full.json index 3ef42b6d305..5d6832c8bd0 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -6198,6 +6198,7 @@ "chat" ], "security": [ + {}, { "bearer_auth": [] }, @@ -6277,7 +6278,9 @@ "meta": { "$ref": "#/components/schemas/OCSMeta" }, - "data": {} + "data": { + "$ref": "#/components/schemas/Room" + } } } } diff --git a/openapi.json b/openapi.json index 9fe3837ae82..0280bc76e5a 100644 --- a/openapi.json +++ b/openapi.json @@ -6080,6 +6080,7 @@ "chat" ], "security": [ + {}, { "bearer_auth": [] }, @@ -6159,7 +6160,9 @@ "meta": { "$ref": "#/components/schemas/OCSMeta" }, - "data": {} + "data": { + "$ref": "#/components/schemas/Room" + } } } } diff --git a/tests/php/Controller/ChatControllerTest.php b/tests/php/Controller/ChatControllerTest.php index b768741c1d0..1135bc9368f 100644 --- a/tests/php/Controller/ChatControllerTest.php +++ b/tests/php/Controller/ChatControllerTest.php @@ -40,6 +40,7 @@ use OCA\Talk\Service\BotService; use OCA\Talk\Service\ParticipantService; use OCA\Talk\Service\ReminderService; +use OCA\Talk\Service\RoomFormatter; use OCA\Talk\Service\SessionService; use OCA\Talk\Share\Helper\FilesMetadataCache; use OCA\Talk\Share\RoomShareProvider; @@ -71,6 +72,7 @@ class ChatControllerTest extends TestCase { private $appManager; /** @var ChatManager|MockObject */ protected $chatManager; + private RoomFormatter|MockObject $roomFormatter; /** @var ReactionManager|MockObject */ protected $reactionManager; /** @var ParticipantService|MockObject */ @@ -129,6 +131,7 @@ public function setUp(): void { $this->userManager = $this->createMock(IUserManager::class); $this->appManager = $this->createMock(IAppManager::class); $this->chatManager = $this->createMock(ChatManager::class); + $this->roomFormatter = $this->createMock(RoomFormatter::class); $this->reactionManager = $this->createMock(ReactionManager::class); $this->participantService = $this->createMock(ParticipantService::class); $this->sessionService = $this->createMock(SessionService::class); @@ -172,6 +175,7 @@ private function recreateChatController() { $this->userManager, $this->appManager, $this->chatManager, + $this->roomFormatter, $this->reactionManager, $this->participantService, $this->sessionService, From b1e243877f0f3909dcca708549c5c602a5848341 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 28 Feb 2024 18:35:07 +0100 Subject: [PATCH 3/7] feat(federation): Allow to mark a conversation as unread again Signed-off-by: Joas Schilling --- docs/chat.md | 4 ++ lib/Controller/ChatController.php | 13 ++++-- .../TalkV1/Controller/ChatController.php | 40 +++++++++++++++++++ openapi-full.json | 5 ++- openapi.json | 5 ++- 5 files changed, 62 insertions(+), 5 deletions(-) diff --git a/docs/chat.md b/docs/chat.md index a5ceb4664d1..cbf279963ae 100644 --- a/docs/chat.md +++ b/docs/chat.md @@ -448,6 +448,10 @@ See [OCP\RichObjectStrings\Definitions](https://github.com/nextcloud/server/blob + `404 Not Found` When the room could not be found for the participant, or the participant is a guest. + - Data in case of `200 OK`: + + **Without** `federation-v1` capability empty + + **With** `federation-v1` capability, see array definition in [Get user´s conversations](conversation.md#get-user-s-conversations) + - Header: | field | type | Description | diff --git a/lib/Controller/ChatController.php b/lib/Controller/ChatController.php index d02e4c765ea..66efec4b281 100644 --- a/lib/Controller/ChatController.php +++ b/lib/Controller/ChatController.php @@ -1082,13 +1082,20 @@ public function setReadMarker(int $lastReadMessage): DataResponse { /** * Mark a chat as unread * - * @return DataResponse, array{X-Chat-Last-Common-Read?: numeric-string}> + * @return DataResponse * * 200: Read marker set successfully */ - #[NoAdminRequired] - #[RequireParticipant] + #[FederationSupported] + #[PublicPage] + #[RequireAuthenticatedParticipant] public function markUnread(): DataResponse { + if ($this->room->getRemoteServer() !== '') { + /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\ChatController $proxy */ + $proxy = \OCP\Server::get(\OCA\Talk\Federation\Proxy\TalkV1\Controller\ChatController::class); + return $proxy->markUnread($this->room, $this->participant, $this->getResponseFormat()); + } + $message = $this->room->getLastMessage(); $unreadId = 0; diff --git a/lib/Federation/Proxy/TalkV1/Controller/ChatController.php b/lib/Federation/Proxy/TalkV1/Controller/ChatController.php index 28dff03b778..3076484c4a4 100644 --- a/lib/Federation/Proxy/TalkV1/Controller/ChatController.php +++ b/lib/Federation/Proxy/TalkV1/Controller/ChatController.php @@ -395,6 +395,46 @@ public function setReadMarker(Room $room, Participant $participant, string $resp ), Http::STATUS_OK, $headers); } + /** + * @see \OCA\Talk\Controller\ChatController::markUnread() + * + * @param 'json'|'xml' $responseFormat + * @return DataResponse + * @throws CannotReachRemoteException + * + * 200: List of mention suggestions returned + */ + public function markUnread(Room $room, Participant $participant, string $responseFormat): DataResponse { + $proxy = $this->proxy->delete( + $participant->getAttendee()->getInvitedCloudId(), + $participant->getAttendee()->getAccessToken(), + $room->getRemoteServer() . '/ocs/v2.php/apps/spreed/api/v1/chat/' . $room->getRemoteToken() . '/read', + ); + + /** @var TalkRoom $data */ + $data = $this->proxy->getOCSData($proxy); + + $this->participantService->updateUnreadInfoForProxyParticipant( + $participant, + $data['unreadMessages'], + $data['unreadMention'], + $data['unreadMentionDirect'], + ); + + $headers = $lastCommonRead = []; + if ($proxy->getHeader('X-Chat-Last-Common-Read')) { + $lastCommonRead[$room->getId()] = (int) $proxy->getHeader('X-Chat-Last-Common-Read'); + $headers['X-Chat-Last-Common-Read'] = (string) $lastCommonRead[$room->getId()]; + } + + return new DataResponse($this->roomFormatter->formatRoom( + $responseFormat, + $lastCommonRead, + $room, + $participant, + ), Http::STATUS_OK, $headers); + } + /** * @see \OCA\Talk\Controller\ChatController::mentions() * diff --git a/openapi-full.json b/openapi-full.json index 5d6832c8bd0..b4872e55df4 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -6297,6 +6297,7 @@ "chat" ], "security": [ + {}, { "bearer_auth": [] }, @@ -6365,7 +6366,9 @@ "meta": { "$ref": "#/components/schemas/OCSMeta" }, - "data": {} + "data": { + "$ref": "#/components/schemas/Room" + } } } } diff --git a/openapi.json b/openapi.json index 0280bc76e5a..162d93a8f9a 100644 --- a/openapi.json +++ b/openapi.json @@ -6179,6 +6179,7 @@ "chat" ], "security": [ + {}, { "bearer_auth": [] }, @@ -6247,7 +6248,9 @@ "meta": { "$ref": "#/components/schemas/OCSMeta" }, - "data": {} + "data": { + "$ref": "#/components/schemas/Room" + } } } } From be9fa82fa8f06c89e388eb3cd2d784947dc52227 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 28 Feb 2024 19:34:09 +0100 Subject: [PATCH 4/7] fix(federation): Store last message id from the proxied message Signed-off-by: Joas Schilling --- lib/Federation/CloudFederationProviderTalk.php | 9 ++++++++- lib/Room.php | 10 +++++++++- lib/Service/RoomService.php | 12 ++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/lib/Federation/CloudFederationProviderTalk.php b/lib/Federation/CloudFederationProviderTalk.php index 0ff47da250b..add53f1fda2 100644 --- a/lib/Federation/CloudFederationProviderTalk.php +++ b/lib/Federation/CloudFederationProviderTalk.php @@ -340,12 +340,19 @@ private function messagePosted(int $remoteAttendeeId, array $notification): arra $message->setMessageType($notification['messageData']['messageType']); $message->setSystemMessage($notification['messageData']['systemMessage']); if ($notification['messageData']['expirationDatetime']) { - $message->setExpirationDateTime(new \DateTimeImmutable($notification['messageData']['expirationDatetime'])); + $message->setExpirationDatetime(new \DateTimeImmutable($notification['messageData']['expirationDatetime'])); } $message->setMessage($notification['messageData']['message']); $message->setMessageParameters($notification['messageData']['messageParameter']); + // FIXME catch unique constraint violation $this->proxyCacheMessagesMapper->insert($message); + $lastMessageId = $room->getLastMessageId(); + if ($notification['messageData']['remoteMessageId'] > $lastMessageId) { + $lastMessageId = (int) $notification['messageData']['remoteMessageId']; + } + $this->roomService->setLastMessageInfo($room, $lastMessageId, new \DateTime()); + if ($this->proxyCacheMessages instanceof ICache) { $cacheKey = sha1(json_encode([$notification['remoteServerUrl'], $notification['remoteToken']])); $cacheData = $this->proxyCacheMessages->get($cacheKey); diff --git a/lib/Room.php b/lib/Room.php index 381d0ff5470..5d6ebd62235 100644 --- a/lib/Room.php +++ b/lib/Room.php @@ -342,8 +342,16 @@ public function setLastActivity(\DateTime $now): void { $this->lastActivity = $now; } + public function getLastMessageId(): int { + return $this->lastMessageId; + } + + public function setLastMessageId(int $lastMessageId): void { + $this->lastMessageId = $lastMessageId; + } + public function getLastMessage(): ?IComment { - if ($this->lastMessageId && $this->lastMessage === null) { + if ($this->lastMessageId && $this->lastMessage === null && $this->getRemoteServer() === '') { $this->lastMessage = $this->manager->loadLastCommentInfo($this->lastMessageId); if ($this->lastMessage === null) { $this->lastMessageId = 0; diff --git a/lib/Service/RoomService.php b/lib/Service/RoomService.php index 1173d359aa0..d606282dd88 100644 --- a/lib/Service/RoomService.php +++ b/lib/Service/RoomService.php @@ -874,6 +874,18 @@ public function setLastMessage(Room $room, IComment $message): void { $room->setLastActivity($message->getCreationDateTime()); } + public function setLastMessageInfo(Room $room, int $messageId, \DateTime $dateTime): void { + $update = $this->db->getQueryBuilder(); + $update->update('talk_rooms') + ->set('last_message', $update->createNamedParameter($messageId)) + ->set('last_activity', $update->createNamedParameter($dateTime, 'datetime')) + ->where($update->expr()->eq('id', $update->createNamedParameter($room->getId(), IQueryBuilder::PARAM_INT))); + $update->executeStatement(); + + $room->setLastMessageId($messageId); + $room->setLastActivity($dateTime); + } + public function setLastActivity(Room $room, \DateTime $now): void { $update = $this->db->getQueryBuilder(); $update->update('talk_rooms') From bb51022fabffed0a3d7f96731078fe155f23c82d Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 28 Feb 2024 19:36:45 +0100 Subject: [PATCH 5/7] feat(federation): Expose the info to show the subline for proxy convos Signed-off-by: Joas Schilling --- lib/Model/ProxyCacheMessages.php | 30 +++++++++++++++-- lib/ResponseDefinitions.php | 15 ++++++++- lib/Service/RoomFormatter.php | 20 ++++++++--- openapi-backend-sipbridge.json | 58 +++++++++++++++++++++++++++++++- openapi-federation.json | 58 +++++++++++++++++++++++++++++++- openapi-full.json | 58 +++++++++++++++++++++++++++++++- openapi.json | 58 +++++++++++++++++++++++++++++++- 7 files changed, 285 insertions(+), 12 deletions(-) diff --git a/lib/Model/ProxyCacheMessages.php b/lib/Model/ProxyCacheMessages.php index 0d7cf7db78d..9803f3e2b70 100644 --- a/lib/Model/ProxyCacheMessages.php +++ b/lib/Model/ProxyCacheMessages.php @@ -26,6 +26,7 @@ namespace OCA\Talk\Model; +use OCA\Talk\ResponseDefinitions; use OCP\AppFramework\Db\Entity; /** @@ -47,14 +48,16 @@ * @method string getMessageType() * @method void setSystemMessage(?string $systemMessage) * @method string|null getSystemMessage() - * @method void setExpirationDateTime(?\DateTimeImmutable $expirationDateTime) - * @method \DateTimeImmutable|null getExpirationDateTime() + * @method void setExpirationDatetime(?\DateTimeImmutable $expirationDatetime) + * @method \DateTimeImmutable|null getExpirationDatetime() * @method void setMessage(?string $message) * @method string|null getMessage() * @method void setMessageParameters(?string $messageParameters) * @method string|null getMessageParameters() + * + * @psalm-import-type TalkRoomProxyMessage from ResponseDefinitions */ -class ProxyCacheMessages extends Entity { +class ProxyCacheMessages extends Entity implements \JsonSerializable { protected string $localToken = ''; protected string $remoteServerUrl = ''; @@ -83,4 +86,25 @@ public function __construct() { $this->addType('message', 'string'); $this->addType('messageParameters', 'string'); } + + /** + * @return TalkRoomProxyMessage + */ + public function jsonSerialize(): array { + $expirationTimestamp = 0; + if ($this->getExpirationDatetime()) { + $expirationTimestamp = $this->getExpirationDatetime()->getTimestamp(); + } + + return [ + 'actorType' => $this->getActorType(), + 'actorId' => $this->getActorId(), + 'actorDisplayName' => $this->getActorDisplayName(), + 'expirationTimestamp' => $expirationTimestamp, + 'messageType' => $this->getMessageType(), + 'systemMessage' => $this->getSystemMessage() ?? '', + 'message' => $this->getMessage() ?? '', + 'messageParameters' => json_decode($this->getMessageParameters() ?? '[]', true), + ]; + } } diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index 82136baef48..1f257d8224b 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -91,6 +91,19 @@ * silent?: bool, * } * + * @psalm-type TalkRoomProxyMessage = array{ + * actorDisplayName: string, + * actorId: string, + * actorType: string, + * expirationTimestamp: int, + * message: string, + * messageParameters: array>, + * messageType: string, + * systemMessage: string, + * } + * + * @psalm-type TalkRoomLastMessage = TalkChatMessage|TalkRoomProxyMessage + * * @psalm-type TalkChatMessageWithParent = TalkChatMessage&array{parent?: TalkChatMessage} * * @psalm-type TalkChatReminder = array{ @@ -208,7 +221,7 @@ * isFavorite: bool, * lastActivity: int, * lastCommonReadMessage: int, - * lastMessage: TalkChatMessage|array, + * lastMessage: TalkRoomLastMessage|array, * lastPing: int, * lastReadMessage: int, * listable: int, diff --git a/lib/Service/RoomFormatter.php b/lib/Service/RoomFormatter.php index 41c3e6aaf69..d20aa45318c 100644 --- a/lib/Service/RoomFormatter.php +++ b/lib/Service/RoomFormatter.php @@ -30,12 +30,14 @@ use OCA\Talk\Config; use OCA\Talk\Model\Attendee; use OCA\Talk\Model\BreakoutRoom; +use OCA\Talk\Model\ProxyCacheMessagesMapper; use OCA\Talk\Model\Session; use OCA\Talk\Participant; use OCA\Talk\ResponseDefinitions; use OCA\Talk\Room; use OCA\Talk\Webinary; use OCP\App\IAppManager; +use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Comments\IComment; use OCP\IConfig; @@ -46,7 +48,7 @@ use OCP\UserStatus\IUserStatus; /** - * @psalm-import-type TalkChatMessage from ResponseDefinitions + * @psalm-import-type TalkRoomLastMessage from ResponseDefinitions * @psalm-import-type TalkRoom from ResponseDefinitions */ class RoomFormatter { @@ -61,6 +63,7 @@ public function __construct( protected IAppManager $appManager, protected IManager $userStatusManager, protected IUserManager $userManager, + protected ProxyCacheMessagesMapper $proxyCacheMessagesMapper, protected IL10N $l10n, protected ?string $userId, ) { @@ -374,6 +377,7 @@ public function formatRoomV4( } } + $roomData['lastMessage'] = []; $lastMessage = $room->getLastMessage(); if ($room->getRemoteServer() === '' && $lastMessage instanceof IComment) { $roomData['lastMessage'] = $this->formatLastMessage( @@ -382,15 +386,23 @@ public function formatRoomV4( $currentParticipant, $lastMessage, ); - } else { - $roomData['lastMessage'] = []; + } elseif ($room->getRemoteServer() !== '') { + try { + $cachedMessage = $this->proxyCacheMessagesMapper->findByRemote( + $room->getRemoteServer(), + $room->getRemoteToken(), + $room->getLastMessageId(), + ); + $roomData['lastMessage'] = $cachedMessage->jsonSerialize(); + } catch (DoesNotExistException $e) { + } } return $roomData; } /** - * @return TalkChatMessage|array + * @return TalkRoomLastMessage|array */ public function formatLastMessage( string $responseFormat, diff --git a/openapi-backend-sipbridge.json b/openapi-backend-sipbridge.json index 4c8c76ae3e1..04f1ce51023 100644 --- a/openapi-backend-sipbridge.json +++ b/openapi-backend-sipbridge.json @@ -478,7 +478,7 @@ "lastMessage": { "oneOf": [ { - "$ref": "#/components/schemas/ChatMessage" + "$ref": "#/components/schemas/RoomLastMessage" }, { "type": "array", @@ -588,6 +588,62 @@ "format": "int64" } } + }, + "RoomLastMessage": { + "oneOf": [ + { + "$ref": "#/components/schemas/ChatMessage" + }, + { + "$ref": "#/components/schemas/RoomProxyMessage" + } + ] + }, + "RoomProxyMessage": { + "type": "object", + "required": [ + "actorDisplayName", + "actorId", + "actorType", + "expirationTimestamp", + "message", + "messageParameters", + "messageType", + "systemMessage" + ], + "properties": { + "actorDisplayName": { + "type": "string" + }, + "actorId": { + "type": "string" + }, + "actorType": { + "type": "string" + }, + "expirationTimestamp": { + "type": "integer", + "format": "int64" + }, + "message": { + "type": "string" + }, + "messageParameters": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + }, + "messageType": { + "type": "string" + }, + "systemMessage": { + "type": "string" + } + } } } }, diff --git a/openapi-federation.json b/openapi-federation.json index b4e024dedd8..94f1cc13e13 100644 --- a/openapi-federation.json +++ b/openapi-federation.json @@ -537,7 +537,7 @@ "lastMessage": { "oneOf": [ { - "$ref": "#/components/schemas/ChatMessage" + "$ref": "#/components/schemas/RoomLastMessage" }, { "type": "array", @@ -647,6 +647,62 @@ "format": "int64" } } + }, + "RoomLastMessage": { + "oneOf": [ + { + "$ref": "#/components/schemas/ChatMessage" + }, + { + "$ref": "#/components/schemas/RoomProxyMessage" + } + ] + }, + "RoomProxyMessage": { + "type": "object", + "required": [ + "actorDisplayName", + "actorId", + "actorType", + "expirationTimestamp", + "message", + "messageParameters", + "messageType", + "systemMessage" + ], + "properties": { + "actorDisplayName": { + "type": "string" + }, + "actorId": { + "type": "string" + }, + "actorType": { + "type": "string" + }, + "expirationTimestamp": { + "type": "integer", + "format": "int64" + }, + "message": { + "type": "string" + }, + "messageParameters": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + }, + "messageType": { + "type": "string" + }, + "systemMessage": { + "type": "string" + } + } } } }, diff --git a/openapi-full.json b/openapi-full.json index b4872e55df4..79512b6a4e8 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -1002,7 +1002,7 @@ "lastMessage": { "oneOf": [ { - "$ref": "#/components/schemas/ChatMessage" + "$ref": "#/components/schemas/RoomLastMessage" }, { "type": "array", @@ -1113,6 +1113,62 @@ } } }, + "RoomLastMessage": { + "oneOf": [ + { + "$ref": "#/components/schemas/ChatMessage" + }, + { + "$ref": "#/components/schemas/RoomProxyMessage" + } + ] + }, + "RoomProxyMessage": { + "type": "object", + "required": [ + "actorDisplayName", + "actorId", + "actorType", + "expirationTimestamp", + "message", + "messageParameters", + "messageType", + "systemMessage" + ], + "properties": { + "actorDisplayName": { + "type": "string" + }, + "actorId": { + "type": "string" + }, + "actorType": { + "type": "string" + }, + "expirationTimestamp": { + "type": "integer", + "format": "int64" + }, + "message": { + "type": "string" + }, + "messageParameters": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + }, + "messageType": { + "type": "string" + }, + "systemMessage": { + "type": "string" + } + } + }, "SignalingSession": { "type": "object", "required": [ diff --git a/openapi.json b/openapi.json index 162d93a8f9a..93b882007eb 100644 --- a/openapi.json +++ b/openapi.json @@ -884,7 +884,7 @@ "lastMessage": { "oneOf": [ { - "$ref": "#/components/schemas/ChatMessage" + "$ref": "#/components/schemas/RoomLastMessage" }, { "type": "array", @@ -995,6 +995,62 @@ } } }, + "RoomLastMessage": { + "oneOf": [ + { + "$ref": "#/components/schemas/ChatMessage" + }, + { + "$ref": "#/components/schemas/RoomProxyMessage" + } + ] + }, + "RoomProxyMessage": { + "type": "object", + "required": [ + "actorDisplayName", + "actorId", + "actorType", + "expirationTimestamp", + "message", + "messageParameters", + "messageType", + "systemMessage" + ], + "properties": { + "actorDisplayName": { + "type": "string" + }, + "actorId": { + "type": "string" + }, + "actorType": { + "type": "string" + }, + "expirationTimestamp": { + "type": "integer", + "format": "int64" + }, + "message": { + "type": "string" + }, + "messageParameters": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + }, + "messageType": { + "type": "string" + }, + "systemMessage": { + "type": "string" + } + } + }, "SignalingSession": { "type": "object", "required": [ From 05f5ef3b39f358937067830f0e8e0268548c58d6 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 28 Feb 2024 21:46:26 +0100 Subject: [PATCH 6/7] feat(federation): Mark conversation as directly mentioned for federated users Signed-off-by: Joas Schilling --- lib/Chat/ChatManager.php | 24 ++++++++++--- lib/Chat/Notifier.php | 56 ++++++++++++++++++++++++++++-- lib/Service/ParticipantService.php | 16 ++++----- 3 files changed, 80 insertions(+), 16 deletions(-) diff --git a/lib/Chat/ChatManager.php b/lib/Chat/ChatManager.php index a4c349cbc1b..fc145c8858d 100644 --- a/lib/Chat/ChatManager.php +++ b/lib/Chat/ChatManager.php @@ -177,17 +177,20 @@ public function addSystemMessage( if ($sendNotifications) { /** @var ?IComment $captionComment */ $captionComment = null; - $alreadyNotifiedUsers = $usersDirectlyMentioned = []; + $alreadyNotifiedUsers = $usersDirectlyMentioned = $federatedUsersDirectlyMentioned = []; if ($messageType === 'file_shared') { if (isset($messageDecoded['parameters']['metaData']['caption'])) { $captionComment = clone $comment; $captionComment->setMessage($messageDecoded['parameters']['metaData']['caption'], self::MAX_CHAT_LENGTH); $usersDirectlyMentioned = $this->notifier->getMentionedUserIds($captionComment); + $federatedUsersDirectlyMentioned = $this->notifier->getMentionedCloudIds($captionComment); } if ($replyTo instanceof IComment) { $alreadyNotifiedUsers = $this->notifier->notifyReplyToAuthor($chat, $comment, $replyTo, $silent); if ($replyTo->getActorType() === Attendee::ACTOR_USERS) { $usersDirectlyMentioned[] = $replyTo->getActorId(); + } elseif ($replyTo->getActorType() === Attendee::ACTOR_FEDERATED_USERS) { + $federatedUsersDirectlyMentioned[] = $replyTo->getActorId(); } } } @@ -195,7 +198,10 @@ public function addSystemMessage( $alreadyNotifiedUsers = $this->notifier->notifyMentionedUsers($chat, $captionComment ?? $comment, $alreadyNotifiedUsers, $silent); if (!empty($alreadyNotifiedUsers)) { $userIds = array_column($alreadyNotifiedUsers, 'id'); - $this->participantService->markUsersAsMentioned($chat, $userIds, (int) $comment->getId(), $usersDirectlyMentioned); + $this->participantService->markUsersAsMentioned($chat, Attendee::ACTOR_USERS, $userIds, (int) $comment->getId(), $usersDirectlyMentioned); + } + if (!empty($federatedUsersDirectlyMentioned)) { + $this->participantService->markUsersAsMentioned($chat, Attendee::ACTOR_FEDERATED_USERS, $federatedUsersDirectlyMentioned, (int) $comment->getId(), $federatedUsersDirectlyMentioned); } $this->notifier->notifyOtherParticipant($chat, $comment, [], $silent); @@ -326,17 +332,23 @@ public function sendMessage(Room $chat, ?Participant $participant, string $actor $alreadyNotifiedUsers = []; $usersDirectlyMentioned = $this->notifier->getMentionedUserIds($comment); + $federatedUsersDirectlyMentioned = $this->notifier->getMentionedCloudIds($comment); if ($replyTo instanceof IComment) { $alreadyNotifiedUsers = $this->notifier->notifyReplyToAuthor($chat, $comment, $replyTo, $silent); if ($replyTo->getActorType() === Attendee::ACTOR_USERS) { $usersDirectlyMentioned[] = $replyTo->getActorId(); + } elseif ($replyTo->getActorType() === Attendee::ACTOR_FEDERATED_USERS) { + $federatedUsersDirectlyMentioned[] = $replyTo->getActorId(); } } $alreadyNotifiedUsers = $this->notifier->notifyMentionedUsers($chat, $comment, $alreadyNotifiedUsers, $silent); if (!empty($alreadyNotifiedUsers)) { $userIds = array_column($alreadyNotifiedUsers, 'id'); - $this->participantService->markUsersAsMentioned($chat, $userIds, (int) $comment->getId(), $usersDirectlyMentioned); + $this->participantService->markUsersAsMentioned($chat, Attendee::ACTOR_USERS, $userIds, (int) $comment->getId(), $usersDirectlyMentioned); + } + if (!empty($federatedUsersDirectlyMentioned)) { + $this->participantService->markUsersAsMentioned($chat, Attendee::ACTOR_FEDERATED_USERS, $federatedUsersDirectlyMentioned, (int) $comment->getId(), $federatedUsersDirectlyMentioned); } // User was not mentioned, send a normal notification @@ -561,12 +573,16 @@ public function editMessage(Room $chat, IComment $comment, Participant $particip if (!empty($addedMentions)) { $usersDirectlyMentionedAfter = $this->notifier->getMentionedUserIds($comment); + $federatedUsersDirectlyMentionedAfter = $this->notifier->getMentionedCloudIds($comment); $addedUsersDirectMentioned = array_diff($usersDirectlyMentionedAfter, $usersDirectlyMentionedBefore); $alreadyNotifiedUsers = $this->notifier->notifyMentionedUsers($chat, $comment, $usersToNotifyBefore, silent: false); if (!empty($alreadyNotifiedUsers)) { $userIds = array_column($alreadyNotifiedUsers, 'id'); - $this->participantService->markUsersAsMentioned($chat, $userIds, (int) $comment->getId(), $addedUsersDirectMentioned); + $this->participantService->markUsersAsMentioned($chat, Attendee::ACTOR_USERS, $userIds, (int) $comment->getId(), $addedUsersDirectMentioned); + } + if (!empty($federatedUsersDirectlyMentionedAfter)) { + $this->participantService->markUsersAsMentioned($chat, Attendee::ACTOR_FEDERATED_USERS, $federatedUsersDirectlyMentionedAfter, (int) $comment->getId(), $federatedUsersDirectlyMentionedAfter); } } } diff --git a/lib/Chat/Notifier.php b/lib/Chat/Notifier.php index 8ba24bd1547..7d6393dda46 100644 --- a/lib/Chat/Notifier.php +++ b/lib/Chat/Notifier.php @@ -202,11 +202,21 @@ private function addMentionAllToList(Room $chat, array $list): array { * @psalm-return array */ public function notifyReplyToAuthor(Room $chat, IComment $comment, IComment $replyTo, bool $silent): array { - if ($replyTo->getActorType() !== Attendee::ACTOR_USERS) { - // No reply notification when the replyTo-author was not a user + if ($replyTo->getActorType() !== Attendee::ACTOR_USERS && $replyTo->getActorType() !== Attendee::ACTOR_FEDERATED_USERS) { + // No reply notification when the replyTo-author was not a user or federated user return []; } + if ($replyTo->getActorType() === Attendee::ACTOR_FEDERATED_USERS) { + return [ + [ + 'id' => $replyTo->getActorId(), + 'type' => $replyTo->getActorType(), + 'reason' => 'reply', + ], + ]; + } + if (!$this->shouldMentionedUserBeNotified($replyTo->getActorId(), $comment, $chat)) { return []; } @@ -407,6 +417,19 @@ public function getMentionedUserIds(IComment $comment): array { }, $mentionedUsers); } + /** + * Returns the cloud IDs of the federated users mentioned in the given comment. + * + * @param IComment $comment + * @return string[] the mentioned cloud IDs + */ + public function getMentionedCloudIds(IComment $comment): array { + $mentionedFederatedUsers = $this->getMentionedFederatedUsers($comment); + return array_map(static function ($mentionedUser) { + return $mentionedUser['id']; + }, $mentionedFederatedUsers); + } + /** * @param IComment $comment * @return array[] @@ -427,7 +450,34 @@ private function getMentionedUsers(IComment $comment): array { $mentionedUsers[] = [ 'id' => $mention['id'], - 'type' => 'users', + 'type' => Attendee::ACTOR_USERS, + 'reason' => 'direct', + ]; + } + return $mentionedUsers; + } + + /** + * @param IComment $comment + * @return array[] + * @psalm-return array + */ + private function getMentionedFederatedUsers(IComment $comment): array { + $mentions = $comment->getMentions(); + + if (empty($mentions)) { + return []; + } + + $mentionedUsers = []; + foreach ($mentions as $mention) { + if ($mention['type'] !== 'federated_user') { + continue; + } + + $mentionedUsers[] = [ + 'id' => $mention['id'], + 'type' => Attendee::ACTOR_FEDERATED_USERS, 'reason' => 'direct', ]; } diff --git a/lib/Service/ParticipantService.php b/lib/Service/ParticipantService.php index 6d0711ed6c3..7ea81e69c7e 100644 --- a/lib/Service/ParticipantService.php +++ b/lib/Service/ParticipantService.php @@ -1283,28 +1283,26 @@ public function updateCallFlags(Room $room, Participant $participant, int $flags } /** - * @param Room $room - * @param string[] $userIds - * @param int $messageId + * @param string[] $actorIds * @param string[] $usersDirectlyMentioned */ - public function markUsersAsMentioned(Room $room, array $userIds, int $messageId, array $usersDirectlyMentioned): void { + public function markUsersAsMentioned(Room $room, string $actorType, array $actorIds, int $messageId, array $actorsDirectlyMentioned): void { $update = $this->connection->getQueryBuilder(); $update->update('talk_attendees') ->set('last_mention_message', $update->createNamedParameter($messageId, IQueryBuilder::PARAM_INT)) ->where($update->expr()->eq('room_id', $update->createNamedParameter($room->getId(), IQueryBuilder::PARAM_INT))) - ->andWhere($update->expr()->eq('actor_type', $update->createNamedParameter(Attendee::ACTOR_USERS))) - ->andWhere($update->expr()->in('actor_id', $update->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY))) + ->andWhere($update->expr()->eq('actor_type', $update->createNamedParameter($actorType))) + ->andWhere($update->expr()->in('actor_id', $update->createNamedParameter($actorIds, IQueryBuilder::PARAM_STR_ARRAY))) ->andWhere($update->expr()->lt('last_mention_message', $update->createNamedParameter($messageId, IQueryBuilder::PARAM_INT))); $update->executeStatement(); - if (!empty($usersDirectlyMentioned)) { + if (!empty($actorsDirectlyMentioned)) { $update = $this->connection->getQueryBuilder(); $update->update('talk_attendees') ->set('last_mention_direct', $update->createNamedParameter($messageId, IQueryBuilder::PARAM_INT)) ->where($update->expr()->eq('room_id', $update->createNamedParameter($room->getId(), IQueryBuilder::PARAM_INT))) - ->andWhere($update->expr()->eq('actor_type', $update->createNamedParameter(Attendee::ACTOR_USERS))) - ->andWhere($update->expr()->in('actor_id', $update->createNamedParameter($usersDirectlyMentioned, IQueryBuilder::PARAM_STR_ARRAY))) + ->andWhere($update->expr()->eq('actor_type', $update->createNamedParameter($actorType))) + ->andWhere($update->expr()->in('actor_id', $update->createNamedParameter($actorsDirectlyMentioned, IQueryBuilder::PARAM_STR_ARRAY))) ->andWhere($update->expr()->lt('last_mention_direct', $update->createNamedParameter($messageId, IQueryBuilder::PARAM_INT))); $update->executeStatement(); } From c2a9f36adaf3b33b5d783857c1d41df49e3d795b Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 28 Feb 2024 22:07:20 +0100 Subject: [PATCH 7/7] fix(federation): Don't fail when the same message is already stored in the proxy cache Signed-off-by: Joas Schilling --- .../CloudFederationProviderTalk.php | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/lib/Federation/CloudFederationProviderTalk.php b/lib/Federation/CloudFederationProviderTalk.php index add53f1fda2..9c506410278 100644 --- a/lib/Federation/CloudFederationProviderTalk.php +++ b/lib/Federation/CloudFederationProviderTalk.php @@ -344,20 +344,29 @@ private function messagePosted(int $remoteAttendeeId, array $notification): arra } $message->setMessage($notification['messageData']['message']); $message->setMessageParameters($notification['messageData']['messageParameter']); - // FIXME catch unique constraint violation - $this->proxyCacheMessagesMapper->insert($message); - - $lastMessageId = $room->getLastMessageId(); - if ($notification['messageData']['remoteMessageId'] > $lastMessageId) { - $lastMessageId = (int) $notification['messageData']['remoteMessageId']; - } - $this->roomService->setLastMessageInfo($room, $lastMessageId, new \DateTime()); + try { + $this->proxyCacheMessagesMapper->insert($message); - if ($this->proxyCacheMessages instanceof ICache) { - $cacheKey = sha1(json_encode([$notification['remoteServerUrl'], $notification['remoteToken']])); - $cacheData = $this->proxyCacheMessages->get($cacheKey); - if ($cacheData === null || $cacheData < $notification['messageData']['remoteMessageId']) { - $this->proxyCacheMessages->set($cacheKey, $notification['messageData']['remoteMessageId'], 300); + $lastMessageId = $room->getLastMessageId(); + if ($notification['messageData']['remoteMessageId'] > $lastMessageId) { + $lastMessageId = (int) $notification['messageData']['remoteMessageId']; + } + $this->roomService->setLastMessageInfo($room, $lastMessageId, new \DateTime()); + + if ($this->proxyCacheMessages instanceof ICache) { + $cacheKey = sha1(json_encode([$notification['remoteServerUrl'], $notification['remoteToken']])); + $cacheData = $this->proxyCacheMessages->get($cacheKey); + if ($cacheData === null || $cacheData < $notification['messageData']['remoteMessageId']) { + $this->proxyCacheMessages->set($cacheKey, $notification['messageData']['remoteMessageId'], 300); + } + } + } catch (DBException $e) { + // DBException::REASON_UNIQUE_CONSTRAINT_VIOLATION happens when + // multiple users are in the same conversation. We are therefore + // informed multiple times about the same remote message. + if ($e->getCode() !== DBException::REASON_UNIQUE_CONSTRAINT_VIOLATION) { + $this->logger->error('Error saving proxy cache message failed: ' . $e->getMessage(), ['exception' => $e]); + throw $e; } }