Introduction

NOTE: Just adding AntiRaid (or any discord bot) to your server will not do much unless you configure it first!

AntiRaid is a discord bot that takes a very unique approach to protecting servers. Instead of providing a single-size-fits-all solution, AntiRaid makes use of "structured templating" and a (work in progress/coming soon) template shop to allow servers to protect themselves in a way that is tailored to them.

Our Philosophy

While many bots like Wick provide a managed anti-raid/anti-nuke solution, these systems often provide suboptimal user experience to many servers as they may not cater to specific needs of the server. Wick, for example, is often criticized for limiting the abilities of server moderators in a way that makes moderation harder. While Wick is useful in many servers and does work quite well in the anti-raid/anti-nuke field from our experience, it may not be the best solution for all servers.

Templating, on the other hand, allows servers to tailor their needs directly without needing to go through the pain of making a custom discord bot, hosting it and then navigating the complexities of the discord API manually while reinventing concepts like stings/punishments/permissions/captchas all over again.

Permissions

Imagine. Imagine a discord bot that you could completely control. You could decide who can use any specific command, who can change the bot's settings, and who can even use the bot at all.

Thats AntiRaid...

AntiRaid has a customizable permission system that uses both Discord permissions for simplicity and kittycat permissions for more specific requirements. For more complex cases, AntiRaid provides support for Lua scripting, which can be used to extend Antiraid with arbitrarily complex permission systems, among other things.

The idea is simple: All roles have permissions attached to them and members can have special permission overrides. The members' permissions are then checked when a command is run.

Modes

Anti-Raid has two different modes for permission checks depending on how custom your needs are:

  • Simple: In simple mode, just specify the exact permissions needed to run a command. This is the default mode.
  • Template: If you have more advanced needs, use custom templates to determine if a user has the required permissions. See Templating for more information on how templating works.

Simple Permission Checks

Since not everyone knows how to code, AntiRaid provides a simple permission-checking system built in that should be enough for most:

  1. Commands can have permissions that gate actions.
  2. Commands can be either real or virtual. Real commands can be run. Virtual commands are placeholders for permission-gating actions.
  3. Commands can be configured by setting their permissions or disabling them (some commands cannot be disabled to avoid breakage).
  4. Server admins can set permissions on their server roles and then override them for specific users through permission overrides.
  5. Server admins can then set permissions on commands and default permissions on modules. These permissions are then checked when a command is run.

Template Permission Checks

For more advanced users, AntiRaid provides a template system that allows you to create custom permission checks. This is done through custom Luau templating.

See the templating guide for more information on how to use Lua templates. Then, just code away!

TIP

For best results, consider limiting the server permissions of other users to the minimum required. Then, use AntiRaid for actual moderation. That's better than giving everyone admin permissions and then trying to restrict them with AntiRaid.

Templating

At AntiRaid, we prioritize flexibility and customization for our users. To this end, our bot supports advanced templating to allow for extensive personalization of embeds and messages. While many bots utilize proprietary languages or templating engines, we have chosen to leverage Lua—a renowned scripting language widely used in game development and other applications. This decision ensures that our users benefit from a powerful, well-documented, and versatile language, enhancing the capability and ease of customizing their AntiRaid experience.

Note: this documentation is still a work-in-progress and things are still being documented better and better by the day!

Lua Templating

At AntiRaid, we prioritize flexibility and customization for our users. To this end, our bot supports advanced templating to allow for extensive personalization of embeds and messages. While many bots utilize proprietary languages or templating engines, we have chosen to leverage Lua—a renowned scripting language widely used in game development and other applications. This decision ensures that our users benefit from a powerful, well-documented, and versatile language, enhancing the capability and ease of customizing their AntiRaid experience.

Specifically, Anti Raid uses a variant of Lua called Luau. If you've ever used Roblox before, this is the same variant of Lua used there too (which is why Luau is also known as Roblox Lua in many places). You can check out the Luau docs for more information on the language itself. Unlike PUC Lua (the reference implementation), Luau is both faster and offers robust sandboxing capabilities allowing AntiRaid to run scripts in as safe an environment as possible.

Getting Started

Note that the remainder of these docs will cover AntiRaids Lua SDKs. To learn more about Lua itself, please checkout Lua's official tutorial for Lua 5.0 here. Other resources for Lua exist (Lua is very popular after all), including Roblox's tutorial (ignore the Studio bits), TutorialPoint and Codecademy.

Limitations

AntiRaid applies the following 3 global limits to all Lua templates. Note that we may provide increased limits as a Premium feature in the future:

#![allow(unused)]
fn main() {
pub const MAX_TEMPLATE_MEMORY_USAGE: usize = 1024 * 1024 * 3; // 3MB maximum memory
pub const MAX_TEMPLATE_LIFETIME: std::time::Duration = std::time::Duration::from_secs(60 * 15); // 15 minutes maximum lifetime
pub const MAX_TEMPLATES_EXECUTION_TIME: std::time::Duration = std::time::Duration::from_secs(30); // 30 seconds maximum execution time
}

The above limits are in place to prevent abuse and ensure that the bot remains responsive. If you require increased limits, please contact support (once again, this may change in the future).

Some key notes

  • Each guild is assigned a dedicated Lua VM. This VM is used to execute Lua code that is used in the templates.
  • The total memory usage that a guild can use is limited to MAX_TEMPLATE_MEMORY_USAGE (currently 3MB). This is to prevent a single guild from using too much memory.
  • Execution of all scripts is timed out when the last executed script takes longer than MAX_TEMPLATES_EXECUTION_TIME (currently 30 seconds).
  • A lua VM will exist for a total of MAX_TEMPLATE_LIFETIME (currently 10 minutes) after the last access before being destroyed. This is to reduce memory+CPU usage.
  • The __stack table can be used to share data across templates safely while the VM is running. without affecting other templates. This is useful for sharing data between templates such as Audit Logs. Note that AntiRaid uses luau sandboxing meaning that _G is readonly.
  • The standard require statement can be used to import AntiRaid modules. Note that the modules are read-only and cannot be monkey-patched etc.
  • Because Lua is a single-threaded language, only one template can be executed at a time

There are 2 valid syntax for a Luau template:

  1. Lua script syntax
local args, token = ...
-- Do something
return output
  1. Function expression syntax (not recommended for new code)
function(args, token)
    -- Do something
    return output
end

Note that option 1 is recommended as it is both more idiomatic and is also valid syntax for LSP's and Luau parsers. Note that option 2 is actually converted to option 1 internally through the below wrapper:

local args, token = ...
{function body here}

Interop

Many features of Lua don't work so well when calling functions within the AntiRaid SDK. For example, both arrays and maps are expressed as tables in Lua. However, AntiRaid, being written in Rust, doesn't know this and hance needs some help to convert certain types for FFI. This is where the @antiraid/interop module comes in.

Arrays

To pass arrays to modules within the AntiRaid SDK, you need to set the metatable to @antiraid/interop#array_metatable. This will allow the SDK to convert the array to a Rust Vec internally.

local interop = require '@antiraid/interop'
setmetatable({a = 5}, interop.array_metatable)

Null

While the Lua nil does work in many cases (and even when calling the SDK), its not the best choice. When querying AntiRaid SDK, the SDK will use the @antiraid/interop#null value to represent a null value. Your Lua templates can also use this value if desired

local interop = require '@antiraid/interop'
local null = interop.null -- This is the null value

Memory Usage

While not strictly useful for interop, it is often desirable to know the memory usage of a Lua template as AntiRaid will kill your template if it exceeds the memory limit. For this, you can use the @antiraid/interop#memusage function.

local interop = require '@antiraid/interop'
print(interop.memusage())

User Error vs Runtime Error

As Lua does not have a built-in way to distinguish between user errors and runtime errors, AntiRaid provides a way to do so. Simply return a table with the key __error set, and the value set to the error message to create a user error. You can use the standard error function for runtime errors. E.g.

-- User Error
return { __error = "You have reached the maximum number of tries in this 5 minute window." }

-- Runtime Error
error("Could not parse user ID for some reason")

Events

All Lua templates are invoked via events. As such, the first argument to the template is an Event. Event is a userdata. The below will explain the most important fields exposed by Event. Note that all fields, unless stated otherwise, are read-only:

  • ``

Template Context

All Lua templates are passed both the Event (denoted by args) and a TemplateContext userdata (denoted by token). Note that like Event, TemplateContext is a userdata (not a table). As such, they cannot be manually constructed in templates themselves.

