From 417bfc8a18745efdd9e2890cc944c02a50a244a1 Mon Sep 17 00:00:00 2001 From: Dmytro Meleshko Date: Fri, 29 Jan 2021 18:21:06 +0200 Subject: [PATCH 1/5] get rid of the remaining calls to BaseManager#resolve to make all cache access explicit --- src/commands/info.ts | 2 +- src/commands/utilities/desc.ts | 11 +++++------ src/core/lib.ts | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/commands/info.ts b/src/commands/info.ts index a85a2e5..2de35d0 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -145,7 +145,7 @@ export default new Command({ description: "Displays info about mentioned user.", async run($: CommonLibrary): Promise { // 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( diff --git a/src/commands/utilities/desc.ts b/src/commands/utilities/desc.ts index 467053c..c5e75f3 100644 --- a/src/commands/utilities/desc.ts +++ b/src/commands/utilities/desc.ts @@ -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}".`); } }); diff --git a/src/core/lib.ts b/src/core/lib.ts index 42852d9..73231b4 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -172,7 +172,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 { From 593efb3602d5abcf201bf59b039f333b1f3f13ff Mon Sep 17 00:00:00 2001 From: Dmytro Meleshko Date: Fri, 29 Jan 2021 19:07:39 +0200 Subject: [PATCH 2/5] raise the ES target level and enable sourcemaps in tsconfig.json --- src/core/command.ts | 18 +++++++++++------- tsconfig.json | 5 +++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/core/command.ts b/src/core/command.ts index 7ba0f02..b5fa826 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -182,12 +182,16 @@ export async function loadCommands(): Promise> { if (cmd.isDirectory()) { if (cmd.name === "subcommands") continue; else $.warn(`You can't have multiple levels of directories! From: "dist/commands/${cmd.name}"`); - } else loadCommand(cmd.name, list, selected.name); + } else if (cmd.name.endsWith(".js")) { + loadCommand(cmd.name, list, selected.name); + } } subdir.close(); categories.set(category, list); - } else loadCommand(selected.name, listMisc); + } else if (selected.name.endsWith(".js")) { + loadCommand(selected.name, listMisc); + } } dir.close(); @@ -236,7 +240,7 @@ export default new Command({ permission: null, aliases: [], async run($: CommonLibrary): Promise { - + }, subcommands: { layer: new Command({ @@ -246,7 +250,7 @@ export default new Command({ permission: null, aliases: [], async run($: CommonLibrary): Promise { - + } }) }, @@ -256,7 +260,7 @@ export default new Command({ usage: '', permission: null, async run($: CommonLibrary): Promise { - + } }), number: new Command({ @@ -265,7 +269,7 @@ export default new Command({ usage: '', permission: null, async run($: CommonLibrary): Promise { - + } }), any: new Command({ @@ -274,7 +278,7 @@ export default new Command({ usage: '', permission: null, async run($: CommonLibrary): Promise { - + } }) });`; diff --git a/tsconfig.json b/tsconfig.json index ba4dd11..34245ef 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "rootDir": "src", "outDir": "dist", - "target": "ES6", + "target": "es2019", "module": "CommonJS", "moduleResolution": "node", "esModuleInterop": true, @@ -11,7 +11,8 @@ "strictNullChecks": true, "strictFunctionTypes": true, "strictPropertyInitialization": true, - "removeComments": true + "removeComments": true, + "sourceMap": true }, "exclude": ["test"] } From 0cba164f3d33b646ea118c684969a0d6c2b086ff Mon Sep 17 00:00:00 2001 From: Dmytro Meleshko Date: Fri, 29 Jan 2021 19:15:18 +0200 Subject: [PATCH 3/5] get rid of @ts-ignore or something --- src/commands/admin.ts | 9 +++++---- src/commands/info.ts | 24 +++++++++--------------- src/core/lib.ts | 1 - 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 47279f9..35c3ff2 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -124,14 +124,15 @@ export default new Command({ number: new Command({ description: "Amount of messages to delete.", async run($: CommonLibrary): Promise { + 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); } }) }), diff --git a/src/commands/info.ts b/src/commands/info.ts index 2de35d0..56b5277 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -1,6 +1,4 @@ import {MessageEmbed, version as djsversion} from "discord.js"; -/// @ts-ignore -import {version} from "../../package.json"; import ms from "ms"; import os from "os"; import Command from "../core/command"; @@ -9,6 +7,8 @@ import {verificationLevels, filterLevels, regions, flags} from "../defs/info"; import moment from "moment"; import utc from "moment"; +const {version} = require("../../package.json"); + export default new Command({ description: "Command to provide all sorts of info about the current server, a user, etc.", run: "Please provide an argument.\nFor help, run `%prefix%help info`.", @@ -36,13 +36,6 @@ export default new Command({ async run($: CommonLibrary): Promise { 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})`, @@ -71,6 +64,11 @@ export default new Command({ `\u3000 • Used: ${formatBytes(process.memoryUsage().heapUsed)}` ]) .setTimestamp(); + const avatarURL = $.client.user?.displayAvatarURL({ + dynamic: true, + size: 2048 + }); + if (avatarURL) embed.setThumbnail(avatarURL); $.channel.send(embed); } }), @@ -156,8 +154,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})) @@ -183,10 +180,7 @@ export default new Command({ `**❯ Server Join Date:** ${moment(member.joinedAt).format("LL LTS")}`, `**❯ Hoist Role:** ${member.roles.hoist ? member.roles.hoist.name : "None"}`, `**❯ Roles:** [${roles.length}]: ${ - roles.length == 0 ? "None" - : roles.length <= 10 - ? roles.join(", ") - : trimArray(roles).join(", ") + roles.length == 0 ? "None" : roles.length <= 10 ? roles.join(", ") : trimArray(roles).join(", ") }` ]); $.channel.send(embed); diff --git a/src/core/lib.ts b/src/core/lib.ts index 73231b4..0f5f193 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -554,7 +554,6 @@ export abstract class GenericStructure { public save(asynchronous = true) { const tag = this.__meta__; - /// @ts-ignore delete this.__meta__; FileManager.write(tag, this, asynchronous); this.__meta__ = tag; From 303e81cc3708c2e65c800d94f78d1a1a86ee1a06 Mon Sep 17 00:00:00 2001 From: Dmytro Meleshko Date: Fri, 29 Jan 2021 21:12:53 +0200 Subject: [PATCH 4/5] reduce the amount of cache lookups --- src/commands/admin.ts | 3 +-- src/commands/info.ts | 5 ++++- src/core/lib.ts | 16 +++++++++------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 35c3ff2..f174750 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -158,8 +158,7 @@ export default new Command({ permission: Command.PERMISSIONS.BOT_SUPPORT, async run($: CommonLibrary): Promise { 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})); diff --git a/src/commands/info.ts b/src/commands/info.ts index 56b5277..9b7b71a 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -76,10 +76,13 @@ export default new Command({ description: "Displays info about the current guild.", async run($: CommonLibrary): Promise { if ($.guild) { + 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 members = $.guild.members.cache; const channels = $.guild.channels.cache; const emojis = $.guild.emojis.cache; const iconURL = $.guild.iconURL({dynamic: true}); diff --git a/src/core/lib.ts b/src/core/lib.ts index 0f5f193..4cadf4d 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -172,7 +172,7 @@ export function formatUTCTimestamp(now = new Date()) { } export function botHasPermission(guild: Guild | null, permission: number): boolean { - return !!(guild?.me?.hasPermission(permission)); + return !!guild?.me?.hasPermission(permission); } export function updateGlobalEmoteRegistry(): void { @@ -216,20 +216,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) => { @@ -248,8 +250,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). From e22250b3f159e444f175c7e548bd50c31e4c04fe Mon Sep 17 00:00:00 2001 From: Dmytro Meleshko Date: Fri, 29 Jan 2021 21:14:31 +0200 Subject: [PATCH 5/5] introduce the terrible hack for reducing memory usage --- src/index.ts | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9721784..fa72c21 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import {Client} from "discord.js"; +import * as discord from "discord.js"; import setup from "./setup"; import {Config} from "./core/structures"; import {loadCommands} from "./core/command"; @@ -6,9 +6,37 @@ import {loadEvents} from "./core/event"; import "discord.js-lavalink-lib"; import LavalinkMusic from "discord.js-lavalink-lib"; +declare module "discord.js" { + interface Presence { + patch(data: any): void; + } +} + +// The terrible hacks were written by none other than The Noble Programmer On The White PC. + +// NOTE: Terrible hack ahead!!! In order to reduce the memory usage of the bot +// we only store the information from presences that we actually end up using, +// which currently is only the (online/idle/dnd/offline/...) status (see +// `src/commands/info.ts`). What data is retrieved from the `data` object +// (which contains the data received from the Gateway) and how can be seen +// here: +// . +const oldPresencePatch = discord.Presence.prototype.patch; +discord.Presence.prototype.patch = function patch(data: any) { + oldPresencePatch.call(this, {status: data.status}); +}; + // 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 +// . +(client["actions"] as any)["PresenceUpdate"].handle = () => {}; (client as any).music = LavalinkMusic(client, { lavalink: {