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", | ||||
|         "inquirer": "^7.3.3", | ||||
|         "moment": "^2.29.1", | ||||
|         "ms": "^2.1.3", | ||||
|         "os": "^0.1.1" | ||||
|         "ms": "^2.1.3" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@types/glob": "^7.1.3", | ||||
|  |  | |||
|  | @ -124,14 +124,15 @@ export default new Command({ | |||
|             number: new Command({ | ||||
|                 description: "Amount of messages to delete.", | ||||
|                 async run($: CommonLibrary): Promise<any> { | ||||
|                     if ($.channel.type === "dm") { | ||||
|                         await $.channel.send("Can't clear messages in the DMs!"); | ||||
|                         return; | ||||
|                     } | ||||
|                     $.message.delete(); | ||||
|                     const fetched = await $.channel.messages.fetch({ | ||||
|                         limit: $.args[0] | ||||
|                     }); | ||||
|                     $.channel | ||||
|                         /// @ts-ignore
 | ||||
|                         .bulkDelete(fetched) | ||||
|                         .catch((error: any) => $.channel.send(`Error: ${error}`)); | ||||
|                     await $.channel.bulkDelete(fetched); | ||||
|                 } | ||||
|             }) | ||||
|         }), | ||||
|  | @ -157,8 +158,7 @@ export default new Command({ | |||
|             permission: Command.PERMISSIONS.BOT_SUPPORT, | ||||
|             async run($: CommonLibrary): Promise<any> { | ||||
|                 const nickName = $.args.join(" "); | ||||
|                 const trav = $.guild?.members.cache.find((member) => member.id === $.client.user?.id); | ||||
|                 await trav?.setNickname(nickName); | ||||
|                 await $.guild?.me?.setNickname(nickName); | ||||
|                 if (botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES)) | ||||
|                     $.message.delete({timeout: 5000}).catch($.handler.bind($)); | ||||
|                 $.channel.send(`Nickname set to \`${nickName}\``).then((m) => m.delete({timeout: 5000})); | ||||
|  |  | |||
|  | @ -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 { | ||||
|     if (guild?.id === "637512823676600330" || process.argv[2] === "dev") return true; | ||||
|     if (guild?.id === "637512823676600330" && channel?.id === "669464416420364288" || process.argv[2] === "dev") return true; | ||||
|     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; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -6,6 +6,9 @@ import {CommonLibrary, formatBytes, trimArray} from "../core/lib"; | |||
| import {verificationLevels, filterLevels, regions, flags} from "../defs/info"; | ||||
| import moment from "moment"; | ||||
| import utc from "moment"; | ||||
| import {Guild} from "discord.js"; | ||||
| 
 | ||||
| const {version} = require("../../package.json"); | ||||
| 
 | ||||
| export default new Command({ | ||||
|     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> { | ||||
|                 const core = os.cpus()[0]; | ||||
|                 const embed = new MessageEmbed() | ||||
|                     .setThumbnail( | ||||
|                         /// @ts-ignore
 | ||||
|                         $.client.user?.displayAvatarURL({ | ||||
|                             dynamic: true, | ||||
|                             size: 2048 | ||||
|                         }) | ||||
|                     ) | ||||
|                     .setColor($.guild?.me?.displayHexColor || "BLUE") | ||||
|                     .addField("General", [ | ||||
|                         `**❯ Client:** ${$.client.user?.tag} (${$.client.user?.id})`, | ||||
|  | @ -66,84 +62,59 @@ export default new Command({ | |||
|                         `\u3000 • Speed: ${core.speed}MHz`, | ||||
|                         `**❯ Memory:**`, | ||||
|                         `\u3000 • Total: ${formatBytes(process.memoryUsage().heapTotal)}`, | ||||
|                         `\u3000 • Used: ${formatBytes(process.memoryUsage().heapTotal)}` | ||||
|                         `\u3000 • Used: ${formatBytes(process.memoryUsage().heapUsed)}` | ||||
|                     ]) | ||||
|                     .setTimestamp(); | ||||
|                 const avatarURL = $.client.user?.displayAvatarURL({ | ||||
|                     dynamic: true, | ||||
|                     size: 2048 | ||||
|                 }); | ||||
|                 if (avatarURL) embed.setThumbnail(avatarURL); | ||||
|                 $.channel.send(embed); | ||||
|             } | ||||
|         }), | ||||
|         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> { | ||||
|                 if ($.guild) { | ||||
|                     const roles = $.guild.roles.cache | ||||
|                         .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); | ||||
|                     $.channel.send(await getGuildInfo($.guild, $.guild)); | ||||
|                 } else { | ||||
|                     $.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({ | ||||
|         description: "Displays info about mentioned user.", | ||||
|         async run($: CommonLibrary): Promise<any> { | ||||
|             // 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) | ||||
|                 return $.channel.send( | ||||
|  | @ -154,8 +125,7 @@ export default new Command({ | |||
|                 .sort((a: {position: number}, b: {position: number}) => b.position - a.position) | ||||
|                 .map((role: {toString: () => any}) => role.toString()) | ||||
|                 .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()) as UserFlags).toArray(); | ||||
|             const userFlags = (await member.user.fetchFlags()).toArray(); | ||||
| 
 | ||||
|             const embed = new MessageEmbed() | ||||
|                 .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 (!$.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."); | ||||
| 
 | ||||
|         if ($.args.length === 0) return $.channel.send("Please provide a new voice channel name."); | ||||
| 
 | ||||
|         const changeVC = $.guild.channels.resolve(voiceChannel.id); | ||||
|         $.channel | ||||
|             .send(`Changed channel name from "${voiceChannel}" to "${$.args.join(" ")}".`) | ||||
|             /// @ts-ignore
 | ||||
|             .then(changeVC?.setName($.args.join(" "))); | ||||
|         const prevName = voiceChannel.name; | ||||
|         const newName = $.args.join(" "); | ||||
|         await voiceChannel.setName(newName); | ||||
|         await $.channel.send(`Changed channel name from "${prevName}" to "${newName}".`); | ||||
|     } | ||||
| }); | ||||
|  |  | |||
|  | @ -12,7 +12,6 @@ export default new Command({ | |||
|         async run({guild, channel, message, args}) { | ||||
|             let output = ""; | ||||
|             for (const query of args) output += queryClosestEmoteByName(query).toString(); | ||||
|             if (botHasPermission(guild, Permissions.FLAGS.MANAGE_MESSAGES)) message.delete(); | ||||
|             channel.send(output); | ||||
|         } | ||||
|     }) | ||||
|  |  | |||
|  | @ -1,32 +1,117 @@ | |||
| import {GuildEmoji} from "discord.js"; | ||||
| import {MessageEmbed} from "discord.js"; | ||||
| import Command from "../../core/command"; | ||||
| import {CommonLibrary} from "../../core/lib"; | ||||
| import vm from "vm"; | ||||
| 
 | ||||
| const REGEX_TIMEOUT_MS = 1000; | ||||
| 
 | ||||
| export default new Command({ | ||||
|     description: "Lists all emotes the bot has in it's registry,", | ||||
|     endpoint: true, | ||||
|     usage: "<regex pattern> (-flags)", | ||||
|     async run($: CommonLibrary): Promise<any> { | ||||
|         const nsfw: string | string[] = []; | ||||
|         const pages = $.client.emojis.cache.filter((x) => !nsfw.includes(x.guild.id), this).array(); | ||||
|         const pagesSplit = $(pages).split(20); | ||||
|         $.log(pagesSplit); | ||||
|         var embed = new MessageEmbed().setTitle("**Emoji list!**").setColor("AQUA"); | ||||
|         displayEmoteList($, $.client.emojis.cache.array()); | ||||
|     }, | ||||
|     any: new Command({ | ||||
|         description: | ||||
|             "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", | ||||
|         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]; | ||||
| 
 | ||||
|                 displayEmoteList($, $.client.emojis.cache.filter((emote) => emote.guild.id === guildID).array()); | ||||
|             } 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 = ""; | ||||
| 
 | ||||
|         for (const emote of pagesSplit[0]) { | ||||
|             desc += `${emote} | ${emote.name}\n`; | ||||
|     // 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); | ||||
| 
 | ||||
|         if (pages > 1) { | ||||
|             embed.setTitle(`**Emotes** (Page 1 of ${pages})`); | ||||
|             const msg = await $.channel.send({embed}); | ||||
| 
 | ||||
|         $.paginate(msg, $.author.id, pages.length, (page) => { | ||||
|             $.paginate(msg, $.author.id, pages, (page) => { | ||||
|                 let desc = ""; | ||||
|             for (const emote of pagesSplit[page]) { | ||||
|                 desc += `${emote} | ${emote.name}\n`; | ||||
|                 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 { | ||||
|     return !!(client.user && guild?.members.resolve(client.user)?.hasPermission(permission)); | ||||
|     return !!guild?.me?.hasPermission(permission); | ||||
| } | ||||
| 
 | ||||
| export function updateGlobalEmoteRegistry(): void { | ||||
|  | @ -212,20 +212,22 @@ $.paginate = async ( | |||
| 
 | ||||
|         callback(page); | ||||
|     }; | ||||
|     const BACKWARDS_EMOJI = "⬅️"; | ||||
|     const FORWARDS_EMOJI = "➡️"; | ||||
|     const handle = (emote: string, reacterID: string) => { | ||||
|         switch (emote) { | ||||
|             case "⬅️": | ||||
|             case BACKWARDS_EMOJI: | ||||
|                 turn(-1); | ||||
|                 break; | ||||
|             case "➡️": | ||||
|             case FORWARDS_EMOJI: | ||||
|                 turn(1); | ||||
|                 break; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // Listen for reactions and call the handler.
 | ||||
|     await message.react("⬅️"); | ||||
|     await message.react("➡️"); | ||||
|     let backwardsReaction = await message.react(BACKWARDS_EMOJI); | ||||
|     let forwardsReaction = await message.react(FORWARDS_EMOJI); | ||||
|     eventListeners.set(message.id, handle); | ||||
|     await message.awaitReactions( | ||||
|         (reaction, user) => { | ||||
|  | @ -244,8 +246,8 @@ $.paginate = async ( | |||
|     ); | ||||
|     // When time's up, remove the bot's own reactions.
 | ||||
|     eventListeners.delete(message.id); | ||||
|     message.reactions.cache.get("⬅️")?.users.remove(message.author); | ||||
|     message.reactions.cache.get("➡️")?.users.remove(message.author); | ||||
|     backwardsReaction.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).
 | ||||
|  |  | |||
|  | @ -4,11 +4,16 @@ import {hasPermission, getPermissionLevel, PermissionNames} from "../core/permis | |||
| import {Permissions} from "discord.js"; | ||||
| import {getPrefix} from "../core/structures"; | ||||
| import $, {replyEventListeners} from "../core/lib"; | ||||
| import quote from "../modules/message_embed"; | ||||
| 
 | ||||
| export default new Event<"message">({ | ||||
|     async on(message) { | ||||
|         const commands = await loadableCommands; | ||||
| 
 | ||||
|         if (message.content.toLowerCase().includes("remember to drink water")) { | ||||
|             message.react("🚱"); | ||||
|         } | ||||
| 
 | ||||
|         // Message Setup //
 | ||||
|         if (message.author.bot) return; | ||||
| 
 | ||||
|  | @ -24,6 +29,10 @@ export default new Event<"message">({ | |||
|         const clientUser = message.client.user; | ||||
|         let usesBotSpecificPrefix = false; | ||||
| 
 | ||||
|         if (!message.content.startsWith(prefix)) { | ||||
|             return quote(message); | ||||
|         } | ||||
| 
 | ||||
|         // If the client user exists, check if it starts with the bot-specific prefix.
 | ||||
|         if (clientUser) { | ||||
|             // If the prefix starts with the bot-specific prefix, go off that instead (these two options must mutually exclude each other).
 | ||||
|  |  | |||
							
								
								
									
										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 {Config} from "./core/structures"; | ||||
| import {loadEvents} from "./core/event"; | ||||
| import "discord.js-lavalink-lib"; | ||||
| import LavalinkMusic from "discord.js-lavalink-lib"; | ||||
| 
 | ||||
| declare module "discord.js" { | ||||
|     interface Presence { | ||||
|         patch(data: any): void; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // The terrible hacks were written by none other than The Noble Programmer On The White PC.
 | ||||
| 
 | ||||
| // NOTE: Terrible hack ahead!!! In order to reduce the memory usage of the bot
 | ||||
| // we only store the information from presences that we actually end up using,
 | ||||
| // which currently is only the (online/idle/dnd/offline/...) status (see
 | ||||
| // `src/commands/info.ts`). What data is retrieved from the `data` object
 | ||||
| // (which contains the data received from the Gateway) and how can be seen
 | ||||
| // here:
 | ||||
| // <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 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, { | ||||
|     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