"Executors" and other sensistive APIs use the TemplateContext to read template_data including the pragma (note that template_data is also exposed to templates as a read-only field). This is what allows AntiRaids capability system to correctly sandbox templates based on what capabilities they have been given.

Examples of executors include the @antiraid/actions ActionExecutor, which allows you to perform actions such as banning/kicking/timing out users and other Discord actions and @antiraid/kv KvExecutor which allow for persistent storage via a key-value interface.

TemplateContext is guaranteed to be valid while accessible in the VM . This means that templates can choose to share their capabilities with other templates using the __stack.

It is also guaranteed that the created executor is complete and does not rely on the token itself whatsoever after creation. This means that a template executor can be used after the template has finished executing (e.g. in a coroutine).

Example

local args, token = ...
print(token)

@antiraid/async

Utilities for asynchronous operations and timing

Methods

sleep

function sleep(duration: f64): f64

Sleep for a given duration.

Parameters

  • duration (f64): The duration to sleep for.

Returns

  • slept_time (f64): The actual duration slept for.

@antiraid/discord

This plugin allows for templates to interact with the Discord API

Types

Serenity.User

A user object in Discord, as represented by AntiRaid. Internal fields are subject to change

Refer to serenity::model::user::User for more documentation on what this type contains. Fields may be incomplete

{
  "id": "0",
  "username": "",
  "global_name": null,
  "avatar": null,
  "bot": false,
  "system": false,
  "mfa_enabled": false,
  "banner": null,
  "accent_color": null,
  "locale": null,
  "verified": null,
  "email": null,
  "flags": 0,
  "premium_type": 0,
  "public_flags": null,
  "member": null
}

Serenity.AuditLogs

A audit log in Discord, as represented by AntiRaid. Internal fields are subject to change

Refer to serenity::model::guild::audit_log::AuditLogs for more documentation on what this type contains. Fields may be incomplete

Serenity.AuditLogs.Action

An audit log action in Discord, as represented by AntiRaid. Internal fields are subject to change

Refer to serenity::model::guild::audit_log::Action for more documentation on what this type contains. Fields may be incomplete

1

Serenity.GuildChannel

A guild channel in Discord, as represented by AntiRaid. Internal fields are subject to change

Refer to serenity::model::channel::GuildChannel for more documentation on what this type contains. Fields may be incomplete

{
  "id": "0",
  "bitrate": null,
  "parent_id": null,
  "guild_id": "0",
  "type": 0,
  "owner_id": null,
  "last_message_id": null,
  "last_pin_timestamp": null,
  "name": "",
  "permission_overwrites": [],
  "position": 0,
  "topic": null,
  "user_limit": null,
  "nsfw": false,
  "rate_limit_per_user": null,
  "rtc_region": null,
  "video_quality_mode": null,
  "message_count": null,
  "member_count": null,
  "thread_metadata": null,
  "member": null,
  "default_auto_archive_duration": null,
  "permissions": null,
  "flags": 0,
  "total_message_sent": null,
  "available_tags": [],
  "applied_tags": [],
  "default_reaction_emoji": null,
  "default_thread_rate_limit_per_user": null,
  "status": null,
  "default_sort_order": null,
  "default_forum_layout": null
}

Serenity.PermissionOverwrite

A permission overwrite in Discord, as represented by AntiRaid. Internal fields are subject to change

Refer to serenity::model::channel::PermissionOverwrite for more documentation on what this type contains. Fields may be incomplete

{
  "allow": "2111062325329919",
  "deny": "2111062325329919",
  "id": "0",
  "type": 0
}

Serenity.ForumEmoji

A forum emoji in Discord, as represented by AntiRaid. Internal fields are subject to change

Refer to serenity::model::channel::ForumEmoji for more documentation on what this type contains. Fields may be incomplete

{
  "emoji_id": "0",
  "emoji_name": null
}

GetAuditLogOptions

Options for getting audit logs in Discord

{
  "action_type": 1,
  "user_id": "0",
  "before": "0",
  "limit": 0
}

Fields

GetChannelOptions

Options for getting a channel in Discord

{
  "channel_id": "0"
}

Fields

  • channel_id (string): The channel ID to get

EditChannelOptions

Options for editing a channel in Discord

{
  "channel_id": "0",
  "reason": "",
  "name": "my-channel",
  "type": 0,
  "position": 7,
  "topic": "My channel topic",
  "nsfw": true,
  "rate_limit_per_user": 5,
  "bitrate": null,
  "permission_overwrites": null,
  "parent_id": "0",
  "rtc_region": "us-west",
  "video_quality_mode": 1,
  "default_auto_archive_duration": 1440,
  "flags": 18,
  "available_tags": null,
  "default_reaction_emoji": {
    "emoji_id": "0",
    "emoji_name": null
  },
  "default_thread_rate_limit_per_user": null,
  "default_sort_order": null,
  "default_forum_layout": null
}

Fields

  • channel_id (string): The channel ID to edit
  • reason (string): The reason for editing the channel
  • name (string?): The name of the channel
  • type (string?): The type of the channel
  • position (number?): The position of the channel
  • topic (string?): The topic of the channel
  • nsfw (bool?): Whether the channel is NSFW
  • rate_limit_per_user (number?): The rate limit per user/Slow mode of the channel
  • bitrate (number?): The bitrate of the channel
  • permission_overwrites ({Serenity.PermissionOverwrite}?): The permission overwrites of the channel
  • parent_id (string??): The parent ID of the channel
  • rtc_region (string??): The RTC region of the channel
  • video_quality_mode (string?): The video quality mode of the channel
  • default_auto_archive_duration (string?): The default auto archive duration of the channel
  • flags (string?): The flags of the channel
  • available_tags ({Serenity.ForumTag}?): The available tags of the channel
  • default_reaction_emoji (Serenity.ForumEmoji??): The default reaction emoji of the channel
  • default_thread_rate_limit_per_user (number?): The default thread rate limit per user
  • default_sort_order (string?): The default sort order of the channel
  • default_forum_layout (string?): The default forum layout of the channel

EditThreadOptions

Options for editing a thread in Discord

{
  "channel_id": "0",
  "reason": "",
  "name": "my-thread",
  "archived": false,
  "auto_archive_duration": 1440,
  "locked": false,
  "invitable": true,
  "rate_limit_per_user": 5,
  "flags": 18,
  "applied_tags": null
}

Fields

  • channel_id (string): The channel ID to edit
  • reason (string): The reason for editing the channel
  • name (string?): The name of the thread
  • archived (bool?): Whether the thread is archived
  • auto_archive_duration (string?): The auto archive duration of the thread
  • locked (bool?): Whether the thread is locked
  • invitable (bool?): Whether the thread is invitable
  • rate_limit_per_user (number?): The rate limit per user/Slow mode of the thread
  • flags (string?): The flags of the thread
  • applied_tags ({Serenity.ForumTag}?): The applied tags of the thread

DeleteChannelOption

Options for deleting a channel in Discord

{
  "channel_id": "0",
  "reason": "My reason here"
}

Fields

  • channel_id (string): The channel ID to delete
  • reason (string): The reason for deleting the channel

CreateMessageEmbedField

A field in a message embed

{
  "name": "",
  "value": "",
  "inline": false
}

Fields

  • name (string): The name of the field
  • value (string): The value of the field
  • inline (bool): Whether the field is inline

CreateMessageEmbedAuthor

An author in a message embed

{
  "name": "",
  "url": null,
  "icon_url": null
}

Fields

  • name (string): The name of the author
  • url (string?): The URL of the author
  • icon_url (string?): The icon URL of the author

CreateMessageEmbedFooter

A footer in a message embed

{
  "text": "",
  "icon_url": null
}

Fields

  • text (string): The text of the footer
  • icon_url (string?): The icon URL of the footer

CreateMessageEmbed

An embed in a message

{
  "title": null,
  "description": null,
  "url": null,
  "timestamp": null,
  "color": null,
  "footer": null,
  "image": null,
  "thumbnail": null,
  "author": null,
  "fields": null
}

Fields

CreateMessageAttachment

An attachment in a message

{
  "filename": "",
  "description": null,
  "content": []
}

Fields

  • filename (string): The filename of the attachment
  • description (string?): The description (if any) of the attachment
  • content ({byte}): The content of the attachment

