dobrograd-13-06-2022/garrysmod/addons/admin-tickets/lua/cats/server.lua
Jonny_Bro (Nikita) e4d5311906 first commit
2023-11-16 15:01:19 +05:00

418 lines
11 KiB
Lua
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

---------------------------------------------------
-- CACHE STORAGE
---------------------------------------------------
cats.currentTickets = cats.currentTickets or {}
cats.adminDataCache = cats.adminDataCache or {}
cats.lastNotifiedTickets = cats.lastNotifiedTickets or {}
-- build receivers list
local function getAdmins(steamID)
local admins = {}
for i, ply in ipairs(player.GetAll()) do
if cats.config.playerCanSeeTicket(ply, steamID) then table.insert(admins, ply) end
end
return admins
end
---------------------------------------------------
-- BASE CODE
---------------------------------------------------
-- create api interface
cats.api = octolib.api({
url = 'https://octothorp.team/cats/api',
headers = { ['Authorization'] = CFG.keys.cats },
})
local ignoreMsgsTime = {}
-- log in console
function cats:Log(text)
print('[# CATS] ' .. os.date('%H:%M:%S - ', os.time()) .. text)
end
local discordMaxEmbedFields = 25
cats.NotifyDiscord = octolib.func.debounceStart(function(self)
if table.IsEmpty(self.currentTickets) then return end
local admins = octolib.array.toKeys(getAdmins(), 'Не занят')
local adminDescriptions = table.Copy(admins)
-- remove busy admins
for _, ticket in pairs(self.currentTickets) do
if IsValid(ticket.admin) then
admins[ticket.admin] = nil
adminDescriptions[ticket.admin] = 'Занят жалобой ' .. ticket.userID
end
end
-- remove afk admins
for admin in pairs(admins) do
if admin:GetAFKTime() > CFG.afkAdminNotActive then
admins[admin] = nil
adminDescriptions[admin] = 'AFK'
end
if not self.config.actualAdminRanks[admin:GetUserGroup()] then
admins[admin] = nil
adminDescriptions[admin] = 'Ранг не обязывает'
end
end
-- count active tickets
local notClaimed = {}
for userID, ticket in pairs(self.currentTickets) do
if not IsValid(ticket.admin) then
notClaimed[#notClaimed + 1] = userID
end
end
if not self.config.iDiffers(notClaimed, self.lastNotifiedTickets) then return end
self.lastNotifiedTickets = notClaimed
if not notClaimed[1] then return end
for _, userID in ipairs(notClaimed) do
local user = player.GetBySteamID(userID)
if IsValid(user) then
self.config.notify(user, self.lang.ticket_noAdmins)
end
end
local webhook = CFG.webhooks.cats
if not webhook then return end
local fields = octolib.table.mapSequential(notClaimed, function(sid)
return {
name = 'https://octothorp.team/cats/' .. sid,
value = self.currentTickets[sid].chatLog[1][2],
}
end)
if #fields+1 > discordMaxEmbedFields then
local missing = #fields - (discordMaxEmbedFields-3)
fields = octolib.array.page(fields, discordMaxEmbedFields - 3)
fields[#fields + 1] = {
value = ('+ Еще %d %s'):format(missing, octolib.string.formatCount(missing, 'жалоба', 'жалобы', 'жалоб')),
}
end
fields[#fields + 1] = { name = '', value = '' } -- divider
fields[#fields + 1] = {
name = 'Админы на сервере',
value = next(adminDescriptions) == nil and 'Никого' or table.concat(octolib.table.mapSequential(adminDescriptions, function(status, admin)
return admin:SteamName() .. ' ' .. status
end), '\n'),
}
local embed = {
-- author = {
-- name = GetHostName(),
-- url = 'https://octothorp.team/join-' .. CFG.serverID,
-- },
title = GetHostName(),
description = ('**%d** %s, но активных админов нет'):format(#notClaimed, octolib.string.formatCount(#notClaimed, 'открытая жалоба', 'открытые жалобы', 'открытых жалоб')),
fields = fields,
thumbnail = { url = ('https://img.icons8.com/color/184/%d.png'):format(#notClaimed) },
}
if #notClaimed > 8 then
embed.color = 0xA52019
if #notClaimed > 10 then
embed.footer = {
text = 'Это какой-то апокалипсис! Пожалуйста, срочно зайдите!',
icon_url = 'https://img.icons8.com/color/48/skull.png',
}
end
elseif #notClaimed > 4 then
embed.color = 0xFF4F00
elseif #notClaimed > 2 then
embed.color = 0xE5BE01
end
octoservices:post('/discord/webhook/' .. webhook, {
content = #notClaimed > 5 and CFG.adminMention or nil,
embeds = { embed },
})
if not table.IsEmpty(admins) then
RunConsoleCommand('sg', 'a', ('Пожалуйста, разберите %d %s или позовите других админов на помощь'):format(#notClaimed, octolib.string.formatCount(#notClaimed, 'жалобу', 'жалобы', 'жалоб')))
end
end, cats.config.notificationDelay)
timer.Create('cats.oldTicketsChecker', 60, 0, function()
local currentTime = os.time()
local callAdmins = false
for userID, ticket in pairs(cats.currentTickets) do
if currentTime - ticket.createdTime > cats.config.oldTicketTrigger and not IsValid(ticket.admin) then
callAdmins = true
break
end
end
if callAdmins then cats:NotifyDiscord() end
end)
-- process & broadcast new ticket message
function cats:DispatchMessage(sender, steamID, msg, sendToAPI)
local ply = player.GetBySteamID(sender)
if msg == '' then
ply:Notify('warning', 'Укажи текст админ-запроса')
return
end
local admins = getAdmins(steamID)
if self.currentTickets[steamID] then
-- ticket exists, append chat message
table.insert(self.currentTickets[steamID].chatLog, {sender, msg, sender ~= steamID})
if sendToAPI then
self.api:post('/msg/' .. steamID, {
owner = sender,
text = msg,
}):Then(function(res)
local msg = res.data
if msg and msg.time then
ignoreMsgsTime[msg.time] = true
end
end)
end
else
-- create new ticket
self.currentTickets[steamID] = {
createdTime = os.time(),
createdGameTime = CurTime(),
chatLog = {{sender, msg}},
userID = steamID
}
if sendToAPI then
self.api:post('/chat', {
owner = steamID,
text = msg,
}):Then(function(res)
local ticket = res.data
if ticket and ticket.created then
ignoreMsgsTime[ticket.created] = true
end
end)
end
hook.Run('cats.created', sender)
end
hook.Run('cats.message', sender, steamID, msg)
-- do networking
netstream.Start(admins, 'cats.dispatchMessage', steamID, sender, msg)
end
netstream.Hook('cats.dispatchMessage', function(ply, steamID, msg)
local user = player.GetBySteamID(steamID)
-- check if user exists
if not IsValid(user) then
cats.config.notify(ply, 'warning', cats.lang.error_playerNotFound)
return
end
-- check access
if not cats.config.playerCanSeeTicket(ply, steamID) then
cats.config.notify(ply, 'warning', cats.lang.error_noAccess)
return
end
-- dispatch
cats:DispatchMessage(ply:SteamID(), steamID, msg, true)
end)
netstream.Hook('cats.closeTicket', function(ply, steamID)
local ticket = cats.currentTickets[steamID]
-- ticket doesn't exist
if not ticket then
cats.config.notify(ply, 'warning', cats.lang.error_ticketNotFound)
return
end
-- ticket already ended
if ticket.ended then
cats.config.notify(ply, 'warning', cats.lang.error_ticketEnded)
return
end
if ticket.adminID == ply:SteamID() then
-- admin closed the ticket
local user = player.GetBySteamID(steamID)
if IsValid(user) then
cats.config.notify(user, cats.lang.ticketClosedBy:format(ticket.admin:Name()))
end
cats.config.notify(ply, cats.lang.ticketClosed)
elseif ticket.userID == ply:SteamID() and not IsValid(ticket.admin) then
-- user cancelled the ticket
else
-- we don't have access to this
cats.config.notify(ply, 'warning', cats.lang.error_noAccess)
return
end
cats:CloseTicket(steamID)
hook.Run('cats.closed', ply:SteamID(), steamID)
end)
-- close ticket
function cats:CloseTicket(steamID)
-- notify clients
netstream.Start(getAdmins(steamID), 'cats.closeTicket', steamID)
self.currentTickets[steamID] = nil
self.api:delete('/chat/' .. steamID)
end
-- claim/unclaim ticket
function cats:ClaimTicket(steamID, ply, doClaim)
local ticket = self.currentTickets[steamID]
-- ticket doesn't exist
if not ticket then
self:Log('Trying to claim inexistant ticket for ' .. steamID)
return
end
-- check access
if IsValid(ticket.admin) and ticket.adminID ~= ply:SteamID() then
cats.config.notify(ply, 'warning', cats.lang.error_noAccess)
return
end
if doClaim then
-- claim ticket
ticket.admin = ply
ticket.adminID = ply:SteamID()
ticket.claimTime = os.time()
else
-- unclaim ticket
ticket.admin = nil
ticket.adminID = nil
end
hook.Run('cats.claim', ply, steamID, doClaim)
netstream.Start(getAdmins(steamID), 'cats.claimTicket', steamID, ply, doClaim)
end
netstream.Hook('cats.claimTicket', function(ply, steamID, doClaim)
local ticket = cats.currentTickets[steamID]
-- check access
if not cats.config.playerCanSeeTicket(ply, steamID) or ply:SteamID() == steamID then
cats.config.notify(ply, 'warning', cats.lang.error_noAccess)
return
end
-- ticket doesn't exist
if not ticket then
cats.config.notify(ply, 'warning', cats.lang.error_ticketNotFound)
return
end
local user = player.GetBySteamID(steamID)
if doClaim then
-- ticket already claimed
if IsValid(ticket.admin) then
cats.config.notify(ply, 'warning', cats.lang.error_ticketAlreadyClaimed)
return
end
-- notify about claim
cats.config.notify(ply, cats.lang.ticketClaimed)
cats.config.notify(user, cats.lang.ticketClaimedBy:format(ply:Name()))
else
-- ticket not claimed yet
if not IsValid(ticket.admin) then
cats.config.notify(ply, 'warning', cats.lang.error_ticketNotClaimed)
return
end
-- notify about unclaim
cats.config.notify(ply, cats.lang.ticketUnclaimed)
cats.config.notify(user, cats.lang.ticketUnclaimedBy:format(ply:Name()))
end
-- do the thing
cats:ClaimTicket(steamID, ply, doClaim)
end)
---------------------------------------------------
-- GAMEMODE HOOKS
---------------------------------------------------
-- initiate tickets
hook.Add('PlayerSay', 'cats', function(ply, text)
local shouldTrigger, msg = cats.config.triggerText(ply, text)
if shouldTrigger then
cats:DispatchMessage(ply:SteamID(), ply:SteamID(), msg, true)
return ''
end
end)
-- deal with disconnected players
hook.Add('PlayerDisconnected', 'cats', function(ply)
for steamID, ticket in pairs(cats.currentTickets) do
if steamID == ply:SteamID() then
-- notify admin
if IsValid(ticket.admin) then
cats.config.notify(ticket.admin, 'warning', cats.lang.ticketUserLeft)
end
-- delete ticket
cats:CloseTicket(steamID)
elseif ticket.adminID == ply:SteamID() then
-- unclaim ticket
cats:ClaimTicket(steamID, ply, false)
end
end
end)
hook.Add('PlayerFinishedLoading', 'cats', function(ply)
if cats.config.playerCanSeeTicket(ply, '') then
netstream.Start(ply, 'cats.syncTickets', cats.currentTickets)
end
end)
local lastMsgTime = 0
timer.Create('cats.checkUpdates', 5, 0, function()
cats.api:get('/util/updates/' .. lastMsgTime):Then(function(res)
lastMsgTime = res.data[1]
for steamID, msgs in pairs(res.data[2]) do
for i, msg in ipairs(msgs) do
if not ignoreMsgsTime[msg.time] then
cats:DispatchMessage(msg.owner, steamID, msg.text)
else
ignoreMsgsTime[msg.time] = nil
end
end
end
end)
end)