From 3bdbed7edda8db51dc3dbb5d8a73c66ade1fe709 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 8 Apr 2026 10:19:38 +0200 Subject: [PATCH] fix(bots): Hide event bots of disabled apps Signed-off-by: Joas Schilling --- lib/Command/Bot/ListBots.php | 21 ++++++++++++++ lib/Command/Bot/Setup.php | 7 +++++ lib/Controller/BotController.php | 21 +++++++++++++- lib/Model/Bot.php | 1 + lib/Service/BotService.php | 12 ++++++++ src/components/AdminSettings/BotsSettings.vue | 2 ++ .../ConversationSettings/BotsSettings.vue | 28 ++++++++++++++++++- src/constants.ts | 1 + 8 files changed, 91 insertions(+), 2 deletions(-) diff --git a/lib/Command/Bot/ListBots.php b/lib/Command/Bot/ListBots.php index 8de65fd947a..9a607f30a74 100644 --- a/lib/Command/Bot/ListBots.php +++ b/lib/Command/Bot/ListBots.php @@ -13,6 +13,9 @@ use OCA\Talk\Model\BotConversation; use OCA\Talk\Model\BotConversationMapper; use OCA\Talk\Model\BotServerMapper; +use OCA\Talk\Service\BotService; +use OCP\App\IAppManager; +use OCP\AppFramework\Utility\ITimeFactory; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -21,6 +24,9 @@ class ListBots extends Base { public function __construct( private BotConversationMapper $botConversationMapper, private BotServerMapper $botServerMapper, + private BotService $botService, + private IAppManager $appManager, + private ITimeFactory $timeFactory, ) { parent::__construct(); } @@ -58,6 +64,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int $botData = $bot->jsonSerialize(); $botData['features'] = Bot::featureFlagsToLabels($botData['features']); + if (!$this->botService->isAppForBotEnabled($bot)) { + $botData['state'] = Bot::STATE_UNAVAILABLE; + if ($input->getOption('output') === 'plain') { + $botData['error_count'] = '' . 1 . ''; + } else { + $botData['error_count'] = 1; + } + $botData['last_error_date'] = $this->timeFactory->getTime(); + if ($input->getOption('output') === 'plain') { + $botData['last_error_message'] = 'App disabled'; + } else { + $botData['last_error_message'] = 'App disabled'; + } + } + if (!$output->isVerbose()) { unset($botData['url']); unset($botData['url_hash']); diff --git a/lib/Command/Bot/Setup.php b/lib/Command/Bot/Setup.php index b27401aa5c2..2aaaa20487a 100644 --- a/lib/Command/Bot/Setup.php +++ b/lib/Command/Bot/Setup.php @@ -16,6 +16,7 @@ use OCA\Talk\Model\BotConversation; use OCA\Talk\Model\BotConversationMapper; use OCA\Talk\Model\BotServerMapper; +use OCA\Talk\Service\BotService; use OCP\AppFramework\Db\DoesNotExistException; use OCP\DB\Exception; use OCP\EventDispatcher\IEventDispatcher; @@ -28,6 +29,7 @@ public function __construct( private Manager $roomManager, private BotServerMapper $botServerMapper, private BotConversationMapper $botConversationMapper, + private BotService $botService, private IEventDispatcher $dispatcher, ) { parent::__construct(); @@ -63,6 +65,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 1; } + if (!$this->botService->isAppForBotEnabled($botServer)) { + $output->writeln('Bot app is disabled: ' . $botServer->getUrl() . ''); + return 1; + } + $returnCode = 0; foreach ($tokens as $token) { try { diff --git a/lib/Controller/BotController.php b/lib/Controller/BotController.php index b32828cd5e1..47d650511e3 100644 --- a/lib/Controller/BotController.php +++ b/lib/Controller/BotController.php @@ -45,6 +45,7 @@ use OCP\Comments\MessageTooLongException; use OCP\Comments\NotFoundException; use OCP\EventDispatcher\IEventDispatcher; +use OCP\IL10N; use OCP\IRequest; use Psr\Log\LoggerInterface; @@ -66,6 +67,7 @@ public function __construct( protected Manager $manager, protected ReactionManager $reactionManager, protected ThreadService $threadService, + protected IL10N $l, protected LoggerInterface $logger, private IEventDispatcher $dispatcher, ) { @@ -346,6 +348,14 @@ public function adminListBots(): DataResponse { foreach ($bots as $bot) { $botData = $bot->jsonSerialize(); unset($botData['secret']); + + if (!$this->botService->isAppForBotEnabled($bot)) { + $botData['state'] = Bot::STATE_UNAVAILABLE; + $botData['error_count'] = 1; + $botData['last_error_date'] = $this->timeFactory->getTime(); + $botData['last_error_message'] = $this->l->t('App disabled'); + } + $data[] = $botData; } @@ -374,6 +384,15 @@ public function listBots(): DataResponse { $bots = $this->botServerMapper->getAllBots(); foreach ($bots as $bot) { $botData = $this->formatBot($bot, in_array($bot->getId(), $alreadyInstalled, true)); + + if (!$this->botService->isAppForBotEnabled($bot)) { + if ($botData['state'] !== Bot::STATE_DISABLED) { + $botData['state'] = Bot::STATE_UNAVAILABLE; + } else { + continue; + } + } + if ($botData !== null) { $data[] = $botData; } @@ -414,7 +433,7 @@ public function enableBot(int $botId): DataResponse { ], Http::STATUS_BAD_REQUEST); } - if ($bot->getState() !== Bot::STATE_ENABLED) { + if ($bot->getState() !== Bot::STATE_ENABLED || !$this->botService->isAppForBotEnabled($bot)) { return new DataResponse([ 'error' => 'bot', ], Http::STATUS_BAD_REQUEST); diff --git a/lib/Model/Bot.php b/lib/Model/Bot.php index ef390bde1d2..f1a0f53af3a 100644 --- a/lib/Model/Bot.php +++ b/lib/Model/Bot.php @@ -13,6 +13,7 @@ class Bot { public const STATE_DISABLED = 0; public const STATE_ENABLED = 1; public const STATE_NO_SETUP = 2; + public const STATE_UNAVAILABLE = 3; public const FEATURE_NONE = 0; public const FEATURE_WEBHOOK = 1; diff --git a/lib/Service/BotService.php b/lib/Service/BotService.php index 4884a0c6d6f..716a436815c 100644 --- a/lib/Service/BotService.php +++ b/lib/Service/BotService.php @@ -27,6 +27,7 @@ use OCA\Talk\Model\BotServerMapper; use OCA\Talk\Room; use OCA\Talk\TalkSession; +use OCP\App\IAppManager; use OCP\AppFramework\Http; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Comments\IComment; @@ -67,6 +68,7 @@ public function __construct( protected LoggerInterface $logger, protected ICertificateManager $certificateManager, protected IEventDispatcher $dispatcher, + protected IAppManager $appManager, ) { $this->activityPubHelper = new ActivityPubHelper(); } @@ -503,4 +505,14 @@ public function validateBotParameters(string $name, string $secret, string $url, throw new \InvalidArgumentException('The provided description is too long (max. 4000 chars)'); } } + + public function isAppForBotEnabled(BotServer $bot): bool { + if (!str_starts_with($bot->getUrl(), Bot::URL_APP_PREFIX)) { + return true; + } + + $url = substr($bot->getUrl(), strlen(Bot::URL_APP_PREFIX)); + [$appId] = explode('/', $url, 2); + return $this->appManager->isEnabledForAnyone($appId); + } } diff --git a/src/components/AdminSettings/BotsSettings.vue b/src/components/AdminSettings/BotsSettings.vue index 59825634f43..0c4ebe33f62 100644 --- a/src/components/AdminSettings/BotsSettings.vue +++ b/src/components/AdminSettings/BotsSettings.vue @@ -150,6 +150,8 @@ export default { return { state_icon_component: IconLockOutline, state_icon_label: t('spreed', 'Locked for moderators'), state_icon_color: 'var(--color-favorite)' } case BOT.STATE.ENABLED: return { state_icon_component: IconCheck, state_icon_label: t('spreed', 'Enabled'), state_icon_color: 'var(--color-border-success)' } + case BOT.STATE.UNAVAILABLE: + return { state_icon_component: IconCancel, state_icon_label: t('spreed', 'App disabled'), state_icon_color: 'var(--color-text-maxcontrast)' } case BOT.STATE.DISABLED: default: return { state_icon_component: IconCancel, state_icon_label: t('spreed', 'Disabled'), state_icon_color: 'var(--color-border-error)' } diff --git a/src/components/ConversationSettings/BotsSettings.vue b/src/components/ConversationSettings/BotsSettings.vue index b8650fd466f..379afa0e61b 100644 --- a/src/components/ConversationSettings/BotsSettings.vue +++ b/src/components/ConversationSettings/BotsSettings.vue @@ -21,11 +21,17 @@ {{ bot.description ?? t('spreed', 'Description is not provided') }} + + + {{ t('spreed', 'The bot is not available anymore') }} +
{{ toggleButtonTitle(bot) }} @@ -38,6 +44,8 @@