CreateMessage

Options for creating a message in Discord

{
  "embeds": null,
  "content": null,
  "attachments": null
}

Fields

MessageHandle

A handle to a message in Discord, as represented by AntiRaid. Internal fields are subject to change

Methods

MessageHandle:data
function MessageHandle:data(): any

Gets the data of the message

Returns
  • data (any): The inner data of the message

DiscordExecutor

DiscordExecutor allows templates to access/use the Discord API in a sandboxed form.

Methods

DiscordExecutor:get_audit_logs
function DiscordExecutor:get_audit_logs(data: GetAuditLogOptions): 

Gets the audit logs

Parameters
Returns
  • SerenityAuditLogs (): The audit log entry
DiscordExecutor:get_channel
function DiscordExecutor:get_channel(data: GetChannelOptions): 

Gets a channel

Parameters
Returns
  • Serenity.GuildChannel (): The guild channel
DiscordExecutor:edit_channel
function DiscordExecutor:edit_channel(data: EditChannelOptions): 

Edits a channel

Parameters
Returns
  • Serenity.GuildChannel (): The guild channel
DiscordExecutor:edit_thread
function DiscordExecutor:edit_thread(data: EditThreadOptions): 

Edits a thread

Parameters
Returns
  • Serenity.GuildChannel (): The guild channel
DiscordExecutor:delete_channel
function DiscordExecutor:delete_channel(data: DeleteChannelOption): 

Deletes a channel

Parameters
Returns
  • Serenity.GuildChannel (): The guild channel
DiscordExecutor:create_message
function DiscordExecutor:create_message(data: CreateMessage): 

Creates a message

Parameters
Returns
  • MessageHandle (): The message

Methods

new

function new(token: TemplateContext): DiscordExecutor

Parameters

Returns


@antiraid/interop

This plugin allows interoperability with AntiRaid and controlled interaction with the low-levels of AntiRaid templating subsystem.

Types

null

null is a special value that represents nothing. It is often used in AntiRaid instead of nil due to issues regarding existence etc. null is not equal to nil but is also an opaque type.

array_metatable

array_metatable is a special metatable that is used to represent arrays across the Lua-AntiRaid templating subsystem boundary. This metatable must be set on all arrays over this boundary and is required to ensure AntiRaid knows the value you're sending it is actually an array and not an arbitrary Luau table.

Methods

array_metatable

function array_metatable(): table

Returns the array metatable.

Returns

  • array_metatable (table): The array metatable.

null

function null(): null

Returns the null value.

Returns

  • null (null): The null value.

memusage

function memusage(): f64

Returns the current memory usage of the Lua VM.

Returns

  • memory_usage (f64): The current memory usage, in bytes, of the Lua VM.

guild_id

function guild_id(): string

Returns the current guild ID of the Lua VM.

Returns

  • guild_id (string): The current guild ID.

current_user

function current_user(): Serenity.User

Returns the current user of the Lua VM.

Returns


@antiraid/img_captcha

This plugin allows for the creation of text/image CAPTCHA's with customizable filters which can be useful in protecting against bots.

Types

CaptchaConfig

Captcha configuration. See examples for the arguments

{
  "char_count": 5,
  "filters": [
    {
      "filter": "Noise",
      "prob": 0.1
    },
    {
      "filter": "Wave",
      "f": 4.0,
      "amp": 2.0,
      "d": "horizontal"
    },
    {
      "filter": "Line",
      "p1": [
        1.0,
        0.0
      ],
      "p2": [
        20.0,
        20.0
      ],
      "thickness": 2.0,
      "color": {
        "r": 0,
        "g": 30,
        "b": 100
      }
    },
    {
      "filter": "RandomLine"
    },
    {
      "filter": "Grid",
      "y_gap": 30,
      "x_gap": 10
    },
    {
      "filter": "ColorInvert"
    }
  ],
  "viewbox_size": [
    512,
    512
  ],
  "set_viewbox_at_idx": null
}

Fields

  • filter (string): The name of the filter to use. See example for the parameters to pass for the filter as well as https://github.com/Anti-Raid/captcha.

Methods

new

function new(config: CaptchaConfig): {u8}

Creates a new CAPTCHA with the given configuration.

Parameters

  • config (CaptchaConfig): The configuration to use for the CAPTCHA.

Returns

  • captcha ({u8}): The created CAPTCHA object.

@antiraid/kv

Utilities for key-value operations.

Types

KvRecord

KvRecord represents a key-value record with metadata.

{
  "key": "",
  "value": null,
  "exists": false,
  "created_at": null,
  "last_updated_at": null
}

Fields

  • key (string): The key of the record.
  • value (any): The value of the record.
  • exists (bool): Whether the record exists.
  • created_at (datetime): The time the record was created.
  • last_updated_at (datetime): The time the record was last updated.

KvExecutor

KvExecutor allows templates to get, store and find persistent data within a server.

Methods

KvExecutor:find
function KvExecutor:find(key: string)
Parameters
  • key (string): The key to search for. % matches zero or more characters; _ matches a single character. To search anywhere in a string, surround {KEY} with %, e.g. %{KEY}%
KvExecutor:get
function KvExecutor:get(key: string)
Parameters
  • key (string): The key to get.
Returns
  • value (any): The value of the key.- exists (bool): Whether the key exists.
KvExecutor:getrecord
function KvExecutor:getrecord(key: string): KvRecord
Parameters
  • key (string): The key to get.
Returns
  • record (KvRecord): The record of the key.
KvExecutor:set
function KvExecutor:set(key: string, value: any)
Parameters
  • key (string): The key to set.
  • value (any): The value to set.
KvExecutor:delete
function KvExecutor:delete(key: string)
Parameters
  • key (string): The key to delete.

Methods

new

function new(token: TemplateContext): KvExecutor

Parameters

Returns


@antiraid/page

Create a page dedicated to your template on a server.

Types

Setting.Column

A setting column

{
  "id": "created_at",
  "name": "Created At",
  "description": "The time the record was created.",
  "column_type": {
    "Scalar": {
      "inner": {
        "TimestampTz": {}
      }
    }
  },
  "nullable": false,
  "suggestions": {
    "None": {}
  },
  "secret": false,
  "ignored_for": [
    "Create",
    "Update"
  ]
}

Fields

  • id (string): The ID of the column.
  • name (string): The name of the column.
  • description (string): The description of the column.
  • column_type (Setting.Column.ColumnType): The type of the column.
  • nullable (bool): Whether the column can be null.
  • suggestions (Setting.Column.ColumnSuggestion): The suggestions for the column.
  • secret (bool): Whether the column is secret.
  • ignored_for ({OperationType}): The operations that the column is ignored for [read-only]. It is not guaranteed that ignored field are sent to the template.

Setting

A setting

