Tried experimental porting of command types from CrossExchange

This commit is contained in:
WatDuhHekBro 2021-03-31 03:41:43 -05:00
parent df3e4e8e6e
commit 0d0d134415
3 changed files with 175 additions and 57 deletions

View File

@ -1,4 +1,4 @@
import Command from "../../core/command";
import Command, {TYPES} from "../../core/command";
import {toTitleCase} from "../../core/lib";
import {loadableCommands, categories} from "../../core/command";
import {getPermissionName} from "../../core/permissions";
@ -68,16 +68,16 @@ export default new Command({
if (permLevel === -1) permLevel = command.permission;
switch (type) {
case Command.TYPES.SUBCOMMAND:
case TYPES.SUBCOMMAND:
header += ` ${command.originalCommandName}`;
break;
case Command.TYPES.USER:
case TYPES.USER:
header += " <user>";
break;
case Command.TYPES.NUMBER:
case TYPES.NUMBER:
header += " <number>";
break;
case Command.TYPES.ANY:
case TYPES.ANY:
header += " <any>";
break;
default:
@ -85,7 +85,7 @@ export default new Command({
break;
}
if (type === Command.TYPES.NONE) {
if (type === TYPES.NONE) {
invalid = true;
break;
}

View File

@ -1,6 +1,5 @@
import {parseVars} from "./lib";
import {Collection} from "discord.js";
import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember} from "discord.js";
import {Collection, Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember} from "discord.js";
import {getPrefix} from "../core/structures";
import glob from "glob";
@ -22,17 +21,27 @@ interface CommandOptions {
aliases?: string[];
run?: (($: CommandMenu) => Promise<any>) | string;
subcommands?: {[key: string]: Command};
channel?: Command;
role?: Command;
emote?: Command;
message?: Command;
user?: Command;
id?: "channel" | "role" | "emote" | "message" | "user";
number?: Command;
any?: Command;
}
export enum TYPES {
SUBCOMMAND,
USER,
NUMBER,
ANY,
NONE
SUBCOMMAND, // Any specifically-defined keywords / string literals.
CHANNEL, // <#...>
ROLE, // <@&...>
EMOTE, // <::ID> (The previous two values, animated and emote name respectively, do not matter at all for finding the emote.)
MESSAGE, // Available by using the built-in "Copy Message Link" or "Copy ID" buttons. https://discordapp.com/channels/<Guild ID>/<Channel ID>/<Message ID> or <Channel ID>-<Message ID> (automatically searches all guilds for the channel ID).
USER, // <@...> and <@!...>
ID, // Any number with 17-19 digits. Only used as a redirect to another subcommand type.
NUMBER, // Any valid number via the Number() function, except for NaN and Infinity (because those can really mess with the program).
ANY, // Generic argument case.
NONE // No subcommands exist.
}
export default class Command {
@ -44,10 +53,14 @@ export default class Command {
public originalCommandName: string | null; // If the command is an alias, what's the original name?
public run: (($: CommandMenu) => Promise<any>) | string;
public readonly subcommands: Collection<string, Command>; // This is the final data structure you'll actually use to work with the commands the aliases point to.
public channel: Command | null;
public role: Command | null;
public emote: Command | null;
public message: Command | null;
public user: Command | null;
public id: Command | null;
public number: Command | null;
public any: Command | null;
public static readonly TYPES = TYPES;
constructor(options?: CommandOptions) {
this.description = options?.description || "No description.";
@ -58,10 +71,35 @@ export default class Command {
this.originalCommandName = null;
this.run = options?.run || "No action was set on this command!";
this.subcommands = new Collection(); // Populate this collection after setting subcommands.
this.channel = options?.channel || null;
this.role = options?.role || null;
this.emote = options?.emote || null;
this.message = options?.message || null;
this.user = options?.user || null;
this.number = options?.number || null;
this.any = options?.any || null;
switch (options?.id) {
case "channel":
this.id = this.channel;
break;
case "role":
this.id = this.role;
break;
case "emote":
this.id = this.emote;
break;
case "message":
this.id = this.message;
break;
case "user":
this.id = this.user;
break;
default:
this.id = null;
break;
}
if (options?.subcommands) {
const baseSubcommands = Object.keys(options.subcommands);
@ -89,20 +127,28 @@ export default class Command {
}
}
if (this.user && this.user.aliases.length > 0)
console.warn(
`There are aliases defined for a "user"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`
);
// Because command aliases don't actually do anything except for subcommands, let the user know that this won't do anything.
warnCommandAliases(this.channel, "channel");
warnCommandAliases(this.role, "role");
warnCommandAliases(this.emote, "emote");
warnCommandAliases(this.message, "message");
warnCommandAliases(this.user, "user");
warnCommandAliases(this.number, "number");
warnCommandAliases(this.any, "any");
if (this.number && this.number.aliases.length > 0)
console.warn(
`There are aliases defined for a "number"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`
);
if (this.any && this.any.aliases.length > 0)
console.warn(
`There are aliases defined for an "any"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`
);
// Warn on unused endpoints too.
if (
this.endpoint &&
(this.subcommands.size > 0 ||
this.channel ||
this.role ||
this.emote ||
this.message ||
this.user ||
this.number ||
this.any)
)
console.warn(`An endpoint cannot have subcommands!`);
}
public execute($: CommandMenu) {
@ -121,41 +167,61 @@ export default class Command {
}
public resolve(param: string): TYPES {
if (this.id && /^\d{17,19}$/.test(param)) {
}
if (this.subcommands.has(param)) return TYPES.SUBCOMMAND;
// Any Discord ID format will automatically format to a user ID.
else if (this.user && /\d{17,19}/.test(param)) return TYPES.USER;
else if (this.channel && /^<#\d{17,19}>$/.test(param)) return TYPES.CHANNEL;
else if (this.role && /^<@&\d{17,19}>$/.test(param)) return TYPES.ROLE;
else if (this.emote && /^<a?:.*?:\d{17,19}>$/.test(param)) return TYPES.EMOTE;
else if (this.message && /(\d{17,19}\/\d{17,19}\/\d{17,19}$)|(^\d{17,19}-\d{17,19}$)/.test(param))
return TYPES.MESSAGE;
else if (this.user && /^<@!?\d{17,19}>$/.test(param)) return TYPES.USER;
// Disallow infinity and allow for 0.
else if (this.number && (Number(param) || param === "0") && !param.includes("Infinity")) return TYPES.NUMBER;
else if (this.number && !Number.isNaN(Number(param)) && param !== "Infinity" && param !== "-Infinity")
return TYPES.NUMBER;
else if (this.any) return TYPES.ANY;
else return TYPES.NONE;
}
public get(param: string): Command {
const type = this.resolve(param);
let command: Command;
switch (type) {
// You can also optionally send in a pre-calculated value if you already called Command.resolve so you don't call it again.
public get(param: string, type?: TYPES): Command {
// This expression only runs once, don't worry.
switch (type ?? this.resolve(param)) {
case TYPES.SUBCOMMAND:
command = this.subcommands.get(param) as Command;
break;
return checkResolvedCommand(this.subcommands.get(param));
case TYPES.CHANNEL:
return checkResolvedCommand(this.channel);
case TYPES.ROLE:
return checkResolvedCommand(this.role);
case TYPES.EMOTE:
return checkResolvedCommand(this.emote);
case TYPES.MESSAGE:
return checkResolvedCommand(this.message);
case TYPES.USER:
command = this.user as Command;
break;
return checkResolvedCommand(this.user);
case TYPES.NUMBER:
command = this.number as Command;
break;
return checkResolvedCommand(this.number);
case TYPES.ANY:
command = this.any as Command;
break;
return checkResolvedCommand(this.any);
default:
command = this;
break;
return this;
}
return command;
}
}
function warnCommandAliases(command: Command | null, type: string) {
if (command && command.aliases.length > 0)
console.warn(
`There are aliases defined for an "${type}"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`
);
}
function checkResolvedCommand(command: Command | null | undefined): Command {
if (!command) throw new Error("FATAL: Command type mismatch while calling Command.get!");
return command;
}
// Internally, it'll keep its original capitalization. It's up to you to convert it to title case when you make a help command.
export const categories = new Collection<string, string[]>();

View File

@ -1,5 +1,5 @@
import {client} from "../index";
import Command, {loadableCommands} from "../core/command";
import Command, {loadableCommands, TYPES} from "../core/command";
import {hasPermission, getPermissionLevel, getPermissionName} from "../core/permissions";
import {Permissions} from "discord.js";
import {getPrefix} from "../core/structures";
@ -102,18 +102,70 @@ client.on("message", async (message) => {
}
const type = command.resolve(param);
command = command.get(param);
command = command.get(param, type);
permLevel = command.permission ?? permLevel;
if (type === Command.TYPES.USER) {
const id = param.match(/\d+/g)![0];
try {
params.push(await message.client.users.fetch(id));
} catch (error) {
return message.channel.send(`No user found by the ID \`${id}\`!`);
// Add argument to parameter list, do different stuff depending on the subcommand type.
switch (type) {
case TYPES.SUBCOMMAND:
break;
case TYPES.CHANNEL: {
const id = param.match(/^<#(\d{17,19})>$/)![1];
console.debug(id);
try {
params.push(message.guild!.channels.cache.get(id));
} catch (error) {
return message.channel.send(`No channel found by the ID \`${id}\` in this guild!`);
}
break;
}
} else if (type === Command.TYPES.NUMBER) params.push(Number(param));
else if (type !== Command.TYPES.SUBCOMMAND) params.push(param);
case TYPES.ROLE: {
const id = param.match(/^<@&(\d{17,19})>$/)![1];
console.debug(id);
try {
params.push(await message.guild!.roles.fetch(id));
} catch (error) {
return message.channel.send(`No role found by the ID \`${id}\` in this guild!`);
}
break;
}
case TYPES.EMOTE: {
const id = param.match(/^<a?:.*?:(\d{17,19})>$/)![1];
console.debug(id);
try {
params.push(message.client.emojis.cache.get(id));
} catch (error) {
return message.channel.send(`No emote found by the ID \`${id}\`!`);
}
break;
}
case TYPES.MESSAGE: {
const id = param.match(/(\d{17,19}\/\d{17,19}\/\d{17,19}$)|(^\d{17,19}-\d{17,19}$)/);
console.log(id);
/*try {
params.push(await message.client.users.fetch(id));
} catch (error) {
return message.channel.send(`No user found by the ID \`${id}\`!`);
}*/
break;
}
case TYPES.USER: {
const id = param.match(/^<@!?(\d{17,19})>$/)![1];
console.debug(param, id);
try {
params.push(await message.client.users.fetch(id));
} catch (error) {
return message.channel.send(`No user found by the ID \`${id}\`!`);
}
break;
}
case TYPES.NUMBER:
params.push(Number(param));
break;
case TYPES.ANY:
params.push(param);
break;
}
}
if (!message.member)