mirror of
				https://github.com/keanuplayz/TravBot-v3.git
				synced 2024-08-15 02:33:12 +00:00 
			
		
		
		
	Reworked event loading
This commit is contained in:
		
							parent
							
								
									3ef487c4a4
								
							
						
					
					
						commit
						945102b7cf
					
				
					 18 changed files with 272 additions and 344 deletions
				
			
		
							
								
								
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -1,7 +1,6 @@ | ||||||
| # Specific to this repository | # Specific to this repository | ||||||
| dist/ | dist/ | ||||||
| data/* | data/ | ||||||
| !data/endpoints.json |  | ||||||
| tmp/ | tmp/ | ||||||
| test* | test* | ||||||
| !test/ | !test/ | ||||||
|  |  | ||||||
|  | @ -1,38 +0,0 @@ | ||||||
| import {Client, ClientEvents, Constants} from "discord.js"; |  | ||||||
| import Storage from "./storage"; |  | ||||||
| 
 |  | ||||||
| interface EventOptions<K extends keyof ClientEvents> { |  | ||||||
|     readonly on?: (...args: ClientEvents[K]) => void; |  | ||||||
|     readonly once?: (...args: ClientEvents[K]) => void; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export default class Event<K extends keyof ClientEvents> { |  | ||||||
|     private readonly on?: (...args: ClientEvents[K]) => void; |  | ||||||
|     private readonly once?: (...args: ClientEvents[K]) => void; |  | ||||||
| 
 |  | ||||||
|     constructor(options: EventOptions<K>) { |  | ||||||
|         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.)` |  | ||||||
|             ); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										158
									
								
								src/core/handler.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								src/core/handler.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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` | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | }); | ||||||
|  | @ -9,36 +9,25 @@ import { | ||||||
|     NewsChannel, |     NewsChannel, | ||||||
|     MessageOptions |     MessageOptions | ||||||
| } from "discord.js"; | } from "discord.js"; | ||||||
| import FileManager from "./storage"; |  | ||||||
| import {eventListeners} from "../events/messageReactionRemove"; |  | ||||||
| import {client} from "../index"; | 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<string, (emote: string, id: string) => 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 { | export function botHasPermission(guild: Guild | null, permission: number): boolean { | ||||||
|     return !!guild?.me?.hasPermission(permission); |     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.
 | // 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.
 | // Pagination function that allows for customization via a callback.
 | ||||||
|  |  | ||||||
|  | @ -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}'`); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  | @ -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}'`); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  | @ -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(); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  | @ -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(); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  | @ -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(); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  | @ -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(); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  | @ -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(); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  | @ -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 |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  | @ -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<string, (emote: string, id: string) => 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); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  | @ -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(); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
							
								
								
									
										13
									
								
								src/index.ts
									
										
									
									
									
								
							
							
						
						
									
										13
									
								
								src/index.ts
									
										
									
									
									
								
							|  | @ -1,17 +1,20 @@ | ||||||
|  | // Bootstrapping Section //
 | ||||||
| import "./globals"; | import "./globals"; | ||||||
| import {Client} from "discord.js"; | import {Client} from "discord.js"; | ||||||
| import setup from "./setup"; | import setup from "./setup"; | ||||||
| import {Config} from "./core/structures"; | 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 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.
 | // This of course won't actually do anything until the setup process is complete and it logs in.
 | ||||||
| export const client = new Client(); | 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(() => { | setup.init().then(() => { | ||||||
|     loadEvents(client); |  | ||||||
|     client.login(Config.token).catch(setup.again); |     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"; | ||||||
|  |  | ||||||
							
								
								
									
										20
									
								
								src/modules/channelListener.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/modules/channelListener.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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}'`); | ||||||
|  |     } | ||||||
|  | }); | ||||||
							
								
								
									
										53
									
								
								src/modules/emoteRegistry.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/modules/emoteRegistry.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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(); | ||||||
|  | }); | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import {Presence, Client} from "discord.js"; | import {Presence} from "discord.js"; | ||||||
| import LavalinkMusic from "discord.js-lavalink-lib"; | import LavalinkMusic from "discord.js-lavalink-lib"; | ||||||
| import {Config} from "../core/structures"; | import {Config} from "../core/structures"; | ||||||
|  | import {client} from "../index"; | ||||||
| 
 | 
 | ||||||
| declare module "discord.js" { | declare module "discord.js" { | ||||||
|     interface Presence { |     interface Presence { | ||||||
|  | @ -28,10 +29,9 @@ Presence.prototype.patch = function patch(data: any) { | ||||||
| // the function which handles those packets is NOP-ed out, which, among other
 | // 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
 | // things, skips the code which caches the referenced users in the packet. See
 | ||||||
| // <https://github.com/discordjs/discord.js/blob/cee6cf70ce76e9b06dc7f25bfd77498e18d7c8d4/src/client/actions/PresenceUpdate.js#L7-L41>.
 | // <https://github.com/discordjs/discord.js/blob/cee6cf70ce76e9b06dc7f25bfd77498e18d7c8d4/src/client/actions/PresenceUpdate.js#L7-L41>.
 | ||||||
| export function attachToClient(client: Client) { | (client["actions"] as any)["PresenceUpdate"].handle = () => {}; | ||||||
|     (client["actions"] as any)["PresenceUpdate"].handle = () => {}; |  | ||||||
| 
 | 
 | ||||||
|     (client as any).music = LavalinkMusic(client, { | (client as any).music = LavalinkMusic(client, { | ||||||
|     lavalink: { |     lavalink: { | ||||||
|         restnode: { |         restnode: { | ||||||
|             host: "localhost", |             host: "localhost", | ||||||
|  | @ -49,5 +49,4 @@ export function attachToClient(client: Client) { | ||||||
|     prefix: Config.prefix, |     prefix: Config.prefix, | ||||||
|     helpCmd: "mhelp", |     helpCmd: "mhelp", | ||||||
|     admins: ["717352467280691331"] |     admins: ["717352467280691331"] | ||||||
|     }); | }); | ||||||
| } |  | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue