2018-06-04 00:24:34 +02:00
package main
import (
"fmt"
"github.com/bwmarrin/discordgo"
2018-06-08 17:36:35 +02:00
"github.com/deckarep/golang-set"
2018-06-04 00:24:34 +02:00
"strings"
2018-06-06 17:28:43 +02:00
"time"
2018-06-04 00:24:34 +02:00
"log"
"regexp"
)
type CommandType int
2018-06-05 13:47:09 +02:00
// These are used to specify Command.CommandType when registering new commands
2018-06-04 00:24:34 +02:00
const (
CommandTypePrefix CommandType = 0
CommandTypeFullMatch CommandType = 1
CommandTypeRegex CommandType = 2
2018-06-04 02:32:40 +02:00
CommandTypeContains CommandType = 3
2018-06-04 00:24:34 +02:00
)
2018-06-05 13:47:09 +02:00
/ *
This struct represents a command object .
The options should be self - explanatory , but they are also explained in the readme .
A struct can be initialized by passing any number of its attributes as parameters .
Everything not set will be set to the go - usual defaults ( "" for string , 0 for int , false for bool , nil for the rest )
Any command that has a Trigger is valid ( but useless if nothing else is specified )
* /
2018-06-04 00:24:34 +02:00
type Command struct {
Trigger string // must be specified
Output string // no output if unspecified
OutputEmbed * discordgo . MessageEmbed // no embed output if unspecified
Type CommandType // defaults to Prefix
2018-06-05 12:31:26 +02:00
Cooldown int // defaults to 0 (no cooldown)
2018-06-05 13:47:09 +02:00
OutputIsReply bool
RequiresMention bool
DeleteInput bool
DMOnly bool
AdminOnly bool
IgnoreCase bool
2018-06-04 00:24:34 +02:00
// for custom commands that go beyond prints and deletions
Function func ( * discordgo . Session , * discordgo . MessageCreate )
2018-06-05 12:31:26 +02:00
2018-06-08 17:36:35 +02:00
UsersOnCooldown mapset . Set // don’t set this manually (it’s overwritten anyway)
2018-06-04 00:24:34 +02:00
}
2018-06-05 13:47:09 +02:00
// Performs basic input validation on a given command and adds it to the global command array
2018-06-04 00:24:34 +02:00
func registerCommand ( command Command ) {
if command . Trigger == "" {
fmt . Println ( "Cannot register a command with no trigger. Skipping." )
return
}
2018-06-05 11:50:40 +02:00
if command . IgnoreCase {
command . Trigger = strings . ToLower ( command . Trigger )
}
2018-06-08 17:36:35 +02:00
command . UsersOnCooldown = mapset . NewSet ( )
2018-06-29 16:50:28 +02:00
commands = append ( commands , & command )
2018-06-04 00:24:34 +02:00
}
2018-06-05 13:47:09 +02:00
/ *
Any message that the bot can read is evaluated here .
The message is matched against each of the command triggers depending on the respective match type .
If one of the commands matches , execute that command and return .
Only one command can be executed per message . Earlier defined commands take precedence .
This is a deliberate choice ( for now ) .
* /
2018-06-04 00:24:34 +02:00
func evaluateMessage ( s * discordgo . Session , m * discordgo . MessageCreate ) {
if m . Author . ID == s . State . User . ID {
log . Printf ( "<Self> %s" , m . Content )
return
}
2018-06-29 16:50:28 +02:00
for _ , command := range commands {
2018-06-05 11:50:40 +02:00
content := m . Content
if command . IgnoreCase {
content = strings . ToLower ( content )
}
2018-06-04 02:32:40 +02:00
if command . RequiresMention {
command . Trigger = fmt . Sprintf ( command . Trigger , s . State . User . ID )
}
2018-06-04 00:24:34 +02:00
switch command . Type {
case CommandTypePrefix :
2018-06-05 11:50:40 +02:00
if strings . HasPrefix ( content , command . Trigger ) {
2018-06-29 16:50:28 +02:00
executeCommand ( s , m , command )
2018-06-04 00:24:34 +02:00
return
}
case CommandTypeFullMatch :
2018-06-05 11:50:40 +02:00
if content == command . Trigger {
2018-06-29 16:50:28 +02:00
executeCommand ( s , m , command )
2018-06-04 00:24:34 +02:00
return
}
case CommandTypeRegex :
2018-06-05 11:50:40 +02:00
match , _ := regexp . MatchString ( command . Trigger , content )
2018-06-04 00:24:34 +02:00
if match {
2018-06-29 16:50:28 +02:00
executeCommand ( s , m , command )
2018-06-04 00:24:34 +02:00
return
}
2018-06-04 02:32:40 +02:00
case CommandTypeContains :
2018-06-05 11:50:40 +02:00
if strings . Contains ( content , command . Trigger ) {
2018-06-29 16:50:28 +02:00
executeCommand ( s , m , command )
2018-06-04 02:32:40 +02:00
return
}
2018-06-04 00:24:34 +02:00
}
}
}
2018-06-05 13:47:09 +02:00
/ *
Executes the given command on the given message and session .
Sets command cooldowns if necessary and also clears them again .
* /
2018-06-29 16:50:28 +02:00
func executeCommand ( session * discordgo . Session , message * discordgo . MessageCreate , command * Command ) {
2018-06-08 17:36:35 +02:00
if isAdmin ( message . Author ) || // no restrictions for admins
2018-06-29 16:50:28 +02:00
( ! command . AdminOnly && ( isDM ( session , message ) || ! command . UsersOnCooldown . Contains ( message . Author . ID ) ) &&
2018-06-08 17:36:35 +02:00
( ! command . DMOnly || isDM ( session , message ) ) ) {
2018-06-06 17:28:43 +02:00
2018-06-04 02:32:40 +02:00
log . Printf ( "Executed command %s triggered by user %s" , command . Trigger , userToString ( message . Author ) )
2018-06-08 17:36:35 +02:00
if command . Cooldown > 0 && ! isDM ( session , message ) && ! isAdmin ( message . Author ) {
2018-06-29 16:50:28 +02:00
command . UsersOnCooldown . Add ( message . Author . ID )
go removeCooldown ( command , message . Author . ID )
2018-06-05 12:31:26 +02:00
}
2018-06-04 00:24:34 +02:00
if command . Function == nil {
// simple reply
if command . OutputEmbed == nil {
messageContent := generateReply ( message , command )
session . ChannelMessageSend ( message . ChannelID , messageContent )
} else {
session . ChannelMessageSendEmbed ( message . ChannelID , command . OutputEmbed )
}
if command . DeleteInput {
session . ChannelMessageDelete ( message . ChannelID , message . ID )
}
2018-06-04 02:32:40 +02:00
} else {
// execute custom function
command . Function ( session , message )
2018-06-04 00:24:34 +02:00
}
} else {
log . Printf ( "Denied command %s to user %s." , command . Trigger , userToString ( message . Author ) )
}
}
2018-06-29 16:50:28 +02:00
func removeCooldown ( command * Command , uid string ) {
time . Sleep ( time . Duration ( command . Cooldown ) * time . Second )
if command . UsersOnCooldown . Contains ( uid ) {
command . UsersOnCooldown . Remove ( uid )
2018-06-08 17:36:35 +02:00
}
2018-06-05 12:31:26 +02:00
}
2018-06-29 16:50:28 +02:00
func generateReply ( message * discordgo . MessageCreate , command * Command ) string {
2018-06-04 00:24:34 +02:00
output := command . Output
if command . OutputIsReply {
output = fmt . Sprintf ( output , message . Author . ID )
}
return output
}