{
  "id": "setting_id",
  "name": "Setting Name",
  "description": "Setting Description",
  "primary_key": "id",
  "title_template": "{col1} - {col2}",
  "columns": [
    {
      "id": "col1",
      "name": "Column 1",
      "description": "Column 1 desc",
      "column_type": {
        "Scalar": {
          "inner": {
            "String": {
              "min_length": 120,
              "max_length": 120,
              "allowed_values": [
                "allowed_value"
              ],
              "kind": {
                "Normal": {}
              }
            }
          }
        }
      },
      "nullable": false,
      "suggestions": {
        "Static": {
          "suggestions": [
            "suggestion"
          ]
        }
      },
      "secret": false,
      "ignored_for": [
        "Create"
      ]
    },
    {
      "id": "col2",
      "name": "Column 2",
      "description": "Column 2 desc",
      "column_type": {
        "Array": {
          "inner": {
            "String": {
              "min_length": 120,
              "max_length": 120,
              "allowed_values": [
                "allowed_value"
              ],
              "kind": {
                "Token": {
                  "default_length": 10
                }
              }
            }
          }
        }
      },
      "nullable": false,
      "suggestions": {
        "Static": {
          "suggestions": [
            "suggestion"
          ]
        }
      },
      "secret": false,
      "ignored_for": [
        "View"
      ]
    },
    {
      "id": "col3",
      "name": "Column 3",
      "description": "Column 3 desc",
      "column_type": {
        "Array": {
          "inner": {
            "String": {
              "min_length": 120,
              "max_length": 120,
              "allowed_values": [
                "allowed_value"
              ],
              "kind": {
                "Textarea": {
                  "ctx": "anything"
                }
              }
            }
          }
        }
      },
      "nullable": false,
      "suggestions": {
        "Static": {
          "suggestions": [
            "suggestion"
          ]
        }
      },
      "secret": false,
      "ignored_for": [
        "Create"
      ]
    },
    {
      "id": "col4",
      "name": "Column 4",
      "description": "Column 4 desc",
      "column_type": {
        "Array": {
          "inner": {
            "String": {
              "min_length": 120,
              "max_length": 120,
              "allowed_values": [
                "allowed_value"
              ],
              "kind": {
                "TemplateRef": {}
              }
            }
          }
        }
      },
      "nullable": false,
      "suggestions": {
        "Static": {
          "suggestions": [
            "suggestion"
          ]
        }
      },
      "secret": false,
      "ignored_for": [
        "Create"
      ]
    },
    {
      "id": "col5",
      "name": "Column 5",
      "description": "Column 5 desc",
      "column_type": {
        "Array": {
          "inner": {
            "String": {
              "min_length": 120,
              "max_length": 120,
              "allowed_values": [
                "allowed_value"
              ],
              "kind": {
                "KittycatPermission": {}
              }
            }
          }
        }
      },
      "nullable": false,
      "suggestions": {
        "Static": {
          "suggestions": [
            "suggestion"
          ]
        }
      },
      "secret": false,
      "ignored_for": [
        "Create"
      ]
    },
    {
      "id": "col6",
      "name": "Column 6",
      "description": "Column 6 desc",
      "column_type": {
        "Array": {
          "inner": {
            "String": {
              "min_length": 120,
              "max_length": 120,
              "allowed_values": [
                "allowed_value"
              ],
              "kind": {
                "User": {}
              }
            }
          }
        }
      },
      "nullable": false,
      "suggestions": {
        "Static": {
          "suggestions": [
            "suggestion"
          ]
        }
      },
      "secret": false,
      "ignored_for": [
        "Create"
      ]
    },
    {
      "id": "col7",
      "name": "Column 7",
      "description": "Column 7 desc",
      "column_type": {
        "Array": {
          "inner": {
            "String": {
              "min_length": 120,
              "max_length": 120,
              "allowed_values": [
                "allowed_value"
              ],
              "kind": {
                "Role": {}
              }
            }
          }
        }
      },
      "nullable": false,
      "suggestions": {
        "Static": {
          "suggestions": [
            "suggestion"
          ]
        }
      },
      "secret": false,
      "ignored_for": [
        "Create"
      ]
    },
    {
      "id": "col8",
      "name": "Column 8",
      "description": "Column 8 desc",
      "column_type": {
        "Array": {
          "inner": {
            "String": {
              "min_length": 120,
              "max_length": 120,
              "allowed_values": [
                "allowed_value"
              ],
              "kind": {
                "Channel": {
                  "needed_bot_permissions": "2048",
                  "allowed_channel_types": [
                    0,
                    2
                  ]
                }
              }
            }
          }
        }
      },
      "nullable": false,
      "suggestions": {
        "Static": {
          "suggestions": [
            "suggestion"
          ]
        }
      },
      "secret": false,
      "ignored_for": [
        "Update"
      ]
    },
    {
      "id": "col9",
      "name": "Column 9",
      "description": "Column 9 desc",
      "column_type": {
        "Scalar": {
          "inner": {
            "Integer": {}
          }
        }
      },
      "nullable": false,
      "suggestions": {
        "Static": {
          "suggestions": [
            "suggestion"
          ]
        }
      },
      "secret": false,
      "ignored_for": [
        "Update"
      ]
    },
    {
      "id": "col10",
      "name": "Column 10",
      "description": "Column 10 desc",
      "column_type": {
        "Scalar": {
          "inner": {
            "Boolean": {}
          }
        }
      },
      "nullable": false,
      "suggestions": {
        "Static": {
          "suggestions": [
            "suggestion"
          ]
        }
      },
      "secret": false,
      "ignored_for": [
        "Update"
      ]
    },
    {
      "id": "created_at",
      "name": "Created At",
      "description": "The time the record was created.",
      "column_type": {
        "Scalar": {
          "inner": {
            "TimestampTz": {}
          }
        }
      },
      "nullable": false,
      "suggestions": {
        "None": {}
      },
      "secret": false,
      "ignored_for": [
        "Create",
        "Update"
      ]
    }
  ],
  "operations": []
}

Fields

  • id (string): The ID of the setting.
  • name (string): The name of the setting.
  • description (string): The description of the setting.
  • operations ({OperationType}): The operations that can be performed on the setting. Note that when using add_settings, you must pass this as the second argument to settings and ignore this field.
  • primary_key (string): The primary key of the setting that UNIQUELY identifies the row. When Delete is called, the value of this is what will be sent in the event. On Update, this key MUST also exist (otherwise, the template MUST error out)
  • title_template (string): The template for the title of each row for the setting. This is a string that can contain placeholders for columns. The placeholders are in the form of {column_id}. For example, if you have a column with ID col1 and another with ID col2, you can have a title template of {col1} - {col2} etc..
  • columns ({Setting.Column}): The columns of the setting.

CreatePageSetting

A table containing a setting for a page

Fields

  • setting (Setting): The setting to add to the page.
  • operations ({string}): The operations to perform on the setting. Elements of the array can be either View, Create, Update or Delete.

CreatePage

An intermediary structure for creating a page for a template

Fields

  • page_id (string): The ID of the page. This field can be updated ONLY if the page is not created yet with no current settings. The ID must not contain spaces, newlines, null characters, or tabs.
  • title (string): The title of the page. This field can be updated ONLY if the page is not created yet.
  • description (string): The description of the page. This field can be updated ONLY if the page is not created yet.
  • settings (table): The settings of the page. This field is read-only.
  • is_created (bool): Whether the page is created. This field is read-only.
  • template (Template): The template of the page. This field is read-only.

Methods

CreatePage:add_setting
function CreatePage:add_setting(setting: CreatePageSetting): nil
Parameters
Returns

Enums

Setting.Column.InnerColumnType

The inner column type of the value

Setting.Column.ColumnType

The type of a setting column

Variants

Setting.Column.ColumnType::Scalar

A scalar column type.

Fields

Setting.Column.ColumnType::Array

An array column type.

Fields

Methods

create_page

function create_page(token: TemplateContext): CreatePage

Parameters

Returns

  • create_page (CreatePage): An empty created page.

@antiraid/permissions

Utilities for handling permission checks.

Types

PermissionResult

PermissionResult is an internal type containing the status of a permission check in AntiRaid. The exact contents are undocumented as of now

LuaPermissionResult

LuaPermissionResult is a type containing the status of a permission check in AntiRaid with prior parsing done for Lua.

{
  "result": {
    "var": "Ok"
  },
  "is_ok": true,
  "code": "Ok",
  "markdown": ""
}

Fields

  • result (PermissionResult): The raw/underlying result of the permission check.
  • is_ok (bool): Whether the permission check was successful.
  • code (string): The code of the permission check.
  • markdown (string): The markdown representation of the permission check.

PermissionCheck

PermissionCheck is a type containing the permissions to check for a user.

{
  "kittycat_perms": [],
  "native_perms": [],
  "inner_and": false
}

Fields

  • kittycat_perms ({Permission}): The kittycat permissions needed to run the command.
  • native_perms ({string}): The native permissions needed to run the command.
  • inner_and (bool): Whether or not the perms are ANDed (all needed) or OR'd (at least one)

Permission

Permission is the primitive permission type used by AntiRaid. See https://github.com/InfinityBotList/kittycat for more information

{
  "namespace": "moderation",
  "perm": "ban",
  "negator": false
}

Fields

  • namespace (string): The namespace of the permission.
  • perm (string): The permission bit on the namespace.
  • negator (bool): Whether the permission is a negator permission or not

Methods

permission_from_string

function permission_from_string(perm_string: string): Permission

Returns a Permission object from a string.

Parameters

  • perm_string (string): The string to parse into a Permission object.

Returns

  • permission (Permission): The parsed Permission object.

permission_to_string

function permission_to_string(permission: Permission): string

Returns a string from a Permission object.

Parameters

  • permission (Permission): The Permission object to parse into a string.

Returns

  • perm_string (string): The parsed string.

has_perm

function has_perm(permissions: {Permission}, permission: Permission): bool

