8000 feature #50131 [Notifier] add Ntfy bridge (mikaelkael) · symfony/symfony@b51e2d3 · GitHub
[go: up one dir, main page]

Skip to content

Commit b51e2d3

Browse files
committed
feature #50131 [Notifier] add Ntfy bridge (mikaelkael)
This PR was squashed before being merged into the 6.4 branch. Discussion ---------- [Notifier] add Ntfy bridge | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | | License | MIT | Doc PR | symfony/symfony-docs#18253 | Recipes PR | symfony/recipes#1204 Add [Ntfy](https://ntfy.sh/) notifier bridge Commits ------- c097511 [Notifier] add Ntfy bridge
2 parents 16014de + c097511 commit b51e2d3

18 files changed

+721
-0
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

+1
Original file line numberDiff line numberDiff line change
@@ -2753,6 +2753,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $
27532753
NotifierBridge\MicrosoftTeams\MicrosoftTeamsTransportFactory::class => 'notifier.transport_factory.microsoft-teams',
27542754
NotifierBridge\Mobyt\MobytTransportFactory::class => 'notifier.transport_factory.mobyt',
27552755
NotifierBridge\Novu\NovuTransportFactory::class => 'notifier.transport_factory.novu',
2756+
NotifierBridge\Ntfy\NtfyTransportFactory::class => 'notifier.transport_factory.ntfy',
27562757
NotifierBridge\Octopush\OctopushTransportFactory::class => 'notifier.transport_factory.octopush',
27572758
NotifierBridge\OneSignal\OneSignalTransportFactory::class => 'notifier.transport_factory.one-signal',
27582759
NotifierBridge\OrangeSms\OrangeSmsTransportFactory::class => 'notifier.transport_factory.orange-sms',

src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php

