Skip to content

Commit

Permalink
Add command disabling, remove CommandRegistry filter methods
Browse files Browse the repository at this point in the history
The `setlang` command will now be re-enabled when additional languages are loaded
  • Loading branch information
zajrik committed Jul 19, 2017
1 parent 9d8819b commit 9871e2c
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 71 deletions.
8 changes: 4 additions & 4 deletions src/client/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,12 @@ export class Client extends Discord.Client

if (!this.passive)
{
// Disable setlang command if there is only one language
if (Lang.langNames.length === 1)
this.disableBase.push('setlang');

this.loadCommand('all');
Lang.loadCommandLocalizations();

// Disable setlang command if there is only one language
if (Lang.langNames.length === 1)
this.commands.get('setlang').disable();
}

registerListeners(this);
Expand Down
37 changes: 37 additions & 0 deletions src/command/Command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { ArgOpts } from '../types/ArgOpts';
*/
export class Command<T extends Client = Client>
{
private _disabled: boolean;

public client: T;
public name: string;
public desc: string;
Expand Down Expand Up @@ -167,6 +169,13 @@ export class Command<T extends Client = Client>
* @type {boolean}
*/

/**
* Whether or not this command is disabled and unable to be called
* currently
* @name Command#disabled
* @type {boolean}
*/

// Middleware function storage for the Command instance
this._middleware = [];

Expand Down Expand Up @@ -215,6 +224,7 @@ export class Command<T extends Client = Client>
if (typeof this.roles === 'undefined') this.roles = [];
if (typeof this.ownerOnly === 'undefined') this.ownerOnly = false;
if (typeof this.external === 'undefined') this.external = false;
if (typeof this._disabled === 'undefined') this._disabled = false;

// Make necessary asserts
if (!this.name) throw new Error(`A command is missing a name:\n${this._classloc}`);
Expand All @@ -237,6 +247,33 @@ export class Command<T extends Client = Client>
if (!(this.action instanceof Function)) throw new TypeError(`Command "${this.name}".action: expected Function, got: ${typeof this.action}`);
}

/**
* Whether or not this command is disabled
* @type {boolean}
*/
public get disabled(): boolean
{
return this._disabled;
}

/**
* Enable this command if it is disabled
* @returns {void}
*/
public enable(): void
{
this._disabled = false;
}

/**
* Disable this command if it is enabled
* @returns {void}
*/
public disable(): void
{
this._disabled = true;
}

/**
* Adds a middleware function to be used when the command is called
* to make modifications to args, determine if the command can
Expand Down
7 changes: 5 additions & 2 deletions src/command/CommandDispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ export class CommandDispatcher<T extends Client>
*/
private async wasCommandCalled(message: Message, dm: boolean): Promise<[boolean, Command<T>, string, string]>
{
type CommandCallData = [boolean, Command<T>, string, string];
const negative: CommandCallData = [false, null, null, null];
const prefixes: string[] = [
`<@${this._client.user.id}>`,
`<@!${this._client.user.id}>`
Expand All @@ -151,14 +153,15 @@ export class CommandDispatcher<T extends Client>
let prefix: string = prefixes.find(a => message.content.trim().startsWith(a));

if (dm && typeof prefix === 'undefined') prefix = '';
if (typeof prefix === 'undefined' && !dm) return [false, null, null, null];
if (typeof prefix === 'undefined' && !dm) return negative;

const commandName: string = message.content.trim()
.slice(prefix.length).trim()
.split(' ')[0];

const command: Command<T> = this._client.commands.findByNameOrAlias(commandName);
if (!command) return [false, null, null, null];
if (!command) return negative;
if (command.disabled) return negative;

return [true, command, prefix, commandName];
}
Expand Down
61 changes: 2 additions & 59 deletions src/command/CommandRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Collection, TextChannel, PermissionResolvable } from 'discord.js';
import { Collection } from 'discord.js';
import { Command } from '../command/Command';
import { Client } from '../client/Client';
import { Message } from '../types/Message';
import { Logger } from '../util/logger/Logger';
import { BaseCommandName } from '../types/BaseCommandName';

Expand Down Expand Up @@ -101,64 +100,8 @@ export class CommandRegistry<T extends Client, K extends string, V extends Comma
*/
public findByNameOrAlias(text: string): V
{
text = text.toLowerCase();
text = text ? text.toLowerCase() : text;
return this.find(c => c.name.toLowerCase() === text
|| !!c.aliases.find(a => a.toLowerCase() === text));
}

/**
* Returns a Promise resolving with a collection of all commands usable
* by the caller in the guild text channel the provided message is in.
* Needs to be async due to having to access guild settings to check
* for disabled groups
* @param {Client} client YAMDBF Client instance
* @param {external:Message} message Discord.js Message object
* @returns {Promise<external:Collection<string, Command>>}
*/
public async filterGuildUsable(client: T, message: Message): Promise<Collection<K, V>>
{
let filtered: Collection<K, V> = new Collection<K, V>();
const currentPermissions: (a: PermissionResolvable) => boolean = a =>
(<TextChannel> message.channel).permissionsFor(message.author).has(a);

const byPermissions: (c: V) => boolean = c =>
c.callerPermissions.length > 0 ? c.callerPermissions.filter(currentPermissions).length > 0 : true;

const byRoles: (c: V) => boolean = c =>
!(c.roles.length > 0 && message.member.roles.filter(role => c.roles.includes(role.name)).size === 0);

const byOwnerOnly: (c: V) => boolean = c =>
(client.isOwner(message.author) && c.ownerOnly) || !c.ownerOnly;

const disabledGroups: string[] = await message.guild.storage.settings.get('disabledGroups') || [];
for (const [name, command] of this.filter(byPermissions).filter(byRoles).filter(byOwnerOnly).entries())
if (!disabledGroups.includes(command.group)) filtered.set(name, command);

return filtered;
}