Checks if a list of permissions in Permission object form contains a specific permission.

Parameters

Returns

  • has_perm (bool): Whether the permission is present in the list of permissions as per kittycat rules.

has_perm_str

function has_perm_str(permissions: {string}, permission: string): bool

Checks if a list of permissions in canonical string form contains a specific permission.

Parameters

  • permissions ({string}): The list of permissions
  • permission (string): The permission to check for.

Returns

  • has_perm (bool): Whether the permission is present in the list of permissions as per kittycat rules.

check_perms

function check_perms(check: PermissionCheck, member_native_perms: Permissions, member_kittycat_perms: {Permission}): LuaPermissionResult

Checks if a permission check passes.

Parameters

  • check (PermissionCheck): The permission check to evaluate.
  • member_native_perms (Permissions): The native permissions of the member.
  • member_kittycat_perms ({Permission}): The kittycat permissions of the member.

Returns


@antiraid/typesext

Extra types used by Anti-Raid Lua templating subsystem to either add in common functionality such as streams or handle things like u64/i64 types performantly.

Types

LuaStream

LuaStream provides a stream implementation. This is returned by MessageHandle's await_component_interaction for instance for handling button clicks/select menu choices etc.

Methods

LuaStream:next
function LuaStream:next(): <T>

Returns the next item in the stream.

Returns
  • item (): The next item in the stream.
LuaStream:for_each
function LuaStream:for_each(callback: function)

Executes a callback for every entry in the stream.

Parameters
  • callback (function): The callback to execute for each entry.

MultiOption

MultiOption allows distinguishing between null and empty fields. Use the value to show both existence and value (Some(Some(value))) an empty object to show existence (Some(None)) or null to show neither (None)

U64

U64 is a 64-bit unsigned integer type. Implements Add/Subtract/Multiply/Divide/Modulus/Power/Integer Division/Equality/Comparison (Lt/Le and its complements Gt/Ge) and ToString with a type name of U64

Methods

U64:to_ne_bytes
function U64:to_ne_bytes(): {u8}

Converts the U64 to a little-endian byte array.

Returns
  • bytes ({u8}): The little-endian byte array.
U64:from_ne_bytes
function U64:from_ne_bytes(bytes: {u8}): U64

Converts a little-endian byte array to a U64.

Parameters
  • bytes ({u8}): The little-endian byte array.
Returns
  • u64 (U64): The U64 value.
U64:to_le_bytes
function U64:to_le_bytes(): {u8}

Converts the U64 to a little-endian byte array.

Returns
  • bytes ({u8}): The little-endian byte array.
U64:from_le_bytes
function U64:from_le_bytes(bytes: {u8}): U64

Converts a little-endian byte array to a U64.

Parameters
  • bytes ({u8}): The little-endian byte array.
Returns
  • u64 (U64): The U64 value.
U64:to_be_bytes
function U64:to_be_bytes(): {u8}

Converts the U64 to a big-endian byte array.

Returns
  • bytes ({u8}): The big-endian byte array.
U64:from_be_bytes
function U64:from_be_bytes(bytes: {u8}): U64

Converts a big-endian byte array to a U64.

Parameters
  • bytes ({u8}): The big-endian byte array.
Returns
  • u64 (U64): The U64 value.
U64:to_i64
function U64:to_i64(): I64

Converts the U64 to an i64.

Returns
  • i64 (I64): The i64 value.

I64

I64 is a 64-bit signed integer type. Implements Add/Subtract/Multiply/Divide/Modulus/Power/Integer Division/Equality/Comparison (Lt/Le and its complements Gt/Ge) and ToString with a type name of I64

Methods

I64:to_ne_bytes
function I64:to_ne_bytes(): {u8}

Converts the I64 to a little-endian byte array.

Returns
  • bytes ({u8}): The little-endian byte array.
I64:from_ne_bytes
function I64:from_ne_bytes(bytes: {u8}): I64

Converts a little-endian byte array to a I64.

Parameters
  • bytes ({u8}): The little-endian byte array.
Returns
  • i64 (I64): The I64 value.
I64:to_le_bytes
function I64:to_le_bytes(): {u8}

Converts the I64 to a little-endian byte array.

Returns
  • bytes ({u8}): The little-endian byte array.
I64:from_le_bytes
function I64:from_le_bytes(bytes: {u8}): I64

Converts a little-endian byte array to a I64.

Parameters
  • bytes ({u8}): The little-endian byte array.
Returns
  • i64 (I64): The I64 value.
I64:to_be_bytes
function I64:to_be_bytes(): {u8}

Converts the I64 to a big-endian byte array.

Returns
  • bytes ({u8}): The big-endian byte array.
I64:from_be_bytes
function I64:from_be_bytes(bytes: {u8}): I64

Converts a big-endian byte array to a I64.

Parameters
  • bytes ({u8}): The big-endian byte array.
Returns
  • i64 (I64): The I64 value.
I64:to_u64
function I64:to_u64(): U64

Converts the I64 to a U64.

Returns
  • u64 (U64): The U64 value.

bitu64

bit32 but for U64 datatype. Note that bit64 is experimental and may not be properly documented at all times. When in doubt, reach for Luau's bit32 documentation and simply replace 31's with 63's

Methods

bitu64:band
function bitu64:band(values: {U64}): U64

Performs a bitwise AND operation on the given values.

Parameters
  • values ({U64}): The values to perform the operation on.
Returns
  • result (U64): The result of the operation.
bitu64:bnor
function bitu64:bnor(n: U64): U64

Performs a bitwise NOR operation on the given value.

Parameters
  • n (U64): The value to perform the operation on.
Returns
  • result (U64): The result of the operation.
bitu64:bor
function bitu64:bor(values: {U64}): U64

Performs a bitwise OR operation on the given values.

Parameters
  • values ({U64}): The values to perform the operation on.
Returns
  • result (U64): The result of the operation.
bitu64:bxor
function bitu64:bxor(values: {U64}): U64

Performs a bitwise XOR operation on the given values.

Parameters
  • values ({U64}): The values to perform the operation on.
Returns
  • result (U64): The result of the operation.
bitu64:btest
function bitu64:btest(values: {U64}): bool

Tests if the bitwise AND of the given values is not zero.

Parameters
  • values ({U64}): The values to perform the operation on.
Returns
  • result (bool): True if the bitwise AND of the values is not zero, false otherwise.
bitu64:extract
function bitu64:extract(n: U64, f: u64, w: u64): U64

Extracts a field from a value.

Parameters
  • n (U64): The value to extract the field from.
  • f (u64): The field to extract.
  • w (u64): The width of the field to extract.
Returns
  • result (U64): The extracted field.
bitu64:lrotate
function bitu64:lrotate(n: U64, i: i64): U64

Rotates a value left or right.

Parameters
  • n (U64): The value to rotate.
  • i (i64): The amount to rotate by.
Returns
  • result (U64): The rotated value.
bitu64:lshift
function bitu64:lshift(n: U64, i: i64): U64

Shifts a value left or right.

Parameters
  • n (U64): The value to shift.
  • i (i64): The amount to shift by.
Returns
  • result (U64): The shifted value.
bitu64:replace
function bitu64:replace(n: U64, v: U64, f: u64, w: u64): U64

Replaces a field in a value.

Parameters
  • n (U64): The value to replace the field in.
  • v (U64): The value to replace the field with.
  • f (u64): The field to replace.
  • w (u64): The width of the field to replace.
Returns
  • result (U64): The value with the field replaced.
bitu64:rrotate
function bitu64:rrotate(n: U64, i: i64): U64

Rotates a value left or right.

Parameters
  • n (U64): The value to rotate.
  • i (i64): The amount to rotate by.
Returns
  • result (U64): The rotated value.
bitu64:rshift
function bitu64:rshift(n: U64, i: i64): U64

Shifts a value left or right.

Parameters
  • n (U64): The value to shift.
  • i (i64): The amount to shift by.
Returns
  • result (U64): The shifted value.

Methods

U64

function U64(value: u64): U64

Creates a new U64.

Parameters

  • value (u64): The value of the U64.

Returns

  • u64 (U64): The U64 value.

I64

function I64(value: i64): I64

Creates a new I64.

Parameters

  • value (i64): The value of the I64.

Returns

  • i64 (I64): The I64 value.

Primitives

u8

type u8 = number

An unsigned 8-bit integer. Note: u8 arrays ({u8}) are often used to represent an array of bytes in AntiRaid

