Skip to content

Commit b9b20d7

Browse files
connorhufabpot
authored andcommitted
Added Discord bridge notifier embeds and test
1 parent 14790d8 commit b9b20d7

16 files changed

+690
-20
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Discord;
13+
14+
use Symfony\Component\Notifier\Bridge\Discord\Embeds\DiscordEmbedInterface;
15+
use Symfony\Component\Notifier\Exception\LogicException;
16+
use Symfony\Component\Notifier\Message\MessageOptionsInterface;
17+
18+
/**
19+
* @author Karoly Gossler <[email protected]>
20+
*
21+
* @experimental in 5.2
22+
*/
23+
final class DiscordOptions implements MessageOptionsInterface
24+
{
25+
private $options = [];
26+
27+
public function __construct(array $options = [])
28+
{
29+
$this->options = $options;
30+
}
31+
32+
public function toArray(): array
33+
{
34+
return $this->options;
35+
}
36+
37+
public function getRecipientId(): string
38+
{
39+
return '';
40+
}
41+
42+
public function username(string $username): self
43+
{
44+
$this->options['username'] = $username;
45+
46+
return $this;
47+
}
48+
49+
public function avatarUrl(string $avatarUrl): self
50+
{
51+
$this->options['avatar_url'] = $avatarUrl;
52+
53+
return $this;
54+
}
55+
56+
public function tts(bool $tts): self
57+
{
58+
$this->options['tts'] = $tts;
59+
60+
return $this;
61+
}
62+
63+
public function addEmbed(DiscordEmbedInterface $embed): self
64+
{
65+
if (!isset($this->options['embeds'])) {
66+
$this->options['embeds'] = [];
67+
}
68+
69+
if (\count($this->options['embeds']) >= 10) {
70+
throw new LogicException(sprintf('The "%s" only supports max 10 embeds.', __CLASS__));
71+
}
72+
73+
$this->options['embeds'][] = $embed->toArray();
74+
75+
return $this;
76+
}
77+
}

src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,20 @@ final class DiscordTransport extends AbstractTransport
3232
protected const HOST = 'discord.com';
3333

3434
private $token;
35-
private $chatChannel;
35+
private $webhookId;
3636

37-
public function __construct(string $token, string $channel = null, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null)
37+
public function __construct(string $token, string $webhookId = null, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null)
3838
{
3939
$this->token = $token;
40-
$this->chatChannel = $channel;
40+
$this->webhookId = $webhookId;
4141
$this->client = $client;
4242

4343
parent::__construct($client, $dispatcher);
4444
}
4545

4646
public function __toString(): string
4747
{
48-
return sprintf('discord://%s?channel=%s', $this->getEndpoint(), $this->chatChannel);
48+
return sprintf('discord://%s?webhook_id=%s', $this->getEndpoint(), $this->webhookId);
4949
}
5050

5151
public function supports(MessageInterface $message): bool
@@ -62,16 +62,47 @@ protected function doSend(MessageInterface $message): SentMessage
6262
throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, ChatMessage::class, get_debug_type($message)));
6363
}
6464

65-
$endpoint = sprintf('https://%s/api/webhooks/%s/%s', $this->getEndpoint(), $this->token, $this->chatChannel);
66-
$options['content'] = $message->getSubject();
65+
$messageOptions = $message->getOptions();
66+
$options = $messageOptions ? $messageOptions->toArray() : [];
67+
68+
$content = $message->getSubject();
69+
70+
if (\strlen($content) > 2000) {
71+
throw new LogicException(sprintf('The subject length of "%s" transport must be less than 2000 characters.', __CLASS__, ChatMessage::class, get_debug_type($message)));
72+
}
73+
74+
$endpoint = sprintf('https://%s/api/webhooks/%s/%s', $this->getEndpoint(), $this->webhookId, $this->token);
75+
$options['content'] = $content;
6776
$response = $this->client->request('POST', $endpoint, [
6877
'json' => array_filter($options),
6978
]);
7079

7180
if (204 !== $response->getStatusCode()) {
7281
$result = $response->toArray(false);
7382

74-
throw new TransportException(sprintf('Unable to post the Discord message: "%s" (%s).', $result['message'], $result['code']), $response);
83+
if (401 === $response->getStatusCode()) {
84+
$originalContent = $message->getSubject();
85+
$errorMessage = $result['message'];
86+
$errorCode = $result['code'];
87+
throw new TransportException(sprintf('Unable to post the Discord message: "%s" (%d: "%s").', $originalContent, $errorCode, $errorMessage), $response);
88+
}
89+
90+
if (400 === $response->getStatusCode()) {
91+
$originalContent = $message->getSubject();
92+
93+
$errorMessage = '';
94+
foreach ($result as $fieldName => $message) {
95+
$message = \is_array($message) ? implode(' ', $message) : $message;
96+
$errorMessage .= $fieldName.': '.$message.' ';
97+
}
98+
99+
$errorMessage = trim($errorMessage);
100+
throw new TransportException(sprintf('Unable to post the Discord message: "%s" (%s).', $originalContent, $errorMessage), $response);
101+
}
102+
103+
throw new TransportException(sprintf('Unable to post the Discord message: "%s" (Status Code: %d).', $message->getSubject(), $response->getStatusCode()), $response);
75104
}
105+
106+
return new SentMessage($message, (string) $this);
76107
}
77108
}

src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransportFactory.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ public function create(Dsn $dsn): TransportInterface
3030
{
3131
$scheme = $dsn->getScheme();
3232
$token = $this->getUser($dsn);
33-
$channel = $dsn->getOption('channel');
33+
$webhookId = $dsn->getOption('webhook_id');
3434
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
3535
$port = $dsn->getPort();
3636

3737
if ('discord' === $scheme) {
38-
return (new DiscordTransport($token, $channel, $this->client, $this->dispatcher))->setHost($host)->setPort($port);
38+
return (new DiscordTransport($token, $webhookId, $this->client, $this->dispatcher))->setHost($host)->setPort($port);
3939
}
4040

4141
throw new UnsupportedSchemeException($dsn, 'discord', $this->getSupportedSchemes());
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Discord\Embeds;
13+
14+
/**
15+
* @author Karoly Gossler <[email protected]>
16+
*
17+
* @experimental in 5.2
18+
*/
19+
abstract class AbstractDiscordEmbed implements DiscordEmbedInterface
20+
{
21+
protected $options = [];
22+
23+
public function toArray(): array
24+
{
25+
return $this->options;
26+
}
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Discord\Embeds;
13+
14+
/**
15+
* @author Karoly Gossler <[email protected]>
16+
*
17+
* @experimental in 5.2
18+
*/
19+
abstract class AbstractDiscordEmbedObject implements DiscordEmbedObjectInterface
20+
{
21+
protected $options = [];
22+
23+
public function toArray(): array
24+
{
25+
return $this->options;
26+
}
27+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Discord\Embeds;
13+
14+
/**
15+
* @author Karoly Gossler <[email protected]>
16+
*
17+
* @experimental in 5.2
18+
*/
19+
final class DiscordAuthorEmbedObject extends AbstractDiscordEmbedObject
20+
{
21+
public function name(string $name): self
22+
{
23+
$this->options['name'] = $name;
24+
25+
return $this;
26+
}
27+
28+
public function url(string $url): self
29+
{
30+
$this->options['url'] = $url;
31+
32+
return $this;
33+
}
34+
35+
public function iconUrl(string $iconUrl): self
36+
{
37+
$this->options['icon_url'] = $iconUrl;
38+
39+
return $this;
40+
}
41+
42+
public function proxyIconUrl(string $proxyIconUrl): self
43+
{
44+
$this->options['proxy_icon_url'] = $proxyIconUrl;
45+
46+
return $this;
47+
}
48+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Discord\Embeds;
13+
14+
/**
15+
* @author Karoly Gossler <[email protected]>
16+
*
17+
* @experimental in 5.2
18+
*/
19+
final class DiscordEmbed extends AbstractDiscordEmbed
20+
{
21+
public function title(string $title): self
22+
{
23+
$this->options['title'] = $title;
24+
25+
return $this;
26+
}
27+
28+
public function description(string $description): self
29+
{
30+
$this->options['description'] = $description;
31+
32+
return $this;
33+
}
34+
35+
public function url(string $url): self
36+
{
37+
$this->options['url'] = $url;
38+
39+
return $this;
40+
}
41+
42+
public function timestamp(\DateTime $timestamp): self
43+
{
44+
$this->options['timestamp'] = $timestamp->format(\DateTimeInterface::ISO8601);
45+
46+
return $this;
47+
}
48+
49+
public function color(int $color): self
50+
{
51+
$this->options['color'] = $color;
52+
53+
return $this;
54+
}
55+
56+
public function footer(DiscordFooterEmbedObject $footer): self
57+
{
58+
$this->options['footer'] = $footer->toArray();
59+
60+
return $this;
61+
}
62+
63+
public function thumbnail(DiscordMediaEmbedObject $thumbnail): self
64+
{
65+
$this->options['thumbnail'] = $thumbnail->toArray();
66+
67+
return $this;
68+
}
69+
70+
public function image(DiscordMediaEmbedObject $image): self
71+
{
72+
$this->options['image'] = $image->toArray();
73+
74+
return $this;
75+
}
76+
77+
public function author(DiscordAuthorEmbedObject $author): self
78+
{
79+
$this->options['author'] = $author->toArray();
80+
81+
return $this;
82+
}
83+
84+
public function addField(DiscordFieldEmbedObject $field): self
85+
{
86+
if (!isset($this->options['fields'])) {
87+
$this->options['fields'] = [];
88+
}
89+
90+
$this->options['fields'][] = $field->toArray();
91+
92+
return $this;
93+
}
94+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Discord\Embeds;
13+
14+
/**
15+
* @author Karoly Gossler <[email protected]>
16+
*/
17+
interface DiscordEmbedInterface
18+
{
19+
public function toArray(): array;
20+
}

0 commit comments

Comments
 (0)