Envoyer des notifications sur Discord

Publié le 13 juin 2023 par William Suppo
Couverture de l'article Envoyer des notifications sur Discord

A travers ce tutoriel, nous allons découvrir comment envoyer une notification sur Discord lorsqu’un événement ce produit.

Cela pourrait être dans le cas de l’inscription d’un utilisateur, lors d’un paiement ou quoi que ce soit d’autres.

Dans notre exemple, nous allons notifier Discord, via leur système de webhook, lorsqu’un article est créé.

Mise en situation

Pour les besoins de notre tutoriel, nous allons créer un modèle Post qui dispose des champs created_at et updated_at de base ainsi que les champs title et content :

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Factories\HasFactory;
6use Illuminate\Database\Eloquent\Model;
7 
8class Post extends Model
9{
10 use HasFactory;
11 
12 protected $fillable = ['title', 'content'];
13}

Voici la migration qui va nous permettre de créer la table posts :

1Schema::create('posts', function (Blueprint $table) {
2 $table->id();
3 $table->timestamps();
4 $table->string('title');
5 $table->text('content');
6});

Continuons, en implémentant dans un PostController une méthode qui va enregistrer un article en base de donnée lors de la soumission d’un formulaire et une autre qui se chargera d’afficher le contenu d’un artile :

1<?php
2 
3namespace App\Http\Controllers;
4 
5use App\Models\Post;
6use App\Notifications\PostCreated;
7use Illuminate\Contracts\View\View;
8use Illuminate\Http\RedirectResponse;
9use Illuminate\Http\Request;
10use Illuminate\Support\Facades\Notification;
11 
12class PostController extends Controller
13{
14 public function store(Request $request): RedirectResponse
15 {
16 $post = Post::create($request->all());
17 
18 return redirect()->back();
19 }
20 
21 public function show(Post $post): View
22 {
23 return view('posts.show', ['post' => $post]);
24 }
25}

Attention, afin de simplifier la compréhension du tutoriel, toute validation et tout contrôle d’accès a été supprimé, nous vous invitons à parcourir la suite de tutoriel sur les bases de Laravel si ces points vous intéressent !

Enfin, il nous reste plus qu’à déclarer nos routes qui pointent sur notre contrôleur :

1Route::post('/posts', [\App\Http\Controllers\PostController::class, 'store'])
2 ->name('posts.store');
3Route::get('/posts/{post}', [\App\Http\Controllers\PostController::class, 'show'])
4 ->name('posts.show');

Nous voilà avec tout le contexte nécessaire pour pouvoir maintenant jouer avec Discord !

Création du channel Discord

Afin de pouvoir notifier Discord, nous allons utiliser le système de notifications Laravel qui effectuera un post sur un webhook Discord.

Avant cela, petit aparté sur le contenu attendu par Discord lors de l'exécution d’un webhook.

Il faut savoir que le contenu d’un message peut-être très varié, ce qui se traduit par une souplesse accrue de leur webhook. Pour ce qui nous concerne, on va considérer que le message est un unique embed de type article qui possède :

En partant des pré-requis susmentionnés, nous pouvons établir les classes que nous allons implémenter :

Commençons par le DiscordMessage qui est ni plus ni moins qu’un DTO :

1<?php
2 
3namespace App\Services\Discord;
4 
5class DiscordMessage
6{
7 public function __construct(
8 public string $authorName,
9 public string $authorAvatar,
10 public string $title,
11 public string $description,
12 public string $url,
13 ) {
14 }
15}

Si vous souhaitez en savoir plus sur les DTOs, vous pouvez consultez nos articles ici, ou bien le dernier en date, celui-ci.

Maintenant que nous avons de quoi créer notre message, il nous faut de quoi l’envoyer, voilà donc notre classe DiscordWebhookChannel qui reprend la logique d’implémentation des autres canaux nécessaires au système de Notification de Laravel :