Constraints

  • range: The range of values this number can take on (accepted values: 0-255)

u16

type u16 = number

An unsigned 16-bit integer.

Constraints

  • range: The range of values this number can take on (accepted values: 0-65535)

u32

type u32 = number

An unsigned 32-bit integer.

Constraints

  • range: The range of values this number can take on (accepted values: 0-4294967295)

u64

type u64 = number

An unsigned 64-bit integer. Note that most, if not all, cases of i64 in the actual API are either string or the I64 custom type from typesext

Constraints

  • range: The range of values this number can take on (accepted values: 0-18446744073709551615)

i8

type i8 = number

A signed 8-bit integer.

Constraints

  • range: The range of values this number can take on (accepted values: -128-127)

i16

type i16 = number

A signed 16-bit integer.

Constraints

  • range: The range of values this number can take on (accepted values: -32768-32767)

i32

type i32 = number

A signed 32-bit integer.

Constraints

  • range: The range of values this number can take on (accepted values: -2147483648-2147483647)

i64

type i64 = number

A signed 64-bit integer. Note that most, if not all, cases of i64 in the actual API are either string or the I64 custom type from typesext

Constraints

  • range: The range of values this number can take on (accepted values: -9223372036854775808-9223372036854775807)

f32

type f32 = number

A 32-bit floating point number.

Constraints

  • range: The range of values this number can take on (accepted values: IEEE 754 single-precision floating point)

f64

type f64 = number

A 64-bit floating point number.

Constraints

  • range: The range of values this number can take on (accepted values: IEEE 754 double-precision floating point)

bool

type bool = boolean

A boolean value.


char

type char = string

A single Unicode character.

Constraints

  • length: The length of the string (accepted values: 1)

string

type string = string

A UTF-8 encoded string.

Constraints

  • encoding: Accepted character encoding (accepted values: UTF-8 only)

function

type function = function

A Lua function.


Methods

array

function array(...: unknown): {unknown}

Helper method to create an array from a list of tables, setting the array_metatable on the result.

Parameters

  • ... (unknown): The elements used to form the array.

Returns

Types

Event

An event that has been dispatched to the template. This is what args is in the template.

Fields

  • title (string): The title name of the event.
  • base_name (string): The base name of the event.
  • name (string): The name of the event.
  • data (unknown): The data of the event.
  • is_deniable (boolean): Whether the event can be denied.
  • uid (string): The unique identifier ID of the event. Will be guaranteed to be unique at a per-guild level.
  • author (string?): The author of the event, if any. If there is no known author, this field will either be nil or null.

TemplatePragma

TemplatePragma contains the pragma of the template. Note that the list of fields below in non-exhaustive as templates can define extra fields on the pragma as well

{
  "lang": "lua",
  "allowed_caps": []
}

Fields

  • lang (string): The language of the template.
  • allowed_caps ({string}): The allowed capabilities provided to the template.

TemplateData

TemplateData is a struct that represents the data associated with a template token. It is used to store the path and pragma of a template token.

{
  "path": "test",
  "template": {
    "Named": "foo"
  },
  "pragma": {
    "lang": "lua",
    "allowed_caps": []
  }
}

Fields

  • path (string): The path of the template token.
  • pragma (TemplatePragma): The pragma of the template.

TemplateContext

TemplateContext is a struct that represents the context of a template. Stores data including the templates data, pragma and what capabilities it should have access to. Passing a TemplateContext is often required when using AntiRaid plugins for security purposes.

Fields

  • template_data (TemplateData): The data associated with the template.

Example Templates

To help you get started with templating, we have provided a few examples below along with explanations of what/how they work

Example 1: Simple Audit Logging

Explanation

1. Pragma

The first line of the template is a pragma. This is a special statement beginning with @pragma or -- @pragma that tells AntiRaid what language the template is written in, what options to use, and how tools such as CI, websites and other automation should handle your template. The rest of the pragma is a JSON object that contains the options. Note that if you do not provide a pragma, a default one will be used, however this will not allow you to provide capabilities to your template such as sending a message on Discord etc.

In this case, we want to tell AntiRaid that we are coding a template in Lua and that we want to allow the capability to send messages to Discord. This is done by adding the allowed_caps key to the pragma and specifying the capabilities we want to allow as seen below:

-- @pragma {"lang":"lua","allowed_caps":["discord:create_message"]}

Another example of pragma is on the Website:

Pragma on website image

{"lang":"lua","builderInfo":{"ver":1,"data":{"embeds":[{"title":"Test","description":"","fields":[]}],"content":""},"checksum":"72e7225dfa2725a979fccc763e2e5bac3855f1cd3c10b5cd00c90b53e724db23"},"allowed_caps":["discord:create_message"]}

Here, notice that the builderInfo contains the embeds, content, and checksum of the template. When the user wants to reread their template, the website only has to reread the pragma statement to reconstruct the state and show the right tab (either Builder if they are just making a simple embed, or Advanced if they were making changes to the content of the template itself). Without the pragma, the website would have to use its own arcane syntax on top of comments or execute the template just to reconstruct state.

2. Creating a message

Next, we need to extract the arguments and token from the context. The arguments are passed to the template when it is executed and contain all the data we need to work with. The token is used to authenticate the template and gain access to the templates context in privileged AntiRaid API's. All of this is provided using variable arguments.

local args, token = ...

There are 3 things we want to import for this template to work. The first is the Discord module, which allows us to send messages to Discord. The second is the Interop module, which provides some functions allowing for seamless interoperability between your template and AntiRaid. The third is the Formatters module, which provides some helper methods for formatting audit log fields (formally known as gwevent_fields).

local discord = require "@antiraid/discord"
local interop = require "@antiraid/interop"
local formatter = require "@antiraid/formatters"

Next, we create the embed. args.event_titlename is specific to Audit Logs and contains the friendly name for an event.

-- Make the embed
local embed = {
    title = args.event_titlename, 
    description = "", -- Start with empty description
}

NOTE: You can use the API Reference to see what functions are available in the AntiRaid SDK

3. Adding fields

TIP: When making a template for a Gateway Event, the fields are passed to the template through a table named fields.

The next step is to add fields to the embed. In this case, we can do this by iterating over fields. In Lua, tables can be iterated over using the builtin pairs function like below:

for key, value in pairs(my_table) do
    -- Do something with key and value
end

In the same way, we can now iterate over args.event_data:

for key, value in pairs(args.event_data) do
    -- Do something with key and value
end

When using Audit Log Events, there are two cases to pay attention to, the first is the field itself being nil and the second is the field type being None. In both cases, we don't want to add the field to the embed. Lets do that!

local should_set = false

if value ~= nil and value.field.type ~= "None" then
    should_set = true
end

Lastly, we need to format the field and add it to the description. Luckily, the formatter plugin provides a function for formatting any categorized field. This function is called format_gwevent_field.

local formatted_value = formatter.format_gwevent_field(value)

4. Sending the message

Finally, we can send the message using the Discord module. To do this, we need to create a message object and set the embeds property to an array containing our embed. This is also where interop comes in handy, as we need to set the metatable of the embeds array to the interop array metatable so AntiRaid knows that embeds is an array. Next, we use the Discord plugin to make a new Discord action executor which then lets us send the message to the specified channel (args.sink is another Audit Log specific variable that contains the channel ID/whatever sink is set to in the database).

local message = { embeds = {} }
setmetatable(message.embeds, interop.array_metatable)

table.insert(message.embeds, embed)

-- Send message using action executor
local discord_executor = discord.new(token);
discord_executor:create_message({
    channel_id = args.sink,
    message = message
})

Finally, we can put the entire loop together as so:

-- @pragma {"lang":"lua","allowed_caps":["discord:create_message"]}
local args, token = ...
local discord = require "@antiraid/discord"
local interop = require "@antiraid/interop"
local formatter = require "@antiraid/formatters"

-- Make the embed
local embed = {
    title = args.event_titlename, 
    description = "", -- Start with empty description
}

-- Add the event data to the description
for key, value in pairs(args.event_data) do
    local should_set = false

    if value ~= nil and value.type ~= "None" then
        should_set = true
    end

    if should_set then
        local formatted_value = formatter.format_gwevent_field(value)
        embed.description = embed.description .. "**" .. key:gsub("_", " "):upper() .. "**: " .. formatted_value .. "\n"
    end
end

