Added more library functions to command handler
This commit is contained in:
parent
bd67f3b8cc
commit
54ce28d8d4
|
@ -1,9 +1,10 @@
|
|||
import {Command, NamedCommand, callMemberByUsername} from "../../core";
|
||||
import {Command, NamedCommand, getMemberByName} from "../../core";
|
||||
import {isAuthorized, getMoneyEmbed} from "./modules/eco-utils";
|
||||
import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./modules/eco-core";
|
||||
import {BuyCommand, ShopCommand} from "./modules/eco-shop";
|
||||
import {MondayCommand, AwardCommand} from "./modules/eco-extras";
|
||||
import {BetCommand} from "./modules/eco-bet";
|
||||
import {GuildMember} from "discord.js";
|
||||
|
||||
export default new NamedCommand({
|
||||
description: "Economy command for Monika.",
|
||||
|
@ -35,10 +36,11 @@ export default new NamedCommand({
|
|||
any: new Command({
|
||||
description: "See how much money someone else has by using their username.",
|
||||
async run({guild, channel, args, message}) {
|
||||
if (isAuthorized(guild, channel))
|
||||
callMemberByUsername(message, args.join(" "), (member) => {
|
||||
channel.send(getMoneyEmbed(member.user));
|
||||
});
|
||||
if (isAuthorized(guild, channel)) {
|
||||
const member = await getMemberByName(guild!, args.join(" "));
|
||||
if (member instanceof GuildMember) channel.send(getMoneyEmbed(member.user));
|
||||
else channel.send(member);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {User} from "discord.js";
|
||||
import {Command, NamedCommand, getMemberByUsername, CHANNEL_TYPE} from "../../core";
|
||||
import {User, GuildMember} from "discord.js";
|
||||
import {Command, NamedCommand, getMemberByName, CHANNEL_TYPE} from "../../core";
|
||||
|
||||
// Quotes must be used here or the numbers will change
|
||||
const registry: {[id: string]: string} = {
|
||||
|
@ -69,12 +69,10 @@ export default new NamedCommand({
|
|||
channelType: CHANNEL_TYPE.GUILD,
|
||||
async run({message, channel, guild, author, client, args}) {
|
||||
const query = args.join(" ") as string;
|
||||
const member = await getMemberByUsername(guild!, query);
|
||||
const member = await getMemberByName(guild!, query);
|
||||
|
||||
if (member && member.id in registry) {
|
||||
const id = member.id;
|
||||
|
||||
if (id in registry) {
|
||||
if (member instanceof GuildMember) {
|
||||
if (member.id in registry) {
|
||||
channel.send(`\`${member.nickname ?? member.user.username}\` - ${registry[member.id]}`);
|
||||
} else {
|
||||
channel.send(
|
||||
|
@ -82,7 +80,7 @@ export default new NamedCommand({
|
|||
);
|
||||
}
|
||||
} else {
|
||||
channel.send(`Couldn't find a user by the name of \`${query}\`!`);
|
||||
channel.send(member);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {MessageEmbed, version as djsversion, Guild, User, GuildMember} from "discord.js";
|
||||
import ms from "ms";
|
||||
import os from "os";
|
||||
import {Command, NamedCommand, getMemberByUsername, CHANNEL_TYPE} from "../../core";
|
||||
import {Command, NamedCommand, getMemberByName, CHANNEL_TYPE} from "../../core";
|
||||
import {formatBytes, trimArray} from "../../lib";
|
||||
import {verificationLevels, filterLevels, regions} from "../../defs/info";
|
||||
import moment, {utc} from "moment";
|
||||
|
@ -35,9 +35,9 @@ export default new NamedCommand({
|
|||
channelType: CHANNEL_TYPE.GUILD,
|
||||
async run({message, channel, guild, author, client, args}) {
|
||||
const name = args.join(" ");
|
||||
const member = await getMemberByUsername(guild!, name);
|
||||
const member = await getMemberByName(guild!, name);
|
||||
|
||||
if (member) {
|
||||
if (member instanceof GuildMember) {
|
||||
channel.send(
|
||||
member.user.displayAvatarURL({
|
||||
dynamic: true,
|
||||
|
@ -45,7 +45,7 @@ export default new NamedCommand({
|
|||
})
|
||||
);
|
||||
} else {
|
||||
channel.send(`No user found by the name \`${name}\`!`);
|
||||
channel.send(member);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {Command, NamedCommand, ask, askYesOrNo, askMultipleChoice, prompt, callMemberByUsername} from "../../core";
|
||||
import {Command, NamedCommand, ask, askYesOrNo, askMultipleChoice, prompt, getMemberByName} from "../../core";
|
||||
import {Storage} from "../../structures";
|
||||
import {User} from "discord.js";
|
||||
import {User, GuildMember} from "discord.js";
|
||||
import moment from "moment";
|
||||
|
||||
const DATE_FORMAT = "D MMMM YYYY";
|
||||
|
@ -383,10 +383,10 @@ export default new NamedCommand({
|
|||
}),
|
||||
any: new Command({
|
||||
description: "See what time it is for someone else (by their username).",
|
||||
async run({channel, args, message}) {
|
||||
callMemberByUsername(message, args.join(" "), (member) => {
|
||||
channel.send(getTimeEmbed(member.user));
|
||||
});
|
||||
async run({channel, args, guild}) {
|
||||
const member = await getMemberByName(guild!, args.join(" "));
|
||||
if (member instanceof GuildMember) channel.send(getTimeEmbed(member.user));
|
||||
else channel.send(member);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -8,9 +8,10 @@ import {
|
|||
Guild,
|
||||
User,
|
||||
GuildMember,
|
||||
GuildChannel
|
||||
GuildChannel,
|
||||
Channel
|
||||
} from "discord.js";
|
||||
import {SingleMessageOptions} from "./libd";
|
||||
import {getChannelByID, getMessageByID, getUserByID, SingleMessageOptions} from "./libd";
|
||||
import {hasPermission, getPermissionLevel, getPermissionName} from "./permissions";
|
||||
import {getPrefix} from "./interface";
|
||||
import {parseVars, requireAllCasesHandledFor} from "../lib";
|
||||
|
@ -338,17 +339,20 @@ export class Command {
|
|||
return this.subcommands.get(param)!.execute(args, menu, metadata);
|
||||
} else if (this.channel && patterns.channel.test(param)) {
|
||||
const id = patterns.channel.exec(param)![1];
|
||||
const channel = menu.client.channels.cache.get(id);
|
||||
const channel = await getChannelByID(id);
|
||||
|
||||
// Users can only enter in this format for text channels, so this restricts it to that.
|
||||
if (channel instanceof TextChannel) {
|
||||
metadata.symbolicArgs.push("<channel>");
|
||||
menu.args.push(channel);
|
||||
return this.channel.execute(args, menu, metadata);
|
||||
if (channel instanceof Channel) {
|
||||
if (channel instanceof TextChannel || channel instanceof DMChannel) {
|
||||
metadata.symbolicArgs.push("<channel>");
|
||||
menu.args.push(channel);
|
||||
return this.channel.execute(args, menu, metadata);
|
||||
} else {
|
||||
return {
|
||||
content: `\`${id}\` is not a valid text channel!`
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
content: `\`${id}\` is not a valid text channel!`
|
||||
};
|
||||
return channel;
|
||||
}
|
||||
} else if (this.role && patterns.role.test(param)) {
|
||||
const id = patterns.role.exec(param)![1];
|
||||
|
@ -397,34 +401,25 @@ export class Command {
|
|||
messageID = result[2];
|
||||
}
|
||||
|
||||
const channel = menu.client.channels.cache.get(channelID);
|
||||
const message = await getMessageByID(channelID, messageID);
|
||||
|
||||
if (channel instanceof TextChannel || channel instanceof DMChannel) {
|
||||
try {
|
||||
metadata.symbolicArgs.push("<message>");
|
||||
menu.args.push(await channel.messages.fetch(messageID));
|
||||
return this.message.execute(args, menu, metadata);
|
||||
} catch {
|
||||
return {
|
||||
content: `\`${messageID}\` isn't a valid message of channel ${channel}!`
|
||||
};
|
||||
}
|
||||
if (message instanceof Message) {
|
||||
metadata.symbolicArgs.push("<message>");
|
||||
menu.args.push(message);
|
||||
return this.message.execute(args, menu, metadata);
|
||||
} else {
|
||||
return {
|
||||
content: `\`${channelID}\` is not a valid text channel!`
|
||||
};
|
||||
return message;
|
||||
}
|
||||
} else if (this.user && patterns.user.test(param)) {
|
||||
const id = patterns.user.exec(param)![1];
|
||||
const user = await getUserByID(id);
|
||||
|
||||
try {
|
||||
if (user instanceof User) {
|
||||
metadata.symbolicArgs.push("<user>");
|
||||
menu.args.push(await menu.client.users.fetch(id));
|
||||
menu.args.push(user);
|
||||
return this.user.execute(args, menu, metadata);
|
||||
} catch {
|
||||
return {
|
||||
content: `No user found by the ID \`${id}\`!`
|
||||
};
|
||||
} else {
|
||||
return user;
|
||||
}
|
||||
} else if (this.id && this.idType && patterns.id.test(param)) {
|
||||
metadata.symbolicArgs.push("<id>");
|
||||
|
@ -434,16 +429,20 @@ export class Command {
|
|||
// Because this part is pretty much a whole bunch of copy pastes.
|
||||
switch (this.idType) {
|
||||
case "channel":
|
||||
const channel = menu.client.channels.cache.get(id);
|
||||
const channel = await getChannelByID(id);
|
||||
|
||||
// Users can only enter in this format for text channels, so this restricts it to that.
|
||||
if (channel instanceof TextChannel) {
|
||||
menu.args.push(channel);
|
||||
return this.id.execute(args, menu, metadata);
|
||||
if (channel instanceof Channel) {
|
||||
if (channel instanceof TextChannel || channel instanceof DMChannel) {
|
||||
metadata.symbolicArgs.push("<channel>");
|
||||
menu.args.push(channel);
|
||||
return this.id.execute(args, menu, metadata);
|
||||
} else {
|
||||
return {
|
||||
content: `\`${id}\` is not a valid text channel!`
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
content: `\`${id}\` isn't a valid text channel!`
|
||||
};
|
||||
return channel;
|
||||
}
|
||||
case "role":
|
||||
if (!menu.guild) {
|
||||
|
@ -474,22 +473,22 @@ export class Command {
|
|||
};
|
||||
}
|
||||
case "message":
|
||||
try {
|
||||
menu.args.push(await menu.channel.messages.fetch(id));
|
||||
const message = await getMessageByID(menu.channel, id);
|
||||
|
||||
if (message instanceof Message) {
|
||||
menu.args.push(message);
|
||||
return this.id.execute(args, menu, metadata);
|
||||
} catch {
|
||||
return {
|
||||
content: `\`${id}\` isn't a valid message of channel ${menu.channel}!`
|
||||
};
|
||||
} else {
|
||||
return message;
|
||||
}
|
||||
case "user":
|
||||
try {
|
||||
menu.args.push(await menu.client.users.fetch(id));
|
||||
const user = await getUserByID(id);
|
||||
|
||||
if (user instanceof User) {
|
||||
menu.args.push(user);
|
||||
return this.id.execute(args, menu, metadata);
|
||||
} catch {
|
||||
return {
|
||||
content: `No user found by the ID \`${id}\`!`
|
||||
};
|
||||
} else {
|
||||
return user;
|
||||
}
|
||||
default:
|
||||
requireAllCasesHandledFor(this.idType);
|
||||
|
|
|
@ -2,16 +2,6 @@
|
|||
export {Command, NamedCommand, CHANNEL_TYPE} from "./command";
|
||||
export {addInterceptRule} from "./handler";
|
||||
export {launch} from "./interface";
|
||||
export {
|
||||
SingleMessageOptions,
|
||||
botHasPermission,
|
||||
paginate,
|
||||
prompt,
|
||||
ask,
|
||||
askYesOrNo,
|
||||
askMultipleChoice,
|
||||
getMemberByUsername,
|
||||
callMemberByUsername
|
||||
} from "./libd";
|
||||
export * from "./libd";
|
||||
export {getCommandList, getCommandInfo} from "./loader";
|
||||
export {hasPermission, getPermissionLevel, getPermissionName} from "./permissions";
|
||||
|
|
|
@ -25,11 +25,13 @@ interface LaunchSettings {
|
|||
// Additionally, each method would return the object so multiple methods could be chained, such as OnionCore.setPermissions(...).setPrefixResolver(...).launch(client).
|
||||
// I decided to not do this because creating a class then having a bunch of boilerplate around it just wouldn't really be worth it.
|
||||
// commandsDirectory requires an absolute path to work, so use __dirname.
|
||||
export async function launch(client: Client, commandsDirectory: string, settings?: LaunchSettings) {
|
||||
export async function launch(newClient: Client, commandsDirectory: string, settings?: LaunchSettings) {
|
||||
// Core Launch Parameters //
|
||||
client.destroy(); // Release any resources/connections being used by the placeholder client.
|
||||
client = newClient;
|
||||
loadableCommands = loadCommands(commandsDirectory);
|
||||
attachMessageHandlerToClient(client);
|
||||
attachEventListenersToClient(client);
|
||||
attachMessageHandlerToClient(newClient);
|
||||
attachEventListenersToClient(newClient);
|
||||
|
||||
// Additional Configuration //
|
||||
if (settings?.permissionLevels) {
|
||||
|
@ -42,6 +44,7 @@ export async function launch(client: Client, commandsDirectory: string, settings
|
|||
|
||||
// Placeholder until properly loaded by the user.
|
||||
export let loadableCommands = (async () => new Collection<string, NamedCommand>())();
|
||||
export let client = new Client();
|
||||
export let permissionLevels: PermissionLevel[] = [
|
||||
{
|
||||
name: "User",
|
||||
|
|
179
src/core/libd.ts
179
src/core/libd.ts
|
@ -7,9 +7,13 @@ import {
|
|||
TextChannel,
|
||||
DMChannel,
|
||||
NewsChannel,
|
||||
MessageOptions
|
||||
MessageOptions,
|
||||
Channel,
|
||||
GuildChannel,
|
||||
User
|
||||
} from "discord.js";
|
||||
import {unreactEventListeners, replyEventListeners} from "./eventListeners";
|
||||
import {client} from "./interface";
|
||||
|
||||
export type SingleMessageOptions = MessageOptions & {split?: false};
|
||||
|
||||
|
@ -20,16 +24,19 @@ export function botHasPermission(guild: Guild | null, permission: number): boole
|
|||
return !!guild?.me?.hasPermission(permission);
|
||||
}
|
||||
|
||||
// The SoonTM Section //
|
||||
// Maybe promisify this section to reduce the potential for creating callback hell? Especially if multiple questions in a row are being asked.
|
||||
|
||||
// Pagination function that allows for customization via a callback.
|
||||
// Define your own pages outside the function because this only manages the actual turning of pages.
|
||||
// It's probably a good idea to modularize the base reaction handler so there's less copy pasted code.
|
||||
// Maybe also make a reaction handler that listens for when reactions are added and removed.
|
||||
// The reaction handler would also run an async function to react in order (parallel to the reaction handler).
|
||||
|
||||
const FIVE_BACKWARDS_EMOJI = "⏪";
|
||||
const BACKWARDS_EMOJI = "⬅️";
|
||||
const FORWARDS_EMOJI = "➡️";
|
||||
const FIVE_FORWARDS_EMOJI = "⏩";
|
||||
|
||||
// Pagination function that allows for customization via a callback.
|
||||
// Define your own pages outside the function because this only manages the actual turning of pages.
|
||||
/**
|
||||
* Takes a message and some additional parameters and makes a reaction page with it. All the pagination logic is taken care of but nothing more, the page index is returned and you have to send a callback to do something with it.
|
||||
*/
|
||||
|
@ -251,44 +258,132 @@ export async function askMultipleChoice(
|
|||
if (!isDeleted) message.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a user by their username. Gets the first one then rolls with it.
|
||||
*/
|
||||
export async function getMemberByUsername(guild: Guild, username: string) {
|
||||
return (
|
||||
await guild.members.fetch({
|
||||
query: username,
|
||||
limit: 1
|
||||
})
|
||||
).first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to handle cases where someone isn't found by a username automatically.
|
||||
*/
|
||||
export async function callMemberByUsername(
|
||||
message: Message,
|
||||
username: string,
|
||||
onSuccess: (member: GuildMember) => void
|
||||
) {
|
||||
const guild = message.guild;
|
||||
const send = message.channel.send;
|
||||
|
||||
if (guild) {
|
||||
const member = await getMemberByUsername(guild, username);
|
||||
|
||||
if (member) onSuccess(member);
|
||||
else send(`Couldn't find a user by the name of \`${username}\`!`);
|
||||
} else send("You must execute this command in a server!");
|
||||
}
|
||||
|
||||
// TO DO Section //
|
||||
|
||||
// getGuildByID() - checks for guild.available (boolean)
|
||||
// getGuildByName()
|
||||
// findMemberByNickname() - gets a member by their nickname or their username
|
||||
// findUserByUsername()
|
||||
|
||||
// For "get x by y" methods:
|
||||
// Caching: All guilds, channels, and roles are fully cached, while the caches for messages, users, and members aren't complete.
|
||||
// It's more reliable to get users/members by fetching their IDs. fetch() will searching through the cache anyway.
|
||||
// For guilds, do an extra check to make sure there isn't an outage (guild.available).
|
||||
|
||||
export function getGuildByID(id: string): Guild | SingleMessageOptions {
|
||||
const guild = client.guilds.cache.get(id);
|
||||
|
||||
if (guild) {
|
||||
if (guild.available) return guild;
|
||||
else return {content: `The guild \`${guild.name}\` (ID: \`${id}\`) is unavailable due to an outage.`};
|
||||
} else {
|
||||
return {
|
||||
content: `No guild found by the ID of \`${id}\`!`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function getGuildByName(name: string): Guild | SingleMessageOptions {
|
||||
const query = name.toLowerCase();
|
||||
const guild = client.guilds.cache.find((guild) => guild.name.toLowerCase().includes(query));
|
||||
|
||||
if (guild) {
|
||||
if (guild.available) return guild;
|
||||
else return {content: `The guild \`${guild.name}\` (ID: \`${guild.id}\`) is unavailable due to an outage.`};
|
||||
} else {
|
||||
return {
|
||||
content: `No guild found by the name of \`${name}\`!`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function getChannelByID(id: string): Promise<Channel | SingleMessageOptions> {
|
||||
try {
|
||||
return await client.channels.fetch(id);
|
||||
} catch {
|
||||
return {content: `No channel found by the ID of \`${id}\`!`};
|
||||
}
|
||||
}
|
||||
|
||||
// Only go through the cached channels (non-DM channels). Plus, searching DM channels by name wouldn't really make sense, nor do they have names to search anyway.
|
||||
export function getChannelByName(name: string): GuildChannel | SingleMessageOptions {
|
||||
const query = name.toLowerCase();
|
||||
const channel = client.channels.cache.find(
|
||||
(channel) => channel instanceof GuildChannel && channel.name.toLowerCase().includes(query)
|
||||
) as GuildChannel | undefined;
|
||||
if (channel) return channel;
|
||||
else return {content: `No channel found by the name of \`${name}\`!`};
|
||||
}
|
||||
|
||||
export async function getMessageByID(
|
||||
channel: TextChannel | DMChannel | NewsChannel | string,
|
||||
id: string
|
||||
): Promise<Message | SingleMessageOptions> {
|
||||
if (typeof channel === "string") {
|
||||
const targetChannel = await getChannelByID(channel);
|
||||
if (targetChannel instanceof TextChannel || targetChannel instanceof DMChannel) channel = targetChannel;
|
||||
else if (targetChannel instanceof Channel) return {content: `\`${id}\` isn't a valid text-based channel!`};
|
||||
else return targetChannel;
|
||||
}
|
||||
|
||||
try {
|
||||
return await channel.messages.fetch(id);
|
||||
} catch {
|
||||
return {content: `\`${id}\` isn't a valid message of the channel ${channel}!`};
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUserByID(id: string): Promise<User | SingleMessageOptions> {
|
||||
try {
|
||||
return await client.users.fetch(id);
|
||||
} catch {
|
||||
return {content: `No user found by the ID of \`${id}\`!`};
|
||||
}
|
||||
}
|
||||
|
||||
// Also check tags (if provided) to narrow down users.
|
||||
export function getUserByName(name: string): User | SingleMessageOptions {
|
||||
let query = name.toLowerCase();
|
||||
const tagMatch = /^(.+?)#(\d{4})$/.exec(name);
|
||||
let tag: string | null = null;
|
||||
|
||||
if (tagMatch) {
|
||||
query = tagMatch[1].toLowerCase();
|
||||
tag = tagMatch[2];
|
||||
}
|
||||
|
||||
const user = client.users.cache.find((user) => {
|
||||
const hasUsernameMatch = user.username.toLowerCase().includes(query);
|
||||
if (tag) return hasUsernameMatch && user.discriminator === tag;
|
||||
else return hasUsernameMatch;
|
||||
});
|
||||
|
||||
if (user) return user;
|
||||
else return {content: `No user found by the name of \`${name}\`!`};
|
||||
}
|
||||
|
||||
export async function getMemberByID(guild: Guild, id: string): Promise<GuildMember | SingleMessageOptions> {
|
||||
try {
|
||||
return await guild.members.fetch(id);
|
||||
} catch {
|
||||
return {content: `No member found by the ID of \`${id}\`!`};
|
||||
}
|
||||
}
|
||||
|
||||
// First checks if a member can be found by that nickname, then check if a member can be found by that username.
|
||||
export async function getMemberByName(guild: Guild, name: string): Promise<GuildMember | SingleMessageOptions> {
|
||||
const member = (
|
||||
await guild.members.fetch({
|
||||
query: name,
|
||||
limit: 1
|
||||
})
|
||||
).first();
|
||||
|
||||
// Search by username if no member is found, then resolve the user into a member if possible.
|
||||
if (member) {
|
||||
return member;
|
||||
} else {
|
||||
const user = getUserByName(name);
|
||||
|
||||
if (user instanceof User) {
|
||||
const member = guild.members.resolve(user);
|
||||
if (member) return member;
|
||||
else return {content: `The user \`${user.tag}\` isn't in this guild!`};
|
||||
} else {
|
||||
return {content: `No member found by the name of \`${name}\`!`};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue