Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion examples/barebonesBot.luau
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,23 @@ local Client = require("../lib/Client")
local config = require("../config")

local bot = Client.new({
token = config.token
token = config.token,
intents = {
Client.Constants.Intents.AllNonPrivileged,
Client.Constants.Intents.MessageContent
}
})

bot:on("ready", function(data)
print(`{data.user.username} has connected!`)
end :: typeof(bot.events.ready))

bot.events.messageCreate = function(message)
print(message.content)
if message.content == "!!ping" then
local test = message:reply("Test!")
-- test:reply("A reply to my own message??")
end
end

bot:connect()
55 changes: 20 additions & 35 deletions examples/basicBot.luau
Original file line number Diff line number Diff line change
Expand Up @@ -9,52 +9,37 @@ local bot = Client.new({
-- Intents can be specified as a list of strings or numbers
"AllNonPrivileged",
-- Constants are provided for all intents
-- If testing, ensure you have the correct intents enabled in the developer portal
Client.Constants.Intents.MessageContent
},
compression = false
})

-- Events are functional in nature with proper types
bot.events.ready = function(data)
bot.events.ready = function()
print("Bot is ready!")
end

-- Typing is lost here, so you can use `typeof` to get the type of the event
bot:on("channelCreate", function(channel)
bot.rest:sendMessage(channel.id, {
content = `This channel named \`{channel.name}\` is cool!`
})
end :: typeof(bot.events.channelCreate))

-- Application commands
local commands = {
{
name = "ping",
description = "Ping the bot",
type = Client.Constants.ApplicationCommandTypes.ChatInput,
options = {}
}
}

local currentCommands = bot.rest:getGlobalApplicationCommands()
for i, command in commands do
if command.name ~= currentCommands[i].name then
bot.rest:upsertGlobalApplicationCommands(commands)
break
bot.events.messageCreate = function(message)
if message.content == "!ping" then
-- Directly interact with the rest client
-- or choose to use the `message` object to send a message
-- e.g. message:reply("Pong!")
local a = message:reply("Test")
a:reply("Replying to my own message!")
-- bot.rest:sendMessage(message.channelId, {
-- content = "Pong!",
-- messageReference = {
-- messageId = message.id
-- }
-- })
end
end

bot.events.interactionCreate = function(interaction)
if interaction.type == Client.Constants.InteractionType.ApplicationCommand then
if interaction.data.name == "ping" then
bot.rest:sendInteractionResponse(interaction.id, interaction.token, {
type = Client.Constants.InteractionResponseTypes.ChannelMessageWithSource,
data = {
content = "Pong!"
}
})
end
end
end
-- Typing is lost here, so you can use `typeof` to get the type of the event
bot:on("channelCreate", function(channel)
-- This is a channel object, so you can use the methods available on it
-- TODO: add this back when the channel object is fixed
end :: typeof(bot.events.channelCreate))

bot:connect()
150 changes: 125 additions & 25 deletions lib/Client.luau
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ local datetime = require("@lune/datetime")
local getApplicationIdFromToken = require("util/getApplicationIdFromToken")
local camelize = require("util/snakeCaseToCamel")

local ChannelCache = require("cache/ChannelCache")
local GuildCache = require("cache/GuildCache")

local Guild = require("structures/Guild")
local Message = require("structures/Message")

-- NOTE: Type hack to make the channel type good when used in events
type AnyChannel = ChannelCache.AnyChannel & { guild: Guild.Guild }
type Message = Message.Message & { channel: AnyChannel }

local Logger = require("log/Logger")
type Logger = Logger.Logger

Expand Down Expand Up @@ -94,7 +104,7 @@ export type Events = {
shardId: number,
v: number,
user: ApiTypes.UserObject,
guilds: { string },
guilds: { Guild.Guild },
sessionId: string,
shard: { number },
applicationId: string
Expand All @@ -105,14 +115,14 @@ export type Events = {
guildId: string
}) -> ())?,

channelCreate: ((ApiTypes.ChannelObject) -> ())?,
channelDelete: ((ApiTypes.ChannelObject) -> ())?,
channelCreate: ((AnyChannel) -> ())?,
channelDelete: ((AnyChannel) -> ())?,
channelUpdate: ((AnyChannel) -> ())?,
channelPinsUpdate: (({
guildId: string,
channelId: string,
lastPinTimestamp: datetime.DateTime?
}) -> ())?,
channelUpdate: ((ApiTypes.ChannelObject) -> ())?,