1<?php
2 
3namespace App\Services\Discord;
4 
5use Illuminate\Http\Client\Factory as Http;
6use Illuminate\Notifications\Notification;
7 
8class DiscordWebhookChannel
9{
10 public function __construct(public Http $http)
11 {
12 }
13 
14 public function send(object $notifiable, Notification $notification): void
15 {
16 if (! $url = $notifiable->routeNotificationFor('discord')) {
17 return;
18 }
19 
20 $payload = $this->buildPayload(
21 $notification->toDiscord($notifiable)
22 );
23 
24 $this->http->post($url, $payload);
25 }
26 
27 protected function buildPayload(DiscordMessage $message): array
28 {
29 return [
30 'embeds' => [
31 [
32 'author' => [
33 'name' => $message->authorName,
34 'icon_url' => $message->authorAvatar,
35 ],
36 'title' => $message->title,
37 'description' => $message->description,
38 'url' => $message->url,
39 ]
40 ],
41 ];
42 }
43}

Ici, la particularité réside dans la récupération de l’url du webhook, elle se fait via le $notifiable sauf que ce dernier n’est pas un utilisateur ou quoique ce soit de notifiable comme l’entend Laravel et nous sommes contraints d’avoir au moins un élément à notifier sinon rien ne se produit. Nous allons voir comment ruser lors de l’utilisation de notre notification peu après.

Création de la notification

Voilà arriver le moment où nous allons implémenter notre notification qui embarquera l’article fraîchement créé en passant par la commande dédiée suivante :

1php artisan make:notification PostCreated

Pour rappel, les canaux à utiliser sont à indiquer en retour de la méthode via() , on y ajoute donc notre DiscordWebhookChannel puis nous créons la méthode toDiscord() qui retournera un DiscordMessage basé sur le contenu de notre article :

1<?php
2 
3namespace App\Notifications;
4 
5use App\Models\Post;
6use App\Services\Discord\DiscordMessage;
7use App\Services\Discord\DiscordWebhookChannel;
8use Illuminate\Bus\Queueable;
9use Illuminate\Notifications\Notification;
10use Illuminate\Support\Str;
11 
12class PostCreated extends Notification
13{
14 use Queueable;
15 
16 public function __construct(public Post $post)
17 {
18 }
19 
20 public function via(object $notifiable): array
21 {
22 return [DiscordWebhookChannel::class];
23 }
24 
25 public function toDiscord(object $notifiable): DiscordMessage
26 {
27 return new DiscordMessage(
28 authorName: 'John',
29 authorAvatar: 'https://i.pravatar.cc/300',
30 title: $this->post->title,
31 description: Str::limit($this->post->content, 200),
32 url: route('posts.show', $this->post),
33 );
34 }
35}

Toujours dans le but de simplifier le tutoriel, nous avons simplifier la notion d’auteur. Libre à vous d’implémenter, par exemple, une relation PostUser pour rendre votre fonctionnalité complète.

Déclenchement de la notification

Nous avons dorénavant tout les éléments en notre possession pour envoyer notre notification.

Pour déclencher l’envoi, nous retournons au sein de la méthode PostController::store() pour y ajouter le code suivant :

1public function store(Request $request): RedirectResponse
2{
3 $post = Post::create($request->all());
4 
5 Notification::route('discord', 'https://discord.com/api/webhooks/****/****')
6 ->notify(new PostCreated($post));
7 
8 return redirect()->back();
9}

Comme annoncé précédemment, nous avons besoin d’au moins un notifiable et c’est ce qu’on produit lorsque la méthode Notification::route() est appelée, un AnonymousNotifiable est instancié ayant pour information une route, le webhook, pour notre canal Discord. D’où l’appel à $notifiable->routeNotificationFor('discord') dans le DiscordWebhookChannel , Eurêka !

Untitled

Conclusion

La question qu’on se pose c’est pourquoi s’embêter à passer par le système de notification de Laravel ? Eh bien, une raison viable selon moi est que si demain vous souhaitez provoquer l’envoi de cette même notification sur d’autre channel (slack, email, websocket, l’api d’un réseau social, etc), vous n’avez qu’à implémenter les méthodes toSlack ou toTwitter, par exemple, pour que ce soit fonctionnel ! Le reste du code ne changeant pas.

William Suppo avatar
William Suppo
Je mange du PHP au p'tit dej', et vous ?

A lire

Autres articles de la même catégorie