Skip to content
60 changes: 60 additions & 0 deletions libs/client/API.lua
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,66 @@ function API:getCurrentApplicationInformation() -- Client:run
return self:request("GET", endpoint)
end

function API:getThreadMember(channel_id, user_id)
local endpoint = f(endpoints.THREAD_MEMBER, channel_id, user_id)
return self:request("GET", endpoint)
end

function API:getThreadMembers(channel_id, query)
local endpoint = f(endpoints.THREAD_MEMBERS, channel_id)
return self:request("GET", endpoint, nil, query)
end

function API:addThreadMember(channel_id, user_id)
local endpoint = f(endpoints.THREAD_MEMBER, channel_id, user_id)
return self:request("PUT", endpoint)
end

function API:removeThreadMember(channel_id, user_id)
local endpoint = f(endpoints.THREAD_MEMBER, channel_id, user_id)
return self:request("DELETE", endpoint)
end

function API:getCurrentThreadMember(channel_id)
local endpoint = f(endpoints.THREAD_MEMBER, channel_id)
return self:request("GET", endpoint)
end

function API:joinThread(channel_id)
local endpoint = f(endpoints.THREAD_MEMBER_ME, channel_id)
return self:request("PUT", endpoint)
end

function API:leaveThread(channel_id)
local endpoint = f(endpoints.THREAD_MEMBER_ME, channel_id)
return self:request("DELETE", endpoint)
end

function API:startThreadWithMessage(channel_id, message_id, payload)
local endpoint = f(endpoints.THREAD_START, channel_id, message_id)
return self:request("POST", endpoint, payload)
end

function API:startThreadWithoutMessage(channel_id, payload)
local endpoint = f(endpoints.THREAD_START_WITHOUT_MESSAGE, channel_id)
return self:request("POST", endpoint, payload)
end

function API:listArchivedPublicThreads(channel_id, query)
local endpoint = f(endpoints.THREAD_ARCHIVED, channel_id)
return self:request("GET", endpoint, nil, query)
end

function API:listArchivedPrivateThreads(channel_id, query)
local endpoint = f(endpoints.THREAD_ARCHIVED_PRIVATE, channel_id)
return self:request("GET", endpoint, nil, query)
end

function API:listJoinedArchivedPrivateThreads(channel_id, query)
local endpoint = f(endpoints.THREAD_JOINED_ARCHIVED_PRIVATE, channel_id)
return self:request("GET", endpoint, nil, query)
end

-- end of auto-generated methods --

return API
5 changes: 4 additions & 1 deletion libs/client/Client.lua
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,10 @@ function Client:getChannel(id)
id = Resolver.channelId(id)
local guild = self._channel_map[id]
if guild then
return guild._text_channels:get(id) or guild._voice_channels:get(id) or guild._categories:get(id)
return guild._text_channels:get(id)
or guild._voice_channels:get(id)
or guild._thread_channels:get(id)
or guild._categories:get(id)
else
return self._private_channels:get(id) or self._group_channels:get(id)
end
Expand Down
107 changes: 100 additions & 7 deletions libs/client/EventHandler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ local channelType = assert(enums.channelType)
local insert = table.insert
local null = json.null

local THREAD_TYPES = require('constants').THREAD_TYPES

local function warning(client, object, id, event)
return client:warning('Uncached %s (%s) on %s', object, id, event)
end
Expand All @@ -26,13 +28,20 @@ end

local function getChannel(client, d)
local channel = client:getChannel(d.channel_id)
if not channel and not d.guild_id then
channel = client._api:getChannel(d.channel_id)
if channel then
if channel.type == channelType.private then
channel = client._private_channels:_insert(channel)
elseif channel.type == channelType.group then
channel = client._group_channels:_insert(channel)
if channel and channel._messages then
return channel
end

local data = client._api:getChannel(d.channel_id)
if data then
if data.type == channelType.private then
channel = client._private_channels:_insert(data)
elseif data.type == channelType.group then
channel = client._group_channels:_insert(data)
elseif THREAD_TYPES[data.type] then
local parent_channel = getChannel(client, {channel_id = data.parent_id})
if parent_channel then
channel = parent_channel._thread_channels:_insert(data, parent_channel)
end
end
end
Expand Down Expand Up @@ -324,6 +333,10 @@ function EventHandler.MESSAGE_CREATE(d, client)
local channel = getChannel(client, d)
if not channel then return warning(client, 'TextChannel', d.channel_id, 'MESSAGE_CREATE') end
local message = channel._messages:_insert(d)
if THREAD_TYPES[channel._type] then
channel._message_count = channel._message_count + 1
channel._total_message_sent = channel._total_message_sent + 1
end
return client:emit('messageCreate', message)
end

