Containers and iterables - SinisterRectus/Discordia GitHub Wiki
Discordia relies on custom classes to store, retrieve, update, and delete Discord data in situations where simple Lua tables may not be sufficient.
When certain payloads are received from the Discord gateway via WebSocket, data is passed to an event handler where it is encapsulated in containers, stored in caches, and sometimes made accessible via other iterables.
Containers
Discordia uses special Container classes to store raw Discord data as objects with useful properties and methods.
Every container has a client
and parent
property. The client
property is a handle for the main client instance that is manages the container while the parent
property is a handle for the object where the container is directly cached. Sometimes, these are the same. For example, guild.parent
and guild.client
are equivalent. Otherwise, the parent will be defined differently. For example, role.parent
and role.guild
are equivalent. Additionally, role.guild.client
and role.client
are equivalent, since both the guild and its roles are necessarily managed by the same client.
Every container has a __hash
method that returns a certain property that can be used to uniquely identify the container. For example, message:__hash()
is equivalent to message.id
and is guaranteed to be unique for all messages.
Every container has a __tostring
method that returns a string of the container name and the container hash. This string has the general format Name: hash
.
Every container has an __eq
method that allowed containers to be compared with the ==
operator. For two containers to be equivalent, they must be of the same type and their hashes must be identical.
The following is a list of Discord containers, their parents, and their hash properties:
Container | Parent | Hash |
---|---|---|
Ban | Guild | user.id |
Emoji | Guild | id |
GroupChannel | Client | id |
Guild | Client | id |
GuildCategoryChannel | Guild | id |
GuildTextChannel | Guild | id |
GuildVoiceChannel | Guild | id |
Invite | Client | code |
Member | Guild | user.id |
Message | TextChannel | id |
PermissionOverwrite | GuildChannel | id |
PrivateChannel | Client | id |
Reaction | Message | emojiId or emojiName |
Relationship | Client | user.id |
Role | Guild | id |
User | Client | id |
Webhook | Client | id |
Iterables
Discordia uses special Iterable classes to cache and access containers. They function similarly to tables, with __len
and __pairs
methods, plus a variety of methods that help to find the object that you want, such as get
, find
, and iter
.
Primary Iterables - Caches
For a variety of containers and the main client instance, there are properties that are some sort of iterable class. Caches are a special type of iterable that are used as the main storage for Discordia containers. Cache properties and the objects that they contain are updated via various gateway events.
Parent | Property | Iterable Type | Container Type |
---|---|---|---|
Client | guilds | Cache | Guild |
Client | groupChannels | Cache | GroupChannel |
Client | privateChannels | Cache | PrivateChannel |
Client | users | Cache | User |
Guild | categories | Cache | GuildCategoryChannel |
Guild | emojis | Cache | Emoji |
Guild | members | Cache | Member |
Guild | roles | Cache | Role |
Guild | textChannels | Cache | GuildTextChannel |
Guild | voiceChannels | Cache | GuildVoiceChannel |
Message | reactions | Cache | Reaction |
TextChannel | permissionOverwrites | Cache | PermissionOverwrite |
TextChannel | messages | WeakCache | Message |
Secondary Iterables
Some other containers have properties for secondary iterables. These iterables do not store original objects; they store references to cached objects listed above. Conveniently, all iterable classes, including caches, have the same methods (and no properties), so Discordia users do not necessarily have to remember which iterable is used; remembering one set of iterable methods is sufficient. Secondary iterable properties and the objects that they reference are updated via various gateway events.
Parent | Property | Iterable Type | Container Type |
---|---|---|---|
GroupChannel | recipients | SecondaryCache | User |
GuildTextChannel | members | FilteredIterable | Member |
Message | mentionedUsers | ArrayIterable | User |
Message | mentionedRoles | ArrayIterable | Role |
Message | mentionedChannels | ArrayIterable | Channel |
Role | members | FilteredIterable | Member |
User | mutualGuilds | FilteredIterable | Guild |
Static Iterables
Some objects cannot be reliably updated by gateway events alone. These objects are instead accessed by HTTP requests via class methods. Every time the methods listed below are called, they will make an HTTP request and return a new copy of an iterable and containers (if successful). Containers stored in static caches (bans, invites, webhooks) are not updated via gateway events. Containers stored in static secondary caches (messages, users) are updated via gateway events, but objects are never added to or removed from the cache via gateway events.
Parent | Method | Iterable Type | Container Type |
---|---|---|---|
Guild | getBans | Cache | Ban |
Guild | getInvites | Cache | Invite |
Guild | getWebhooks | Cache | Webhook |
GuildChannel | getInvites | Cache | Invite |
GuildTextChannel | getWebhooks | Cache | Webhook |
Reaction | getUsers | SecondaryCache | User |
TextChannel | getMessages | SecondaryCache | Message |
TextChannel | getPinnedMessages | SecondaryCache | Message |
Examples
Below are some examples of accessing role objects. In some cases, the following predicate is used to filter by color:
local function fn(r) return r.color > 0 end
Counting Objects
local n = #guild.roles -- count all roles
local n = guild.roles:count(fn) -- count some roles
Accessing One Object
local role = guild.roles:get('1234567890') -- get by Snowflake ID
local role = guild.roles:find(fn) -- get by predicate
Accessing Many Objects
for id, role in pairs(guild.roles) do
print(id, role.name) -- iterate all roles
end
for role in guild.roles:iter() do
print(role.name) -- iterate all roles
end
for role in guild.roles:findAll(fn) do
print(role.name, role.color) -- iterate through some roles
end
local roles = roles:toArray() -- get an array of roles
local roles = roles:toArray('position') -- get a sorted array of roles
local roles = roles:toArray('position', fn) -- get a sorted, filtered array of roles
Accessing Properties in Bulk
local rows = guild.roles:select('position', 'id', 'name') -- get a sorted array of properties
local row = rows[1]
print(row[1]) -- position
print(row[2]) -- id
print(row[3]) -- name
Special Cases
There are some special cases or exceptions to the rules outlined above.
Guild Unavailability
When there is a Discord server outage, some guilds (and their child objects) may become unavailable. When this happens, Discordia guild objects may not be cached at all, may be mostly empty, or may be an old copy. In the latter two cases, guild.unavailible
would be true
.
Offline Members
By default, offline guild members may not be cached in Discordia. To compensate for missing members, Guild:getMember
will make an HTTP request to fetch a member if it is not cached. If you do need to have all members cached, then the cacheAllMembers
option must be enabled on client initialization. You can check the ratio of cached to total members by comparing the #guild.members
and guild.totalMemberCount
values.
Private Channels
Private channels may not be cached if they were previously closed. If you are unable to access the channel object via either Client:getChannel
or other methods, use User:getPrivateMessage
or the User:send
shortcut.
Messages
Discordia has no cached messages on startup and will only weakly cache the most recent messages as they are received. If you want to access messages that are not cached, either use TextChannel:getMessages
(and related methods) or TextChannel:getMessage
, which will make an HTTP request to fetch the message if it is not cached.
Permission Overwrites
Permission overwrites are only cached if they were edited. If you want to get an overwrite for a specific role or member, use GuildChannel:getPermissionOverwriteFor
. This will create a new object if one does not already exist.