Liste blanche Discord pour FiveM : autoriser les joueurs par rôle Discord

Créez une liste blanche de rôle Discord pour votre serveur FiveM : playerConnecting avec deferrals, l'identifiant Discord, une vérification de rôle de bot via l'API REST, et un token sûr.

Vous voulez que seulement les joueurs avec un certain rôle Discord rejoignent : membres, supporters, whitelistés. FiveM n'a pas de vérification Discord intégrée, donc vous l'ajoutez dans playerConnecting : lisez l'identifiant Discord du joueur, demandez à Discord ses rôles via un bot, et acceptez ou rejetez la connexion.

Vous avez besoin de trois choses : un bot Discord dans votre serveur, un script sur le serveur FiveM, et un endroit sûr pour le token.

Comment cela fonctionne

  1. Un joueur se connecte. FiveM déclenche playerConnecting et vous donne ses identifiants.
  2. Vous pausez la connexion avec les deferrals (voir playerConnecting et deferrals).
  3. Votre serveur demande l'API REST Discord : cet utilisateur est-il dans la guilde, et quels rôles a-t-il ?
  4. Vous appelez deferrals.done() pour le laisser entrer, ou deferrals.done('reason') pour rejeter.

Créer le bot

  1. Ouvrez le portail des développeurs Discord et créez une application, puis ajoutez un Bot à celle-ci.
  2. Copiez le token du bot. Traitez-le comme un mot de passe.
  3. Aucune intention privilégiée n'est nécessaire pour rechercher un seul membre par ID. (L'Intent des membres du serveur importe seulement si un bot liste chaque membre ou écoute les événements des membres.)
  4. Invitez le bot à votre serveur Discord. Il n'a pas besoin de droits administrateur, il doit seulement être un membre.
  5. Dans Discord, activez le mode développeur, puis copiez l'ID de votre serveur (guilde) et l'ID du rôle du rôle whitelist.

Garder le token hors du code

Définissez-le comme une convar dans server.cfg, ou dans un fichier cfg que vous exec et ne partagez jamais :

cfg
set discord_bot_token "paste-token-here"
set discord_guild_id "123456789012345678"
set discord_whitelist_role "123456789012345679"

Utilisez set, pas setr. Une convar setr est répliquée à chaque client, ce qui fuirait le token aux joueurs.

Lisez-les dans le script :

lua
local TOKEN = GetConvar('discord_bot_token', '')
local GUILD = GetConvar('discord_guild_id', '')
local ROLE  = GetConvar('discord_whitelist_role', '')

Attention : si le token finit jamais dans une capture d'écran, un message de support ou un repository public, réinitialisez-le dans le portail des développeurs tout de suite.

Lire l'identifiant Discord

Les identifiants d'un joueur ressemblent à discord:123456789012345678. Bouclez dessus et enlevez le préfixe :

lua
local function getDiscordId(src)
    for _, id in ipairs(GetPlayerIdentifiers(src)) do
        if id:sub(1, 8) == 'discord:' then
            return id:sub(9)
        end
    end
end

Demander à Discord les rôles

Le point de terminaison est GET https://discord.com/api/v10/guilds/{guild.id}/members/{user.id}, autorisé avec Authorization: Bot <token>. La réponse contient un tableau roles d'IDs de rôles.

lua
local function hasWhitelistRole(discordId, cb)
    PerformHttpRequest(
        ('https://discord.com/api/v10/guilds/%s/members/%s'):format(GUILD, discordId),
        function(status, body)
            if status == 200 then
                local member = json.decode(body)
                for _, role in ipairs(member.roles or {}) do
                    if role == ROLE then return cb(true) end
                end
                return cb(false)
            elseif status == 404 then
                return cb(false, 'not_in_guild')
            end
            cb(nil, status) -- rate limit or Discord error
        end,
        'GET', '',
        { ['Authorization'] = 'Bot ' .. TOKEN }
    )
end

Un 404 signifie que l'utilisateur n'est pas un membre de votre serveur Discord. N'importe quel autre statut, comme 429 ou 5xx, est un problème en cours, pas un « non ».

Le gestionnaire de connexion