stageInstanceCreate: (({
id: string,
Expand Down Expand Up @@ -186,14 +196,14 @@ export type Events = {
guildAuditLogEntryCreate: ((ApiTypes.AuditLogEntryObject) -> ())?,
guildBanAdd: ((ApiTypes.UserObject) -> ())?,
guildBanRemove: ((ApiTypes.UserObject) -> ())?,
guildCreate: ((ApiTypes.GuildObject) -> ())?,
guildCreate: ((Guild.Guild) -> ())?,
guildUnavailable: ((string) -> ())?,
guildDelete: ((string) -> ())?,
guildStickersUpdate: (({
guildId: string,
stickers: { ApiTypes.StickerObject }
}) -> ())?,
guildUpdate: ((ApiTypes.GuildObject) -> ())?,
guildUpdate: ((Guild.Guild) -> ())?,

integrationCreate: ((ApiTypes.IntegrationObject) -> ())?,
integrationDelete: (({
Expand All @@ -212,21 +222,18 @@ export type Events = {
inviteDelete: ((ApiTypes.InviteObject) -> ())?,

guildMemberAdd: ((ApiTypes.GuildMemberObject) -> ())?,
guildMemberDelete: ((ApiTypes.UserObject) -> ())?,
guildMemberRemove: ((ApiTypes.UserObject) -> ())?,
guildMemberUpdate: ((ApiTypes.GuildMemberObject) -> ())?,

messageCreate: ((ApiTypes.MessageObject) -> ())?,
messageDelete: (({
id: string,
channelId: string,
guildId: string?
}) -> ())?,
messageCreate: ((Message) -> ())?,
messageDelete: ((Message) -> ())?,
messageDeleteBulk: ((
ids: { string },
channelId: string,
guildId: string?
) -> ())?,
messageUpdate: ((ApiTypes.MessageObject) -> ())?,
messageUpdate: ((Message) -> ())?,

reactionAdd: ((ApiTypes.ReactionObject) -> ())?,
reactionRemove: ((ApiTypes.ReactionObject) -> ())?,
reactionRemoveAll: (({
Expand Down Expand Up @@ -329,6 +336,10 @@ export type ClientOptions = {

export type Client = typeof(setmetatable({} :: {
Constants: typeof(Constants),

guilds: GuildCache.GuildCache,

channelGuildMap: { [string]: string },

token: string,
logger: Logger,
Expand Down Expand Up @@ -396,9 +407,21 @@ function Client.new(options: ClientOptions): Client

self.events = options.events or {}

self.guilds = GuildCache.new(self.rest)

return self
end

function Client.getChannel(self: Client, channelId: string, _guildId: string?): AnyChannel?
local guildId = _guildId or self.guilds.channelGuildMap[channelId]
local guild = self.guilds:get(guildId)
if guild then
local channel = guild.channels:get(channelId)
return channel
end
return
end

function Client._callEvent(self: Client, event: Event, ...: any)
if not self.events[event] then
return
Expand All @@ -407,20 +430,28 @@ function Client._callEvent(self: Client, event: Event, ...: any)
if self.events[event] then
if typeof(self.events[event]) == "table" then
for _, callback in ipairs(self.events[event]) do
callback(camelize(...))
callback(...)
end
else
self.events[event](camelize(...))
self.events[event](...)
end
end
end

function Client.handleDispatch(self: Client, event: string, shard: Shard, packet: GatewayTypes.Payload<any>)
local data = packet.d

if event == "READY" then
self.id = data.user.id
self.rest:setApplicationId(data.application.id)

if data.guilds then
for i, guildData in data.guilds do
guildData.shard_id = shard.id
local guild = self.guilds:add(guildData)
data.guilds[i] = guild
end
end

self:_callEvent("ready", {
shardId = shard.id,
Expand All @@ -434,14 +465,49 @@ function Client.handleDispatch(self: Client, event: string, shard: Shard, packet
end

if event == "WEBHOOKS_UPDATE" then
self:_callEvent("webhooksUpdate", data)
self:_callEvent("webhooksUpdate", camelize(data))
elseif event == "CHANNEL_CREATE" then
self:_callEvent("channelCreate", data)
local guild = self.guilds:get(data.guild_id)
if guild then
local alreadyExists = guild.channels:get(data.id)

data.guild = guild

local channel = guild.channels:add(data)
self.guilds.channelGuildMap[channel.id] = guild.id

if not alreadyExists and channel then
self:_callEvent("channelCreate", channel)
end
end
elseif event == "CHANNEL_DELETE" then
-- TODO: dm channels
if data.guild_id then
local guild = self.guilds:get(data.guild_id)
if guild then
local channel = guild.channels:get(data.id)
if channel then
self.guilds.channelGuildMap[channel.id] = nil
guild.channels:remove(data.id)
self:_callEvent("channelDelete", channel)
return
end
end
end
self:_callEvent("channelDelete", data)
elseif event == "CHANNEL_PINS_UPDATE" then
self:_callEvent("channelPinsUpdate", data)
elseif event == "CHANNEL_UPDATE" then
-- TODO
local guild = self.guilds:get(data.guild_id)
if guild then
local channel = guild.channels:add(data)
if not channel.guild then
channel.guild = guild
end

self:_callEvent("channelUpdate", channel)
end
self:_callEvent("channelUpdate", data)
elseif event == "STAGE_INSTANCE_CREATE" then
self:_callEvent("stageInstanceCreate", data)
Expand Down Expand Up @@ -494,19 +560,22 @@ function Client.handleDispatch(self: Client, event: string, shard: Shard, packet
elseif event == "GUILD_BAN_REMOVE" then
self:_callEvent("guildBanRemove", data)
elseif event == "GUILD_CREATE" then
self:_callEvent("guildCreate", data)
local guild = self.guilds:add(data)
self:_callEvent("guildCreate", guild)
elseif event == "GUILD_DELETE" then
if data.unavailable then
self:_callEvent("guildUnavailable", data.id)
return
end
self.guilds:remove(data.id)
self:_callEvent("guildDelete", data.id)
elseif event == "GUILD_INTEGRATIONS_UPDATE" then
self:_callEvent("integrationUpdate", data)
elseif event == "GUILD_STICKERS_UPDATE" then
self:_callEvent("guildStickersUpdate", data)
elseif event == "GUILD_UPDATE" then
self:_callEvent("guildUpdate", data)
local guild = self.guilds:add(data)
self:_callEvent("guildUpdate", guild)
elseif event == "INTEGRATION_CREATE" then
self:_callEvent("integrationCreate", data)
elseif event == "INTEGRATION_DELETE" then
Expand All @@ -530,9 +599,32 @@ function Client.handleDispatch(self: Client, event: string, shard: Shard, packet
elseif event == "GUILD_MEMBER_UPDATE" then
self:_callEvent("guildMemberUpdate", data)
elseif event == "MESSAGE_CREATE" then
self:_callEvent("messageCreate", data)
local channel = self:getChannel(data.channel_id, data.guild_id)
if channel then
if not channel:isTextBased() then return end

-- TODO: thread channels
local existing = channel.messages:get(data.id)
-- TODO: handle existing
data.channel = channel

local message = not existing and channel.messages:add(data)
channel.lastMessageId = data.id

self:_callEvent("messageCreate", message)
end
elseif event == "MESSAGE_DELETE" then
self:_callEvent("messageDelete", data)
local channel = self:getChannel(data.channel_id, data.guild_id)
if channel then
if not channel:isTextBased() then return end

-- TODO: thread channels
local message = channel.messages:get(data.id)
if message then
channel.messages:remove(data.id)
self:_callEvent("messageDelete", message)
end
end
elseif event == "MESSAGE_DELETE_BULK" then
self:_callEvent("messageDeleteBulk", data)
elseif event == "MESSAGE_REACTION_ADD" then
Expand All @@ -544,8 +636,16 @@ function Client.handleDispatch(self: Client, event: string, shard: Shard, packet
elseif event == "MESSAGE_REACTION_REMOVE_EMOJI" then
self:_callEvent("reactionRemoveEmoji", data)
elseif event == "MESSAGE_UPDATE" then
if not data.edited_timestamp then return end
self:_callEvent("messageUpdate", data)
local channel = self:getChannel(data.channel_id, data.guild_id)
if channel then
if not channel:isTextBased() then return end

local message = channel.messages:get(data.id)
if message then
message:update(data)
self:_callEvent("messageUpdate", message)
end
end
elseif event == "MESSAGE_POLL_VOTE_ADD" then
self:_callEvent("messagePollVoteAdd", data)
elseif event == "MESSAGE_POLL_VOTE_REMOVE" then
Expand Down
Loading