From 4d2ebd211d158b9a65dc83c4f2673e864c0b562d Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Thu, 13 Aug 2020 18:05:23 -0500 Subject: [PATCH] Add a command handler --- examples/basic.nim | 172 +++++++++++++++++--------------- src/nimcord.nim | 4 +- src/nimcord/client.nim | 4 +- src/nimcord/clientobjects.nim | 1 + src/nimcord/commandsystem.nim | 60 +++++++++++ src/nimcord/eventdispatcher.nim | 6 +- 6 files changed, 164 insertions(+), 83 deletions(-) create mode 100644 src/nimcord/commandsystem.nim diff --git a/examples/basic.nim b/examples/basic.nim index 21e2545..cb55b37 100644 --- a/examples/basic.nim +++ b/examples/basic.nim @@ -8,10 +8,93 @@ if (not isNil(tokenStream)): tokenStream.close() -var bot = newDiscordClient(tkn) +var bot = newDiscordClient(tkn, "?") +let pingCommand = Command(name: "ping", commandBody: proc(ctx: CommandContext) = + discard ctx.channel.sendMessage("PONG") +) + +let modifyChannelTopicCommand = Command(name: "modifyChannelTopic", commandBody: proc(ctx: CommandContext) = + let modifyTopic = ctx.message.content.substr(20) + + discard ctx.channel.sendMessage("Modifing Channel!") + discard ctx.channel.modifyChannel(ChannelFields(topic: some(modifyTopic))) +) + +let deleteChannelCommand = Command(name: "deleteChannel", commandBody: proc(ctx: CommandContext) = + let channelID = getIDFromJson(ctx.arguments[0]) + var channel: Channel = ctx.client.cache.getChannel(channelID) + + # Check if we could find the channel to delete + if (channel != nil): + discard channel.sendMessage("Deleting Channel!") + discard channel.deleteChannel() + discard ctx.channel.sendMessage("Deleted Channel!") +) + +let bulkDeleteMessagesCommand = Command(name: "bulkDeleteMessages", commandBody: proc(ctx: CommandContext) = + var amount: int = 25 + if (ctx.message.content.len > 19): + amount = parseIntEasy(ctx.arguments[0]) + + # Get the message to delete, then delete them. + let messages = ctx.channel.getMessages(MessagesGetRequest(limit: some(amount), before: some(ctx.message.id))) + discard ctx.channel.bulkDeleteMessages(messages) + + # Delete the message that was used to run this command. + discard ctx.message.deleteMessage() +) + +let reactToMessageCommand = Command(name: "reactToMessage", commandBody: proc(ctx: CommandContext) = + let emojis = @[newEmoji("⏮️"), newEmoji("⬅️"), newEmoji("⏹️"), newEmoji("➡️"), newEmoji("⏭️")] + for emoji in emojis: + discard ctx.message.addReaction(emoji) +) + +let testEmbedCommand = Command(name: "testEmbed", commandBody: proc(ctx: CommandContext) = + var embed = Embed() + embed.setTitle("This embed is being sent from Nimcord!") + embed.setDescription("Nimcord was developed in about a week of actual work so there will likely be issues.") + embed.addField("Title", "value") + embed.addField("Inline-0", "This is an inline field 0", true) + embed.addField("Inline-1", "This is an inline field 1", true) + embed.setColor(0xffb900) + discard ctx.channel.sendMessage("", false, embed) +) + +let sendFileCommand = Command(name: "sendFile", commandBody: proc(ctx: CommandContext) = + let filePath = ctx.message.content.substr(10) + + let splitFile = splitFile(filePath) + let fileName = splitFile.name & splitFile.ext + + let file = DiscordFile(filePath: filePath, fileName: fileName) + discard ctx.channel.sendMessage("", false, nil, @[file]) +) + +let sendImageCommand = Command(name: "sendImage", commandBody: proc(ctx: CommandContext) = + let filePath = ctx.message.content.substr(11) + + let splitFile = splitFile(filePath) + let fileName = splitFile.name & splitFile.ext + + let file = DiscordFile(filePath: filePath, fileName: fileName) + + var embed = Embed() + embed.setTitle("Image attachment test.") + embed.setImage("attachment://" & fileName) + discard ctx.channel.sendMessage("", false, embed, @[file]) +) + +# You can even register commands like this: +registerCommand(Command(name: "ping2", commandBody: proc(ctx: CommandContext) = + discard ctx.channel.sendMessage("PONG3") +)) + +# Listen for the ready event. registerEventListener(EventType.evtReady, proc(bEvt: BaseEvent) = - let event = ReadyEvent(bEvt) + # Cast the BaseEvent to the ReadyEvent which is what we're listening to. + let event = ReadyEvent(bEvt) echo "Ready! (v", 0, ".", 0, ".", 1, ")" echo "Logged in as: ", bot.clientUser.username, "#", bot.clientUser.discriminator @@ -20,83 +103,16 @@ registerEventListener(EventType.evtReady, proc(bEvt: BaseEvent) = let presence = newPresence("with Nimcord", activityTypeGame, clientStatusIdle, false) asyncCheck event.shard.updateClientPresence(presence) -) -registerEventListener(EventType.evtMessageCreate, proc(bEvt: BaseEvent) = - let event = MessageCreateEvent(bEvt) - - if (event.message.content == "?ping"): - var channel: Channel = event.message.getMessageChannel(event.shard.client.cache) - if (channel != nil): - discard channel.sendMessage("PONG") - elif (event.message.content.startsWith("?modifyChannelTopic")): - let modifyTopic = event.message.content.substr(20) - - var channel: Channel = event.message.getMessageChannel(event.shard.client.cache) - if (channel != nil): - discard channel.sendMessage("Modifing Channel!") - discard channel.modifyChannel(ChannelFields(topic: some(modifyTopic))) - elif (event.message.content.startsWith("?deleteChannel")): - let channelID = getIDFromJson(event.message.content.substr(15)) - var channel: Channel = event.shard.client.cache.getChannel(channelID) - - if (channel != nil): - discard channel.sendMessage("Deleting Channel!") - discard channel.deleteChannel() - discard channel.sendMessage("Deleted Channel!") - elif (event.message.content.startsWith("?getMessages")): - var channel: Channel = event.message.getMessageChannel(event.shard.client.cache) - if (channel != nil): - discard channel.getMessages(MessagesGetRequest(limit: some(15), before: some(event.message.id))) - elif (event.message.content.startsWith("?bulkDeleteMessages")): - var channel: Channel = event.message.getMessageChannel(event.shard.client.cache) - if (channel != nil): - var amount: int = 25 - if (event.message.content.len > 19): - amount = parseIntEasy(event.message.content.substr(20)) - let messages = channel.getMessages(MessagesGetRequest(limit: some(amount), before: some(event.message.id))) - discard channel.bulkDeleteMessages(messages) - elif (event.message.content.startsWith("?reactToMessage")): - var channel: Channel = event.message.getMessageChannel(event.shard.client.cache) - if (channel != nil): - let emojis = @[newEmoji("⏮️"), newEmoji("⬅️"), newEmoji("⏹️"), newEmoji("➡️"), newEmoji("⏭️")] - for emoji in emojis: - discard event.message.addReaction(emoji) - elif (event.message.content.startsWith("?testEmbed")): - var channel: Channel = event.message.getMessageChannel(event.shard.client.cache) - if (channel != nil): - var embed = Embed() - embed.setTitle("This embed is being sent from Nimcord!") - embed.setDescription("Nimcord was developed in about a week of actual work so there will likely be issues.") - embed.addField("Title", "value") - embed.addField("Inline-0", "This is an inline field 0", true) - embed.addField("Inline-1", "This is an inline field 1", true) - embed.setColor(0xffb900) - discard channel.sendMessage("", false, embed) - elif (event.message.content.startsWith("?sendFile")): - var channel: Channel = event.message.getMessageChannel(event.shard.client.cache) - if (channel != nil): - let filePath = event.message.content.substr(10) - - let splitFile = splitFile(filePath) - let fileName = splitFile.name & splitFile.ext - - let file = DiscordFile(filePath: filePath, fileName: fileName) - discard channel.sendMessage("", false, nil, @[file]) - elif (event.message.content.startsWith("?sendImage")): - var channel: Channel = event.message.getMessageChannel(event.shard.client.cache) - if (channel != nil): - let filePath = event.message.content.substr(11) - - let splitFile = splitFile(filePath) - let fileName = splitFile.name & splitFile.ext - - let file = DiscordFile(filePath: filePath, fileName: fileName) - - var embed = Embed() - embed.setTitle("Image attachment test.") - embed.setImage("attachment://" & fileName) - discard channel.sendMessage("", false, embed, @[file]) + # Register commands. You don't need to register them in EventReady. + registerCommand(pingCommand) + registerCommand(modifyChannelTopicCommand) + registerCommand(deleteChannelCommand) + registerCommand(bulkDeleteMessagesCommand) + registerCommand(reactToMessageCommand) + registerCommand(testEmbedCommand) + registerCommand(sendFileCommand) + registerCommand(sendImageCommand) ) waitFor bot.startConnection() \ No newline at end of file diff --git a/src/nimcord.nim b/src/nimcord.nim index 17639d3..e738b12 100644 --- a/src/nimcord.nim +++ b/src/nimcord.nim @@ -16,11 +16,11 @@ import nimcord/[cache, channel, client, clientobjects, discordobject] import nimcord/[embed, emoji, eventdispatcher, eventhandler, guild] import nimcord/[image, member, message, nimcordutils, permission] -import nimcord/[presence, role, user] +import nimcord/[presence, role, user, commandsystem] export cache, channel, client, clientobjects, discordobject export embed, emoji, eventdispatcher, eventhandler, guild export image, member, message, nimcordutils, permission -export presence, role, user +export presence, role, user, commandsystem const NimCordVersion = "v0.0.1" \ No newline at end of file diff --git a/src/nimcord/client.nim b/src/nimcord/client.nim index e629ba2..ddf0783 100644 --- a/src/nimcord/client.nim +++ b/src/nimcord/client.nim @@ -247,7 +247,7 @@ proc updateClientPresence*(shard: Shard, presence: Presence) {.async.} = await shard.sendGatewayRequest(jsonPayload) -proc newDiscordClient*(tkn: string): DiscordClient = +proc newDiscordClient*(tkn: string, commandPrefix: string): DiscordClient = ## Create a DiscordClient using a token. ## ## Sets globalDiscordClient to the newly created client. @@ -256,4 +256,4 @@ proc newDiscordClient*(tkn: string): DiscordClient = var cac: Cache new(cac) - result = DiscordClient(token: tkn, cache: cac) + result = DiscordClient(token: tkn, cache: cac, commandPrefix: commandPrefix) \ No newline at end of file diff --git a/src/nimcord/clientobjects.nim b/src/nimcord/clientobjects.nim index ec73ef7..cb0f634 100644 --- a/src/nimcord/clientobjects.nim +++ b/src/nimcord/clientobjects.nim @@ -9,6 +9,7 @@ type shards*: seq[Shard] shardCount*: int endpoint*: string + commandPrefix*: string Shard* = ref object id*: int diff --git a/src/nimcord/commandsystem.nim b/src/nimcord/commandsystem.nim new file mode 100644 index 0000000..22e7d4d --- /dev/null +++ b/src/nimcord/commandsystem.nim @@ -0,0 +1,60 @@ +import tables, message, member, user, channel, strutils, clientobjects, cache + +type + CommandContext* = ref object of RootObj + ## Object to make it easier to create commands. + message*: Message ## The message that ran the command + channel*: Channel ## The channel that this command was ran in. + author*: GuildMember ## The GuildMember that ran the command. + user*: User ## The user who ran the command. + arguments*: seq[string] ## The command arguments. + client*: DiscordClient ## The DiscordClient. + + Command* = ref object of RootObj + ## Command object. + name*: string ## The name of the command. + commandBody*: proc(ctx: CommandContext) ## The command body of the command. + commandRequirements*: proc(ctx: CommandContext): bool ## The requirements of the command, + ## ran before commandBody to check if + ## the command can run. + +# Table storing all the commands +let registeredCommands = newTable[string, Command]() + +proc registerCommand*(command: Command) = + ## Register a Command. + ## + ## Examples: + ## + ## .. code-block:: nim + ## registerCommand(Command(name: "ping", commandBody: proc(ctx: CommandContext) = + ## discard ctx.channel.sendMessage("PONG") + ## )) + registeredCommands.add(command.name, command) + +proc fireCommand*(client: DiscordClient, message: Message) = + ## Fire a command. This is already called by Nimcord. Not any need to call this. + + # If the message doesn't start with the prefix, then + # it probably isn't a commnand. + if not message.content.startsWith(client.commandPrefix): + return + + # Get the arguments for the command + var arguments: seq[string] = message.content.split(" ") + + # Extract the command name from arguments + let commandName = arguments[0].substr(1) + arguments.del(0) + + ## Dispatches an event so something can listen to it. + if (registeredCommands.hasKey(commandName)): + let commandContext = CommandContext(message: message, channel: message.getMessageChannel(client.cache), + author: message.member, user: message.author, arguments: arguments, client: client) + + let command = registeredCommands[commandName] + if command.commandRequirements != nil: + if command.commandRequirements(commandContext): + command.commandBody(commandContext) + else: + command.commandBody(commandContext) \ No newline at end of file diff --git a/src/nimcord/eventdispatcher.nim b/src/nimcord/eventdispatcher.nim index b63ddc5..0ea5b63 100644 --- a/src/nimcord/eventdispatcher.nim +++ b/src/nimcord/eventdispatcher.nim @@ -1,6 +1,7 @@ import eventhandler, json, tables, message, emoji, user, member, role import guild, channel, nimcordutils, httpClient, strformat, cache -import asyncdispatch, clientobjects, discordobject, presence +import sequtils, asyncdispatch, clientobjects, discordobject, presence +import commandsystem proc readyEvent(shard: Shard, json: JsonNode) = var readyEvent = ReadyEvent(shard: shard, readyPayload: json, name: $EventType.evtReady) @@ -275,6 +276,9 @@ proc messageCreateEvent(shard: Shard, json: JsonNode) = shard.client.cache.messages[msg.id] = msg let messageCreateEvnt = MessageCreateEvent(shard: shard, message: msg, name: $EventType.evtMessageCreate) + + + shard.client.fireCommand(msg) # Fire a command dispatchEvent(messageCreateEvnt) proc messageUpdateEvent(shard: Shard, json: JsonNode) =