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 }