HTTP requests and Discord webhooks
Server scripts can call web services with modkit.http. The request runs on a worker thread, the game keeps running, and your callback gets the answer a moment later. HTTPS works.
The webhook URL belongs into server.cfg
A Discord webhook URL is a secret. Anyone who has it can post into your channel. Keep it out of the script and read it with modkit.config.
set discord_webhook https://discord.com/api/webhooks/123456789/your-token
Use set, not sets. Lines with sets are shown in the public server list.
A complete resource
resources/discordlog/manifest.lua
name "discordlog"
server "server.lua"
resources/discordlog/server.lua
local WEBHOOK = modkit.config.get("discord_webhook")
if not WEBHOOK then
modkit.log.warn("set discord_webhook in server.cfg to use this resource")
return
end
local queue = {}
local sending = false
local function pump()
if sending or #queue == 0 then return end
sending = true
local text = table.remove(queue, 1)
modkit.http.postJson(WEBHOOK, { content = text }, function(res)
sending = false
if res.status == 429 then
local wait = tonumber(res.headers["retry-after"]) or 2
table.insert(queue, 1, text)
modkit.setTimeout(math.ceil(wait * 1000), pump)
return
end
if not res.ok then modkit.log.warn("discord answered " .. res.status .. " " .. tostring(res.error)) end
pump()
end)
end
local function say(text)
if #queue >= 50 then table.remove(queue, 1) end
queue[#queue + 1] = text:sub(1, 1900)
pump()
end
modkit.events.add("serverStarted", function() say("Server is up.") end)
modkit.events.add("playerConnected", function(player)
say(player.name .. " joined (" .. modkit.players.count() .. " online)")
end)
modkit.events.add("playerDisconnected", function(player) say(player.name .. " left") end)
modkit.events.add("playerRejected", function(player, reason) say(player.name .. " was rejected: " .. reason) end)
modkit.events.add("playerDied", function(player, killer)
if killer and killer.steamId then
say(killer.name .. " (" .. killer.species .. ") killed " .. player.name .. " (" .. player.species .. ")")
end
end)
modkit.events.add("chat", function(player, text)
say("**" .. player.name .. "**: " .. text)
end)
The queue sends one message at a time. Discord limits how fast a webhook may post and answers with status 429 and a retry-after header when you are too fast. The script waits that long and tries again. Without a queue a busy chat would lose messages.
text:sub(1, 1900) keeps a message under Discord's limit of 2000 characters.
Reading an API
modkit.http.get("https://api.example.org/motd", function(res)
if not res.ok then return end
local ok, data = pcall(modkit.json.decode, res.body)
if ok and type(data) == "table" and data.text then modkit.world.announce(data.text, "News", 10) end
end)
Always check res.ok, and wrap json.decode in pcall. A web service can answer with an error page instead of JSON.
Rules of thumb
- The callback runs on the game thread like every other handler. Keep it short.
- At most 128 requests can wait at once. Four run at the same time. A request that does not fit raises an error in
modkit.http.request. - A response larger than 4 MB is cut and
erroris set. statusis0when the server was not reached.errorthen holds the step that failed and the Windows error code, for examplerequest failed (12007)for a host name that does not resolve.- The default timeout is 15 seconds, set
timeoutin milliseconds to change it. - Client scripts have no HTTP function. Let the client ask the server through an event and answer with
player:call.