138 lines
5 KiB
Lua
138 lines
5 KiB
Lua
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|||
|
|
-- ic-web – ESX-Bridge, Rechte und Logging
|
|||
|
|
--
|
|||
|
|
-- Alle Framework-Zugriffe zentral. ESX wird bei Bedarf geholt, nicht beim
|
|||
|
|
-- Laden: beim Start ist es je nach Reihenfolge noch nicht bereit, und ein
|
|||
|
|
-- einmal fehlgeschlagener Versuch haette die Bridge dauerhaft lahmgelegt.
|
|||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
ICWeb = ICWeb or {}
|
|||
|
|
ICWeb.FW = {}
|
|||
|
|
local FW = ICWeb.FW
|
|||
|
|
|
|||
|
|
local ESX
|
|||
|
|
|
|||
|
|
local function esx()
|
|||
|
|
if ESX then return ESX end
|
|||
|
|
local ok, obj = pcall(function() return exports['es_extended']:getSharedObject() end)
|
|||
|
|
if ok and obj then ESX = obj end
|
|||
|
|
return ESX
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local function player(src)
|
|||
|
|
local api = esx()
|
|||
|
|
return api and api.GetPlayerFromId(src) or nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
function FW.GetIdentifier(src)
|
|||
|
|
local xPlayer = player(src)
|
|||
|
|
return xPlayer and xPlayer.identifier or nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
function FW.GetName(src)
|
|||
|
|
local xPlayer = player(src)
|
|||
|
|
if not xPlayer then return GetPlayerName(src) or 'Unbekannt' end
|
|||
|
|
if xPlayer.getName then return xPlayer.getName() end
|
|||
|
|
return xPlayer.name or 'Unbekannt'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Job und Dienstgrad. Beides wird fuer die Registrierung gebraucht.
|
|||
|
|
function FW.GetJob(src)
|
|||
|
|
local xPlayer = player(src)
|
|||
|
|
if not xPlayer or not xPlayer.job then return nil, 0 end
|
|||
|
|
return xPlayer.job.name, tonumber(xPlayer.job.grade) or 0
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Jobliste fuer die Adminansicht. Der Job entscheidet nicht mehr ueber Rechte,
|
|||
|
|
-- er steht nur noch als Besitzervermerk an einer Domaene.
|
|||
|
|
function FW.GetJobs()
|
|||
|
|
local api = esx()
|
|||
|
|
if not api or not api.GetJobs then return {} end
|
|||
|
|
|
|||
|
|
local out = {}
|
|||
|
|
for name, job in pairs(api.GetJobs()) do
|
|||
|
|
out[#out + 1] = { name = name, label = job.label or name }
|
|||
|
|
end
|
|||
|
|
table.sort(out, function(a, b) return a.label < b.label end)
|
|||
|
|
return out
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
function FW.Notify(src, message)
|
|||
|
|
local xPlayer = player(src)
|
|||
|
|
if xPlayer and xPlayer.showNotification then
|
|||
|
|
xPlayer.showNotification(message)
|
|||
|
|
return
|
|||
|
|
end
|
|||
|
|
TriggerClientEvent('esx:showNotification', src, message)
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- ── Adminrechte ─────────────────────────────────────────────────────────────
|
|||
|
|
function FW.IsAdmin(src)
|
|||
|
|
if not src or src == 0 then return true end -- Serverkonsole
|
|||
|
|
|
|||
|
|
local mode = Config.Admin.mode or 'either'
|
|||
|
|
|
|||
|
|
local okEsx = false
|
|||
|
|
local xPlayer = player(src)
|
|||
|
|
if xPlayer and xPlayer.getGroup then
|
|||
|
|
okEsx = Config.Admin.esxGroups[xPlayer.getGroup()] == true
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local okAce = IsPlayerAceAllowed(src, Config.Admin.acePermission) == true
|
|||
|
|
|
|||
|
|
if mode == 'esx' then return okEsx end
|
|||
|
|
if mode == 'ace' then return okAce end
|
|||
|
|
if mode == 'both' then return okEsx and okAce end
|
|||
|
|
return okEsx or okAce
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- ── Logging ─────────────────────────────────────────────────────────────────
|
|||
|
|
-- Ausschliesslich ueber fmsdk. Fehlt es, wird verworfen statt zu crashen.
|
|||
|
|
ICWeb.Log = {}
|
|||
|
|
|
|||
|
|
function ICWeb.Log.Write(src, action, details, level)
|
|||
|
|
if GetResourceState('fmsdk') ~= 'started' then
|
|||
|
|
if Config.Debug then
|
|||
|
|
print(('[ic-web] fmsdk fehlt, Log verworfen: %s'):format(tostring(action)))
|
|||
|
|
end
|
|||
|
|
return false
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local meta = details or {}
|
|||
|
|
meta.action = (Config.Logging.actionPrefix or 'ic_web.') .. tostring(action)
|
|||
|
|
meta.resource = GetCurrentResourceName()
|
|||
|
|
meta.playerSource = src or nil
|
|||
|
|
meta.identifier = src and FW.GetIdentifier(src) or nil
|
|||
|
|
|
|||
|
|
local ok = pcall(function()
|
|||
|
|
exports.fmsdk:Log(Config.Logging.dataset or 'ic_web', level or 'info',
|
|||
|
|
('Webhosting: %s'):format(meta.action), meta)
|
|||
|
|
end)
|
|||
|
|
return ok
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- ── Drossel ─────────────────────────────────────────────────────────────────
|
|||
|
|
-- Jedes RegisterNetEvent ist frei aufrufbar.
|
|||
|
|
local buckets = {}
|
|||
|
|
|
|||
|
|
function ICWeb.ThrottleOk(src, name, maxPerWindow, windowMs)
|
|||
|
|
local now = GetGameTimer()
|
|||
|
|
local key = ('%s:%s'):format(src, name)
|
|||
|
|
local entry = buckets[key]
|
|||
|
|
|
|||
|
|
if not entry or (now - entry.start) > windowMs then
|
|||
|
|
buckets[key] = { start = now, count = 1 }
|
|||
|
|
return true
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
entry.count = entry.count + 1
|
|||
|
|
return entry.count <= maxPerWindow
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
AddEventHandler('playerDropped', function()
|
|||
|
|
local prefix = tostring(source) .. ':'
|
|||
|
|
for key in pairs(buckets) do
|
|||
|
|
if key:sub(1, #prefix) == prefix then buckets[key] = nil end
|
|||
|
|
end
|
|||
|
|
end)
|