Browse Source

feat(bots): Allow bots to get invoked for reactions

Signed-off-by: Joas Schilling <coding@schilljs.com>
pull/14335/head
Joas Schilling 9 months ago
parent
commit
ce7a13a109
No known key found for this signature in database GPG Key ID: F72FA5B49FFA96B0
  1. 12
      docs/events.md
  2. 4
      lib/AppInfo/Application.php
  3. 29
      lib/Chat/ReactionManager.php
  4. 2
      lib/Controller/BotController.php
  5. 2
      lib/Controller/ReactionController.php
  6. 45
      lib/Events/AReactionEvent.php
  7. 12
      lib/Events/BeforeReactionAddedEvent.php
  8. 12
      lib/Events/BeforeReactionRemovedEvent.php
  9. 40
      lib/Events/BotInvokeEvent.php
  10. 12
      lib/Events/ReactionAddedEvent.php
  11. 12
      lib/Events/ReactionRemovedEvent.php
  12. 10
      lib/Listener/BotListener.php
  13. 3
      lib/Model/Bot.php
  14. 66
      lib/Service/BotService.php
  15. 2
      lib/Service/SampleConversationsService.php

12
docs/events.md

@ -186,6 +186,18 @@ listen to the `OCA\Talk\Events\SystemMessagesMultipleSentEvent` event instead.
* Since: 18.0.0
* Since: 19.0.0 - Method `getParent()` was added
### Reaction added
* Before event: `OCA\Talk\Events\BeforeReactionAddedEvent`
* After event: `OCA\Talk\Events\ReactionAddedEvent`
* Since: 21.0.0
### Reaction removed
* Before event: `OCA\Talk\Events\BeforeReactionRemovedEvent`
* After event: `OCA\Talk\Events\ReactionRemovedEvent`
* Since: 21.0.0
## Other events
### Turn servers get

4
lib/AppInfo/Application.php

