WardenX

Getting Started

Getting Started

WardenX allows you to write custom Lua scripts to handle Discord events and commands. Scripts are sandboxed for security and have access to specific Discord objects.

Global Variables

These variables are automatically available in your scripts depending on the context.

  • interaction Discord: Available in command, button, and modal scripts.
  • guild Discord: The current server object.
  • member Discord: The member who triggered the event.
  • webhook WardenX: Available in the WardenX webhook event.
  • storage WardenX: Persistent data storage object for your server.
  • wardenx WardenX: Utility object for advanced script interactions.

Script Folders WardenX

Scripts are organized by their purpose. The dashboard handles the placement automatically when you create them in specific sections.

commands/
For slash commands. Use the --[[ @wardenx ... ]] metadata block at the top to declare options.
events/
For general Discord events like members joining or messages being sent.
buttons/
For handling button interactions (filename must match custom ID).
modals/
For handling modal submissions (filename must match custom ID).
lib/
For reusable libraries and shared helpers. Load them from command, event, button, or modal scripts via wardenx.run("filename") to keep repeated logic in one place.

Command Metadata

--[[ @wardenx
description = "Greet a user"
options = {
    { name = "user", description = "The user to greet", type = "user", required = true }
}
--]]

Supported Events Discord

Discord IDs should always be treated as strings in Lua to prevent precision loss.

Member Join | Member Leave
Triggered when a member joins or leaves. Provides member or user object.
Message Received | Edited | Deleted
Triggered on message actions. Provides message object.
Voice State Change
Triggered when joining/leaving/moving voice channels. Provides member, channel, and oldChannel.
Role Created | Deleted
Provides the role object.
Member Role Added | Removed
Triggered when roles are added to or removed from a member. Provides member and roles (array of role objects). Also provides role as the first role when at least one role is present in the event payload.
Channel Created | Deleted
Provides the channel object.
WardenX Webhook Received
Triggered by a POST to the server's private webhook URL. Provides guild and a webhook table containing method, body, lowercase headers, query parameters, and parsed json when the content type is JSON. Repeated header or query values are represented as arrays.
-- events/WardenXWebhookEvent.lua
print(webhook.body)

if webhook.json ~= nil then
    print(webhook.json.name)
end

Webhook URLs use a private 256-bit token. Keep the URL secret and regenerate or revoke it immediately if it is exposed. Requests are limited to 1 MiB and 30 requests per minute per server.

WardenX Utility WardenX

The wardenx global provides core functionality for script interaction.

wardenx.newEmbed()
Returns a new LuaEmbedBuilder instance.
wardenx.newButton(label, customId)
Returns a new LuaButton instance.
wardenx.run(scriptName, [argsOrFunc], [...args])
Loads and executes another script from lib/ or commands/. Use it to compose scripts, share utilities, and call specific helper functions without duplicating code.

Usage Patterns

wardenx.run("myLib", { key = value })
Injects values as globals in the target script context. Useful for passing runtime data such as IDs, flags, or precomputed values.
wardenx.run("myLib", "functionName", ...)
Calls a named function from the target script and forwards additional arguments. Useful when a library exposes multiple entry points.
-- lib/moderation.lua
function canModerate(targetRoleLevel, actorRoleLevel)
    return actorRoleLevel > targetRoleLevel
end

function formatCase(caseId)
    return "CASE-" .. tostring(caseId)
end

-- commands/warn.lua
local allowed = wardenx.run("moderation", "canModerate", 3, 7)
if not allowed then
    interaction:replyEphemeral("You cannot moderate this target.")
    return
end

local caseLabel = wardenx.run("moderation", "formatCase", 1234)
interaction:reply("Created " .. caseLabel)

-- Injected globals example
wardenx.run("audit", {
    action = "warn",
    targetId = "123456789012345678"
})

Tip: Use lib/ for shared modules and keep command/event files focused on orchestration (inputs, permission checks, replies).

HTTP Requests WardenX

Call external HTTP APIs synchronously from a script. Use pcall when you want to handle connection, timeout, validation, or response-size errors without stopping the script.

wardenx.request(url, [options])
Sends an HTTP request. options may contain method, headers, and body. Supported methods are GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS.

Response

The returned table contains status (number), ok (true for status 200–299), body (string), url (the final URL after redirects), and headers. Header names are lowercase and each value is an array to preserve repeated headers.

local response = wardenx.request("https://api.example.com/items", {
    method = "POST",
    headers = {
        ["Authorization"] = "Bearer token",
        ["Content-Type"] = "application/json"
    },
    body = '{"name":"example"}'
})

if response.ok then
    print(response.body)
else
    print("API returned status " .. response.status)
end

Persistent Storage WardenX

Data saved in storage persists across restarts. Supports strings, numbers, booleans, and nested tables.

storage:set(key, value)
Saves a value associated with a key.
storage:get(key)
Retrieves a value. Returns nil if not found.
storage:remove(key)
Deletes a specific key.
storage:clear()
Deletes all data for the server.
storage:getAll()
Returns a table of all stored keys and values.

Moderation Logs WardenX

