├── fxmanifest.lua ├── errors.json ├── readme.md └── server.lua /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | game 'gta5' 3 | lua54 'yes' 4 | author 'Jv$t' 5 | description 'Discord Integrations' 6 | version '2.0.3' 7 | 8 | server_scripts { 9 | 'server.lua' 10 | } 11 | 12 | files { 13 | "errors.json" 14 | } 15 | -------------------------------------------------------------------------------- /errors.json: -------------------------------------------------------------------------------- 1 | { 2 | "400": "The request was improperly formatted, or the server couldn\"t understand it..!", 3 | "401": "Unauthorized token!", 4 | "403": "Your Discord Token is probably wrong or does not have correct permissions!", 5 | "404": "Enpoint inexistent!", 6 | "429": "Too many requests!" 7 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # grm-discord v2! 2 | Discord integrations for Fivem 3 | 4 | # How to install 5 | - Download the script and drop in the resources folder 6 | - Open your server.cfg and add this: ensure grm-discord 7 | - Go to: https://discord.com/developers/applications 8 | - Create a new bot and invite in your server 9 | - Go back to your server.cfg 10 | - Paste the below with your own bot token & guild id 11 | 12 | ``` 13 | setr discord:token token here 14 | setr discord:guild guild id here 15 | ``` 16 | --- 17 | 18 | # Some Example's 19 | ```lua 20 | local Discord = exports["grm-discord"] 21 | 22 | ---GetUser 23 | ---@param source number 24 | ---@param refresh boolean 25 | ---@return table|nil 26 | Discord:GetUser(source, true) 27 | 28 | ---GetGuild 29 | ---@return table 30 | Discord:GetGuild() 31 | 32 | ---GetRoleInfo 33 | ---@param role string|string[] 34 | ---@return table 35 | Discord:GetRoleInfo(source, role) 36 | 37 | ---SetNickname 38 | ---@param source number 39 | ---@param nickname string 40 | ---@return boolean|nil 41 | Discord:SetNickname(source, newNickname) 42 | 43 | ---BanUser 44 | ---@param source number 45 | ---@param reason string 46 | ---@return boolean|nil 47 | Discord:BanUser(source, reason) 48 | 49 | ---KickUser 50 | ---@param source number 51 | ---@param reason string 52 | ---@return boolean|nil 53 | Discord:KickUser(source, reason) 54 | 55 | ---TimeoutUser 56 | ---@param source number 57 | ---@param duration string 58 | ---@param reason string 59 | ---@return boolean|nil 60 | Discord:TimeoutUser(source, duration, reason) 61 | 62 | ---RemoveTimeoutUser 63 | ---@param source number 64 | ---@return boolean|nil 65 | Discord:RemoveTimeoutUser(source) 66 | 67 | ---IsUserTimeout 68 | ---@param source number 69 | ---@return boolean|nil 70 | Discord:IsUserTimeout(source) 71 | 72 | ---GetGuildRoles 73 | ---@return table 74 | Discord:GetGuildRoles() 75 | 76 | ---AddRole 77 | ---@param source number 78 | ---@param role string|string[] 79 | ---@return boolean|nil 80 | Discord:AddRole(source, role) 81 | 82 | ---RemoveRole 83 | ---@param source number 84 | ---@param role string|string[] 85 | ---@return boolean|nil 86 | Discord:RemoveRole(source, role) 87 | 88 | ---HaveRole 89 | ---@param source number 90 | ---@param role string|string[] 91 | ---@return table|nil 92 | Discord:HaveRole(source, role) 93 | ``` 94 | -------------------------------------------------------------------------------- /server.lua: -------------------------------------------------------------------------------- 1 | 2 | local Discord = {} 3 | local Players = {} 4 | 5 | local Token = ("Bot %s"):format(GetConvar("discord:token", "")) 6 | local Errors = json.decode(LoadResourceFile(GetCurrentResourceName(), "errors.json")) 7 | local Logo = GetConvar("discord:logo", "") 8 | 9 | local Data = { 10 | ["Authorization"] = Token, 11 | ["X-Audit-Log-Reason"] = "Grm Discord v2!", 12 | ['Content-Type'] = 'application/json' 13 | } 14 | 15 | ---@param method string 16 | ---@param endpoint string 17 | ---@param jsondata table 18 | ---@return any 19 | function Discord.Request(method, endpoint, jsondata) 20 | local p = promise.new() 21 | 22 | local u = "https://discord.com/api/%s" 23 | local c = function(...) p:resolve({ ... }) end 24 | 25 | PerformHttpRequest(u:format(endpoint), c, method, jsondata or "", Data) 26 | 27 | local c, r, h = table.unpack(Citizen.Await(p)) 28 | 29 | if c ~= 200 and c ~= 204 then return error(Errors[tostring(c)]) end 30 | 31 | return r, h 32 | end 33 | 34 | ---@param source number 35 | ---@return number|nil 36 | function Discord.GetUserId(source) 37 | local license = GetPlayerIdentifierByType(source, "discord") 38 | return license and license:gsub("discord:", "") or nil 39 | end 40 | 41 | local Guild = GetConvar("discord:guild", "") 42 | 43 | ---@return table 44 | function Discord.GetGuild() 45 | return Discord.Request("GET", ("guilds/%s"):format(Guild)) 46 | end 47 | 48 | exports("GetGuild", Discord.GetGuild) 49 | 50 | ---@param source number 51 | ---@param refresh boolean 52 | ---@return table|nil 53 | function Discord.GetUser(source, refresh) 54 | local id = Discord.GetUserId(source) 55 | 56 | if not id then return warn(("player.%s does not have a discord account linked!"):format(source)) end 57 | 58 | if not refresh and Players[id] then return Players[id] end 59 | 60 | local result = Discord.Request("GET", ("guilds/%s/members/%s"):format(Guild, id)) 61 | 62 | if not result then return warn(("player.%s is not inside server.%s"):format(source, Guild)) end 63 | 64 | result = json.decode(result) 65 | 66 | local d, m, y = result.joined_at:sub(9, 10), result.joined_at:sub(6,7), result.joined_at:sub(1,4) 67 | result.joined_at = ("%s/%s/%s"):format(d, m, y) 68 | 69 | local user = table.clone(result.user) 70 | result.user = nil 71 | 72 | for key, val in pairs(user) do result[key] = val end 73 | 74 | local url = "https://cdn.discordapp.com/avatars/%s/%s" 75 | result.avatar = result.avatar and url:format(id, result.avatar) or Logo 76 | 77 | Players[id] = result 78 | 79 | return result 80 | end 81 | 82 | exports("GetUser", Discord.GetUser) 83 | 84 | ---@param source number 85 | ---@param nickname string 86 | ---@return boolean|nil 87 | function Discord.SetNickname(source, nickname) 88 | local id = Discord.GetUserId(source) 89 | 90 | if not id then return warn(("player.%s does not have a discord account linked!"):format(source)) end 91 | 92 | Discord.Request("PATCH", ("guilds/%s/members/%s"):format(Guild, id), json.encode({ nick = tostring(nickname) })) 93 | 94 | if Players[id] then Players[id].nick = nickname end 95 | 96 | return true 97 | end 98 | 99 | exports("SetNickname", Discord.SetNickname) 100 | 101 | ---@param source number 102 | ---@param reason string 103 | ---@return boolean|nil 104 | function Discord.BanUser(source, reason) 105 | local id = Discord.GetUserId(source) 106 | 107 | if not id then return warn(("player.%s does not have a discord account linked!"):format(source)) end 108 | 109 | Discord.Request("PUT", ("guilds/%s/bans/%s"):format(Guild, id), json.encode({reason = tostring(reason)})) 110 | 111 | Players[id] = nil 112 | 113 | return true 114 | end 115 | 116 | exports("BanUser", Discord.BanUser) 117 | 118 | ---@param source number 119 | ---@param reason string 120 | ---@return boolean|nil 121 | function Discord.KickUser(source, reason) 122 | local id = Discord.GetUserId(source) 123 | 124 | if not id then return warn(("player.%s does not have a discord account linked!"):format(source)) end 125 | 126 | Discord.Request("PUT", ("guilds/%s/kicks/%s"):format(Guild, id), json.encode({reason = tostring(reason)})) 127 | 128 | Players[id] = nil 129 | 130 | return true 131 | end 132 | 133 | exports("KickUser", Discord.KickUser) 134 | 135 | ---@param source number 136 | ---@param duration string 137 | ---@param reason string 138 | ---@return boolean|nil 139 | function Discord.TimeoutUser(source, duration, reason) 140 | local id = Discord.GetUserId(source) 141 | 142 | if not id then return warn(("player.%s does not have a discord account linked!"):format(source)) end 143 | 144 | local durations = { ["1m"] = 60, ["5m"] = 5*60, ["10m"] = 10*60, ["1h"] = 60*60, ["1d"] = 24*60*60, ["1w"] = 7*24*60*60 } 145 | 146 | local durationSeconds = durations[duration] 147 | if not durationSeconds then return warn("Invalid duration specified.") end 148 | 149 | local timeoutEndTime = os.time() + durationSeconds 150 | local timeoutData = { communication_disabled_until = os.date("!%Y-%m-%dT%H:%M:%SZ", timeoutEndTime), reason = tostring(reason) } 151 | 152 | Discord.Request("PATCH", ("guilds/%s/members/%s"):format(Guild, id), json.encode(timeoutData)) 153 | 154 | return true 155 | end 156 | 157 | exports("TimeoutUser", Discord.TimeoutUser) 158 | 159 | ---@param source number 160 | ---@return boolean|nil 161 | function Discord.RemoveTimeoutUser(source) 162 | local id = Discord.GetUserId(source) 163 | 164 | if not id then return warn(("player.%s does not have a discord account linked!"):format(source)) end 165 | 166 | local timeoutData = { communication_disabled_until = false, reason = "Removing timeout" } 167 | 168 | Discord.Request("PATCH", ("guilds/%s/members/%s"):format(Guild, id), json.encode(timeoutData)) 169 | 170 | return true 171 | end 172 | 173 | exports("RemoveTimeoutUser", Discord.RemoveTimeoutUser) 174 | 175 | ---@param source number 176 | ---@return boolean|nil 177 | function Discord.IsUserTimeout(source) 178 | local id = Discord.GetUserId(source) 179 | 180 | if not id then return warn(("player.%s does not have a discord account linked!"):format(source)) end 181 | 182 | local memberInfo = Discord.GetUser(source) 183 | 184 | return memberInfo?.communication_disabled_unti 185 | end 186 | 187 | exports("IsUserTimeout", Discord.IsUserTimeout) 188 | 189 | ---@return table 190 | function Discord.GetGuildRoles() 191 | local result = Discord.Request("GET", ("guilds/%s/roles"):format(Guild)) 192 | 193 | if not result then return warn(("Failed to retrieve roles from server %s."):format(source, Guild)) end 194 | 195 | result = json.decode(result) 196 | 197 | return result 198 | end 199 | 200 | exports("GetGuildRoles", Discord.GetGuildRoles) 201 | 202 | ---@param source number 203 | ---@param role string|string[] 204 | ---@return table|nil 205 | function Discord.HaveRole(source, role) 206 | local id = Discord.GetUserId(source) 207 | 208 | if not id then return warn(("player.%s does not have a discord account linked!"):format(source)) end 209 | 210 | local roles = Players[id]?.roles or Discord.GetUser(source, true)?.roles 211 | 212 | if not roles then return warn(("Could not find roles for player.%s"):format(source)) end 213 | 214 | role = type(role) ~= "table" and { role } or role 215 | 216 | local result = {} 217 | 218 | for i = 1, #role do 219 | result[tostring(role[i])] = lib.table.contains(roles, tostring(role[i])) 220 | end 221 | 222 | return result 223 | end 224 | 225 | exports("HaveRole", Discord.HaveRole) 226 | 227 | ---@param source number 228 | ---@param role string|string[] 229 | ---@return boolean|nil 230 | function Discord.AddRole(source, role) 231 | local id = Discord.GetUserId(source) 232 | 233 | if not id then return warn(("player.%s does not have a discord account linked!"):format(source)) end 234 | 235 | local roles = Players[id]?.roles or Discord.GetUser(source, true)?.roles 236 | 237 | if not roles then return warn(("Could not find roles for player.%s"):format(source)) end 238 | 239 | role = type(role) ~= "table" and { role } or role 240 | 241 | for _, val in pairs(role) do table.insert(roles, tostring(val)) end 242 | 243 | Discord.Request('PATCH', ('guilds/%s/members/%s'):format(Guild, id), json.encode({ roles = roles })) 244 | 245 | return true 246 | end 247 | 248 | exports("AddRole", Discord.AddRole) 249 | 250 | ---@param source number 251 | ---@param role string|string[] 252 | ---@return boolean|nil 253 | function Discord.RemoveRole(source, role) 254 | local user = Discord.GetUser(source, true) 255 | 256 | if not user?.roles then return warn(("Could not find roles for player.%s"):format(source)) end 257 | 258 | role = type(role) ~= "table" and { role } or role 259 | 260 | for i, v in ipairs(user.roles) do if lib.table.contains(role, v) then table.remove(user.roles, i) end end 261 | 262 | Discord.Request('PATCH', ('guilds/%s/members/%s'):format(Guild, user.id), json.encode({ roles = user.roles })) 263 | 264 | return true 265 | end 266 | 267 | exports("RemoveRole", Discord.RemoveRole) 268 | 269 | ---@param role string|string[] 270 | ---@return table 271 | function Discord.GetRoleInfo(role) 272 | role = type(role) ~= "table" and { role } or role 273 | 274 | for i = 1, #role do role[i] = tostring(role[i]) end -- prevent issues 275 | 276 | local result = Discord.Request('GET', ('guilds/%s/roles'):format(Guild)) 277 | 278 | result = json.decode(result) 279 | 280 | local roles = {} 281 | 282 | for i, val in pairs(result) do 283 | roles[val.id] = lib.table.contains(role, val.id) and val or nil 284 | end 285 | 286 | return roles 287 | end 288 | 289 | exports("GetRoleInfo", Discord.GetRoleInfo) 290 | --------------------------------------------------------------------------------