UNPKG

8.53 kBPlain TextView Raw
1import { ICommand } from '../Interfaces/ICommand';
2import { Message, Collection } from 'discord.js';
3import { Commands } from '../Database/Models/Commands';
4import { IContext } from '../Interfaces/IContext';
5import { ICommandArgument } from '../Interfaces/ICommandArgument';
6
7/**
8 * @module Command
9 * @preferred
10 */
11
12export 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 * Returns false if command is on cooldown
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 // @ts-ignore
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 // Overall all data for this command
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 // If namespace === name then this is the parent and should be synced as such
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}