diff --git a/src/commands/admin.ts b/src/commands/admin.ts index ff65330..fc9a15c 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -2,6 +2,8 @@ import Command from "../core/command"; import {CommonLibrary, logs} from "../core/lib"; import {Config, Storage} from "../core/structures"; import {PermissionNames, getPermissionLevel} from "../core/permissions"; +import {botHasPermission} from "../index"; +import {Permissions} from "discord.js"; function getLogBuffer(type: string) { @@ -11,12 +13,7 @@ function getLogBuffer(type: string) }]}; } -const activities: { [type: string]: string } = { - playing: "", - listening: "", - streaming: "", - watching: "" -}; +const activities = ["playing", "listening", "streaming", "watching"]; export default new Command({ description: "An all-in-one command to do admin stuff. You need to be either an admin of the server or one of the bot's mechanics to use this command.", @@ -63,7 +60,7 @@ export default new Command({ $.channel.send(getLogBuffer("info")); }, any: new Command({ - description: `Select a verbosity to listen to. Available levels: \`[${Object.keys(logs)}]\``, + description: `Select a verbosity to listen to. Available levels: \`[${Object.keys(logs).join(", ")}]\``, async run($: CommonLibrary): Promise { const type = $.args[0]; @@ -71,7 +68,7 @@ export default new Command({ if(type in logs) $.channel.send(getLogBuffer(type)); else - $.channel.send(`Couldn't find a verbosity level named \`${type}\`! The available types are \`[${Object.keys(logs)}]\`.`); + $.channel.send(`Couldn't find a verbosity level named \`${type}\`! The available types are \`[${Object.keys(logs).join(", ")}]\`.`); } }) }), @@ -118,16 +115,13 @@ export default new Command({ permission: Command.PERMISSIONS.BOT_SUPPORT, async run($: CommonLibrary): Promise { - try { - const nickName = $.args.join(" "); - const trav = $.guild?.members.cache.find(member => member.id === $.client.user?.id); - await trav?.setNickname(nickName); - $.message.delete({timeout: 5000}); - $.channel.send(`Nickname set to \`${nickName}\``) - .then(m => m.delete({timeout: 5000})); - } catch (e) { - console.log(e); - } + const nickName = $.args.join(" "); + const trav = $.guild?.members.cache.find(member => member.id === $.client.user?.id); + await trav?.setNickname(nickName); + if(botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES)) + $.message.delete({timeout: 5000}).catch($.handler.bind($)); + $.channel.send(`Nickname set to \`${nickName}\``) + .then(m => m.delete({timeout: 5000})); } }), guilds: new Command({ @@ -152,17 +146,17 @@ export default new Command({ $.channel.send("Activity set to default.") }, any: new Command({ - description: `Select an activity type to set. Available levels: \`[${Object.keys(activities)}]\``, + description: `Select an activity type to set. Available levels: \`[${activities.join(", ")}]\``, async run($: CommonLibrary): Promise { const type = $.args[0]; - if(type in activities) { + if(activities.includes(type)) { $.client.user?.setActivity($.args.slice(1).join(" "), {type: $.args[0].toUpperCase()}) $.channel.send(`Set activity to \`${$.args[0].toUpperCase()}\` \`${$.args.slice(1).join(" ")}\`.`) } else - $.channel.send(`Couldn't find an activity type named \`${type}\`! The available types are \`[${Object.keys(activities)}]\`.`); + $.channel.send(`Couldn't find an activity type named \`${type}\`! The available types are \`[${activities.join(", ")}]\`.`); } }) }) diff --git a/src/commands/fun.ts b/src/commands/fun.ts index 99834d3..35d7b51 100644 --- a/src/commands/fun.ts +++ b/src/commands/fun.ts @@ -41,7 +41,7 @@ export default new Command({ async run($: CommonLibrary): Promise { const sender = $.message.author; - $.channel.send(responses[Math.floor(Math.random() * responses.length)] + ` <@${sender.id}>`); + $.channel.send($(responses).random() + ` <@${sender.id}>`); } }) }), diff --git a/src/commands/info.ts b/src/commands/info.ts index 739c8ad..7a00557 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -6,7 +6,7 @@ import { verificationLevels, filterLevels, regions, flags } from "../defs/info"; export default new Command({ description: "Command to provide all sorts of info about the current server, a user, etc.", - run: "Please provide an argument.\nFor help, run `.help info`.", + run: "Please provide an argument.\nFor help, run `%prefix%help info`.", subcommands: { avatar: new Command({ diff --git a/src/core/event.ts b/src/core/event.ts index 6b2c813..4ede652 100644 --- a/src/core/event.ts +++ b/src/core/event.ts @@ -1,34 +1,31 @@ -import {Client} from "discord.js"; +import {Client, ClientEvents, Constants} from "discord.js"; import Storage from "./storage"; import $ from "./lib"; -// Last Updated: Discord.js v12.2.0 -const EVENTS = ["channelCreate", "channelDelete", "channelPinsUpdate", "channelUpdate", "debug", "warn", "disconnect", "emojiCreate", "emojiDelete", "emojiUpdate", "error", "guildBanAdd", "guildBanRemove", "guildCreate", "guildDelete", "guildUnavailable", "guildIntegrationsUpdate", "guildMemberAdd", "guildMemberAvailable", "guildMemberRemove", "guildMembersChunk", "guildMemberSpeaking", "guildMemberUpdate", "guildUpdate", "inviteCreate", "inviteDelete", "message", "messageDelete", "messageReactionRemoveAll", "messageReactionRemoveEmoji", "messageDeleteBulk", "messageReactionAdd", "messageReactionRemove", "messageUpdate", "presenceUpdate", "rateLimit", "ready", "invalidated", "roleCreate", "roleDelete", "roleUpdate", "typingStart", "userUpdate", "voiceStateUpdate", "webhookUpdate", "shardDisconnect", "shardError", "shardReady", "shardReconnecting", "shardResume"]; - -interface EventOptions +interface EventOptions { - readonly on?: Function; - readonly once?: Function; + readonly on?: (...args: ClientEvents[K]) => void; + readonly once?: (...args: ClientEvents[K]) => void; } -export default class Event +export default class Event { - private readonly on: Function|null; - private readonly once: Function|null; + private readonly on?: (...args: ClientEvents[K]) => void; + private readonly once?: (...args: ClientEvents[K]) => void; - constructor(options: EventOptions) + constructor(options: EventOptions) { - this.on = options.on || null; - this.once = options.once || null; + this.on = options.on; + this.once = options.once; } // For this function, I'm going to assume that the event is used with the correct arguments and that the event tag is checked in "storage.ts". - public attach(client: Client, event: string) + public attach(client: Client, event: K) { if(this.on) - client.on(event as any, this.on as any); + client.on(event, this.on); if(this.once) - client.once(event as any, this.once as any); + client.once(event, this.once); } } @@ -39,7 +36,7 @@ export async function loadEvents(client: Client) const header = file.substring(0, file.indexOf(".js")); const event = (await import(`../events/${header}`)).default; - if(EVENTS.includes(header)) + if((Object.values(Constants.Events) as string[]).includes(header)) { event.attach(client, header); $.log(`Loading Event: ${header}`); diff --git a/src/core/lib.ts b/src/core/lib.ts index a052276..e22e354 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -3,7 +3,7 @@ import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, Guild import chalk from "chalk"; import FileManager from "./storage"; import {eventListeners} from "../events/messageReactionRemove"; -import {client} from "../index"; +import {botHasPermission} from "../index"; /** A type that describes what the library module does. */ export interface CommonLibrary @@ -170,10 +170,10 @@ $.paginate = async(message: Message, senderID: string, total: number, callback: await message.awaitReactions((reaction, user) => { // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. - const canDeleteEmotes = !!(client.user && message.guild?.members.resolve(client.user)?.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES)); + const canDeleteEmotes = botHasPermission(message.guild, Permissions.FLAGS.MANAGE_MESSAGES); handle(reaction.emoji.name, user.id); - if(canDeleteEmotes && user.id !== client.user?.id) + if(canDeleteEmotes) reaction.users.remove(user); return false; @@ -258,7 +258,7 @@ export function parseArgs(line: string): string[] * - `%%` = `%` * - If the invalid token is null/undefined, nothing is changed. */ -export function parseVars(line: string, definitions: {[key: string]: string}, invalid: string|null|undefined = ""): string +export function parseVars(line: string, definitions: {[key: string]: string}, invalid: string|null = ""): string { let result = ""; let inVariable = false; @@ -276,7 +276,7 @@ export function parseVars(line: string, definitions: {[key: string]: string}, in { if(token in definitions) result += definitions[token]; - else if(invalid === undefined || invalid === null) + else if(invalid === null) result += `%${token}%`; else result += invalid; @@ -352,7 +352,7 @@ export interface GenericJSON export abstract class GenericStructure { - protected __meta__ = "generic"; + private __meta__ = "generic"; constructor(tag?: string) { diff --git a/src/core/permissions.ts b/src/core/permissions.ts index a69a944..56ea4d9 100644 --- a/src/core/permissions.ts +++ b/src/core/permissions.ts @@ -38,30 +38,17 @@ const length = Object.keys(PERMISSIONS).length / 2; export function hasPermission(member: GuildMember, permission: PERMISSIONS): boolean { - if(permission === PERMISSIONS.NONE) - return true; - for(let i = length-1; i >= permission; i--) - { - const condition = PermissionChecker[i](member); - - if(condition) + if(PermissionChecker[i](member)) return true; - } - return false; } export function getPermissionLevel(member: GuildMember): number { for(let i = length-1; i >= 0; i--) - { - const condition = PermissionChecker[i](member); - - if(condition) + if(PermissionChecker[i](member)) return i; - } - return 0; } diff --git a/src/events/message.ts b/src/events/message.ts index becc9bd..a7eb4cd 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -1,15 +1,15 @@ import Event from "../core/event"; import Command, {loadCommands} from "../core/command"; import {hasPermission, getPermissionLevel, PermissionNames} from "../core/permissions"; -import $ from "../core/lib"; -import {Message, Permissions, Collection} from "discord.js"; +import {Permissions, Collection} from "discord.js"; import {getPrefix} from "../core/structures"; +import $ from "../core/lib"; // It's a rather hacky solution, but since there's no top-level await, I just have to make the loading conditional. let commands: Collection|null = null; -export default new Event({ - async on(message: Message) +export default new Event<"message">({ + async on(message) { // Load commands if it hasn't already done so. Luckily, it's called once at most. if(!commands) diff --git a/src/events/messageReactionRemove.ts b/src/events/messageReactionRemove.ts index 986e94e..4304d27 100644 --- a/src/events/messageReactionRemove.ts +++ b/src/events/messageReactionRemove.ts @@ -1,15 +1,15 @@ import Event from "../core/event"; -import {MessageReaction, User, PartialUser, Permissions} from "discord.js"; -import {client} from "../index"; +import {Permissions} from "discord.js"; +import {botHasPermission} from "../index"; // A list of message ID and callback pairs. You get the emote name and ID of the user reacting. export const eventListeners: Map void> = new Map(); // Attached to the client, there can be one event listener attached to a message ID which is executed if present. -export default new Event({ - on(reaction: MessageReaction, user: User|PartialUser) +export default new Event<"messageReactionRemove">({ + on(reaction, user) { - const canDeleteEmotes = !!(client.user && reaction.message.guild?.members.resolve(client.user)?.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES)); + const canDeleteEmotes = botHasPermission(reaction.message.guild, Permissions.FLAGS.MANAGE_MESSAGES); if(!canDeleteEmotes) { diff --git a/src/events/ready.ts b/src/events/ready.ts index 7c4b78e..a7ff1e8 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -3,7 +3,7 @@ import {client} from "../index"; import $ from "../core/lib"; import {Config} from "../core/structures"; -export default new Event({ +export default new Event<"ready">({ once() { if(client.user) diff --git a/src/index.ts b/src/index.ts index 0c2718a..39c2b29 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import {Client} from "discord.js"; +import {Client, Guild} from "discord.js"; import setup from "./setup"; import {Config} from "./core/structures"; import {loadCommands} from "./core/command"; @@ -13,4 +13,9 @@ setup.init().then(() => { loadCommands(); loadEvents(client); client.login(Config.token).catch(setup.again); -}); \ No newline at end of file +}); + +export function botHasPermission(guild: Guild|null, permission: number): boolean +{ + return !!(client.user && guild?.members.resolve(client.user)?.hasPermission(permission)) +} \ No newline at end of file diff --git a/src/setup.ts b/src/setup.ts index cfcfb5d..7f08a93 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -44,7 +44,7 @@ export default { Config.admins = admins !== "" ? admins.split(" ") : []; const support = (answers.support as string); Config.support = support !== "" ? support.split(" ") : []; - Config.save(); + Config.save(false); } }, /** Prompt the user to set their token again. */