local message = { embeds = {} }
setmetatable(message.embeds, interop.array_metatable)

table.insert(message.embeds, embed)

-- Send message using action executor
local discord_executor = discord.new(token);
discord_executor:create_message({
    channel_id = args.sink,
    message = message
})

Luau Ecosystem Integration Notes

At AntiRaid, we believe that bot developers should be able to access the best tools available to them. To this end, we have integrated some popular Luau libraries into our templating environment to provide a more powerful and flexible experience while taking into account security and stability of the bot. Below, we outline the libraries that are currently available for use in your Lua templates:

Libraries

AntiRaid is not affiliated with any of the libraries mentioned above. We do not claim ownership of these libraries or their trademarks whatsoever, nor do we provide any guarantees or warranties regarding their functionality. AntiRaid absolves all liability for any damages or losses incurred through the use of these libraries, both to the libraries owner and to AntiRaid itself.

Hooks And Events

AntiRaid makes use of events for all communication between modules. Modules can either recieve events (e.g. from Discord) or generate their own events (such as PunishmentCreate/PunishmentExpire). This is what makes Anti-Raid so flexible and powerful, especially in templating.

Hooks is a module that listens for all Anti-Raid events and lets you dispatch a template whener an event is recieved (or generated) by Anti-Raid.

Discord Events

Discord Events are special. AntiRaid uses Serenity for handling Discord events. As such, please see Serenity's Documentation for what fields are available in each event. It's much better documentation than what we can come up with.

AntiRaid does not modify the events in any way, so you can expect the same fields as what Serenity provides. Note, however, that only the fields are available to templates, not the methods provided by Serenity.

Captcha

Introduction

WARNING: Captcha's are being heavily churned/rewritten into a new Lua plugin

A CAPTCHA is a security measure that can be used to try and filter out the more basic and spammy bots from accessing your server. It presents a challenge that users must complete to prove they are human. This can help protect your server from unwanted automated access and ensure that real users can interact with your community. Note that CAPTCHA's are NOT foolproof and that more sophistiated bots may still bypass them.

Of course, with the current landscape with AI everywhere, just serving a single CAPTCHA type (or config) does not really work. Furthermore, AntiRaid recognizes that different servers have different needs. Therefore, we provide a flexible CAPTCHA system, fully integrated with our Lua Templating system that allows you to completely customize the CAPTCHA experience for your users. This occurs through filters.

Examples

Sample CAPTCHA

local interop = require "@antiraid/interop"
local img_captcha = require "@antiraid/img_captcha"

local captcha_config = {}

-- Basic options
captcha_config.char_count = 7
captcha_config.filters = {}
setmetatable(captcha_config.filters, interop.array_metatable) -- Filters is an array
captcha_config.viewbox_size = { 280, 160 }
setmetatable(captcha_config.viewbox_size, interop.array_metatable) -- Viewbox size is a tuple

-- Add noise filter
local noise_filter = {
    filter = "Noise",
    prob = 0.05
}

table.insert(captcha_config.filters, noise_filter)

-- Add wave filter
local wave_filter = {
    filter = "Wave",
    f = 4.0, -- Frequency
    amp = 20.0, -- Amplitude
    d = "horizontal" -- Direction
}

table.insert(captcha_config.filters, wave_filter)

-- Add grid filter
local grid_filter = {
    filter = "Grid",
    x_gap = 10,
    y_gap = 30
}

table.insert(captcha_config.filters, grid_filter)

-- Add line filter
local line_filter = {
    filter = "Line",
    p1 = setmetatable({ 0.0, 0.0 }, interop.array_metatable),
    p2 = setmetatable({ 30.0, 100.0 }, interop.array_metatable),
    thickness = 7.0,
    color = setmetatable({ 0, 0, 0 }, interop.array_metatable)
}

table.insert(captcha_config.filters, line_filter)

-- Add color invert filter
local color_invert_filter = {
    filter = "ColorInvert"
}

table.insert(captcha_config.filters, color_invert_filter)

-- Add random line filter
local random_line_filter = {
    filter = "RandomLine"
}

table.insert(captcha_config.filters, random_line_filter)

local captcha = img_captcha.new(captcha_config)

return captcha

CAPTCHA with increasing char count with maximum of 5 tries per user

local args, token = ...
local interop = require "@antiraid/interop"
local img_captcha = require "@antiraid/img_captcha"

local captcha_config = {}

-- Check __stack.users
if __stack._captcha_user_tries == nil then
    __stack._captcha_user_tries = {} -- Initialize users table
end

-- Check __stack._captcha_user_tries[args.user.id]
if __stack._captcha_user_tries[args.user.id] == nil then
    __stack._captcha_user_tries[args.user.id] = 0 -- Initialize user's try count
end

-- Check if user has reached maximum tries
if __stack._captcha_user_tries[args.user.id] >= 5 then
    return { __error = "You have reached the maximum number of tries in this 5 minute window."}
end

-- Basic options
captcha_config.char_count = math.min(7 + __stack._captcha_user_tries[args.user.id], 10) -- Increment the number of characters

captcha_config.filters = {}
setmetatable(captcha_config.filters, interop.array_metatable) -- Filters is an array
captcha_config.viewbox_size = { 280, 160 }
setmetatable(captcha_config.viewbox_size, interop.array_metatable) -- Viewbox size is a tuple

-- Increment the maximum number of tries
__stack._captcha_user_tries[args.user.id] += 1

captcha = img_captcha.new(captcha_config)
return captcha

Lockdowns

Lockdowns are a way to allow restricting (or 'locking down') specific channels or roles within a server when under an attack or other such crises.

Migrating from other bots

If you are coming from Wick or another anti-nuke bot like Wick, please note that AntiRaid's lockdown functionality only applies to locking down channels and roles. This means that the following Wick features are not present in AntiRaid lockdowns and are instead part of other more appropriate modules as listed below:

  • Join Auto Kick (present in Inspector Auto Response Member Join)
  • Join Auto Ban (present in Inspector Auto Response Member Join)
  • Role Lockdown (WIP, not yet implemented)
  • All / Server Wide (Most likely will not be implemented)

Note that blind lockdowns are not yet implemented in Anti-Raid.

For the sake of comparisons, here is how each lockdown mode compares to Wick's lockdown modes:

  • Quick Server Lockdown (qsl) -> Wick's Channels (sc) lockdown (general performance+requirements for use should be the same as Wick's lockdown feature)
  • Traditional Server Lockdown (tsl) -> No equivalent in Wick
  • Single-Channel Lockdown (scl) -> Wick's Channel (c) lockdown (note that locking down multiple specific channels at once is not yet implemented in AntiRaid)

Usage Notes

If you want to know more details on each type of lockdown, how they are applied and how multiple lockdown conflicts are resolved, please refer to the dev docs for lockdown

Member Roles

When you first setup lockdown for the first time, you will be prompted for a set of member roles like below:

picture of member roles screen in settings page

These roles are what AntiRaid will actually lock-down which is why they are also known as 'critical roles'.

Quick Server Lockdowns

For servers that can meet its restrictions, a quick server lockdown is the fastest way to lockdown your server in a raid. It is recommended to use this lockdown mode if possible. When using this mode, it is important to note one critical requirement:

  • All critical roles must have View Channel and Send Messages. All other roles must not have View Channel and Send Messages.

What this looks like is something like the following:

Picture of a normal role Figure 1 shows a normal role without View Channel of Send Messages permissions

Picture of a critical role Figure 2 shows a critical role with View Channel and Send Messages permissions

The above two figures are how you want to configure your critical/member and normal roles. Basically, turn off View Channel and Send Messages for all your normal roles and turn it on for your critical/member roles you set up earlier in the settings for lockdown.

To make a quick server lockdown, you can use the qsl type. For example, to lock down a server, you can use the following slash command:

/lockdown lock type:qsl reason:There is a raid going on

Traditional Server Lockdown

Traditional Server Lockdown is a more traditional lockdown method. It is more flexible than Quick Server Lockdown as it has no required prior setup. However, it is much slower and should be avoided if possible.

WARNING: Super large servers may have outages when using a traditional server lockdown that a quick server lockdown may not lead to.

To make a traditional server lockdown, you can use the tsl type. For example, to lock down a server, you can use the following slash command:

/lockdown lock type:tsl reason:There is a raid going on

Single-Channel Lockdown

In some cases, only a single channel needs to be locked down. In such a case, a single-channel lockdown is needed.

