-- nite_script: verbindet den FiveM-Server mit einem NITE-Server (nite-portal.com) -- Live-Status, Konto-Verknuepfung mit /nite, Whitelist mit Bewerbungen und Rollen-Abgleich. -- Einrichtung: siehe README.md local api = GetConvar('nite_script_api', 'https://nite-portal.com'):gsub('/+$', '') local key = GetConvar('nite_script_key', '') local useAce = GetConvar('nite_script_ace', 'true') ~= 'false' local esxMode = GetConvar('nite_script_esx', 'auto') local headers = { ['Content-Type'] = 'application/json', ['Authorization'] = 'Server ' .. key } local joins, drops, jobs = {}, {}, {} local applied = {} -- license -> { gruppe = true } local accounts = {} -- license -> NITE-Konto aus der letzten Pruefung local ESX = nil local function log(msg) print(('^5[nite_script]^7 %s'):format(msg)) end -- license: bevorzugt, license2: als Ersatz (manche Konten haben nur diese) local function license(src) local fallback for _, id in ipairs(GetPlayerIdentifiers(src)) do if id:sub(1, 8) == 'license:' then return id end if id:sub(1, 9) == 'license2:' then fallback = id end end return fallback end local function call(method, path, body, cb) PerformHttpRequest(api .. '/api/v1' .. path, function(status, text) local ok, data = pcall(json.decode, text or '') if cb then cb(status, ok and data or nil) end end, method, body and json.encode(body) or '', headers) end -- ACE-Gruppen setzen: NITE-Rolle -> group. fuer genau diesen Spieler local function setGroups(lic, list) if not useAce or not lic then return end local want = {} for _, g in ipairs(list or {}) do if type(g) == 'string' and g:match('^[%w_%-]+$') then want[g] = true end end local have = applied[lic] or {} for g in pairs(have) do if not want[g] then ExecuteCommand(('remove_principal identifier.%s group.%s'):format(lic, g)) end end for g in pairs(want) do if not have[g] then ExecuteCommand(('add_principal identifier.%s group.%s'):format(lic, g)) end end applied[lic] = next(want) and want or nil end local function jobOf(src) if not ESX then return nil end local xPlayer = ESX.GetPlayerFromId(src) local job = xPlayer and xPlayer.getJob and xPlayer.getJob() if job and job.name then return job.name:lower(), tonumber(job.grade) or 0 end end local function snapshot() local list = {} for _, src in ipairs(GetPlayers()) do local l = license(src) if l then local job, grade = jobOf(src) list[#list + 1] = { license = l, name = (GetPlayerName(src) or ''):sub(1, 64), job = job, grade = grade } end end return list end local function online() local set = {} for _, src in ipairs(GetPlayers()) do local l = license(src) if l then set[l] = true end end return set end local function applyAll(groups) if type(groups) ~= 'table' then return end for lic in pairs(online()) do setGroups(lic, groups[lic]) end end local function reportJob(src, job) local l = license(src) if l and job and job.name then jobs[#jobs + 1] = { license = l, job = job.name:lower(), grade = tonumber(job.grade) or 0 } end end CreateThread(function() if key == '' then return log('^1nite_script_key fehlt in der server.cfg^7') end if esxMode ~= 'false' and GetResourceState('es_extended') == 'started' then local ok, obj = pcall(function() return exports['es_extended']:getSharedObject() end) if ok and obj then ESX = obj; log('ESX gefunden, Jobs werden an NITE gemeldet') end end if useAce and not IsPrincipalAceAllowed('resource.' .. GetCurrentResourceName(), 'command.add_principal') then log('^3Fuer den Rollen-Abgleich in die server.cfg: add_ace resource.nite_script command.add_principal allow und add_ace resource.nite_script command.remove_principal allow^7') end local max = GetConvarInt('sv_maxclients', 48) call('POST', '/fivem/heartbeat', { players = #GetPlayers(), max = max, sync = snapshot() }, function(status, res) if status == 200 and res then applyAll(res.groups); log('mit NITE verbunden') elseif status == 401 then log('^1Schluessel ungueltig: in NITE unter Servereinstellungen > FiveM neu erstellen^7') else log(('NITE gerade nicht erreichbar (%s)'):format(tostring(status))) end end) while true do Wait(30000) local j, d, b = joins, drops, jobs joins, drops, jobs = {}, {}, {} call('POST', '/fivem/heartbeat', { players = #GetPlayers(), max = max, joins = j, drops = d, jobs = b }, function(status, res) if status == 200 and res then applyAll(res.groups) end end) end end) AddEventHandler('playerJoining', function() local src = source local l = license(src) if l then joins[#joins + 1] = { license = l, name = (GetPlayerName(src) or ''):sub(1, 64) } end end) AddEventHandler('playerDropped', function() local l = license(source) if not l then return end drops[#drops + 1] = l setGroups(l, {}) accounts[l] = nil end) AddEventHandler('playerConnecting', function(name, _, deferrals) local src = source local l = license(src) deferrals.defer() Wait(0) if not l then return deferrals.done('Kein Rockstar-Konto gefunden.') end deferrals.update('NITE prueft deinen Zugang ...') call('POST', '/fivem/connect', { license = l, name = name:sub(1, 64) }, function(status, res) -- Ist NITE nicht erreichbar, kommen Spieler trotzdem rein if status ~= 200 or not res then return deferrals.done() end accounts[l] = res.nite if res.allowed then setGroups(l, res.groups); return deferrals.done() end if res.code then return deferrals.done(('Verknuepfe dein NITE-Konto: Gib den Code %s im NITE-Server unter FiveM ein (10 Minuten gueltig).'):format(res.code)) end local reasons = { not_member = 'Tritt zuerst dem NITE-Server bei.', banned = 'Du bist auf dem NITE-Server gesperrt.', account_disabled = 'Dein NITE-Konto ist gesperrt.', not_whitelisted = 'Du stehst nicht auf der Whitelist. Bewirb dich im NITE-Server unter FiveM.', application_pending = 'Deine Bewerbung wird noch geprueft.', application_denied = 'Deine Bewerbung wurde abgelehnt.' } deferrals.done(reasons[res.reason] or ('Kein Zugang ueber NITE (' .. tostring(res.reason) .. ').')) end) end) -- ESX: Job beim Laden und bei jedem Wechsel melden, NITE vergibt die zugeordneten Rollen AddEventHandler('esx:playerLoaded', function(playerId, xPlayer) if not ESX then return end reportJob(playerId, xPlayer and xPlayer.getJob and xPlayer.getJob() or nil) end) AddEventHandler('esx:setJob', function(playerId, job) if not ESX then return end reportJob(playerId, job) end) RegisterCommand('nite', function(src) local l = src > 0 and license(src) if not l then return end call('POST', '/fivem/connect', { license = l, name = (GetPlayerName(src) or ''):sub(1, 64), want_code = true }, function(status, res) if status ~= 200 or not res then return end local msg = res.linked and 'Dein NITE-Konto ist bereits verknuepft.' or res.code and ('Dein NITE-Code: ' .. res.code .. ' (10 Minuten gueltig). Im NITE-Server unter FiveM eingeben.') or 'Verknuepfen ist gerade nicht moeglich.' TriggerClientEvent('chat:addMessage', src, { args = { 'NITE', msg } }) end) end, false) -- Fuer andere Ressourcen: exports.nite_script:GetNiteAccount(source) -> { user_id, username, display_name } oder nil exports('GetNiteAccount', function(src) local l = license(src) return l and accounts[l] or nil end) AddEventHandler('onResourceStop', function(name) if name ~= GetCurrentResourceName() then return end for lic in pairs(applied) do setGroups(lic, {}) end end)