Expand All @@ -343,6 +356,9 @@ end
function EventHandler.MESSAGE_DELETE(d, client) -- message object not provided
local channel = getChannel(client, d)
if not channel then return warning(client, 'TextChannel', d.channel_id, 'MESSAGE_DELETE') end
if THREAD_TYPES[channel._type] then
channel._message_count = channel._message_count - 1
end
local message = channel._messages:_delete(d.id)
if message then
return client:emit('messageDelete', message)
Expand All @@ -354,6 +370,9 @@ end
function EventHandler.MESSAGE_DELETE_BULK(d, client)
local channel = getChannel(client, d)
if not channel then return warning(client, 'TextChannel', d.channel_id, 'MESSAGE_DELETE_BULK') end
if THREAD_TYPES[channel._type] then
channel._message_count = channel._message_count - #d.ids
end
for _, id in ipairs(d.ids) do
local message = channel._messages:_delete(id)
if message then
Expand Down Expand Up @@ -552,21 +571,95 @@ function EventHandler.AUTO_MODERATION_ACTION_EXECUTION(d, client)
end

function EventHandler.THREAD_CREATE(d, client)
local parent_channel = client:getChannel(d.parent_id)
if not parent_channel then return warning(client, 'GuildChannel', d.parent_id, 'THREAD_CREATE') end
local channel = parent_channel._thread_channels:_insert(d, parent_channel)
return client:emit('threadCreate', channel, d.newly_created)
end

function EventHandler.THREAD_UPDATE(d, client)
local parent_channel = client:getChannel(d.parent_id)
if not parent_channel then return warning(client, 'GuildChannel', d.parent_id, 'THREAD_UPDATE') end
local channel = parent_channel._thread_channels:_insert(d, parent_channel)
return client:emit('threadUpdate', channel)
end

function EventHandler.THREAD_DELETE(d, client)
local parent_channel = client:getChannel(d.parent_id)
if not parent_channel then return warning(client, 'GuildChannel', d.parent_id, 'THREAD_REMOVE') end
if not d.thread_metadata then
return client:emit('threadDeleteUncached', d, parent_channel)
end
local channel = parent_channel._thread_channels:_remove(d)
return client:emit('threadDelete', channel)
end

local function clearStaleThreads(threads)
for thread in threads:iter() do
if thread._thread_metadata.archived then
threads:_delete(thread.id)
end
end
end

function EventHandler.THREAD_LIST_SYNC(d, client)
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'THREAD_LIST_SYNC') end
local synchedThreads = {}
-- remove archived threads from cache to save space
if d.channel_ids then
for _, channel_id in ipairs(d.channel_ids) do
local channel = client:getChannel(channel_id)
if channel then
clearStaleThreads(channel._thread_channels)
else
warning(client, 'GuildChannel', channel_id, 'THREAD_LIST_SYNC')
end
end
else
clearStaleThreads(guild._thread_channels)
end
-- load and sync the new GuildThreadChannel data
for _, data in ipairs(d.threads) do
local channel = client:getChannel(data.parent_id)
if channel then
insert(synchedThreads, channel._thread_channels:_insert(data, channel))
else
warning(client, 'GuildChannel', data.parent_id, 'THREAD_LIST_SYNC')
end
end
-- load and sync new ThreadMember data
for _, data in ipairs(d.members) do
local thread = guild._thread_channels:get(data.id)
if thread then
thread._members:_insert(data)
else
warning(client, 'GuildThreadChannel', data.id, 'THREAD_LIST_SYNC')
end
end
return client:emit('threadListSync', synchedThreads, guild)
end

function EventHandler.THREAD_MEMBER_UPDATE(d, client)
local thread = client:getChannel(d.id)
if not thread then return warning(client, 'GuildThreadChannel', d.id, 'THREAD_MEMBER_UPDATE') end
local member = thread._members:_insert(d)
return client:emit('threadMemberUpdate', member)
end

function EventHandler.THREAD_MEMBERS_UPDATE(d, client)
local thread = client:getChannel(d.id)
if not thread then return warning(client, 'GuildThreadChannel', d.id, 'THREAD_MEMBERS_UPDATE') end
thread._member_count = d.member_count
if d.added_members then
thread._members:_load(d.added_members)
end
if d.removed_member_ids then
for _, id in ipairs(d.removed_member_ids) do
thread._members:_delete(id)
end
end
return client:emit('threadMembersUpdate', thread)
end

function EventHandler.GUILD_STICKERS_UPDATE(d, client)
Expand Down
29 changes: 29 additions & 0 deletions libs/client/Resolver.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ local ffi = require('ffi')
local ssl = require('openssl')
local class = require('class')
local enums = require('enums')
local Date = require('utils/Date')

