1 /** 2 Utilties for building user-controlled commands with the dscord bot interface 3 */ 4 5 module dscord.bot.command; 6 7 import std.regex, 8 std.array, 9 std.algorithm; 10 11 import dscord.gateway.events, 12 dscord.types.all; 13 14 /** 15 A UDA that can be used to flag a function as a command handler. 16 */ 17 struct Command { 18 string trigger; 19 string description = ""; 20 string group = ""; 21 bool regex = false; 22 uint level = 0; 23 } 24 25 /** 26 A delegate type which can be used in UDA's to adjust a CommandObjects settings 27 or behavior. 28 */ 29 alias CommandObjectUpdate = void delegate(CommandObject); 30 31 /** 32 Sets a commands description. 33 */ 34 CommandObjectUpdate CommandDescription(string desc) { 35 return (c) {c.description = desc; }; 36 } 37 38 /** 39 Sets a commands group. 40 */ 41 CommandObjectUpdate CommandGroup(string group) { 42 return (c) {c.setGroup(group);}; 43 } 44 45 /** 46 Sets whether a command uses regex matching 47 */ 48 CommandObjectUpdate CommandRegex(bool rgx) { 49 return (c) {c.setRegex(rgx);}; 50 } 51 52 /** 53 Sets a commands permission level. 54 */ 55 CommandObjectUpdate CommandLevel(uint level) { 56 return (c) {c.level = level;}; 57 } 58 59 60 /** 61 A delegate type which represents a function used for handling commands. 62 */ 63 alias CommandHandler = void delegate(CommandEvent); 64 65 /** 66 A CommandObject represents the configuration/state for a single command. 67 */ 68 class CommandObject { 69 /** The command "trigger" or name */ 70 string trigger; 71 72 /** The description / help text for the command */ 73 string description; 74 75 /** The permissions level required for the command */ 76 uint level; 77 78 /** Whether this command is enabled */ 79 bool enabled = true; 80 81 /** The function handler for this command */ 82 CommandHandler func; 83 84 private { 85 // Compiled matching regex 86 Regex!char rgx; 87 88 string group; 89 bool useRegex; 90 } 91 92 this(Command cmd, CommandHandler func) { 93 this.func = func; 94 this.trigger = cmd.trigger; 95 this.description = cmd.description; 96 this.level = cmd.level; 97 this.setGroup(cmd.group); 98 this.setRegex(cmd.regex); 99 } 100 101 /** 102 Sets this commands group. 103 */ 104 void setGroup(string group) { 105 this.group = group; 106 this.rebuild(); 107 } 108 109 /** 110 Sets whether this command uses regex matching. 111 */ 112 void setRegex(bool rgx) { 113 this.useRegex = rgx; 114 this.rebuild(); 115 } 116 117 /** 118 Rebuilds the locally cached regex. 119 */ 120 void rebuild() { 121 if (this.useRegex) { 122 this.rgx = regex(this.trigger); 123 } else { 124 // Append space to grouping 125 group = (this.group != "" ? this.group ~ " " : ""); 126 this.rgx = regex("^" ~ group ~ this.trigger); 127 } 128 } 129 130 /** 131 Returns a Regex capture group matched against the commands regex. 132 */ 133 Captures!string match(string msg) { 134 return msg.matchFirst(this.rgx); 135 } 136 } 137 138 /** 139 Special event encapsulating MessageCreate's, containing specific Bot utilties 140 and functionality. 141 */ 142 class CommandEvent { 143 CommandObject cmd; 144 MessageCreate event; 145 Message msg; 146 147 /** The message contents */ 148 string contents; 149 150 /** Array of arguments */ 151 string[] args; 152 153 this(MessageCreate event) { 154 this.event = event; 155 this.msg = event.message; 156 } 157 158 @property string cleanedContents() { 159 return this.args.join(" "); 160 } 161 162 bool has(ushort index) { 163 return (index < this.args.length); 164 } 165 166 string arg(ushort index) { 167 return this.args[index]; 168 } 169 } 170 171 /** 172 The Commandable template is a virtual implementation which handles the command 173 UDAs, storing them within a local "commands" mapping. 174 */ 175 mixin template Commandable() { 176 CommandObject[string] commands; 177 178 void loadCommands(T)() { 179 CommandObject obj; 180 foreach (mem; __traits(allMembers, T)) { 181 foreach(attr; __traits(getAttributes, __traits(getMember, T, mem))) { 182 static if (is(typeof(attr) == Command)) { 183 obj = this.registerCommand(new CommandObject(attr, mixin("&(cast(T)this)." ~ mem))); 184 } 185 static if (is(typeof(attr) == CommandObjectUpdate)) { 186 attr(obj); 187 } 188 } 189 } 190 } 191 192 /** 193 Registers a command from a CommandObject 194 */ 195 CommandObject registerCommand(CommandObject obj) { 196 this.commands[obj.trigger] = obj; 197 return obj; 198 } 199 }