mirror of
				https://github.com/keanuplayz/TravBot-v3.git
				synced 2024-08-15 02:33:12 +00:00 
			
		
		
		
	Merge branch 'typescript' of https://github.com/keanuplayz/TravBot-v3 into experimental-core
This commit is contained in:
		
						commit
						00addd468c
					
				
					 12 changed files with 2242 additions and 119 deletions
				
			
		
							
								
								
									
										1919
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										1919
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -10,8 +10,7 @@ | ||||||
|         "glob": "^7.1.6", |         "glob": "^7.1.6", | ||||||
|         "inquirer": "^7.3.3", |         "inquirer": "^7.3.3", | ||||||
|         "moment": "^2.29.1", |         "moment": "^2.29.1", | ||||||
|         "ms": "^2.1.3", |         "ms": "^2.1.3" | ||||||
|         "os": "^0.1.1" |  | ||||||
|     }, |     }, | ||||||
|     "devDependencies": { |     "devDependencies": { | ||||||
|         "@types/glob": "^7.1.3", |         "@types/glob": "^7.1.3", | ||||||
|  |  | ||||||
|  | @ -124,14 +124,15 @@ export default new Command({ | ||||||
|             number: new Command({ |             number: new Command({ | ||||||
|                 description: "Amount of messages to delete.", |                 description: "Amount of messages to delete.", | ||||||
|                 async run($: CommonLibrary): Promise<any> { |                 async run($: CommonLibrary): Promise<any> { | ||||||
|  |                     if ($.channel.type === "dm") { | ||||||
|  |                         await $.channel.send("Can't clear messages in the DMs!"); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|                     $.message.delete(); |                     $.message.delete(); | ||||||
|                     const fetched = await $.channel.messages.fetch({ |                     const fetched = await $.channel.messages.fetch({ | ||||||
|                         limit: $.args[0] |                         limit: $.args[0] | ||||||
|                     }); |                     }); | ||||||
|                     $.channel |                     await $.channel.bulkDelete(fetched); | ||||||
|                         /// @ts-ignore
 |  | ||||||
|                         .bulkDelete(fetched) |  | ||||||
|                         .catch((error: any) => $.channel.send(`Error: ${error}`)); |  | ||||||
|                 } |                 } | ||||||
|             }) |             }) | ||||||
|         }), |         }), | ||||||
|  | @ -157,8 +158,7 @@ export default new Command({ | ||||||
|             permission: Command.PERMISSIONS.BOT_SUPPORT, |             permission: Command.PERMISSIONS.BOT_SUPPORT, | ||||||
|             async run($: CommonLibrary): Promise<any> { |             async run($: CommonLibrary): Promise<any> { | ||||||
|                 const nickName = $.args.join(" "); |                 const nickName = $.args.join(" "); | ||||||
|                 const trav = $.guild?.members.cache.find((member) => member.id === $.client.user?.id); |                 await $.guild?.me?.setNickname(nickName); | ||||||
|                 await trav?.setNickname(nickName); |  | ||||||
|                 if (botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES)) |                 if (botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES)) | ||||||
|                     $.message.delete({timeout: 5000}).catch($.handler.bind($)); |                     $.message.delete({timeout: 5000}).catch($.handler.bind($)); | ||||||
|                 $.channel.send(`Nickname set to \`${nickName}\``).then((m) => m.delete({timeout: 5000})); |                 $.channel.send(`Nickname set to \`${nickName}\``).then((m) => m.delete({timeout: 5000})); | ||||||
|  |  | ||||||
|  | @ -62,9 +62,9 @@ export function getSendEmbed(sender: User, receiver: User, amount: number): obje | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function isAuthorized(guild: Guild | null, channel: TextChannel | DMChannel | NewsChannel): boolean { | export function isAuthorized(guild: Guild | null, channel: TextChannel | DMChannel | NewsChannel): boolean { | ||||||
|     if (guild?.id === "637512823676600330" || process.argv[2] === "dev") return true; |     if (guild?.id === "637512823676600330" && channel?.id === "669464416420364288" || process.argv[2] === "dev") return true; | ||||||
|     else { |     else { | ||||||
|         channel.send("Sorry, this command can only be used in Monika's emote server."); |         channel.send("Sorry, this command can only be used in Monika's emote server. (#mon-stocks)"); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -6,6 +6,9 @@ import {CommonLibrary, formatBytes, trimArray} from "../core/lib"; | ||||||
| import {verificationLevels, filterLevels, regions, flags} from "../defs/info"; | import {verificationLevels, filterLevels, regions, flags} from "../defs/info"; | ||||||
| import moment from "moment"; | import moment from "moment"; | ||||||
| import utc from "moment"; | import utc from "moment"; | ||||||
|  | import {Guild} from "discord.js"; | ||||||
|  | 
 | ||||||
|  | const {version} = require("../../package.json"); | ||||||
| 
 | 
 | ||||||
| export default new Command({ | export default new Command({ | ||||||
|     description: "Command to provide all sorts of info about the current server, a user, etc.", |     description: "Command to provide all sorts of info about the current server, a user, etc.", | ||||||
|  | @ -34,13 +37,6 @@ export default new Command({ | ||||||
|             async run($: CommonLibrary): Promise<any> { |             async run($: CommonLibrary): Promise<any> { | ||||||
|                 const core = os.cpus()[0]; |                 const core = os.cpus()[0]; | ||||||
|                 const embed = new MessageEmbed() |                 const embed = new MessageEmbed() | ||||||
|                     .setThumbnail( |  | ||||||
|                         /// @ts-ignore
 |  | ||||||
|                         $.client.user?.displayAvatarURL({ |  | ||||||
|                             dynamic: true, |  | ||||||
|                             size: 2048 |  | ||||||
|                         }) |  | ||||||
|                     ) |  | ||||||
|                     .setColor($.guild?.me?.displayHexColor || "BLUE") |                     .setColor($.guild?.me?.displayHexColor || "BLUE") | ||||||
|                     .addField("General", [ |                     .addField("General", [ | ||||||
|                         `**❯ Client:** ${$.client.user?.tag} (${$.client.user?.id})`, |                         `**❯ Client:** ${$.client.user?.tag} (${$.client.user?.id})`, | ||||||
|  | @ -66,84 +62,59 @@ export default new Command({ | ||||||
|                         `\u3000 • Speed: ${core.speed}MHz`, |                         `\u3000 • Speed: ${core.speed}MHz`, | ||||||
|                         `**❯ Memory:**`, |                         `**❯ Memory:**`, | ||||||
|                         `\u3000 • Total: ${formatBytes(process.memoryUsage().heapTotal)}`, |                         `\u3000 • Total: ${formatBytes(process.memoryUsage().heapTotal)}`, | ||||||
|                         `\u3000 • Used: ${formatBytes(process.memoryUsage().heapTotal)}` |                         `\u3000 • Used: ${formatBytes(process.memoryUsage().heapUsed)}` | ||||||
|                     ]) |                     ]) | ||||||
|                     .setTimestamp(); |                     .setTimestamp(); | ||||||
|  |                 const avatarURL = $.client.user?.displayAvatarURL({ | ||||||
|  |                     dynamic: true, | ||||||
|  |                     size: 2048 | ||||||
|  |                 }); | ||||||
|  |                 if (avatarURL) embed.setThumbnail(avatarURL); | ||||||
|                 $.channel.send(embed); |                 $.channel.send(embed); | ||||||
|             } |             } | ||||||
|         }), |         }), | ||||||
|         guild: new Command({ |         guild: new Command({ | ||||||
|             description: "Displays info about the current guild.", |             description: "Displays info about the current guild or another guild.", | ||||||
|  |             usage: "(<guild name>/<guild ID>)", | ||||||
|             async run($: CommonLibrary): Promise<any> { |             async run($: CommonLibrary): Promise<any> { | ||||||
|                 if ($.guild) { |                 if ($.guild) { | ||||||
|                     const roles = $.guild.roles.cache |                     $.channel.send(await getGuildInfo($.guild, $.guild)); | ||||||
|                         .sort((a, b) => b.position - a.position) |  | ||||||
|                         .map((role) => role.toString()); |  | ||||||
|                     const members = $.guild.members.cache; |  | ||||||
|                     const channels = $.guild.channels.cache; |  | ||||||
|                     const emojis = $.guild.emojis.cache; |  | ||||||
|                     const iconURL = $.guild.iconURL({dynamic: true}); |  | ||||||
|                     const embed = new MessageEmbed() |  | ||||||
|                         .setDescription(`**Guild information for __${$.guild.name}__**`) |  | ||||||
|                         .setColor("BLUE"); |  | ||||||
|                     if (iconURL) |  | ||||||
|                         embed |  | ||||||
|                             .setThumbnail(iconURL) |  | ||||||
|                             .addField("General", [ |  | ||||||
|                                 `**❯ Name:** ${$.guild.name}`, |  | ||||||
|                                 `**❯ ID:** ${$.guild.id}`, |  | ||||||
|                                 `**❯ Owner:** ${$.guild.owner?.user.tag} (${$.guild.ownerID})`, |  | ||||||
|                                 `**❯ Region:** ${regions[$.guild.region]}`, |  | ||||||
|                                 `**❯ Boost Tier:** ${$.guild.premiumTier ? `Tier ${$.guild.premiumTier}` : "None"}`, |  | ||||||
|                                 `**❯ Explicit Filter:** ${filterLevels[$.guild.explicitContentFilter]}`, |  | ||||||
|                                 `**❯ Verification Level:** ${verificationLevels[$.guild.verificationLevel]}`, |  | ||||||
|                                 `**❯ Time Created:** ${moment($.guild.createdTimestamp).format("LT")} ${moment( |  | ||||||
|                                     $.guild.createdTimestamp |  | ||||||
|                                 ).format("LL")} ${moment($.guild.createdTimestamp).fromNow()})`,
 |  | ||||||
|                                 "\u200b" |  | ||||||
|                             ]) |  | ||||||
|                             .addField("Statistics", [ |  | ||||||
|                                 `**❯ Role Count:** ${roles.length}`, |  | ||||||
|                                 `**❯ Emoji Count:** ${emojis.size}`, |  | ||||||
|                                 `**❯ Regular Emoji Count:** ${emojis.filter((emoji) => !emoji.animated).size}`, |  | ||||||
|                                 `**❯ Animated Emoji Count:** ${emojis.filter((emoji) => emoji.animated).size}`, |  | ||||||
|                                 `**❯ Member Count:** ${$.guild.memberCount}`, |  | ||||||
|                                 `**❯ Humans:** ${members.filter((member) => !member.user.bot).size}`, |  | ||||||
|                                 `**❯ Bots:** ${members.filter((member) => member.user.bot).size}`, |  | ||||||
|                                 `**❯ Text Channels:** ${channels.filter((channel) => channel.type === "text").size}`, |  | ||||||
|                                 `**❯ Voice Channels:** ${channels.filter((channel) => channel.type === "voice").size}`, |  | ||||||
|                                 `**❯ Boost Count:** ${$.guild.premiumSubscriptionCount || "0"}`, |  | ||||||
|                                 `\u200b` |  | ||||||
|                             ]) |  | ||||||
|                             .addField("Presence", [ |  | ||||||
|                                 `**❯ Online:** ${members.filter((member) => member.presence.status === "online").size}`, |  | ||||||
|                                 `**❯ Idle:** ${members.filter((member) => member.presence.status === "idle").size}`, |  | ||||||
|                                 `**❯ Do Not Disturb:** ${ |  | ||||||
|                                     members.filter((member) => member.presence.status === "dnd").size |  | ||||||
|                                 }`,
 |  | ||||||
|                                 `**❯ Offline:** ${ |  | ||||||
|                                     members.filter((member) => member.presence.status === "offline").size |  | ||||||
|                                 }`,
 |  | ||||||
|                                 "\u200b" |  | ||||||
|                             ]) |  | ||||||
|                             .addField( |  | ||||||
|                                 `Roles [${roles.length - 1}]`, |  | ||||||
|                                 roles.length < 10 ? roles.join(", ") : roles.length > 10 ? trimArray(roles) : "None" |  | ||||||
|                             ) |  | ||||||
|                             .setTimestamp(); |  | ||||||
| 
 |  | ||||||
|                     $.channel.send(embed); |  | ||||||
|                 } else { |                 } else { | ||||||
|                     $.channel.send("Please execute this command in a guild."); |                     $.channel.send("Please execute this command in a guild."); | ||||||
|                 } |                 } | ||||||
|             } |             }, | ||||||
|  |             any: new Command({ | ||||||
|  |                 description: "Display info about a guild by finding its name or ID.", | ||||||
|  |                 async run($: CommonLibrary): Promise<any> { | ||||||
|  |                     // If a guild ID is provided (avoid the "number" subcommand because of inaccuracies), search for that guild
 | ||||||
|  |                     if ($.args.length === 1 && /^\d{17,19}$/.test($.args[0])) { | ||||||
|  |                         const id = $.args[0]; | ||||||
|  |                         const guild = $.client.guilds.cache.get(id); | ||||||
|  | 
 | ||||||
|  |                         if (guild) { | ||||||
|  |                             $.channel.send(await getGuildInfo(guild, $.guild)); | ||||||
|  |                         } else { | ||||||
|  |                             $.channel.send(`None of the servers I'm in matches the guild ID \`${id}\`!`); | ||||||
|  |                         } | ||||||
|  |                     } else { | ||||||
|  |                         const query: string = $.args.join(" ").toLowerCase(); | ||||||
|  |                         const guild = $.client.guilds.cache.find((guild) => guild.name.toLowerCase().includes(query)); | ||||||
|  | 
 | ||||||
|  |                         if (guild) { | ||||||
|  |                             $.channel.send(await getGuildInfo(guild, $.guild)); | ||||||
|  |                         } else { | ||||||
|  |                             $.channel.send(`None of the servers I'm in matches the query \`${query}\`!`); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|         }) |         }) | ||||||
|     }, |     }, | ||||||
|     user: new Command({ |     user: new Command({ | ||||||
|         description: "Displays info about mentioned user.", |         description: "Displays info about mentioned user.", | ||||||
|         async run($: CommonLibrary): Promise<any> { |         async run($: CommonLibrary): Promise<any> { | ||||||
|             // Transforms the User object into a GuildMember object of the current guild.
 |             // Transforms the User object into a GuildMember object of the current guild.
 | ||||||
|             const member = $.guild?.members.resolve($.args[0]) ?? (await $.guild?.members.fetch($.args[0])); |             const member = await $.guild?.members.fetch($.args[0]); | ||||||
| 
 | 
 | ||||||
|             if (!member) |             if (!member) | ||||||
|                 return $.channel.send( |                 return $.channel.send( | ||||||
|  | @ -154,8 +125,7 @@ export default new Command({ | ||||||
|                 .sort((a: {position: number}, b: {position: number}) => b.position - a.position) |                 .sort((a: {position: number}, b: {position: number}) => b.position - a.position) | ||||||
|                 .map((role: {toString: () => any}) => role.toString()) |                 .map((role: {toString: () => any}) => role.toString()) | ||||||
|                 .slice(0, -1); |                 .slice(0, -1); | ||||||
|             // @ts-ignore - Discord.js' typings seem to be outdated here. According to their v12 docs, it's User.fetchFlags() instead of User.flags.
 |             const userFlags = (await member.user.fetchFlags()).toArray(); | ||||||
|             const userFlags = ((await member.user.fetchFlags()) as UserFlags).toArray(); |  | ||||||
| 
 | 
 | ||||||
|             const embed = new MessageEmbed() |             const embed = new MessageEmbed() | ||||||
|                 .setThumbnail(member.user.displayAvatarURL({dynamic: true, size: 512})) |                 .setThumbnail(member.user.displayAvatarURL({dynamic: true, size: 512})) | ||||||
|  | @ -188,3 +158,64 @@ export default new Command({ | ||||||
|         } |         } | ||||||
|     }) |     }) | ||||||
| }); | }); | ||||||
|  | 
 | ||||||
|  | async function getGuildInfo(guild: Guild, currentGuild: Guild | null) { | ||||||
|  |     const members = await guild.members.fetch({ | ||||||
|  |         withPresences: true, | ||||||
|  |         force: true | ||||||
|  |     }); | ||||||
|  |     const roles = guild.roles.cache.sort((a, b) => b.position - a.position).map((role) => role.toString()); | ||||||
|  |     const channels = guild.channels.cache; | ||||||
|  |     const emojis = guild.emojis.cache; | ||||||
|  |     const iconURL = guild.iconURL({dynamic: true}); | ||||||
|  |     const embed = new MessageEmbed().setDescription(`**Guild information for __${guild.name}__**`).setColor("BLUE"); | ||||||
|  |     const displayRoles = !!(currentGuild && guild.id === currentGuild.id); | ||||||
|  |     if (iconURL) { | ||||||
|  |         embed | ||||||
|  |             .setThumbnail(iconURL) | ||||||
|  |             .addField("General", [ | ||||||
|  |                 `**❯ Name:** ${guild.name}`, | ||||||
|  |                 `**❯ ID:** ${guild.id}`, | ||||||
|  |                 `**❯ Owner:** ${guild.owner?.user.tag} (${guild.ownerID})`, | ||||||
|  |                 `**❯ Region:** ${regions[guild.region]}`, | ||||||
|  |                 `**❯ Boost Tier:** ${guild.premiumTier ? `Tier ${guild.premiumTier}` : "None"}`, | ||||||
|  |                 `**❯ Explicit Filter:** ${filterLevels[guild.explicitContentFilter]}`, | ||||||
|  |                 `**❯ Verification Level:** ${verificationLevels[guild.verificationLevel]}`, | ||||||
|  |                 `**❯ Time Created:** ${moment(guild.createdTimestamp).format("LT")} ${moment( | ||||||
|  |                     guild.createdTimestamp | ||||||
|  |                 ).format("LL")} ${moment(guild.createdTimestamp).fromNow()}`,
 | ||||||
|  |                 "\u200b" | ||||||
|  |             ]) | ||||||
|  |             .addField("Statistics", [ | ||||||
|  |                 `**❯ Role Count:** ${roles.length}`, | ||||||
|  |                 `**❯ Emoji Count:** ${emojis.size}`, | ||||||
|  |                 `**❯ Regular Emoji Count:** ${emojis.filter((emoji) => !emoji.animated).size}`, | ||||||
|  |                 `**❯ Animated Emoji Count:** ${emojis.filter((emoji) => emoji.animated).size}`, | ||||||
|  |                 `**❯ Member Count:** ${guild.memberCount}`, | ||||||
|  |                 `**❯ Humans:** ${members.filter((member) => !member.user.bot).size}`, | ||||||
|  |                 `**❯ Bots:** ${members.filter((member) => member.user.bot).size}`, | ||||||
|  |                 `**❯ Text Channels:** ${channels.filter((channel) => channel.type === "text").size}`, | ||||||
|  |                 `**❯ Voice Channels:** ${channels.filter((channel) => channel.type === "voice").size}`, | ||||||
|  |                 `**❯ Boost Count:** ${guild.premiumSubscriptionCount || "0"}`, | ||||||
|  |                 `\u200b` | ||||||
|  |             ]) | ||||||
|  |             .addField("Presence", [ | ||||||
|  |                 `**❯ Online:** ${members.filter((member) => member.presence.status === "online").size}`, | ||||||
|  |                 `**❯ Idle:** ${members.filter((member) => member.presence.status === "idle").size}`, | ||||||
|  |                 `**❯ Do Not Disturb:** ${members.filter((member) => member.presence.status === "dnd").size}`, | ||||||
|  |                 `**❯ Offline:** ${members.filter((member) => member.presence.status === "offline").size}`, | ||||||
|  |                 displayRoles ? "\u200b" : "" | ||||||
|  |             ]) | ||||||
|  |             .setTimestamp(); | ||||||
|  | 
 | ||||||
|  |         // Only add the roles if the guild the bot is sending the message to is the same one that's being requested.
 | ||||||
|  |         if (displayRoles) { | ||||||
|  |             embed.addField( | ||||||
|  |                 `Roles [${roles.length - 1}]`, | ||||||
|  |                 roles.length < 10 ? roles.join(", ") : roles.length > 10 ? trimArray(roles) : "None" | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return embed; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -9,15 +9,14 @@ export default new Command({ | ||||||
| 
 | 
 | ||||||
|         if (!voiceChannel) return $.channel.send("You are not in a voice channel."); |         if (!voiceChannel) return $.channel.send("You are not in a voice channel."); | ||||||
| 
 | 
 | ||||||
|         if (!$.guild?.me?.hasPermission("MANAGE_CHANNELS")) |         if (!voiceChannel.guild.me?.hasPermission("MANAGE_CHANNELS")) | ||||||
|             return $.channel.send("I am lacking the required permissions to perform this action."); |             return $.channel.send("I am lacking the required permissions to perform this action."); | ||||||
| 
 | 
 | ||||||
|         if ($.args.length === 0) return $.channel.send("Please provide a new voice channel name."); |         if ($.args.length === 0) return $.channel.send("Please provide a new voice channel name."); | ||||||
| 
 | 
 | ||||||
|         const changeVC = $.guild.channels.resolve(voiceChannel.id); |         const prevName = voiceChannel.name; | ||||||
|         $.channel |         const newName = $.args.join(" "); | ||||||
|             .send(`Changed channel name from "${voiceChannel}" to "${$.args.join(" ")}".`) |         await voiceChannel.setName(newName); | ||||||
|             /// @ts-ignore
 |         await $.channel.send(`Changed channel name from "${prevName}" to "${newName}".`); | ||||||
|             .then(changeVC?.setName($.args.join(" "))); |  | ||||||
|     } |     } | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -12,7 +12,6 @@ export default new Command({ | ||||||
|         async run({guild, channel, message, args}) { |         async run({guild, channel, message, args}) { | ||||||
|             let output = ""; |             let output = ""; | ||||||
|             for (const query of args) output += queryClosestEmoteByName(query).toString(); |             for (const query of args) output += queryClosestEmoteByName(query).toString(); | ||||||
|             if (botHasPermission(guild, Permissions.FLAGS.MANAGE_MESSAGES)) message.delete(); |  | ||||||
|             channel.send(output); |             channel.send(output); | ||||||
|         } |         } | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|  | @ -1,32 +1,117 @@ | ||||||
|  | import {GuildEmoji} from "discord.js"; | ||||||
| import {MessageEmbed} from "discord.js"; | import {MessageEmbed} from "discord.js"; | ||||||
| import Command from "../../core/command"; | import Command from "../../core/command"; | ||||||
| import {CommonLibrary} from "../../core/lib"; | import {CommonLibrary} from "../../core/lib"; | ||||||
|  | import vm from "vm"; | ||||||
|  | 
 | ||||||
|  | const REGEX_TIMEOUT_MS = 1000; | ||||||
| 
 | 
 | ||||||
| export default new Command({ | export default new Command({ | ||||||
|     description: "Lists all emotes the bot has in it's registry,", |     description: "Lists all emotes the bot has in it's registry,", | ||||||
|     endpoint: true, |     usage: "<regex pattern> (-flags)", | ||||||
|     async run($: CommonLibrary): Promise<any> { |     async run($: CommonLibrary): Promise<any> { | ||||||
|         const nsfw: string | string[] = []; |         displayEmoteList($, $.client.emojis.cache.array()); | ||||||
|         const pages = $.client.emojis.cache.filter((x) => !nsfw.includes(x.guild.id), this).array(); |     }, | ||||||
|         const pagesSplit = $(pages).split(20); |     any: new Command({ | ||||||
|         $.log(pagesSplit); |         description: | ||||||
|         var embed = new MessageEmbed().setTitle("**Emoji list!**").setColor("AQUA"); |             "Filters emotes by via a regular expression. Flags can be added by adding a dash at the end. For example, to do a case-insensitive search, do %prefix%lsemotes somepattern -i", | ||||||
|         let desc = ""; |         async run($: CommonLibrary): Promise<any> { | ||||||
|  |             // If a guild ID is provided, filter all emotes by that guild (but only if there aren't any arguments afterward)
 | ||||||
|  |             if ($.args.length === 1 && /^\d{17,19}$/.test($.args[0])) { | ||||||
|  |                 const guildID: string = $.args[0]; | ||||||
| 
 | 
 | ||||||
|         for (const emote of pagesSplit[0]) { |                 displayEmoteList($, $.client.emojis.cache.filter((emote) => emote.guild.id === guildID).array()); | ||||||
|             desc += `${emote} | ${emote.name}\n`; |             } else { | ||||||
|  |                 // Otherwise, search via a regex pattern
 | ||||||
|  |                 let flags: string | undefined = undefined; | ||||||
|  | 
 | ||||||
|  |                 if (/^-[dgimsuy]{1,7}$/.test($.args[$.args.length - 1])) { | ||||||
|  |                     flags = $.args.pop().substring(1); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 let emoteCollection = $.client.emojis.cache.array(); | ||||||
|  |                 // Creates a sandbox to stop a regular expression if it takes too much time to search.
 | ||||||
|  |                 // To avoid passing in a giant data structure, I'll just pass in the structure {[id: string]: [name: string]}.
 | ||||||
|  |                 //let emotes: {[id: string]: string} = {};
 | ||||||
|  |                 let emotes = new Map<string, string>(); | ||||||
|  | 
 | ||||||
|  |                 for (const emote of emoteCollection) { | ||||||
|  |                     emotes.set(emote.id, emote.name); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 // The result will be sandbox.emotes because it'll be modified in-place.
 | ||||||
|  |                 const sandbox = { | ||||||
|  |                     regex: new RegExp($.args.join(" "), flags), | ||||||
|  |                     emotes | ||||||
|  |                 }; | ||||||
|  |                 const context = vm.createContext(sandbox); | ||||||
|  | 
 | ||||||
|  |                 if (vm.isContext(sandbox)) { | ||||||
|  |                     // Restrict an entire query to the timeout specified.
 | ||||||
|  |                     try { | ||||||
|  |                         const script = new vm.Script( | ||||||
|  |                             "for(const [id, name] of emotes.entries()) if(!regex.test(name)) emotes.delete(id);" | ||||||
|  |                         ); | ||||||
|  |                         script.runInContext(context, {timeout: REGEX_TIMEOUT_MS}); | ||||||
|  |                         emotes = sandbox.emotes; | ||||||
|  |                         emoteCollection = emoteCollection.filter((emote) => emotes.has(emote.id)); // Only allow emotes that haven't been deleted.
 | ||||||
|  |                         displayEmoteList($, emoteCollection); | ||||||
|  |                     } catch (error) { | ||||||
|  |                         if (error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") { | ||||||
|  |                             $.channel.send( | ||||||
|  |                                 `The regular expression you entered exceeded the time limit of ${REGEX_TIMEOUT_MS} milliseconds.` | ||||||
|  |                             ); | ||||||
|  |                         } else { | ||||||
|  |                             throw new Error(error); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     $.channel.send("Failed to initialize sandbox."); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }) | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | async function displayEmoteList($: CommonLibrary, emotes: GuildEmoji[]) { | ||||||
|  |     emotes.sort((a, b) => { | ||||||
|  |         const first = a.name.toLowerCase(); | ||||||
|  |         const second = b.name.toLowerCase(); | ||||||
|  | 
 | ||||||
|  |         if (first > second) return 1; | ||||||
|  |         else if (first < second) return -1; | ||||||
|  |         else return 0; | ||||||
|  |     }); | ||||||
|  |     const sections = $(emotes).split(20); | ||||||
|  |     const pages = sections.length; | ||||||
|  |     const embed = new MessageEmbed().setTitle("**Emotes**").setColor("AQUA"); | ||||||
|  |     let desc = ""; | ||||||
|  | 
 | ||||||
|  |     // Gather the first page (if it even exists, which it might not if there no valid emotes appear)
 | ||||||
|  |     if (pages > 0) { | ||||||
|  |         for (const emote of sections[0]) { | ||||||
|  |             desc += `${emote} ${emote.name} (**${emote.guild.name}**)\n`; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         embed.setDescription(desc); |         embed.setDescription(desc); | ||||||
|         const msg = await $.channel.send({embed}); |  | ||||||
| 
 | 
 | ||||||
|         $.paginate(msg, $.author.id, pages.length, (page) => { |         if (pages > 1) { | ||||||
|             let desc = ""; |             embed.setTitle(`**Emotes** (Page 1 of ${pages})`); | ||||||
|             for (const emote of pagesSplit[page]) { |             const msg = await $.channel.send({embed}); | ||||||
|                 desc += `${emote} | ${emote.name}\n`; | 
 | ||||||
|             } |             $.paginate(msg, $.author.id, pages, (page) => { | ||||||
|             embed.setDescription(desc); |                 let desc = ""; | ||||||
|             msg.edit(embed); |                 for (const emote of sections[page]) { | ||||||
|         }); |                     desc += `${emote} ${emote.name} (**${emote.guild.name}**)\n`; | ||||||
|  |                 } | ||||||
|  |                 embed.setTitle(`**Emotes** (Page ${page + 1} of ${pages})`); | ||||||
|  |                 embed.setDescription(desc); | ||||||
|  |                 msg.edit(embed); | ||||||
|  |             }); | ||||||
|  |         } else { | ||||||
|  |             await $.channel.send({embed}); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         $.channel.send("No valid emotes found by that query."); | ||||||
|     } |     } | ||||||
| }); | } | ||||||
|  |  | ||||||
|  | @ -168,7 +168,7 @@ export function formatUTCTimestamp(now = new Date()) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function botHasPermission(guild: Guild | null, permission: number): boolean { | export function botHasPermission(guild: Guild | null, permission: number): boolean { | ||||||
|     return !!(client.user && guild?.members.resolve(client.user)?.hasPermission(permission)); |     return !!guild?.me?.hasPermission(permission); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function updateGlobalEmoteRegistry(): void { | export function updateGlobalEmoteRegistry(): void { | ||||||
|  | @ -212,20 +212,22 @@ $.paginate = async ( | ||||||
| 
 | 
 | ||||||
|         callback(page); |         callback(page); | ||||||
|     }; |     }; | ||||||
|  |     const BACKWARDS_EMOJI = "⬅️"; | ||||||
|  |     const FORWARDS_EMOJI = "➡️"; | ||||||
|     const handle = (emote: string, reacterID: string) => { |     const handle = (emote: string, reacterID: string) => { | ||||||
|         switch (emote) { |         switch (emote) { | ||||||
|             case "⬅️": |             case BACKWARDS_EMOJI: | ||||||
|                 turn(-1); |                 turn(-1); | ||||||
|                 break; |                 break; | ||||||
|             case "➡️": |             case FORWARDS_EMOJI: | ||||||
|                 turn(1); |                 turn(1); | ||||||
|                 break; |                 break; | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     // Listen for reactions and call the handler.
 |     // Listen for reactions and call the handler.
 | ||||||
|     await message.react("⬅️"); |     let backwardsReaction = await message.react(BACKWARDS_EMOJI); | ||||||
|     await message.react("➡️"); |     let forwardsReaction = await message.react(FORWARDS_EMOJI); | ||||||
|     eventListeners.set(message.id, handle); |     eventListeners.set(message.id, handle); | ||||||
|     await message.awaitReactions( |     await message.awaitReactions( | ||||||
|         (reaction, user) => { |         (reaction, user) => { | ||||||
|  | @ -244,8 +246,8 @@ $.paginate = async ( | ||||||
|     ); |     ); | ||||||
|     // When time's up, remove the bot's own reactions.
 |     // When time's up, remove the bot's own reactions.
 | ||||||
|     eventListeners.delete(message.id); |     eventListeners.delete(message.id); | ||||||
|     message.reactions.cache.get("⬅️")?.users.remove(message.author); |     backwardsReaction.users.remove(message.author); | ||||||
|     message.reactions.cache.get("➡️")?.users.remove(message.author); |     forwardsReaction.users.remove(message.author); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // Waits for the sender to either confirm an action or let it pass (and delete the message).
 | // Waits for the sender to either confirm an action or let it pass (and delete the message).
 | ||||||
|  |  | ||||||
|  | @ -4,11 +4,16 @@ import {hasPermission, getPermissionLevel, PermissionNames} from "../core/permis | ||||||
| import {Permissions} from "discord.js"; | import {Permissions} from "discord.js"; | ||||||
| import {getPrefix} from "../core/structures"; | import {getPrefix} from "../core/structures"; | ||||||
| import $, {replyEventListeners} from "../core/lib"; | import $, {replyEventListeners} from "../core/lib"; | ||||||
|  | import quote from "../modules/message_embed"; | ||||||
| 
 | 
 | ||||||
| export default new Event<"message">({ | export default new Event<"message">({ | ||||||
|     async on(message) { |     async on(message) { | ||||||
|         const commands = await loadableCommands; |         const commands = await loadableCommands; | ||||||
| 
 | 
 | ||||||
|  |         if (message.content.toLowerCase().includes("remember to drink water")) { | ||||||
|  |             message.react("🚱"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         // Message Setup //
 |         // Message Setup //
 | ||||||
|         if (message.author.bot) return; |         if (message.author.bot) return; | ||||||
| 
 | 
 | ||||||
|  | @ -24,6 +29,10 @@ export default new Event<"message">({ | ||||||
|         const clientUser = message.client.user; |         const clientUser = message.client.user; | ||||||
|         let usesBotSpecificPrefix = false; |         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 the client user exists, check if it starts with the bot-specific prefix.
 | ||||||
|         if (clientUser) { |         if (clientUser) { | ||||||
|             // If the prefix starts with the bot-specific prefix, go off that instead (these two options must mutually exclude each other).
 |             // If the prefix starts with the bot-specific prefix, go off that instead (these two options must mutually exclude each other).
 | ||||||
|  |  | ||||||
							
								
								
									
										32
									
								
								src/index.ts
									
										
									
									
									
								
							
							
						
						
									
										32
									
								
								src/index.ts
									
										
									
									
									
								
							|  | @ -1,13 +1,41 @@ | ||||||
| import {Client} from "discord.js"; | import * as discord 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 {loadEvents} from "./core/event"; | ||||||
| import "discord.js-lavalink-lib"; | import "discord.js-lavalink-lib"; | ||||||
| import LavalinkMusic from "discord.js-lavalink-lib"; | import LavalinkMusic from "discord.js-lavalink-lib"; | ||||||
| 
 | 
 | ||||||
|  | declare module "discord.js" { | ||||||
|  |     interface Presence { | ||||||
|  |         patch(data: any): void; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // The terrible hacks were written by none other than The Noble Programmer On The White PC.
 | ||||||
|  | 
 | ||||||
|  | // NOTE: Terrible hack ahead!!! In order to reduce the memory usage of the bot
 | ||||||
|  | // we only store the information from presences that we actually end up using,
 | ||||||
|  | // which currently is only the (online/idle/dnd/offline/...) status (see
 | ||||||
|  | // `src/commands/info.ts`). What data is retrieved from the `data` object
 | ||||||
|  | // (which contains the data received from the Gateway) and how can be seen
 | ||||||
|  | // here:
 | ||||||
|  | // <https://github.com/discordjs/discord.js/blob/cee6cf70ce76e9b06dc7f25bfd77498e18d7c8d4/src/structures/Presence.js#L81-L110>.
 | ||||||
|  | const oldPresencePatch = discord.Presence.prototype.patch; | ||||||
|  | discord.Presence.prototype.patch = function patch(data: any) { | ||||||
|  |     oldPresencePatch.call(this, {status: data.status}); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| // 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 discord.Client(); | ||||||
|  | 
 | ||||||
|  | // NOTE: Terrible hack continued!!! Unfortunately we can't receive the presence
 | ||||||
|  | // data at all when the GUILD_PRESENCES intent is disabled, so while we do
 | ||||||
|  | // waste network bandwidth and the CPU time for decoding the incoming packets,
 | ||||||
|  | // the function which handles those packets is NOP-ed out, which, among other
 | ||||||
|  | // things, skips the code which caches the referenced users in the packet. See
 | ||||||
|  | // <https://github.com/discordjs/discord.js/blob/cee6cf70ce76e9b06dc7f25bfd77498e18d7c8d4/src/client/actions/PresenceUpdate.js#L7-L41>.
 | ||||||
|  | (client["actions"] as any)["PresenceUpdate"].handle = () => {}; | ||||||
| 
 | 
 | ||||||
| (client as any).music = LavalinkMusic(client, { | (client as any).music = LavalinkMusic(client, { | ||||||
|     lavalink: { |     lavalink: { | ||||||
|  |  | ||||||
							
								
								
									
										60
									
								
								src/modules/message_embed.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/modules/message_embed.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | ||||||
|  | import { client } from '..' | ||||||
|  | import { Message, TextChannel, APIMessage, MessageEmbed } from 'discord.js' | ||||||
|  | import { getPrefix } from '../core/structures' | ||||||
|  | import { DiscordAPIError } from 'discord.js' | ||||||
|  | 
 | ||||||
|  | export default async function quote(message: Message) { | ||||||
|  |     if (message.author.bot) return | ||||||
|  |     // const message_link_regex = message.content.match(/(!)?https?:\/\/\w+\.com\/channels\/(\d+)\/(\d+)\/(\d+)/)
 | ||||||
|  |     const message_link_regex = message.content.match(/([<!]?)https?:\/\/(?:ptb\.|canary\.|)discord(?:app)?\.com\/channels\/(\d+)\/(\d+)\/(\d+)(>?)/) | ||||||
|  | 
 | ||||||
|  |     if (message_link_regex == null) return | ||||||
|  |     const [, char, guildID, channelID, messageID] = message_link_regex | ||||||
|  | 
 | ||||||
|  |     if (char || message.content.startsWith(getPrefix(message.guild))) return | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |         const channel = client.guilds.cache.get(guildID)?.channels.cache.get(channelID) as TextChannel | ||||||
|  |         const link_message = await channel.messages.fetch(messageID) | ||||||
|  | 
 | ||||||
|  |         let rtmsg: string | APIMessage = '' | ||||||
|  |         if (link_message.cleanContent) { | ||||||
|  |             rtmsg = new APIMessage(message.channel as TextChannel, { | ||||||
|  |                 content: link_message.cleanContent, | ||||||
|  |                 disableMentions: 'all', | ||||||
|  |                 files: link_message.attachments.array() | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const embeds = [ | ||||||
|  |             ...link_message.embeds.filter(v => v.type == 'rich'), | ||||||
|  |             ...link_message.attachments.values() | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|  |         /// @ts-ignore
 | ||||||
|  |         if (!link_message.cleanContent && embeds.empty) { | ||||||
|  |             const Embed = new MessageEmbed() | ||||||
|  |                 .setDescription('🚫 The message is empty.') | ||||||
|  |             return message.channel.send(Embed) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const infoEmbed = new MessageEmbed() | ||||||
|  |             .setAuthor( | ||||||
|  |                 link_message.author.username, | ||||||
|  |                 link_message.author.displayAvatarURL({format: 'png', dynamic: true, size: 4096})) | ||||||
|  |             .setTimestamp(link_message.createdTimestamp) | ||||||
|  |             .setDescription(`${link_message.cleanContent}\n\nSent in **${link_message.guild?.name}** | <#${link_message.channel.id}> ([link](https://discord.com/channels/${guildID}/${channelID}/${messageID}))`); | ||||||
|  |             if (link_message.attachments.size !== 0) { | ||||||
|  |                 const image = link_message.attachments.first(); | ||||||
|  |                 /// @ts-ignore
 | ||||||
|  |                 infoEmbed.setImage(image.url); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         await message.channel.send(infoEmbed) | ||||||
|  |     } catch (error) { | ||||||
|  |         if (error instanceof DiscordAPIError) { | ||||||
|  |             message.channel.send("I don't have access to this channel, or something else went wrong.") | ||||||
|  |         } | ||||||
|  |         return console.error(error) | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue