summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoas Schilling <coding@schilljs.com>2023-08-07 14:39:28 +0200
committerJoas Schilling <coding@schilljs.com>2023-08-08 10:46:57 +0200
commit13c5ced6ac827ae7e51415be01274eadb4a945b0 (patch)
tree82e1b610b9254d4b1205432696e956a49d545aef
parent49e69db76dca56bbd93ae841a17d581a25703e23 (diff)
feat(bot): Allow reactions by bots
Signed-off-by: Joas Schilling <coding@schilljs.com>
-rw-r--r--appinfo/routes/routesBotController.php10
-rw-r--r--lib/Chat/ReactionManager.php30
-rw-r--r--lib/Controller/BotController.php139
-rw-r--r--lib/Controller/ReactionController.php6
-rw-r--r--tests/integration/features/bootstrap/FeatureContext.php9
-rw-r--r--tests/integration/features/chat/bots.feature8
6 files changed, 161 insertions, 41 deletions
diff --git a/appinfo/routes/routesBotController.php b/appinfo/routes/routesBotController.php
index f61d6aa1f..90910d2a3 100644
--- a/appinfo/routes/routesBotController.php
+++ b/appinfo/routes/routesBotController.php
@@ -28,6 +28,12 @@ $requirements = [
'token' => '[a-z0-9]{4,30}',
];
+$requirementsWithMessageId = [
+ 'apiVersion' => 'v1',
+ 'token' => '[a-z0-9]{4,30}',
+ 'messageId' => '[0-9]+',
+];
+
$requirementsWithBotId = [
'apiVersion' => 'v1',
'token' => '[a-z0-9]{4,30}',
@@ -38,6 +44,10 @@ return [
'ocs' => [
/** @see \OCA\Talk\Controller\BotController::sendMessage() */
['name' => 'Bot#sendMessage', 'url' => '/api/{apiVersion}/bot/{token}/message', 'verb' => 'POST', 'requirements' => $requirements],
+ /** @see \OCA\Talk\Controller\BotController::react() */
+ ['name' => 'Bot#react', 'url' => '/api/{apiVersion}/bot/{token}/reaction/{messageId}', 'verb' => 'POST', 'requirements' => $requirementsWithMessageId],
+ /** @see \OCA\Talk\Controller\BotController::deleteReaction() */
+ ['name' => 'Bot#deleteReaction', 'url' => '/api/{apiVersion}/bot/{token}/reaction/{messageId}', 'verb' => 'DELETE', 'requirements' => $requirementsWithMessageId],
/** @see \OCA\Talk\Controller\BotController::listBots() */
['name' => 'Bot#listBots', 'url' => '/api/{apiVersion}/bot/{token}', 'verb' => 'GET', 'requirements' => $requirements],
/** @see \OCA\Talk\Controller\BotController::enableBot() */
diff --git a/lib/Chat/ReactionManager.php b/lib/Chat/ReactionManager.php
index 52de4d53a..d00fb39f8 100644
--- a/lib/Chat/ReactionManager.php
+++ b/lib/Chat/ReactionManager.php
@@ -63,7 +63,8 @@ class ReactionManager {
* Add reaction
*
* @param Room $chat
- * @param Participant $participant
+ * @param string $actorType
+ * @param string $actorId
* @param integer $messageId
* @param string $reaction
* @return IComment
@@ -72,14 +73,14 @@ class ReactionManager {
* @throws ReactionNotSupportedException
* @throws ReactionOutOfContextException
*/
- public function addReactionMessage(Room $chat, Participant $participant, int $messageId, string $reaction): IComment {
+ public function addReactionMessage(Room $chat, string $actorType, string $actorId, int $messageId, string $reaction): IComment {
$parentMessage = $this->getCommentToReact($chat, (string) $messageId);
try {
// Check if the user already reacted with the same reaction
$this->commentsManager->getReactionComment(
(int) $parentMessage->getId(),
- $participant->getAttendee()->getActorType(),
- $participant->getAttendee()->getActorId(),
+ $actorType,
+ $actorId,
$reaction
);
throw new ReactionAlreadyExistsException();
@@ -87,8 +88,8 @@ class ReactionManager {
}
$comment = $this->commentsManager->create(
- $participant->getAttendee()->getActorType(),
- $participant->getAttendee()->getActorId(),
+ $actorType,
+ $actorId,
'chat',
(string) $chat->getId()
);
@@ -105,7 +106,8 @@ class ReactionManager {
* Delete reaction
*
* @param Room $chat
- * @param Participant $participant
+ * @param string $actorType
+ * @param string $actorId
* @param integer $messageId
* @param string $reaction
* @return IComment
@@ -113,20 +115,20 @@ class ReactionManager {
* @throws ReactionNotSupportedException
* @throws ReactionOutOfContextException
*/
- public function deleteReactionMessage(Room $chat, Participant $participant, int $messageId, string $reaction): IComment {
+ public function deleteReactionMessage(Room $chat, string $actorType, string $actorId, int $messageId, string $reaction): IComment {
// Just to verify that messageId is part of the room and throw error if not.
$this->getCommentToReact($chat, (string) $messageId);
$comment = $this->commentsManager->getReactionComment(
$messageId,
- $participant->getAttendee()->getActorType(),
- $participant->getAttendee()->getActorId(),
+ $actorType,
+ $actorId,
$reaction
);
$comment->setMessage(
json_encode([
- 'deleted_by_type' => $participant->getAttendee()->getActorType(),
- 'deleted_by_id' => $participant->getAttendee()->getActorId(),
+ 'deleted_by_type' => $actorType,
+ 'deleted_by_id' => $actorId,
'deleted_on' => $this->timeFactory->getDateTime()->getTimestamp(),
])
);
@@ -135,8 +137,8 @@ class ReactionManager {
$this->chatManager->addSystemMessage(
$chat,
- $participant->getAttendee()->getActorType(),
- $participant->getAttendee()->getActorId(),
+ $actorType,
+ $actorId,
json_encode(['message' => 'reaction_revoked', 'parameters' => ['message' => (int) $comment->getId()]]),
$this->timeFactory->getDateTime(),
false,
diff --git a/lib/Controller/BotController.php b/lib/Controller/BotController.php
index 6492bd95e..3e763dc13 100644
--- a/lib/Controller/BotController.php
+++ b/lib/Controller/BotController.php
@@ -27,6 +27,10 @@ declare(strict_types=1);
namespace OCA\Talk\Controller;
use OCA\Talk\Chat\ChatManager;
+use OCA\Talk\Chat\ReactionManager;
+use OCA\Talk\Exceptions\ReactionAlreadyExistsException;
+use OCA\Talk\Exceptions\ReactionNotSupportedException;
+use OCA\Talk\Exceptions\ReactionOutOfContextException;
use OCA\Talk\Exceptions\UnauthorizedException;
use OCA\Talk\Manager;
use OCA\Talk\Middleware\Attribute\RequireLoggedInModeratorParticipant;
@@ -63,42 +67,31 @@ class BotController extends AEnvironmentAwareController {
protected BotServerMapper $botServerMapper,
protected BotService $botService,
protected Manager $manager,
+ protected ReactionManager $reactionManager,
protected LoggerInterface $logger,
) {
parent::__construct($appName, $request);
}
/**
- * Sends a new chat message to the given room.
- *
- * The author and timestamp are automatically set to the current user/guest
- * and time.
- *
- * @param string $token conversation token
- * @param string $message the message to send
- * @param string $referenceId for the message to be able to later identify it again
- * @param int $replyTo Parent id which this message is a reply to
- * @param bool $silent If sent silent the chat message will not create any notifications
- * @return DataResponse the status code is "201 Created" if successful, and
- * "404 Not found" if the room or session for a guest user was not
- * found".
+ * @param string $token
+ * @param string $message
+ * @return Bot
+ * @throws \InvalidArgumentException When the request could not be linked with a bot
*/
- #[BruteForceProtection(action: 'bot')]
- #[PublicPage]
- public function sendMessage(string $token, string $message, string $referenceId = '', int $replyTo = 0, bool $silent = false): DataResponse {
+ protected function getBotFromHeaders(string $token, string $message): Bot {
$random = $this->request->getHeader('X-Nextcloud-Talk-Bot-Random');
if (empty($random) || strlen($random) < 32) {
$this->logger->error('Invalid Random received from bot response');
- return new DataResponse([], Http::STATUS_BAD_REQUEST);
+ throw new \InvalidArgumentException('Invalid Random received from bot response', Http::STATUS_BAD_REQUEST);
}
$checksum = $this->request->getHeader('X-Nextcloud-Talk-Bot-Signature');
if (empty($checksum)) {
$this->logger->error('Invalid Signature received from bot response');
- return new DataResponse([], Http::STATUS_BAD_REQUEST);
+ throw new \InvalidArgumentException('Invalid Signature received from bot response', Http::STATUS_BAD_REQUEST);
}
$bots = $this->botService->getBotsForToken($token);
- $bot = null;
foreach ($bots as $botAttempt) {
try {
$this->checksumVerificationService->validateRequest(
@@ -107,16 +100,40 @@ class BotController extends AEnvironmentAwareController {
$botAttempt->getBotServer()->getSecret(),
$message
);
- $bot = $botAttempt;
- break;
+ return $botAttempt;
} catch (UnauthorizedException) {
}
}
- if (!$bot instanceof Bot) {
- $this->logger->debug('No valid Bot entry found');
- $response = new DataResponse([], Http::STATUS_UNAUTHORIZED);
- $response->throttle(['action' => 'bot']);
+ $this->logger->debug('No valid Bot entry found');
+ throw new \InvalidArgumentException('No valid Bot entry found', Http::STATUS_UNAUTHORIZED);
+ }
+
+ /**
+ * Sends a new chat message to the given room.
+ *
+ * The author and timestamp are automatically set to the current user/guest
+ * and time.
+ *
+ * @param string $token conversation token
+ * @param string $message the message to send
+ * @param string $referenceId for the message to be able to later identify it again
+ * @param int $replyTo Parent id which this message is a reply to
+ * @param bool $silent If sent silent the chat message will not create any notifications
+ * @return DataResponse the status code is "201 Created" if successful, and
+ * "404 Not found" if the room or session for a guest user was not
+ * found".
+ */
+ #[BruteForceProtection(action: 'bot')]
+ #[PublicPage]
+ public function sendMessage(string $token, string $message, string $referenceId = '', int $replyTo = 0, bool $silent = false): DataResponse {
+ try {
+ $bot = $this->getBotFromHeaders($token, $message);
+ } catch (\InvalidArgumentException $e) {
+ $response = new DataResponse([], $e->getCode());
+ if ($e->getCode() === Http::STATUS_UNAUTHORIZED) {
+ $response->throttle(['action' => 'bot']);
+ }
return $response;
}
@@ -149,6 +166,78 @@ class BotController extends AEnvironmentAwareController {
return new DataResponse([], Http::STATUS_CREATED);
}
+ #[BruteForceProtection(action: 'bot')]
+ #[PublicPage]
+ public function react(string $token, int $messageId, string $reaction): DataResponse {
+ try {
+ $bot = $this->getBotFromHeaders($token, $reaction);
+ } catch (\InvalidArgumentException $e) {
+ $response = new DataResponse([], $e->getCode());
+ if ($e->getCode() === Http::STATUS_UNAUTHORIZED) {
+ $response->throttle(['action' => 'bot']);
+ }
+ return $response;
+ }
+
+ $room = $this->manager->getRoomByToken($token);
+
+ $actorType = Attendee::ACTOR_BOTS;
+ $actorId = Attendee::ACTOR_BOT_PREFIX . $bot->getBotServer()->getUrlHash();
+
+ try {
+ $this->reactionManager->addReactionMessage(
+ $room,
+ $actorType,
+ $actorId,
+ $messageId,
+ $reaction
+ );
+ } catch (NotFoundException) {
+ return new DataResponse([], Http::STATUS_NOT_FOUND);
+ } catch (ReactionAlreadyExistsException) {
+ return new DataResponse([], Http::STATUS_OK);
+ } catch (ReactionNotSupportedException | ReactionOutOfContextException | \Exception) {
+ return new DataResponse([], Http::STATUS_BAD_REQUEST);
+ }
+
+ return new DataResponse([], Http::STATUS_CREATED);
+ }
+
+ #[BruteForceProtection(action: 'bot')]
+ #[PublicPage]
+ public function deleteReaction(string $token, int $messageId, string $reaction): DataResponse {
+ try {
+ $bot = $this->getBotFromHeaders($token, $reaction);
+ } catch (\InvalidArgumentException $e) {
+ $response = new DataResponse([], $e->getCode());
+ if ($e->getCode() === Http::STATUS_UNAUTHORIZED) {
+ $response->throttle(['action' => 'bot']);
+ }
+ return $response;
+ }
+
+ $room = $this->manager->getRoomByToken($token);
+
+ $actorType = Attendee::ACTOR_BOTS;
+ $actorId = Attendee::ACTOR_BOT_PREFIX . $bot->getBotServer()->getUrlHash();
+
+ try {
+ $this->reactionManager->deleteReactionMessage(
+ $room,
+ $actorType,
+ $actorId,
+ $messageId,
+ $reaction
+ );
+ } catch (ReactionNotSupportedException | ReactionOutOfContextException | NotFoundException) {
+ return new DataResponse([], Http::STATUS_NOT_FOUND);
+ } catch (\Exception) {
+ return new DataResponse([], Http::STATUS_BAD_REQUEST);
+ }
+
+ return new DataResponse([], Http::STATUS_OK);
+ }
+
#[NoAdminRequired]
#[RequireLoggedInModeratorParticipant]
public function listBots(): DataResponse {
diff --git a/lib/Controller/ReactionController.php b/lib/Controller/ReactionController.php
index add91bec7..5d9bbe531 100644
--- a/lib/Controller/ReactionController.php
+++ b/lib/Controller/ReactionController.php
@@ -60,7 +60,8 @@ class ReactionController extends AEnvironmentAwareController {
try {
$this->reactionManager->addReactionMessage(
$this->getRoom(),
- $this->getParticipant(),
+ $this->getParticipant()->getAttendee()->getActorType(),
+ $this->getParticipant()->getAttendee()->getActorId(),
$messageId,
$reaction
);
@@ -85,7 +86,8 @@ class ReactionController extends AEnvironmentAwareController {
try {
$this->reactionManager->deleteReactionMessage(
$this->getRoom(),
- $this->getParticipant(),
+ $this->getParticipant()->getAttendee()->getActorType(),
+ $this->getParticipant()->getAttendee()->getActorId(),
$messageId,
$reaction
);
diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php
index 28c7796dc..cf4037692 100644
--- a/tests/integration/features/bootstrap/FeatureContext.php
+++ b/tests/integration/features/bootstrap/FeatureContext.php
@@ -3191,6 +3191,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
* @Given /^user "([^"]*)" retrieve reactions "([^"]*)" of message "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*/
public function userRetrieveReactionsOfMessageInRoomWith(string $user, string $reaction, string $message, string $identifier, int $statusCode, string $apiVersion = 'v1', ?TableNode $formData = null): void {
+ $message = str_replace('\n', "\n", $message);
$token = self::$identifierToToken[$identifier];
$messageId = self::$textToMessageId[$message];
$this->setCurrentUser($user);
@@ -3208,6 +3209,14 @@ class FeatureContext implements Context, SnippetAcceptingContext {
foreach ($formData->getHash() as $row) {
$reaction = $row['reaction'];
unset($row['reaction']);
+
+ if ($row['actorType'] === 'bots') {
+ $result = preg_match('/BOT\(([^)]+)\)/', $row['actorId'], $matches);
+ if ($result && isset(self::$botNameToHash[$matches[1]])) {
+ $row['actorId'] = 'bot-' . self::$botNameToHash[$matches[1]];
+ }
+ }
+
$expected[$reaction][] = $row;
}
diff --git a/tests/integration/features/chat/bots.feature b/tests/integration/features/chat/bots.feature
index 6a1024c00..89ff60b56 100644
--- a/tests/integration/features/chat/bots.feature
+++ b/tests/integration/features/chat/bots.feature
@@ -60,6 +60,14 @@ Feature: chat/bots
| room | users | participant1 | participant1-displayname | - Task 2\n-Task 3 | [] |
| room | users | participant1 | participant1-displayname | * Task 1 | [] |
| room | users | participant1 | participant1-displayname | - Before call | [] |
+ Then user "participant1" retrieve reactions "👍" of message "- Before call" in room "room" with 200
+ | actorType | actorId | actorDisplayName | reaction |
+ Then user "participant1" retrieve reactions "👍" of message "* Task 1" in room "room" with 200
+ | actorType | actorId | actorDisplayName | reaction |
+ | bots | BOT(Call summary) | Call summary (Bot) | 👍 |
+ Then user "participant1" retrieve reactions "👍" of message "- Task 2\n-Task 3" in room "room" with 200
+ | actorType | actorId | actorDisplayName | reaction |
+ | bots | BOT(Call summary) | Call summary (Bot) | 👍 |
# Different states bot
# Already enabled