mirror of
				https://github.com/keanuplayz/TravBot-v3.git
				synced 2024-08-15 02:33:12 +00:00 
			
		
		
		
	Updated library functions
This commit is contained in:
		
							parent
							
								
									3798c27df9
								
							
						
					
					
						commit
						c980a182f8
					
				
					 14 changed files with 479 additions and 542 deletions
				
			
		|  | @ -75,27 +75,24 @@ Because versions are assigned to batches of changes rather than single changes ( | |||
| ```ts | ||||
| const pages = ["one", "two", "three"]; | ||||
| 
 | ||||
| paginate(channel, author.id, pages.length, (page) => { | ||||
| 	return { | ||||
| 		content: pages[page] | ||||
| 	}; | ||||
| }); | ||||
| paginate(send, page => { | ||||
| 	return {content: pages[page]}; | ||||
| }, pages.length, author.id); | ||||
| ``` | ||||
| 
 | ||||
| `prompt()` | ||||
| `confirm()` | ||||
| ```ts | ||||
| const msg = await channel.send('Are you sure you want to delete this?'); | ||||
| 
 | ||||
| prompt(msg, author.id, () => { | ||||
| 	//... | ||||
| }); | ||||
| const result = await confirm(await send("Are you sure you want to delete this?"), author.id); // boolean | null | ||||
| ``` | ||||
| 
 | ||||
| `callMemberByUsername()` | ||||
| `askMultipleChoice()` | ||||
| ```ts | ||||
| callMemberByUsername(message, args.join(" "), (member) => { | ||||
| 	channel.send(`Your nickname is ${member.nickname}.`); | ||||
| }); | ||||
| const result = await askMultipleChoice(await send("Which of the following numbers is your favorite?"), author.id, 4, 10000); // number (0 to 3) | null | ||||
| ``` | ||||
| 
 | ||||
| `askForReply()` | ||||
| ```ts | ||||
| const reply = await askForReply(await send("What is your favorite thing to do?"), author.id, 10000); // Message | null | ||||
| ``` | ||||
| 
 | ||||
| ## [src/lib](../src/lib.ts) - General utility functions | ||||
|  |  | |||
|  | @ -4,7 +4,6 @@ import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./modu | |||
| import {BuyCommand, ShopCommand} from "./modules/eco-shop"; | ||||
| import {MondayCommand, AwardCommand} from "./modules/eco-extras"; | ||||
| import {BetCommand} from "./modules/eco-bet"; | ||||
| import {GuildMember} from "discord.js"; | ||||
| 
 | ||||
| export default new NamedCommand({ | ||||
|     description: "Economy command for Monika.", | ||||
|  | @ -38,7 +37,7 @@ export default new NamedCommand({ | |||
|         async run({send, guild, channel, args, message, combined}) { | ||||
|             if (isAuthorized(guild, channel)) { | ||||
|                 const member = await getMemberByName(guild!, combined); | ||||
|                 if (member instanceof GuildMember) send(getMoneyEmbed(member.user)); | ||||
|                 if (typeof member !== "string") send(getMoneyEmbed(member.user)); | ||||
|                 else send(member); | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import {Command, NamedCommand, askYesOrNo} from "../../../core"; | ||||
| import {Command, NamedCommand, confirm} from "../../../core"; | ||||
| import {pluralise} from "../../../lib"; | ||||
| import {Storage} from "../../../structures"; | ||||
| import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco-utils"; | ||||
| import {isAuthorized, getMoneyEmbed} from "./eco-utils"; | ||||
| import {User} from "discord.js"; | ||||
| 
 | ||||
| export const BetCommand = new NamedCommand({ | ||||
|  | @ -79,14 +79,15 @@ export const BetCommand = new NamedCommand({ | |||
|                             return send(`Bet duration is too long, maximum duration is ${durationBounds.max}`); | ||||
| 
 | ||||
|                         // Ask target whether or not they want to take the bet.
 | ||||
|                         const takeBet = await askYesOrNo( | ||||
|                         const takeBet = await confirm( | ||||
|                             await send( | ||||
|                                 `<@${target.id}>, do you want to take this bet of ${pluralise(amount, "Mon", "s")}` | ||||
|                             ), | ||||
|                             target.id | ||||
|                         ); | ||||
| 
 | ||||
|                         if (takeBet) { | ||||
|                         if (!takeBet) return send(`<@${target.id}> has rejected your bet, <@${author.id}>`); | ||||
| 
 | ||||
|                         // [MEDIUM PRIORITY: bet persistence to prevent losses in case of shutdown.]
 | ||||
|                         // Remove amount money from both parts at the start to avoid duplication of money.
 | ||||
|                         sender.money -= amount; | ||||
|  | @ -97,7 +98,7 @@ export const BetCommand = new NamedCommand({ | |||
|                         Storage.save(); | ||||
| 
 | ||||
|                         // Notify both users.
 | ||||
|                             await send( | ||||
|                         send( | ||||
|                             `<@${target.id}> has taken <@${author.id}>'s bet, the bet amount of ${pluralise( | ||||
|                                 amount, | ||||
|                                 "Mon", | ||||
|  | @ -155,12 +156,12 @@ export const BetCommand = new NamedCommand({ | |||
|                                             `By the people's votes, <@${target.id}> couldn't be determined to have won or lost the bet that <@${author.id}> had sent them.` | ||||
|                                         ); | ||||
|                                     } | ||||
| 
 | ||||
|                                     sender.ecoBetInsurance -= amount; | ||||
|                                     receiver.ecoBetInsurance -= amount; | ||||
|                                     Storage.save(); | ||||
|                                 }); | ||||
|                         }, duration); | ||||
|                         } else return await send(`<@${target.id}> has rejected your bet, <@${author.id}>`); | ||||
|                     } else return; | ||||
|                 } | ||||
|             }) | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import {GuildMember} from "discord.js"; | ||||
| import {Command, getMemberByName, NamedCommand, prompt, RestCommand} from "../../../core"; | ||||
| import {Command, getMemberByName, NamedCommand, confirm, RestCommand} from "../../../core"; | ||||
| import {pluralise} from "../../../lib"; | ||||
| import {Storage} from "../../../structures"; | ||||
| import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco-utils"; | ||||
|  | @ -90,7 +89,7 @@ export const LeaderboardCommand = new NamedCommand({ | |||
|                 const user = await client.users.fetch(id); | ||||
| 
 | ||||
|                 fields.push({ | ||||
|                     name: `#${i + 1}. ${user.username}#${user.discriminator}`, | ||||
|                     name: `#${i + 1}. ${user.tag}`, | ||||
|                     value: pluralise(users[id].money, "Mon", "s") | ||||
|                 }); | ||||
|             } | ||||
|  | @ -158,42 +157,38 @@ export const PayCommand = new NamedCommand({ | |||
|                     return send("You have to use this in a server if you want to send Mons with a username!"); | ||||
| 
 | ||||
|                 const member = await getMemberByName(guild, combined); | ||||
|                 if (!(member instanceof GuildMember)) return send(member); | ||||
|                 if (typeof member === "string") return send(member); | ||||
|                 else if (member.user.id === author.id) return send("You can't send Mons to yourself!"); | ||||
|                 else if (member.user.bot && process.argv[2] !== "dev") return send("You can't send Mons to a bot!"); | ||||
| 
 | ||||
|                 const target = member.user; | ||||
| 
 | ||||
|                 return prompt( | ||||
|                     await send( | ||||
|                         `Are you sure you want to send ${pluralise( | ||||
|                             amount, | ||||
|                             "Mon", | ||||
|                             "s" | ||||
|                         )} to this person?\n*(This message will automatically be deleted after 10 seconds.)*`,
 | ||||
|                         { | ||||
|                 const result = await confirm( | ||||
|                     await send(`Are you sure you want to send ${pluralise(amount, "Mon", "s")} to this person?`, { | ||||
|                         embed: { | ||||
|                             color: ECO_EMBED_COLOR, | ||||
|                             author: { | ||||
|                                     name: `${target.username}#${target.discriminator}`, | ||||
|                                 name: target.tag, | ||||
|                                 icon_url: target.displayAvatarURL({ | ||||
|                                     format: "png", | ||||
|                                     dynamic: true | ||||
|                                 }) | ||||
|                             } | ||||
|                         } | ||||
|                         } | ||||
|                     ), | ||||
|                     author.id, | ||||
|                     () => { | ||||
|                     }), | ||||
|                     author.id | ||||
|                 ); | ||||
| 
 | ||||
|                 if (result) { | ||||
|                     const receiver = Storage.getUser(target.id); | ||||
|                     sender.money -= amount; | ||||
|                     receiver.money += amount; | ||||
|                     Storage.save(); | ||||
|                     send(getSendEmbed(author, target, amount)); | ||||
|                 } | ||||
|                 ); | ||||
|             } | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|     }) | ||||
| }); | ||||
|  |  | |||
|  | @ -34,12 +34,17 @@ export const ShopCommand = new NamedCommand({ | |||
|             const shopPages = split(ShopItems, 5); | ||||
|             const pageAmount = shopPages.length; | ||||
| 
 | ||||
|             paginate(send, author.id, pageAmount, (page, hasMultiplePages) => { | ||||
|             paginate( | ||||
|                 send, | ||||
|                 (page, hasMultiplePages) => { | ||||
|                     return getShopEmbed( | ||||
|                         shopPages[page], | ||||
|                         hasMultiplePages ? `Shop (Page ${page + 1} of ${pageAmount})` : "Shop" | ||||
|                     ); | ||||
|             }); | ||||
|                 }, | ||||
|                 pageAmount, | ||||
|                 author.id | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|  |  | |||
|  | @ -42,11 +42,11 @@ export function getSendEmbed(sender: User, receiver: User, amount: number): obje | |||
|             description: `${sender.toString()} has sent ${pluralise(amount, "Mon", "s")} to ${receiver.toString()}!`, | ||||
|             fields: [ | ||||
|                 { | ||||
|                     name: `Sender: ${sender.username}#${sender.discriminator}`, | ||||
|                     name: `Sender: ${sender.tag}`, | ||||
|                     value: pluralise(Storage.getUser(sender.id).money, "Mon", "s") | ||||
|                 }, | ||||
|                 { | ||||
|                     name: `Receiver: ${receiver.username}#${receiver.discriminator}`, | ||||
|                     name: `Receiver: ${receiver.tag}`, | ||||
|                     value: pluralise(Storage.getUser(receiver.id).money, "Mon", "s") | ||||
|                 } | ||||
|             ], | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import {User, GuildMember} from "discord.js"; | ||||
| import {User} from "discord.js"; | ||||
| import {Command, NamedCommand, getMemberByName, CHANNEL_TYPE, RestCommand} from "../../core"; | ||||
| 
 | ||||
| // Quotes must be used here or the numbers will change
 | ||||
|  | @ -70,7 +70,7 @@ export default new NamedCommand({ | |||
|         async run({send, message, channel, guild, author, client, args, combined}) { | ||||
|             const member = await getMemberByName(guild!, combined); | ||||
| 
 | ||||
|             if (member instanceof GuildMember) { | ||||
|             if (typeof member !== "string") { | ||||
|                 if (member.id in registry) { | ||||
|                     send(`\`${member.nickname ?? member.user.username}\` - ${registry[member.id]}`); | ||||
|                 } else { | ||||
|  |  | |||
|  | @ -20,7 +20,9 @@ export default new NamedCommand({ | |||
|         const commands = await getCommandList(); | ||||
|         const categoryArray = commands.keyArray(); | ||||
| 
 | ||||
|         paginate(send, author.id, categoryArray.length, (page, hasMultiplePages) => { | ||||
|         paginate( | ||||
|             send, | ||||
|             (page, hasMultiplePages) => { | ||||
|                 const category = categoryArray[page]; | ||||
|                 const commandList = commands.get(category)!; | ||||
|                 let output = `Legend: \`<type>\`, \`[list/of/stuff]\`, \`(optional)\`, \`(<optional type>)\`, \`([optional/list/...])\`\n`; | ||||
|  | @ -29,7 +31,10 @@ export default new NamedCommand({ | |||
|                     .setTitle(hasMultiplePages ? `${category} (Page ${page + 1} of ${categoryArray.length})` : category) | ||||
|                     .setDescription(output) | ||||
|                     .setColor(EMBED_COLOR); | ||||
|         }); | ||||
|             }, | ||||
|             categoryArray.length, | ||||
|             author.id | ||||
|         ); | ||||
|     }, | ||||
|     any: new Command({ | ||||
|         async run({send, message, channel, guild, author, member, client, args}) { | ||||
|  |  | |||
|  | @ -36,7 +36,7 @@ export default new NamedCommand({ | |||
|                 async run({send, message, channel, guild, author, client, args, combined}) { | ||||
|                     const member = await getMemberByName(guild!, combined); | ||||
| 
 | ||||
|                     if (member instanceof GuildMember) { | ||||
|                     if (typeof member !== "string") { | ||||
|                         send( | ||||
|                             member.user.displayAvatarURL({ | ||||
|                                 dynamic: true, | ||||
|  | @ -110,7 +110,7 @@ export default new NamedCommand({ | |||
|                 async run({send, message, channel, guild, author, member, client, args, combined}) { | ||||
|                     const targetGuild = getGuildByName(combined); | ||||
| 
 | ||||
|                     if (targetGuild instanceof Guild) { | ||||
|                     if (typeof targetGuild !== "string") { | ||||
|                         send(await getGuildInfo(targetGuild, guild)); | ||||
|                     } else { | ||||
|                         send(targetGuild); | ||||
|  |  | |||
|  | @ -90,7 +90,9 @@ async function displayEmoteList(emotes: GuildEmoji[], send: SendFunction, author | |||
| 
 | ||||
|     // Gather the first page (if it even exists, which it might not if there no valid emotes appear)
 | ||||
|     if (pages > 0) { | ||||
|         paginate(send, author.id, pages, (page, hasMultiplePages) => { | ||||
|         paginate( | ||||
|             send, | ||||
|             (page, hasMultiplePages) => { | ||||
|                 embed.setTitle(hasMultiplePages ? `**Emotes** (Page ${page + 1} of ${pages})` : "**Emotes**"); | ||||
| 
 | ||||
|                 let desc = ""; | ||||
|  | @ -100,7 +102,10 @@ async function displayEmoteList(emotes: GuildEmoji[], send: SendFunction, author | |||
|                 embed.setDescription(desc); | ||||
| 
 | ||||
|                 return embed; | ||||
|         }); | ||||
|             }, | ||||
|             pages, | ||||
|             author.id | ||||
|         ); | ||||
|     } else { | ||||
|         send("No valid emotes found by that query."); | ||||
|     } | ||||
|  |  | |||
|  | @ -1,15 +1,6 @@ | |||
| import { | ||||
|     Command, | ||||
|     NamedCommand, | ||||
|     ask, | ||||
|     askYesOrNo, | ||||
|     askMultipleChoice, | ||||
|     prompt, | ||||
|     getMemberByName, | ||||
|     RestCommand | ||||
| } from "../../core"; | ||||
| import {Command, NamedCommand, askForReply, confirm, askMultipleChoice, getMemberByName, RestCommand} from "../../core"; | ||||
| import {Storage} from "../../structures"; | ||||
| import {User, GuildMember} from "discord.js"; | ||||
| import {User} from "discord.js"; | ||||
| import moment from "moment"; | ||||
| 
 | ||||
| const DATE_FORMAT = "D MMMM YYYY"; | ||||
|  | @ -178,34 +169,31 @@ function getTimeEmbed(user: User) { | |||
| export default new NamedCommand({ | ||||
|     description: "Show others what time it is for you.", | ||||
|     aliases: ["tz"], | ||||
|     async run({send, channel, author}) { | ||||
|     async run({send, author}) { | ||||
|         send(getTimeEmbed(author)); | ||||
|     }, | ||||
|     subcommands: { | ||||
|         // Welcome to callback hell. We hope you enjoy your stay here!
 | ||||
|         setup: new NamedCommand({ | ||||
|             description: "Registers your timezone information for the bot.", | ||||
|             async run({send, author, channel}) { | ||||
|             async run({send, author}) { | ||||
|                 const profile = Storage.getUser(author.id); | ||||
|                 profile.timezone = null; | ||||
|                 profile.daylightSavingsRegion = null; | ||||
|                 let hour: number; | ||||
| 
 | ||||
|                 ask( | ||||
|                 // Parse and validate reply
 | ||||
|                 const reply = await askForReply( | ||||
|                     await send( | ||||
|                         "What hour (0 to 23) is it for you right now?\n*(Note: Make sure to use Discord's inline reply feature or this won't work!)*" | ||||
|                     ), | ||||
|                     author.id, | ||||
|                     (reply) => { | ||||
|                         hour = parseInt(reply); | ||||
|                     30000 | ||||
|                 ); | ||||
|                 if (reply === null) return send("Message timed out."); | ||||
|                 const hour = parseInt(reply.content); | ||||
|                 const isValidHour = !isNaN(hour) && hour >= 0 && hour <= 23; | ||||
|                 if (!isValidHour) return reply.reply("you need to enter in a valid integer between 0 to 23"); | ||||
| 
 | ||||
|                         if (isNaN(hour)) { | ||||
|                             return false; | ||||
|                         } | ||||
| 
 | ||||
|                         return hour >= 0 && hour <= 23; | ||||
|                     }, | ||||
|                     async () => { | ||||
|                 // You need to also take into account whether or not it's the same day in UTC or not.
 | ||||
|                 // The problem this setup avoids is messing up timezones by 24 hours.
 | ||||
|                 // For example, the old logic was just (hour - hourUTC). When I setup my timezone (UTC-6) at 18:00, it was UTC 00:00.
 | ||||
|  | @ -265,10 +253,8 @@ export default new NamedCommand({ | |||
| 
 | ||||
|                 // I calculate the list beforehand and check for duplicates to reduce unnecessary asking.
 | ||||
|                 if (duplicates.includes(hour)) { | ||||
|                             const isSameDay = await askYesOrNo( | ||||
|                                 await send( | ||||
|                                     `Is the current day of the month the ${moment().utc().format("Do")} for you?` | ||||
|                                 ), | ||||
|                     const isSameDay = await confirm( | ||||
|                         await send(`Is the current day of the month the ${moment().utc().format("Do")} for you?`), | ||||
|                         author.id | ||||
|                     ); | ||||
| 
 | ||||
|  | @ -297,7 +283,7 @@ export default new NamedCommand({ | |||
|                 } | ||||
| 
 | ||||
|                 // I should note that error handling should be added sometime because await throws an exception on Promise.reject.
 | ||||
|                         const hasDST = await askYesOrNo( | ||||
|                 const hasDST = await confirm( | ||||
|                     await send("Does your timezone change based on daylight savings?"), | ||||
|                     author.id | ||||
|                 ); | ||||
|  | @ -322,39 +308,45 @@ export default new NamedCommand({ | |||
|                         finalize(); | ||||
|                     }; | ||||
| 
 | ||||
|                             askMultipleChoice(await send(DST_NOTE_SETUP), author.id, [ | ||||
|                                 () => finalizeDST("na"), | ||||
|                                 () => finalizeDST("eu"), | ||||
|                                 () => finalizeDST("sh") | ||||
|                             ]); | ||||
|                     const index = await askMultipleChoice(await send(DST_NOTE_SETUP), author.id, 3); | ||||
| 
 | ||||
|                     switch (index) { | ||||
|                         case 0: | ||||
|                             finalizeDST("na"); | ||||
|                             break; | ||||
|                         case 1: | ||||
|                             finalizeDST("eu"); | ||||
|                             break; | ||||
|                         case 2: | ||||
|                             finalizeDST("sh"); | ||||
|                             break; | ||||
|                     } | ||||
|                 } else { | ||||
|                     finalize(); | ||||
|                 } | ||||
|                     }, | ||||
|                     () => "you need to enter in a valid integer between 0 to 23" | ||||
|                 ); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|         }), | ||||
|         delete: new NamedCommand({ | ||||
|             description: "Delete your timezone information.", | ||||
|             async run({send, channel, author}) { | ||||
|                 prompt( | ||||
|                     await send( | ||||
|                         "Are you sure you want to delete your timezone information?\n*(This message will automatically be deleted after 10 seconds.)*" | ||||
|                     ), | ||||
|                     author.id, | ||||
|                     () => { | ||||
|             async run({send, author}) { | ||||
|                 const result = await confirm( | ||||
|                     await send("Are you sure you want to delete your timezone information?"), | ||||
|                     author.id | ||||
|                 ); | ||||
| 
 | ||||
|                 if (result) { | ||||
|                     const profile = Storage.getUser(author.id); | ||||
|                     profile.timezone = null; | ||||
|                     profile.daylightSavingsRegion = null; | ||||
|                     Storage.save(); | ||||
|                 } | ||||
|                 ); | ||||
|             } | ||||
|         }), | ||||
|         utc: new NamedCommand({ | ||||
|             description: "Displays UTC time.", | ||||
|             async run({send, channel}) { | ||||
|             async run({send}) { | ||||
|                 const time = moment().utc(); | ||||
| 
 | ||||
|                 send({ | ||||
|  | @ -386,15 +378,15 @@ export default new NamedCommand({ | |||
|     id: "user", | ||||
|     user: new Command({ | ||||
|         description: "See what time it is for someone else.", | ||||
|         async run({send, channel, args}) { | ||||
|         async run({send, args}) { | ||||
|             send(getTimeEmbed(args[0])); | ||||
|         } | ||||
|     }), | ||||
|     any: new RestCommand({ | ||||
|         description: "See what time it is for someone else (by their username).", | ||||
|         async run({send, channel, args, guild, combined}) { | ||||
|         async run({send, guild, combined}) { | ||||
|             const member = await getMemberByName(guild!, combined); | ||||
|             if (member instanceof GuildMember) send(getTimeEmbed(member.user)); | ||||
|             if (typeof member !== "string") send(getTimeEmbed(member.user)); | ||||
|             else send(member); | ||||
|         } | ||||
|     }) | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ import { | |||
|     GuildChannel, | ||||
|     Channel | ||||
| } from "discord.js"; | ||||
| import {getChannelByID, getGuildByID, getMessageByID, getUserByID, SingleMessageOptions, SendFunction} from "./libd"; | ||||
| import {getChannelByID, getGuildByID, getMessageByID, getUserByID, SendFunction} from "./libd"; | ||||
| import {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; | ||||
| import {getPrefix} from "./interface"; | ||||
| import {parseVars, requireAllCasesHandledFor} from "../lib"; | ||||
|  | @ -244,18 +244,14 @@ export class Command extends BaseCommand { | |||
|     } | ||||
| 
 | ||||
|     // Go through the arguments provided and find the right subcommand, then execute with the given arguments.
 | ||||
|     // Will return null if it successfully executes, SingleMessageOptions if there's an error (to let the user know what it is).
 | ||||
|     // Will return null if it successfully executes, string if there's an error (to let the user know what it is).
 | ||||
|     //
 | ||||
|     // Calls the resulting subcommand's execute method in order to make more modular code, basically pushing the chain of execution to the subcommand.
 | ||||
|     // For example, a numeric subcommand would accept args of [4] then execute on it.
 | ||||
|     //
 | ||||
|     // Because each Command instance is isolated from others, it becomes practically impossible to predict the total amount of subcommands when isolating the code to handle each individual layer of recursion.
 | ||||
|     // Therefore, if a Command is declared as a rest type, any typed args that come at the end must be handled manually.
 | ||||
|     public async execute( | ||||
|         args: string[], | ||||
|         menu: CommandMenu, | ||||
|         metadata: ExecuteCommandMetadata | ||||
|     ): Promise<SingleMessageOptions | null> { | ||||
|     public async execute(args: string[], menu: CommandMenu, metadata: ExecuteCommandMetadata): Promise<string | null> { | ||||
|         // Update inherited properties if the current command specifies a property.
 | ||||
|         // In case there are no initial arguments, these should go first so that it can register.
 | ||||
|         if (this.permission !== -1) metadata.permission = this.permission; | ||||
|  | @ -292,9 +288,7 @@ export class Command extends BaseCommand { | |||
|                     const errorMessage = error.stack ?? error; | ||||
|                     console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`); | ||||
| 
 | ||||
|                     return { | ||||
|                         content: `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\`` | ||||
|                     }; | ||||
|                     return `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\``; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|  | @ -313,15 +307,13 @@ export class Command extends BaseCommand { | |||
|             const id = patterns.channel.exec(param)![1]; | ||||
|             const channel = await getChannelByID(id); | ||||
| 
 | ||||
|             if (channel instanceof Channel) { | ||||
|             if (typeof channel !== "string") { | ||||
|                 if (channel instanceof TextChannel || channel instanceof DMChannel) { | ||||
|                     metadata.symbolicArgs.push("<channel>"); | ||||
|                     menu.args.push(channel); | ||||
|                     return this.channel.execute(args, menu, metadata); | ||||
|                 } else { | ||||
|                     return { | ||||
|                         content: `\`${id}\` is not a valid text channel!` | ||||
|                     }; | ||||
|                     return `\`${id}\` is not a valid text channel!`; | ||||
|                 } | ||||
|             } else { | ||||
|                 return channel; | ||||
|  | @ -330,9 +322,7 @@ export class Command extends BaseCommand { | |||
|             const id = patterns.role.exec(param)![1]; | ||||
| 
 | ||||
|             if (!menu.guild) { | ||||
|                 return { | ||||
|                     content: "You can't use role parameters in DM channels!" | ||||
|                 }; | ||||
|                 return "You can't use role parameters in DM channels!"; | ||||
|             } | ||||
| 
 | ||||
|             const role = menu.guild.roles.cache.get(id); | ||||
|  | @ -342,9 +332,7 @@ export class Command extends BaseCommand { | |||
|                 menu.args.push(role); | ||||
|                 return this.role.execute(args, menu, metadata); | ||||
|             } else { | ||||
|                 return { | ||||
|                     content: `\`${id}\` is not a valid role in this server!` | ||||
|                 }; | ||||
|                 return `\`${id}\` is not a valid role in this server!`; | ||||
|             } | ||||
|         } else if (this.emote && patterns.emote.test(param)) { | ||||
|             const id = patterns.emote.exec(param)![1]; | ||||
|  | @ -355,9 +343,7 @@ export class Command extends BaseCommand { | |||
|                 menu.args.push(emote); | ||||
|                 return this.emote.execute(args, menu, metadata); | ||||
|             } else { | ||||
|                 return { | ||||
|                     content: `\`${id}\` isn't a valid emote!` | ||||
|                 }; | ||||
|                 return `\`${id}\` isn't a valid emote!`; | ||||
|             } | ||||
|         } else if (this.message && (isMessageLink || isMessagePair)) { | ||||
|             let channelID = ""; | ||||
|  | @ -375,7 +361,7 @@ export class Command extends BaseCommand { | |||
| 
 | ||||
|             const message = await getMessageByID(channelID, messageID); | ||||
| 
 | ||||
|             if (message instanceof Message) { | ||||
|             if (typeof message !== "string") { | ||||
|                 metadata.symbolicArgs.push("<message>"); | ||||
|                 menu.args.push(message); | ||||
|                 return this.message.execute(args, menu, metadata); | ||||
|  | @ -386,7 +372,7 @@ export class Command extends BaseCommand { | |||
|             const id = patterns.user.exec(param)![1]; | ||||
|             const user = await getUserByID(id); | ||||
| 
 | ||||
|             if (user instanceof User) { | ||||
|             if (typeof user !== "string") { | ||||
|                 metadata.symbolicArgs.push("<user>"); | ||||
|                 menu.args.push(user); | ||||
|                 return this.user.execute(args, menu, metadata); | ||||
|  | @ -403,24 +389,20 @@ export class Command extends BaseCommand { | |||
|                 case "channel": | ||||
|                     const channel = await getChannelByID(id); | ||||
| 
 | ||||
|                     if (channel instanceof Channel) { | ||||
|                     if (typeof channel !== "string") { | ||||
|                         if (channel instanceof TextChannel || channel instanceof DMChannel) { | ||||
|                             metadata.symbolicArgs.push("<channel>"); | ||||
|                             menu.args.push(channel); | ||||
|                             return this.id.execute(args, menu, metadata); | ||||
|                         } else { | ||||
|                             return { | ||||
|                                 content: `\`${id}\` is not a valid text channel!` | ||||
|                             }; | ||||
|                             return `\`${id}\` is not a valid text channel!`; | ||||
|                         } | ||||
|                     } else { | ||||
|                         return channel; | ||||
|                     } | ||||
|                 case "role": | ||||
|                     if (!menu.guild) { | ||||
|                         return { | ||||
|                             content: "You can't use role parameters in DM channels!" | ||||
|                         }; | ||||
|                         return "You can't use role parameters in DM channels!"; | ||||
|                     } | ||||
| 
 | ||||
|                     const role = menu.guild.roles.cache.get(id); | ||||
|  | @ -429,9 +411,7 @@ export class Command extends BaseCommand { | |||
|                         menu.args.push(role); | ||||
|                         return this.id.execute(args, menu, metadata); | ||||
|                     } else { | ||||
|                         return { | ||||
|                             content: `\`${id}\` isn't a valid role in this server!` | ||||
|                         }; | ||||
|                         return `\`${id}\` isn't a valid role in this server!`; | ||||
|                     } | ||||
|                 case "emote": | ||||
|                     const emote = menu.client.emojis.cache.get(id); | ||||
|  | @ -440,14 +420,12 @@ export class Command extends BaseCommand { | |||
|                         menu.args.push(emote); | ||||
|                         return this.id.execute(args, menu, metadata); | ||||
|                     } else { | ||||
|                         return { | ||||
|                             content: `\`${id}\` isn't a valid emote!` | ||||
|                         }; | ||||
|                         return `\`${id}\` isn't a valid emote!`; | ||||
|                     } | ||||
|                 case "message": | ||||
|                     const message = await getMessageByID(menu.channel, id); | ||||
| 
 | ||||
|                     if (message instanceof Message) { | ||||
|                     if (typeof message !== "string") { | ||||
|                         menu.args.push(message); | ||||
|                         return this.id.execute(args, menu, metadata); | ||||
|                     } else { | ||||
|  | @ -456,7 +434,7 @@ export class Command extends BaseCommand { | |||
|                 case "user": | ||||
|                     const user = await getUserByID(id); | ||||
| 
 | ||||
|                     if (user instanceof User) { | ||||
|                     if (typeof user !== "string") { | ||||
|                         menu.args.push(user); | ||||
|                         return this.id.execute(args, menu, metadata); | ||||
|                     } else { | ||||
|  | @ -465,7 +443,7 @@ export class Command extends BaseCommand { | |||
|                 case "guild": | ||||
|                     const guild = getGuildByID(id); | ||||
| 
 | ||||
|                     if (guild instanceof Guild) { | ||||
|                     if (typeof guild !== "string") { | ||||
|                         menu.args.push(guild); | ||||
|                         return this.id.execute(args, menu, metadata); | ||||
|                     } else { | ||||
|  | @ -489,11 +467,9 @@ export class Command extends BaseCommand { | |||
|             return this.any.execute(args.join(" "), menu, metadata); | ||||
|         } else { | ||||
|             metadata.symbolicArgs.push(`"${param}"`); | ||||
|             return { | ||||
|                 content: `No valid command sequence matching \`${metadata.header} ${metadata.symbolicArgs.join( | ||||
|             return `No valid command sequence matching \`${metadata.header} ${metadata.symbolicArgs.join( | ||||
|                 " " | ||||
|                 )}\` found.` | ||||
|             }; | ||||
|             )}\` found.`; | ||||
|         } | ||||
| 
 | ||||
|         // Note: Do NOT add a return statement here. In case one of the other sections is missing
 | ||||
|  | @ -682,7 +658,7 @@ export class RestCommand extends BaseCommand { | |||
|         combined: string, | ||||
|         menu: CommandMenu, | ||||
|         metadata: ExecuteCommandMetadata | ||||
|     ): Promise<SingleMessageOptions | null> { | ||||
|     ): Promise<string | null> { | ||||
|         // Update inherited properties if the current command specifies a property.
 | ||||
|         // In case there are no initial arguments, these should go first so that it can register.
 | ||||
|         if (this.permission !== -1) metadata.permission = this.permission; | ||||
|  | @ -716,9 +692,7 @@ export class RestCommand extends BaseCommand { | |||
|                 const errorMessage = error.stack ?? error; | ||||
|                 console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`); | ||||
| 
 | ||||
|                 return { | ||||
|                     content: `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\`` | ||||
|                 }; | ||||
|                 return `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\``; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  | @ -743,36 +717,34 @@ export class RestCommand extends BaseCommand { | |||
| 
 | ||||
| // See if there is anything that'll prevent the user from executing the command.
 | ||||
| // Returns null if successful, otherwise returns a message with the error.
 | ||||
| function canExecute(menu: CommandMenu, metadata: ExecuteCommandMetadata): SingleMessageOptions | null { | ||||
| function canExecute(menu: CommandMenu, metadata: ExecuteCommandMetadata): string | null { | ||||
|     // 1. Does this command specify a required channel type? If so, does the channel type match?
 | ||||
|     if ( | ||||
|         metadata.channelType === CHANNEL_TYPE.GUILD && | ||||
|         (!(menu.channel instanceof GuildChannel) || menu.guild === null || menu.member === null) | ||||
|     ) { | ||||
|         return {content: "This command must be executed in a server."}; | ||||
|         return "This command must be executed in a server."; | ||||
|     } else if ( | ||||
|         metadata.channelType === CHANNEL_TYPE.DM && | ||||
|         (menu.channel.type !== "dm" || menu.guild !== null || menu.member !== null) | ||||
|     ) { | ||||
|         return {content: "This command must be executed as a direct message."}; | ||||
|         return "This command must be executed as a direct message."; | ||||
|     } | ||||
| 
 | ||||
|     // 2. Is this an NSFW command where the channel prevents such use? (DM channels bypass this requirement.)
 | ||||
|     if (metadata.nsfw && menu.channel.type !== "dm" && !menu.channel.nsfw) { | ||||
|         return {content: "This command must be executed in either an NSFW channel or as a direct message."}; | ||||
|         return "This command must be executed in either an NSFW channel or as a direct message."; | ||||
|     } | ||||
| 
 | ||||
|     // 3. Does the user have permission to execute the command?
 | ||||
|     if (!hasPermission(menu.author, menu.member, metadata.permission)) { | ||||
|         const userPermLevel = getPermissionLevel(menu.author, menu.member); | ||||
| 
 | ||||
|         return { | ||||
|             content: `You don't have access to this command! Your permission level is \`${getPermissionName( | ||||
|         return `You don't have access to this command! Your permission level is \`${getPermissionName( | ||||
|             userPermLevel | ||||
|         )}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName( | ||||
|             metadata.permission | ||||
|             )}\` (${metadata.permission}).` | ||||
|         }; | ||||
|         )}\` (${metadata.permission}).`; | ||||
|     } | ||||
| 
 | ||||
|     return null; | ||||
|  |  | |||
							
								
								
									
										274
									
								
								src/core/libd.ts
									
										
									
									
									
								
							
							
						
						
									
										274
									
								
								src/core/libd.ts
									
										
									
									
									
								
							|  | @ -15,7 +15,9 @@ import { | |||
|     MessageAdditions, | ||||
|     SplitOptions, | ||||
|     APIMessage, | ||||
|     StringResolvable | ||||
|     StringResolvable, | ||||
|     EmojiIdentifierResolvable, | ||||
|     MessageReaction | ||||
| } from "discord.js"; | ||||
| import {unreactEventListeners, replyEventListeners} from "./eventListeners"; | ||||
| import {client} from "./interface"; | ||||
|  | @ -31,19 +33,6 @@ export type SendFunction = (( | |||
|     ((content: StringResolvable, options: MessageOptions & {split: true | SplitOptions}) => Promise<Message[]>) & | ||||
|     ((content: StringResolvable, options: MessageOptions) => Promise<Message | Message[]>); | ||||
| 
 | ||||
| /** | ||||
|  * Tests if a bot has a certain permission in a specified guild. | ||||
|  */ | ||||
| export function botHasPermission(guild: Guild | null, permission: number): boolean { | ||||
|     return !!guild?.me?.hasPermission(permission); | ||||
| } | ||||
| 
 | ||||
| // The SoonTM Section //
 | ||||
| // Maybe promisify this section to reduce the potential for creating callback hell? Especially if multiple questions in a row are being asked.
 | ||||
| // It's probably a good idea to modularize the base reaction handler so there's less copy pasted code.
 | ||||
| // Maybe also make a reaction handler that listens for when reactions are added and removed.
 | ||||
| // The reaction handler would also run an async function to react in order (parallel to the reaction handler).
 | ||||
| 
 | ||||
| const FIVE_BACKWARDS_EMOJI = "⏪"; | ||||
| const BACKWARDS_EMOJI = "⬅️"; | ||||
| const FORWARDS_EMOJI = "➡️"; | ||||
|  | @ -56,34 +45,35 @@ const FIVE_FORWARDS_EMOJI = "⏩"; | |||
|  */ | ||||
| export async function paginate( | ||||
|     send: SendFunction, | ||||
|     senderID: string, | ||||
|     total: number, | ||||
|     callback: (page: number, hasMultiplePages: boolean) => SingleMessageOptions, | ||||
|     onTurnPage: (page: number, hasMultiplePages: boolean) => SingleMessageOptions, | ||||
|     totalPages: number, | ||||
|     listenTo: string | null = null, | ||||
|     duration = 60000 | ||||
| ) { | ||||
|     const hasMultiplePages = total > 1; | ||||
|     const message = await send(callback(0, hasMultiplePages)); | ||||
| ): Promise<void> { | ||||
|     const hasMultiplePages = totalPages > 1; | ||||
|     const message = await send(onTurnPage(0, hasMultiplePages)); | ||||
| 
 | ||||
|     if (hasMultiplePages) { | ||||
|         let page = 0; | ||||
|         const turn = (amount: number) => { | ||||
|             page += amount; | ||||
| 
 | ||||
|             if (page >= total) { | ||||
|                 page %= total; | ||||
|             if (page >= totalPages) { | ||||
|                 page %= totalPages; | ||||
|             } else if (page < 0) { | ||||
|                 // Assuming 3 total pages, it's a bit tricker, but if we just take the modulo of the absolute value (|page| % total), we get (1 2 0 ...), and we just need the pattern (2 1 0 ...). It needs to reverse order except for when it's 0. I want to find a better solution, but for the time being... total - (|page| % total) unless (|page| % total) = 0, then return 0.
 | ||||
|                 const flattened = Math.abs(page) % total; | ||||
|                 if (flattened !== 0) page = total - flattened; | ||||
|                 const flattened = Math.abs(page) % totalPages; | ||||
|                 if (flattened !== 0) page = totalPages - flattened; | ||||
|             } | ||||
| 
 | ||||
|             message.edit(callback(page, true)); | ||||
|             message.edit(onTurnPage(page, true)); | ||||
|         }; | ||||
|         const handle = (emote: string, reacterID: string) => { | ||||
|             if (senderID === reacterID) { | ||||
|             if (reacterID === listenTo || listenTo === null) { | ||||
|                 collector.resetTimer(); // The timer refresh MUST be present in both react and unreact.
 | ||||
|                 switch (emote) { | ||||
|                     case FIVE_BACKWARDS_EMOJI: | ||||
|                         if (total > 5) turn(-5); | ||||
|                         if (totalPages > 5) turn(-5); | ||||
|                         break; | ||||
|                     case BACKWARDS_EMOJI: | ||||
|                         turn(-1); | ||||
|  | @ -92,28 +82,28 @@ export async function paginate( | |||
|                         turn(1); | ||||
|                         break; | ||||
|                     case FIVE_FORWARDS_EMOJI: | ||||
|                         if (total > 5) turn(5); | ||||
|                         if (totalPages > 5) turn(5); | ||||
|                         break; | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         // Listen for reactions and call the handler.
 | ||||
|         let backwardsReactionFive = total > 5 ? await message.react(FIVE_BACKWARDS_EMOJI) : null; | ||||
|         let backwardsReactionFive = totalPages > 5 ? await message.react(FIVE_BACKWARDS_EMOJI) : null; | ||||
|         let backwardsReaction = await message.react(BACKWARDS_EMOJI); | ||||
|         let forwardsReaction = await message.react(FORWARDS_EMOJI); | ||||
|         let forwardsReactionFive = total > 5 ? await message.react(FIVE_FORWARDS_EMOJI) : null; | ||||
|         let forwardsReactionFive = totalPages > 5 ? await message.react(FIVE_FORWARDS_EMOJI) : null; | ||||
|         unreactEventListeners.set(message.id, handle); | ||||
| 
 | ||||
|         const collector = message.createReactionCollector( | ||||
|             (reaction, user) => { | ||||
|                 if (user.id === senderID) { | ||||
|                 // This check is actually redundant because of handle().
 | ||||
|                 if (user.id === listenTo || listenTo === null) { | ||||
|                     // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error.
 | ||||
|                     // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission.
 | ||||
|                     const canDeleteEmotes = botHasPermission(message.guild, Permissions.FLAGS.MANAGE_MESSAGES); | ||||
|                     handle(reaction.emoji.name, user.id); | ||||
|                     if (canDeleteEmotes) reaction.users.remove(user); | ||||
|                     collector.resetTimer(); | ||||
|                 } | ||||
| 
 | ||||
|                 return false; | ||||
|  | @ -134,22 +124,28 @@ export async function paginate( | |||
|     } | ||||
| } | ||||
| 
 | ||||
| // Waits for the sender to either confirm an action or let it pass (and delete the message).
 | ||||
| // This should probably be renamed to "confirm" now that I think of it, "prompt" is better used elsewhere.
 | ||||
| // Append "\n*(This message will automatically be deleted after 10 seconds.)*" in the future?
 | ||||
| /** | ||||
|  * Prompts the user about a decision before following through. | ||||
|  */ | ||||
| export async function prompt(message: Message, senderID: string, onConfirm: () => void, duration = 10000) { | ||||
|     let isDeleted = false; | ||||
| //export function generateMulti
 | ||||
| // paginate after generateonetimeprompt
 | ||||
| 
 | ||||
|     message.react("✅"); | ||||
| // Returns null if timed out, otherwise, returns the value.
 | ||||
| export function generateOneTimePrompt<T>( | ||||
|     message: Message, | ||||
|     stack: {[emote: string]: T}, | ||||
|     listenTo: string | null = null, | ||||
|     duration = 60000 | ||||
| ): Promise<T | null> { | ||||
|     return new Promise(async (resolve) => { | ||||
|         // First, start reacting to the message in order.
 | ||||
|         reactInOrder(message, Object.keys(stack)); | ||||
| 
 | ||||
|         // Then setup the reaction listener in parallel.
 | ||||
|         await message.awaitReactions( | ||||
|         (reaction, user) => { | ||||
|             if (user.id === senderID) { | ||||
|                 if (reaction.emoji.name === "✅") { | ||||
|                     onConfirm(); | ||||
|                     isDeleted = true; | ||||
|             (reaction: MessageReaction, user: User) => { | ||||
|                 if (user.id === listenTo || listenTo === null) { | ||||
|                     const emote = reaction.emoji.name; | ||||
| 
 | ||||
|                     if (emote in stack) { | ||||
|                         resolve(stack[emote]); | ||||
|                         message.delete(); | ||||
|                     } | ||||
|                 } | ||||
|  | @ -163,69 +159,36 @@ export async function prompt(message: Message, senderID: string, onConfirm: () = | |||
|             {time: duration} | ||||
|         ); | ||||
| 
 | ||||
|     if (!isDeleted) message.delete(); | ||||
| } | ||||
| 
 | ||||
| // Asks the user for some input using the inline reply feature. The message here is a message you send beforehand.
 | ||||
| // If the reply is rejected, reply with an error message (when stable support comes from discord.js).
 | ||||
| // Append "\n*(Note: Make sure to use Discord's inline reply feature or this won't work!)*" in the future? And also the "you can now reply to this message" edit.
 | ||||
| export function ask( | ||||
|     message: Message, | ||||
|     senderID: string, | ||||
|     condition: (reply: string) => boolean, | ||||
|     onSuccess: () => void, | ||||
|     onReject: () => string, | ||||
|     timeout = 60000 | ||||
| ) { | ||||
|     const referenceID = `${message.channel.id}-${message.id}`; | ||||
| 
 | ||||
|     replyEventListeners.set(referenceID, (reply) => { | ||||
|         if (reply.author.id === senderID) { | ||||
|             if (condition(reply.content)) { | ||||
|                 onSuccess(); | ||||
|                 replyEventListeners.delete(referenceID); | ||||
|             } else { | ||||
|                 reply.reply(onReject()); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     setTimeout(() => { | ||||
|         replyEventListeners.set(referenceID, (reply) => { | ||||
|             reply.reply("that action timed out, try using the command again"); | ||||
|             replyEventListeners.delete(referenceID); | ||||
|         }); | ||||
|     }, timeout); | ||||
| } | ||||
| 
 | ||||
| export function askYesOrNo(message: Message, senderID: string, timeout = 30000): Promise<boolean> { | ||||
|     return new Promise(async (resolve, reject) => { | ||||
|         let isDeleted = false; | ||||
| 
 | ||||
|         await message.react("✅"); | ||||
|         message.react("❌"); | ||||
|         await message.awaitReactions( | ||||
|             (reaction, user) => { | ||||
|                 if (user.id === senderID) { | ||||
|                     const isCheckReacted = reaction.emoji.name === "✅"; | ||||
| 
 | ||||
|                     if (isCheckReacted || reaction.emoji.name === "❌") { | ||||
|                         resolve(isCheckReacted); | ||||
|                         isDeleted = true; | ||||
|         if (!message.deleted) { | ||||
|             message.delete(); | ||||
|             resolve(null); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| // Start a parallel chain of ordered reactions, allowing a collector to end early.
 | ||||
| // Check if the collector ended early by seeing if the message is already deleted.
 | ||||
| // Though apparently, message.deleted doesn't seem to update fast enough, so just put a try catch block on message.react().
 | ||||
| async function reactInOrder(message: Message, emotes: EmojiIdentifierResolvable[]): Promise<void> { | ||||
|     for (const emote of emotes) { | ||||
|         try { | ||||
|             await message.react(emote); | ||||
|         } catch { | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|                 return false; | ||||
| export function confirm(message: Message, senderID: string, timeout = 30000): Promise<boolean | null> { | ||||
|     return generateOneTimePrompt( | ||||
|         message, | ||||
|         { | ||||
|             "✅": true, | ||||
|             "❌": false | ||||
|         }, | ||||
|             {time: timeout} | ||||
|         senderID, | ||||
|         timeout | ||||
|     ); | ||||
| 
 | ||||
|         if (!isDeleted) { | ||||
|             message.delete(); | ||||
|             reject("Prompt timed out."); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| // This MUST be split into an array. These emojis are made up of several characters each, adding up to 29 in length.
 | ||||
|  | @ -236,40 +199,47 @@ const multiNumbers = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6 | |||
| export async function askMultipleChoice( | ||||
|     message: Message, | ||||
|     senderID: string, | ||||
|     callbackStack: (() => void)[], | ||||
|     choices: number, | ||||
|     timeout = 90000 | ||||
| ) { | ||||
|     if (callbackStack.length > multiNumbers.length) { | ||||
|         message.channel.send( | ||||
|             `\`ERROR: The amount of callbacks in "askMultipleChoice" must not exceed the total amount of allowed options (${multiNumbers.length})!\`` | ||||
| ): Promise<number | null> { | ||||
|     if (choices > multiNumbers.length) | ||||
|         throw new Error( | ||||
|             `askMultipleChoice only accepts up to ${multiNumbers.length} options, ${choices} was provided.` | ||||
|         ); | ||||
|         return; | ||||
|     const numbers: {[emote: string]: number} = {}; | ||||
|     for (let i = 0; i < choices; i++) numbers[multiNumbers[i]] = i; | ||||
|     return generateOneTimePrompt(message, numbers, senderID, timeout); | ||||
| } | ||||
| 
 | ||||
|     let isDeleted = false; | ||||
| // Asks the user for some input using the inline reply feature. The message here is a message you send beforehand.
 | ||||
| // If the reply is rejected, reply with an error message (when stable support comes from discord.js).
 | ||||
| export function askForReply(message: Message, listenTo: string, timeout?: number): Promise<Message | null> { | ||||
|     return new Promise((resolve) => { | ||||
|         const referenceID = `${message.channel.id}-${message.id}`; | ||||
| 
 | ||||
|     for (let i = 0; i < callbackStack.length; i++) { | ||||
|         await message.react(multiNumbers[i]); | ||||
|     } | ||||
| 
 | ||||
|     await message.awaitReactions( | ||||
|         (reaction, user) => { | ||||
|             if (user.id === senderID) { | ||||
|                 const index = multiNumbers.indexOf(reaction.emoji.name); | ||||
| 
 | ||||
|                 if (index !== -1) { | ||||
|                     callbackStack[index](); | ||||
|                     isDeleted = true; | ||||
|         replyEventListeners.set(referenceID, (reply) => { | ||||
|             if (reply.author.id === listenTo) { | ||||
|                 message.delete(); | ||||
|                 replyEventListeners.delete(referenceID); | ||||
|                 resolve(reply); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         if (timeout) { | ||||
|             client.setTimeout(() => { | ||||
|                 if (!message.deleted) message.delete(); | ||||
|                 replyEventListeners.delete(referenceID); | ||||
|                 resolve(null); | ||||
|             }, timeout); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
|             return false; | ||||
|         }, | ||||
|         {time: timeout} | ||||
|     ); | ||||
| 
 | ||||
|     if (!isDeleted) message.delete(); | ||||
| /** | ||||
|  * Tests if a bot has a certain permission in a specified guild. | ||||
|  */ | ||||
| export function botHasPermission(guild: Guild | null, permission: number): boolean { | ||||
|     return !!guild?.me?.hasPermission(permission); | ||||
| } | ||||
| 
 | ||||
| // For "get x by y" methods:
 | ||||
|  | @ -277,79 +247,75 @@ export async function askMultipleChoice( | |||
| // It's more reliable to get users/members by fetching their IDs. fetch() will searching through the cache anyway.
 | ||||
| // For guilds, do an extra check to make sure there isn't an outage (guild.available).
 | ||||
| 
 | ||||
| export function getGuildByID(id: string): Guild | SingleMessageOptions { | ||||
| export function getGuildByID(id: string): Guild | string { | ||||
|     const guild = client.guilds.cache.get(id); | ||||
| 
 | ||||
|     if (guild) { | ||||
|         if (guild.available) return guild; | ||||
|         else return {content: `The guild \`${guild.name}\` (ID: \`${id}\`) is unavailable due to an outage.`}; | ||||
|         else return `The guild \`${guild.name}\` (ID: \`${id}\`) is unavailable due to an outage.`; | ||||
|     } else { | ||||
|         return { | ||||
|             content: `No guild found by the ID of \`${id}\`!` | ||||
|         }; | ||||
|         return `No guild found by the ID of \`${id}\`!`; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export function getGuildByName(name: string): Guild | SingleMessageOptions { | ||||
| export function getGuildByName(name: string): Guild | string { | ||||
|     const query = name.toLowerCase(); | ||||
|     const guild = client.guilds.cache.find((guild) => guild.name.toLowerCase().includes(query)); | ||||
| 
 | ||||
|     if (guild) { | ||||
|         if (guild.available) return guild; | ||||
|         else return {content: `The guild \`${guild.name}\` (ID: \`${guild.id}\`) is unavailable due to an outage.`}; | ||||
|         else return `The guild \`${guild.name}\` (ID: \`${guild.id}\`) is unavailable due to an outage.`; | ||||
|     } else { | ||||
|         return { | ||||
|             content: `No guild found by the name of \`${name}\`!` | ||||
|         }; | ||||
|         return `No guild found by the name of \`${name}\`!`; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export async function getChannelByID(id: string): Promise<Channel | SingleMessageOptions> { | ||||
| export async function getChannelByID(id: string): Promise<Channel | string> { | ||||
|     try { | ||||
|         return await client.channels.fetch(id); | ||||
|     } catch { | ||||
|         return {content: `No channel found by the ID of \`${id}\`!`}; | ||||
|         return `No channel found by the ID of \`${id}\`!`; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Only go through the cached channels (non-DM channels). Plus, searching DM channels by name wouldn't really make sense, nor do they have names to search anyway.
 | ||||
| export function getChannelByName(name: string): GuildChannel | SingleMessageOptions { | ||||
| export function getChannelByName(name: string): GuildChannel | string { | ||||
|     const query = name.toLowerCase(); | ||||
|     const channel = client.channels.cache.find( | ||||
|         (channel) => channel instanceof GuildChannel && channel.name.toLowerCase().includes(query) | ||||
|     ) as GuildChannel | undefined; | ||||
|     if (channel) return channel; | ||||
|     else return {content: `No channel found by the name of \`${name}\`!`}; | ||||
|     else return `No channel found by the name of \`${name}\`!`; | ||||
| } | ||||
| 
 | ||||
| export async function getMessageByID( | ||||
|     channel: TextChannel | DMChannel | NewsChannel | string, | ||||
|     id: string | ||||
| ): Promise<Message | SingleMessageOptions> { | ||||
| ): Promise<Message | string> { | ||||
|     if (typeof channel === "string") { | ||||
|         const targetChannel = await getChannelByID(channel); | ||||
|         if (targetChannel instanceof TextChannel || targetChannel instanceof DMChannel) channel = targetChannel; | ||||
|         else if (targetChannel instanceof Channel) return {content: `\`${id}\` isn't a valid text-based channel!`}; | ||||
|         else if (targetChannel instanceof Channel) return `\`${id}\` isn't a valid text-based channel!`; | ||||
|         else return targetChannel; | ||||
|     } | ||||
| 
 | ||||
|     try { | ||||
|         return await channel.messages.fetch(id); | ||||
|     } catch { | ||||
|         return {content: `\`${id}\` isn't a valid message of the channel ${channel}!`}; | ||||
|         return `\`${id}\` isn't a valid message of the channel ${channel}!`; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export async function getUserByID(id: string): Promise<User | SingleMessageOptions> { | ||||
| export async function getUserByID(id: string): Promise<User | string> { | ||||
|     try { | ||||
|         return await client.users.fetch(id); | ||||
|     } catch { | ||||
|         return {content: `No user found by the ID of \`${id}\`!`}; | ||||
|         return `No user found by the ID of \`${id}\`!`; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Also check tags (if provided) to narrow down users.
 | ||||
| export function getUserByName(name: string): User | SingleMessageOptions { | ||||
| export function getUserByName(name: string): User | string { | ||||
|     let query = name.toLowerCase(); | ||||
|     const tagMatch = /^(.+?)#(\d{4})$/.exec(name); | ||||
|     let tag: string | null = null; | ||||
|  | @ -366,19 +332,19 @@ export function getUserByName(name: string): User | SingleMessageOptions { | |||
|     }); | ||||
| 
 | ||||
|     if (user) return user; | ||||
|     else return {content: `No user found by the name of \`${name}\`!`}; | ||||
|     else return `No user found by the name of \`${name}\`!`; | ||||
| } | ||||
| 
 | ||||
| export async function getMemberByID(guild: Guild, id: string): Promise<GuildMember | SingleMessageOptions> { | ||||
| export async function getMemberByID(guild: Guild, id: string): Promise<GuildMember | string> { | ||||
|     try { | ||||
|         return await guild.members.fetch(id); | ||||
|     } catch { | ||||
|         return {content: `No member found by the ID of \`${id}\`!`}; | ||||
|         return `No member found by the ID of \`${id}\`!`; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // First checks if a member can be found by that nickname, then check if a member can be found by that username.
 | ||||
| export async function getMemberByName(guild: Guild, name: string): Promise<GuildMember | SingleMessageOptions> { | ||||
| export async function getMemberByName(guild: Guild, name: string): Promise<GuildMember | string> { | ||||
|     const member = ( | ||||
|         await guild.members.fetch({ | ||||
|             query: name, | ||||
|  | @ -395,9 +361,9 @@ export async function getMemberByName(guild: Guild, name: string): Promise<Guild | |||
|         if (user instanceof User) { | ||||
|             const member = guild.members.resolve(user); | ||||
|             if (member) return member; | ||||
|             else return {content: `The user \`${user.tag}\` isn't in this guild!`}; | ||||
|             else return `The user \`${user.tag}\` isn't in this guild!`; | ||||
|         } else { | ||||
|             return {content: `No member found by the name of \`${name}\`!`}; | ||||
|             return `No member found by the name of \`${name}\`!`; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| import {client} from "../index"; | ||||
| import {Message, MessageEmbed} from "discord.js"; | ||||
| import {MessageEmbed} from "discord.js"; | ||||
| import {getPrefix} from "../structures"; | ||||
| import {getMessageByID} from "../core"; | ||||
| 
 | ||||
|  | @ -13,7 +13,7 @@ client.on("message", async (message) => { | |||
|     const linkMessage = await getMessageByID(channelID, messageID); | ||||
| 
 | ||||
|     // If it's an invalid link (or the bot doesn't have access to it).
 | ||||
|     if (!(linkMessage instanceof Message)) { | ||||
|     if (typeof linkMessage === "string") { | ||||
|         return message.channel.send("I don't have access to that channel!"); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue