games / fivem
Intégration Discord : webhooks et bot de logs
Explication
Ce que vous allez apprendre
- Envoyer un message formaté (embed) vers Discord avec
PerformHttpRequest - Organiser plusieurs webhooks distincts par catégorie de log (bans, économie, admin)
- Respecter les limites de taux imposées par Discord avec une file d'attente
- Gérer le code 429 (rate limit dépassé) sans perdre silencieusement un log
- Sécuriser une route exposée à un bot externe avec le système ACE déjà vu
Dans quel contexte ?
Une équipe de modération d'un serveur RP ne peut pas rester connectée en permanence à la console du serveur pour surveiller les bannissements ou les anomalies économiques. Faire remonter automatiquement ces événements vers des salons Discord dédiés permet à toute l'équipe de rester informée en temps réel, sans jamais avoir besoin de se connecter directement au serveur.
| Catégorie de log | Webhook dédié | Exemple d'événement |
|---|---|---|
| Bannissements | Webhooks.bans | Un joueur banni par un admin |
| Économie | Webhooks.economy | Pic d'argent anormal détecté |
| Administration | Webhooks.admin | Commande sensible utilisée |
Piège fréquent
Envoyer des logs vers Discord sans file d'attente expose au code 429 (limite de 5 requêtes/2s par webhook dépassée) : les requêtes en trop sont simplement refusées, ce qui fait perdre silencieusement des logs potentiellement importants sans qu'aucune erreur ne remonte clairement.
Sortir les logs du serveur lui-même
Les techniques de logging vues en leçon 18 (console, fichiers) sont utiles pour déboguer, mais une équipe de modération ne surveille pas la console du serveur en continu. La quasi-totalité des communautés FiveM utilisent Discord comme plateforme de communication ; y faire remonter automatiquement les événements importants (bans, anomalies économiques) permet à toute l'équipe de rester informée en temps réel, sans avoir à se connecter au serveur lui-même.
Un webhook, la solution la plus simple qui suffit presque toujours
Un "bot Discord" complet nécessite une connexion permanente et une gestion d'état complexe. Pour simplement ENVOYER des messages formatés vers un salon Discord, un webhook suffit largement : c'est une simple URL à laquelle on envoie une requête HTTP, sans authentification complexe ni connexion permanente à maintenir. FiveM peut envoyer cette requête nativement avec PerformHttpRequest, sans dépendance externe.
Pourquoi respecter les limites imposées par Discord
Discord impose une limite stricte au nombre de requêtes qu'un même webhook peut recevoir en peu de temps. Dépasser cette limite ne provoque pas une erreur silencieuse ignorable : les requêtes en trop sont refusées (code 429), ce qui, sans précaution, ferait perdre des logs potentiellement importants. Une file d'attente qui espace les envois et retente automatiquement en cas de refus évite cette perte silencieuse d'information.
Séparer les catégories de logs, une question d'organisation
Envoyer tous les événements (bans, anomalies économiques, actions admin) vers un unique salon Discord le rend vite illisible. Utiliser un webhook distinct par catégorie permet à chaque salon Discord de rester focalisé sur un seul type d'information, facilitant grandement le travail de l'équipe de modération qui doit filtrer rapidement ce qui compte.
Ne jamais oublier la sécurité, même pour "juste afficher un statut"
Une route exposée, même pour une information qui semble anodine comme le nombre de joueurs connectés, doit rester protégée par les mêmes principes de permissions (ACE, vus en leçon 8) que n'importe quelle autre commande sensible — l'exposition d'informations, même mineures, doit toujours rester une décision consciente plutôt qu'un oubli.
Commandes & code
Intégration Discord : webhooks et bot de logs
Un webhook Discord suffit pour la plupart des logs serveur (pas besoin d'un bot complet avec gateway websocket).
-- Envoi d'un embed Discord via webhook, depuis le serveur FiveM (PerformHttpRequest, natif, pas de dépendance externe)
local function SendDiscordLog(webhookUrl, title, description, color, fields)
local embed = {
{
title = title,
description = description,
color = color, -- couleur en décimal, ex: 15158332 pour un rouge
fields = fields or {},
footer = { text = 'Technologik RP' },
timestamp = os.date('!%Y-%m-%dT%H:%M:%SZ'), -- format ISO 8601 requis par l'API Discord
}
}
PerformHttpRequest(webhookUrl, function(statusCode, response, headers)
if statusCode ~= 204 and statusCode ~= 200 then
print(('[Discord] Échec envoi webhook, code %s'):format(statusCode))
end
end, 'POST', json.encode({ embeds = embed }), { ['Content-Type'] = 'application/json' })
end-- Bibliothèque de logs centralisée avec plusieurs webhooks par catégorie (séparer bans, économie, admin...)
local Webhooks = {
bans = GetConvar('discord_webhook_bans', ''),
economy = GetConvar('discord_webhook_economy', ''),
admin = GetConvar('discord_webhook_admin', ''),
}
local function LogBan(adminName, targetName, reason, duration)
SendDiscordLog(Webhooks.bans, 'Bannissement', nil, 15158332, {
{ name = 'Admin', value = adminName, inline = true },
{ name = 'Joueur', value = targetName, inline = true },
{ name = 'Durée', value = duration, inline = true },
{ name = 'Raison', value = reason, inline = false },
})
end
local function LogEconomyAnomaly(playerName, amount, reason)
SendDiscordLog(Webhooks.economy, 'Anomalie économique', reason, 16776960, {
{ name = 'Joueur', value = playerName, inline = true },
{ name = 'Montant', value = tostring(amount), inline = true },
})
end-- Rate-limiting des webhooks : Discord limite à 5 requêtes/2s par webhook, une file d'attente évite les 429
local WebhookQueue = {}
local isProcessingQueue = false
local function enqueueWebhook(url, payload)
table.insert(WebhookQueue, { url = url, payload = payload })
if not isProcessingQueue then
isProcessingQueue = true
Citizen.CreateThread(function()
while #WebhookQueue > 0 do
local item = table.remove(WebhookQueue, 1)
PerformHttpRequest(item.url, function(code)
if code == 429 then
table.insert(WebhookQueue, 1, item) -- remis en tête de file, sera retenté
end
end, 'POST', item.payload, { ['Content-Type'] = 'application/json' })
Citizen.Wait(500) -- respecte la limite de 5 req/2s avec de la marge
end
isProcessingQueue = false
end)
end
end-- Vérification de statut serveur exposée à un bot Discord externe via une route HTTP simple (ox_lib ou txAdmin API)
-- côté serveur : endpoint minimal consultable par un bot pour afficher "X/64 joueurs en ligne" en topic du salon
RegisterCommand('discord_status_json', function()
local players = {}
for _, playerId in ipairs(GetPlayers()) do
table.insert(players, GetPlayerName(playerId))
end
return json.encode({
online = #players,
max = GetConvarInt('sv_maxclients', 64),
players = players,
})
end, true) -- true = commande restreinte, à sécuriser via un ACE appropriéRésumé
- Un webhook Discord (via
PerformHttpRequest) suffit pour la majorité des besoins de logs, sans dépendance à une lib de bot. - Séparer les webhooks par catégorie (bans, économie, admin) simplifie la modération et le filtrage côté Discord.
- Discord limite à 5 requêtes/2s par webhook : une file d'attente avec retry sur 429 évite la perte silencieuse de logs.
- Toute route exposée pour un bot externe doit rester protégée par ACE, même si elle ne fait que lire un statut.
Exercices pratiques
Mission : des logs Discord qui disparaissent en silence
Objectif : Diagnostiquer une perte de logs due à l'absence de file d'attente, la corriger, puis évaluer un choix d'organisation des webhooks.
Contexte
L'équipe de modération de technologik_rp remarque que certains bannissements n'apparaissent jamais dans le salon Discord dédié, alors que la commande de ban a bien fonctionné en jeu. La fonction SendDiscordLog de la leçon appelle directement PerformHttpRequest à chaque log, sans jamais passer par la file d'attente enqueueWebhook également définie dans la ressource mais jamais utilisée.