From 3ef487c4a4d7eb1227091923c4399e004e409cdb Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 30 Mar 2021 21:19:04 -0500 Subject: [PATCH 1/3] Reorganized lib/libd functions and Lavalink --- src/commands/admin.ts | 4 +- src/commands/fun/cookie.ts | 2 +- src/commands/fun/neko.ts | 2 +- src/commands/info.ts | 3 +- src/core/command.ts | 2 +- src/core/lib.ts | 165 +++++++++++++++++++++++++++++++++++++ src/core/libd.ts | 164 ------------------------------------ src/core/structures.ts | 2 +- src/index.ts | 56 +------------ src/modules/lavalink.ts | 53 ++++++++++++ 10 files changed, 230 insertions(+), 223 deletions(-) create mode 100644 src/modules/lavalink.ts diff --git a/src/commands/admin.ts b/src/commands/admin.ts index e0c4362..72c5686 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -1,9 +1,9 @@ import Command, {handler} from "../core/command"; -import {botHasPermission, clean} from "../core/libd"; +import {clean} from "../core/lib"; +import {botHasPermission} from "../core/libd"; import {Config, Storage} from "../core/structures"; import {getPermissionLevel, getPermissionName} from "../core/permissions"; import {Permissions} from "discord.js"; -import * as discord from "discord.js"; import {logs} from "../globals"; function getLogBuffer(type: string) { diff --git a/src/commands/fun/cookie.ts b/src/commands/fun/cookie.ts index 39a3948..06c7086 100644 --- a/src/commands/fun/cookie.ts +++ b/src/commands/fun/cookie.ts @@ -1,7 +1,7 @@ import {User} from "discord.js"; import Command from "../../core/command"; import {random} from "../../core/lib"; -import {parseVars} from "../../core/libd"; +import {parseVars} from "../../core/lib"; const cookies = [ `has given %target% a chocolate chip cookie!`, diff --git a/src/commands/fun/neko.ts b/src/commands/fun/neko.ts index 1990bc2..113fb15 100644 --- a/src/commands/fun/neko.ts +++ b/src/commands/fun/neko.ts @@ -1,6 +1,6 @@ import {URL} from "url"; import Command from "../../core/command"; -import {getContent} from "../../core/libd"; +import {getContent} from "../../core/lib"; const endpoints: {sfw: {[key: string]: string}} = { sfw: { diff --git a/src/commands/info.ts b/src/commands/info.ts index 7142ba5..b95f19a 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -2,7 +2,8 @@ import {MessageEmbed, version as djsversion} from "discord.js"; import ms from "ms"; import os from "os"; import Command from "../core/command"; -import {formatBytes, trimArray, getMemberByUsername} from "../core/libd"; +import {formatBytes, trimArray} from "../core/lib"; +import {getMemberByUsername} from "../core/libd"; import {verificationLevels, filterLevels, regions} from "../defs/info"; import moment from "moment"; import utc from "moment"; diff --git a/src/core/command.ts b/src/core/command.ts index c40d5a0..c13b3a3 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -1,4 +1,4 @@ -import {parseVars} from "./libd"; +import {parseVars} from "./lib"; import {Collection} from "discord.js"; import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember} from "discord.js"; import {getPrefix} from "../core/structures"; diff --git a/src/core/lib.ts b/src/core/lib.ts index a0ebcb7..21b4a95 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -1,4 +1,169 @@ // Library for pure functions +import {get} from "https"; +import FileManager from "./storage"; + +/** + * Splits a command by spaces while accounting for quotes which capture string arguments. + * - `\"` = `"` + * - `\\` = `\` + */ +export function parseArgs(line: string): string[] { + let result = []; + let selection = ""; + let inString = false; + let isEscaped = false; + + for (let c of line) { + if (isEscaped) { + if (['"', "\\"].includes(c)) selection += c; + else selection += "\\" + c; + + isEscaped = false; + } else if (c === "\\") isEscaped = true; + else if (c === '"') inString = !inString; + else if (c === " " && !inString) { + result.push(selection); + selection = ""; + } else selection += c; + } + + if (selection.length > 0) result.push(selection); + + return result; +} + +/** + * Allows you to store a template string with variable markers and parse it later. + * - Use `%name%` for variables + * - `%%` = `%` + * - If the invalid token is null/undefined, nothing is changed. + */ +export function parseVars(line: string, definitions: {[key: string]: string}, invalid: string | null = ""): string { + let result = ""; + let inVariable = false; + let token = ""; + + for (const c of line) { + if (c === "%") { + if (inVariable) { + if (token === "") result += "%"; + else { + if (token in definitions) result += definitions[token]; + else if (invalid === null) result += `%${token}%`; + else result += invalid; + + token = ""; + } + } + + inVariable = !inVariable; + } else if (inVariable) token += c; + else result += c; + } + + return result; +} + +export function isType(value: any, type: any): boolean { + if (value === undefined && type === undefined) return true; + else if (value === null && type === null) return true; + else return value !== undefined && value !== null && value.constructor === type; +} + +/** + * Checks a value to see if it matches the fallback's type, otherwise returns the fallback. + * For the purposes of the templates system, this function will only check array types, objects should be checked under their own type (as you'd do anyway with something like a User object). + * If at any point the value doesn't match the data structure provided, the fallback is returned. + * Warning: Type checking is based on the fallback's type. Be sure that the "type" parameter is accurate to this! + */ +export function select(value: any, fallback: T, type: Function, isArray = false): T { + if (isArray && isType(value, Array)) { + for (let item of value) if (!isType(item, type)) return fallback; + return value; + } else { + if (isType(value, type)) return value; + else return fallback; + } +} + +export function clean(text: any) { + if (typeof text === "string") + return text.replace(/`/g, "`" + String.fromCharCode(8203)).replace(/@/g, "@" + String.fromCharCode(8203)); + else return text; +} + +export function trimArray(arr: any, maxLen = 10) { + if (arr.length > maxLen) { + const len = arr.length - maxLen; + arr = arr.slice(0, maxLen); + arr.push(`${len} more...`); + } + return arr; +} + +export function formatBytes(bytes: any) { + if (bytes === 0) return "0 Bytes"; + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(2))} ${sizes[i]}`; +} + +export function getContent(url: string): Promise<{url: string}> { + return new Promise((resolve, reject) => { + get(url, (res) => { + const {statusCode} = res; + if (statusCode !== 200) { + res.resume(); + reject(`Request failed. Status code: ${statusCode}`); + } + res.setEncoding("utf8"); + let rawData = ""; + res.on("data", (chunk: string) => { + rawData += chunk; + }); + res.on("end", () => { + try { + const parsedData = JSON.parse(rawData); + resolve(parsedData); + } catch (e) { + reject(`Error: ${e.message}`); + } + }); + }).on("error", (err: {message: any}) => { + reject(`Error: ${err.message}`); + }); + }); +} + +export interface GenericJSON { + [key: string]: any; +} + +export abstract class GenericStructure { + private __meta__ = "generic"; + + constructor(tag?: string) { + this.__meta__ = tag || this.__meta__; + } + + public save(asynchronous = true) { + const tag = this.__meta__; + /// @ts-ignore + delete this.__meta__; + FileManager.write(tag, this, asynchronous); + this.__meta__ = tag; + } +} + +// A 50% chance would be "Math.random() < 0.5" because Math.random() can be [0, 1), so to make two equal ranges, you'd need [0, 0.5)U[0.5, 1). +// Similar logic would follow for any other percentage. Math.random() < 1 is always true (100% chance) and Math.random() < 0 is always false (0% chance). +export const Random = { + num: (min: number, max: number) => Math.random() * (max - min) + min, + int: (min: number, max: number) => Math.floor(Random.num(min, max)), + chance: (decimal: number) => Math.random() < decimal, + sign: (number = 1) => number * (Random.chance(0.5) ? -1 : 1), + deviation: (base: number, deviation: number) => Random.num(base - deviation, base + deviation) +}; /** * Pluralises a word and chooses a suffix attached to the root provided. diff --git a/src/core/libd.ts b/src/core/libd.ts index 20a1ab1..ab391cf 100644 --- a/src/core/libd.ts +++ b/src/core/libd.ts @@ -9,7 +9,6 @@ import { NewsChannel, MessageOptions } from "discord.js"; -import {get} from "https"; import FileManager from "./storage"; import {eventListeners} from "../events/messageReactionRemove"; import {client} from "../index"; @@ -274,166 +273,3 @@ export async function callMemberByUsername( else send(`Couldn't find a user by the name of \`${username}\`!`); } else send("You must execute this command in a server!"); } - -/** - * Splits a command by spaces while accounting for quotes which capture string arguments. - * - `\"` = `"` - * - `\\` = `\` - */ -export function parseArgs(line: string): string[] { - let result = []; - let selection = ""; - let inString = false; - let isEscaped = false; - - for (let c of line) { - if (isEscaped) { - if (['"', "\\"].includes(c)) selection += c; - else selection += "\\" + c; - - isEscaped = false; - } else if (c === "\\") isEscaped = true; - else if (c === '"') inString = !inString; - else if (c === " " && !inString) { - result.push(selection); - selection = ""; - } else selection += c; - } - - if (selection.length > 0) result.push(selection); - - return result; -} - -/** - * Allows you to store a template string with variable markers and parse it later. - * - Use `%name%` for variables - * - `%%` = `%` - * - If the invalid token is null/undefined, nothing is changed. - */ -export function parseVars(line: string, definitions: {[key: string]: string}, invalid: string | null = ""): string { - let result = ""; - let inVariable = false; - let token = ""; - - for (const c of line) { - if (c === "%") { - if (inVariable) { - if (token === "") result += "%"; - else { - if (token in definitions) result += definitions[token]; - else if (invalid === null) result += `%${token}%`; - else result += invalid; - - token = ""; - } - } - - inVariable = !inVariable; - } else if (inVariable) token += c; - else result += c; - } - - return result; -} - -export function isType(value: any, type: any): boolean { - if (value === undefined && type === undefined) return true; - else if (value === null && type === null) return true; - else return value !== undefined && value !== null && value.constructor === type; -} - -/** - * Checks a value to see if it matches the fallback's type, otherwise returns the fallback. - * For the purposes of the templates system, this function will only check array types, objects should be checked under their own type (as you'd do anyway with something like a User object). - * If at any point the value doesn't match the data structure provided, the fallback is returned. - * Warning: Type checking is based on the fallback's type. Be sure that the "type" parameter is accurate to this! - */ -export function select(value: any, fallback: T, type: Function, isArray = false): T { - if (isArray && isType(value, Array)) { - for (let item of value) if (!isType(item, type)) return fallback; - return value; - } else { - if (isType(value, type)) return value; - else return fallback; - } -} - -export function clean(text: any) { - if (typeof text === "string") - return text.replace(/`/g, "`" + String.fromCharCode(8203)).replace(/@/g, "@" + String.fromCharCode(8203)); - else return text; -} - -export function trimArray(arr: any, maxLen = 10) { - if (arr.length > maxLen) { - const len = arr.length - maxLen; - arr = arr.slice(0, maxLen); - arr.push(`${len} more...`); - } - return arr; -} - -export function formatBytes(bytes: any) { - if (bytes === 0) return "0 Bytes"; - const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; - const i = Math.floor(Math.log(bytes) / Math.log(1024)); - return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(2))} ${sizes[i]}`; -} - -export function getContent(url: string): Promise<{url: string}> { - return new Promise((resolve, reject) => { - get(url, (res) => { - const {statusCode} = res; - if (statusCode !== 200) { - res.resume(); - reject(`Request failed. Status code: ${statusCode}`); - } - res.setEncoding("utf8"); - let rawData = ""; - res.on("data", (chunk: string) => { - rawData += chunk; - }); - res.on("end", () => { - try { - const parsedData = JSON.parse(rawData); - resolve(parsedData); - } catch (e) { - reject(`Error: ${e.message}`); - } - }); - }).on("error", (err: {message: any}) => { - reject(`Error: ${err.message}`); - }); - }); -} - -export interface GenericJSON { - [key: string]: any; -} - -export abstract class GenericStructure { - private __meta__ = "generic"; - - constructor(tag?: string) { - this.__meta__ = tag || this.__meta__; - } - - public save(asynchronous = true) { - const tag = this.__meta__; - /// @ts-ignore - delete this.__meta__; - FileManager.write(tag, this, asynchronous); - this.__meta__ = tag; - } -} - -// A 50% chance would be "Math.random() < 0.5" because Math.random() can be [0, 1), so to make two equal ranges, you'd need [0, 0.5)U[0.5, 1). -// Similar logic would follow for any other percentage. Math.random() < 1 is always true (100% chance) and Math.random() < 0 is always false (0% chance). -export const Random = { - num: (min: number, max: number) => Math.random() * (max - min) + min, - int: (min: number, max: number) => Math.floor(Random.num(min, max)), - chance: (decimal: number) => Math.random() < decimal, - sign: (number = 1) => number * (Random.chance(0.5) ? -1 : 1), - deviation: (base: number, deviation: number) => Random.num(base - deviation, base + deviation) -}; diff --git a/src/core/structures.ts b/src/core/structures.ts index 07d8f5c..74e5adf 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -1,5 +1,5 @@ import FileManager from "./storage"; -import {select, GenericJSON, GenericStructure} from "./libd"; +import {select, GenericJSON, GenericStructure} from "./lib"; import {watch} from "fs"; import {Guild as DiscordGuild, Snowflake} from "discord.js"; diff --git a/src/index.ts b/src/index.ts index c6320a8..2ccf92c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,62 +1,14 @@ import "./globals"; -import * as discord from "discord.js"; +import {Client} from "discord.js"; import setup from "./setup"; import {Config} from "./core/structures"; import {loadEvents} from "./core/event"; -import "discord.js-lavalink-lib"; -import LavalinkMusic from "discord.js-lavalink-lib"; - -declare module "discord.js" { - interface Presence { - patch(data: any): void; - } -} - -// The terrible hacks were written by none other than The Noble Programmer On The White PC. - -// NOTE: Terrible hack ahead!!! In order to reduce the memory usage of the bot -// we only store the information from presences that we actually end up using, -// which currently is only the (online/idle/dnd/offline/...) status (see -// `src/commands/info.ts`). What data is retrieved from the `data` object -// (which contains the data received from the Gateway) and how can be seen -// here: -// . -const oldPresencePatch = discord.Presence.prototype.patch; -discord.Presence.prototype.patch = function patch(data: any) { - oldPresencePatch.call(this, {status: data.status}); -}; +import {attachToClient} from "./modules/lavalink"; // This is here in order to make it much less of a headache to access the client from other files. // This of course won't actually do anything until the setup process is complete and it logs in. -export const client = new discord.Client(); - -// NOTE: Terrible hack continued!!! Unfortunately we can't receive the presence -// data at all when the GUILD_PRESENCES intent is disabled, so while we do -// waste network bandwidth and the CPU time for decoding the incoming packets, -// the function which handles those packets is NOP-ed out, which, among other -// things, skips the code which caches the referenced users in the packet. See -// . -(client["actions"] as any)["PresenceUpdate"].handle = () => {}; - -(client as any).music = LavalinkMusic(client, { - lavalink: { - restnode: { - host: "localhost", - port: 2333, - password: "youshallnotpass" - }, - nodes: [ - { - host: "localhost", - port: 2333, - password: "youshallnotpass" - } - ] - }, - prefix: Config.prefix, - helpCmd: "mhelp", - admins: ["717352467280691331"] -}); +export const client = new Client(); +attachToClient(client); // Command loading will start as soon as an instance of "core/command" is loaded, which is loaded during "events/message". setup.init().then(() => { diff --git a/src/modules/lavalink.ts b/src/modules/lavalink.ts new file mode 100644 index 0000000..5e0ff90 --- /dev/null +++ b/src/modules/lavalink.ts @@ -0,0 +1,53 @@ +import {Presence, Client} from "discord.js"; +import LavalinkMusic from "discord.js-lavalink-lib"; +import {Config} from "../core/structures"; + +declare module "discord.js" { + interface Presence { + patch(data: any): void; + } +} + +// The terrible hacks were written by none other than The Noble Programmer On The White PC. + +// NOTE: Terrible hack ahead!!! In order to reduce the memory usage of the bot +// we only store the information from presences that we actually end up using, +// which currently is only the (online/idle/dnd/offline/...) status (see +// `src/commands/info.ts`). What data is retrieved from the `data` object +// (which contains the data received from the Gateway) and how can be seen +// here: +// . +const oldPresencePatch = Presence.prototype.patch; +Presence.prototype.patch = function patch(data: any) { + oldPresencePatch.call(this, {status: data.status}); +}; + +// NOTE: Terrible hack continued!!! Unfortunately we can't receive the presence +// data at all when the GUILD_PRESENCES intent is disabled, so while we do +// waste network bandwidth and the CPU time for decoding the incoming packets, +// the function which handles those packets is NOP-ed out, which, among other +// things, skips the code which caches the referenced users in the packet. See +// . +export function attachToClient(client: Client) { + (client["actions"] as any)["PresenceUpdate"].handle = () => {}; + + (client as any).music = LavalinkMusic(client, { + lavalink: { + restnode: { + host: "localhost", + port: 2333, + password: "youshallnotpass" + }, + nodes: [ + { + host: "localhost", + port: 2333, + password: "youshallnotpass" + } + ] + }, + prefix: Config.prefix, + helpCmd: "mhelp", + admins: ["717352467280691331"] + }); +} From 945102b7cf54ce67f1c5abaf82664884444aeb01 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 30 Mar 2021 21:56:25 -0500 Subject: [PATCH 2/3] Reworked event loading --- .gitignore | 3 +- src/core/event.ts | 38 ------- src/core/handler.ts | 158 ++++++++++++++++++++++++++++ src/core/libd.ts | 37 +++---- src/events/channelCreate.ts | 13 --- src/events/channelDelete.ts | 13 --- src/events/emojiCreate.ts | 9 -- src/events/emojiDelete.ts | 9 -- src/events/emojiUpdate.ts | 9 -- src/events/guildCreate.ts | 9 -- src/events/guildDelete.ts | 9 -- src/events/message.ts | 149 -------------------------- src/events/messageReactionRemove.ts | 18 ---- src/events/ready.ts | 17 --- src/index.ts | 13 ++- src/modules/channelListener.ts | 20 ++++ src/modules/emoteRegistry.ts | 53 ++++++++++ src/modules/lavalink.ts | 39 ++++--- 18 files changed, 272 insertions(+), 344 deletions(-) delete mode 100644 src/core/event.ts create mode 100644 src/core/handler.ts delete mode 100644 src/events/channelCreate.ts delete mode 100644 src/events/channelDelete.ts delete mode 100644 src/events/emojiCreate.ts delete mode 100644 src/events/emojiDelete.ts delete mode 100644 src/events/emojiUpdate.ts delete mode 100644 src/events/guildCreate.ts delete mode 100644 src/events/guildDelete.ts delete mode 100644 src/events/message.ts delete mode 100644 src/events/messageReactionRemove.ts delete mode 100644 src/events/ready.ts create mode 100644 src/modules/channelListener.ts create mode 100644 src/modules/emoteRegistry.ts diff --git a/.gitignore b/.gitignore index a3becd9..109f5cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # Specific to this repository dist/ -data/* -!data/endpoints.json +data/ tmp/ test* !test/ diff --git a/src/core/event.ts b/src/core/event.ts deleted file mode 100644 index d2b6f9a..0000000 --- a/src/core/event.ts +++ /dev/null @@ -1,38 +0,0 @@ -import {Client, ClientEvents, Constants} from "discord.js"; -import Storage from "./storage"; - -interface EventOptions { - readonly on?: (...args: ClientEvents[K]) => void; - readonly once?: (...args: ClientEvents[K]) => void; -} - -export default class Event { - private readonly on?: (...args: ClientEvents[K]) => void; - private readonly once?: (...args: ClientEvents[K]) => void; - - constructor(options: EventOptions) { - 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: K) { - if (this.on) client.on(event, this.on); - if (this.once) client.once(event, this.once); - } -} - -export async function loadEvents(client: Client) { - for (const file of Storage.open("dist/events", (filename: string) => filename.endsWith(".js"))) { - const header = file.substring(0, file.indexOf(".js")); - const event = (await import(`../events/${header}`)).default; - - if ((Object.values(Constants.Events) as string[]).includes(header)) { - event.attach(client, header); - console.log(`Loading Event: ${header}`); - } else - console.warn( - `"${header}" is not a valid event type! Did you misspell it? (Note: If you fixed the issue, delete "dist" because the compiler won't automatically delete any extra files.)` - ); - } -} diff --git a/src/core/handler.ts b/src/core/handler.ts new file mode 100644 index 0000000..1de6c1e --- /dev/null +++ b/src/core/handler.ts @@ -0,0 +1,158 @@ +import {client} from "../index"; +import Command, {loadableCommands} from "../core/command"; +import {hasPermission, getPermissionLevel, getPermissionName} from "../core/permissions"; +import {Permissions} from "discord.js"; +import {getPrefix} from "../core/structures"; +import {replyEventListeners} from "../core/libd"; +import quote from "../modules/message_embed"; +import {Config} from "../core/structures"; + +client.on("message", async (message) => { + const commands = await loadableCommands; + + if (message.content.toLowerCase().includes("remember to drink water")) { + message.react("🚱"); + } + + // Message Setup // + if (message.author.bot) return; + + // If there's an inline reply, fire off that event listener (if it exists). + if (message.reference) { + const reference = message.reference; + replyEventListeners.get(`${reference.channelID}-${reference.messageID}`)?.(message); + } + + let prefix = getPrefix(message.guild); + const originalPrefix = prefix; + let exitEarly = !message.content.startsWith(prefix); + const clientUser = message.client.user; + let usesBotSpecificPrefix = false; + + if (!message.content.startsWith(prefix)) { + return quote(message); + } + + // If the client user exists, check if it starts with the bot-specific prefix. + if (clientUser) { + // If the prefix starts with the bot-specific prefix, go off that instead (these two options must mutually exclude each other). + // The pattern here has an optional space at the end to capture that and make it not mess with the header and args. + const matches = message.content.match(new RegExp(`^<@!?${clientUser.id}> ?`)); + + if (matches) { + prefix = matches[0]; + exitEarly = false; + usesBotSpecificPrefix = true; + } + } + + // If it doesn't start with the current normal prefix or the bot-specific unique prefix, exit the thread of execution early. + // Inline replies should still be captured here because if it doesn't exit early, two characters for a two-length prefix would still trigger commands. + if (exitEarly) return; + + const [header, ...args] = message.content.substring(prefix.length).split(/ +/); + + // If the message is just the prefix itself, move onto this block. + if (header === "" && args.length === 0) { + // I moved the bot-specific prefix to a separate conditional block to separate the logic. + // And because it listens for the mention as a prefix instead of a free-form mention, inline replies (probably) shouldn't ever trigger this unintentionally. + if (usesBotSpecificPrefix) { + message.channel.send(`${message.author.toString()}, my prefix on this guild is \`${originalPrefix}\`.`); + return; + } + } + + if (!commands.has(header)) return; + + if ( + message.channel.type === "text" && + !message.channel.permissionsFor(message.client.user || "")?.has(Permissions.FLAGS.SEND_MESSAGES) + ) { + let status; + + if (message.member?.hasPermission(Permissions.FLAGS.ADMINISTRATOR)) + status = + "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended."; + else + status = + "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong."; + + return message.author.send( + `I don't have permission to send messages in ${message.channel.toString()}. ${status}` + ); + } + + console.log( + `${message.author.username}#${message.author.discriminator} executed the command "${header}" with arguments "${args}".` + ); + + // Subcommand Recursion // + let command = commands.get(header); + if (!command) return console.warn(`Command "${header}" was called but for some reason it's still undefined!`); + const params: any[] = []; + let isEndpoint = false; + let permLevel = command.permission ?? 0; + + for (let param of args) { + if (command.endpoint) { + if (command.subcommands.size > 0 || command.user || command.number || command.any) + console.warn(`An endpoint cannot have subcommands! Check ${originalPrefix}${header} again.`); + isEndpoint = true; + break; + } + + const type = command.resolve(param); + command = command.get(param); + 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}\`!`); + } + } else if (type === Command.TYPES.NUMBER) params.push(Number(param)); + else if (type !== Command.TYPES.SUBCOMMAND) params.push(param); + } + + if (!message.member) + return console.warn("This command was likely called from a DM channel meaning the member object is null."); + + if (!hasPermission(message.member, permLevel)) { + const userPermLevel = getPermissionLevel(message.member); + return message.channel.send( + `You don't have access to this command! Your permission level is \`${getPermissionName( + userPermLevel + )}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName( + permLevel + )}\` (${permLevel}).` + ); + } + + if (isEndpoint) return message.channel.send("Too many arguments!"); + + // Execute with dynamic library attached. // + // The purpose of using $.bind($) is to clone the function so as to not modify the original $. + // The cloned function doesn't copy the properties, so Object.assign() is used. + // Object.assign() modifies the first element and returns that, the second element applies its properties and the third element applies its own overriding the second one. + command.execute({ + args: params, + author: message.author, + channel: message.channel, + client: message.client, + guild: message.guild, + member: message.member, + message: message + }); +}); + +client.once("ready", () => { + if (client.user) { + console.ready(`Logged in as ${client.user.username}#${client.user.discriminator}.`); + client.user.setActivity({ + type: "LISTENING", + name: `${Config.prefix}help` + }); + } +}); diff --git a/src/core/libd.ts b/src/core/libd.ts index ab391cf..71ba70a 100644 --- a/src/core/libd.ts +++ b/src/core/libd.ts @@ -9,36 +9,25 @@ import { NewsChannel, MessageOptions } from "discord.js"; -import FileManager from "./storage"; -import {eventListeners} from "../events/messageReactionRemove"; import {client} from "../index"; -import {EmoteRegistryDump} from "./structures"; + +// A list of message ID and callback pairs. You get the emote name and ID of the user reacting. +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. +client.on("messageReactionRemove", (reaction, user) => { + const canDeleteEmotes = botHasPermission(reaction.message.guild, Permissions.FLAGS.MANAGE_MESSAGES); + + if (!canDeleteEmotes) { + const callback = eventListeners.get(reaction.message.id); + callback && callback(reaction.emoji.name, user.id); + } +}); export function botHasPermission(guild: Guild | null, permission: number): boolean { return !!guild?.me?.hasPermission(permission); } -export function updateGlobalEmoteRegistry(): void { - const data: EmoteRegistryDump = {version: 1, list: []}; - - for (const guild of client.guilds.cache.values()) { - for (const emote of guild.emojis.cache.values()) { - data.list.push({ - ref: emote.name, - id: emote.id, - name: emote.name, - requires_colons: emote.requiresColons || false, - animated: emote.animated, - url: emote.url, - guild_id: emote.guild.name, - guild_name: emote.guild.name - }); - } - } - - FileManager.write("emote-registry", data, true); -} - // 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. diff --git a/src/events/channelCreate.ts b/src/events/channelCreate.ts deleted file mode 100644 index 47c2aa3..0000000 --- a/src/events/channelCreate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import Event from "../core/event"; -import {client} from "../index"; -import * as discord from "discord.js"; - -export default new Event<"channelCreate">({ - async on(channel) { - const botGuilds = client.guilds; - if (channel instanceof discord.GuildChannel) { - const createdGuild = await botGuilds.fetch(channel.guild.id); - console.log(`Channel created in '${createdGuild.name}' called '#${channel.name}'`); - } - } -}); diff --git a/src/events/channelDelete.ts b/src/events/channelDelete.ts deleted file mode 100644 index ac835ed..0000000 --- a/src/events/channelDelete.ts +++ /dev/null @@ -1,13 +0,0 @@ -import Event from "../core/event"; -import {client} from "../index"; -import * as discord from "discord.js"; - -export default new Event<"channelDelete">({ - async on(channel) { - const botGuilds = client.guilds; - if (channel instanceof discord.GuildChannel) { - const createdGuild = await botGuilds.fetch(channel.guild.id); - console.log(`Channel deleted in '${createdGuild.name}' called '#${channel.name}'`); - } - } -}); diff --git a/src/events/emojiCreate.ts b/src/events/emojiCreate.ts deleted file mode 100644 index 92ef88e..0000000 --- a/src/events/emojiCreate.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Event from "../core/event"; -import {updateGlobalEmoteRegistry} from "../core/libd"; - -export default new Event<"emojiCreate">({ - on(emote) { - console.log(`Updated emote registry. ${emote.name}`); - updateGlobalEmoteRegistry(); - } -}); diff --git a/src/events/emojiDelete.ts b/src/events/emojiDelete.ts deleted file mode 100644 index dc34190..0000000 --- a/src/events/emojiDelete.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Event from "../core/event"; -import {updateGlobalEmoteRegistry} from "../core/libd"; - -export default new Event<"emojiDelete">({ - on() { - console.log("Updated emote registry."); - updateGlobalEmoteRegistry(); - } -}); diff --git a/src/events/emojiUpdate.ts b/src/events/emojiUpdate.ts deleted file mode 100644 index bd0b6b0..0000000 --- a/src/events/emojiUpdate.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Event from "../core/event"; -import {updateGlobalEmoteRegistry} from "../core/libd"; - -export default new Event<"emojiUpdate">({ - on() { - console.log("Updated emote registry."); - updateGlobalEmoteRegistry(); - } -}); diff --git a/src/events/guildCreate.ts b/src/events/guildCreate.ts deleted file mode 100644 index 480bd4d..0000000 --- a/src/events/guildCreate.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Event from "../core/event"; -import {updateGlobalEmoteRegistry} from "../core/libd"; - -export default new Event<"guildCreate">({ - on() { - console.log("Updated emote registry."); - updateGlobalEmoteRegistry(); - } -}); diff --git a/src/events/guildDelete.ts b/src/events/guildDelete.ts deleted file mode 100644 index 755a049..0000000 --- a/src/events/guildDelete.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Event from "../core/event"; -import {updateGlobalEmoteRegistry} from "../core/libd"; - -export default new Event<"guildDelete">({ - on() { - console.log("Updated emote registry."); - updateGlobalEmoteRegistry(); - } -}); diff --git a/src/events/message.ts b/src/events/message.ts deleted file mode 100644 index d805ed8..0000000 --- a/src/events/message.ts +++ /dev/null @@ -1,149 +0,0 @@ -import Event from "../core/event"; -import Command, {loadableCommands} from "../core/command"; -import {hasPermission, getPermissionLevel, getPermissionName} from "../core/permissions"; -import {Permissions} from "discord.js"; -import {getPrefix} from "../core/structures"; -import {replyEventListeners} from "../core/libd"; -import quote from "../modules/message_embed"; - -export default new Event<"message">({ - async on(message) { - const commands = await loadableCommands; - - if (message.content.toLowerCase().includes("remember to drink water")) { - message.react("🚱"); - } - - // Message Setup // - if (message.author.bot) return; - - // If there's an inline reply, fire off that event listener (if it exists). - if (message.reference) { - const reference = message.reference; - replyEventListeners.get(`${reference.channelID}-${reference.messageID}`)?.(message); - } - - let prefix = getPrefix(message.guild); - const originalPrefix = prefix; - let exitEarly = !message.content.startsWith(prefix); - const clientUser = message.client.user; - let usesBotSpecificPrefix = false; - - if (!message.content.startsWith(prefix)) { - return quote(message); - } - - // If the client user exists, check if it starts with the bot-specific prefix. - if (clientUser) { - // If the prefix starts with the bot-specific prefix, go off that instead (these two options must mutually exclude each other). - // The pattern here has an optional space at the end to capture that and make it not mess with the header and args. - const matches = message.content.match(new RegExp(`^<@!?${clientUser.id}> ?`)); - - if (matches) { - prefix = matches[0]; - exitEarly = false; - usesBotSpecificPrefix = true; - } - } - - // If it doesn't start with the current normal prefix or the bot-specific unique prefix, exit the thread of execution early. - // Inline replies should still be captured here because if it doesn't exit early, two characters for a two-length prefix would still trigger commands. - if (exitEarly) return; - - const [header, ...args] = message.content.substring(prefix.length).split(/ +/); - - // If the message is just the prefix itself, move onto this block. - if (header === "" && args.length === 0) { - // I moved the bot-specific prefix to a separate conditional block to separate the logic. - // And because it listens for the mention as a prefix instead of a free-form mention, inline replies (probably) shouldn't ever trigger this unintentionally. - if (usesBotSpecificPrefix) { - message.channel.send(`${message.author.toString()}, my prefix on this guild is \`${originalPrefix}\`.`); - return; - } - } - - if (!commands.has(header)) return; - - if ( - message.channel.type === "text" && - !message.channel.permissionsFor(message.client.user || "")?.has(Permissions.FLAGS.SEND_MESSAGES) - ) { - let status; - - if (message.member?.hasPermission(Permissions.FLAGS.ADMINISTRATOR)) - status = - "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended."; - else - status = - "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong."; - - return message.author.send( - `I don't have permission to send messages in ${message.channel.toString()}. ${status}` - ); - } - - console.log( - `${message.author.username}#${message.author.discriminator} executed the command "${header}" with arguments "${args}".` - ); - - // Subcommand Recursion // - let command = commands.get(header); - if (!command) return console.warn(`Command "${header}" was called but for some reason it's still undefined!`); - const params: any[] = []; - let isEndpoint = false; - let permLevel = command.permission ?? 0; - - for (let param of args) { - if (command.endpoint) { - if (command.subcommands.size > 0 || command.user || command.number || command.any) - console.warn(`An endpoint cannot have subcommands! Check ${originalPrefix}${header} again.`); - isEndpoint = true; - break; - } - - const type = command.resolve(param); - command = command.get(param); - 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}\`!`); - } - } else if (type === Command.TYPES.NUMBER) params.push(Number(param)); - else if (type !== Command.TYPES.SUBCOMMAND) params.push(param); - } - - if (!message.member) - return console.warn("This command was likely called from a DM channel meaning the member object is null."); - - if (!hasPermission(message.member, permLevel)) { - const userPermLevel = getPermissionLevel(message.member); - return message.channel.send( - `You don't have access to this command! Your permission level is \`${getPermissionName( - userPermLevel - )}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName( - permLevel - )}\` (${permLevel}).` - ); - } - - if (isEndpoint) return message.channel.send("Too many arguments!"); - - // Execute with dynamic library attached. // - // The purpose of using $.bind($) is to clone the function so as to not modify the original $. - // The cloned function doesn't copy the properties, so Object.assign() is used. - // Object.assign() modifies the first element and returns that, the second element applies its properties and the third element applies its own overriding the second one. - command.execute({ - args: params, - author: message.author, - channel: message.channel, - client: message.client, - guild: message.guild, - member: message.member, - message: message - }); - } -}); diff --git a/src/events/messageReactionRemove.ts b/src/events/messageReactionRemove.ts deleted file mode 100644 index 4b3dbb2..0000000 --- a/src/events/messageReactionRemove.ts +++ /dev/null @@ -1,18 +0,0 @@ -import Event from "../core/event"; -import {Permissions} from "discord.js"; -import {botHasPermission} from "../core/libd"; - -// 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<"messageReactionRemove">({ - on(reaction, user) { - const canDeleteEmotes = botHasPermission(reaction.message.guild, Permissions.FLAGS.MANAGE_MESSAGES); - - if (!canDeleteEmotes) { - const callback = eventListeners.get(reaction.message.id); - callback && callback(reaction.emoji.name, user.id); - } - } -}); diff --git a/src/events/ready.ts b/src/events/ready.ts deleted file mode 100644 index a4e1b9c..0000000 --- a/src/events/ready.ts +++ /dev/null @@ -1,17 +0,0 @@ -import Event from "../core/event"; -import {client} from "../index"; -import {Config} from "../core/structures"; -import {updateGlobalEmoteRegistry} from "../core/libd"; - -export default new Event<"ready">({ - once() { - if (client.user) { - console.ready(`Logged in as ${client.user.username}#${client.user.discriminator}.`); - client.user.setActivity({ - type: "LISTENING", - name: `${Config.prefix}help` - }); - } - updateGlobalEmoteRegistry(); - } -}); diff --git a/src/index.ts b/src/index.ts index 2ccf92c..9f709e0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,17 +1,20 @@ +// Bootstrapping Section // import "./globals"; import {Client} from "discord.js"; import setup from "./setup"; import {Config} from "./core/structures"; -import {loadEvents} from "./core/event"; -import {attachToClient} from "./modules/lavalink"; // This is here in order to make it much less of a headache to access the client from other files. // This of course won't actually do anything until the setup process is complete and it logs in. export const client = new Client(); -attachToClient(client); -// Command loading will start as soon as an instance of "core/command" is loaded, which is loaded during "events/message". +// Send the login request to Discord's API and then load modules while waiting for it. setup.init().then(() => { - loadEvents(client); client.login(Config.token).catch(setup.again); }); + +// Initialize Modules // +import "./core/handler"; // Command loading will start as soon as an instance of "core/command" is loaded, which is loaded in "core/handler". +import "./modules/lavalink"; +import "./modules/emoteRegistry"; +import "./modules/channelListener"; diff --git a/src/modules/channelListener.ts b/src/modules/channelListener.ts new file mode 100644 index 0000000..910d1ab --- /dev/null +++ b/src/modules/channelListener.ts @@ -0,0 +1,20 @@ +import {client} from "../index"; +import {GuildChannel} from "discord.js"; + +client.on("channelCreate", async (channel) => { + const botGuilds = client.guilds; + + if (channel instanceof GuildChannel) { + const createdGuild = await botGuilds.fetch(channel.guild.id); + console.log(`Channel created in '${createdGuild.name}' called '#${channel.name}'`); + } +}); + +client.on("channelDelete", async (channel) => { + const botGuilds = client.guilds; + + if (channel instanceof GuildChannel) { + const createdGuild = await botGuilds.fetch(channel.guild.id); + console.log(`Channel deleted in '${createdGuild.name}' called '#${channel.name}'`); + } +}); diff --git a/src/modules/emoteRegistry.ts b/src/modules/emoteRegistry.ts new file mode 100644 index 0000000..bcc7ed0 --- /dev/null +++ b/src/modules/emoteRegistry.ts @@ -0,0 +1,53 @@ +import {client} from "../index"; +import FileManager from "../core/storage"; +import {EmoteRegistryDump} from "../core/structures"; + +function updateGlobalEmoteRegistry(): void { + const data: EmoteRegistryDump = {version: 1, list: []}; + + for (const guild of client.guilds.cache.values()) { + for (const emote of guild.emojis.cache.values()) { + data.list.push({ + ref: emote.name, + id: emote.id, + name: emote.name, + requires_colons: emote.requiresColons || false, + animated: emote.animated, + url: emote.url, + guild_id: emote.guild.name, + guild_name: emote.guild.name + }); + } + } + + FileManager.write("emote-registry", data, true); +} + +client.on("emojiCreate", (emote) => { + console.log(`Updated emote registry. ${emote.name}`); + updateGlobalEmoteRegistry(); +}); + +client.on("emojiDelete", () => { + console.log("Updated emote registry."); + updateGlobalEmoteRegistry(); +}); + +client.on("emojiUpdate", () => { + console.log("Updated emote registry."); + updateGlobalEmoteRegistry(); +}); + +client.on("guildCreate", () => { + console.log("Updated emote registry."); + updateGlobalEmoteRegistry(); +}); + +client.on("guildDelete", () => { + console.log("Updated emote registry."); + updateGlobalEmoteRegistry(); +}); + +client.on("ready", () => { + updateGlobalEmoteRegistry(); +}); diff --git a/src/modules/lavalink.ts b/src/modules/lavalink.ts index 5e0ff90..39dc5c8 100644 --- a/src/modules/lavalink.ts +++ b/src/modules/lavalink.ts @@ -1,6 +1,7 @@ -import {Presence, Client} from "discord.js"; +import {Presence} from "discord.js"; import LavalinkMusic from "discord.js-lavalink-lib"; import {Config} from "../core/structures"; +import {client} from "../index"; declare module "discord.js" { interface Presence { @@ -28,26 +29,24 @@ Presence.prototype.patch = function patch(data: any) { // the function which handles those packets is NOP-ed out, which, among other // things, skips the code which caches the referenced users in the packet. See // . -export function attachToClient(client: Client) { - (client["actions"] as any)["PresenceUpdate"].handle = () => {}; +(client["actions"] as any)["PresenceUpdate"].handle = () => {}; - (client as any).music = LavalinkMusic(client, { - lavalink: { - restnode: { +(client as any).music = LavalinkMusic(client, { + lavalink: { + restnode: { + host: "localhost", + port: 2333, + password: "youshallnotpass" + }, + nodes: [ + { host: "localhost", port: 2333, password: "youshallnotpass" - }, - nodes: [ - { - host: "localhost", - port: 2333, - password: "youshallnotpass" - } - ] - }, - prefix: Config.prefix, - helpCmd: "mhelp", - admins: ["717352467280691331"] - }); -} + } + ] + }, + prefix: Config.prefix, + helpCmd: "mhelp", + admins: ["717352467280691331"] +}); From 974985586d133fe0a99ef7e6c8f3d9f327ec0573 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 30 Mar 2021 22:22:25 -0500 Subject: [PATCH 3/3] Rearranged command categories --- src/commands/fun/eco.ts | 8 ++++---- .../fun/{subcommands => modules}/eco-core.ts | 0 .../fun/{subcommands => modules}/eco-extras.ts | 0 .../fun/{subcommands => modules}/eco-shop-items.ts | 0 .../fun/{subcommands => modules}/eco-shop.ts | 0 .../fun/{subcommands => modules}/eco-utils.ts | 0 src/commands/fun/owoify.ts | 6 +++--- src/commands/{ => system}/admin.ts | 12 ++++++------ src/commands/{ => system}/help.ts | 8 ++++---- src/commands/{utilities => utility}/desc.ts | 0 src/commands/{utilities => utility}/emote.ts | 2 +- src/commands/{ => utility}/info.ts | 8 ++++---- src/commands/{utilities => utility}/lsemotes.ts | 0 .../subcommands => utility/modules}/emote-utils.ts | 0 src/commands/{utilities => utility}/react.ts | 2 +- src/commands/{utilities => utility}/say.ts | 0 src/commands/{ => utility}/scanemotes.ts | 4 ++-- src/commands/{utilities => utility}/shorten.ts | 0 src/commands/{utilities => utility}/time.ts | 0 src/core/permissions.ts | 2 +- 20 files changed, 26 insertions(+), 26 deletions(-) rename src/commands/fun/{subcommands => modules}/eco-core.ts (100%) rename src/commands/fun/{subcommands => modules}/eco-extras.ts (100%) rename src/commands/fun/{subcommands => modules}/eco-shop-items.ts (100%) rename src/commands/fun/{subcommands => modules}/eco-shop.ts (100%) rename src/commands/fun/{subcommands => modules}/eco-utils.ts (100%) rename src/commands/{ => system}/admin.ts (96%) rename src/commands/{ => system}/help.ts (96%) rename src/commands/{utilities => utility}/desc.ts (100%) rename src/commands/{utilities => utility}/emote.ts (89%) rename src/commands/{ => utility}/info.ts (98%) rename src/commands/{utilities => utility}/lsemotes.ts (100%) rename src/commands/{utilities/subcommands => utility/modules}/emote-utils.ts (100%) rename src/commands/{utilities => utility}/react.ts (98%) rename src/commands/{utilities => utility}/say.ts (100%) rename src/commands/{ => utility}/scanemotes.ts (99%) rename src/commands/{utilities => utility}/shorten.ts (100%) rename src/commands/{utilities => utility}/time.ts (100%) diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index 6df36ae..380cc33 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -1,8 +1,8 @@ import Command from "../../core/command"; -import {isAuthorized, getMoneyEmbed} from "./subcommands/eco-utils"; -import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./subcommands/eco-core"; -import {BuyCommand, ShopCommand} from "./subcommands/eco-shop"; -import {MondayCommand} from "./subcommands/eco-extras"; +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} from "./modules/eco-extras"; import {callMemberByUsername} from "../../core/libd"; export default new Command({ diff --git a/src/commands/fun/subcommands/eco-core.ts b/src/commands/fun/modules/eco-core.ts similarity index 100% rename from src/commands/fun/subcommands/eco-core.ts rename to src/commands/fun/modules/eco-core.ts diff --git a/src/commands/fun/subcommands/eco-extras.ts b/src/commands/fun/modules/eco-extras.ts similarity index 100% rename from src/commands/fun/subcommands/eco-extras.ts rename to src/commands/fun/modules/eco-extras.ts diff --git a/src/commands/fun/subcommands/eco-shop-items.ts b/src/commands/fun/modules/eco-shop-items.ts similarity index 100% rename from src/commands/fun/subcommands/eco-shop-items.ts rename to src/commands/fun/modules/eco-shop-items.ts diff --git a/src/commands/fun/subcommands/eco-shop.ts b/src/commands/fun/modules/eco-shop.ts similarity index 100% rename from src/commands/fun/subcommands/eco-shop.ts rename to src/commands/fun/modules/eco-shop.ts diff --git a/src/commands/fun/subcommands/eco-utils.ts b/src/commands/fun/modules/eco-utils.ts similarity index 100% rename from src/commands/fun/subcommands/eco-utils.ts rename to src/commands/fun/modules/eco-utils.ts diff --git a/src/commands/fun/owoify.ts b/src/commands/fun/owoify.ts index 789d74a..af041df 100644 --- a/src/commands/fun/owoify.ts +++ b/src/commands/fun/owoify.ts @@ -1,12 +1,12 @@ -/// @ts-nocheck import Command from "../../core/command"; -import {getContent} from "../../core/libd"; +import {getContent} from "../../core/lib"; +import {URL} from "url"; export default new Command({ description: "OwO-ifies the input.", async run($) { let url = new URL(`https://nekos.life/api/v2/owoify?text=${$.args.join(" ")}`); - const content = await getContent(url.toString()); + const content = (await getContent(url.toString())) as any; // Apparently, the object in question is {owo: string}. $.channel.send(content.owo); } }); diff --git a/src/commands/admin.ts b/src/commands/system/admin.ts similarity index 96% rename from src/commands/admin.ts rename to src/commands/system/admin.ts index 72c5686..15ab73d 100644 --- a/src/commands/admin.ts +++ b/src/commands/system/admin.ts @@ -1,10 +1,10 @@ -import Command, {handler} from "../core/command"; -import {clean} from "../core/lib"; -import {botHasPermission} from "../core/libd"; -import {Config, Storage} from "../core/structures"; -import {getPermissionLevel, getPermissionName} from "../core/permissions"; +import Command, {handler} from "../../core/command"; +import {clean} from "../../core/lib"; +import {botHasPermission} from "../../core/libd"; +import {Config, Storage} from "../../core/structures"; +import {getPermissionLevel, getPermissionName} from "../../core/permissions"; import {Permissions} from "discord.js"; -import {logs} from "../globals"; +import {logs} from "../../globals"; function getLogBuffer(type: string) { return { diff --git a/src/commands/help.ts b/src/commands/system/help.ts similarity index 96% rename from src/commands/help.ts rename to src/commands/system/help.ts index 71f2ae3..fdf1249 100644 --- a/src/commands/help.ts +++ b/src/commands/system/help.ts @@ -1,7 +1,7 @@ -import Command from "../core/command"; -import {toTitleCase} from "../core/lib"; -import {loadableCommands, categories} from "../core/command"; -import {getPermissionName} from "../core/permissions"; +import Command from "../../core/command"; +import {toTitleCase} from "../../core/lib"; +import {loadableCommands, categories} from "../../core/command"; +import {getPermissionName} from "../../core/permissions"; export default new Command({ description: "Lists all commands. If a command is specified, their arguments are listed as well.", diff --git a/src/commands/utilities/desc.ts b/src/commands/utility/desc.ts similarity index 100% rename from src/commands/utilities/desc.ts rename to src/commands/utility/desc.ts diff --git a/src/commands/utilities/emote.ts b/src/commands/utility/emote.ts similarity index 89% rename from src/commands/utilities/emote.ts rename to src/commands/utility/emote.ts index b84d9ef..addaec0 100644 --- a/src/commands/utilities/emote.ts +++ b/src/commands/utility/emote.ts @@ -1,5 +1,5 @@ import Command from "../../core/command"; -import {queryClosestEmoteByName} from "./subcommands/emote-utils"; +import {queryClosestEmoteByName} from "./modules/emote-utils"; import {botHasPermission} from "../../core/libd"; import {Permissions} from "discord.js"; diff --git a/src/commands/info.ts b/src/commands/utility/info.ts similarity index 98% rename from src/commands/info.ts rename to src/commands/utility/info.ts index b95f19a..e2e6025 100644 --- a/src/commands/info.ts +++ b/src/commands/utility/info.ts @@ -1,10 +1,10 @@ import {MessageEmbed, version as djsversion} from "discord.js"; import ms from "ms"; import os from "os"; -import Command from "../core/command"; -import {formatBytes, trimArray} from "../core/lib"; -import {getMemberByUsername} from "../core/libd"; -import {verificationLevels, filterLevels, regions} from "../defs/info"; +import Command from "../../core/command"; +import {formatBytes, trimArray} from "../../core/lib"; +import {getMemberByUsername} from "../../core/libd"; +import {verificationLevels, filterLevels, regions} from "../../defs/info"; import moment from "moment"; import utc from "moment"; import {Guild} from "discord.js"; diff --git a/src/commands/utilities/lsemotes.ts b/src/commands/utility/lsemotes.ts similarity index 100% rename from src/commands/utilities/lsemotes.ts rename to src/commands/utility/lsemotes.ts diff --git a/src/commands/utilities/subcommands/emote-utils.ts b/src/commands/utility/modules/emote-utils.ts similarity index 100% rename from src/commands/utilities/subcommands/emote-utils.ts rename to src/commands/utility/modules/emote-utils.ts diff --git a/src/commands/utilities/react.ts b/src/commands/utility/react.ts similarity index 98% rename from src/commands/utilities/react.ts rename to src/commands/utility/react.ts index cc7c031..152e98d 100644 --- a/src/commands/utilities/react.ts +++ b/src/commands/utility/react.ts @@ -1,6 +1,6 @@ import Command from "../../core/command"; import {Message, Channel, TextChannel} from "discord.js"; -import {queryClosestEmoteByName} from "./subcommands/emote-utils"; +import {queryClosestEmoteByName} from "./modules/emote-utils"; export default new Command({ description: diff --git a/src/commands/utilities/say.ts b/src/commands/utility/say.ts similarity index 100% rename from src/commands/utilities/say.ts rename to src/commands/utility/say.ts diff --git a/src/commands/scanemotes.ts b/src/commands/utility/scanemotes.ts similarity index 99% rename from src/commands/scanemotes.ts rename to src/commands/utility/scanemotes.ts index 9596f9f..b087793 100644 --- a/src/commands/scanemotes.ts +++ b/src/commands/utility/scanemotes.ts @@ -1,5 +1,5 @@ -import Command, {handler} from "../core/command"; -import {pluralise} from "../core/lib"; +import Command, {handler} from "../../core/command"; +import {pluralise} from "../../core/lib"; import moment from "moment"; import {Collection, TextChannel} from "discord.js"; diff --git a/src/commands/utilities/shorten.ts b/src/commands/utility/shorten.ts similarity index 100% rename from src/commands/utilities/shorten.ts rename to src/commands/utility/shorten.ts diff --git a/src/commands/utilities/time.ts b/src/commands/utility/time.ts similarity index 100% rename from src/commands/utilities/time.ts rename to src/commands/utility/time.ts diff --git a/src/core/permissions.ts b/src/core/permissions.ts index c4c89ba..37b0000 100644 --- a/src/core/permissions.ts +++ b/src/core/permissions.ts @@ -63,6 +63,6 @@ export function getPermissionLevel(member: GuildMember): number { } export function getPermissionName(level: number) { - if (level > length || length < 0) return "N/A"; + if (level > length || level < 0) return "N/A"; else return PermissionLevels[level].name; }