lua
AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
    local src = source
    deferrals.defer()
    Wait(0)

    deferrals.update('Checking your Discord role...')

    local discordId = getDiscordId(src)
    if not discordId then
        return deferrals.done('Open the Discord app on your PC, then reconnect.')
    end

    hasWhitelistRole(discordId, function(ok, info)
        if ok then
            deferrals.done()
        elseif ok == false and info == 'not_in_guild' then
            deferrals.done('Join our Discord server first.')
        elseif ok == false then
            deferrals.done('You do not have the whitelist role.')
        else
            deferrals.done('Could not reach Discord (' .. tostring(info) .. '). Try again in a moment.')
        end
    end)
end)

Enregistrez source dans une locale d'abord : après Wait, le global source ne pointe plus vers ce joueur.

Limites de débit et mise en cache

Discord limite les requêtes API par route et globalement. Au redémarrage, beaucoup de joueurs se reconnectent à la fois et chacun déclenche une requête. Deux protections simples :

  • Mettez les résultats en cache pendant quelques minutes par ID Discord, pour qu'un joueur qui réessaie n'appelle pas l'API à nouveau.
  • Gérez 429. Discord répond avec un en-tête Retry-After. Affiche un message « réessayez » et ne boucleriez pas dessus.
lua
local cache = {}
local TTL = 5 * 60 * 1000

local function cachedCheck(discordId, cb)
    local hit = cache[discordId]
    if hit and GetGameTimer() - hit.at < TTL then
        return cb(hit.ok)
    end
    hasWhitelistRole(discordId, function(ok, info)
        if ok ~= nil then cache[discordId] = { ok = ok, at = GetGameTimer() } end
        cb(ok, info)
    end)
end

Utilisez cachedCheck dans le gestionnaire à la place de hasWhitelistRole. Un « oui » en cache signifie un rôle supprimé qui fonctionne jusqu'à cinq minutes de plus, ce qui est bien pour une whitelist.

Autres points

  • Ne faites pas défaut en ouverture. Si Discord est en panne, rejetez avec un message clair au lieu de laisser entrer tout le monde, à moins que vous ayez décidé que c'est acceptable.
  • Contournement du personnel. Si vous voulez que les admins rejoignent sans le rôle, vérifiez leurs identifiants contre une liste avant d'appeler Discord.
  • Les joueurs sans Discord n'obtiennent pas d'identifiant. Votre message ci-dessus leur dit quoi faire.
  • Pour bloquer un tricheur pour de bon, bannissez-les au lieu de supprimer un rôle. Voir modération avec txAdmin.

Checklist

Problème Solution
Pas d'identifiant discord: L'application Discord du joueur doit être en cours d'exécution et connectée
401 de Discord Token incorrect ou réinitialisé, ou préfixe Bot manquant
403 de Discord Le bot n'est pas dans ce serveur, ou le token appartient à un autre bot
404 de Discord L'utilisateur n'est pas dans votre serveur Discord
429 de Discord Limite de débit : mettez les résultats en cache et affichez un message de réessai
Token visible pour les joueurs Utilisez set, pas setr, et gardez-le hors des fichiers partagés
Le joueur est coincé sur « Checking » Chaque chemin de code doit se terminer par deferrals.done

Réponses rapides

Le joueur a-t-il besoin que Discord soit ouvert pour rejoindre ?

Oui. FiveM ne envoie l'identifiant discord: que quand l'application Discord est en cours d'exécution et connectée sur le PC du joueur. Sans cela, l'identifiant manque et votre script doit afficher un message clair.

Où je mets le token du bot ?

Dans une convar définie dans server.cfg ou un fichier cfg séparé, lu avec GetConvar. Ne le mettez jamais dans un fichier Lua qui est partagé, téléchargé ou stocké dans un repository.

J'appelle l'API Discord à chaque connexion ?

Vous pouvez, mais mettez en cache le résultat pendant quelques minutes. Discord limite les requêtes et un redémarrage avec beaucoup de joueurs qui se reconnectent peut atteindre la limite.

Des scripts sans ce problème

Tebex TemplateUn thème premium sans code pour votre boutique Tebex, entièrement modifiable depuis le panel Tebex.Voir le script →Mic PhoneUn téléphone pliable qui se déplie en tablette et se prolonge jusqu’au vrai téléphone du joueur.Voir le script →

À lire aussi