Files
VnUtest/garrysmod/gamemodes/militaryrp/plugins/serverlogs/sv_plugin.lua
2026-03-31 10:59:30 +03:00

563 lines
19 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
local PLUGIN = PLUGIN
util.AddNetworkString("ixServerLogs_Request")
util.AddNetworkString("ixServerLogs_Response")
util.AddNetworkString("ixServerLogs_Clear")
util.AddNetworkString("ixServerLogs_OpenMenu")
-- Функция проверки доступа к логам через CAMI
local function HasLogAccess(client)
return AdminPrivs[client:GetUserGroup()]
end
-- Инициализация системы логов
function PLUGIN:InitializeLogSystem()
self.logs = self.logs or {}
self.logFile = nil
-- Создаем папку для логов
local logPath = ix.config.Get("logFilePath", "helix_logs")
if not file.Exists(logPath, "DATA") then
file.CreateDir(logPath)
end
if ix.config.Get("logSaveToFile", true) then
-- Создаем файл для текущей сессии
local date = os.date("%Y-%m-%d_%H-%M-%S")
self.logFile = logPath .. "/log_" .. date .. ".txt"
file.Write(self.logFile, "=== Server Logs Started: " .. os.date("%Y-%m-%d %H:%M:%S") .. " ===\n")
end
-- Загружаем логи из предыдущей сессии
self:LoadPersistentLogs()
end
-- Загрузка логов из файлов
function PLUGIN:LoadPersistentLogs()
local logPath = ix.config.Get("logFilePath", "helix_logs")
local files, _ = file.Find(logPath .. "/log_*.txt", "DATA")
if #files == 0 then return end
-- Сортируем файлы по дате (в обратном порядке)
table.sort(files, function(a, b) return a > b end)
local logsToLoad = {}
local maxToLoad = 500 -- Лимит загрузки для производительности
for _, fileName in ipairs(files) do
if #logsToLoad >= maxToLoad then break end
local content = file.Read(logPath .. "/" .. fileName, "DATA")
if not content then continue end
local lines = string.Split(content, "\n")
-- Читаем с конца
for i = #lines, 1, -1 do
local line = lines[i]
if not line or line == "" or string.StartWith(line, "===") then continue end
-- Пример лога: [10:38:40][Боевые действия] Игрок умер
-- Нам нужно распарсить это обратно в таблицу
local timeStr, categoryName, msg = string.match(line, "%[(%d%d:%d%d:%d%d)%]%[(.-)%] (.*)")
if timeStr and categoryName and msg then
-- Находим ID категории по имени
local categoryID = "ADMIN"
for catID, cat in pairs(self.LOG_CATEGORIES) do
if cat.name == categoryName then
categoryID = catID
break
end
end
table.insert(logsToLoad, {
id = #logsToLoad + 1,
type = "LOADED", -- Специальный тип для загруженных
category = categoryID,
message = "[АРХИВ] " .. msg,
timestamp = 0, -- Мы не знаем точную дату, только время
timeString = timeStr,
isPersistent = true
})
if #logsToLoad >= maxToLoad then break end
end
end
end
-- Переворачиваем чтобы были по хронологии и добавляем в начало текущих логов
for i = #logsToLoad, 1, -1 do
table.insert(self.logs, logsToLoad[i])
end
end
-- Добавление лога
function PLUGIN:AddLog(logType, message, target, data)
if not self.LOG_TYPES[logType] then
print("[ServerLogs] Unknown log type: " .. tostring(logType))
return
end
local logEntry = {
id = #self.logs + 1,
type = logType,
category = self.LOG_TYPES[logType].category,
message = message,
target = target and {
name = IsValid(target) and target:Nick() or tostring(target),
steamid = IsValid(target) and target:SteamID() or "N/A"
} or nil,
data = data or {},
timestamp = os.time(),
timeString = os.date("%H:%M:%S")
}
table.insert(self.logs, logEntry)
-- Ограничиваем количество логов в памяти
local maxEntries = ix.config.Get("logMaxEntries", 1000)
if #self.logs > maxEntries then
table.remove(self.logs, 1)
end
-- Сохраняем в файл
if self.logFile then
local logString = string.format("[%s][%s] %s\n",
logEntry.timeString,
self.LOG_CATEGORIES[logEntry.category].name,
message
)
file.Append(self.logFile, logString)
end
-- Выводим в консоль
MsgC(self.LOG_CATEGORIES[logEntry.category].color, "[LOG] ", Color(255, 255, 255), message .. "\n")
end
-- Получение логов с фильтрацией
function PLUGIN:GetLogs(filters)
filters = filters or {}
local result = {}
for i = #self.logs, 1, -1 do
local log = self.logs[i]
local match = true
-- Фильтр по категории
if filters.category and log.category ~= filters.category then
match = false
end
-- Фильтр по типу
if filters.type and log.type ~= filters.type then
match = false
end
-- Фильтр по игроку
if filters.player and log.target and log.target.steamid ~= filters.player then
match = false
end
-- Фильтр по времени
if filters.startTime and log.timestamp < filters.startTime then
match = false
end
if filters.endTime and log.timestamp > filters.endTime then
match = false
end
-- Фильтр по тексту
if filters.search and not string.find(string.lower(log.message), string.lower(filters.search)) then
match = false
end
if match then
table.insert(result, log)
-- Ограничиваем количество результатов
if filters.limit and #result >= filters.limit then
break
end
end
end
return result
end
-- Сетевая обработка запроса логов
net.Receive("ixServerLogs_Request", function(len, client)
if not HasLogAccess(client) then return end
local filters = net.ReadTable()
local logs = PLUGIN:GetLogs(filters)
-- Отправляем логи порциями
local chunkSize = 50
for i = 1, #logs, chunkSize do
local chunk = {}
for j = i, math.min(i + chunkSize - 1, #logs) do
table.insert(chunk, logs[j])
end
net.Start("ixServerLogs_Response")
net.WriteUInt(i == 1 and 1 or 0, 2) -- Первый пакет или нет
net.WriteTable(chunk)
net.Send(client)
end
-- Отправляем финальный пакет если логов нет
if #logs == 0 then
net.Start("ixServerLogs_Response")
net.WriteUInt(1, 2)
net.WriteTable({})
net.Send(client)
end
end)
PLUGIN.logs = {}
PLUGIN:AddLog("ADMIN_COMMAND", client:Nick() .. " очистил логи сервера", client)
end)
-- Выполнение админ-команд
function PLUGIN:OnCommandRun(client, command, arguments)
if not IsValid(client) then return end
local cmdObj = ix.command.list[string.lower(command)]
if not cmdObj then return end
-- Логируем только если команда админская или важная
if cmdObj.adminOnly or cmdObj.superAdminOnly or cmdObj.privilege then
local argsStr = table.concat(arguments, " ")
local message = string.format("%s выполнил команду: /%s %s", client:Nick(), command, argsStr)
self:AddLog("COMMAND_RUN", message, client, {
command = command,
args = arguments
})
end
end
-- Самоубийство
function PLUGIN:CanPlayerSuicide(client)
self:AddLog("PLAYER_DEATH", client:Nick() .. " пытается совершить самоубийство", client)
end
-- Поднятие предмета
function PLUGIN:PlayerPickupItem(client, item)
if not IsValid(client) or not item then return end
local itemName = item.GetName and item:GetName() or item.name or "Предмет"
local message = string.format("%s поднял предмет: %s", client:Nick(), itemName)
self:AddLog("ITEM_PICKUP", message, client, {
item = itemName
})
end
-- Выбрасывание предмета
function PLUGIN:PlayerDroppedItem(client, item)
if not IsValid(client) or not item then return end
local itemName = item.GetName and item:GetName() or item.name or "Предмет"
local message = string.format("%s выбросил предмет: %s", client:Nick(), itemName)
self:AddLog("ITEM_DROP", message, client, {
item = itemName
})
end
-- ======================
-- ХУКИ ДЛЯ ЛОГИРОВАНИЯ
-- ======================
-- Смерть игрока
function PLUGIN:PlayerDeath(victim, inflictor, attacker)
local attackerName = "Мир"
local attackerSteamID = "world"
local weapon = "unknown"
if IsValid(attacker) and attacker:IsPlayer() then
attackerName = attacker:Nick()
attackerSteamID = attacker:SteamID()
weapon = IsValid(attacker:GetActiveWeapon()) and attacker:GetActiveWeapon():GetClass() or "unknown"
end
local message = ""
if IsValid(attacker) and attacker:IsPlayer() then
if attacker == victim then
message = victim:Nick() .. " покончил с собой"
else
message = attackerName .. " убил " .. victim:Nick() .. " используя " .. weapon
end
else
message = victim:Nick() .. " умер"
end
self:AddLog("PLAYER_DEATH", message, victim, {
attacker = attackerSteamID,
weapon = weapon
})
end
-- Получение урона
function PLUGIN:PlayerHurt(victim, attacker, health, damage)
if not IsValid(attacker) or not attacker:IsPlayer() or attacker == victim then return end
local message = string.format("%s нанёс %d урона игроку %s (осталось %d HP)",
attacker:Nick(), damage, victim:Nick(), health)
self:AddLog("PLAYER_HURT", message, victim, {
attacker = attacker:SteamID(),
damage = damage,
health = health
})
end
-- Подключение игрока
function PLUGIN:PlayerInitialSpawn(client)
local message = string.format("%s подключился к серверу (SteamID: %s)",
client:Nick(), client:SteamID())
self:AddLog("PLAYER_CONNECT", message, client)
end
-- Отключение игрока
function PLUGIN:PlayerDisconnected(client)
local message = string.format("%s отключился от сервера", client:Nick())
self:AddLog("PLAYER_DISCONNECT", message, client)
end
-- Спавн игрока
function PLUGIN:PlayerSpawn(client)
if not client:GetCharacter() then return end
local message = string.format("%s заспавнился", client:Nick())
self:AddLog("PLAYER_SPAWN", message, client)
end
-- Сообщения в чат
function PLUGIN:PlayerSay(client, text, teamChat)
local chatType = teamChat and "[TEAM]" or "[ALL]"
local message = string.format("%s %s: %s", chatType, client:Nick(), text)
self:AddLog("CHAT_MESSAGE", message, client, {
text = text,
team = teamChat
})
end
-- Создание персонажа
function PLUGIN:OnCharacterCreated(client, character)
local message = string.format("%s создал персонажа '%s'",
client:Nick(), character:GetName())
self:AddLog("CHARACTER_CREATE", message, client, {
characterID = character:GetID(),
characterName = character:GetName()
})
end
-- Удаление персонажа
function PLUGIN:PreCharacterDeleted(client, character)
local message = string.format("%s удалил персонажа '%s'",
client:Nick(), character:GetName())
self:AddLog("CHARACTER_DELETE", message, client, {
characterID = character:GetID(),
characterName = character:GetName()
})
end
-- Смена персонажа
function PLUGIN:PlayerLoadedCharacter(client, character, oldCharacter)
if oldCharacter then
local message = string.format("%s сменил персонажа с '%s' на '%s'",
client:Nick(), oldCharacter:GetName(), character:GetName())
self:AddLog("CHARACTER_SWITCH", message, client, {
oldCharacter = oldCharacter:GetName(),
newCharacter = character:GetName()
})
end
end
-- Покупка предмета
function PLUGIN:CharacterVendorTraded(client, vendor, x, y, invID, price, isSell)
local action = isSell and "продал" or "купил"
local message = string.format("%s %s предмет за %d",
client:Nick(), action, price)
self:AddLog("ITEM_PURCHASE", message, client, {
vendor = vendor,
price = price,
sell = isSell
})
end
-- Использование предмета
function PLUGIN:OnItemTransferred(item, curInv, inventory)
if item.player then
local message = string.format("%s переместил предмет '%s'",
item.player:Nick(), item:GetName())
self:AddLog("INVENTORY", message, item.player, {
item = item:GetName(),
itemID = item:GetID()
})
end
end
-- Инициализация при загрузке плагина
function PLUGIN:InitPostEntity()
self:InitializeLogSystem()
self:AddLog("ADMIN_COMMAND", "Система логов инициализирована")
end
-- ======================
-- КОМАНДЫ HELIX
-- ======================
-- Регистрация CAMI разрешения
if CAMI then
CAMI.RegisterPrivilege({
Name = "Helix - View Server Logs",
MinAccess = "admin",
Description = "Позволяет просматривать логи сервера"
})
CAMI.RegisterPrivilege({
Name = "Helix - Clear Server Logs",
MinAccess = "superadmin",
Description = "Позволяет очищать логи сервера"
})
CAMI.RegisterPrivilege({
Name = "Helix - Use Handcuffs",
MinAccess = "admin",
Description = "Позволяет использовать наручники"
})
end
-- Команда для просмотра логов
ix.command.Add("ViewLogs", {
description = "Открыть панель просмотра логов сервера",
OnCheckAccess = function(self, client)
return HasLogAccess(client)
end,
OnRun = function(self, client)
-- Отправляем команду на открытие меню на клиенте
net.Start("ixServerLogs_OpenMenu")
net.Send(client)
return "@serverLogsOpened"
end
})
-- Альтернативная команда
ix.command.Add("ServerLogs", {
description = "Открыть панель просмотра логов сервера",
OnCheckAccess = function(self, client)
return HasLogAccess(client)
end,
OnRun = function(self, client)
-- Отправляем команду на открытие меню на клиенте
net.Start("ixServerLogs_OpenMenu")
net.Send(client)
return "@serverLogsOpened"
end
})
-- Команда для очистки логов
ix.command.Add("ClearLogs", {
description = "Очистить все логи сервера",
superAdminOnly = true,
OnCheckAccess = function(self, client)
-- Проверка через CAMI
if CAMI then
return CAMI.PlayerHasAccess(client, "Helix - Clear Server Logs", function(allowed)
return allowed
end)
end
-- Fallback на стандартную проверку суперадмина
return client:IsSuperAdmin()
end,
OnRun = function(self, client)
PLUGIN.logs = {}
PLUGIN:AddLog("ADMIN_COMMAND", client:Nick() .. " очистил логи сервера", client)
return "@serverLogsCleared"
end
})
-- Команда для просмотра последних логов в консоли
ix.command.Add("LastLogs", {
description = "Показать последние 20 логов в консоли",
adminOnly = true,
arguments = {
ix.type.number,
},
argumentNames = {"количество"},
OnCheckAccess = function(self, client)
-- Проверка через CAMI
if CAMI then
return CAMI.PlayerHasAccess(client, "Helix - View Server Logs", function(allowed)
return allowed
end)
end
return client:IsAdmin()
end,
OnRun = function(self, client, count)
count = math.Clamp(count or 20, 1, 100)
local logs = PLUGIN:GetLogs({limit = count})
client:ChatPrint("=== Последние " .. #logs .. " логов ===")
for i, log in ipairs(logs) do
local categoryName = PLUGIN.LOG_CATEGORIES[log.category].name
local targetInfo = log.target and (" [" .. log.target.name .. "]") or ""
client:ChatPrint(string.format("[%s][%s]%s %s",
log.timeString, categoryName, targetInfo, log.message))
end
return "@serverLogsDisplayed", #logs
end
})
-- Команда для поиска в логах
ix.command.Add("SearchLogs", {
description = "Найти логи по тексту или игроку",
adminOnly = true,
arguments = {
ix.type.text,
},
argumentNames = {"поисковый запрос"},
OnCheckAccess = function(self, client)
-- Проверка через CAMI
if CAMI then
return CAMI.PlayerHasAccess(client, "Helix - View Server Logs", function(allowed)
return allowed
end)
end
return client:IsAdmin()
end,
OnRun = function(self, client, search)
local logs = PLUGIN:GetLogs({search = search, limit = 50})
client:ChatPrint("=== Найдено логов: " .. #logs .. " ===")
for i, log in ipairs(logs) do
local categoryName = PLUGIN.LOG_CATEGORIES[log.category].name
local targetInfo = log.target and (" [" .. log.target.name .. "]") or ""
client:ChatPrint(string.format("[%s][%s]%s %s",
log.timeString, categoryName, targetInfo, log.message))
end
return "@serverLogsSearchResults", #logs
end
})