Discord whitelist для FiveM: разрешите игроков по роли Discord

Постройте Discord whitelist роль для вашего FiveM сервера: playerConnecting с deferrals, Discord идентификатор, проверка ролей бота через REST API и безопасный токен.

Вы хотите, чтобы только игроки с определённой ролью Discord присоединились: члены, сторонники, whitelisted. FiveM не имеет встроенной проверки Discord, поэтому вы добавляете её в playerConnecting: читайте Discord идентификатор игрока, просите Discord их роли через бота, и принимайте или отклоняйте соединение.

Вам нужны три вещи: Discord бот на вашем сервере, скрипт на FiveM сервере и безопасное место для токена.

Как это работает

  1. Игрок присоединяется. FiveM срабатывает playerConnecting и даёт вам их идентификаторы.
  2. Вы приостанавливаете соединение с deferrals (см. playerConnecting and deferrals).
  3. Ваш сервер спрашивает Discord REST API: этот пользователь в гильдии, и какие роли они имеют?
  4. Вы вызываете deferrals.done() чтобы их пустить, или deferrals.done('reason') чтобы отклонить.

Создайте бота

  1. Откройте Discord портал разработчика и создайте приложение, затем добавьте Bot к нему.
  2. Скопируйте токен бота. Обращайтесь с ним как с паролем.
  3. Привилегированное намерение не требуется для поиска одного члена по ID. (Server Members Intent имеет значение только, если бот списывает каждого члена или слушает события членов.)
  4. Пригласите бота на ваш Discord сервер. Ему не требуются права администратора, он только должен быть членом.
  5. В Discord включите Developer Mode, затем скопируйте свой server (guild) ID и role ID роли whitelist.

Держите токен вне кода

Установите это как convar в server.cfg, или в cfg файл, который вы exec и никогда не делитесь:

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

Используйте set, не setr. Convar setr реплицируется каждому клиенту, что утекла бы токен игрокам.

Читайте их в скрипте:

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

Внимание: если токен когда-либо заканчивает в скриншоте, сообщении поддержки или общественном репозитории, переустановите его на портале разработчика прямо сейчас.

Читайте Discord идентификатор

Идентификаторы игрока выглядят как discord:123456789012345678. Зациклите их и очистите префикс:

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

Просите Discord за роли

Конечная точка — GET https://discord.com/api/v10/guilds/{guild.id}/members/{user.id}, авторизованная с Authorization: Bot <token>. Ответ содержит массив roles ID ролей.

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

404 означает, что пользователь не является членом вашего Discord сервера. Любой другой статус, такой как 429 или 5xx, — это проблема на пути, не «нет».

Обработчик соединения

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)

Сохраняйте source в локальной первой: после Wait, глобальный source больше не указывает на этого игрока.

Лимиты и кэширование

Discord ограничивает запросы API за маршрут и глобально. При перезагрузке, много игроков переподключается одновременно и каждый из них срабатывает запрос. Две простые защиты:

  • Кэшируйте результаты на несколько минут на Discord ID, поэтому игрок, который повторит попытку, не вызовет API снова.
  • Обработайте 429. Discord отвечает с заголовком Retry-After. Показывайте сообщение «попробуйте снова» и не зациклите это.
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

Используйте cachedCheck в обработчике вместо hasWhitelistRole. Кэшированное «да» означает удалённую роль, работает до пяти минут дольше, что хорошо для whitelist.

Другие моменты

  • Не терпите неудачу открытой. Если Discord выключен, отклоните ясным сообщением вместо того, чтобы все пустить, пока вы не решили, что это приемлемо.
  • Обход персонала. Если вы хотите администраторов присоединиться без роли, проверьте их идентификаторы против списка перед вызовом Discord.
  • Игроки без Discord получают никакой идентификатор. Ваше сообщение выше сообщает им, что делать.
  • Чтобы заблокировать читера навсегда, запретите их вместо удаления роли. См. moderating with txAdmin.

Контрольный список

Проблема Решение
Нет discord: идентификатора Приложение Discord игрока должно работать и быть залогировано
401 из Discord Неправильный или переустановленный токен бота, или отсутствующий префикс Bot
403 из Discord Бот не на этом сервере, или токен принадлежит другому боту
404 из Discord Пользователь не на вашем Discord сервере
429 из Discord Лимит: кэшируйте результаты и показывайте сообщение повтора
Токен видимый игрокам Используйте set, не setr, и держите это вне общих файлов
Игрок застрял на «Checking» Каждый путь кода должен заканчиваться deferrals.done

Короткие ответы

Нужно ли игроку иметь Discord открытым для присоединения?

Да. FiveM отправляет только discord: идентификатор, когда приложение Discord открыто и залогировано на ПК игрока. Без этого идентификатор отсутствует и ваш скрипт должен показать ясное сообщение.

Где мне положить токен бота?

В convar, установленный в server.cfg или отдельный cfg файл, читаемый с GetConvar. Никогда не кладите это в Lua файл, который общий, загруженный или хранится в репозитории.

Я вызываю Discord API при каждом подключении?

Вы можете, но кэшируйте результат на несколько минут. Discord ограничивает запросы и перезагрузка со множеством игроков, переподключающихся, может поразить предел.

Скрипты без этой проблемы

Tebex TemplateПремиальная тема для вашего магазина Tebex без кода, полностью настраиваемая из панели Tebex.Смотреть скрипт →Mic PhoneСкладной телефон, который раскладывается в планшет и работает и на настоящем телефоне игрока.Смотреть скрипт →

Читайте также