Skip to content

Commit 7c93432

Browse files
author
=
committed
refactor: improve webhook handling and validation, update installation instructions
1 parent 607eb9c commit 7c93432

8 files changed

Lines changed: 69 additions & 32 deletions

File tree

discord-webhooks/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Send Discord webhook notifications for various server events in Pelican Panel.
1919

2020
## Installation
2121

22-
1. Copy the `discord-webhooks` folder to your Pelican Panel plugins directory or import it with `https://github.com/jami100YT/pelican-plugins/archive/refs/tags/latest.zip`
22+
1. Copy the `discord-webhooks` folder to your Pelican Panel plugins directory
2323
2. Install the plugin
2424

2525
## Server Status Detection
@@ -28,7 +28,7 @@ Server start/stop detection works via a scheduled command that checks server sta
2828

2929
**Manual check:**
3030
```bash
31-
php artisan webhooks:check-status
31+
php artisan discord-webhooks:check-status
3232
```
3333

3434
## Usage

discord-webhooks/database/migrations/001_create_webhooks_table.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ public function up(): void
1212
$table->increments('id');
1313
$table->string('name');
1414
$table->string('webhook_url');
15-
$table->unsignedInteger('server_id')->nullable();
15+
$table->foreignId('server_id')->nullable()->constrained('servers')->nullOnDelete();
1616
$table->json('events')->default('[]');
1717
$table->boolean('enabled')->default(true);
1818
$table->timestamp('last_triggered_at')->nullable();
1919
$table->timestamps();
2020

21-
$table->foreign('server_id')->references('id')->on('servers')->nullOnDelete();
21+
// foreignId above handles FK constraint and nullOnDelete
2222
});
2323
}
2424

