Discord whitelist para FiveM: permitir jogadores por role Discord

Construa uma whitelist de role Discord para seu servidor FiveM: playerConnecting com deferrals, o identificador Discord, uma verificação de role de bot através da API REST, e um token seguro.

Você quer que apenas jogadores com um certo role Discord entrem: membros, apoiadores, whitelisted. FiveM não tem verificação Discord integrada, então você adiciona uma em playerConnecting: leia o identificador Discord do jogador, pergunte ao Discord por seus roles através de um bot, e aceite ou rejeite a conexão.

Você precisa de três coisas: um bot Discord no seu servidor, um script no servidor FiveM, e um lugar seguro para o token.

Como funciona

  1. Um jogador se conecta. FiveM dispara playerConnecting e lhe dá seus identificadores.
  2. Você pausa a conexão com deferrals (veja playerConnecting and deferrals).
  3. Seu servidor pergunta à API REST do Discord: este usuário está na guild, e quais roles ele tem?
  4. Você chama deferrals.done() para deixá-lo entrar, ou deferrals.done('reason') para rejeitar.

Crie o bot

  1. Abra o portal de desenvolvedor do Discord e crie uma aplicação, depois adicione um Bot a ela.
  2. Copie o token do bot. Trate-o como uma senha.
  3. Nenhum intent privilegiado é necessário para procurar um membro por ID. (O Server Members Intent apenas importa se um bot lista cada membro ou ouve eventos de membro.)
  4. Convide o bot para seu servidor Discord. Não precisa direitos de admin, apenas tem que ser um membro.
  5. No Discord, ative o Developer Mode, depois copie seu server (guild) ID e o role ID do role de whitelist.

Mantenha o token fora do código

Defina como uma convar em server.cfg, ou em um arquivo cfg que você exec e nunca compartilha:

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

Use set, não setr. Uma convar setr é replicada para cada cliente, o que vazaria o token para os jogadores.

Leia-os no script:

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

Atenção: se o token nunca acabar em uma screenshot, uma mensagem de suporte ou um repositório público, resete-o no portal de desenvolvedor imediatamente.

Leia o identificador Discord

Os identificadores de um jogador se parecem com discord:123456789012345678. Faça loop sobre eles e remova o prefixo:

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

Pergunte ao Discord pelos roles

O endpoint é GET https://discord.com/api/v10/guilds/{guild.id}/members/{user.id}, autorizado com Authorization: Bot <token>. A resposta contém um array roles de IDs de role.

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

Um 404 significa que o usuário não é membro do seu servidor Discord. Qualquer outro status, como 429 ou 5xx, é um problema no caminho, não um "não".

O handler de conexão

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)

Salve source em um local primeiro: após Wait, o global source não aponta mais para este jogador.

Rate limits e caching

Discord limita requests de API por rota e globalmente. Em um restart, muitos jogadores se reconectam de uma vez e cada um dispara um request. Duas proteções simples:

  • Cache resultados por alguns minutos por ID Discord, então um jogador que tenta novamente não chama a API novamente.
  • Lide com 429. Discord responde com um header Retry-After. Mostre uma mensagem "try again" e não faça loop nela.
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

Use cachedCheck no handler no lugar de hasWhitelistRole. Um "sim" em cache significa um role removido funciona até cinco minutos mais, o que é bem para uma whitelist.

Outros pontos

  • Não falhe aberto. Se Discord estiver inativo, rejeite com uma mensagem clara em vez de deixar todos entrar, a menos que você tenha decidido que é aceitável.
  • Bypass de staff. Se você quer que admins entrem sem o role, verifique seus identificadores contra uma lista antes de chamar Discord.
  • Jogadores sem Discord obtêm nenhum identificador. Sua mensagem acima os diz o que fazer.
  • Para bloquear um trapaceiro para sempre, bani-o em vez de remover um role. Veja moderating with txAdmin.

Checklist

Sintoma Solução
Sem identificador discord: O app Discord do jogador deve estar rodando e conectado
401 do Discord Token de bot errado ou resetado, ou faltando prefixo Bot
403 do Discord O bot não está naquele servidor, ou o token pertence a outro bot
404 do Discord O usuário não está no seu servidor Discord
429 do Discord Rate limit: cache resultados e mostre uma mensagem de retry
Token visível para jogadores Use set, não setr, e mantenha fora de arquivos compartilhados
Jogador preso em "Checking" Cada caminho de código deve terminar com deferrals.done

Respostas rápidas

O jogador precisa ter Discord aberto para entrar?

Sim. FiveM apenas envia o identificador discord: quando o aplicativo desktop Discord está rodando e conectado no PC do jogador. Sem ele, o identificador está faltando e seu script deve mostrar uma mensagem clara.

Onde coloco o token do bot?

Em uma convar definida em server.cfg ou um arquivo cfg separado, leia com GetConvar. Nunca coloque em um arquivo Lua que é compartilhado, uploadado ou armazenado em um repositório.

Chamo a API Discord a cada conexão?

Você pode, mas cache o resultado por alguns minutos. Discord limita requests e um restart com muitos jogadores se reconectando pode atingir o limite.

Scripts sem esse problema

Tebex TemplateUm tema premium para sua loja Tebex, sem código e editado todo pelo painel da Tebex.Ver script →Mic PhoneUm celular dobrável que abre em tablet e chega ao celular de verdade do jogador.Ver script →

Continue lendo