Discord whitelist для FiveM: разрешите игроков по роли Discord
Постройте Discord whitelist роль для вашего FiveM сервера: playerConnecting с deferrals, Discord идентификатор, проверка ролей бота через REST API и безопасный токен.
Вы хотите, чтобы только игроки с определённой ролью Discord присоединились: члены, сторонники, whitelisted. FiveM не имеет встроенной проверки Discord, поэтому вы добавляете её в playerConnecting: читайте Discord идентификатор игрока, просите Discord их роли через бота, и принимайте или отклоняйте соединение.
Вам нужны три вещи: Discord бот на вашем сервере, скрипт на FiveM сервере и безопасное место для токена.
Как это работает
- Игрок присоединяется. FiveM срабатывает
playerConnectingи даёт вам их идентификаторы. - Вы приостанавливаете соединение с deferrals (см. playerConnecting and deferrals).
- Ваш сервер спрашивает Discord REST API: этот пользователь в гильдии, и какие роли они имеют?
- Вы вызываете
deferrals.done()чтобы их пустить, илиdeferrals.done('reason')чтобы отклонить.
Создайте бота
- Откройте Discord портал разработчика и создайте приложение, затем добавьте Bot к нему.
- Скопируйте токен бота. Обращайтесь с ним как с паролем.
- Привилегированное намерение не требуется для поиска одного члена по ID. (Server Members Intent имеет значение только, если бот списывает каждого члена или слушает события членов.)
- Пригласите бота на ваш Discord сервер. Ему не требуются права администратора, он только должен быть членом.
- В Discord включите Developer Mode, затем скопируйте свой server (guild) ID и role ID роли whitelist.
Держите токен вне кода
Установите это как convar в server.cfg, или в cfg файл, который вы exec и никогда не делитесь:
set discord_bot_token "paste-token-here"
set discord_guild_id "123456789012345678"
set discord_whitelist_role "123456789012345679"Используйте set, не setr. Convar setr реплицируется каждому клиенту, что утекла бы токен игрокам.
Читайте их в скрипте:
local TOKEN = GetConvar('discord_bot_token', '')
local GUILD = GetConvar('discord_guild_id', '')
local ROLE = GetConvar('discord_whitelist_role', '')Внимание: если токен когда-либо заканчивает в скриншоте, сообщении поддержки или общественном репозитории, переустановите его на портале разработчика прямо сейчас.
Читайте Discord идентификатор
Идентификаторы игрока выглядят как discord:123456789012345678. Зациклите их и очистите префикс:
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 ролей.
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 }
)
end404 означает, что пользователь не является членом вашего Discord сервера. Любой другой статус, такой как 429 или 5xx, — это проблема на пути, не «нет».
Обработчик соединения
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. Показывайте сообщение «попробуйте снова» и не зациклите это.
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 ограничивает запросы и перезагрузка со множеством игроков, переподключающихся, может поразить предел.

