You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

541 lines
18 KiB
Lua

local enums = require('enums')
local json = require('json')
local channelType = enums.channelType
local insert = table.insert
local null = json.null
local function warning(client, object, id, event)
return client:warning('Uncached %s (%s) on %s', object, id, event)
end
local function checkReady(shard)
for _, v in pairs(shard._loading) do
if next(v) then return end
end
shard._ready = true
shard._loading = nil
collectgarbage()
local client = shard._client
client:emit('shardReady', shard._id)
for _, other in pairs(client._shards) do
if not other._ready then return end
end
return client:emit('ready')
end
local function getChannel(client, id)
local guild = client._channel_map[id]
if guild then
return guild._text_channels:get(id)
else
return client._private_channels:get(id) or client._group_channels:get(id)
end
end
local EventHandler = setmetatable({}, {__index = function(self, k)
self[k] = function(_, _, shard)
return shard:warning('Unhandled gateway event: %s', k)
end
return self[k]
end})
function EventHandler.READY(d, client, shard)
shard:info('Received READY')
shard:emit('READY')
shard._session_id = d.session_id
client._user = client._users:_insert(d.user)
local guilds = client._guilds
local group_channels = client._group_channels
local private_channels = client._private_channels
local relationships = client._relationships
for _, channel in ipairs(d.private_channels) do
if channel.type == channelType.private then
private_channels:_insert(channel)
elseif channel.type == channelType.group then
group_channels:_insert(channel)
end
end
local loading = shard._loading
if d.user.bot then
for _, guild in ipairs(d.guilds) do
guilds:_insert(guild)
loading.guilds[guild.id] = true
end
else
if client._options.syncGuilds then
local ids = {}
for _, guild in ipairs(d.guilds) do
guilds:_insert(guild)
if not guild.unavailable then
loading.syncs[guild.id] = true
insert(ids, guild.id)
end
end
shard:syncGuilds(ids)
else
guilds:_load(d.guilds)
end
end
relationships:_load(d.relationships)
for _, presence in ipairs(d.presences) do
local relationship = relationships:get(presence.user.id)
if relationship then
relationship:_loadPresence(presence)
end
end
return checkReady(shard)
end
function EventHandler.RESUMED(_, client, shard)
shard:info('Received RESUMED')
return client:emit('shardResumed', shard._id)
end
function EventHandler.GUILD_MEMBERS_CHUNK(d, client, shard)
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_MEMBERS_CHUNK') end
guild._members:_load(d.members)
if shard._loading and guild._member_count == #guild._members then
shard._loading.chunks[d.guild_id] = nil
return checkReady(shard)
end
end
function EventHandler.GUILD_SYNC(d, client, shard)
local guild = client._guilds:get(d.id)
if not guild then return warning(client, 'Guild', d.id, 'GUILD_SYNC') end
guild._large = d.large
guild:_loadMembers(d, shard)
if shard._loading then
shard._loading.syncs[d.id] = nil
return checkReady(shard)
end
end
function EventHandler.CHANNEL_CREATE(d, client)
local channel
local t = d.type
if t == channelType.text or t == channelType.news then
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'CHANNEL_CREATE') end
channel = guild._text_channels:_insert(d)
elseif t == channelType.voice then
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'CHANNEL_CREATE') end
channel = guild._voice_channels:_insert(d)
elseif t == channelType.private then
channel = client._private_channels:_insert(d)
elseif t == channelType.group then
channel = client._group_channels:_insert(d)
elseif t == channelType.category then
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'CHANNEL_CREATE') end
channel = guild._categories:_insert(d)
else
return client:warning('Unhandled CHANNEL_CREATE (type %s)', d.type)
end
return client:emit('channelCreate', channel)
end
function EventHandler.CHANNEL_UPDATE(d, client)
local channel
local t = d.type
if t == channelType.text or t == channelType.news then
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'CHANNEL_UPDATE') end
channel = guild._text_channels:_insert(d)
elseif t == channelType.voice then
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'CHANNEL_UPDATE') end
channel = guild._voice_channels:_insert(d)
elseif t == channelType.private then -- private channels should never update
channel = client._private_channels:_insert(d)
elseif t == channelType.group then
channel = client._group_channels:_insert(d)
elseif t == channelType.category then
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'CHANNEL_UPDATE') end
channel = guild._categories:_insert(d)
else
return client:warning('Unhandled CHANNEL_UPDATE (type %s)', d.type)
end
return client:emit('channelUpdate', channel)
end
function EventHandler.CHANNEL_DELETE(d, client)
local channel
local t = d.type
if t == channelType.text or t == channelType.news then
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'CHANNEL_DELETE') end
channel = guild._text_channels:_remove(d)
elseif t == channelType.voice then
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'CHANNEL_DELETE') end
channel = guild._voice_channels:_remove(d)
elseif t == channelType.private then
channel = client._private_channels:_remove(d)
elseif t == channelType.group then
channel = client._group_channels:_remove(d)
elseif t == channelType.category then
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'CHANNEL_DELETE') end
channel = guild._categories:_remove(d)
else
return client:warning('Unhandled CHANNEL_DELETE (type %s)', d.type)
end
return client:emit('channelDelete', channel)
end
function EventHandler.CHANNEL_RECIPIENT_ADD(d, client)
local channel = client._group_channels:get(d.channel_id)
if not channel then return warning(client, 'GroupChannel', d.channel_id, 'CHANNEL_RECIPIENT_ADD') end
local user = channel._recipients:_insert(d.user)
return client:emit('recipientAdd', channel, user)
end
function EventHandler.CHANNEL_RECIPIENT_REMOVE(d, client)
local channel = client._group_channels:get(d.channel_id)
if not channel then return warning(client, 'GroupChannel', d.channel_id, 'CHANNEL_RECIPIENT_REMOVE') end
local user = channel._recipients:_remove(d.user)
return client:emit('recipientRemove', channel, user)
end
function EventHandler.GUILD_CREATE(d, client, shard)
if client._options.syncGuilds and not d.unavailable and not client._user._bot then
shard:syncGuilds({d.id})
end
local guild = client._guilds:get(d.id)
if guild then
if guild._unavailable and not d.unavailable then
guild:_load(d)
guild:_makeAvailable(d)
client:emit('guildAvailable', guild)
end
if shard._loading then
shard._loading.guilds[d.id] = nil
return checkReady(shard)
end
else
guild = client._guilds:_insert(d)
return client:emit('guildCreate', guild)
end
end
function EventHandler.GUILD_UPDATE(d, client)
local guild = client._guilds:_insert(d)
return client:emit('guildUpdate', guild)
end
function EventHandler.GUILD_DELETE(d, client)
if d.unavailable then
local guild = client._guilds:_insert(d)
return client:emit('guildUnavailable', guild)
else
local guild = client._guilds:_remove(d)
return client:emit('guildDelete', guild)
end
end
function EventHandler.GUILD_BAN_ADD(d, client)
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_BAN_ADD') end
local user = client._users:_insert(d.user)
return client:emit('userBan', user, guild)
end
function EventHandler.GUILD_BAN_REMOVE(d, client)
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_BAN_REMOVE') end
local user = client._users:_insert(d.user)
return client:emit('userUnban', user, guild)
end
function EventHandler.GUILD_EMOJIS_UPDATE(d, client)
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_EMOJIS_UPDATE') end
guild._emojis:_load(d.emojis, true)
return client:emit('emojisUpdate', guild)
end
function EventHandler.GUILD_MEMBER_ADD(d, client)
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_MEMBER_ADD') end
local member = guild._members:_insert(d)
guild._member_count = guild._member_count + 1
return client:emit('memberJoin', member)
end
function EventHandler.GUILD_MEMBER_UPDATE(d, client)
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_MEMBER_UPDATE') end
local member = guild._members:_insert(d)
return client:emit('memberUpdate', member)
end
function EventHandler.GUILD_MEMBER_REMOVE(d, client)
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_MEMBER_REMOVE') end
local member = guild._members:_remove(d)
guild._member_count = guild._member_count - 1
return client:emit('memberLeave', member)
end
function EventHandler.GUILD_ROLE_CREATE(d, client)
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_ROLE_CREATE') end
local role = guild._roles:_insert(d.role)
return client:emit('roleCreate', role)
end
function EventHandler.GUILD_ROLE_UPDATE(d, client)
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_ROLE_UPDATE') end
local role = guild._roles:_insert(d.role)
return client:emit('roleUpdate', role)
end
function EventHandler.GUILD_ROLE_DELETE(d, client) -- role object not provided
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_ROLE_DELETE') end
local role = guild._roles:_delete(d.role_id)
if not role then return warning(client, 'Role', d.role_id, 'GUILD_ROLE_DELETE') end
return client:emit('roleDelete', role)
end
function EventHandler.MESSAGE_CREATE(d, client)
local channel = getChannel(client, d.channel_id)
if not channel then return warning(client, 'TextChannel', d.channel_id, 'MESSAGE_CREATE') end
local message = channel._messages:_insert(d)
return client:emit('messageCreate', message)
end
function EventHandler.MESSAGE_UPDATE(d, client) -- may not contain the whole message
local channel = getChannel(client, d.channel_id)
if not channel then return warning(client, 'TextChannel', d.channel_id, 'MESSAGE_UPDATE') end
local message = channel._messages:get(d.id)
if message then
message:_setOldContent(d)
message:_load(d)
return client:emit('messageUpdate', message)
else
return client:emit('messageUpdateUncached', channel, d.id)
end
end
function EventHandler.MESSAGE_DELETE(d, client) -- message object not provided
local channel = getChannel(client, d.channel_id)
if not channel then return warning(client, 'TextChannel', d.channel_id, 'MESSAGE_DELETE') end
local message = channel._messages:_delete(d.id)
if message then
return client:emit('messageDelete', message)
else
return client:emit('messageDeleteUncached', channel, d.id)
end
end
function EventHandler.MESSAGE_DELETE_BULK(d, client)
local channel = getChannel(client, d.channel_id)
if not channel then return warning(client, 'TextChannel', d.channel_id, 'MESSAGE_DELETE_BULK') end
for _, id in ipairs(d.ids) do
local message = channel._messages:_delete(id)
if message then
client:emit('messageDelete', message)
else
client:emit('messageDeleteUncached', channel, id)
end
end
end
function EventHandler.MESSAGE_REACTION_ADD(d, client)
local channel = getChannel(client, d.channel_id)
if not channel then return warning(client, 'TextChannel', d.channel_id, 'MESSAGE_REACTION_ADD') end
local message = channel._messages:get(d.message_id)
if message then
local reaction = message:_addReaction(d)
return client:emit('reactionAdd', reaction, d.user_id)
else
local k = d.emoji.id ~= null and d.emoji.id or d.emoji.name
return client:emit('reactionAddUncached', channel, d.message_id, k, d.user_id)
end
end
function EventHandler.MESSAGE_REACTION_REMOVE(d, client)
local channel = getChannel(client, d.channel_id)
if not channel then return warning(client, 'TextChannel', d.channel_id, 'MESSAGE_REACTION_REMOVE') end
local message = channel._messages:get(d.message_id)
if message then
local reaction = message:_removeReaction(d)
if not reaction then -- uncached reaction?
local k = d.emoji.id ~= null and d.emoji.id or d.emoji.name
return warning(client, 'Reaction', k, 'MESSAGE_REACTION_REMOVE')
end
return client:emit('reactionRemove', reaction, d.user_id)
else
local k = d.emoji.id ~= null and d.emoji.id or d.emoji.name
return client:emit('reactionRemoveUncached', channel, d.message_id, k, d.user_id)
end
end
function EventHandler.MESSAGE_REACTION_REMOVE_ALL(d, client)
local channel = getChannel(client, d.channel_id)
if not channel then return warning(client, 'TextChannel', d.channel_id, 'MESSAGE_REACTION_REMOVE_ALL') end
local message = channel._messages:get(d.message_id)
if message then
local reactions = message._reactions
if reactions then
for reaction in reactions:iter() do
reaction._count = 0
end
message._reactions = nil
end
return client:emit('reactionRemoveAll', message)
else
return client:emit('reactionRemoveAllUncached', channel, d.message_id)
end
end
function EventHandler.CHANNEL_PINS_UPDATE(d, client)
local channel = getChannel(client, d.channel_id)
if not channel then return warning(client, 'TextChannel', d.channel_id, 'CHANNEL_PINS_UPDATE') end
return client:emit('pinsUpdate', channel)
end
function EventHandler.PRESENCE_UPDATE(d, client) -- may have incomplete data
local user = client._users:get(d.user.id)
if user then
user:_load(d.user)
end
if d.guild_id then
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'PRESENCE_UPDATE') end
local member
if client._options.cacheAllMembers then
member = guild._members:get(d.user.id)
if not member then return end -- still loading or member left
else
if d.status == 'offline' then -- uncache offline members
member = guild._members:_delete(d.user.id)
else
if d.user.username then -- member was offline
member = guild._members:_insert(d)
elseif user then -- member was invisible, user is still cached
member = guild._members:_insert(d)
member._user = user
end
end
end
if member then
member:_loadPresence(d)
return client:emit('presenceUpdate', member)
end
else
local relationship = client._relationships:get(d.user.id)
if relationship then
relationship:_loadPresence(d)
return client:emit('relationshipUpdate', relationship)
end
end
end
function EventHandler.RELATIONSHIP_ADD(d, client)
local relationship = client._relationships:_insert(d)
return client:emit('relationshipAdd', relationship)
end
function EventHandler.RELATIONSHIP_REMOVE(d, client)
local relationship = client._relationships:_remove(d)
return client:emit('relationshipRemove', relationship)
end
function EventHandler.TYPING_START(d, client)
return client:emit('typingStart', d.user_id, d.channel_id, d.timestamp)
end
function EventHandler.USER_UPDATE(d, client)
client._user:_load(d)
return client:emit('userUpdate', client._user)
end
local function load(obj, d)
for k, v in pairs(d) do obj[k] = v end
end
function EventHandler.VOICE_STATE_UPDATE(d, client)
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'VOICE_STATE_UPDATE') end
local member = d.member and guild._members:_insert(d.member) or guild._members:get(d.user_id)
if not member then return warning(client, 'Member', d.user_id, 'VOICE_STATE_UPDATE') end
local states = guild._voice_states
local channels = guild._voice_channels
local new_channel_id = d.channel_id
local state = states[d.user_id]
if state then -- user is already connected
local old_channel_id = state.channel_id
load(state, d)
if new_channel_id ~= null then -- state changed, but user has not disconnected
if new_channel_id == old_channel_id then -- user did not change channels
client:emit('voiceUpdate', member)
else -- user changed channels
local old = channels:get(old_channel_id)
local new = channels:get(new_channel_id)
if d.user_id == client._user._id then -- move connection to new channel
local connection = old._connection
if connection then
new._connection = connection
old._connection = nil
connection._channel = new
connection:_continue(true)
end
end
client:emit('voiceChannelLeave', member, old)
client:emit('voiceChannelJoin', member, new)
end
else -- user has disconnected
states[d.user_id] = nil
local old = channels:get(old_channel_id)
client:emit('voiceChannelLeave', member, old)
client:emit('voiceDisconnect', member)
end
else -- user has connected
states[d.user_id] = d
local new = channels:get(new_channel_id)
client:emit('voiceConnect', member)
client:emit('voiceChannelJoin', member, new)
end
end
function EventHandler.VOICE_SERVER_UPDATE(d, client)
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'VOICE_SERVER_UPDATE') end
local state = guild._voice_states[client._user._id]
if not state then return client:warning('Voice state not initialized before VOICE_SERVER_UPDATE') end
load(state, d)
local channel = guild._voice_channels:get(state.channel_id)
if not channel then return warning(client, 'GuildVoiceChannel', state.channel_id, 'VOICE_SERVER_UPDATE') end
local connection = channel._connection
if not connection then return client:warning('Voice connection not initialized before VOICE_SERVER_UPDATE') end
return client._voice:_prepareConnection(state, connection)
end
function EventHandler.WEBHOOKS_UPDATE(d, client) -- webhook object is not provided
local guild = client._guilds:get(d.guild_id)
if not guild then return warning(client, 'Guild', d.guild_id, 'WEBHOOKS_UDPATE') end
local channel = guild._text_channels:get(d.channel_id)
if not channel then return warning(client, 'TextChannel', d.channel_id, 'WEBHOOKS_UPDATE') end
return client:emit('webhooksUpdate', channel)
end
return EventHandler