Created via guild:newModlog(). Automatically handles formatting and sending logs to the server's log channel.

modlog:setTarget(userOrMemberOrId)
Sets the subject of the moderation action.
modlog:setModerator(userOrMemberOrId)
Sets the moderator who performed the action.
modlog:setAction(actionName)
e.g., "Ban", "Mute", "Warning".
modlog:setReason(reason)
Sets the reason for the log.
modlog:setDetails(details)
Adds extra information to the log.
modlog:setLogType(type)
Sets the category (e.g., "MODERATION", "MESSAGE").
modlog:send()
Finalizes and sends the log.

Interactions Discord

The interaction object is used specifically for slash commands, button clicks, and modal submissions.

interaction:getType()
Returns SLASH_COMMAND, BUTTON, or MODAL.
interaction:reply(content, [embed], [components])
Sends a visible reply. components can be a single button or table of buttons.
interaction:replyEphemeral(content, [embed], [components])
Sends a private reply visible only to the user.
interaction:deferReply() / deferReplyEphemeral()
Acknowledges the interaction to prevent timeout.
interaction:editOrigin(content, [embed], [components])
Edits the original message the interaction was triggered on (for buttons).
interaction:getOption(name)
Retrieves value of a command option.
interaction:getModalValue(id)
Gets the value from a modal text input.
interaction:cancelJava() WardenX
Stops WardenX's built-in Java handler for this command.
interaction:getMember() / getUser()
Returns the LuaMember or LuaUser who triggered the interaction.
interaction:getChannel() / getGuild()
Returns where the interaction happened.

Servers (Guilds) Discord

guild:getId()
Returns the guild's unique ID string.
guild:getName()
Returns the server's name.
guild:getIconUrl()
Returns the URL of the guild's icon.
guild:getSelf()
Returns the LuaMember representing the bot.
guild:getMemberCount()
Returns number of members.
guild:getMembers()
Returns a list of all LuaMember objects.
guild:getMember(id)
Returns a LuaMember for the given ID.
guild:getOwner()
Returns the owner LuaMember.
guild:getRoles()
Returns a list of all LuaRole objects.
guild:getRole(id)
Returns a LuaRole by ID (use everyone for public role).
guild:getTextChannels() / getVoiceChannels()
Returns specific channel types.
guild:getChannel(id)
Returns a LuaChannel for the given ID.
guild:newModlog() WardenX
Returns a new LuaModlog instance.
guild:createTextChannel(name, [parentCategory])
Creates a new text channel. Pass a LuaCategory as parentCategory to create it under that category.
guild:sendMessage(channelId, content, [embed])
Sends a message to a specific channel.
guild:ban(userId, [options])
Bans a user. Options: {time, timescale, reason}. Supported timescales: HOURS, DAYS.
guild:softBan(userId, [options])
Bans and immediately unbans to clear messages.
guild:kick(userId, [reason])
Kicks a user.
guild:unban(userId)
Unbans a user.

Members & Users Discord

LuaMember objects represent a user within a specific server and inherit all LuaUser methods.

member:getId()
Returns the member's unique ID.
member:getUsername() / getGlobalName()
Returns unique username or global display name.
member:getEffectiveName()
Returns nickname, global name, or username (whichever is available).
member:getNickname()
Returns the guild-specific nickname.
member:getAvatarUrl()
Returns effective avatar URL.
member:getGuild()
Returns the LuaGuild the member belongs to.
member:canInteract(other)
Checks if member can interact with another member or role.
member:getHighestRole() / getRoles()
Returns role information.
member:hasPermission(name) / hasPermissions(...)
Checks for Discord permissions.
member:hasRole(roleId)
Returns true if the member has the specified role ID.
member:isOwner() / isBot()
Boolean status checks.
member:setNickname(name)
Changes the member's nickname.
member:addRole(roleId) / removeRole(roleId)
Manages member roles.
member:timeout(minutes, [reason])
Timeouts the member.
member:removeTimeout() / isTimedOut()
Timeout management.
member:kick([reason]) / ban([options])
Moderation actions.

LuaUser

Represents a global Discord user.

user:getId() / getUsername()
Basic user info.
user:getAvatarUrl()
Returns the URL of the user's avatar.
user:isBot()
Returns true if the user is a bot.

Roles Discord

role:getId()
Returns the role's unique ID.
role:getName() / role:setName(name)
Gets or sets the role name.
role:getPosition()
Returns the role's position in the hierarchy.
role:getColorHex() / role:setColor(hex)
Gets or sets the role color (e.g., "#FF0000").
role:isHoisted() / isMentionable() / isManaged()
Boolean status checks.
role:hasPermission(name) / hasPermissions(...)
Checks for Discord permissions.
role:delete()
Deletes the role from the server.

Channels & Categories Discord

