1 | import { ICommand } from '../Interfaces/ICommand';
|
2 | import { Message, Collection } from 'discord.js';
|
3 | import { Commands } from '../Database/Models/Commands';
|
4 | import { IContext } from '../Interfaces/IContext';
|
5 | import { ICommandArgument } from '../Interfaces/ICommandArgument';
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | export abstract class Command implements ICommand {
|
13 | public AllowedGuilds: string[];
|
14 | public Data: any;
|
15 | public Blurb: string;
|
16 | public BotContext: IContext;
|
17 | public CooldownRate: number;
|
18 | public LastRun: number;
|
19 | public Arguments: ICommandArgument[];
|
20 | public Parametrized: boolean;
|
21 | public Disabled: boolean;
|
22 | public Type: string;
|
23 | public Lockdown: boolean;
|
24 | private Modified: boolean;
|
25 |
|
26 | public constructor(
|
27 | public AllowedChannels: string[],
|
28 | public AllowedRoles: string[],
|
29 | public AllowedUsers: string[],
|
30 | public RequiresDatabase = false
|
31 | ) {
|
32 | this.Modified = false;
|
33 | }
|
34 |
|
35 | public get LocalData() {
|
36 | return this.Data[this.Name()];
|
37 | }
|
38 |
|
39 | public set LocalData(value: any) {
|
40 | this.Data[this.Name()] = value;
|
41 | }
|
42 |
|
43 | public get HasLocalData(): boolean {
|
44 | return !!this.Data[this.Name()];
|
45 | }
|
46 |
|
47 | abstract Run(message: Message, args?: any): Promise<any>;
|
48 |
|
49 | public Name(): string {
|
50 | return Object.getPrototypeOf(this).constructor.NAME;
|
51 | }
|
52 |
|
53 | public Namespace(): string {
|
54 | return Object.getPrototypeOf(this).constructor.NAMESPACE;
|
55 | }
|
56 |
|
57 | private HasLocalField(name: string): boolean {
|
58 | return this.HasLocalData && !!this.LocalData[name];
|
59 | }
|
60 |
|
61 | protected ValidateChannel(channelId: string): boolean {
|
62 | const AllowedChannels = this.GetValidationPermission('Channel');
|
63 |
|
64 | return AllowedChannels.length === 0 || AllowedChannels.includes(channelId);
|
65 | }
|
66 |
|
67 | protected ValidateRoles(roles: Collection<any, any>): boolean {
|
68 | const AllowedRoles = this.GetValidationPermission('Role');
|
69 |
|
70 | return AllowedRoles.length === 0 || roles.find((role) => AllowedRoles.includes(role.id));
|
71 | }
|
72 |
|
73 | protected ValidateUsers(user: string): boolean {
|
74 | const AllowedUsers = this.GetValidationPermission('User');
|
75 |
|
76 | return AllowedUsers.length === 0 || AllowedUsers.includes(user);
|
77 | }
|
78 |
|
79 | private GetValidationPermission(type: string): any {
|
80 | const key = `Allowed${type}s`;
|
81 |
|
82 | if (this.HasLocalData) {
|
83 | return [ ...this[key], ...(this.LocalData[key] || []) ];
|
84 | } else {
|
85 | return this[key];
|
86 | }
|
87 | }
|
88 |
|
89 | public ModifyPermissions(type: string, action: string, key: string, local = false) {
|
90 | const permissionKey = `Allowed${type}s`;
|
91 |
|
92 | if (!local) {
|
93 | if (action === 'add') {
|
94 | this[permissionKey].push(key);
|
95 | } else if (this[permissionKey]) {
|
96 | this[permissionKey] = this[permissionKey].filter((entry) => key === entry);
|
97 | }
|
98 | } else if (this.HasLocalField(permissionKey)) {
|
99 | if (action === 'add') {
|
100 | this.LocalData[permissionKey].push(key);
|
101 | } else if (this.LocalData[permissionKey]) {
|
102 | this.LocalData[permissionKey] = this.LocalData[permissionKey].filter(
|
103 | (entry) => key === entry
|
104 | );
|
105 | }
|
106 | } else {
|
107 | this.LocalData[permissionKey] = [ key ];
|
108 | }
|
109 |
|
110 | this.Modified = true;
|
111 | }
|
112 |
|
113 | public AddAllowedChannel(channelId: string, local = true): void {
|
114 | return this.ModifyPermissions('Channel', 'add', channelId, local);
|
115 | }
|
116 |
|
117 | public AddAllowedRole(roleId: string, local = true): void {
|
118 | return this.ModifyPermissions('Role', 'add', roleId, local);
|
119 | }
|
120 |
|
121 | public AddAllowedUser(user: string, local = true): void {
|
122 | return this.ModifyPermissions('User', 'add', user, local);
|
123 | }
|
124 |
|
125 | public Call(message: Message, isOwner: boolean): Promise<any> {
|
126 | if (!isOwner) {
|
127 | if (
|
128 | this.AllowedGuilds &&
|
129 | this.AllowedGuilds.length > 0 &&
|
130 | !this.AllowedGuilds.includes(message.guild.id)
|
131 | ) {
|
132 | console.warn(
|
133 | '[Failed Guild Permission]',
|
134 | message.member.displayName,
|
135 | message.guild.id,
|
136 | message.content
|
137 | );
|
138 | }
|
139 |
|
140 | if (
|
141 | !this.ValidateRoles(message.member.roles) ||
|
142 | !this.ValidateChannel(message.channel.id)
|
143 | ) {
|
144 | console.warn(
|
145 | '[Failed Permission]',
|
146 | message.member.displayName,
|
147 | message.channel.id,
|
148 | message.content
|
149 | );
|
150 |
|
151 | message.channel.send(
|
152 | `[Permission Failure] I'm afraid I can't let you do that, ${message.member
|
153 | .displayName}`
|
154 | );
|
155 |
|
156 | return Promise.resolve('');
|
157 | }
|
158 |
|
159 | const isOnCooldown = this.CheckCooldown();
|
160 |
|
161 | if (isOnCooldown) {
|
162 | message.channel.send(
|
163 | '[Rate Warning] This command is still on cooldown, please wait.'
|
164 | );
|
165 | }
|
166 | }
|
167 |
|
168 | return this.Run(message, {
|
169 | ...this.GetArguments(message),
|
170 | ...this.ContextInjection()
|
171 | }).then(() => this.Save());
|
172 | }
|
173 |
|
174 | |
175 |
|
176 |
|
177 | public CheckCooldown(): boolean {
|
178 | if (this.CooldownRate && this.CooldownRate !== 0) {
|
179 | const timestamp = new Date().getTime();
|
180 |
|
181 | if (!this.LastRun || timestamp - this.LastRun >= this.CooldownRate) {
|
182 | this.LastRun = timestamp;
|
183 |
|
184 | return false;
|
185 | }
|
186 |
|
187 | return true;
|
188 | }
|
189 |
|
190 | return false;
|
191 | }
|
192 |
|
193 |
|
194 | public ContextInjection(context?: any) {
|
195 | return {};
|
196 | }
|
197 |
|
198 | public GetArguments(message: Message): any {
|
199 | const args = this.Arguments;
|
200 |
|
201 | if (!args || args.length === 0) {
|
202 | return null;
|
203 | }
|
204 |
|
205 | if (this.Parametrized === true) {
|
206 | return this.GetParameterizedArguments(message.content);
|
207 | } else {
|
208 | return this.GetGenericArguments(message.content);
|
209 | }
|
210 | }
|
211 |
|
212 | private GetParameterizedArguments(content: string) {
|
213 | const parts = content.replace(/\u2014/g, '--').split('--').slice(1);
|
214 | const results = {};
|
215 | const values = {};
|
216 | const args = this.Arguments;
|
217 |
|
218 | parts.forEach((part) => {
|
219 | const words = part.split(' ');
|
220 |
|
221 | if (words[0].indexOf('=') >= 0) {
|
222 | const keys = words[0].split('=');
|
223 |
|
224 | if (words.length === 1) {
|
225 | values[keys[0]] = keys[1];
|
226 | } else {
|
227 | const key = keys[0];
|
228 | const value = [ keys[1], ...words.slice(1) ];
|
229 |
|
230 | values[key] = value.join(' ').trim();
|
231 | }
|
232 | } else {
|
233 | values[words[0]] = words.slice(1).join(' ').trim() || true;
|
234 | }
|
235 | });
|
236 |
|
237 | args.forEach((argument: ICommandArgument) => {
|
238 | if (values[argument.name]) {
|
239 | results[argument.name] = values[argument.name];
|
240 | }
|
241 |
|
242 | if (argument.alias && values[argument.alias]) {
|
243 | results[argument.name] = values[argument.alias];
|
244 | }
|
245 | });
|
246 |
|
247 | return results;
|
248 | }
|
249 |
|
250 | private GetGenericArguments(content: string) {
|
251 | const parts = content.split(' ').slice(1);
|
252 | const args = this.Arguments;
|
253 | const results = { args: [] };
|
254 |
|
255 | args.forEach((argument: ICommandArgument, index: number) => {
|
256 | if (parts[index]) {
|
257 | results[argument.name] = parts[index].trim();
|
258 | }
|
259 | });
|
260 |
|
261 | return results;
|
262 | }
|
263 |
|
264 | public RemoveAllowedChannel(channelId: string, local = true): void {
|
265 | return this.ModifyPermissions('Channel', 'remove', channelId, local);
|
266 | }
|
267 |
|
268 | public RemoveAllowedRole(roleId: string, local = true): void {
|
269 | return this.ModifyPermissions('Role', 'remove', roleId, local);
|
270 | }
|
271 |
|
272 | public RemoveAllowedUsers(user: string, local = true): void {
|
273 | return this.ModifyPermissions('User', 'remove', user, local);
|
274 | }
|
275 |
|
276 | public async Save(force?: boolean): Promise<any> {
|
277 | if (this.RequiresDatabase === false) {
|
278 | return Promise.resolve();
|
279 | }
|
280 |
|
281 | let command = await Commands.findOne({ Namespace: this.Namespace() });
|
282 |
|
283 | if (!command) {
|
284 | command = new Commands({
|
285 | Namespace: this.Namespace(),
|
286 | AllowedChannels: [],
|
287 | AllowedRoles: [],
|
288 | AllowedUsers: [],
|
289 | AllowedGuilds: [],
|
290 | Data: {}
|
291 | });
|
292 | }
|
293 |
|
294 | if (this.Modified || force === true) {
|
295 |
|
296 | command.Data[this.Name()] = {
|
297 | AllowedChannels: this.AllowedChannels,
|
298 | AllowedRoles: this.AllowedRoles,
|
299 | AllowedUsers: this.AllowedUsers,
|
300 | AllowedGuilds: this.AllowedGuilds,
|
301 | Type: this.Type,
|
302 | Data: (this.Data[this.Name()] || { data: {} }).Data || {}
|
303 | };
|
304 | }
|
305 |
|
306 |
|
307 | if (this.Namespace() === this.Name()) {
|
308 | command.AllowedChannels = this.AllowedChannels;
|
309 | command.AllowedRoles = this.AllowedRoles;
|
310 | command.AllowedUsers = this.AllowedUsers;
|
311 | command.AllowedGuilds = this.AllowedGuilds;
|
312 | }
|
313 |
|
314 | return command.save();
|
315 | }
|
316 |
|
317 | public Validate(message: Message): boolean {
|
318 | let hasPermission = true;
|
319 | if (
|
320 | this.AllowedGuilds &&
|
321 | this.AllowedGuilds.length > 0 &&
|
322 | !this.AllowedGuilds.includes(message.guild.id)
|
323 | ) {
|
324 | hasPermission = false;
|
325 | }
|
326 |
|
327 | if (
|
328 | !this.ValidateRoles(message.member.roles) ||
|
329 | !this.ValidateChannel(message.channel.id)
|
330 | ) {
|
331 | hasPermission = false;
|
332 | }
|
333 |
|
334 | if (this.Lockdown === true) {
|
335 | if (
|
336 | (!this.AllowedRoles || this.AllowedRoles.length === 0) &&
|
337 | message.author.id !== this.BotContext.Owner
|
338 | ) {
|
339 | hasPermission = false;
|
340 | }
|
341 | }
|
342 |
|
343 | return hasPermission;
|
344 | }
|
345 | }
|