diff options
author | Joas Schilling <coding@schilljs.com> | 2023-07-23 14:12:33 +0200 |
---|---|---|
committer | Joas Schilling <coding@schilljs.com> | 2023-08-07 11:35:24 +0200 |
commit | ae1fe40ac0378b1e98a0609699261ac36b583bbe (patch) | |
tree | e7f66330d99511134be1c04ff0a041218ff02df5 | |
parent | 495e09b4e588b26274ca9ec687389f6addec1fdf (diff) |
feat(bots): Add commands and events to install bots
Signed-off-by: Joas Schilling <coding@schilljs.com>
-rw-r--r-- | appinfo/info.xml | 6 | ||||
-rw-r--r-- | lib/AppInfo/Application.php | 2 | ||||
-rw-r--r-- | lib/Command/Bot/Install.php | 103 | ||||
-rw-r--r-- | lib/Command/Bot/ListBots.php | 89 | ||||
-rw-r--r-- | lib/Command/Bot/Remove.php | 68 | ||||
-rw-r--r-- | lib/Command/Bot/Setup.php | 103 | ||||
-rw-r--r-- | lib/Command/Bot/State.php | 83 | ||||
-rw-r--r-- | lib/Command/Bot/Uninstall.php | 65 | ||||
-rw-r--r-- | lib/Events/BotInstallEvent.php | 55 | ||||
-rw-r--r-- | lib/Listener/BotListener.php | 25 | ||||
-rw-r--r-- | lib/Model/Bot.php | 6 | ||||
-rw-r--r-- | lib/Model/BotConversationMapper.php | 20 | ||||
-rw-r--r-- | lib/Model/BotServer.php | 2 | ||||
-rw-r--r-- | lib/Model/BotServerMapper.php | 35 | ||||
-rw-r--r-- | lib/Service/BotService.php | 6 |
15 files changed, 665 insertions, 3 deletions
diff --git a/appinfo/info.xml b/appinfo/info.xml index d99540c6c..bea4033df 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -81,6 +81,12 @@ And in the works for the [coming versions](https://github.com/nextcloud/spreed/m </repair-steps> <commands> + <command>OCA\Talk\Command\Bot\Install</command> + <command>OCA\Talk\Command\Bot\ListBots</command> + <command>OCA\Talk\Command\Bot\Remove</command> + <command>OCA\Talk\Command\Bot\State</command> + <command>OCA\Talk\Command\Bot\Setup</command> + <command>OCA\Talk\Command\Bot\Uninstall</command> <command>OCA\Talk\Command\Command\Add</command> <command>OCA\Talk\Command\Command\AddSamples</command> <command>OCA\Talk\Command\Command\Delete</command> diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 43c46441e..d462604d0 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -45,6 +45,7 @@ use OCA\Talk\Dashboard\TalkWidget; use OCA\Talk\Deck\DeckPluginLoader; use OCA\Talk\Events\AttendeesAddedEvent; use OCA\Talk\Events\AttendeesRemovedEvent; +use OCA\Talk\Events\BotInstallEvent; use OCA\Talk\Events\RoomEvent; use OCA\Talk\Events\SendCallNotificationEvent; use OCA\Talk\Federation\CloudFederationProviderTalk; @@ -125,6 +126,7 @@ class Application extends App implements IBootstrap { $context->registerEventListener(AddContentSecurityPolicyEvent::class, CSPListener::class); $context->registerEventListener(AddFeaturePolicyEvent::class, FeaturePolicyListener::class); + $context->registerEventListener(BotInstallEvent::class, BotListener::class); $context->registerEventListener(GroupDeletedEvent::class, GroupDeletedListener::class); $context->registerEventListener(GroupChangedEvent::class, DisplayNameListener::class); $context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class); diff --git a/lib/Command/Bot/Install.php b/lib/Command/Bot/Install.php new file mode 100644 index 000000000..1f0ba5160 --- /dev/null +++ b/lib/Command/Bot/Install.php @@ -0,0 +1,103 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com> + * + * @author Joas Schilling <coding@schilljs.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Talk\Command\Bot; + +use OC\Core\Command\Base; +use OCA\Talk\Model\Bot; +use OCA\Talk\Model\BotServer; +use OCA\Talk\Model\BotServerMapper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class Install extends Base { + public function __construct( + private BotServerMapper $botServerMapper, + ) { + parent::__construct(); + } + + protected function configure(): void { + parent::configure(); + $this + ->setName('talk:bot:install') + ->setDescription('Install a new bot on the server') + ->addArgument( + 'name', + InputArgument::REQUIRED, + 'The name under which the messages will be posted' + ) + ->addArgument( + 'secret', + InputArgument::REQUIRED, + 'Secret used to validate API calls' + ) + ->addArgument( + 'url', + InputArgument::REQUIRED, + 'Webhook endpoint to post messages to' + ) + ->addArgument( + 'description', + InputArgument::OPTIONAL, + 'Optional description shown in the admin settings' + ) + ->addOption( + 'no-setup', + null, + InputOption::VALUE_NONE, + 'Prevent moderators from setting up the bot in a conversation' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $name = $input->getArgument('name'); + $secret = $input->getArgument('secret'); + $url = $input->getArgument('url'); + $description = $input->getArgument('description'); + $noSetup = $input->getOption('no-setup'); + + $bot = new BotServer(); + $bot->setName($name); + $bot->setSecret($secret); + $bot->setUrl($url); + $bot->setUrlHash(sha1($url)); + $bot->setDescription($description); + $bot->setState($noSetup ? Bot::STATE_NO_SETUP : Bot::STATE_ENABLED); + try { + $this->botServerMapper->insert($bot); + } catch (\Exception $e) { + $output->writeln('<error>' . get_class($e) . ': ' . $e->getMessage() . '</error>'); + return 1; + } + + + $output->writeln('<info>Bot installed</info>'); + return 0; + } +} diff --git a/lib/Command/Bot/ListBots.php b/lib/Command/Bot/ListBots.php new file mode 100644 index 000000000..4f5251c93 --- /dev/null +++ b/lib/Command/Bot/ListBots.php @@ -0,0 +1,89 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com> + * + * @author Joas Schilling <coding@schilljs.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Talk\Command\Bot; + +use OC\Core\Command\Base; +use OCA\Talk\Model\BotConversation; +use OCA\Talk\Model\BotConversationMapper; +use OCA\Talk\Model\BotServerMapper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class ListBots extends Base { + public function __construct( + private BotConversationMapper $botConversationMapper, + private BotServerMapper $botServerMapper, + ) { + parent::__construct(); + } + + protected function configure(): void { + parent::configure(); + $this + ->setName('talk:bot:list') + ->setDescription('List all installed bots of the server or a conversation') + ->addArgument( + 'token', + InputArgument::OPTIONAL, + 'Conversation token to limit the bot list for' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $bots = $this->botServerMapper->getAllBots(); + $token = $input->getArgument('token'); + + if ($token) { + $botIds = array_map(static function (BotConversation $bot): int { + return $bot->getBotId(); + }, $this->botConversationMapper->findForToken($token)); + } + + $data = []; + foreach ($bots as $bot) { + if ($token && !in_array($bot->getId(), $botIds, true)) { + continue; + } + + $botData = $bot->jsonSerialize(); + + if (!$output->isVerbose()) { + unset($botData['url']); + unset($botData['url_hash']); + unset($botData['secret']); + unset($botData['last_error_date']); + unset($botData['last_error_message']); + } + + $data[] = $botData; + } + + $this->writeTableInOutputFormat($input, $output, $data); + return 0; + } +} diff --git a/lib/Command/Bot/Remove.php b/lib/Command/Bot/Remove.php new file mode 100644 index 000000000..fa6378ea9 --- /dev/null +++ b/lib/Command/Bot/Remove.php @@ -0,0 +1,68 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com> + * + * @author Joas Schilling <coding@schilljs.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Talk\Command\Bot; + +use OC\Core\Command\Base; +use OCA\Talk\Model\BotConversationMapper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class Remove extends Base { + public function __construct( + private BotConversationMapper $botConversationMapper, + ) { + parent::__construct(); + } + + protected function configure(): void { + parent::configure(); + $this + ->setName('talk:bot:remove') + ->setDescription('Remove a bot from a conversation') + ->addArgument( + 'bot-id', + InputArgument::REQUIRED, + 'The ID of the bot to remove in a conversation' + ) + ->addArgument( + 'token', + InputArgument::IS_ARRAY, + 'Conversation tokens to remove bot up for' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $botId = (int) $input->getArgument('bot-id'); + $tokens = $input->getArgument('token'); + + $this->botConversationMapper->deleteByBotIdAndTokens($botId, $tokens); + + $output->writeln('<info>Remove bot from given conversations</info>'); + return 0; + } +} diff --git a/lib/Command/Bot/Setup.php b/lib/Command/Bot/Setup.php new file mode 100644 index 000000000..00562e965 --- /dev/null +++ b/lib/Command/Bot/Setup.php @@ -0,0 +1,103 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com> + * + * @author Joas Schilling <coding@schilljs.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Talk\Command\Bot; + +use OC\Core\Command\Base; +use OCA\Talk\Exceptions\RoomNotFoundException; +use OCA\Talk\Manager; +use OCA\Talk\Model\Bot; +use OCA\Talk\Model\BotConversation; +use OCA\Talk\Model\BotConversationMapper; +use OCA\Talk\Model\BotServerMapper; +use OCP\AppFramework\Db\DoesNotExistException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class Setup extends Base { + public function __construct( + private Manager $roomManager, + private BotServerMapper $botServerMapper, + private BotConversationMapper $botConversationMapper, + ) { + parent::__construct(); + } + + protected function configure(): void { + parent::configure(); + $this + ->setName('talk:bot:setup') + ->setDescription('Add a bot to a conversation') + ->addArgument( + 'bot-id', + InputArgument::REQUIRED, + 'The ID of the bot to set up in a conversation' + ) + ->addArgument( + 'token', + InputArgument::IS_ARRAY, + 'Conversation tokens to set the bot up for' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $botId = (int) $input->getArgument('bot-id'); + $tokens = $input->getArgument('token'); + + try { + $this->botServerMapper->findById($botId); + } catch (DoesNotExistException) { + $output->writeln('<error>Bot could not be found by id: ' . $botId . '</error>'); + return 1; + } + + $returnCode = 0; + foreach ($tokens as $token) { + try { + $this->roomManager->getRoomByToken($token); + } catch (RoomNotFoundException) { + $output->writeln('<error>Conversation could not be found by token: ' . $token . '</error>'); + return 1; + } + + $bot = new BotConversation(); + $bot->setBotId($botId); + $bot->setToken($token); + $bot->setState(Bot::STATE_ENABLED); + + try { + $this->botConversationMapper->insert($bot); + $output->writeln('<info>Successfully set up for conversation ' . $token . '</info>'); + } catch (\Exception $e) { + $output->writeln('<error>' . get_class($e) . ': ' . $e->getMessage() . '</error>'); + $returnCode = 3; + } + } + + return $returnCode; + } +} diff --git a/lib/Command/Bot/State.php b/lib/Command/Bot/State.php new file mode 100644 index 000000000..9c059a3ca --- /dev/null +++ b/lib/Command/Bot/State.php @@ -0,0 +1,83 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com> + * + * @author Joas Schilling <coding@schilljs.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Talk\Command\Bot; + +use OC\Core\Command\Base; +use OCA\Talk\Model\Bot; +use OCA\Talk\Model\BotServerMapper; +use OCP\AppFramework\Db\DoesNotExistException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class State extends Base { + public function __construct( + private BotServerMapper $botServerMapper, + ) { + parent::__construct(); + } + + protected function configure(): void { + parent::configure(); + $this + ->setName('talk:bot:state') + ->setDescription('List all installed bots of the server or a conversation') + ->addArgument( + 'bot-id', + InputArgument::REQUIRED, + 'Bot ID to change the state for' + ) + ->addArgument( + 'state', + InputArgument::REQUIRED, + 'New state for the bot (0 = disabled, 1 = enabled, 2 = no setup via GUI)' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $botId = (int) $input->getArgument('bot-id'); + $state = (int) $input->getArgument('state'); + + if (!in_array($state, [Bot::STATE_DISABLED, Bot::STATE_ENABLED, Bot::STATE_NO_SETUP], true)) { + $output->writeln('<error>Provided state is invalid</error>'); + return 1; + } + + try { + $bot = $this->botServerMapper->findById($botId); + } catch (DoesNotExistException) { + $output->writeln('<error>Bot could not be found by id: ' . $botId . '</error>'); + return 1; + } + + $bot->setState($state); + $this->botServerMapper->update($bot); |