discord-webhooks/src/Console/Commands/CheckServerStatus.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ public function handle(DaemonServerRepository $repository, DiscordWebhookService
4242

4343
$cacheKey = "webhook_server_status_{$server->id}";
4444
$previousState = Cache::get($cacheKey, 'unknown');
45+
// Always refresh the cache TTL, even if state hasn't changed
46+
Cache::put($cacheKey, $currentState, now()->addHours(24));
4547

4648
if ($previousState !== $currentState) {
47-
Cache::put($cacheKey, $currentState, now()->addHours(24));
48-
4949
if ($previousState !== 'unknown') {
5050
if ($currentState === 'running') {
5151
$webhookService->triggerEvent(WebhookEvent::ServerStarted, $server);

discord-webhooks/src/Filament/Server/Resources/Webhooks/Pages/ManageWebhooks.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
namespace Notjami\Webhooks\Filament\Server\Resources\Webhooks\Pages;
44

5-
use Notjami\Webhooks\Filament\Server\Resources\Webhooks\WebhookResource;
65
use Filament\Resources\Pages\ManageRecords;
6+
use Notjami\Webhooks\Filament\Server\Resources\Webhooks\WebhookResource;
77

88
class ManageWebhooks extends ManageRecords
99
{

discord-webhooks/src/Filament/Server/Resources/Webhooks/WebhookResource.php

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@
22

33
namespace Notjami\Webhooks\Filament\Server\Resources\Webhooks;
44

5-
use App\Models\Server;
6-
use Notjami\Webhooks\Filament\Server\Resources\Webhooks\Pages\ManageWebhooks;
7-
use Notjami\Webhooks\Models\Webhook;
8-
use Notjami\Webhooks\Enums\WebhookEvent;
9-
use Notjami\Webhooks\Services\DiscordWebhookService;
5+
use Filament\Actions\Action;
106
use Filament\Actions\CreateAction;
117
use Filament\Actions\DeleteAction;
128
use Filament\Actions\EditAction;
13-
use Filament\Actions\Action;
149
use Filament\Forms\Components\Select;
1510
use Filament\Forms\Components\TextInput;
1611
use Filament\Forms\Components\Toggle;
@@ -20,7 +15,10 @@
2015
use Filament\Tables\Columns\IconColumn;
2116
use Filament\Tables\Columns\TextColumn;
2217
use Filament\Tables\Table;
23-
18+
use Notjami\Webhooks\Enums\WebhookEvent;
19+
use Notjami\Webhooks\Filament\Server\Resources\Webhooks\Pages\ManageWebhooks;
20+
use Notjami\Webhooks\Models\Webhook;
21+
use Notjami\Webhooks\Services\DiscordWebhookService;
2422
class WebhookResource extends Resource
2523
{
2624
protected static ?string $model = Webhook::class;
@@ -58,12 +56,29 @@ public static function table(Table $table): Table
5856
TextColumn::make('webhook_url')
5957
->label('Webhook URL')
6058
->limit(30)
61-
->tooltip(fn ($state) => $state),
59+
->formatStateUsing(function ($state) {
60+
// Mask the URL: show scheme://host/...****
61+
if (empty($state)) return '';
62+
$parsed = parse_url($state);
63+
if (!$parsed || !isset($parsed['scheme'], $parsed['host'])) return '••••••';
64+
$last4 = substr($parsed['path'] ?? '', -4);
65+
$masked = $parsed['scheme'] . '://' . $parsed['host'] . '/...';
66+
if ($last4) {
67+
$masked .= $last4;
68+
}
69+
return $masked;
70+
})
71+
->tooltip('••••••'),
6272
TextColumn::make('events')
6373
->label('Events')
6474
->badge()
6575
->formatStateUsing(function ($state) {
66-
return collect($state)->map(fn ($event) => WebhookEvent::from($event)->getLabel())->join(', ');
76+
$events = is_array($state) ? $state : (array) $state;
77+
return collect($events)
78+
->map(fn ($event) => WebhookEvent::tryFrom($event))
79+
->filter()
80+
->map(fn ($event) => $event->getLabel())
81+
->join(', ');
6782
}),
6883
IconColumn::make('enabled')
6984
->label('Enabled')
@@ -123,6 +138,7 @@ public static function form(Schema $schema): Schema
123138
->placeholder('https://discord.com/api/webhooks/...')
124139
->required()
125140
->url()
141+
->regex('/^https:\/\/discord\\.com\/api\/webhooks\/.+/')
126142
->maxLength(500)
127143
->columnSpanFull(),
128144
Select::make('events')

discord-webhooks/src/Models/Webhook.php

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

33
namespace Notjami\Webhooks\Models;
44

5+
use Illuminate\Database\Eloquent\Builder;
6+
57
use App\Models\Server;
68
use Carbon\Carbon;
79
use Illuminate\Database\Eloquent\Model;
@@ -50,31 +52,38 @@ public function server(): BelongsTo
5052
return $this->belongsTo(Server::class);
5153
}
5254

53-
public function hasEvent(WebhookEvent $event): bool
55+
/**
56+
* Check if the webhook has the given event.
57+
*
58+
* @param WebhookEvent|string $event
59+
* @return bool
60+
*/
61+
public function hasEvent(WebhookEvent|string $event): bool
5462
{
55-
return in_array($event->value, $this->events);
63+
$value = $event instanceof WebhookEvent ? $event->value : $event;
64+
return in_array($value, $this->events, true);
5665
}
5766

5867
public function appliesToServer(Server $server): bool
5968
{
6069
return $this->server_id === null || $this->server_id === $server->id;
6170
}
6271

63-
public function scopeEnabled($query)
72+
public function scopeEnabled(Builder $query): Builder
6473
{
6574
return $query->where('enabled', true);
6675
}
6776

68-
public function scopeForEvent($query, WebhookEvent $event)
77+
public function scopeForEvent(Builder $query, WebhookEvent $event): Builder
6978
{
7079
return $query->whereJsonContains('events', $event->value);
7180
}
7281

73-
public function scopeForServer($query, Server $server)
82+
public function scopeForServer(Builder $query, Server $server): Builder
7483
{
7584
return $query->where(function ($q) use ($server) {
7685
$q->whereNull('server_id')
77-
->orWhere('server_id', $server->id);
86+
->orWhere('server_id', $server->id);
7887
});
7988
}
8089
}

discord-webhooks/src/Providers/WebhooksPluginProvider.php

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,33 +41,36 @@ protected function registerServerStatusListeners(): void
4141

4242
// Server Installation Events
4343
Event::listen('eloquent.updating: App\Models\Server', function (Server $server) {
44-
$service = app(DiscordWebhookService::class);
45-
4644
// Check if status changed to installing
4745
if ($server->isDirty('status') && $server->status === 'installing') {
48-
$service->triggerEvent(WebhookEvent::ServerInstalling, $server);
46+
DB::afterCommit(function () use ($server) {
47+
app(DiscordWebhookService::class)->triggerEvent(WebhookEvent::ServerInstalling, $server);
48+
});
4949
}
5050

5151
// Check if installation completed (status changed from installing to null/running)
5252
if ($server->isDirty('status') && $server->getOriginal('status') === 'installing' && $server->status === null) {
53-
$service->triggerEvent(WebhookEvent::ServerInstalled, $server);
53+
DB::afterCommit(function () use ($server) {
54+
app(DiscordWebhookService::class)->triggerEvent(WebhookEvent::ServerInstalled, $server);
55+
});
5456
}
5557
});
5658

5759
// Try to listen for daemon status events if they exist
5860
$statusEvents = [
5961
'App\Events\Server\Started' => WebhookEvent::ServerStarted,
6062
'App\Events\Server\Stopped' => WebhookEvent::ServerStopped,
61-
'App\Events\Server\Starting' => WebhookEvent::ServerStarted,
62-
'App\Events\Server\Stopping' => WebhookEvent::ServerStopped,
6363
];
6464

6565
foreach ($statusEvents as $eventClass => $webhookEvent) {
6666
if (class_exists($eventClass)) {
6767
Event::listen($eventClass, function ($event) use ($webhookEvent) {
6868
$server = $event->server ?? null;
6969
if ($server instanceof Server) {
70-
app(DiscordWebhookService::class)->triggerEvent($webhookEvent, $server);
70+
// Only update the cache/state, do not send webhook here
71+
$cacheKey = "webhook_server_status_{$server->id}";
72+
$state = $webhookEvent === WebhookEvent::ServerStarted ? 'running' : 'offline';
73+
\Illuminate\Support\Facades\Cache::put($cacheKey, $state, now()->addHours(24));
7174
}
7275
});
7376
}

discord-webhooks/src/Services/DiscordWebhookService.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,10 @@ public function triggerEvent(WebhookEvent $event, Server $server): void
9191

9292
foreach ($webhooks as $webhook) {
9393
try {
94-
$this->sendServerEvent($webhook, $server, $event);
95-
96-
$webhook->update(['last_triggered_at' => now()]);
94+
$sent = $this->sendServerEvent($webhook, $server, $event);
95+
if ($sent) {
96+
$webhook->update(['last_triggered_at' => now()]);
97+
}
9798
} catch (\Exception $e) {
9899
Log::error('Failed to send webhook', [
99100
'webhook_id' => $webhook->id,
@@ -106,6 +107,14 @@ public function triggerEvent(WebhookEvent $event, Server $server): void
106107

107108
protected function send(Webhook $webhook, array $payload): bool
108109
{
110+
// Enforce Discord webhook URL pattern as a second layer of validation
111+
if (!preg_match('/^https:\/\/discord\.com\/api\/webhooks\/.+/', $webhook->webhook_url)) {
112+
Log::warning('Rejected non-Discord webhook URL', [
113+
'webhook_id' => $webhook->id,
114+
'url' => $webhook->webhook_url,
115+
]);
116+
return false;
117+
}
109118
try {
110119
$response = Http::timeout(10)
111120
->post($webhook->webhook_url, $payload);

0 commit comments

Comments
 (0)