diff --git a/lib/Events/BotInvokeEvent.php b/lib/Events/BotInvokeEvent.php index 27146d680c1..b508a463720 100644 --- a/lib/Events/BotInvokeEvent.php +++ b/lib/Events/BotInvokeEvent.php @@ -11,20 +11,36 @@ use OCP\EventDispatcher\Event; /** + * @psalm-type ChatMessageParentData = array{ + * type: 'Note', + * actor: array{ + * type: 'Person', + * id: non-empty-string, + * name: non-empty-string, + * }, + * object: array{ + * type: 'Note', + * id: numeric-string, + * name: string, + * content: non-empty-string, + * mediaType: 'text/markdown'|'text/plain', + * }, + * } * @psalm-type ChatMessageData = array{ * type: 'Activity'|'Create', * actor: array{ * type: 'Person', * id: non-empty-string, * name: non-empty-string, - * talkParticipantType: non-empty-string, + * talkParticipantType: numeric-string, * }, * object: array{ * type: 'Note', - * id: non-empty-string, + * id: numeric-string, * name: string, * content: non-empty-string, * mediaType: 'text/markdown'|'text/plain', + * inReplyTo?: ChatMessageParentData, * }, * target: array{ * type: 'Collection', diff --git a/lib/Service/ActivityPubHelper.php b/lib/Service/ActivityPubHelper.php new file mode 100644 index 00000000000..8d649093483 --- /dev/null +++ b/lib/Service/ActivityPubHelper.php @@ -0,0 +1,87 @@ + 'Application', + 'id' => Attendee::ACTOR_BOTS . '/' . Attendee::ACTOR_BOT_PREFIX . $bot->getUrlHash(), + 'name' => $bot->getName(), + ]; + } + + /** + * @return array{type: 'Collection', id: non-empty-string, name: string} + */ + public function generateCollectionFromRoom(Room $room): array { + /** @var non-empty-string $token */ + $token = $room->getToken(); + return [ + 'type' => 'Collection', + 'id' => $token, + 'name' => $room->getName(), + ]; + } + + /** + * @psalm-param ?ChatMessageParentData $inReplyTo + * @psalm-return NoteType&array{inReplyTo?: ChatMessageParentData} + */ + public function generateNote(IComment $comment, array $messageData, string $messageType, ?array $inReplyTo = null): array { + /** @var string $content */ + $content = json_encode($messageData, JSON_THROW_ON_ERROR); + /** @var numeric-string $messageId */ + $messageId = $comment->getId(); + /** @var 'text/markdown'|'text/plain' $mediaType */ + $mediaType = 'text/markdown';// FIXME or text/plain when markdown is disabled + $note = [ + 'type' => 'Note', + 'id' => $messageId, + 'name' => $messageType, + 'content' => $content, + 'mediaType' => $mediaType, + ]; + if ($inReplyTo !== null) { + $note['inReplyTo'] = $inReplyTo; + } + return $note; + } + + /** + * @return array{type: 'Person', id: non-falsy-string, name: string, talkParticipantType: numeric-string} + */ + public function generatePersonFromAttendee(Attendee $attendee): array { + return [ + 'type' => 'Person', + 'id' => $attendee->getActorType() . '/' . $attendee->getActorId(), + 'name' => $attendee->getDisplayName(), + 'talkParticipantType' => (string)$attendee->getParticipantType(), + ]; + } + + /** + * @return array{type: 'Person', id: non-falsy-string, name: string} + */ + public function generatePersonFromMessageActor(Message $message): array { + return [ + 'type' => 'Person', + 'id' => $message->getActorType() . '/' . $message->getActorId(), + 'name' => $message->getActorDisplayName(), + ]; + } +} diff --git a/lib/Service/BotService.php b/lib/Service/BotService.php index ba3f0e85684..dacee94f0af 100644 --- a/lib/Service/BotService.php +++ b/lib/Service/BotService.php @@ -46,6 +46,8 @@ * @psalm-import-type InvocationData from BotInvokeEvent */ class BotService { + private ActivityPubHelper $activityPubHelper; + public function __construct( protected BotServerMapper $botServerMapper, protected BotConversationMapper $botConversationMapper, @@ -62,37 +64,22 @@ public function __construct( protected ICertificateManager $certificateManager, protected IEventDispatcher $dispatcher, ) { + $this->activityPubHelper = new ActivityPubHelper(); } public function afterBotEnabled(BotEnabledEvent $event): void { $this->invokeBots([$event->getBotServer()], $event->getRoom(), null, [ 'type' => 'Join', - 'actor' => [ - 'type' => 'Application', - 'id' => Attendee::ACTOR_BOTS . '/' . Attendee::ACTOR_BOT_PREFIX . $event->getBotServer()->getUrlHash(), - 'name' => $event->getBotServer()->getName(), - ], - 'object' => [ - 'type' => 'Collection', - 'id' => $event->getRoom()->getToken(), - 'name' => $event->getRoom()->getName(), - ], + 'actor' => $this->activityPubHelper->generateApplicationFromBot($event->getBotServer()), + 'object' => $this->activityPubHelper->generateCollectionFromRoom($event->getRoom()), ]); } public function afterBotDisabled(BotDisabledEvent $event): void { $this->invokeBots([$event->getBotServer()], $event->getRoom(), null, [ 'type' => 'Leave', - 'actor' => [ - 'type' => 'Application', - 'id' => Attendee::ACTOR_BOTS . '/' . Attendee::ACTOR_BOT_PREFIX . $event->getBotServer()->getUrlHash(), - 'name' => $event->getBotServer()->getName(), - ], - 'object' => [ - 'type' => 'Collection', - 'id' => $event->getRoom()->getToken(), - 'name' => $event->getRoom()->getName(), - ], + 'actor' => $this->activityPubHelper->generateApplicationFromBot($event->getBotServer()), + 'object' => $this->activityPubHelper->generateCollectionFromRoom($event->getRoom()), ]); } @@ -108,6 +95,28 @@ public function afterChatMessageSent(ChatMessageSentEvent $event, MessageParser return; } + $inReplyTo = null; + $parent = $event->getParent(); + if ($parent instanceof IComment) { + $parentMessage = $messageParser->createMessage( + $event->getRoom(), + $event->getParticipant(), + $parent, + $this->l10nFactory->get('spreed', 'en', 'en') + ); + $messageParser->parseMessage($parentMessage); + $parentMessageData = [ + 'message' => $parentMessage->getMessage(), + 'parameters' => $parentMessage->getMessageParameters(), + ]; + + $inReplyTo = [ + 'type' => 'Note', + 'actor' => $this->activityPubHelper->generatePersonFromMessageActor($parentMessage), + 'object' => $this->activityPubHelper->generateNote($parent, $parentMessageData, 'message'), + ]; + } + $message = $messageParser->createMessage( $event->getRoom(), $event->getParticipant(), @@ -124,24 +133,9 @@ public function afterChatMessageSent(ChatMessageSentEvent $event, MessageParser $this->invokeBots($botServers, $event->getRoom(), $event->getComment(), [ 'type' => 'Create', - 'actor' => [ - 'type' => 'Person', - 'id' => $attendee->getActorType() . '/' . $attendee->getActorId(), - 'name' => $attendee->getDisplayName(), - 'talkParticipantType' => (string)$attendee->getParticipantType(), - ], - 'object' => [ - 'type' => 'Note', - 'id' => $event->getComment()->getId(), - 'name' => 'message', - 'content' => json_encode($messageData, JSON_THROW_ON_ERROR), - 'mediaType' => 'text/markdown', // FIXME or text/plain when markdown is disabled - ], - 'target' => [ - 'type' => 'Collection', - 'id' => $event->getRoom()->getToken(), - 'name' => $event->getRoom()->getName(), - ] + 'actor' => $this->activityPubHelper->generatePersonFromAttendee($attendee), + 'object' => $this->activityPubHelper->generateNote($event->getComment(), $messageData, 'message', $inReplyTo), + 'target' => $this->activityPubHelper->generateCollectionFromRoom($event->getRoom()), ]); } @@ -167,23 +161,9 @@ public function afterSystemMessageSent(SystemMessageSentEvent $event, MessagePar $this->invokeBots($botServers, $event->getRoom(), $event->getComment(), [ 'type' => 'Activity', - 'actor' => [ - 'type' => 'Person', - 'id' => $message->getActorType() . '/' . $message->getActorId(), - 'name' => $message->getActorDisplayName(), - ], - 'object' => [ - 'type' => 'Note', - 'id' => $event->getComment()->getId(), - 'name' => $message->getMessageRaw(), - 'content' => json_encode($messageData), - 'mediaType' => 'text/markdown', - ], - 'target' => [ - 'type' => 'Collection', - 'id' => $event->getRoom()->getToken(), - 'name' => $event->getRoom()->getName(), - ] + 'actor' => $this->activityPubHelper->generatePersonFromMessageActor($message), + 'object' => $this->activityPubHelper->generateNote($event->getComment(), $messageData, $message->getMessageRaw()), + 'target' => $this->activityPubHelper->generateCollectionFromRoom($event->getRoom()), ]); }