diff --git a/lib/Federation/BackendNotifier.php b/lib/Federation/BackendNotifier.php index db891f07c92..1ae56366c6f 100644 --- a/lib/Federation/BackendNotifier.php +++ b/lib/Federation/BackendNotifier.php @@ -560,4 +560,29 @@ protected function getServerRemoteUrl(): string { return $server; } + + public function sendReactionNotification( + string $remoteServer, + int $localAttendeeId, + string $accessToken, + string $localToken, + array $reactionData + ): ?bool { + $remote = $this->prepareRemoteUrl($remoteServer); + + $notification = $this->cloudFederationFactory->getCloudFederationNotification(); + $notification->setMessage( + FederationManager::NOTIFICATION_REACTION_ADDED, + FederationManager::TALK_ROOM_RESOURCE, + (string)$localAttendeeId, + [ + 'remoteServerUrl' => $this->getServerRemoteUrl(), + 'sharedSecret' => $accessToken, + 'remoteToken' => $localToken, + 'reactionData' => $reactionData, + ] + ); + + return $this->sendUpdateToRemote($remote, $notification); + } } diff --git a/lib/Federation/FederationManager.php b/lib/Federation/FederationManager.php index 4c3706b9d51..05b2f07f051 100644 --- a/lib/Federation/FederationManager.php +++ b/lib/Federation/FederationManager.php @@ -46,6 +46,7 @@ class FederationManager { public const NOTIFICATION_PARTICIPANT_MODIFIED = 'PARTICIPANT_MODIFIED'; public const NOTIFICATION_ROOM_MODIFIED = 'ROOM_MODIFIED'; public const NOTIFICATION_MESSAGE_POSTED = 'MESSAGE_POSTED'; + public const NOTIFICATION_REACTION_ADDED = 'talk-reaction-added'; public const TOKEN_LENGTH = 64; public function __construct( diff --git a/lib/Federation/Proxy/TalkV1/Notifier/MessageSentListener.php b/lib/Federation/Proxy/TalkV1/Notifier/MessageSentListener.php index 7f54abb095d..e9eb0a70b27 100644 --- a/lib/Federation/Proxy/TalkV1/Notifier/MessageSentListener.php +++ b/lib/Federation/Proxy/TalkV1/Notifier/MessageSentListener.php @@ -37,6 +37,8 @@ public function __construct( protected MessageParser $messageParser, protected IFactory $l10nFactory, protected ChatManager $chatManager, + protected IUserManager $userManager, + protected IAppConfig $appConfig, ) { } @@ -57,6 +59,14 @@ public function handle(Event $event): void { $chatMessage = $this->messageParser->createMessage($event->getRoom(), null, $event->getComment(), $l); $this->messageParser->parseMessage($chatMessage); + $messageType = $chatMessage->getMessageType(); + + // Handle reaction events + if ($messageType === ChatManager::VERB_REACTION) { + $this->createReactionNotification($event->getRoom(), $event->getComment()); + return; + } + $systemMessage = $chatMessage->getMessageType() === ChatManager::VERB_SYSTEM ? $chatMessage->getMessageRaw() : ''; if ($systemMessage !== 'message_edited' && $systemMessage !== 'message_deleted' @@ -124,4 +134,84 @@ public function handle(Event $event): void { } } } + + private function createReactionNotification(Room $chat, IComment $reaction): void { + $originalCommentId = $reaction->getObjectId(); + $originalComment = $this->chatManager->getCommentById($originalCommentId); + + if ($originalComment === null) { + return; + } + + // Check if the original comment's author should be notified + if ($originalComment->getActorType() === Attendee::ACTOR_USERS || $originalComment->getActorType() === Attendee::ACTOR_FEDERATED_USERS) { + try { + $participant = $this->participantService->getParticipant($chat, $originalComment->getActorId(), false); + } catch (ParticipantNotFoundException $e) { + return; + } + + $notificationLevel = $participant->getAttendee()->getNotificationLevel(); + if ($notificationLevel === Participant::NOTIFY_DEFAULT) { + if ($chat->getType() === Room::TYPE_ONE_TO_ONE) { + $notificationLevel = Participant::NOTIFY_ALWAYS; + } else { + $notificationLevel = $this->getDefaultGroupNotification(); + } + } + + if ($notificationLevel === Participant::NOTIFY_ALWAYS) { + $this->sendReactionNotification( + $participant, + $chat, + $reaction, + $originalComment + ); + } + } + } + + private function sendReactionNotification( + Participant $participant, + Room $room, + IComment $reaction, + IComment $originalComment + ): void { + $attendee = $participant->getAttendee(); + $cloudId = $this->cloudIdManager->resolveCloudId($attendee->getActorId()); + + $notificationData = [ + 'remoteMessageId' => (int)$originalComment->getId(), + 'reaction' => $reaction->getMessage(), + 'reactorType' => $reaction->getActorType(), + 'reactorId' => $reaction->getActorId(), + 'reactorDisplayName' => $this->getDisplayName($reaction->getActorType(), $reaction->getActorId()), + 'creationDatetime' => $reaction->getCreationDateTime()->format(\DateTime::ATOM), + ]; + + // Send the notification using BackendNotifier + $success = $this->backendNotifier->sendReactionNotification( + $cloudId->getRemote(), + $attendee->getId(), + $attendee->getAccessToken(), + $room->getToken(), + $notificationData + ); + + if ($success === null) { + $this->participantService->removeAttendee($room, $participant, AAttendeeRemovedEvent::REASON_LEFT); + } + } + + private function getDisplayName(string $actorType, string $actorId): string { + if ($actorType === Attendee::ACTOR_USERS) { + $user = $this->userManager->get($actorId); + return $user ? $user->getDisplayName() : $actorId; + } elseif ($actorType === Attendee::ACTOR_FEDERATED_USERS) { + $cloudId = $this->cloudIdManager->resolveCloudId($actorId); + return $cloudId->getDisplayName() ?? $actorId; + } + return $actorId; + } + } diff --git a/tests/integration/features/federation/chat.feature b/tests/integration/features/federation/chat.feature index 5550308477d..e959cc3182d 100644 --- a/tests/integration/features/federation/chat.feature +++ b/tests/integration/features/federation/chat.feature @@ -509,3 +509,44 @@ Feature: federation/chat | actorType | actorId | actorDisplayName | reaction | | users | participant1 | participant1-displayname | 🚀 | | federated_users | participant2@{$REMOTE_URL} | participant2-displayname | 🚀 | + + # Scenario: Verifying Notifications for Local Participants + + # Given the following "spreed" app config is set + # | federation_enabled | yes | + # And user "participant1" creates room "room" (v4) + # | roomType | 2 | + # | roomName | room | + # And user "participant1" adds federated_user "participant3" to room "room" with 200 (v4) + # And user "participant1" adds user "participant2" to room "room" with 200 (v4) + # And user "participant3" has the following invitations (v1) + # | remoteServerUrl | remoteToken | state | inviterCloudId | inviterDisplayName | + # | LOCAL | room | 0 | participant1@http://localhost:8080 | participant1-displayname | + # And user "participant3" accepts invite to room "room" of server "LOCAL" with 200 (v1) + # | id | name | type | remoteServer | remoteToken | + # | LOCAL::room | room | 2 | LOCAL | room | + + # # Enable Notifications for Local User + # And user "participant2" sets notifications to all for room "room" (v4) + + # #fed user + # #And user "participant3" sets notifications to all for room "room" (v4) + # And user "participant3" sets notifications to all for room "LOCAL::room" (v4) + + # # Sending a Message + # When user "participant1" sends message "Message 1" to room "room" with 201 + + # And user "participant1" react with "🚀" on message "Message 1" to room "room" with 201 + # | actorType | actorId | actorDisplayName | reaction | + # | users | participant1 | participant1-displayname | 🚀 | + + # # Verification for Message Notification + # Then user "participant2" has the following notifications + # | app | object_type | object_id | subject | message | + # | spreed | chat | room/Message 1 | participant1-displayname sent a message in conversation room | Message 1 | + # | spreed | room | room | participant1-displayname invited you to a group conversation: room | | + + # Then user "participant3" has the following notifications + # | app | object_type | object_id | subject | message | + # | spreed | chat | LOCAL::room/Message 1 | participant1-displayname sent a message in conversation room | Message 1 | + # #| spreed | room | LOCAL::room | participant1-displayname invited you to a group conversation: room | | \ No newline at end of file