local permission = assert(enums.permission)
local gatewayIntent = assert(enums.gatewayIntent)
local actionType = assert(enums.actionType)
local channelFlag = assert(enums.channelFlag)
local messageFlag = assert(enums.messageFlag)
local base64 = ssl.base64
local readFileSync = fs.readFileSync
Expand Down Expand Up @@ -45,6 +47,8 @@ function Resolver.userId(obj)
return obj.id
elseif isInstance(obj, classes.Member) then
return obj.user.id
elseif isInstance(obj, classes.ThreadMember) then
return obj.id
elseif isInstance(obj, classes.Message) then
return obj.author.id
elseif isInstance(obj, classes.Guild) then
Expand Down Expand Up @@ -211,6 +215,17 @@ function Resolver.messageFlag(obj)
return n
end

function Resolver.channelFlag(obj)
local t = type(obj)
local n = nil
if t == 'string' then
n = channelFlag[obj]
elseif t == 'number' then
n = channelFlag(obj) and obj
end
return n
end

function Resolver.base64(obj)
if type(obj) == 'string' then
if obj:find('data:.*;base64,') == 1 then
Expand All @@ -225,4 +240,18 @@ function Resolver.base64(obj)
return nil
end

function Resolver.isoTimestamp(obj)
local t = type(obj)
if isInstance(obj, Date) then
return obj:toISO()
elseif t == 'table' then
return Date.fromTable(obj):toISO()
elseif t == 'number' then
return Date.fromSeconds(obj):toISO()
elseif t == 'string' then
return obj
end
return nil
end

return Resolver
7 changes: 6 additions & 1 deletion libs/constants.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ return {
ID_DELAY = 5000, -- milliseconds
GATEWAY_DELAY = 500, -- milliseconds,
DISCORD_EPOCH = 1420070400000, -- milliseconds
API_VERSION = 8,
API_VERSION = 9,
DEFAULT_AVATARS = 5,
ZWSP = '\226\128\139',
NS_PER_US = 1000,
Expand All @@ -14,4 +14,9 @@ return {
HOUR_PER_DAY = 24,
DAY_PER_WEEK = 7,
GATEWAY_VERSION_VOICE = 8,
THREAD_TYPES = {
[10] = true,
[11] = true,
[12] = true,
}
}
22 changes: 21 additions & 1 deletion libs/containers/Guild.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ local Resolver = require('client/Resolver')
local AuditLogEntry = require('containers/AuditLogEntry')
local GuildTextChannel = require('containers/GuildTextChannel')
local GuildVoiceChannel = require('containers/GuildVoiceChannel')
local GuildThreadChannel = require('containers/GuildThreadChannel')
local GuildCategoryChannel = require('containers/GuildCategoryChannel')
local Snowflake = require('containers/abstract/Snowflake')

Expand All @@ -36,6 +37,7 @@ function Guild:__init(data, parent)
self._members = Cache({}, Member, self)
self._text_channels = Cache({}, GuildTextChannel, self)
self._voice_channels = Cache({}, GuildVoiceChannel, self)
self._thread_channels = Cache({}, GuildThreadChannel)
self._categories = Cache({}, GuildCategoryChannel, self)
self._voice_states = {}
if not data.unavailable then
Expand Down Expand Up @@ -83,6 +85,8 @@ function Guild:_makeAvailable(data)
end
end

self:_loadThreads(data)

return self:_loadMembers(data)

end
Expand All @@ -101,6 +105,17 @@ function Guild:_loadMembers(data)
end
end

function Guild:_loadThreads(data)
if data.threads then
for _, thread in ipairs(data.threads) do
local parent = self:getChannel(thread.parent_id)
if parent then
parent._thread_channels:_insert(thread, parent)
end
end
end
end

function Guild:_modify(payload)
local data, err = self.client._api:modifyGuild(self._id, payload)
if data then
Expand Down Expand Up @@ -219,7 +234,7 @@ end
]=]
function Guild:getChannel(id)
id = Resolver.channelId(id)
return self._text_channels:get(id) or self._voice_channels:get(id) or self._categories:get(id)
return self._text_channels:get(id) or self._voice_channels:get(id) or self._thread_channels:get(id) or self._categories:get(id)
end

--[=[
Expand Down Expand Up @@ -953,6 +968,11 @@ function get.voiceChannels(self)
return self._voice_channels
end

--[=[@p threadChannels Cache An iterable cache of all active thread channels that exist in this guild.]=]
function get.threadChannels(self)
return self._thread_channels
end

--[=[@p categories Cache An iterable cache of all channel categories that exist in this guild.]=]
function get.categories(self)
return self._categories
Expand Down
Loading