+4
Original file line numberDiff line numberDiff line change
@@ -291,5 +291,9 @@
291291
->set('notifier.transport_factory.novu', Bridge\Novu\NovuTransportFactory::class)
292292
->parent('notifier.transport_factory.abstract')
293293
->tag('texter.transport_factory')
294+
295+
->set('notifier.transport_factory.ntfy', Bridge\Ntfy\NtfyTransportFactory::class)
296+
->parent('notifier.transport_factory.abstract')
297+
->tag('texter.transport_factory')
294298
;
295299
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/Tests export-ignore
2+
/phpunit.xml.dist export-ignore
3+
/.gitattributes export-ignore
4+
/.gitignore export-ignore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vendor/
2+
composer.lock
3+
phpunit.xml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CHANGELOG
2+
=========
3+
4+
6.4
5+
---
6+
7+
* Add the bridge
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2023-present Fabien Potencier
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Notifier\Bridge\Ntfy;
13+
14+
use Symfony\Component\Notifier\Exception\LogicException;
15+
use Symfony\Component\Notifier\Message\MessageOptionsInterface;
16+
use Symfony\Component\Notifier\Notification\Notification;
17+
18+
/**
19+
* @author Mickael Perraud <mikaelkael.fr@gmail.com>
20+
*/
21+
final class NtfyOptions implements MessageOptionsInterface
22+
{
23+
public const PRIORITY_URGENT = 5;
24+
public const PRIORITY_HIGH = 4;
25+
public const PRIORITY_DEFAULT = 3;
26+
public const PRIORITY_LOW = 2;
27+
public const PRIORITY_MIN = 1;
28+
29+
public function __construct(private array $options = [])
30+
{
31+
}
32+
33+
public static function fromNotification(Notification $notification): self
34+
{
35+
$options = new self();
36+
$options->setTitle($notification->getSubject());
37+
$options->setMessage($notification->getContent());
38+
$options->setStringPriority($notification->getImportance());
39+
$options->addTag($notification->getEmoji());
40+
41+
return $options;
42+
}
43+
44+
public function toArray(): array
45+
{
46+
return $this->options;
47+
}
48+
49+
public function getRecipientId(): ?string
50+
{
51+
return null;
52+
}
53+
54+
public function setMessage(string $message): self
55+
{
56+
$this->options['message'] = $message;
57+
58+
return $this;
59+
}
60+
61+
public function setTitle(string $title): self
62+
{
63+
$this->options['title'] = $title;
64+
65+
return $this;
66+
}
67+
68+
public function setStringPriority(string $priority): self
69+
{
70+
switch ($priority) {
71+
case Notification::IMPORTANCE_URGENT:
72+
return $this->setPriority(self::PRIORITY_URGENT);
73+
case Notification::IMPORTANCE_HIGH:
74+
return $this->setPriority(self::PRIORITY_HIGH);
75+
case Notification::IMPORTANCE_LOW:
76+
return $this->setPriority(self::PRIORITY_LOW);
77+
default:
78+
return $this->setPriority(self::PRIORITY_DEFAULT);
79+
}
80+
}
81+
82+
public function setPriority(int $priority): self
83+
{
84+
if (\in_array($priority, [
85+
self::PRIORITY_MIN, self::PRIORITY_LOW, self::PRIORITY_DEFAULT, self::PRIORITY_HIGH, self::PRIORITY_URGENT,
86+
])) {
87+
$this->options['priority'] = $priority;
88+
}
89+
90+
return $this;
91+
}
92+
93+
public function addTag(string $tag): self
94+
{
95+
$this->options['tags'][] = $tag;
96+
97+
return $this;
98+
}
99+
100+
public function setTags(array $tags): self
101+
{
102+
$this->options['tags'] = $tags;
103+
104+
return $this;
105+
}
106+
107+
public function setDelay(\DateTimeInterface $dateTime): self
108+
{
109+
if ($dateTime > (new \DateTime())) {
110+
$this->options['delay'] = (string) $dateTime->getTimestamp();
111+
} else {
112+
throw new LogicException('Delayed date must be defined in the future.');
113+
}
114+
115+
return $this;
116+
}
117+
118+
public function setActions(array $actions): self
119+
{
120+
$this->options['actions'] = $actions;
121+
122+
return $this;
123+
}
124+
125+
public function addAction(array $action): self
126+
{
127+
$this->options['actions'][] = $action;
128+
129+
return $this;
130+
}
131+
132+
public function setClick(string $url): self
133+
{
134+
$this->options['click'] = $url;
135+
136+
return $this;
137+
}
138+
139+
public function setAttachment(string $attachment): self
140+
{
141+
$this->options['attach'] = $attachment;
142+
143+
return $this;
144+
}
145+
146+
public function setFilename(string $filename): self
147+
{
148+
$this->options['filename'] = $filename;
149+
150+
return $this;
151+
}
152+
153+
public function setEmail(string $email): self
154+
{
155+
$this->options['email'] = $email;
156+
157+
return $this;
158+
}
159+
160+
public function setCache(bool $enable): self
161+
{
162+
if (!$enable) {
163+
$this->options['cache'] = 'no';
164+
} else {
165+
unset($this->options['cache']);
166+
}
167+
168+
return $this;
169+
}
170+
171+
public function setFirebase(bool $enable): self
172+
{
173+
if (!$enable) {
174+
$this->options['firebase'] = 'no';
175+
} else {
176+
unset($this->options['firebase']);
177+
}
178+
179+
return $this;
180+
}
181+
}
10000
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Notifier\Bridge\Ntfy;
13+
14+
use Symfony\Component\Notifier\Exception\LogicException;
15+
use Symfony\Component\Notifier\Exception\TransportException;
16+
use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException;
17+
use Symfony\Component\Notifier\Message\MessageInterface;
18+
use Symfony\Component\Notifier\Message\PushMessage;
19+
use Symfony\Component\Notifier\Message\SentMessage;
20+
use Symfony\Component\Notifier\Transport\AbstractTransport;
21+
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
22+
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
23+
use Symfony\Contracts\HttpClient\HttpClientInterface;
24+
25+
/**
26+
* @author Mickael Perraud <mikaelkael.fr@gmail.com>
27+
*/
28+
final class NtfyTransport extends AbstractTransport
29+
{
30+
protected const HOST = 'ntfy.sh';
31+
private ?string $user = null;
32+
private ?string $password = null;
33+
34+
public function __construct(private string $topic, private bool $secureHttp = true, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null)
35+
{
36+
parent::__construct($client, $dispatcher);
37+
}
38+
39+
public function getTopic(): string
40+
{
41+
return $this->topic;
42+
}
43+
44+
public function setPassword(?string $password): self
45+
{
46+
$this->password = $password;
47+
48+
return $this;
49+
}
50+
51+
public function setUser(?string $user): self
52+
{
53+
$this->user = $user;
54+
55+
return $this;
56+
}
57+
58+
protected function doSend(MessageInterface $message): SentMessage
59+
{
60+
if (!$message instanceof PushMessage) {
61+
throw new UnsupportedMessageTypeException(__CLASS__, PushMessage::class, $message);
62+
}
63+
64+
if ($message->getOptions() && !$message->getOptions() instanceof NtfyOptions) {
65+
throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" for options.', __CLASS__, NtfyOptions::class));
66+
}
67+
68+
if (!($opts = $message->getOptions()) && $notification = $message->getNotification()) {
69+
$opts = NtfyOptions::fromNotification($notification);
70+
}
71+
72+
$options = $opts ? $opts->toArray() : [];
73+
74+
$options['topic'] = $this->getTopic();
75+
76+
if (!isset($options['title'])) {
77+
$options['title'] = $message->getSubject();
78+
}
79+
if (!isset($options['message'])) {
80+
$options['message'] = $message->getContent();
81+
}
82+
83+
$headers = [];
84+
85+
if (null !== $this->user && null !== $this->password) {
86+
$headers['Authorization'] = 'Basic '.rtrim(base64_encode($this->user.':'.$this->password), '=');
87+
}
88+
89+
$response = $this->client->request('POST', ($this->secureHttp ? 'https' : 'http').'://'.$this->getEndpoint(), [
90+
'headers' => $headers,
91+
'json' => $options,
92+
]);
93+
94+
try {
95+
$statusCode = $response->getStatusCode();
96+
} catch (TransportExceptionInterface $e) {
97+
throw new TransportException('Could not reach the remote Ntfy server.', $response, 0, $e);
98+
}
99+
100+
if (200 !== $statusCode) {
101+
throw new TransportException(sprintf('Unable to send the Ntfy push notification: "%s".', $response->getContent(false)), $response);
102+
}
103+
104+
$result = $response->toArray(false);
105+
106+
if (empty($result['id'])) {
107+
throw new TransportException(sprintf('Unable to send the Ntfy push notification: "%s".', $response->getContent(false)), $response);
108+
}
109+
110+
$sentMessage = new SentMessage($message, (string) $this);
111+
$sentMessage->setMessageId($result['id']);
112+
113+
return $sentMessage;
114+
}
115+
116+
public function supports(MessageInterface $message): bool
117+
{
118+
return $message instanceof PushMessage &&
119+
(null === $message->getOptions() || $message->getOptions() instanceof NtfyOptions);
120+
}
121+
122+
public function __toString(): string
123+
{
124+
return sprintf('ntfy://%s/%s', $this->getEndpoint(), $this->getTopic());
125+
}
126+
}

0 commit comments

Comments
 (0)
0