From 7d35b6266b2ab8ff6201a8407e09ae3446c44493 Mon Sep 17 00:00:00 2001 From: Brock H Caldwell Date: Fri, 8 Aug 2025 23:38:53 -0500 Subject: [PATCH] feat: ntfy integration --- .env | 7 + composer.json | 2 + composer.lock | 148 +++++++++++++++++- config/packages/notifier.yaml | 13 ++ config/services.yaml | 4 + src/Base/Service/Broadcaster.php | 44 ++++-- src/Download/Downloader/ProcessDownloader.php | 3 + symfony.lock | 21 +++ 8 files changed, 229 insertions(+), 13 deletions(-) create mode 100644 config/packages/notifier.yaml diff --git a/.env b/.env index ca9cade..dafc6cd 100644 --- a/.env +++ b/.env @@ -51,3 +51,10 @@ OIDC_CLIENT_ID="Enter your OIDC client id" OIDC_CLIENT_SECRET="Enter your OIDC client secret" OIDC_BYPASS_FORM_LOGIN=false ###< drenso/symfony-oidc-bundle ### + +###> symfony/ntfy-notifier ### +# NTFY_DSN=ntfy://default/TOPIC +###< symfony/ntfy-notifier ### + +NOTIFICATION_TRANSPORT= +NTFY_DNS= diff --git a/composer.json b/composer.json index 0b13cb1..4d80427 100644 --- a/composer.json +++ b/composer.json @@ -43,6 +43,8 @@ "symfony/mailer": "7.3.*", "symfony/mercure-bundle": "^0.3.9", "symfony/messenger": "7.3.*", + "symfony/notifier": "7.3.*", + "symfony/ntfy-notifier": "7.3.*", "symfony/runtime": "7.3.*", "symfony/scheduler": "7.3.*", "symfony/security-bundle": "7.3.*", diff --git a/composer.lock b/composer.lock index d2a973b..e03a5c0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f4953f7b233a466d94269f35096f3385", + "content-hash": "6f57ba35ae317ec6370836bda0012db8", "packages": [ { "name": "1tomany/rich-bundle", @@ -7578,6 +7578,152 @@ ], "time": "2025-02-19T08:51:26+00:00" }, + { + "name": "symfony/notifier", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/notifier.git", + "reference": "9e68a3266c8b0381f8756022b1c1ba3c0264416e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/notifier/zipball/9e68a3266c8b0381f8756022b1c1ba3c0264416e", + "reference": "9e68a3266c8b0381f8756022b1c1ba3c0264416e", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3" + }, + "conflict": { + "symfony/event-dispatcher": "<6.4", + "symfony/event-dispatcher-contracts": "<2.5", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "symfony/event-dispatcher-contracts": "^2.5|^3", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Notifier\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Sends notifications via one or more channels (email, SMS, ...)", + "homepage": "https://symfony.com", + "keywords": [ + "notification", + "notifier" + ], + "support": { + "source": "https://github.com/symfony/notifier/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-05-01T12:12:53+00:00" + }, + { + "name": "symfony/ntfy-notifier", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/ntfy-notifier.git", + "reference": "094154ba36eac54078a71076effe46feaec59036" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ntfy-notifier/zipball/094154ba36eac54078a71076effe46feaec59036", + "reference": "094154ba36eac54078a71076effe46feaec59036", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/clock": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/notifier": "^7.3" + }, + "type": "symfony-notifier-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Notifier\\Bridge\\Ntfy\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mickael Perraud", + "email": "mikaelkael.fr@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Ntfy Notifier Bridge", + "homepage": "https://symfony.com", + "keywords": [ + "Ntfy", + "notifier" + ], + "support": { + "source": "https://github.com/symfony/ntfy-notifier/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-02-13T10:27:54+00:00" + }, { "name": "symfony/options-resolver", "version": "v7.3.0", diff --git a/config/packages/notifier.yaml b/config/packages/notifier.yaml new file mode 100644 index 0000000..d54ff95 --- /dev/null +++ b/config/packages/notifier.yaml @@ -0,0 +1,13 @@ +framework: + notifier: + chatter_transports: + texter_transports: + ntfy: '%notification.ntfy.dsn%' + channel_policy: + # use chat/slack, chat/telegram, sms/twilio or sms/nexmo + urgent: ['email'] + high: ['email'] + medium: ['email'] + low: ['email'] + admin_recipients: + - { email: admin@example.com } diff --git a/config/services.yaml b/config/services.yaml index b8885fa..4d9d2dc 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -44,6 +44,10 @@ parameters: auth.oidc.client_secret: '%env(OIDC_CLIENT_SECRET)%' auth.oidc.bypass_form_login: '%env(bool:OIDC_BYPASS_FORM_LOGIN)%' + # Notifications + notification.transport: '%env(NOTIFICATION_TRANSPORT)%' + notification.ntfy.dsn: '%env(NTFY_DSN)%' + services: # default configuration for services in *this* file _defaults: diff --git a/src/Base/Service/Broadcaster.php b/src/Base/Service/Broadcaster.php index b3d6c6c..2d18a1a 100644 --- a/src/Base/Service/Broadcaster.php +++ b/src/Base/Service/Broadcaster.php @@ -2,33 +2,53 @@ namespace App\Base\Service; +use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Mercure\HubInterface; use Symfony\Component\Mercure\Update; +use Symfony\Component\Notifier\Notification\Notification; +use Symfony\Component\Notifier\NotifierInterface; use Twig\Environment; readonly class Broadcaster { public function __construct( + #[Autowire(param: 'notification.transport')] + private string $notificationTransport, #[Autowire(service: 'twig')] private Environment $renderer, private HubInterface $hub, private RequestStack $requestStack, + private NotifierInterface $notifier, + private LoggerInterface $logger, ) {} - public function alert(string $title, string $message, string $type = "success"): void + public function alert(string $title, string $message, string $type = "success", bool $sendPush = false): void { - $userAlertTopic = $this->requestStack->getCurrentRequest()->getSession()->get('mercure_alert_topic'); - $update = new Update( - $userAlertTopic, - $this->renderer->render('broadcast/Alert.stream.html.twig', [ - 'alert_id' => uniqid(), - 'title' => $title, - 'message' => $message, - 'type' => $type, - ]) - ); - $this->hub->publish($update); + try { + $userAlertTopic = $this->requestStack->getCurrentRequest()->getSession()->get('mercure_alert_topic'); + $update = new Update( + $userAlertTopic, + $this->renderer->render('broadcast/Alert.stream.html.twig', [ + 'alert_id' => uniqid(), + 'title' => $title, + 'message' => $message, + 'type' => $type, + ]) + ); + $this->hub->publish($update); + } catch (\Throwable $exception) { + // ToDo: look for better handling to get message to end user + } + + if (true === $sendPush && in_array($this->notificationTransport, ['ntfy'])) { + try { + $notification = new Notification($title, ['push'])->content($message); + $this->notifier->send($notification); + } catch (\Throwable $exception) { + $this->logger->error('Unable to send push notification: ' . $exception->getMessage()); + } + } } } diff --git a/src/Download/Downloader/ProcessDownloader.php b/src/Download/Downloader/ProcessDownloader.php index 947c7c4..3dbc744 100644 --- a/src/Download/Downloader/ProcessDownloader.php +++ b/src/Download/Downloader/ProcessDownloader.php @@ -2,6 +2,7 @@ namespace App\Download\Downloader; +use App\Base\Service\Broadcaster; use App\Base\Service\MediaFiles; use App\Download\Framework\Entity\Download; use Doctrine\ORM\EntityManagerInterface; @@ -19,6 +20,7 @@ class ProcessDownloader implements DownloaderInterface private EntityManagerInterface $entityManager, private MediaFiles $mediaFiles, private CacheInterface $cache, + private readonly Broadcaster $broadcaster, ) {} /** @@ -82,6 +84,7 @@ class ProcessDownloader implements DownloaderInterface }); if ($downloadEntity->getStatus() !== 'Paused') { $downloadEntity->setProgress(100); + $this->broadcaster->alert('Success', '"'.$title.'" has finished downloading."', sendPush: true); } } catch (ProcessFailedException $exception) { $downloadEntity->setStatus('Failed'); diff --git a/symfony.lock b/symfony.lock index fa7f589..9a3cb0e 100644 --- a/symfony.lock +++ b/symfony.lock @@ -232,6 +232,27 @@ "config/packages/messenger.yaml" ] }, + "symfony/notifier": { + "version": "7.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "5.0", + "ref": "178877daf79d2dbd62129dd03612cb1a2cb407cc" + }, + "files": [ + "config/packages/notifier.yaml" + ] + }, + "symfony/ntfy-notifier": { + "version": "7.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.4", + "ref": "0d5e496659d7361bb4e6648eb8332f8cf097533d" + } + }, "symfony/property-info": { "version": "7.3", "recipe": {