-
Notifications
You must be signed in to change notification settings - Fork 508
feat(bot): Allow reactions by bots #10138
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
Signed-off-by: Joas Schilling <[email protected]>
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,10 @@ | |
| 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 @@ public function __construct( | |
| 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 @@ public function sendMessage(string $token, string $message, string $referenceId | |
| $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; | ||
|
Comment on lines
+133
to
137
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am looking for a way to avoid repetition here and keep the same logic in one place. Maybe a wrapper like this: private function handleInvalidArgumentException(\InvalidArgumentException $e) {
$response = new DataResponse([], $e->getCode());
if ($e->getCode() === Http::STATUS_UNAUTHORIZED) {
$response->throttle(['action' => 'bot']);
}
return $response;
}Block from l. 130 to l. 138 is used several times. I wonder we could extract it in a method, but then we will have something like: $bot = $this->loadBot($token, $message);
if ($bot instanceof DataResponse) {
return $bot;
}And I don't like it ^^
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, I don't like mixed return types, that's why I went for the throwing instead. |
||
| } | ||
|
|
||
|
|
@@ -149,6 +166,78 @@ public function sendMessage(string $token, string $message, string $referenceId | |
| 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) { | ||
nickvergessen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return new DataResponse([], Http::STATUS_BAD_REQUEST); | ||
| } | ||
|
|
||
| return new DataResponse([], Http::STATUS_OK); | ||
| } | ||
|
|
||
| #[NoAdminRequired] | ||
| #[RequireLoggedInModeratorParticipant] | ||
| public function listBots(): DataResponse { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.