diff --git a/client/gui_c.lua b/client/gui_c.lua index cb6c22d1..fe510525 100644 --- a/client/gui_c.lua +++ b/client/gui_c.lua @@ -827,9 +827,70 @@ function GenerateMenu() -- this is a big ass function TriggerEvent("EasyAdmin:showNotification", GetLocalisedText("nodiscordpresent")) end end + + if GetConvar("ea_enableActionHistory", "true") == "true" and permissions["player.actionhistory.view"] then + local actionHistoryMenu = _menuPool:AddSubMenu(thisPlayer, GetLocalisedText("actionhistory"), GetLocalisedText("actionhistoryguide"), true) + actionHistoryMenu:SetMenuWidthOffset(menuWidth) + local loadingItem = NativeUI.CreateItem(GetLocalisedText("actionsloading"), GetLocalisedText("actionsloadingguide")) + actionHistoryMenu:AddItem(loadingItem) + TriggerServerEvent("EasyAdmin:GetActionHistory", thePlayer.discord) + + RegisterNetEvent("EasyAdmin:ReceiveActionHistory") + AddEventHandler("EasyAdmin:ReceiveActionHistory", function(actionHistory) + actionHistoryMenu:Clear() + if #actionHistory == 0 then + local noActionsItem = NativeUI.CreateItem(GetLocalisedText("noactions"), GetLocalisedText("noactionsguide")) + actionHistoryMenu:AddItem(noActionsItem) + end + for i, action in ipairs(actionHistory) do + local actionSubmenu = _menuPool:AddSubMenu(actionHistoryMenu, "[#"..action.id.."] " .. action.action .. " by " .. action.moderator, GetLocalisedText("reason") .. ": " .. action.reason or "", true) + actionSubmenu:SetMenuWidthOffset(menuWidth) + if action.action == "BAN" and permissions["player.ban.remove"] then + local actionUnban = NativeUI.CreateItem(GetLocalisedText("unbanplayer"), GetLocalisedText("unbanplayerguide")) + actionUnban.Activated = function(ParentMenu, SelectedItem) + TriggerServerEvent("EasyAdmin:UnbanPlayer", action.id) + TriggerEvent("EasyAdmin:showNotification", GetLocalisedText("unbanplayer")) + TriggerServerEvent("EasyAdmin:GetActionHistory", thePlayer.discord) + ParentMenu:Visible(false) + ParentMenu.ParentMenu:Visible(true) + end + actionSubmenu:AddItem(actionUnban) + end + if permissions["player.actionhistory.delete"] then + local actionDelete = NativeUI.CreateItem(GetLocalisedText("deleteaction"), GetLocalisedText("deleteactionguide")) + actionDelete.Activated = function(ParentMenu, SelectedItem) + TriggerServerEvent("EasyAdmin:DeleteAction", action.id) + TriggerEvent("EasyAdmin:showNotification", GetLocalisedText("actiondeleted")) + TriggerServerEvent("EasyAdmin:GetActionHistory", thePlayer.discord) + ParentMenu:Visible(false) + ParentMenu.ParentMenu:Visible(true) + end + actionSubmenu:AddItem(actionDelete) + end + local punishedDiscord = NativeUI.CreateItem(GetLocalisedText("getplayerdiscord"), GetLocalisedText("getplayerdiscordguide")) + punishedDiscord.Activated = function(ParentMenu, SelectedItem) + if action.discord then + copyToClipboard(action.discord) + else + TriggerEvent("EasyAdmin:showNotification", GetLocalisedText("nodiscordpresent")) + end + end + actionSubmenu:AddItem(punishedDiscord) + local moderatorDiscord = NativeUI.CreateItem(GetLocalisedText("getmoderatordiscord"), GetLocalisedText("getmoderatordiscordguide")) + moderatorDiscord.Activated = function(ParentMenu, SelectedItem) + if action.moderatorId then + copyToClipboard(action.moderatorId) + else + TriggerEvent("EasyAdmin:showNotification", GetLocalisedText("nodiscordpresent")) + end + end + actionSubmenu:AddItem(moderatorDiscord) + actionSubmenu:RefreshIndex() + end + end) + end ExecutePluginsFunction("playerMenu", thePlayer.id) - if GetResourceState("es_extended") == "started" and not ESX then local thisItem = NativeUI.CreateItem("~y~[ESX]~s~ Options","You can buy the ESX Plugin from https://blumlaut.tebex.io to use this Feature.") @@ -1755,4 +1816,4 @@ function DrawPlayerInfoLoop() end end) -end +end \ No newline at end of file diff --git a/fxmanifest.lua b/fxmanifest.lua index 89d7c441..6e3de5a9 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -16,6 +16,7 @@ node_version '22' shared_script 'shared/util_shared.lua' server_scripts { + "server/storage.lua", "server/*.lua", "dist/*.js", "plugins/**/*_shared.lua", @@ -87,5 +88,7 @@ convar_category 'EasyAdmin' { { "Channel for Discord bot to enable live status", "$ea_botStatusChannel", "CV_STRING", "true" }, { "Enable Allowlist", "$ea_enableAllowlist", "CV_BOOL", "false" }, { "Routing Bucket Options", "$ea_routingBucketOptions", "CV_BOOL", "false" }, + { "Enable Action History", "$ea_enableActionHistory", "CV_BOOL", "true" }, + { "Action History Expiry", "$ea_actionHistoryExpiry", "CV_INT", "30" }, -- Recommended time is 30 days } } \ No newline at end of file diff --git a/language/en.json b/language/en.json index 905f3d97..9b97e3b4 100644 --- a/language/en.json +++ b/language/en.json @@ -115,6 +115,20 @@ "bucketguide": "Forces either player or yourself into the other's routing bucket.", "playerbucketjoined": "Joined player bucket.", "playerbucketforced": "Player has been forced to your bucket.", + + "actionhistory": "Action History", + "actionhistoryguide": "View moderation actions against this user.", + "actionsloading": "Loading...", + "actionsloadingguide": "Please wait while we fetch the data.", + "noactions": "No actions found.", + "noactionsguide": "No past moderation actions for this user.", + "deleteaction": "Delete Action", + "deleteactionguide": "Delete this action from the history.", + "actiondeleted": "Action deleted.", + "getplayerdiscord": "Copy Discord of Player", + "getplayerdiscordguide": "Copy the Discord ID of the Player.", + "getmoderatordiscord": "Copy Discord of Moderator", + "getmoderatordiscordguide": "Copy the Discord ID of the Moderator.", "copydiscord": "Copy Discord ID", "discordcopied": "Discord ID copied to clipboard.", diff --git a/server/action_history.lua b/server/action_history.lua new file mode 100644 index 00000000..40a6b9fb --- /dev/null +++ b/server/action_history.lua @@ -0,0 +1,176 @@ +------------------------------------ +------------------------------------ +---- DONT TOUCH ANY OF THIS IF YOU DON'T KNOW WHAT YOU ARE DOING +---- THESE ARE **NOT** CONFIG VALUES, USE THE CONVARS IF YOU WANT TO CHANGE SOMETHING +---- +---- +---- If you are a developer and want to change something, consider writing a plugin instead: +---- https://easyadmin.readthedocs.io/en/latest/plugins/ +---- +------------------------------------ +------------------------------------ + +local actions = {} + +moderationNotification = GetConvar("ea_moderationNotification", "false") +reportNotification = GetConvar("ea_reportNotification", "false") +detailNotification = GetConvar("ea_detailNotification", "false") +minimumMatchingIdentifierCount = GetConvarInt("ea_minIdentifierMatches", 2) + +RegisterNetEvent("EasyAdmin:GetActionHistory", function(discordId) + if DoesPlayerHavePermission(source, "player.actionhistory.view") then + if not discordId then + PrintDebugMessage("No Discord ID provided, returning empty action history.", 2) + TriggerClientEvent("EasyAdmin:ReceiveActionHistory", source, {}) + return + end + local history = Storage.getAction(discordId) + TriggerClientEvent("EasyAdmin:ReceiveActionHistory", source, history) + else + PrintDebugMessage("Player does not have permission to view action history.", 2) + TriggerClientEvent("EasyAdmin:ReceiveActionHistory", source, {}) + end +end) + +RegisterNetEvent("EasyAdmin:LogAction", function(action) + if DoesPlayerHavePermission(source, "player.actionhistory.add") then + if not action then + PrintDebugMessage("Action not defined.", 2) + end + Storage.addAction(action.type, action.discordId, action.reason, action.moderator, action.moderatorId, action.expire, action.expireString) + PrintDebugMessage("Action logged successfully.", 2) + end +end) + +RegisterNetEvent("EasyAdmin:DeleteAction", function(actionId) + if DoesPlayerHavePermission(source, "player.actionhistory.delete") then + if not actionId then + PrintDebugMessage("Invalid parameters provided for action deletion.", 2) + return + end + Storage.removeAction(actionId) + PrintDebugMessage("Action deleted successfully.", 2) + local preferredWebhook = detailNotification ~= "false" and detailNotification or moderationNotification + SendWebhookMessage(preferredWebhook, string.format(GetLocalisedText("actionhistorydeleted"), getName(source, false, true), actionId), "", 16777214) + else + PrintDebugMessage("Player does not have permission to delete actions.", 2) + end +end) + +-- MOVED TO STORAGE.LUA + +-- AddEventHandler("EasyAdmin:LogAction", function(data, remove, forceChange) +-- if GetConvar("ea_enableActionHistory", "true") == "true" then +-- local change = (forceChange or false) +-- local content = LoadResourceFile(GetCurrentResourceName(), "actions.json") +-- if not content then +-- PrintDebugMessage("actions.json file was missing, we created a new one.", 2) +-- local saved = SaveResourceFile(GetCurrentResourceName(), "actions.json", json.encode({}), -1) +-- if not saved then +-- PrintDebugMessage("^1Saving actions.json failed! Please check if EasyAdmin has Permission to write in its own folder!^7", 1) +-- end +-- content = json.encode({}) +-- end +-- actions = json.decode(content) + +-- if not actions then +-- PrintDebugMessage("^1-^2-^3-^4-^5-^6-^8-^9-^1-^2-^3-^4-^5-^6-^8-^9-^1-^2-^3-^3!^1FATAL ERROR^3!^3-^2-^1-^9-^8-^6-^5-^4-^3-^2-^1-^9-^8-^6-^5-^4-^3-^2-^7\n") +-- PrintDebugMessage("^1Failed^7 to load Actions!\n") +-- PrintDebugMessage("Please check your actions file for errors, ^Action history *will not* work!^7\n") +-- PrintDebugMessage("^1-^2-^3-^4-^5-^6-^8-^9-^1-^2-^3-^4-^5-^6-^8-^9-^1-^2-^3-^3!^1FATAL ERROR^3!^3-^2-^1-^9-^8-^6-^5-^4-^3-^2-^1-^9-^8-^6-^5-^4-^3-^2-^7\n") +-- return +-- end + +-- if data and not remove then +-- if data.action == "BAN" then +-- table.insert(actions, { +-- time = os.time(), +-- id = #actions + 1, +-- banId = data.banId, +-- action = data.action, +-- discord = data.discord, +-- reason = data.reason, +-- moderator = data.moderator, +-- moderatorId = data.moderatorId, +-- expire = data.expire, +-- expireString = data.expireString +-- }) +-- elseif data.action == "OFFLINE BAN" then +-- table.insert(actions, { +-- time = os.time(), +-- id = #actions + 1, +-- banId = data.banId, +-- action = data.action, +-- discord = data.discord, +-- reason = data.reason, +-- moderator = data.moderator, +-- moderatorId = data.moderatorId, +-- expire = data.expire, +-- expireString = data.expireString +-- }) +-- elseif data.action == "KICK" then +-- table.insert(actions, { +-- time = os.time(), +-- id = #actions + 1, +-- action = data.action, +-- discord = data.discord, +-- reason = data.reason, +-- moderator = data.moderator, +-- moderatorId = data.moderatorId, +-- }) +-- elseif data.action == "WARN" then +-- table.insert(actions, { +-- time = os.time(), +-- id = #actions + 1, +-- action = data.action, +-- discord = data.discord, +-- reason = data.reason, +-- moderator = data.moderator, +-- moderatorId = data.moderatorId, +-- }) +-- elseif data.action == "UNBAN" then +-- for i, act in ipairs(actions) do +-- if act.banId == data.banId then +-- act["action"] = data.action +-- break +-- end +-- end +-- end +-- PrintDebugMessage("Added the following to actions:\n"..table_to_string(data), 4) +-- change=true +-- elseif not data then +-- return +-- end +-- if data and remove then +-- PrintDebugMessage("Removed the following data from actions:\n"..table_to_string(data), 4) +-- change = true +-- end +-- if change then +-- PrintDebugMessage("Actions changed, saving..", 4) +-- local saved = SaveResourceFile(GetCurrentResourceName(), "actions.json", json.encode(actions, {indent = true}), -1) +-- if not saved then +-- PrintDebugMessage("^1Saving actions.json failed! Please check if EasyAdmin has Permission to write in its own folder!^7", 1) +-- end +-- end +-- PrintDebugMessage("Completed Actions Updated.", 4) +-- end +-- end) + +for i, action in ipairs(actions) do + if action.time + (GetConvar("ea_actionHistoryExpiry", 30) * 24 * 60 * 60) < os.time() then + table.remove(actions, i) + PrintDebugMessage("Removed expired action: " .. json.encode(action), 4) + end +end + +local change = (forceChange or false) +local content = LoadResourceFile(GetCurrentResourceName(), "actions.json") +if not content then + PrintDebugMessage("actions.json file was missing, we created a new one.", 2) + local saved = SaveResourceFile(GetCurrentResourceName(), "actions.json", json.encode({}), -1) + if not saved then + PrintDebugMessage("^1Saving actions.json failed! Please check if EasyAdmin has Permission to write in its own folder!^7", 1) + end + content = json.encode({}) +end +actions = json.decode(content) \ No newline at end of file diff --git a/server/admin_server.lua b/server/admin_server.lua index 972b5a36..4e88b368 100644 --- a/server/admin_server.lua +++ b/server/admin_server.lua @@ -10,7 +10,6 @@ ------------------------------------ ------------------------------------ - -- Cooldowns for Admin Actions AdminCooldowns = {} @@ -325,6 +324,14 @@ Citizen.CreateThread(function() reason = formatShortcuts(reason) SendWebhookMessage(moderationNotification,string.format(GetLocalisedText("adminkickedplayer"), getName(source, false, true), getName(playerId, true, true), reason), "kick", 16711680) PrintDebugMessage("Kicking Player "..getName(source, true).." for "..reason, 3) + if GetConvar("ea_enableActionHistory", "true") == "true" then + local playerDiscord = GetPlayerIdentifierByType(playerId, 'discord') + local discordId + if playerDiscord then + discordId = playerDiscord:match("discord:(%d+)") + TriggerEvent("EasyAdmin:LogAction", { action = "kick", license = discordId, reason = reason, banner = getName(source, true, true)}) + end + end DropPlayer(playerId, string.format(GetLocalisedText("kicked"), getName(source), reason) ) elseif CachedPlayers[playerId].immune then TriggerClientEvent("EasyAdmin:showNotification", source, GetLocalisedText("adminimmune")) @@ -802,11 +809,16 @@ Citizen.CreateThread(function() }) TriggerClientEvent("txcl:showWarning", id, getName(src), string.format(GetLocalisedText("warned"), reason, WarnedPlayers[id].warns, maxWarnings), GetLocalisedText("warnedtitle"), GetLocalisedText("warnedby"),GetLocalisedText("warndismiss")) SendWebhookMessage(moderationNotification,string.format(GetLocalisedText("adminwarnedplayer"), getName(src, false, true), getName(id, true, true), reason, WarnedPlayers[id].warns, maxWarnings), "warn", 16711680) + local test_name = getName(source, true, false) + -- local warn = { action = "WARN", discord = CachedPlayers[id].discord, reason = reason, moderator = test_name, moderatorId = CachedPlayers[source].discord } + Storage.addAction("WARN", CachedPlayers[id].discord, reason, test_name, CachedPlayers[source].discord) + -- TriggerEvent("EasyAdmin:LogAction", { action = "WARN", discord = CachedPlayers[id].discord, reason = reason, moderator = getName(source, true, false), moderatorId = CachedPlayers[source].discord }) if WarnedPlayers[id].warns >= maxWarnings then if GetConvar("ea_warnAction", "kick") == "kick" then SendWebhookMessage(moderationNotification,string.format(GetLocalisedText("adminkickedplayer"), getName(src, false, true), getName(id, true, true), reason), "kick", 16711680) DropPlayer(id, GetLocalisedText("warnkicked")) WarnedPlayers[id] = nil + TriggerEvent("EasyAdmin:LogAction", {action = "KICK", discord = CachedPlayers[id].discord, reason = "Reached maximum warnings", moderator = "Server", moderatorId = 0}) elseif GetConvar("ea_warnAction", "kick") == "ban" then local bannedIdentifiers = CachedPlayers[id].identifiers or getAllPlayerIdentifiers(id) local bannedUsername = CachedPlayers[id].name or getName(id, true) @@ -815,8 +827,7 @@ Citizen.CreateThread(function() reason = GetLocalisedText("warnbanned").. string.format(GetLocalisedText("reasonadd"), CachedPlayers[id].name, getName(source, true) ) local ban = {banid = GetFreshBanId(), name = bannedUsername,identifiers = bannedIdentifiers, banner = getName(source, true), reason = reason, expire = expires } updateBlacklist( ban ) - - + TriggerEvent("EasyAdmin:LogAction", {action = "BAN", discord = CachedPlayers[id].discord, reason = "Reached maximum warnings", moderator = "Server", moderatorId = 0}) PrintDebugMessage("Player "..getName(source,true).." warnbanned player "..CachedPlayers[id].name.." for "..reason, 3) SendWebhookMessage(moderationNotification,string.format(GetLocalisedText("adminbannedplayer"), getName(source, false, true), bannedUsername, reason, formatDateString( expires ), tostring(ban.banid) ), "ban", 16711680) @@ -966,6 +977,7 @@ Citizen.CreateThread(function() local matchingIdentifierCount = 0 local matchingIdentifiers = {} local showProgress = GetConvar("ea_presentDeferral", "true") + local blacklist = Storage.getBanList() deferrals.defer() Wait(0) diff --git a/server/banlist.lua b/server/banlist.lua index a04b8f57..cee7b64a 100644 --- a/server/banlist.lua +++ b/server/banlist.lua @@ -33,10 +33,13 @@ RegisterServerEvent("EasyAdmin:banPlayer", function(playerId,reason,expires) end reason = formatShortcuts(reason).. string.format(GetLocalisedText("reasonadd"), CachedPlayers[playerId].name, getName(source) ) - local ban = {banid = GetFreshBanId(), name = username,identifiers = bannedIdentifiers, banner = getName(source, true), reason = reason, expire = expires, expireString = formatDateString(expires) } - updateBlacklist( ban ) + local banId = GetFreshBanId() + -- local ban = {banid = GetFreshBanId(), name = username,identifiers = bannedIdentifiers, banner = getName(source, true), reason = reason, expire = expires, expireString = formatDateString(expires), action = "BAN", time = os.time() } + Storage.addBan(banId, username, bannedIdentifiers, getName(source), reason, expires, formatDateString(expires), "BAN", os.time()) + Storage.addAction("BAN", CachedPlayers[playerId].discord, reason, getName(source), CachedPlayers[source].discord) + -- updateBlacklist( ban ) PrintDebugMessage("Player "..getName(source,true).." banned player "..CachedPlayers[playerId].name.." for "..reason, 3) - SendWebhookMessage(moderationNotification,string.format(GetLocalisedText("adminbannedplayer"), getName(source, false, true), CachedPlayers[playerId].name, reason, formatDateString( expires ), tostring(ban.banid) ), "ban", 16711680) + SendWebhookMessage(moderationNotification,string.format(GetLocalisedText("adminbannedplayer"), getName(source, false, true), CachedPlayers[playerId].name, reason, formatDateString( expires ), tostring(banId) ), "ban", 16711680) DropPlayer(playerId, string.format(GetLocalisedText("banned"), reason, formatDateString( expires ) ) ) elseif CachedPlayers[playerId].immune then TriggerClientEvent("EasyAdmin:showNotification", source, GetLocalisedText("adminimmune")) @@ -65,8 +68,11 @@ RegisterServerEvent("EasyAdmin:offlinebanPlayer", function(playerId,reason,expir end reason = formatShortcuts(reason).. string.format(GetLocalisedText("reasonadd"), CachedPlayers[playerId].name, getName(source) ) - local ban = {banid = GetFreshBanId(), name = username,identifiers = bannedIdentifiers, banner = getName(source), reason = reason, expire = expires } - updateBlacklist( ban ) + --local ban = {banid = GetFreshBanId(), name = username,identifiers = bannedIdentifiers, banner = getName(source), reason = reason, expire = expires, action = "OFFLINE BAN", time = os.time() } + Storage.addBan(GetFreshBanId(), username, bannedIdentifiers, getName(source), reason, expires, formatDateString(expires), "OFFLINE BAN", os.time()) + Storage.addAction("OFFLINE BAN", CachedPlayers[playerId].discord, reason, getName(source), CachedPlayers[source].discord) + -- updateBlacklist( ban ) + -- TriggerEvent("EasyAdmin:LogAction", ban) PrintDebugMessage("Player "..getName(source,true).." offline banned player "..CachedPlayers[playerId].name.." for "..reason, 3) SendWebhookMessage(moderationNotification,string.format(GetLocalisedText("adminofflinebannedplayer"), getName(source, false, true), CachedPlayers[playerId].name, reason, formatDateString( expires ) ), "ban", 16711680) end @@ -79,6 +85,7 @@ AddEventHandler('banCheater', function(playerId,reason) Citizen.Trace("^1EasyAdmin^7: the banCheater event is ^1deprecated^7 and has been removed! Please adjust your ^3"..GetInvokingResource().."^7 Resource to use EasyAdmin:addBan instead.") end) + ---Adds a ban to the banlist, either for an online or offline player ---@param playerId number|string|table @The ID of the player or a table of identifiers if the player is offline ---@param reason string @The reason for the ban @@ -104,7 +111,6 @@ function addBanExport(playerId,reason,expires,banner) PrintDebugMessage("Couldn't find any Infos about Player "..playerId..", no ban issued.", 1) return false end - if expires and expires < os.time() then expires = os.time()+expires @@ -112,9 +118,10 @@ function addBanExport(playerId,reason,expires,banner) expires = 10444633200 end reason = formatShortcuts(reason).. string.format(GetLocalisedText("reasonadd"), getName(tostring(playerId) or "?"), banner or "Unknown" ) - local ban = {banid = GetFreshBanId(), name = bannedUsername,identifiers = bannedIdentifiers, banner = banner or "Unknown", reason = reason, expire = expires, expireString = formatDateString(expires) } - updateBlacklist( ban ) - + -- local ban = {banid = GetFreshBanId(), name = bannedUsername,identifiers = bannedIdentifiers, banner = banner or "Unknown", reason = reason, expire = expires, expireString = formatDateString(expires) } + -- updateBlacklist( ban ) + Storage.addBan(GetFreshBanId(), bannedUsername, bannedIdentifiers, banner or "Unknown", reason, expires, formatDateString(expires), "BAN", os.time()) + Storage.addAction("BAN", bannedIdentifiers[1], reason, banner or "Unknown", source, expires, formatDateString(expires)) if source then PrintDebugMessage("Player "..getName(source,true).." added ban "..reason, 3) end @@ -129,6 +136,7 @@ end exports('addBan', addBanExport) AddEventHandler("EasyAdmin:addBan", addBanExport) +-- Is this required anymore with storage updates? RegisterServerEvent("EasyAdmin:updateBanlist", function(playerId) local src = source if DoesPlayerHavePermission(source, "player.ban.view") then @@ -142,7 +150,7 @@ end) RegisterServerEvent("EasyAdmin:requestBanlist", function() local src = source if DoesPlayerHavePermission(source, "player.ban.view") then - TriggerLatentClientEvent("EasyAdmin:fillBanlist", src, 100000, blacklist) + TriggerLatentClientEvent("EasyAdmin:fillBanlist", src, 100000, Storage.getBanList()) PrintDebugMessage("Banlist Requested by "..getName(src,true), 3) end end) @@ -164,11 +172,11 @@ RegisterCommand("unban", function(source, args, rawCommand) SendWebhookMessage(moderationNotification,string.format(GetLocalisedText("adminunbannedplayer"), getName(source, false, true), args[1], "Unbanned via Command"), "ban", 16711680) end end, false) - RegisterServerEvent("EasyAdmin:editBan", function(ban) if DoesPlayerHavePermission(source, "player.ban.edit") then - updateBan(ban.banid,ban) + Storage.updateBan(ban.banid, ban) + --updateBan(ban.banid,ban) -- TODO Webhook end end) @@ -177,31 +185,34 @@ end) ---@param banId number @The ID of the ban to be removed ---@return boolean @True if the unban was successful, false otherwise function unbanPlayer(banId) - local thisBan = nil - for i,ban in ipairs(blacklist) do - if ban.banid == banId then - thisBan = ban - break - end - end - if thisBan == nil then - return false - end - UnbanId(banId) - return true + return Storage.removeBan(banId) + -- local thisBan = nil + -- for i,ban in ipairs(blacklist) do + -- if ban.banid == banId then + -- thisBan = ban + -- break + -- end + -- end + -- if thisBan == nil then + -- return false + -- end + -- UnbanId(banId) + -- return true end exports('unbanPlayer', unbanPlayer) + ---Fetches a ban entry by its ID ---@param banId number @The ID of the ban to fetch ---@return table|false @The ban entry if found, false otherwise function fetchBan(banId) - for i,ban in ipairs(blacklist) do - if ban.banid == banId then - return ban - end - end - return false + return Storage.getBan(banId) + -- for i,ban in ipairs(blacklist) do + -- if ban.banid == banId then + -- return ban + -- end + -- end + -- return false end exports('fetchBan', fetchBan) @@ -220,6 +231,7 @@ end) ---Generates a new unique ban ID ---@return number @The next available ban ID function GetFreshBanId() + local blacklist = Storage.getBanList() if blacklist[#blacklist] then return blacklist[#blacklist].banid+1 else @@ -228,7 +240,7 @@ function GetFreshBanId() end exports('GetFreshBanId', GetFreshBanId) - +-- Is this required with removal of custom ban list convar? RegisterCommand("convertbanlist", function(source, args, rawCommand) if GetConvar("ea_custombanlist", "false") == "true" then local content = LoadResourceFile(GetCurrentResourceName(), "banlist.json") @@ -305,29 +317,6 @@ end ---@return nil function updateBlacklist(data,remove, forceChange) local change = (forceChange or false) --mark if file was changed to save up on disk writes. - if GetConvar("ea_custombanlist", "false") == "true" then - PrintDebugMessage("You are using a Custom Banlist System, this is ^3not currently supported^7 and WILL cause issues! Only use this if you know what you are doing, otherwise, disable ea_custombanlist.", 1) - if data and not remove then - addBan(data) - TriggerEvent("ea_data:addBan", data) - - elseif data and remove then - UnbanId(data.banid) - elseif not data then - TriggerEvent('ea_data:retrieveBanlist', function(banlist) - blacklist = banlist - PrintDebugMessage("updated banlist custom banlist", 4) - for i,theBan in ipairs(blacklist) do - if theBan.expire < os.time() then - table.remove(blacklist,i) - PrintDebugMessage("removing old ban custom banlist", 4) - TriggerEvent("ea_data:removeBan", theBan) - end - end - end) - end - return - end local content = LoadResourceFile(GetCurrentResourceName(), "banlist.json") if not content then @@ -410,87 +399,94 @@ end ---@param reason string @The reason for the ban ---@return nil function BanIdentifier(identifier,reason) - updateBlacklist( {identifiers = {identifier} , banner = "Unknown", reason = reason, expire = 10444633200} ) + Storage.addBan(GetFreshBanId(), "Unknown", {identifier}, "Unknown", reason, 10444633200, formatDateString(10444633200), "BAN", os.time()) + --updateBlacklist( {identifiers = {identifier} , banner = "Unknown", reason = reason, expire = 10444633200} ) end + ---Bans a player using multiple identifiers ---@param identifier table @A table of identifiers for the player to ban ---@param reason string @The reason for the ban ---@return nil function BanIdentifiers(identifier,reason) - updateBlacklist( {identifiers = identifier , banner = "Unknown", reason = reason, expire = 10444633200} ) + Storage.addBan(GetFreshBanId(), "Unknown", identifier, "Unknown", reason, 10444633200, formatDateString(10444633200), "BAN", os.time()) + --updateBlacklist( {identifiers = identifier , banner = "Unknown", reason = reason, expire = 10444633200} ) end ---Unbans a player using their identifier ---@param identifier string @The identifier of the player to unban ---@return nil function UnbanIdentifier(identifier) - if identifier then - for i,ban in pairs(blacklist) do - for index,id in pairs(ban.identifiers) do - if identifier == id then - table.remove(blacklist,i) - local saved = SaveResourceFile(GetCurrentResourceName(), "banlist.json", json.encode(blacklist, {indent = true}), -1) - if not saved then - PrintDebugMessage("^1Saving banlist.json failed! Please check if EasyAdmin has Permission to write in its own folder!^7", 1) - end + Storage.removeBanIdentifier(identifier) + -- if identifier then + -- for i,ban in pairs(blacklist) do + -- for index,id in pairs(ban.identifiers) do + -- if identifier == id then + -- table.remove(blacklist,i) + -- local saved = SaveResourceFile(GetCurrentResourceName(), "banlist.json", json.encode(blacklist, {indent = true}), -1) + -- if not saved then + -- PrintDebugMessage("^1Saving banlist.json failed! Please check if EasyAdmin has Permission to write in its own folder!^7", 1) + -- end - if GetConvar("ea_custombanlist", "false") == "true" then - TriggerEvent("ea_data:removeBan", ban) - end - PrintDebugMessage("removed ban as per unbanidentifier func", 4) - return - end - end - end - end + -- if GetConvar("ea_custombanlist", "false") == "true" then + -- TriggerEvent("ea_data:removeBan", ban) + -- end + -- PrintDebugMessage("removed ban as per unbanidentifier func", 4) + -- return + -- end + -- end + -- end + -- end end ---Unbans a player using their ban ID ---@param id number @The ID of the ban to remove ---@return nil function UnbanId(id) - for i,ban in pairs(blacklist) do - if ban.banid == id then - table.remove(blacklist,i) - local saved = SaveResourceFile(GetCurrentResourceName(), "banlist.json", json.encode(blacklist, {indent = true}), -1) - if not saved then - PrintDebugMessage("^1Saving banlist.json failed! Please check if EasyAdmin has Permission to write in its own folder!^7", 1) - end + Storage.removeBan(id) + -- for i,ban in pairs(blacklist) do + -- if ban.banid == id then + -- table.remove(blacklist,i) + -- local saved = SaveResourceFile(GetCurrentResourceName(), "banlist.json", json.encode(blacklist, {indent = true}), -1) + -- if not saved then + -- PrintDebugMessage("^1Saving banlist.json failed! Please check if EasyAdmin has Permission to write in its own folder!^7", 1) + -- end - if GetConvar("ea_custombanlist", "false") == "true" then - TriggerEvent("ea_data:removeBan", ban) - end - end - end + -- if GetConvar("ea_custombanlist", "false") == "true" then + -- TriggerEvent("ea_data:removeBan", ban) + -- end + -- TriggerEvent("EasyAdmin:LogAction", {action = "UNBAN", banId = id }) + -- end + -- end end + ---Performs upgrades on the banlist format if necessary ---@return boolean @True if the banlist was upgraded, false otherwise function performBanlistUpgrades() local upgraded = false - + local banlist = Storage.getBanList() local takenIds = {} - for i,b in pairs(blacklist) do + for i,b in pairs(banlist) do if takenIds[b.banid] then local freshId = GetFreshBanId() PrintDebugMessage("ID "..b.banid.." was assigned twice, reassigned to "..freshId, 4) - blacklist[i].banid = freshId + banlist[i].banid = freshId upgraded = true end takenIds[b.banid] = true end takenIds=nil - for i,ban in pairs(blacklist) do + for i,ban in pairs(banlist) do if type(i) == "string" then PrintDebugMessage("Ban "..ban.banid.." had a string as indice, fixed it.", 4) - blacklist[i] = nil - table.insert(blacklist,ban) + banlist[i] = nil + table.insert(banlist,ban) upgraded = true end end - for i,ban in ipairs(blacklist) do + for i,ban in ipairs(banlist) do if ban.identifiers then for k, identifier in pairs(ban.identifiers) do if identifier == "" then @@ -505,9 +501,9 @@ function performBanlistUpgrades() ban.expireString = formatDateString(ban.expire) end end - if blacklist[1] and (blacklist[1].identifier or blacklist[1].steam or blacklist[1].discord) then + if banlist[1] and (banlist[1].identifier or banlist[1].steam or banlist[1].discord) then Citizen.Trace("Upgrading Banlist...\n", 4) - for i,ban in ipairs(blacklist) do + for i,ban in ipairs(banlist) do if not ban.identifiers then ban.identifiers = {} PrintDebugMessage("Ban "..ban.banid.." had no identifiers, added one.", 4) @@ -534,6 +530,7 @@ function performBanlistUpgrades() end Citizen.Trace("Banlist Upgraded.\n", 4) end + Storage.updateBanlist(banlist) return upgraded end @@ -542,14 +539,15 @@ end ---@param theIdentifier string @The identifier to check ---@return boolean @True if the identifier is banned, false otherwise function IsIdentifierBanned(theIdentifier) - local identifierfound = false - for index,value in ipairs(blacklist) do - for i,identifier in ipairs(value.identifiers) do - if theIdentifier == identifier then - identifierfound = true - end - end - end - return identifierfound + return Storage.getBanIdentifier(theIdentifier) + -- local identifierfound = false + -- for index,value in ipairs(blacklist) do + -- for i,identifier in ipairs(value.identifiers) do + -- if theIdentifier == identifier then + -- identifierfound = true + -- end + -- end + -- end + -- return identifierfound end exports('IsIdentifierBanned', IsIdentifierBanned) \ No newline at end of file diff --git a/server/storage.lua b/server/storage.lua new file mode 100644 index 00000000..8469a5d6 --- /dev/null +++ b/server/storage.lua @@ -0,0 +1,226 @@ +------------------------------------ +------------------------------------ +---- DONT TOUCH ANY OF THIS IF YOU DON'T KNOW WHAT YOU ARE DOING +---- THESE ARE **NOT** CONFIG VALUES, USE THE CONVARS IF YOU WANT TO CHANGE SOMETHING +---- +---- +---- If you are a developer and want to change something, consider writing a plugin instead: +---- https://easyadmin.readthedocs.io/en/latest/plugins/ +---- +------------------------------------ +------------------------------------ + +local currentVersion = 1 +banlist = {} +actions = {} + +local function LoadList(fileName) + local content = LoadResourceFile(GetCurrentResourceName(), fileName .. ".json") + local defaultData = { + version = currentVersion, + data = {} + } + if content then + local decoded = json.decode(content) + + if not decoded then + decoded = defaultData + + elseif not decoded.version then + if decoded[1] or next(decoded) ~= nil then + decoded = { + version = currentVersion, + data = decoded + } + else + decoded = defaultData + end + end + + SaveResourceFile(GetCurrentResourceName(), fileName .. ".json", json.encode(decoded, { indent=true }), -1) + return decoded + else + SaveResourceFile(GetCurrentResourceName(), fileName .. ".json", json.encode(defaultData, { indent=true }), -1) + return defaultData + end +end + +local function updateList(fileName) + +end + +banlist = LoadList("banlist").data +actions = LoadList("actions").data + +Storage = { + getBan = function(banId) + for i, ban in ipairs(banlist) do + if ban.banid == banId then + return ban + end + end + return false + end, + getBanIdentifier = function(identifiers) + local found = false + for i, ban in ipairs(banlist) do + for j, identifier in ipairs(identifiers) do + if ban.identifiers[identifier] then + found = true + break + end + end + end + return found + end, + addBan = function(banId, username, bannedIdentifiers, moderator, reason, expires, expiryString, type, time) + table.insert(banlist, { + time = os.time(), + banid = banId, + username = username, + identifiers = bannedIdentifiers, + banner = moderator, + reason = reason, + expire = expires, + expiryString = expiryString, + type = type, + timeLeft = time, + }) + local content = LoadResourceFile(GetCurrentResourceName(), "banlist.json") + if not content then + PrintDebugMessage("banlist.json file was missing, we created a new one.", 2) + content = json.encode({ + version = 1, + bans = {} + }) + end + local saved = SaveResourceFile(GetCurrentResourceName(), "banlist.json", json.encode(banlist, {indent = true}), -1) + if not saved then + PrintDebugMessage("^1Saving banlist.json failed! Please check if EasyAdmin has Permission to write in its own folder!^7", 1) + end + end, + -- Not too sure what this one does + -- updateBan = function(banId, .....) + -- end, + updateBanlist = function(banlist) + local content = LoadResourceFile(GetCurrentResourceName(), "banlist.json") + if not content then + PrintDebugMessage("banlist.json file was missing, we created a new one.", 2) + content = json.encode({}) + end + local saved = SaveResourceFile(GetCurrentResourceName(), "banlist.json", json.encode(banlist, {indent = true}), -1) + if not saved then + PrintDebugMessage("^1Saving banlist.json failed! Please check if EasyAdmin has Permission to write in its own folder!^7", 1) + end + end, + removeBan = function(banId) + for i, ban in ipairs(banlist) do + if ban.banid == banId then + table.remove(banlist, i) + local content = LoadResourceFile(GetCurrentResourceName(), "banlist.json") + if not content then + PrintDebugMessage("banlist.json file was missing, we created a new one.", 2) + content = json.encode({}) + end + local saved = SaveResourceFile(GetCurrentResourceName(), "banlist.json", json.encode(banlist, {indent = true}), -1) + if not saved then + PrintDebugMessage("^1Saving banlist.json failed! Please check if EasyAdmin has Permission to write in its own folder!^7", 1) + return false + end + return true + else + return false + end + end + end, + removeBanIdentifier = function(identifiers) + for i, ban in ipairs(banlist) do + for j, identifier in ipairs(identifiers) do + if ban.identifiers[identifier] then + table.remove(banlist, i) + local content = LoadResourceFile(GetCurrentResourceName(), "banlist.json") + if not content then + PrintDebugMessage("banlist.json file was missing, we created a new one.", 2) + content = json.encode({}) + end + local saved = SaveResourceFile(GetCurrentResourceName(), "banlist.json", json.encode(banlist, {indent = true}), -1) + if not saved then + PrintDebugMessage("^1Saving banlist.json failed! Please check if EasyAdmin has Permission to write in its own folder!^7", 1) + return + end + return + end + end + end + return + end, + getBanList = function() + return banlist + end, + getAction = function(discordId) + local userActions = {} + for _, act in ipairs(actions) do + if act.discord == discordId then + table.insert(userActions, act) + end + end + return userActions + end, + addAction = function(type, identifier, reason, moderator_name, moderator_identifier) + table.insert(actions, { + time = os.time(), + id = #actions + 1, + action = type, + discord = identifier, + reason = reason, + moderator = moderator_name, + moderatorId = moderator_identifier, + }) + local content = LoadResourceFile(GetCurrentResourceName(), "actions.json") + if not content then + PrintDebugMessage("actions.json file was missing, we created a new one.", 2) + content = json.encode({}) + end + local saved = SaveResourceFile(GetCurrentResourceName(), "actions.json", json.encode(actions, {indent = true}), -1) + if not saved then + PrintDebugMessage("^1Saving actions.json failed! Please check if EasyAdmin has Permission to write in its own folder!^7", 1) + end + return + end, + removeAction = function(actionId) + for i, act in ipairs(actions) do + if act.id == actionId then + table.remove(actions, i) + local content = LoadResourceFile(GetCurrentResourceName(), "actions.json") + if not content then + PrintDebugMessage("actions.json file was missing, we created a new one.", 2) + content = json.encode({}) + end + local saved = SaveResourceFile(GetCurrentResourceName(), "actions.json", json.encode(actions, {indent = true}), -1) + if not saved then + PrintDebugMessage("^1Saving actions.json failed! Please check if EasyAdmin has Permission to write in its own folder!^7", 1) + end + end + end + return + end, + apiVersion = 1, +} + +Citizen.CreateThread(function() + local banContent = LoadResourceFile(GetCurrentResourceName(), "banlist.json") + if banContent then + local data = json.decode(banContent) + if data.version ~= currentVersion then + updateList('banlist') + end + end + + local actionContent = LoadResourceFile(GetCurrentResourceName(), "actions.json") + if actionContent then + local data = json.decode(actionContent) + if data.version ~= currentVersion then + updateList('actions') + end + end +end) \ No newline at end of file diff --git a/shared/util_shared.lua b/shared/util_shared.lua index 030a6a93..36a19a0a 100644 --- a/shared/util_shared.lua +++ b/shared/util_shared.lua @@ -14,6 +14,9 @@ permissions = { ["player.screenshot"] = false, ["player.mute"] = false, ["player.warn"] = false, + ["player.actionhistory.view"] = false, + ["player.actionhistory.add"] = false, + ["player.actionhistory.delete"] = false, ["player.teleport.everyone"] = false, ["player.reports.view"] = false, ["player.reports.claim"] = false,