To make a single-channel lockdown, you can use the scl type. For example, to lock down a server, you can use the following slash command:

/lockdown lock type:scl/<channel_id> reason:There is a raid going on

Where <channel_id> is the ID of the channel to lockdown.

Backups

What's backed up

Guild structure (roles/channels/name/icon/other settings), some messages (Discord has limits here) and attachments (within reasonable limits).

What's not backed up?

Members and bots. Note that for members, we have something coming up soon that may allow you to 'backup/restore' (with consent + regularly re-providing consent) members.

Note that new discord features may also not be backed up/restored immediately on release.

More details

See the dev guide to learn more about the format

Go Jobserver

The Go Jobserver handles server backups, message prunes and other long running tasks on a server

Backups

Note that this document describes the technical details of the backup system

Format

A backup is an https://github.com/infinitybotlist/iblfile with the standard AutoEncryptedFile format and has the following fields:

  • backup_opts - JSON containing a types.BackupCreateOpts object
  • core/guild - The core guild data (discordgo.Guild)
  • assets/{asset_name} - The guild icon data ([]byte)
  • messages/{channel_id} - The messages in a channel along with basic attachment metadata ([]types.BackupMessage).
  • dbg/* - Debug information. This may vary across backups and MUST NOT be used in restoring a backup.
  • attachments/{attachment_id} - The attachments data itself

Definitions

  • Critical Roles: the roles which are given to members and should hence be locked down. In essence, one can define a set of critical roles (hence the name) which are either the critical roles and defaults to the @everyone role if not set.

Lockdown Types

Quick Server Lockdown

Specificity

  • 0 (Lowest specificity)

Rationale

Quickly lockdown a server as fast as possible

Syntax

  • qsl

Description

Quick Lockdown allows for quickly locking down a server given the following permission overwrite setup:

  • All critical roles must have View Channel and Send Messages. All other roles must not have View Channel and Send Messages

Internally, qsl modifies only the critical roles to the locked down set of permissions. This requires much fewer API calls and is hence much faster than traditional lockdowns.

Traditional Server Lockdown

Specificity

  • 1 (TSL > QSL as it updates all channels in a server)

Rationale

In many cases, the requirements for qsl are not feasible for servers to meet. In such a case, a traditional lockdown is needed.

Syntax

  • tsl

Description

Traditional Lockdown is a more traditional lockdown method. It is more flexible than qsl as it has no required prior setup. However, it is much slower and should be avoided if possible.

Internally, tsl works by iterating over all channels and setting the permission overwrites for all critical roles to the locked down set. This is a slow process and can take a long time for large servers. In addition, super large servers may have outages when using a tsl that a qsl may not lead to.

Single-Channel Lockdown

Specificity

  • 2 (SCL > TSL as it updates a single channel)

Rationale

In some cases, only a single channel needs to be locked down. In such a case, a single-channel lockdown is needed.

Syntax

  • scl/<channel_id>

Where <channel_id> is the ID of the channel to lockdown

Description

Single-Channel Lockdown is a lockdown method that locks down a single channel.

Internally, scl/<channel_id> works by setting the permission overwrites for all critical roles to the locked down set for the specified channel. This is a fast process and is recommended for locking down a single channel.

Specificity

When multiple lockdowns are made on the same item (which will now be called a handle from now on), there needs to be a way to know what lockdown owns/has the handle. In AntiRaid, this is controlled through specificity based on the rules:

  • Rule 0: When a handle is locked, the priority is added without replacing older priorities. When a handle is unlocked, the priority is removed leading to its previous value.
  • Rule 1: A handle is controlled unlocked by a lockdown A if the lockdown (say, lockdown B) corresponding to the largest specificity that has locked the handle is less than the specificity of lockdown A. Otherwise, it is considered locked and cannot be modified by lockdown A.
  • Rule 2: The underlying permissions or permission overwrites of a role/channel are defined as the saved permissions/permission overwrites of the role/channel of the oldest possible lockdown which has saved said data.

As an example, consider a case where a tsl is first applied and then an scl/<channel_id>. As per Rule 1, the tsl has a lower specificity than the scl/<channel_id> and so the scl/<channel_id> will also lock the channel handle. When the tsl is then removed, the channel is still locked by scl/<channel_id> which has a greater specificity. Hence, by Rule 1, the scl/<channel_id> locked channel will remain locked even after the tsl is removed as expected.

Next, consider what happens when the scl/<channel_id> is removed. As tsl stores the original channel permission overwrites of all channels and was created before the scl/<channel_id>, Rule 2 applies. Hence, the underlying permissions of the channel is considered to come from the tsl's stored data and NOT the scl/<channel_id> which was set during the lockdown. This means that when the scl/<channel_id> is removed, the channel will revert to the permissions it had before the tsl was applied which was the original channels permissions.

As such, using Rules 1 and 2, the following holds true:

tsl + scl/<channel_id> - tsl - scl/<channel_id> = 0

Silverpelt

Silverpelt provides a standard library for all Anti-Raid modules.

To create a new Anti-Raid bot making use of Anti-Raid modules, simply implement the trait silverpelt::module::Module. These modules must then be added to a SilverpeltCache which is then inserted into silverpelt::Data.

Most things in silverpelt are abstracted out through traits or dispatched via events. This allows silverpelt to be used as an abstract interface allowing for Anti-Raid to quickly evolve and change/adapt to different targets.

Note: AntiRaid uses a event-driven architecture. This means that modules+the main bot process make events that are dispatched to modules. The event system is currently passive (meaning there is no continously running event loop), however this is subject to change in the future.

Interfaces

Sting

Silverpelt provides concrete structures, utilities and special events for handling stings.

Punishments

Silverpelt provides concrete structures, utilities and special events for handling punishments.

Some extra misc points

  • A command is the base unit for access control. This means that all operations with differing access controls must have commands associated with them.

  • This means that all operations (list backup, create/restore backup, delete backup) MUST have associated commands

  • Sometimes, an operation (such as a web-only operation) may not have a module/command associated with it. In such cases, a 'virtual' module should be used. Virtual modules are modules with commands that are not registered via Discord's API. They are used to group commands together for access control purposes and to ensure that each operation is tied to a command

Anti-Raid Templating System

Supported Languages

  • Lua (luau / Roblox Lua) - Tier 1

Lua is the recommended language for templating

WIP/Potential Languages

  • JavaScript (see lang_javascript_quickjs and lang_javascript_v8 for the current load experiments + integration experiments), potential but unlikely unless someone finds a solution
  • WebAssembly (potential, not yet decided)

Language Requirements

  1. All languages must export the following modules/helpers to the extent required as per the templating documentation. (TODO: Improve this spec)
  • Messages
  • Permissions
  • Captcha
  • Actions
  • Key Value API
  1. All languages must provide a way to sandbox the execution of the code. This is a security requirement. In particular, timeouts and heap/stack/memory limits are required.
  2. Callers must use the abstracted out function calls from lib.rs

My language vent

For reference on my discord vents: https://discord.com/channels/763812938875535361/1040734156327501905/1267195190100361440

Why is lua the only sane language for embedding V8 has big ffi problems with rust. If you try spawning too many isolates, you have a 100% chance of aborting your process and this issue can only be resolved by performing unsafe void* pointer casts Quickjs is a bit too slow and poorly documented Rhai is good but it’s a custom language and it’s sandboxing abilities need unsafe code to fully work (and said unsafe code involves pointer arithmetic that is not thread safe) and heap memory limits require you to manually calculate the heap usage Tera has virtually no safety features and will gladly execute an infinite recursion For starlark/skylark, go to the point on rhai but hopefully without the unsafe bits I can understand now why the game modding industry uses lua, it’s basically the only sane language for handling user input Lua is legit the only sane scripting language on this entire list

[rhai is not only slower than lua, its sandboxing (i said it above here too in a vent i think) requires actual pointer arithmetic that isnt thread safe, its also a custom lang no one knows while lua is well known in the game community. Luau is used in Roblox games so it caters to Discords target market as well]

Template Tokens

All lua templates include a special template token in addition to the template arguments. Modules requiring more privileged levels of access (or otherwise require the template state) should require this token and use it to access the required template state.

Text difference utilities

diff.rs: Taken from https://docs.rs/text-diff/latest/src/text_diff with some modernizing. All credits go to the author.

Other utilities are taken too. To be documented