channel:getId() / getName() / setName(name)
Basic channel info.
channel:sendMessage(content, [embed], [components])
Sends a message to the channel.
channel:getType()
Returns "TEXT", "VOICE", etc.
channel:getCategory()
Returns the parent LuaCategory.
channel:setChannelCategory(categoryOrNil)
Sets the parent category for supported guild channels. Pass nil to remove the channel from its category.
channel:setPermissions(target, [allowed], [denied])
Creates or updates channel overrides for a LuaMember or LuaRole. allowed/denied are arrays of permission names.
channel:getPermissions(target)
Returns override data for the target as { allowed = {...}, denied = {...} }, or nil if no override exists.
channel:clearPermissions(target)
Removes the channel permission override for a LuaMember or LuaRole.
channel:delete()
Deletes the channel.

LuaCategory

category:getName() / setName(name)
Category name management.
category:getChannels()
Returns a list of channels in the category.
category:createTextChannel(name)
Creates a new text channel inside this category.
category:setPermissions(target, [allowed], [denied])
Creates or updates category overrides for a LuaMember or LuaRole.
category:getPermissions(target)
Returns override data for the target as { allowed = {...}, denied = {...} }, or nil.
category:clearPermissions(target)
Removes the category permission override for a LuaMember or LuaRole.

Messages Discord

message:getId() / getContent()
Returns the ID or text content of the message.
message:getAuthor() / getAuthorId()
Returns author information.
message:getChannel() / getGuild()
Returns location of the message.
message:update(content, [embed], [components])
Updates the message (only if sent by the bot).
message:parseEmbed(embed) / parseComponents(components)
Converts Lua embed/button values into Discord-ready objects used by update methods.
message:delete()
Deletes the message.

Embeds Discord

Use wardenx.newEmbed() to create a builder.

embed:setTitle(text, [url]) / setDescription(text)
Sets core embed content.
embed:addField(name, value, [inline])
Adds a field to the embed.
embed:setColor(hex) / setThumbnail(url) / setImage(url)
Visual styling.
embed:setAuthor(name, [url], [icon]) / setFooter(text, [icon])
Sets author and footer info.
embed:build()
Finalizes the builder and returns a LuaMessageEmbed.

LuaMessageEmbed

Read-only representation of a sent or received embed.

embed:getTitle() / getDescription() / getFields()
Retrieves embed data.
embed:getBuilder()
Returns a LuaEmbedBuilder pre-filled with this embed's data for easy editing.

Components (Buttons) Discord

button:setStyle(style)
Styles: PRIMARY, SECONDARY, SUCCESS, DANGER, LINK.
button:setAction(function(interaction) ... end) WardenX
Sets a Lua function to run when clicked.
button:getCustomId() / getCallback() / hasCallback()
Returns button identity and callback metadata.

LuaEmbedField

field:getName() / setName(name)
Gets or sets the field title.
field:getValue() / setValue(value)
Gets or sets the field body text.
field:isInline() / setInline(inline)
Reads or updates inline field layout.
field:markAsImmutable()
Marks the field immutable so subsequent edits are blocked.

Advanced Features WardenX

Type Casting

Use type hints in the editor for better autocomplete.

local m: member = guild:getMember("ID")
m:getNickname() -- Editor now knows 'm' is a member

Security & Limits WardenX

  • Sandboxing: os, io, debug, package, and luajava libraries are disabled.
  • Action limits: Discord actions, storage mutations, and HTTP calls count toward the per-script action budget.
  • HTTP timeouts: Requests have short connection and total-call deadlines so unavailable APIs cannot block a script indefinitely.
  • HTTP size: Request and response bodies are limited to 1 MiB.
  • Network access: Private, loopback, link-local, multicast, and reserved network destinations are blocked, including redirect targets.

Code Examples

Map users roles to global storage

function saveRoles(m: member)
    local roles = m:getRoles() or {}
    if #roles == 0 then return end

    local roleIds = {}
    for _,role in ipairs(roles) do
        print("saving " .. role:getName() .. " with role id: " .. tostring(role:getId()))
        table.insert(roleIds, tostring(role:getId()))
    end

    local existingData = storage:get("roleStores") or {}

    local userId = m:getId()
    existingData[userId] = roleIds

    storage:set("roleStores", existingData)
end

Make your own custom welcome messages with fallback functionality, using the Member Joined event

local embed: embedBuilder = wardenx.newEmbed()

embed:setTitle(string.format("User %s has joined the server!", member:getEffectiveName()))

--[[

you can either

 - hardcode the welcome channel
local welcomeChannel = guild:getChannel("channelid")
	- id needs to be in a string as its too long of a number, thank lua for that

or

 - get it dynamically from server storage
 	 - this means you can change the target channel via command, instead of needing to edit the script
local welcomeChannel = storage:get("welcomeChannel")

--]]

local welcomeChannel = storage:get("welcomeChannel")

if welcomeChannel == nil then
    -- send server owner message
    guild:getOwner():sendMessage(string.format(
        "hey, welcomeChannel is nil, so i cant send a welcome message for %s",
        member:getEffectiveName()))

    -- create a modlog

    local modlog = guild:newModlog()

    -- configure it
    modlog:setAction("failed welcome message")
    modlog:setDetails("welcomeChannel is nil")
    modlog:setModerator(guild:getSelf())

    -- send it
    modlog:send()
else
    welcomeChannel:sendMessage("", embed:build())
end

I'll add more eventually