-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathactiontext_server.lua
More file actions
273 lines (240 loc) · 10.2 KB
/
actiontext_server.lua
File metadata and controls
273 lines (240 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
local discordAvatarCache = {}
local discordAvatarFetchedAt = {}
local discordMemberCache = {}
local discordMemberFetchedAt = {}
local FRAMEWORK = 'none'
local ESXObj = nil
local QBCoreObj = nil
pcall(function()
if GetResourceState and GetResourceState('qb-core') == 'started' then
FRAMEWORK = 'qbcore'
elseif GetResourceState and GetResourceState('es_extended') == 'started' then
FRAMEWORK = 'esx'
end
end)
if FRAMEWORK == 'none' then
pcall(function()
if exports and exports['qb-core'] and exports['qb-core'].GetCoreObject then
FRAMEWORK = 'qbcore'
end
end)
end
if FRAMEWORK == 'none' then
pcall(function()
TriggerEvent('esx:getSharedObject', function(obj) if obj then FRAMEWORK = 'esx' end end)
end)
end
local cfgFramework = (Config.GLOBAL and Config.GLOBAL.nui and Config.GLOBAL.nui.framework) or 'none'
if cfgFramework and cfgFramework ~= '' and cfgFramework ~= 'none' then
FRAMEWORK = cfgFramework
end
if FRAMEWORK == 'esx' then
pcall(function()
TriggerEvent('esx:getSharedObject', function(obj) ESXObj = obj end)
end)
elseif FRAMEWORK == 'qbcore' then
pcall(function()
if exports and exports['qb-core'] and exports['qb-core'].GetCoreObject then
QBCoreObj = exports['qb-core']:GetCoreObject()
elseif _G and _G.QBCore then
QBCoreObj = _G.QBCore
end
end)
end
print(('ActionText: framework detected/using = %s'):format(FRAMEWORK))
local function getCharacterName(src)
local name = nil
if FRAMEWORK == 'esx' and ESXObj then
local ok, xPlayer = pcall(function() return ESXObj.GetPlayerFromId(src) end)
if ok and xPlayer then
if xPlayer.getName then
pcall(function() name = xPlayer.getName() end)
end
end
elseif FRAMEWORK == 'qbcore' and QBCoreObj then
local ok, ply = pcall(function() return QBCoreObj.Functions.GetPlayer(src) end)
if ok and ply and ply.PlayerData then
local pd = ply.PlayerData
if pd.charinfo then
local fn = pd.charinfo.firstname or ''
local ln = pd.charinfo.lastname or ''
name = (fn .. ' ' .. ln):gsub('%s+', ' '):match('^%s*(.-)%s*$')
elseif pd.name then
name = pd.name
end
end
end
if not name or name == '' then
name = GetPlayerName(src) or ('Player' .. tostring(src))
end
return name
end
local function getDiscordIdentifier(src)
local ids = GetPlayerIdentifiers(src) or {}
for _, id in ipairs(ids) do
if type(id) == 'string' and id:sub(1,8) == 'discord:' then
return id:sub(9)
end
end
return nil
end
local function fetchAndCacheDiscordAvatar(src)
if not (Config.GLOBAL and Config.GLOBAL.nui and Config.GLOBAL.nui.discord and Config.GLOBAL.nui.discord.enabled) then
return
end
local botToken = (Config.GLOBAL.nui.discord.botToken)
if not botToken or botToken == '' then return end
local discordId = getDiscordIdentifier(src)
if not discordId then return end
local now = (os.time() * 1000)
local last = discordAvatarFetchedAt[discordId] or 0
local refresh = (Config.GLOBAL.nui.discord.refreshMs) or (60 * 60 * 1000)
if discordAvatarCache[discordId] and (now - last) < refresh then
return
end
local url = ('https://discord.com/api/v10/users/%s'):format(discordId)
PerformHttpRequest(url, function(status, text, headers)
if status == 200 and text then
local ok, data = pcall(function() return json.decode(text) end)
if ok and data and data.avatar then
local avatarUrl = ('https://cdn.discordapp.com/avatars/%s/%s.png?size=128'):format(discordId, data.avatar)
discordAvatarCache[discordId] = avatarUrl
discordAvatarFetchedAt[discordId] = (os.time() * 1000)
local showOnlyIfMember = (Config.GLOBAL.nui.discord.membershipCheck == true)
if showOnlyIfMember and Config.GLOBAL.nui.discord.guildId then
local gUrl = ('https://discord.com/api/v10/guilds/%s/members/%s'):format(Config.GLOBAL.nui.discord.guildId, discordId)
PerformHttpRequest(gUrl, function(gStatus, gBody, gHeaders)
local isMember = (gStatus == 200)
discordMemberCache[discordId] = isMember
discordMemberFetchedAt[discordId] = (os.time() * 1000)
if isMember then
TriggerClientEvent('actiontext:avatarUpdate', -1, src, avatarUrl)
end
end, 'GET', '', { ['Authorization'] = ('Bot %s'):format(botToken) })
else
TriggerClientEvent('actiontext:avatarUpdate', -1, src, avatarUrl)
end
return
end
end
discordAvatarCache[discordId] = nil
discordAvatarFetchedAt[discordId] = (os.time() * 1000)
end, 'GET', '', { ['Authorization'] = ('Bot %s'):format(botToken), ['Content-Type'] = 'application/json' })
end
RegisterNetEvent('actiontext:send')
AddEventHandler('actiontext:send', function(actionType, text)
local src = source
local playerName = GetPlayerName(src) or ('Player' .. tostring(src))
local lang = (Config.GLOBAL and Config.GLOBAL.lang) or {}
if not global_spam_tracker then global_spam_tracker = {} end
local now = (os.time() * 1000)
local spamCfg = (Config.GLOBAL and Config.GLOBAL.spam) or { cooldownMs = 1000, burstLimit = 3, burstWindowMs = 10000 }
local s = global_spam_tracker[src]
if not s then
s = { times = {} }
global_spam_tracker[src] = s
end
local newTimes = {}
for _, t in ipairs(s.times) do
if now - t <= spamCfg.burstWindowMs then table.insert(newTimes, t) end
end
s.times = newTimes
local isAdmin = false
if spamCfg and spamCfg.allowAdminBypass and spamCfg.adminAcePermission then
pcall(function()
isAdmin = IsPlayerAceAllowed(src, spamCfg.adminAcePermission)
end)
end
if not isAdmin and #s.times >= spamCfg.burstLimit then
if spamCfg.notify ~= false then
local prefix = (spamCfg and spamCfg.spamPrefix) or lang.spamPrefix or 'SPAM'
local msg = (spamCfg and spamCfg.spamMessage) or lang.spamMessage or 'You are sending actions too quickly. Please slow down.'
TriggerClientEvent('chat:addMessage', src, { color = { 255, 100, 100 }, args = { prefix, msg } })
end
return
end
if not isAdmin and #s.times > 0 then
local last = s.times[#s.times]
if now - last < spamCfg.cooldownMs then
if spamCfg.notify ~= false then
local prefix = (spamCfg and spamCfg.spamPrefix) or lang.spamPrefix or 'SPAM'
local msg = (spamCfg and spamCfg.spamMessage) or lang.spamMessage or 'Please wait before sending another action.'
TriggerClientEvent('chat:addMessage', src, { color = { 255, 100, 100 }, args = { prefix, msg } })
end
return
end
end
table.insert(s.times, now)
if Config.GLOBAL and Config.GLOBAL.serverLogging then
print(('[ActionText] /%s from %s (id=%s): %s'):format(actionType, playerName, tostring(src), text))
end
do
local nuiCfg = (Config.GLOBAL and Config.GLOBAL.nui) or {}
local maxLen = nuiCfg.maxTextLength or 0
if type(text) == 'string' and maxLen > 0 and #text > maxLen then
text = text:sub(1, maxLen) .. '…'
end
end
pcall(function() fetchAndCacheDiscordAvatar(src) end)
local avatarUrl = nil
local discordId = getDiscordIdentifier(src)
if discordId and discordAvatarCache[discordId] then
local allow = true
if (Config.GLOBAL.nui.discord and Config.GLOBAL.nui.discord.membershipCheck) and Config.GLOBAL.nui.discord.guildId then
allow = (discordMemberCache[discordId] == true)
end
if allow then avatarUrl = discordAvatarCache[discordId] end
end
local displayName = pcall(function() return getCharacterName(src) end) and getCharacterName(src) or GetPlayerName(src)
TriggerClientEvent('actiontext:display', -1, src, actionType, text, avatarUrl, displayName)
local postChat = false
local chatColor = { 255, 183, 0 }
local formatted = ('%s %s'):format(playerName, text)
if actionType == 'me' and Config.ME and Config.ME.chat then
postChat = true
chatColor = (Config.ME.chatColor or chatColor)
formatted = (Config.ME.chatFormat or '%s %s'):format(playerName, text)
if Config.ME.useChatPrefix and Config.ME.chatPrefix then
local pf = (Config.ME.chatPrefixFormat or '^*%s^*^7 ')
formatted = (pf:format(Config.ME.chatPrefix) or '') .. formatted
else
formatted = '^7' .. formatted
end
elseif actionType == 'do' and Config.DO and Config.DO.chat then
postChat = true
chatColor = (Config.DO.chatColor or chatColor)
formatted = (Config.DO.chatFormat or '%s %s'):format(playerName, text)
if Config.DO.useChatPrefix and Config.DO.chatPrefix then
local pf = (Config.DO.chatPrefixFormat or '^*%s^*^7 ')
formatted = (pf:format(Config.DO.chatPrefix) or '') .. formatted
else
formatted = '^7' .. formatted
end
end
if postChat then
TriggerClientEvent('chat:addMessage', -1, {
color = chatColor,
multiline = true,
args = { formatted }
})
end
end)
AddEventHandler('onResourceStart', function(resourceName)
if resourceName ~= GetCurrentResourceName() then return end
Citizen.CreateThread(function()
Wait(1000)
local players = GetPlayers()
for _, pid in ipairs(players) do
local n = tonumber(pid)
if n then
pcall(function() fetchAndCacheDiscordAvatar(n) end)
end
end
end)
end)
AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
local src = source
SetTimeout(1500, function()
pcall(function() fetchAndCacheDiscordAvatar(src) end)
end)
end)