Skip to content

Commit c151aa5

Browse files
authored
Merge pull request #97 from utopia-php/refactor-discord-adapter
Refactor: discord adapter accept URL instead of ID and token
2 parents c51915d + 509ceed commit c151aa5

File tree

5 files changed

+95
-17
lines changed

5 files changed

+95
-17
lines changed

.env.dev

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ VONAGE_API_KEY=
2727
VONAGE_API_SECRET=
2828
VONAGE_TO=
2929
VONAGE_FROM=
30-
DISCORD_WEBHOOK_ID=
31-
DISCORD_WEBHOOK_TOKEN=
30+
DISCORD_WEBHOOK_URL=
3231
FAST2SMS_API_KEY=
3332
FAST2SMS_SENDER_ID=
3433
FAST2SMS_MESSAGE_ID=

.github/workflows/test.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ jobs:
4343
VONAGE_API_SECRET: ${{ secrets.VONAGE_API_SECRET }}
4444
VONAGE_TO: ${{ secrets.VONAGE_TO }}
4545
VONAGE_FROM: ${{ secrets.VONAGE_FROM }}
46-
DISCORD_WEBHOOK_ID: ${{ secrets.DISCORD_WEBHOOK_ID }}
47-
DISCORD_WEBHOOK_TOKEN: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
46+
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
4847
FAST2SMS_API_KEY: ${{ secrets.FAST2SMS_API_KEY }}
4948
FAST2SMS_SENDER_ID: ${{ secrets.FAST2SMS_SENDER_ID }}
5049
FAST2SMS_MESSAGE_ID: ${{ secrets.FAST2SMS_MESSAGE_ID }}

docker-compose.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ services:
3636
- VONAGE_API_SECRET
3737
- VONAGE_TO
3838
- VONAGE_FROM
39-
- DISCORD_WEBHOOK_ID
40-
- DISCORD_WEBHOOK_TOKEN
39+
- DISCORD_WEBHOOK_URL
4140
- FAST2SMS_API_KEY
4241
- FAST2SMS_SENDER_ID
4342
- FAST2SMS_MESSAGE_ID

src/Utopia/Messaging/Adapter/Chat/Discord.php

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,47 @@
55
use Utopia\Messaging\Adapter;
66
use Utopia\Messaging\Messages\Discord as DiscordMessage;
77
use Utopia\Messaging\Response;
8+
use InvalidArgumentException;
89