/**
* Returns all commands usable by the caller within the DM channel the provided
* message is in
* @param {Client} client YAMDBF Client instance
* @param {external:Message} message - Discord.js Message object
* @returns {external:Collection<string, Command>}
*/
public filterDMUsable(client: T, message: Message): Collection<K, V>
{
return this.filter(c => !c.guildOnly &&
((client.isOwner(message.author) && c.ownerOnly) || !c.ownerOnly));
}

/**
* Returns all commands that can have their help looked up by the caller
* in the DM channel the message is in
* @param {Client} client YAMDBF Client instance
* @param {external:Message} message Discord.js Message object
* @returns {external:Collection<string, Command>}
*/
public filterDMHelp(client: T, message: Message): Collection<K, V>
{
return this.filter(c => (client.isOwner(message.author) && c.ownerOnly) || !c.ownerOnly);
}
}
4 changes: 2 additions & 2 deletions src/command/base/Help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default class extends Command
{
const usableCommands: Collection<string, Command> = this.client.commands
.filter(c => !(!this.client.isOwner(message.author) && c.ownerOnly))
.filter(c => !c.hidden);
.filter(c => !c.hidden && !c.disabled);

const widest: number = usableCommands
.map(c => c.name.length)
Expand Down Expand Up @@ -78,7 +78,7 @@ export default class extends Command
else
{
command = this.client.commands
.filter(c => !(!this.client.isOwner(message.author) && c.ownerOnly))
.filter(c => !c.disabled && !(!this.client.isOwner(message.author) && c.ownerOnly))
.find(c => c.name === commandName || c.aliases.includes(commandName));

if (!command) output = res('CMD_HELP_UNKNOWN_COMMAND');
Expand Down
8 changes: 8 additions & 0 deletions src/command/base/Reload.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Collection } from 'discord.js';
import { ResourceLoader } from '../../types/ResourceLoader';
import { Message } from '../../types/Message';
import { Command } from '../Command';
Expand Down Expand Up @@ -26,9 +27,16 @@ export default class extends Command
if (commandName && !command)
return this.respond(message, res('CMD_RELOAD_ERR_UNKNOWN_COMMAND', { commandName }));

const disabled: string[] = this.client.commands.filter(c => c.disabled).map(c => c.name);

if (command) this.client.loadCommand(command.name);
else this.client.loadCommand('all');

let filteredCommands: Collection<string, Command> =
this.client.commands.filter(c => disabled.includes(c.name));

for (const cmd of filteredCommands.values()) cmd.disable();

const end: number = now();
const name: string = command ? command.name : null;
return this.respond(message, res('CMD_RELOAD_SUCCESS',
Expand Down
21 changes: 19 additions & 2 deletions src/localization/Lang.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,19 @@ export class Lang
return Lang._instance.meta[lang] || {};
}

/**
* To be run after loading any localizations
* @private
*/
private static postLoad(): void
{
if (Lang.langNames.length > 1 && Lang._instance.client.commands.get('setlang').disabled)
{
Lang._instance.client.commands.get('setlang').enable();
Lang.logger.info('Lang', `Additional langugage loaded, enabled 'setlang' command.`);
}
}

/**
* Load all localization files (`*.lang`) from the given directory.
* This can be used to manually load custom localizations
Expand Down Expand Up @@ -172,6 +185,8 @@ export class Lang
Lang._instance.langs[langName] = parsedLanguageFile;
}
}

Lang.postLoad();
}

/**
Expand All @@ -191,7 +206,7 @@ export class Lang
if (Lang._instance.client.localeDir)
Lang.loadLocalizationsFrom(Lang._instance.client.localeDir);

Lang.logger.info('Lang', `Loaded string localizations for ${Object.keys(Lang.langs).length} languages`);
Lang.logger.info('Lang', `Loaded string localizations for ${Object.keys(Lang.langs).length} languages.`);
}

/**
Expand Down Expand Up @@ -231,6 +246,8 @@ export class Lang
}

}

Lang.postLoad();
}

/**
Expand All @@ -252,7 +269,7 @@ export class Lang
for (const lang of Object.keys(Lang._instance.commandInfo[command]))
helpTextLangs.add(lang);

Lang.logger.info('Lang', `Loaded helptext localizations for ${helpTextLangs.size} languages`);
Lang.logger.info('Lang', `Loaded helptext localizations for ${helpTextLangs.size} languages.`);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion test/test_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Test extends Client
readyText: 'Test client ready',
// provider: Providers.SQLiteProvider('sqlite://./db.sqlite'),
// commandsDir: './commands',
localeDir: './locale',
// localeDir: './locale',
// defaultLang: 'al_bhed',
pause: true,
plugins: [TestPlugin],
Expand Down
3 changes: 2 additions & 1 deletion test/test_plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export class TestPlugin extends Plugin implements IPlugin
public async init(): Promise<void>
{
this.client.commands.registerExternal(this.client, new TestCommand());
Lang.loadCommandLocalizationsFrom('./commands');
// Lang.loadCommandLocalizationsFrom('./commands');
// Lang.loadLocalizationsFrom('./locale');
// throw new Error('foo');
}
}

0 comments on commit 9871e2c

Please sign in to comment.