Lista blanca de Discord para FiveM: permite jugadores por rol de Discord

Construye una lista blanca de rol de Discord para tu servidor FiveM: playerConnecting con deferrals, el identificador de Discord, una comprobación de rol de bot a través de la API REST, y un token seguro.

Quieres que solo los jugadores con un cierto rol de Discord se unan: miembros, simpatizantes, autorizados. FiveM no tiene una comprobación de Discord incorporada, así que añades una en playerConnecting: lee el identificador de Discord del jugador, pregunta a Discord por sus roles a través de un bot, y acepta o rechaza la conexión.

Necesitas tres cosas: un bot de Discord en tu servidor, un script en el servidor FiveM, y un lugar seguro para el token.

Cómo funciona

  1. Un jugador se conecta. FiveM activa playerConnecting y te da sus identificadores.
  2. Pausas la conexión con deferrals (consulta playerConnecting y deferrals).
  3. Tu servidor pregunta a la API REST de Discord: ¿este usuario está en el gremio, y qué roles tiene?
  4. Llamas a deferrals.done() para dejarlos entrar, o deferrals.done('reason') para rechazar.

Crea el bot

  1. Abre el portal de desarrolladores de Discord y crea una aplicación, luego añade un Bot a ella.
  2. Copia el token del bot. Trátalo como una contraseña.
  3. No se necesita ningún intent privilegiado para buscar un miembro por ID. (El Server Members Intent solo importa si un bot lista a cada miembro o escucha eventos de miembros.)
  4. Invita el bot a tu servidor de Discord. No necesita derechos de administrador, solo tiene que ser un miembro.
  5. En Discord, activa Modo de Desarrollador, luego copia tu id de servidor (gremio) y la id de rol del rol de lista blanca.

Mantén el token fuera del código

Establécelo como un convar en server.cfg, o en un archivo cfg que exec y nunca compartas:

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

Usa set, no setr. Un convar setr es replicado a cada cliente, que filtraría el token a los jugadores.

Léelos en el script:

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

Cuidado: si el token alguna vez termina en una captura de pantalla, un mensaje de soporte o un repositorio público, reinícialo en el portal de desarrolladores inmediatamente.

Lee el identificador de Discord

Los identificadores de un jugador se parecen a discord:123456789012345678. Itera sobre ellos y quita el prefijo:

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

Pregunta a Discord por los roles

El endpoint es GET https://discord.com/api/v10/guilds/{guild.id}/members/{user.id}, autorizado con Authorization: Bot <token>. La respuesta contiene un array roles de ids de roles.

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 significa que el usuario no es miembro de tu servidor de Discord. Cualquier otro estado, como 429 o 5xx, es un problema en el camino, no un «no».

El manejador de conexión

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)

Guarda source en una variable local primero: después de Wait, el source global ya no apunta a este jugador.

Límites de velocidad y cacheo

Discord limita las solicitudes de API por ruta y globalmente. En un reinicio, muchos jugadores se reconectan a la vez y cada uno activa una solicitud. Dos protecciones simples:

  • Cachea resultados durante unos minutos por ID de Discord, así que un jugador que reintenta no llama a la API nuevamente.
  • Maneja 429. Discord responde con un header Retry-After. Muestra un mensaje «intenta de nuevo» y no hagas un bucle en él.
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

Usa cachedCheck en el manejador en lugar de hasWhitelistRole. Un «sí» cacheado significa que un rol eliminado funciona hasta cinco minutos más, lo que está bien para una lista blanca.

Otros puntos

  • No falles abierto. Si Discord está caído, rechaza con un mensaje claro en lugar de dejar a todos entrar, a menos que hayas decidido que es aceptable.
  • Bypass de personal. Si quieres que los administradores se unan sin el rol, comprueba sus identificadores contra una lista antes de llamar a Discord.
  • Jugadores sin Discord no obtienen ningún identificador. Tu mensaje anterior les dice qué hacer.
  • Para bloquear a un tramposo para siempre, banéalo en lugar de eliminar un rol. Consulta moderación con txAdmin.

Lista de verificación

Síntoma Solución
Sin identificador discord: La aplicación de Discord del jugador debe estar ejecutándose e iniciada sesión
401 de Discord Token de bot incorrecto o reiniciado, o falta el prefijo Bot
403 de Discord El bot no está en ese servidor, o el token pertenece a otro bot
404 de Discord El usuario no está en tu servidor de Discord
429 de Discord Límite de velocidad: cachea resultados y muestra un mensaje de reintento
Token visible para los jugadores Usa set, no setr, y mantenlo fuera de archivos compartidos
Jugador atrapado en «Checking» Cada ruta de código debe terminar con deferrals.done

Respuestas rápidas

¿Necesita el jugador tener Discord abierto para unirse?

Sí. FiveM solo envía el identificador discord: cuando la aplicación de Discord está ejecutándose e iniciada sesión en la PC del jugador. Sin él, el identificador falta y tu script debe mostrar un mensaje claro.

¿Dónde pongo el token del bot?

En un convar establecido en server.cfg o un archivo cfg separado, leído con GetConvar. Nunca lo pongas en un archivo Lua que se comparta, se cargue o se almacene en un repositorio.

¿Llamo a la API de Discord en cada conexión?

Puedes, pero cachea el resultado durante unos minutos. Discord limita las solicitudes y un reinicio con muchos jugadores reconectándose puede golpear el límite.

Scripts que evitan este problema

Tebex TemplateUn tema premium para tu tienda Tebex, sin código y editable desde el panel.Ver script →Mic PhoneUn móvil plegable que se abre en tablet y llega al móvil real del jugador.Ver script →

Sigue leyendo