@ -59,6 +59,8 @@ use OCA\Talk\Events\GuestsCleanedUpEvent;
use OCA\Talk\Events\LobbyModifiedEvent;
use OCA\Talk\Events\MessageParseEvent;
use OCA\Talk\Events\ParticipantModifiedEvent;
use OCA\Talk\Events\ReactionAddedEvent;
use OCA\Talk\Events\ReactionRemovedEvent;
use OCA\Talk\Events\RoomCreatedEvent;
use OCA\Talk\Events\RoomDeletedEvent;
use OCA\Talk\Events\RoomModifiedEvent;
@ -194,6 +196,8 @@ class Application extends App implements IBootstrap {
$context->registerEventListener(BotInstallEvent::class, BotListener::class);
$context->registerEventListener(BotUninstallEvent::class, BotListener::class);
$context->registerEventListener(ChatMessageSentEvent::class, BotListener::class);
$context->registerEventListener(ReactionAddedEvent::class, BotListener::class);
$context->registerEventListener(ReactionRemovedEvent::class, BotListener::class);
$context->registerEventListener(SystemMessageSentEvent::class, BotListener::class);
// Chat listeners

29
lib/Chat/ReactionManager.php

@ -8,6 +8,10 @@ declare(strict_types=1);
namespace OCA\Talk\Chat;
use OCA\Talk\Events\BeforeReactionAddedEvent;
use OCA\Talk\Events\BeforeReactionRemovedEvent;
use OCA\Talk\Events\ReactionAddedEvent;
use OCA\Talk\Events\ReactionRemovedEvent;
use OCA\Talk\Exceptions\ReactionAlreadyExistsException;
use OCA\Talk\Exceptions\ReactionNotSupportedException;
use OCA\Talk\Exceptions\ReactionOutOfContextException;
@ -17,6 +21,7 @@ use OCA\Talk\Room;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Comments\IComment;
use OCP\Comments\NotFoundException;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IL10N;
use OCP\PreConditionNotMetException;
@ -31,6 +36,7 @@ class ReactionManager {
private IL10N $l,
private MessageParser $messageParser,
private Notifier $notifier,
protected IEventDispatcher $dispatcher,
protected ITimeFactory $timeFactory,
) {
}
@ -38,18 +44,12 @@ class ReactionManager {
/**
* Add reaction
*
* @param Room $chat
* @param string $actorType
* @param string $actorId
* @param integer $messageId
* @param string $reaction
* @return IComment
* @throws NotFoundException
* @throws ReactionAlreadyExistsException
* @throws ReactionNotSupportedException
* @throws ReactionOutOfContextException
*/
public function addReactionMessage(Room $chat, string $actorType, string $actorId, int $messageId, string $reaction): IComment {
public function addReactionMessage(Room $chat, string $actorType, string $actorId, string $actorDisplayName, int $messageId, string $reaction): IComment {
$parentMessage = $this->getCommentToReact($chat, (string)$messageId);
try {
// Check if the user already reacted with the same reaction
@ -72,8 +72,15 @@ class ReactionManager {
$comment->setParentId($parentMessage->getId());
$comment->setMessage($reaction);
$comment->setVerb(ChatManager::VERB_REACTION);
$event = new BeforeReactionAddedEvent($chat, $parentMessage, $actorType, $actorId, $actorDisplayName, $reaction);
$this->dispatcher->dispatchTyped($event);
$this->commentsManager->save($comment);
$event = new ReactionAddedEvent($chat, $parentMessage, $actorType, $actorId, $actorDisplayName, $reaction);
$this->dispatcher->dispatchTyped($event);
$this->notifier->notifyReacted($chat, $parentMessage, $comment);
return $comment;
}
@ -91,10 +98,13 @@ class ReactionManager {
* @throws ReactionNotSupportedException
* @throws ReactionOutOfContextException
*/
public function deleteReactionMessage(Room $chat, string $actorType, string $actorId, int $messageId, string $reaction): IComment {
public function deleteReactionMessage(Room $chat, string $actorType, string $actorId, string $actorDisplayName, int $messageId, string $reaction): IComment {
// Just to verify that messageId is part of the room and throw error if not.
$parentComment = $this->getCommentToReact($chat, (string)$messageId);
$event = new BeforeReactionRemovedEvent($chat, $parentComment, $actorType, $actorId, $actorDisplayName, $reaction);
$this->dispatcher->dispatchTyped($event);
$comment = $this->commentsManager->getReactionComment(
$messageId,
$actorType,
@ -123,6 +133,9 @@ class ReactionManager {
true
);
$event = new ReactionRemovedEvent($chat, $parentComment, $actorType, $actorId, $actorDisplayName, $reaction);
$this->dispatcher->dispatchTyped($event);
return $comment;
}

2
lib/Controller/BotController.php

@ -217,6 +217,7 @@ class BotController extends AEnvironmentAwareOCSController {
$room,
$actorType,
$actorId,
$bot->getBotServer()->getName(),
$messageId,
$reaction
);
@ -270,6 +271,7 @@ class BotController extends AEnvironmentAwareOCSController {
$room,
$actorType,
$actorId,
$bot->getBotServer()->getName(),
$messageId,
$reaction
);

2
lib/Controller/ReactionController.php

@ -68,6 +68,7 @@ class ReactionController extends AEnvironmentAwareOCSController {
$this->getRoom(),
$this->getParticipant()->getAttendee()->getActorType(),
$this->getParticipant()->getAttendee()->getActorId(),
$this->getParticipant()->getAttendee()->getDisplayName(),
$messageId,
$reaction
);
@ -113,6 +114,7 @@ class ReactionController extends AEnvironmentAwareOCSController {
$this->getRoom(),
$this->getParticipant()->getAttendee()->getActorType(),
$this->getParticipant()->getAttendee()->getActorId(),
$this->getParticipant()->getAttendee()->getDisplayName(),
$messageId,
$reaction
);

45
lib/Events/AReactionEvent.php

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Events;
use OCA\Talk\Room;
use OCP\Comments\IComment;
abstract class AReactionEvent extends ARoomEvent {
public function __construct(
Room $room,
protected IComment $message,
protected string $actorType,
protected string $actorId,
protected string $actorDisplayName,
protected string $reaction,
) {
parent::__construct($room);
}
public function getMessage(): IComment {
return $this->message;
}
public function getActorType(): string {
return $this->actorType;
}
public function getActorId(): string {
return $this->actorId;
}
public function getActorDisplayName(): string {
return $this->actorDisplayName;
}
public function getReaction(): string {
return $this->reaction;
}
}

12
lib/Events/BeforeReactionAddedEvent.php

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Events;
class BeforeReactionAddedEvent extends AReactionEvent {
}

12
lib/Events/BeforeReactionRemovedEvent.php

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Events;
class BeforeReactionRemovedEvent extends AReactionEvent {
}

40
lib/Events/BotInvokeEvent.php

@ -48,6 +48,44 @@ use OCP\EventDispatcher\Event;
* name: non-empty-string,
* },
* }
* @psalm-type ReactionMessageData = array{
* type: 'Like',
* actor: array{
* type: 'Person',
* id: non-empty-string,
* name: non-empty-string,
* talkParticipantType: numeric-string,
* },
* object: array{
* type: 'Note',
* id: numeric-string,
* name: string,
* content: non-empty-string,
* mediaType: 'text/markdown'|'text/plain',
* inReplyTo?: ChatMessageParentData,
* },
* target: array{
* type: 'Collection',
* id: non-empty-string,
* name: non-empty-string,
* },
* content: string,
* }
* @psalm-type UndoReactionMessageData = array{
* type: 'Undo',
* actor: array{
* type: 'Person',
* id: non-empty-string,
* name: non-empty-string,
* talkParticipantType: numeric-string,
* },
* object: ReactionMessageData,
* target: array{
* type: 'Collection',
* id: non-empty-string,
* name: non-empty-string,
* },
* }
* @psalm-type BotManagementData = array{
* type: 'Join'|'Leave',
* actor: array{
@ -61,7 +99,7 @@ use OCP\EventDispatcher\Event;
* name: non-empty-string,
* },
* }
* @psalm-type InvocationData = ChatMessageData|BotManagementData
* @psalm-type InvocationData = ChatMessageData|ReactionMessageData|UndoReactionMessageData|BotManagementData
*/
class BotInvokeEvent extends Event {
/** @var list<string> */

12
lib/Events/ReactionAddedEvent.php

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Events;
class ReactionAddedEvent extends AReactionEvent {
}

12
lib/Events/ReactionRemovedEvent.php

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Events;
class ReactionRemovedEvent extends AReactionEvent {
}

10
lib/Listener/BotListener.php

@ -15,6 +15,8 @@ use OCA\Talk\Events\BotEnabledEvent;
use OCA\Talk\Events\BotInstallEvent;
use OCA\Talk\Events\BotUninstallEvent;
use OCA\Talk\Events\ChatMessageSentEvent;
use OCA\Talk\Events\ReactionAddedEvent;
use OCA\Talk\Events\ReactionRemovedEvent;
use OCA\Talk\Events\SystemMessageSentEvent;
use OCA\Talk\Model\Bot;
use OCA\Talk\Model\BotConversationMapper;
@ -65,6 +67,14 @@ class BotListener implements IEventListener {
$this->botService->afterChatMessageSent($event, $messageParser);
return;
}
if ($event instanceof ReactionAddedEvent) {
$this->botService->afterReactionAdded($event, $messageParser);
return;
}
if ($event instanceof ReactionRemovedEvent) {
$this->botService->afterReactionRemoved($event, $messageParser);
return;
}
if ($event instanceof SystemMessageSentEvent) {
$this->botService->afterSystemMessageSent($event, $messageParser);
}

3
lib/Model/Bot.php

@ -18,11 +18,13 @@ class Bot {
public const FEATURE_WEBHOOK = 1;
public const FEATURE_RESPONSE = 2;
public const FEATURE_EVENT = 4;
public const FEATURE_REACTION = 8;
public const FEATURE_LABEL_NONE = 'none';
public const FEATURE_LABEL_WEBHOOK = 'webhook';
public const FEATURE_LABEL_RESPONSE = 'response';
public const FEATURE_LABEL_EVENT = 'event';
public const FEATURE_LABEL_REACTION = 'reaction';
public const URL_APP_PREFIX = 'nextcloudapp://';
public const FEATURE_MAP = [
@ -30,6 +32,7 @@ class Bot {
self::FEATURE_WEBHOOK => self::FEATURE_LABEL_WEBHOOK,
self::FEATURE_RESPONSE => self::FEATURE_LABEL_RESPONSE,
self::FEATURE_EVENT => self::FEATURE_LABEL_EVENT,
self::FEATURE_REACTION => self::FEATURE_LABEL_REACTION,
];
public function __construct(

66
lib/Service/BotService.php

@ -16,6 +16,8 @@ use OCA\Talk\Events\BotDisabledEvent;
use OCA\Talk\Events\BotEnabledEvent;
use OCA\Talk\Events\BotInvokeEvent;
use OCA\Talk\Events\ChatMessageSentEvent;
use OCA\Talk\Events\ReactionAddedEvent;
use OCA\Talk\Events\ReactionRemovedEvent;
use OCA\Talk\Events\SystemMessageSentEvent;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\Bot;
@ -167,6 +169,69 @@ class BotService {
]);
}
public function afterReactionAdded(ReactionAddedEvent $event, MessageParser $messageParser): void {
$bots = $this->getBotsForToken($event->getRoom()->getToken(), Bot::FEATURE_REACTION);
if (empty($bots)) {
return;
}
$message = $messageParser->createMessage(
$event->getRoom(),
null,
$event->getMessage(),
$this->l10nFactory->get('spreed', 'en', 'en')
);
$messageParser->parseMessage($message);
$messageData = [
'message' => $message->getMessage(),
'parameters' => $message->getMessageParameters(),
];
$botServers = array_map(static fn (Bot $bot): BotServer => $bot->getBotServer(), $bots);
$this->invokeBots($botServers, $event->getRoom(), $event->getMessage(), [
'type' => 'Like',
'actor' => $this->activityPubHelper->generatePersonFromMessageActor($message),
'object' => $this->activityPubHelper->generateNote($event->getMessage(), $messageData, $message->getMessageRaw()),
'target' => $this->activityPubHelper->generateCollectionFromRoom($event->getRoom()),
'content' => $event->getReaction(),
]);
}
public function afterReactionRemoved(ReactionRemovedEvent $event, MessageParser $messageParser): void {
$bots = $this->getBotsForToken($event->getRoom()->getToken(), Bot::FEATURE_REACTION);
if (empty($bots)) {
return;
}
$message = $messageParser->createMessage(
$event->getRoom(),
null,
$event->getMessage(),
$this->l10nFactory->get('spreed', 'en', 'en')
);
$messageParser->parseMessage($message);
$messageData = [
'message' => $message->getMessage(),
'parameters' => $message->getMessageParameters(),
];
$botServers = array_map(static fn (Bot $bot): BotServer => $bot->getBotServer(), $bots);
$this->invokeBots($botServers, $event->getRoom(), $event->getMessage(), [
'type' => 'Undo',
'actor' => $this->activityPubHelper->generatePersonFromMessageActor($message),
'object' => [
'type' => 'Like',
'actor' => $this->activityPubHelper->generatePersonFromMessageActor($message),
'object' => $this->activityPubHelper->generateNote($event->getMessage(), $messageData, $message->getMessageRaw()),
'target' => $this->activityPubHelper->generateCollectionFromRoom($event->getRoom()),
'content' => $event->getReaction(),
],
'target' => $this->activityPubHelper->generateCollectionFromRoom($event->getRoom()),
]);
}
/**
* @param BotServer[] $bots
* @param InvocationData $body
@ -188,6 +253,7 @@ class BotService {
$room,
Attendee::ACTOR_BOTS,
Attendee::ACTOR_BOT_PREFIX . $bot->getUrlHash(),
$bot->getName(),
(int)$comment->getId(),
$reaction
);

2
lib/Service/SampleConversationsService.php

@ -169,7 +169,7 @@ In the conversation menu, you can access various settings to manage your convers
$previous = $this->chatManager->postSampleMessage($room, $message, $replyTo);
foreach ($reactions as $reaction) {
$this->reactionManager->addReactionMessage($room, Attendee::ACTOR_GUESTS, Attendee::ACTOR_ID_SAMPLE, (int)$previous->getId(), $reaction);
$this->reactionManager->addReactionMessage($room, Attendee::ACTOR_GUESTS, Attendee::ACTOR_ID_SAMPLE, '', (int)$previous->getId(), $reaction);
}
}
}

Loading…
Cancel
Save