910
class Discord extends Adapter
1011
{
1112
protected const NAME = 'Discord';
1213
protected const TYPE = 'chat';
1314
protected const MESSAGE_TYPE = DiscordMessage::class;
15+
protected string $webhookId = '';
1416

1517
/**
16-
* @param string $webhookId Your Discord webhook ID.
17-
* @param string $webhookToken Your Discord webhook token.
18+
* @param string $webhookURL Your Discord webhook URL.
19+
* @throws InvalidArgumentException When webhook URL is invalid
1820
*/
1921
public function __construct(
20-
private string $webhookId,
21-
private string $webhookToken,
22+
private string $webhookURL
2223
) {
24+
// Validate URL format
25+
if (!filter_var($webhookURL, FILTER_VALIDATE_URL)) {
26+
throw new InvalidArgumentException('Invalid Discord webhook URL format.');
27+
}
28+
29+
// Validate URL uses https scheme
30+
$urlParts = parse_url($webhookURL);
31+
if (!isset($urlParts['scheme']) || $urlParts['scheme'] !== 'https') {
32+
throw new InvalidArgumentException('Discord webhook URL must use HTTPS scheme.');
33+
}
34+
35+
// Validate host is discord.com
36+
if (!isset($urlParts['host']) || $urlParts['host'] !== 'discord.com') {
37+
throw new InvalidArgumentException('Discord webhook URL must use discord.com as host.');
38+
}
39+
40+
// Extract and validate webhook ID
41+
$parts = explode('/webhooks/', $urlParts['path']);
42+
if (count($parts) >= 2) {
43+
$webhookParts = explode('/', $parts[1]);
44+
$this->webhookId = $webhookParts[0];
45+
}
46+
if (empty($this->webhookId)) {
47+
throw new InvalidArgumentException('Discord webhook ID cannot be empty.');
48+
}
2349
}
2450

2551
public function getName(): string
@@ -69,7 +95,7 @@ protected function process(DiscordMessage $message): array
6995
$response = new Response($this->getType());
7096
$result = $this->request(
7197
method: 'POST',
72-
url: "https://discord.com/api/webhooks/{$this->webhookId}/{$this->webhookToken}{$queryString}",
98+
url: "{$this->webhookURL}{$queryString}",
7399
headers: [
74100
'Content-Type: application/json',
75101
],

tests/Messaging/Adapter/Chat/DiscordTest.php

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Utopia\Tests\Adapter\Chat;
44

5+
use InvalidArgumentException;
56
use Utopia\Messaging\Adapter\Chat\Discord;
67
use Utopia\Messaging\Messages\Discord as DiscordMessage;
78
use Utopia\Tests\Adapter\Base;
@@ -10,13 +11,9 @@ class DiscordTest extends Base
1011
{
1112
public function testSendMessage(): void
1213
{
13-
$id = \getenv('DISCORD_WEBHOOK_ID');
14-
$token = \getenv('DISCORD_WEBHOOK_TOKEN');
14+
$url = \getenv('DISCORD_WEBHOOK_URL');
1515

16-
$sender = new Discord(
17-
webhookId: $id,
18-
webhookToken: $token
19-
);
16+
$sender = new Discord($url);
2017

2118
$content = 'Test Content';
2219

@@ -29,4 +26,62 @@ public function testSendMessage(): void
2926

3027
$this->assertResponse($result);
3128
}
29+
30+
/**
31+
* @return array<array<string>>
32+
*/
33+
public static function invalidURLProvider(): array
34+
{
35+
return [
36+
'invalid URL format' => ['not-a-url'],
37+
'invalid scheme (http)' => ['http://discord.com/api/webhooks/123456789012345678/abcdefghijklmnopqrstuvwxyz'],
38+
'invalid host' => ['https://example.com/api/webhooks/123456789012345678/abcdefghijklmnopqrstuvwxyz'],
39+
'missing path' => ['https://discord.com'],
40+
'no webhooks segment' => ['https://discord.com/api/invalid/123456789012345678/token'],
41+
'missing webhook ID' => ['https://discord.com/api/webhooks//token'],
42+
];
43+
}
44+
45+
/**
46+
* @dataProvider invalidURLProvider
47+
*/
48+
public function testInvalidURLs(string $invalidURL): void
49+
{
50+
$this->expectException(InvalidArgumentException::class);
51+
new Discord($invalidURL);
52+
}
53+
54+
public function testValidURLVariations(): void
55+
{
56+
// Valid URL format variations
57+
$validURLs = [
58+
'with api path' => 'https://discord.com/api/webhooks/123456789012345678/abcdefghijklmnopqrstuvwxyz',
59+
'without api path' => 'https://discord.com/webhooks/123456789012345678/abcdefghijklmnopqrstuvwxyz',
60+
'with trailing slash' => 'https://discord.com/api/webhooks/123456789012345678/abcdefghijklmnopqrstuvwxyz/',
61+
];
62+
63+
foreach ($validURLs as $label => $url) {
64+
try {
65+
$discord = new Discord($url);
66+
// If we get here, the URL was accepted
67+
$this->assertTrue(true, "Valid URL variant '{$label}' was accepted as expected");
68+
} catch (InvalidArgumentException $e) {
69+
$this->fail("Valid URL variant '{$label}' was rejected: " . $e->getMessage());
70+
}
71+
}
72+
}
73+
74+
public function testWebhookIDExtraction(): void
75+
{
76+
// Create a reflection of Discord to access protected properties
77+
$webhookId = '123456789012345678';
78+
$url = "https://discord.com/api/webhooks/{$webhookId}/abcdefghijklmnopqrstuvwxyz";
79+
80+
$discord = new Discord($url);
81+
$reflector = new \ReflectionClass($discord);
82+
$property = $reflector->getProperty('webhookId');
83+
$property->setAccessible(true);
84+
85+
$this->assertEquals($webhookId, $property->getValue($discord), 'Webhook ID was not correctly extracted');
86+
}
3287
}

0 commit comments

Comments
 (0)