Lots of slash command work, added workaround for eris-fleet request debugging
This commit is contained in:
		
							parent
							
								
									b8aeb6625a
								
							
						
					
					
						commit
						2cffdf6628
					
				
					 61 changed files with 417 additions and 244 deletions
				
			
		
							
								
								
									
										2
									
								
								app.js
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								app.js
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -111,7 +111,7 @@ const Admiral = new Fleet({
 | 
			
		|||
      requestTimeout: 30000
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  useCentralRequestHandler: true,
 | 
			
		||||
  useCentralRequestHandler: process.env.DEBUG_LOG ? false : true, // workaround for eris-fleet weirdness
 | 
			
		||||
  services: [
 | 
			
		||||
    { name: "prometheus", ServiceWorker: PrometheusWorker },
 | 
			
		||||
    { name: "image", ServiceWorker: ImageWorker }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,21 +8,33 @@ class Command {
 | 
			
		|||
    this.args = options.args;
 | 
			
		||||
    if (options.type === "classic") {
 | 
			
		||||
      this.message = options.message;
 | 
			
		||||
      this.channel = options.message.channel;
 | 
			
		||||
      this.author = options.message.author;
 | 
			
		||||
      this.content = options.content;
 | 
			
		||||
      this.specialArgs = options.specialArgs;
 | 
			
		||||
      this.reference = {
 | 
			
		||||
        messageReference: {
 | 
			
		||||
          channelID: this.message.channel.id,
 | 
			
		||||
          channelID: this.channel.id,
 | 
			
		||||
          messageID: this.message.id,
 | 
			
		||||
          guildID: this.message.channel.guild ? this.message.channel.guild.id : undefined,
 | 
			
		||||
          guildID: this.channel.guild ? this.channel.guild.id : undefined,
 | 
			
		||||
          failIfNotExists: false
 | 
			
		||||
        },
 | 
			
		||||
        allowedMentions: {
 | 
			
		||||
          repliedUser: false
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
    } else {
 | 
			
		||||
    } else if (options.type === "application") {
 | 
			
		||||
      this.interaction = options.interaction;
 | 
			
		||||
      this.channel = options.interaction.channel;
 | 
			
		||||
      this.author = options.interaction.guildID ? options.interaction.member : options.interaction.user;
 | 
			
		||||
      if (options.interaction.data.options) {
 | 
			
		||||
        this.specialArgs = this.options = options.interaction.data.options.reduce((obj, item) => {
 | 
			
		||||
          obj[item.name] = item.value;
 | 
			
		||||
          return obj;
 | 
			
		||||
        }, {});
 | 
			
		||||
      } else {
 | 
			
		||||
        this.specialArgs = this.options = {};
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -32,7 +44,7 @@ class Command {
 | 
			
		|||
 | 
			
		||||
  async acknowledge() {
 | 
			
		||||
    if (this.type === "classic") {
 | 
			
		||||
      await this.message.channel.sendTyping();
 | 
			
		||||
      await this.client.sendChannelTyping(this.channel.id);
 | 
			
		||||
    } else {
 | 
			
		||||
      await this.interaction.acknowledge();
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,18 +33,43 @@ class ImageCommand extends Command {
 | 
			
		|||
      ]
 | 
			
		||||
    };*/
 | 
			
		||||
 | 
			
		||||
  constructor(client, cluster, worker, ipc, options) {
 | 
			
		||||
    super(client, cluster, worker, ipc, options);
 | 
			
		||||
    this.flags = [];
 | 
			
		||||
    if (this.constructor.requiresText || this.constructor.textOptional) {
 | 
			
		||||
      this.flags.push({
 | 
			
		||||
        name: "text",
 | 
			
		||||
        type: 3,
 | 
			
		||||
        description: "The text to put on the image",
 | 
			
		||||
        required: !this.constructor.textOptional
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    if (this.constructor.requiresImage) {
 | 
			
		||||
      this.flags.push({
 | 
			
		||||
        name: "image",
 | 
			
		||||
        type: 11,
 | 
			
		||||
        description: "An image/GIF attachment"
 | 
			
		||||
      }, {
 | 
			
		||||
        name: "link",
 | 
			
		||||
        type: 3,
 | 
			
		||||
        description: "An image/GIF URL"
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async criteria() {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async run() {
 | 
			
		||||
    const timestamp = this.type === "classic" ? this.message.createdAt : Math.floor((this.interaction.id / 4194304) + 1420070400000);
 | 
			
		||||
    // check if this command has already been run in this channel with the same arguments, and we are awaiting its result
 | 
			
		||||
    // if so, don't re-run it
 | 
			
		||||
    if (runningCommands.has(this.message.author.id) && (new Date(runningCommands.get(this.message.author.id)) - new Date(this.message.createdAt)) < 5000) {
 | 
			
		||||
    if (runningCommands.has(this.author.id) && (new Date(runningCommands.get(this.author.id)) - new Date(timestamp)) < 5000) {
 | 
			
		||||
      return "Please slow down a bit.";
 | 
			
		||||
    }
 | 
			
		||||
    // before awaiting the command result, add this command to the set of running commands
 | 
			
		||||
    runningCommands.set(this.message.author.id, this.message.createdAt);
 | 
			
		||||
    runningCommands.set(this.author.id, timestamp);
 | 
			
		||||
 | 
			
		||||
    const magickParams = {
 | 
			
		||||
      cmd: this.constructor.command,
 | 
			
		||||
| 
						 | 
				
			
			@ -53,15 +78,15 @@ class ImageCommand extends Command {
 | 
			
		|||
 | 
			
		||||
    if (this.constructor.requiresImage) {
 | 
			
		||||
      try {
 | 
			
		||||
        const image = await imageDetect(this.client, this.message, true);
 | 
			
		||||
        const image = await imageDetect(this.client, this.message, this.interaction, this.options, true);
 | 
			
		||||
        if (image === undefined) {
 | 
			
		||||
          runningCommands.delete(this.message.author.id);
 | 
			
		||||
          runningCommands.delete(this.author.id);
 | 
			
		||||
          return this.constructor.noImage;
 | 
			
		||||
        } else if (image.type === "large") {
 | 
			
		||||
          runningCommands.delete(this.message.author.id);
 | 
			
		||||
          runningCommands.delete(this.author.id);
 | 
			
		||||
          return "That image is too large (>= 25MB)! Try using a smaller image.";
 | 
			
		||||
        } else if (image.type === "tenorlimit") {
 | 
			
		||||
          runningCommands.delete(this.message.author.id);
 | 
			
		||||
          runningCommands.delete(this.author.id);
 | 
			
		||||
          return "I've been rate-limited by Tenor. Please try uploading your GIF elsewhere.";
 | 
			
		||||
        }
 | 
			
		||||
        magickParams.path = image.path;
 | 
			
		||||
| 
						 | 
				
			
			@ -70,15 +95,16 @@ class ImageCommand extends Command {
 | 
			
		|||
        magickParams.params.delay = image.delay ?? 0;
 | 
			
		||||
        if (this.constructor.requiresGIF) magickParams.onlyGIF = true;
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        runningCommands.delete(this.message.author.id);
 | 
			
		||||
        runningCommands.delete(this.author.id);
 | 
			
		||||
        throw e;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this.constructor.requiresText) {
 | 
			
		||||
      if (this.args.length === 0 || !await this.criteria(this.args)) {
 | 
			
		||||
        runningCommands.delete(this.message.author.id);
 | 
			
		||||
      const text = this.type === "classic" ? this.args : this.options.text;
 | 
			
		||||
      if (text.length === 0 || !await this.criteria(text)) {
 | 
			
		||||
        runningCommands.delete(this.author.id);
 | 
			
		||||
        return this.constructor.noText;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -93,7 +119,7 @@ class ImageCommand extends Command {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    let status;
 | 
			
		||||
    if (magickParams.params.type === "image/gif") {
 | 
			
		||||
    if (magickParams.params.type === "image/gif" && this.type === "classic") {
 | 
			
		||||
      status = await this.processMessage(this.message);
 | 
			
		||||
    } else {
 | 
			
		||||
      this.acknowledge();
 | 
			
		||||
| 
						 | 
				
			
			@ -113,7 +139,7 @@ class ImageCommand extends Command {
 | 
			
		|||
      throw e;
 | 
			
		||||
    } finally {
 | 
			
		||||
      if (status && (status.channel.messages ? status.channel.messages.has(status.id) : await this.client.getMessage(status.channel.id, status.id).catch(() => undefined))) await status.delete();
 | 
			
		||||
      runningCommands.delete(this.message.author.id);
 | 
			
		||||
      runningCommands.delete(this.author.id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -124,6 +150,7 @@ class ImageCommand extends Command {
 | 
			
		|||
 | 
			
		||||
  static requiresImage = true;
 | 
			
		||||
  static requiresText = false;
 | 
			
		||||
  static textOptional = false;
 | 
			
		||||
  static requiresGIF = false;
 | 
			
		||||
  static noImage = "You need to provide an image/GIF!";
 | 
			
		||||
  static noText = "You need to provide some text!";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,10 +2,10 @@ import Command from "./command.js";
 | 
			
		|||
import { players, queues } from "../utils/soundplayer.js";
 | 
			
		||||
 | 
			
		||||
class MusicCommand extends Command {
 | 
			
		||||
  constructor(client, cluster, worker, ipc, message, args, content, specialArgs) {
 | 
			
		||||
    super(client, cluster, worker, ipc, message, args, content, specialArgs);
 | 
			
		||||
    this.connection = players.get(message.channel.guild.id);
 | 
			
		||||
    this.queue = queues.get(message.channel.guild.id);
 | 
			
		||||
  constructor(client, cluster, worker, ipc, options) {
 | 
			
		||||
    super(client, cluster, worker, ipc, options);
 | 
			
		||||
    this.connection = players.get(options.message.channel.guild.id);
 | 
			
		||||
    this.queue = queues.get(options.message.channel.guild.id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static requires = ["sound"];
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,7 @@ class CowsayCommand extends Command {
 | 
			
		|||
  static description = "Makes an ASCII cow say a message";
 | 
			
		||||
  static aliases = ["cow"];
 | 
			
		||||
  static arguments = ["{cow}", "[text]"];
 | 
			
		||||
  static slashAllowed = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default CowsayCommand;
 | 
			
		||||
| 
						 | 
				
			
			@ -9,6 +9,7 @@ class FullwidthCommand extends Command {
 | 
			
		|||
  static description = "Converts a message to fullwidth/aesthetic text";
 | 
			
		||||
  static aliases = ["aesthetic", "aesthetics", "aes"];
 | 
			
		||||
  static arguments = ["[text]"];
 | 
			
		||||
  static slashAllowed = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default FullwidthCommand;
 | 
			
		||||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ import ImageCommand from "../../classes/imageCommand.js";
 | 
			
		|||
class HomebrewCommand extends ImageCommand {
 | 
			
		||||
  params() {
 | 
			
		||||
    return {
 | 
			
		||||
      caption: this.args.join(" ").toLowerCase().replaceAll("\n", " ")
 | 
			
		||||
      caption: (this.type === "classic" ? this.args.join(" ") : this.options.text).toLowerCase().replaceAll("\n", " ")
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,7 @@ class MCCommand extends Command {
 | 
			
		|||
  static description = "Generates a Minecraft achievement image";
 | 
			
		||||
  static aliases = ["ach", "achievement", "minecraft"];
 | 
			
		||||
  static arguments = ["[text]"];
 | 
			
		||||
  static slashAllowed = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default MCCommand;
 | 
			
		||||
| 
						 | 
				
			
			@ -27,6 +27,7 @@ class RetroCommand extends ImageCommand {
 | 
			
		|||
  static requiresText = true;
 | 
			
		||||
  static noText = "You need to provide some text to make retro!";
 | 
			
		||||
  static command = "retro";
 | 
			
		||||
  static slashAllowed = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default RetroCommand;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,6 +29,7 @@ class RPSCommand extends Command {
 | 
			
		|||
  static description = "Plays rock, paper, scissors with me";
 | 
			
		||||
  static aliases = ["rockpaperscissors"];
 | 
			
		||||
  static arguments = ["[rock/paper/scissors]"];
 | 
			
		||||
  static slashAllowed = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default RPSCommand;
 | 
			
		||||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ import ImageCommand from "../../classes/imageCommand.js";
 | 
			
		|||
 | 
			
		||||
class SonicCommand extends ImageCommand {
 | 
			
		||||
  params() {
 | 
			
		||||
    const cleanedMessage = this.args.join(" ").replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%");
 | 
			
		||||
    const cleanedMessage = (this.type === "classic" ? this.args.join(" ") : this.options.text).replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%");
 | 
			
		||||
    return {
 | 
			
		||||
      text: wrap(cleanedMessage, {width: 15, indent: ""})
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,6 +25,7 @@ class XKCDCommand extends Command {
 | 
			
		|||
 | 
			
		||||
  static description = "Gets an XKCD comic";
 | 
			
		||||
  static arguments = ["{id}"];
 | 
			
		||||
  static slashAllowed = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default XKCDCommand;
 | 
			
		||||
| 
						 | 
				
			
			@ -12,16 +12,16 @@ class AvatarCommand extends Command {
 | 
			
		|||
        const user = await this.client.getRESTUser(this.args[0]);
 | 
			
		||||
        return user.avatar ? this.client._formatImage(`/avatars/${user.id}/${user.avatar}`, null, 1024) : `https://cdn.discordapp.com/embed/avatars/${user.discriminator % 5}.png`; // repeat of hacky "solution" from above
 | 
			
		||||
      } catch {
 | 
			
		||||
        return this.message.author.dynamicAvatarURL(null, 1024);
 | 
			
		||||
        return this.author.dynamicAvatarURL(null, 1024);
 | 
			
		||||
      }
 | 
			
		||||
    } else if (this.args.join(" ") !== "" && this.message.channel.guild) {
 | 
			
		||||
    } else if (this.args.join(" ") !== "" && this.channel.guild) {
 | 
			
		||||
      const userRegex = new RegExp(this.args.join("|"), "i");
 | 
			
		||||
      const member = this.message.channel.guild.members.find(element => {
 | 
			
		||||
      const member = this.channel.guild.members.find(element => {
 | 
			
		||||
        return userRegex.test(element.nick) ? userRegex.test(element.nick) : userRegex.test(element.username);
 | 
			
		||||
      });
 | 
			
		||||
      return member ? member.user.dynamicAvatarURL(null, 1024) : this.message.author.dynamicAvatarURL(null, 1024);
 | 
			
		||||
      return member ? member.user.dynamicAvatarURL(null, 1024) : this.author.dynamicAvatarURL(null, 1024);
 | 
			
		||||
    } else {
 | 
			
		||||
      return this.message.author.dynamicAvatarURL(null, 1024);
 | 
			
		||||
      return this.author.dynamicAvatarURL(null, 1024);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,16 +12,16 @@ class BannerCommand extends Command {
 | 
			
		|||
        const user = await this.client.getRESTUser(this.args[0]);
 | 
			
		||||
        return user.banner ? this.client._formatImage(`/banners/${user.id}/${user.banner}`, null, 1024) : "This user doesn't have a banner!";
 | 
			
		||||
      } catch {
 | 
			
		||||
        return this.message.author.banner ? this.message.author.dynamicBannerURL(null, 1024) : "You don't have a banner!";
 | 
			
		||||
        return this.author.banner ? this.author.dynamicBannerURL(null, 1024) : "You don't have a banner!";
 | 
			
		||||
      }
 | 
			
		||||
    } else if (this.args.join(" ") !== "" && this.message.channel.guild) {
 | 
			
		||||
    } else if (this.args.join(" ") !== "" && this.channel.guild) {
 | 
			
		||||
      const userRegex = new RegExp(this.args.join("|"), "i");
 | 
			
		||||
      const member = this.message.channel.guild.members.find(element => {
 | 
			
		||||
      const member = this.channel.guild.members.find(element => {
 | 
			
		||||
        return userRegex.test(element.nick) ?? userRegex.test(element.username);
 | 
			
		||||
      });
 | 
			
		||||
      return member && member.user.banner ? member.user.dynamicBannerURL(null, 1024) : (this.message.author.banner ? this.message.author.dynamicBannerURL(null, 1024) : "This user doesn't have a banner!");
 | 
			
		||||
      return member && member.user.banner ? member.user.dynamicBannerURL(null, 1024) : (this.author.banner ? this.author.dynamicBannerURL(null, 1024) : "This user doesn't have a banner!");
 | 
			
		||||
    } else {
 | 
			
		||||
      return this.message.author.banner ? this.message.author.dynamicBannerURL(null, 1024) : "You don't have a banner!";
 | 
			
		||||
      return this.author.banner ? this.author.dynamicBannerURL(null, 1024) : "You don't have a banner!";
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,7 @@ class BroadcastCommand extends Command {
 | 
			
		|||
  run() {
 | 
			
		||||
    return new Promise((resolve) => {
 | 
			
		||||
      const owners = process.env.OWNER.split(",");
 | 
			
		||||
      if (!owners.includes(this.message.author.id)) return "Only the bot owner can broadcast messages!";
 | 
			
		||||
      if (!owners.includes(this.author.id)) return "Only the bot owner can broadcast messages!";
 | 
			
		||||
      if (this.args.length !== 0) {
 | 
			
		||||
        this.ipc.broadcast("playbroadcast", this.args.join(" "));
 | 
			
		||||
        this.ipc.register("broadcastSuccess", () => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,24 +3,23 @@ import Command from "../../classes/command.js";
 | 
			
		|||
 | 
			
		||||
class ChannelCommand extends Command {
 | 
			
		||||
  async run() {
 | 
			
		||||
    if (this.type !== "classic") return "This command only works with the old command style!";
 | 
			
		||||
    if (!this.message.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    const owners = process.env.OWNER.split(",");
 | 
			
		||||
    if (!this.message.member.permissions.has("administrator") && !owners.includes(this.message.member.id)) return "You need to be an administrator to enable/disable me!";
 | 
			
		||||
    if (this.args.length === 0) return "You need to provide whether I should be enabled or disabled in this channel!";
 | 
			
		||||
    if (this.args[0] !== "disable" && this.args[0] !== "enable") return "That's not a valid option!";
 | 
			
		||||
 | 
			
		||||
    const guildDB = await db.getGuild(this.message.channel.guild.id);
 | 
			
		||||
    const guildDB = await db.getGuild(this.channel.guild.id);
 | 
			
		||||
 | 
			
		||||
    if (this.args[0].toLowerCase() === "disable") {
 | 
			
		||||
      let channel;
 | 
			
		||||
      if (this.args[1] && this.args[1].match(/^<?[@#]?[&!]?\d+>?$/) && this.args[1] >= 21154535154122752n) {
 | 
			
		||||
        const id = this.args[1].replaceAll("@", "").replaceAll("#", "").replaceAll("!", "").replaceAll("&", "").replaceAll("<", "").replaceAll(">", "");
 | 
			
		||||
        if (guildDB.disabled.includes(id)) return "I'm already disabled in this channel!";
 | 
			
		||||
        channel = this.message.channel.guild.channels.get(id);
 | 
			
		||||
        channel = this.channel.guild.channels.get(id);
 | 
			
		||||
      } else {
 | 
			
		||||
        if (guildDB.disabled.includes(this.message.channel.id)) return "I'm already disabled in this channel!";
 | 
			
		||||
        channel = this.message.channel;
 | 
			
		||||
        if (guildDB.disabled.includes(this.channel.id)) return "I'm already disabled in this channel!";
 | 
			
		||||
        channel = this.channel;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      await db.disableChannel(channel);
 | 
			
		||||
| 
						 | 
				
			
			@ -30,10 +29,10 @@ class ChannelCommand extends Command {
 | 
			
		|||
      if (this.args[1] && this.args[1].match(/^<?[@#]?[&!]?\d+>?$/) && this.args[1] >= 21154535154122752n) {
 | 
			
		||||
        const id = this.args[1].replaceAll("@", "").replaceAll("#", "").replaceAll("!", "").replaceAll("&", "").replaceAll("<", "").replaceAll(">", "");
 | 
			
		||||
        if (!guildDB.disabled.includes(id)) return "I'm not disabled in that channel!";
 | 
			
		||||
        channel = this.message.channel.guild.channels.get(id);
 | 
			
		||||
        channel = this.channel.guild.channels.get(id);
 | 
			
		||||
      } else {
 | 
			
		||||
        if (!guildDB.disabled.includes(this.message.channel.id)) return "I'm not disabled in this channel!";
 | 
			
		||||
        channel = this.message.channel;
 | 
			
		||||
        if (!guildDB.disabled.includes(this.channel.id)) return "I'm not disabled in this channel!";
 | 
			
		||||
        channel = this.channel;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      await db.enableChannel(channel);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,13 +4,13 @@ import * as collections from "../../utils/collections.js";
 | 
			
		|||
 | 
			
		||||
class CommandCommand extends Command {
 | 
			
		||||
  async run() {
 | 
			
		||||
    if (!this.message.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    const owners = process.env.OWNER.split(",");
 | 
			
		||||
    if (!this.message.member.permissions.has("administrator") && !owners.includes(this.message.member.id)) return "You need to be an administrator to enable/disable me!";
 | 
			
		||||
    if (this.args.length === 0) return "You need to provide what command to enable/disable!";
 | 
			
		||||
    if (this.args[0] !== "disable" && this.args[0] !== "enable") return "That's not a valid option!";
 | 
			
		||||
 | 
			
		||||
    const guildDB = await db.getGuild(this.message.channel.guild.id);
 | 
			
		||||
    const guildDB = await db.getGuild(this.channel.guild.id);
 | 
			
		||||
    const disabled = guildDB.disabled_commands ?? guildDB.disabledCommands;
 | 
			
		||||
 | 
			
		||||
    if (this.args[0].toLowerCase() === "disable") {
 | 
			
		||||
| 
						 | 
				
			
			@ -19,14 +19,14 @@ class CommandCommand extends Command {
 | 
			
		|||
      if (command === "command") return "You can't disable that command!";
 | 
			
		||||
      if (disabled && disabled.includes(command)) return "That command is already disabled!";
 | 
			
		||||
 | 
			
		||||
      await db.disableCommand(this.message.channel.guild.id, command);
 | 
			
		||||
      await db.disableCommand(this.channel.guild.id, command);
 | 
			
		||||
      return `The command has been disabled. To re-enable it, just run \`${guildDB.prefix}command enable ${command}\`.`;
 | 
			
		||||
    } else if (this.args[0].toLowerCase() === "enable") {
 | 
			
		||||
      if (!collections.commands.has(this.args[1].toLowerCase()) && !collections.aliases.has(this.args[1].toLowerCase())) return "That isn't a command!";
 | 
			
		||||
      const command = collections.aliases.get(this.args[1].toLowerCase()) ?? this.args[1].toLowerCase();
 | 
			
		||||
      if (disabled && !disabled.includes(command)) return "That command isn't disabled!";
 | 
			
		||||
 | 
			
		||||
      await db.enableCommand(this.message.channel.guild.id, command);
 | 
			
		||||
      await db.enableCommand(this.channel.guild.id, command);
 | 
			
		||||
      return `The command \`${command}\` has been re-enabled.`;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ import Command from "../../classes/command.js";
 | 
			
		|||
 | 
			
		||||
class CountCommand extends Command {
 | 
			
		||||
  async run() {
 | 
			
		||||
    if (this.message.channel.guild && !this.message.channel.permissionsOf(this.client.user.id).has("embedLinks")) return "I don't have the `Embed Links` permission!";
 | 
			
		||||
    if (this.channel.guild && !this.channel.permissionsOf(this.client.user.id).has("embedLinks")) return "I don't have the `Embed Links` permission!";
 | 
			
		||||
    const counts = await database.getCounts();
 | 
			
		||||
    const countArray = [];
 | 
			
		||||
    for (const entry of Object.entries(counts)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -33,8 +33,8 @@ class CountCommand extends Command {
 | 
			
		|||
          },
 | 
			
		||||
          description: value.join("\n"),
 | 
			
		||||
          author: {
 | 
			
		||||
            name: this.message.author.username,
 | 
			
		||||
            icon_url: this.message.author.avatarURL
 | 
			
		||||
            name: this.author.username,
 | 
			
		||||
            icon_url: this.author.avatarURL
 | 
			
		||||
          }
 | 
			
		||||
        }]
 | 
			
		||||
      });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ import Command from "../../classes/command.js";
 | 
			
		|||
class EvalCommand extends Command {
 | 
			
		||||
  async run() {
 | 
			
		||||
    const owners = process.env.OWNER.split(",");
 | 
			
		||||
    if (!owners.includes(this.message.author.id)) return "Only the bot owner can use eval!";
 | 
			
		||||
    if (!owners.includes(this.author.id)) return "Only the bot owner can use eval!";
 | 
			
		||||
    const code = this.args.join(" ");
 | 
			
		||||
    try {
 | 
			
		||||
      const evaled = eval(code);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ import Command from "../../classes/command.js";
 | 
			
		|||
class EvalRawCommand extends Command {
 | 
			
		||||
  async run() {
 | 
			
		||||
    const owners = process.env.OWNER.split(",");
 | 
			
		||||
    if (!owners.includes(this.message.author.id)) return "Only the bot owner can use evalraw!";
 | 
			
		||||
    if (!owners.includes(this.author.id)) return "Only the bot owner can use evalraw!";
 | 
			
		||||
    const code = this.args.join(" ");
 | 
			
		||||
    try {
 | 
			
		||||
      const evaled = eval(code);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ import Command from "../../classes/command.js";
 | 
			
		|||
class ExecCommand extends Command {
 | 
			
		||||
  async run() {
 | 
			
		||||
    const owners = process.env.OWNER.split(",");
 | 
			
		||||
    if (!owners.includes(this.message.author.id)) return "Only the bot owner can use exec!";
 | 
			
		||||
    if (!owners.includes(this.author.id)) return "Only the bot owner can use exec!";
 | 
			
		||||
    const code = this.args.join(" ");
 | 
			
		||||
    try {
 | 
			
		||||
      const execed = await exec(code);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,7 @@ const tips = ["You can change the bot's prefix using the prefix command.", "Imag
 | 
			
		|||
 | 
			
		||||
class HelpCommand extends Command {
 | 
			
		||||
  async run() {
 | 
			
		||||
    const { prefix } = this.message.channel.guild ? await database.getGuild(this.message.channel.guild.id) : "N/A";
 | 
			
		||||
    const { prefix } = this.channel.guild ? await database.getGuild(this.channel.guild.id) : "N/A";
 | 
			
		||||
    if (this.args.length !== 0 && (collections.commands.has(this.args[0].toLowerCase()) || collections.aliases.has(this.args[0].toLowerCase()))) {
 | 
			
		||||
      const command = collections.aliases.get(this.args[0].toLowerCase()) ?? this.args[0].toLowerCase();
 | 
			
		||||
      const info = collections.info.get(command);
 | 
			
		||||
| 
						 | 
				
			
			@ -19,7 +19,7 @@ class HelpCommand extends Command {
 | 
			
		|||
            name: "esmBot Help",
 | 
			
		||||
            icon_url: this.client.user.avatarURL
 | 
			
		||||
          },
 | 
			
		||||
          title: `${this.message.channel.guild ? prefix : ""}${command}`,
 | 
			
		||||
          title: `${this.channel.guild ? prefix : ""}${command}`,
 | 
			
		||||
          url: "https://projectlounge.pw/esmBot/help.html",
 | 
			
		||||
          description: command === "tags" ? "The main tags command. Check the help page for more info: https://projectlounge.pw/esmBot/help.html" : info.description,
 | 
			
		||||
          color: 16711680,
 | 
			
		||||
| 
						 | 
				
			
			@ -49,7 +49,7 @@ class HelpCommand extends Command {
 | 
			
		|||
      }
 | 
			
		||||
      return embed;
 | 
			
		||||
    } else {
 | 
			
		||||
      if (this.message.channel.guild && !this.message.channel.permissionsOf(this.client.user.id).has("embedLinks")) return "I don't have the `Embed Links` permission!";
 | 
			
		||||
      if (this.channel.guild && !this.channel.permissionsOf(this.client.user.id).has("embedLinks")) return "I don't have the `Embed Links` permission!";
 | 
			
		||||
      const pages = [];
 | 
			
		||||
      if (help.categories === help.categoryTemplate && !help.generated) await help.generateList();
 | 
			
		||||
      for (const category of Object.keys(help.categories)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -85,7 +85,7 @@ class HelpCommand extends Command {
 | 
			
		|||
            },
 | 
			
		||||
            fields: [{
 | 
			
		||||
              name: "Prefix",
 | 
			
		||||
              value: this.message.channel.guild ? prefix : "N/A"
 | 
			
		||||
              value: this.channel.guild ? prefix : "N/A"
 | 
			
		||||
            }, {
 | 
			
		||||
              name: "Tip",
 | 
			
		||||
              value: random(tips)
 | 
			
		||||
| 
						 | 
				
			
			@ -100,6 +100,7 @@ class HelpCommand extends Command {
 | 
			
		|||
  static description = "Gets a list of commands";
 | 
			
		||||
  static aliases = ["commands"];
 | 
			
		||||
  static arguments = ["{command}"];
 | 
			
		||||
  static slashAllowed = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default HelpCommand;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ import Command from "../../classes/command.js";
 | 
			
		|||
 | 
			
		||||
class ImageSearchCommand extends Command {
 | 
			
		||||
  async run() {
 | 
			
		||||
    if (this.message.channel.guild && !this.message.channel.permissionsOf(this.client.user.id).has("embedLinks")) return "I don't have the `Embed Links` permission!";
 | 
			
		||||
    if (this.channel.guild && !this.channel.permissionsOf(this.client.user.id).has("embedLinks")) return "I don't have the `Embed Links` permission!";
 | 
			
		||||
    if (this.args.length === 0) return "You need to provide something to search for!";
 | 
			
		||||
    this.acknowledge();
 | 
			
		||||
    const embeds = [];
 | 
			
		||||
| 
						 | 
				
			
			@ -27,8 +27,8 @@ class ImageSearchCommand extends Command {
 | 
			
		|||
            url: encodeURI(value.img_src)
 | 
			
		||||
          },
 | 
			
		||||
          author: {
 | 
			
		||||
            name: this.message.author.username,
 | 
			
		||||
            icon_url: this.message.author.avatarURL
 | 
			
		||||
            name: this.author.username,
 | 
			
		||||
            icon_url: this.author.avatarURL
 | 
			
		||||
          }
 | 
			
		||||
        }]
 | 
			
		||||
      });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ import Command from "../../classes/command.js";
 | 
			
		|||
class ImageReloadCommand extends Command {
 | 
			
		||||
  async run() {
 | 
			
		||||
    const owners = process.env.OWNER.split(",");
 | 
			
		||||
    if (!owners.includes(this.message.author.id)) return "Only the bot owner can reload the image servers!";
 | 
			
		||||
    if (!owners.includes(this.author.id)) return "Only the bot owner can reload the image servers!";
 | 
			
		||||
    const amount = await this.ipc.serviceCommand("image", { type: "reload" }, true);
 | 
			
		||||
    if (amount > 0) {
 | 
			
		||||
      return `Successfully connected to ${amount} image servers.`;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@ class InviteCommand extends Command {
 | 
			
		|||
 | 
			
		||||
  static description = "Gets my invite link";
 | 
			
		||||
  static aliases = ["botinfo", "credits"];
 | 
			
		||||
  static slashAllowed = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default InviteCommand;
 | 
			
		||||
| 
						 | 
				
			
			@ -2,10 +2,16 @@ import Command from "../../classes/command.js";
 | 
			
		|||
 | 
			
		||||
class PingCommand extends Command {
 | 
			
		||||
  async run() {
 | 
			
		||||
    const pingMessage = await this.client.createMessage(this.message.channel.id, Object.assign({
 | 
			
		||||
      content: "🏓 Ping?"
 | 
			
		||||
    }, this.reference));
 | 
			
		||||
    pingMessage.edit(`🏓 Pong!\n\`\`\`\nLatency: ${pingMessage.timestamp - this.message.timestamp}ms${this.message.channel.guild ? `\nShard Latency: ${Math.round(this.client.shards.get(this.client.guildShardMap[this.message.channel.guild.id]).latency)}ms` : ""}\n\`\`\``);
 | 
			
		||||
    if (this.type === "classic") {
 | 
			
		||||
      const pingMessage = await this.client.createMessage(this.channel.id, Object.assign({
 | 
			
		||||
        content: "🏓 Ping?"
 | 
			
		||||
      }, this.reference));
 | 
			
		||||
      pingMessage.edit(`🏓 Pong!\n\`\`\`\nLatency: ${pingMessage.timestamp - this.message.timestamp}ms${this.channel.guild ? `\nShard Latency: ${Math.round(this.client.shards.get(this.client.guildShardMap[this.channel.guild.id]).latency)}ms` : ""}\n\`\`\``);
 | 
			
		||||
    } else {
 | 
			
		||||
      await this.interaction.createMessage("🏓 Ping?");
 | 
			
		||||
      const pingMessage = await this.interaction.getOriginalMessage();
 | 
			
		||||
      await this.interaction.editOriginalMessage(`🏓 Pong!\n\`\`\`\nLatency: ${pingMessage.timestamp - Math.floor((this.interaction.id / 4194304) + 1420070400000)}ms${this.interaction.guildID ? `\nShard Latency: ${Math.round(this.client.shards.get(this.client.guildShardMap[this.interaction.guildID]).latency)}ms` : ""}\n\`\`\``);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static description = "Pings Discord's servers";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,12 +3,12 @@ import Command from "../../classes/command.js";
 | 
			
		|||
 | 
			
		||||
class PrefixCommand extends Command {
 | 
			
		||||
  async run() {
 | 
			
		||||
    if (!this.message.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    const guild = await database.getGuild(this.message.channel.guild.id);
 | 
			
		||||
    if (!this.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    const guild = await database.getGuild(this.channel.guild.id);
 | 
			
		||||
    if (this.args.length !== 0) {
 | 
			
		||||
      const owners = process.env.OWNER.split(",");
 | 
			
		||||
      if (!this.message.member.permissions.has("administrator") && !owners.includes(this.message.member.id)) return "You need to be an administrator to change the bot prefix!";
 | 
			
		||||
      await database.setPrefix(this.args[0], this.message.channel.guild);
 | 
			
		||||
      await database.setPrefix(this.args[0], this.channel.guild);
 | 
			
		||||
      return `The prefix has been changed to ${this.args[0]}.`;
 | 
			
		||||
    } else {
 | 
			
		||||
      return `The current prefix is \`${guild.prefix}\`.`;
 | 
			
		||||
| 
						 | 
				
			
			@ -18,6 +18,7 @@ class PrefixCommand extends Command {
 | 
			
		|||
  static description = "Checks/changes the server prefix";
 | 
			
		||||
  static aliases = ["setprefix", "changeprefix", "checkprefix"];
 | 
			
		||||
  static arguments = ["{prefix}"];
 | 
			
		||||
  static slashAllowed = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default PrefixCommand;
 | 
			
		||||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ import imageDetect from "../../utils/imagedetect.js";
 | 
			
		|||
 | 
			
		||||
class QrReadCommand extends Command {
 | 
			
		||||
  async run() {
 | 
			
		||||
    const image = await imageDetect(this.client, this.message);
 | 
			
		||||
    const image = await imageDetect(this.client, this.message, this.interaction, this.options);
 | 
			
		||||
    if (image === undefined) return "You need to provide an image/GIF with a QR code to read!";
 | 
			
		||||
    this.acknowledge();
 | 
			
		||||
    const data = await (await fetch(image.path)).buffer();
 | 
			
		||||
| 
						 | 
				
			
			@ -18,6 +18,15 @@ class QrReadCommand extends Command {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  static description = "Reads a QR code";
 | 
			
		||||
  static flags = [{
 | 
			
		||||
    name: "image",
 | 
			
		||||
    type: 11,
 | 
			
		||||
    description: "An image/GIF attachment"
 | 
			
		||||
  }, {
 | 
			
		||||
    name: "link",
 | 
			
		||||
    type: 3,
 | 
			
		||||
    description: "An image/GIF URL"
 | 
			
		||||
  }];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default QrReadCommand;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,13 +4,22 @@ import imageDetect from "../../utils/imagedetect.js";
 | 
			
		|||
class RawCommand extends Command {
 | 
			
		||||
  async run() {
 | 
			
		||||
    this.acknowledge();
 | 
			
		||||
    const image = await imageDetect(this.client, this.message);
 | 
			
		||||
    const image = await imageDetect(this.client, this.message, this.interaction, this.options);
 | 
			
		||||
    if (image === undefined) return "You need to provide an image/GIF to get a raw URL!";
 | 
			
		||||
    return image.path;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static description = "Gets a direct image URL (useful for saving GIFs from sites like Tenor)";
 | 
			
		||||
  static aliases = ["gif", "getgif", "giflink", "imglink", "getimg", "rawgif", "rawimg"];
 | 
			
		||||
  static flags = [{
 | 
			
		||||
    name: "image",
 | 
			
		||||
    type: 11,
 | 
			
		||||
    description: "An image/GIF attachment"
 | 
			
		||||
  }, {
 | 
			
		||||
    name: "link",
 | 
			
		||||
    type: 3,
 | 
			
		||||
    description: "An image/GIF URL"
 | 
			
		||||
  }];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default RawCommand;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,7 @@ class ReloadCommand extends Command {
 | 
			
		|||
  run() {
 | 
			
		||||
    return new Promise((resolve) => {
 | 
			
		||||
      const owners = process.env.OWNER.split(",");
 | 
			
		||||
      if (!owners.includes(this.message.author.id)) return resolve("Only the bot owner can reload commands!");
 | 
			
		||||
      if (!owners.includes(this.author.id)) return resolve("Only the bot owner can reload commands!");
 | 
			
		||||
      if (this.args.length === 0) return resolve("You need to provide a command to reload!");
 | 
			
		||||
      this.ipc.broadcast("reload", this.args[0]);
 | 
			
		||||
      this.ipc.register("reloadSuccess", () => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,8 +3,8 @@ import Command from "../../classes/command.js";
 | 
			
		|||
class RestartCommand extends Command {
 | 
			
		||||
  async run() {
 | 
			
		||||
    const owners = process.env.OWNER.split(",");
 | 
			
		||||
    if (!owners.includes(this.message.author.id)) return "Only the bot owner can restart me!";
 | 
			
		||||
    await this.client.createMessage(this.message.channel.id, Object.assign({
 | 
			
		||||
    if (!owners.includes(this.author.id)) return "Only the bot owner can restart me!";
 | 
			
		||||
    await this.client.createMessage(this.channel.id, Object.assign({
 | 
			
		||||
      content: "esmBot is restarting."
 | 
			
		||||
    }, this.reference));
 | 
			
		||||
    this.ipc.restartAllClusters(true);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,44 +2,44 @@ import Command from "../../classes/command.js";
 | 
			
		|||
 | 
			
		||||
class ServerInfoCommand extends Command {
 | 
			
		||||
  async run() {
 | 
			
		||||
    if (!this.message.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    const owner = await this.message.channel.guild.members.get(this.message.channel.guild.ownerID);
 | 
			
		||||
    if (!this.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    const owner = await this.channel.guild.members.get(this.channel.guild.ownerID);
 | 
			
		||||
    return {
 | 
			
		||||
      embeds: [{
 | 
			
		||||
        title: this.message.channel.guild.name,
 | 
			
		||||
        title: this.channel.guild.name,
 | 
			
		||||
        thumbnail: {
 | 
			
		||||
          url: this.message.channel.guild.iconURL
 | 
			
		||||
          url: this.channel.guild.iconURL
 | 
			
		||||
        },
 | 
			
		||||
        image: {
 | 
			
		||||
          url: this.message.channel.guild.bannerURL
 | 
			
		||||
          url: this.channel.guild.bannerURL
 | 
			
		||||
        },
 | 
			
		||||
        color: 16711680,
 | 
			
		||||
        fields: [
 | 
			
		||||
          {
 | 
			
		||||
            name: "🔢 **ID:**",
 | 
			
		||||
            value: this.message.channel.guild.id
 | 
			
		||||
            value: this.channel.guild.id
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            name: "👤 **Owner:**",
 | 
			
		||||
            value: owner ? `${owner.user.username}#${owner.user.discriminator}` : this.message.channel.guild.ownerID
 | 
			
		||||
            value: owner ? `${owner.user.username}#${owner.user.discriminator}` : this.channel.guild.ownerID
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            name: "🗓 **Created on:**",
 | 
			
		||||
            value: `<t:${Math.floor(this.message.channel.guild.createdAt / 1000)}:F>`
 | 
			
		||||
            value: `<t:${Math.floor(this.channel.guild.createdAt / 1000)}:F>`
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            name: "👥 **Users:**",
 | 
			
		||||
            value: this.message.channel.guild.memberCount,
 | 
			
		||||
            value: this.channel.guild.memberCount,
 | 
			
		||||
            inline: true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            name: "💬 **Channels:**",
 | 
			
		||||
            value: this.message.channel.guild.channels.size,
 | 
			
		||||
            value: this.channel.guild.channels.size,
 | 
			
		||||
            inline: true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            name: "😃 **Emojis:**",
 | 
			
		||||
            value: this.message.channel.guild.emojis.length,
 | 
			
		||||
            value: this.channel.guild.emojis.length,
 | 
			
		||||
            inline: true
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,6 +10,7 @@ class SnowflakeCommand extends Command {
 | 
			
		|||
  static description = "Converts a Discord snowflake id into a timestamp";
 | 
			
		||||
  static aliases = ["timestamp", "snowstamp", "snow"];
 | 
			
		||||
  static arguments = ["[id]"];
 | 
			
		||||
  static slashAllowed = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default SnowflakeCommand;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,7 @@ class SoundReloadCommand extends Command {
 | 
			
		|||
  run() {
 | 
			
		||||
    return new Promise((resolve) => {
 | 
			
		||||
      const owners = process.env.OWNER.split(",");
 | 
			
		||||
      if (!owners.includes(this.message.author.id)) return "Only the bot owner can reload Lavalink!";
 | 
			
		||||
      if (!owners.includes(this.author.id)) return "Only the bot owner can reload Lavalink!";
 | 
			
		||||
      this.acknowledge();
 | 
			
		||||
      this.ipc.broadcast("soundreload");
 | 
			
		||||
      this.ipc.register("soundReloadSuccess", (msg) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -62,7 +62,7 @@ class StatsCommand extends Command {
 | 
			
		|||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "name": "Shard",
 | 
			
		||||
          "value": this.message.channel.guild ? this.client.guildShardMap[this.message.channel.guild.id] : "N/A",
 | 
			
		||||
          "value": this.channel.guild ? this.client.guildShardMap[this.channel.guild.id] : "N/A",
 | 
			
		||||
          "inline": true
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ import imagedetect from "../../utils/imagedetect.js";
 | 
			
		|||
 | 
			
		||||
class StickerCommand extends Command {
 | 
			
		||||
  async run() {
 | 
			
		||||
    const result = await imagedetect(this.client, this.message, false, false, true);
 | 
			
		||||
    const result = await imagedetect(this.client, this.message, this.interaction, this.options, false, false, true);
 | 
			
		||||
    if (!result) return "You need to provide a sticker!";
 | 
			
		||||
    if (result.format_type === 1) { // PNG
 | 
			
		||||
      return `https://cdn.discordapp.com/stickers/${result.id}.png`;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,7 @@ import Command from "../../classes/command.js";
 | 
			
		|||
 | 
			
		||||
class UserInfoCommand extends Command {
 | 
			
		||||
  async run() {
 | 
			
		||||
    const getUser = this.message.mentions.length >= 1 ? this.message.mentions[0] : (this.args.length !== 0 ? await this.ipc.fetchUser(this.args[0]) : this.message.author);
 | 
			
		||||
    const getUser = this.message.mentions.length >= 1 ? this.message.mentions[0] : (this.args.length !== 0 ? await this.ipc.fetchUser(this.args[0]) : this.author);
 | 
			
		||||
    let user;
 | 
			
		||||
    if (getUser) {
 | 
			
		||||
      user = getUser;
 | 
			
		||||
| 
						 | 
				
			
			@ -10,18 +10,18 @@ class UserInfoCommand extends Command {
 | 
			
		|||
      try {
 | 
			
		||||
        user = await this.client.getRESTUser(this.args[0]);
 | 
			
		||||
      } catch {
 | 
			
		||||
        user = this.message.author;
 | 
			
		||||
        user = this.author;
 | 
			
		||||
      }
 | 
			
		||||
    } else if (this.args.join(" ") !== "") {
 | 
			
		||||
      const userRegex = new RegExp(this.args.join("|"), "i");
 | 
			
		||||
      const member = this.client.users.find(element => {
 | 
			
		||||
        return userRegex.test(element.username);
 | 
			
		||||
      });
 | 
			
		||||
      user = member ?? this.message.author;
 | 
			
		||||
      user = member ?? this.author;
 | 
			
		||||
    } else {
 | 
			
		||||
      user = this.message.author;
 | 
			
		||||
      user = this.author;
 | 
			
		||||
    }
 | 
			
		||||
    const member = this.message.channel.guild ? this.message.channel.guild.members.get(user.id) : undefined;
 | 
			
		||||
    const member = this.channel.guild ? this.channel.guild.members.get(user.id) : undefined;
 | 
			
		||||
    return {
 | 
			
		||||
      embeds: [{
 | 
			
		||||
        title: `${user.username}#${user.discriminator}`,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,15 @@
 | 
			
		|||
import ImageCommand from "../../classes/imageCommand.js";
 | 
			
		||||
 | 
			
		||||
class BlurpleCommand extends ImageCommand {
 | 
			
		||||
  constructor(client, cluster, worker, ipc, options) {
 | 
			
		||||
    super(client, cluster, worker, ipc, options);
 | 
			
		||||
    this.flags.push({
 | 
			
		||||
      name: "old",
 | 
			
		||||
      description: "Use the old blurple color",
 | 
			
		||||
      type: 5
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  params() {
 | 
			
		||||
    return {
 | 
			
		||||
      old: !!this.specialArgs.old,
 | 
			
		||||
| 
						 | 
				
			
			@ -9,10 +18,6 @@ class BlurpleCommand extends ImageCommand {
 | 
			
		|||
  }
 | 
			
		||||
  
 | 
			
		||||
  static description = "Turns an image blurple";
 | 
			
		||||
  static flags = [{
 | 
			
		||||
    name: "old",
 | 
			
		||||
    description: "Use the old blurple color"
 | 
			
		||||
  }];
 | 
			
		||||
 | 
			
		||||
  static noImage = "You need to provide an image/GIF to make blurple!";
 | 
			
		||||
  static command = "colors";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,9 +2,29 @@ import ImageCommand from "../../classes/imageCommand.js";
 | 
			
		|||
const allowedFonts = ["futura", "impact", "helvetica", "arial", "roboto", "noto", "times"];
 | 
			
		||||
 | 
			
		||||
class CaptionCommand extends ImageCommand {
 | 
			
		||||
  constructor(client, cluster, worker, ipc, options) {
 | 
			
		||||
    super(client, cluster, worker, ipc, options);
 | 
			
		||||
    this.flags.push({
 | 
			
		||||
      name: "noegg",
 | 
			
		||||
      description: "Disable... something. Not saying what it is though.",
 | 
			
		||||
      type: 5
 | 
			
		||||
    }, {
 | 
			
		||||
      name: "font",
 | 
			
		||||
      type: 3,
 | 
			
		||||
      choices: (() => {
 | 
			
		||||
        const array = [];
 | 
			
		||||
        for (const font of allowedFonts) {
 | 
			
		||||
          array.push({ name: font, value: font });
 | 
			
		||||
        }
 | 
			
		||||
        return array;
 | 
			
		||||
      })(),
 | 
			
		||||
      description: "Specify the font you want to use (default: futura)"
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  params(url) {
 | 
			
		||||
    const newArgs = this.args.filter(item => !item.includes(url));
 | 
			
		||||
    let newCaption = newArgs.join(" ").replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%");
 | 
			
		||||
    const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text;
 | 
			
		||||
    let newCaption = newArgs.replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%");
 | 
			
		||||
    if (process.env.NODE_ENV === "development" && newCaption.toLowerCase() === "get real" && !this.specialArgs.noEgg) newCaption = `I'm tired of people telling me to "get real". Every day I put captions on images for people, some funny and some not, but out of all of those "get real" remains the most used caption. Why? I am simply a computer program running on a server, I am unable to manifest myself into the real world. As such, I'm confused as to why anyone would want me to "get real". Is this form not good enough? Alas, as I am simply a bot, I must follow the tasks that I was originally intended to perform, so here goes:\n${newCaption}`;
 | 
			
		||||
    return {
 | 
			
		||||
      caption: newCaption,
 | 
			
		||||
| 
						 | 
				
			
			@ -15,14 +35,6 @@ class CaptionCommand extends ImageCommand {
 | 
			
		|||
  static description = "Adds a caption to an image";
 | 
			
		||||
  static aliases = ["gifc", "gcaption", "ifcaption", "ifunnycaption"];
 | 
			
		||||
  static arguments = ["[text]"];
 | 
			
		||||
  static flags = [{
 | 
			
		||||
    name: "noEgg",
 | 
			
		||||
    description: "Disable... something. Not saying what it is though."
 | 
			
		||||
  }, {
 | 
			
		||||
    name: "font",
 | 
			
		||||
    type: allowedFonts.join("|"),
 | 
			
		||||
    description: "Specify the font you want to use (default: `futura`)"
 | 
			
		||||
  }];
 | 
			
		||||
 | 
			
		||||
  static requiresText = true;
 | 
			
		||||
  static noText = "You need to provide some text to add a caption!";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,10 +3,30 @@ const words = ["me irl", "dank", "follow my second account @esmBot_", "2016", "m
 | 
			
		|||
const allowedFonts = ["futura", "impact", "helvetica", "arial", "roboto", "noto", "times"];
 | 
			
		||||
 | 
			
		||||
class CaptionTwoCommand extends ImageCommand {
 | 
			
		||||
  constructor(client, cluster, worker, ipc, options) {
 | 
			
		||||
    super(client, cluster, worker, ipc, options);
 | 
			
		||||
    this.flags.push({
 | 
			
		||||
      name: "top",
 | 
			
		||||
      description: "Put the caption on the top of an image instead of the bottom",
 | 
			
		||||
      type: 5
 | 
			
		||||
    }, {
 | 
			
		||||
      name: "font",
 | 
			
		||||
      type: 3,
 | 
			
		||||
      choices: (() => {
 | 
			
		||||
        const array = [];
 | 
			
		||||
        for (const font of allowedFonts) {
 | 
			
		||||
          array.push({ name: font, value: font });
 | 
			
		||||
        }
 | 
			
		||||
        return array;
 | 
			
		||||
      })(),
 | 
			
		||||
      description: "Specify the font you want to use (default: helvetica)"
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  params(url) {
 | 
			
		||||
    const newArgs = this.args.filter(item => !item.includes(url));
 | 
			
		||||
    const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text;
 | 
			
		||||
    return {
 | 
			
		||||
      caption: newArgs.length !== 0 ? newArgs.join(" ").replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%") : words.sort(() => 0.5 - Math.random()).slice(0, Math.floor(Math.random() * words.length + 1)).join(" "),
 | 
			
		||||
      caption: newArgs && newArgs.trim() ? newArgs.replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%") : words.sort(() => 0.5 - Math.random()).slice(0, Math.floor(Math.random() * words.length + 1)).join(" "),
 | 
			
		||||
      top: !!this.specialArgs.top,
 | 
			
		||||
      font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "helvetica"
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			@ -15,15 +35,8 @@ class CaptionTwoCommand extends ImageCommand {
 | 
			
		|||
  static description = "Adds a me.me caption/tag list to an image";
 | 
			
		||||
  static aliases = ["tags2", "meirl", "memecaption", "medotmecaption"];
 | 
			
		||||
  static arguments = ["{text}"];
 | 
			
		||||
  static flags = [{
 | 
			
		||||
    name: "top",
 | 
			
		||||
    description: "Put the caption on the top of an image instead of the bottom"
 | 
			
		||||
  }, {
 | 
			
		||||
    name: "font",
 | 
			
		||||
    type: allowedFonts.join("|"),
 | 
			
		||||
    description: "Specify the font you want to use (default: `helvetica`)"
 | 
			
		||||
  }];
 | 
			
		||||
 | 
			
		||||
  static textOptional = true;
 | 
			
		||||
  static noText = "You need to provide some text to add a caption!";
 | 
			
		||||
  static noImage = "You need to provide an image/GIF to add a caption!";
 | 
			
		||||
  static command = "captionTwo";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,9 +2,29 @@ import ImageCommand from "../../classes/imageCommand.js";
 | 
			
		|||
const allowedFonts = ["futura", "impact", "helvetica", "arial", "roboto", "noto", "times"];
 | 
			
		||||
 | 
			
		||||
class MemeCommand extends ImageCommand {
 | 
			
		||||
  constructor(client, cluster, worker, ipc, options) {
 | 
			
		||||
    super(client, cluster, worker, ipc, options);
 | 
			
		||||
    this.flags.push({
 | 
			
		||||
      name: "case",
 | 
			
		||||
      description: "Make the meme text case-sensitive (allows for lowercase text)",
 | 
			
		||||
      type: 5
 | 
			
		||||
    }, {
 | 
			
		||||
      name: "font",
 | 
			
		||||
      type: 3,
 | 
			
		||||
      choices: (() => {
 | 
			
		||||
        const array = [];
 | 
			
		||||
        for (const font of allowedFonts) {
 | 
			
		||||
          array.push({ name: font, value: font });
 | 
			
		||||
        }
 | 
			
		||||
        return array;
 | 
			
		||||
      })(),
 | 
			
		||||
      description: "Specify the font you want to use (default: impact)"
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  params(url) {
 | 
			
		||||
    const newArgs = this.args.filter(item => !item.includes(url));
 | 
			
		||||
    const [topText, bottomText] = newArgs.join(" ").split(/(?<!\\),/).map(elem => elem.trim());
 | 
			
		||||
    const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text;
 | 
			
		||||
    const [topText, bottomText] = newArgs.split(/(?<!\\),/).map(elem => elem.trim());
 | 
			
		||||
    return {
 | 
			
		||||
      top: (this.specialArgs.case ? topText : topText.toUpperCase()).replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%"),
 | 
			
		||||
      bottom: bottomText ? (this.specialArgs.case ? bottomText : bottomText.toUpperCase()).replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%") : "",
 | 
			
		||||
| 
						 | 
				
			
			@ -14,14 +34,6 @@ class MemeCommand extends ImageCommand {
 | 
			
		|||
 | 
			
		||||
  static description = "Generates a meme from an image (separate top/bottom text with a comma)";
 | 
			
		||||
  static arguments = ["[top text]", "{bottom text}"];
 | 
			
		||||
  static flags = [{
 | 
			
		||||
    name: "case",
 | 
			
		||||
    description: "Make the meme text case-sensitive (allows for lowercase text)"
 | 
			
		||||
  }, {
 | 
			
		||||
    name: "font",
 | 
			
		||||
    type: allowedFonts.join("|"),
 | 
			
		||||
    description: "Specify the font you want to use (default: `impact`)"
 | 
			
		||||
  }];
 | 
			
		||||
 | 
			
		||||
  static requiresText = true;
 | 
			
		||||
  static noText = "You need to provide some text to generate a meme!";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,22 @@ import ImageCommand from "../../classes/imageCommand.js";
 | 
			
		|||
const allowedFonts = ["futura", "impact", "helvetica", "arial", "roboto", "noto", "times"];
 | 
			
		||||
 | 
			
		||||
class MotivateCommand extends ImageCommand {
 | 
			
		||||
  constructor(client, cluster, worker, ipc, options) {
 | 
			
		||||
    super(client, cluster, worker, ipc, options);
 | 
			
		||||
    this.flags.push({
 | 
			
		||||
      name: "font",
 | 
			
		||||
      type: 3,
 | 
			
		||||
      choices: (() => {
 | 
			
		||||
        const array = [];
 | 
			
		||||
        for (const font of allowedFonts) {
 | 
			
		||||
          array.push({ name: font, value: font });
 | 
			
		||||
        }
 | 
			
		||||
        return array;
 | 
			
		||||
      })(),
 | 
			
		||||
      description: "Specify the font you want to use (default: times)"
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  params(url) {
 | 
			
		||||
    const newArgs = this.args.filter(item => !item.includes(url));
 | 
			
		||||
    const [topText, bottomText] = newArgs.join(" ").split(/(?<!\\),/).map(elem => elem.trim());
 | 
			
		||||
| 
						 | 
				
			
			@ -15,11 +31,6 @@ class MotivateCommand extends ImageCommand {
 | 
			
		|||
  static description = "Generates a motivational poster";
 | 
			
		||||
  static aliases = ["motivational", "motiv", "demotiv", "demotivational", "poster", "motivation", "demotivate"];
 | 
			
		||||
  static arguments = ["[top text]", "{bottom text}"];
 | 
			
		||||
  static flags = [{
 | 
			
		||||
    name: "font",
 | 
			
		||||
    type: allowedFonts.join("|"),
 | 
			
		||||
    description: "Specify the font you want to use (default: `times`)"
 | 
			
		||||
  }];
 | 
			
		||||
 | 
			
		||||
  static requiresText = true;
 | 
			
		||||
  static noText = "You need to provide some text to generate a motivational poster!";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,17 @@
 | 
			
		|||
import ImageCommand from "../../classes/imageCommand.js";
 | 
			
		||||
 | 
			
		||||
class SnapchatCommand extends ImageCommand {
 | 
			
		||||
  constructor(client, cluster, worker, ipc, options) {
 | 
			
		||||
    super(client, cluster, worker, ipc, options);
 | 
			
		||||
    this.flags.push({
 | 
			
		||||
      name: "position",
 | 
			
		||||
      type: 10,
 | 
			
		||||
      description: "Set the position of the caption as a decimal (0.0 is top, 1.0 is bottom, default is 0.5)",
 | 
			
		||||
      min_value: 0,
 | 
			
		||||
      max_value: 1
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  params(url) {
 | 
			
		||||
    const newArgs = this.args.filter(item => !item.includes(url));
 | 
			
		||||
    const position = parseFloat(this.specialArgs.position);
 | 
			
		||||
| 
						 | 
				
			
			@ -13,11 +24,6 @@ class SnapchatCommand extends ImageCommand {
 | 
			
		|||
  static description = "Adds a Snapchat style caption to an image";
 | 
			
		||||
  static aliases = ["snap", "caption3"];
 | 
			
		||||
  static arguments = ["[text]"];
 | 
			
		||||
  static flags = [{
 | 
			
		||||
    name: "position",
 | 
			
		||||
    type: "number",
 | 
			
		||||
    description: "Set the position of the caption as a decimal (0.0 is top, 1.0 is bottom, default is 0.5)"
 | 
			
		||||
  }];
 | 
			
		||||
 | 
			
		||||
  static requiresText = true;
 | 
			
		||||
  static noText = "You need to provide some text to add a caption!";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,18 @@
 | 
			
		|||
import ImageCommand from "../../classes/imageCommand.js";
 | 
			
		||||
 | 
			
		||||
class SpeedCommand extends ImageCommand {
 | 
			
		||||
  constructor(client, cluster, worker, ipc, options) {
 | 
			
		||||
    super(client, cluster, worker, ipc, options);
 | 
			
		||||
    this.flags.push({
 | 
			
		||||
      name: "multiplier",
 | 
			
		||||
      type: 4,
 | 
			
		||||
      description: "Set the speed multiplier (default: 2)",
 | 
			
		||||
      min_value: 1
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  params() {
 | 
			
		||||
    const speed = parseInt(this.args[0]);
 | 
			
		||||
    const speed = parseInt(this.type === "classic" ? this.args[0] : this.options.multiplier);
 | 
			
		||||
    return {
 | 
			
		||||
      speed: isNaN(speed) || speed < 1 ? 2 : speed
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,17 @@
 | 
			
		|||
import ImageCommand from "../../classes/imageCommand.js";
 | 
			
		||||
 | 
			
		||||
class UncaptionCommand extends ImageCommand {
 | 
			
		||||
  constructor(client, cluster, worker, ipc, options) {
 | 
			
		||||
    super(client, cluster, worker, ipc, options);
 | 
			
		||||
    this.flags.push({
 | 
			
		||||
      name: "tolerance",
 | 
			
		||||
      type: 10,
 | 
			
		||||
      description: "Set the shade tolerance for the caption detection (0.0 is highest, 1.0 is lowest, default is 0.95)",
 | 
			
		||||
      min_value: 0,
 | 
			
		||||
      max_value: 1
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  params() {
 | 
			
		||||
    const tolerance = parseFloat(this.specialArgs.tolerance);
 | 
			
		||||
    return {
 | 
			
		||||
| 
						 | 
				
			
			@ -9,11 +20,6 @@ class UncaptionCommand extends ImageCommand {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  static description = "Removes the caption from an image";
 | 
			
		||||
  static flags = [{
 | 
			
		||||
    name: "tolerance",
 | 
			
		||||
    type: "number",
 | 
			
		||||
    description: "Set the shade tolerance for the caption detection (0.0 is highest, 1.0 is lowest, default is 0.95)"
 | 
			
		||||
  }];
 | 
			
		||||
 | 
			
		||||
  static noImage = "You need to provide an image/GIF to uncaption!";
 | 
			
		||||
  static command = "uncaption";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,10 +3,10 @@ import MusicCommand from "../../classes/musicCommand.js";
 | 
			
		|||
 | 
			
		||||
class HostCommand extends MusicCommand {
 | 
			
		||||
  async run() {
 | 
			
		||||
    if (!this.message.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.message.member.voiceState.channelID) return "You need to be in a voice channel first!";
 | 
			
		||||
    if (!this.message.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    if (this.connection.host !== this.message.author.id && this.message.author.id !== process.env.OWNER) return "Only the current voice session host can choose another host!";
 | 
			
		||||
    if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    if (this.connection.host !== this.author.id && this.author.id !== process.env.OWNER) return "Only the current voice session host can choose another host!";
 | 
			
		||||
    if (this.args.length === 0) return "You need to provide who you want the host to be!";
 | 
			
		||||
    const getUser = this.message.mentions.length >= 1 ? this.message.mentions[0] : (this.args.length !== 0 ? await this.ipc.fetchUser(this.args[0]) : null);
 | 
			
		||||
    let user;
 | 
			
		||||
| 
						 | 
				
			
			@ -27,11 +27,11 @@ class HostCommand extends MusicCommand {
 | 
			
		|||
    }
 | 
			
		||||
    if (!user) return "I can't find that user!";
 | 
			
		||||
    if (user.bot) return "Setting a bot as the session host isn't a very good idea.";
 | 
			
		||||
    const member = this.message.channel.guild ? this.message.channel.guild.members.get(user.id) : undefined;
 | 
			
		||||
    const member = this.channel.guild ? this.channel.guild.members.get(user.id) : undefined;
 | 
			
		||||
    if (!member) return "That user isn't in this server!";
 | 
			
		||||
    const object = this.connection;
 | 
			
		||||
    object.host = member.id;
 | 
			
		||||
    players.set(this.message.channel.guild.id, object);
 | 
			
		||||
    players.set(this.channel.guild.id, object);
 | 
			
		||||
    return `🔊 ${member.mention} is the new voice channel host.`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,13 +3,13 @@ import MusicCommand from "../../classes/musicCommand.js";
 | 
			
		|||
 | 
			
		||||
class LoopCommand extends MusicCommand {
 | 
			
		||||
  async run() {
 | 
			
		||||
    if (!this.message.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.message.member.voiceState.channelID) return "You need to be in a voice channel first!";
 | 
			
		||||
    if (!this.message.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    if (this.connection.host !== this.message.author.id && !this.message.member.permissions.has("manageChannels")) return "Only the current voice session host can loop the music!";
 | 
			
		||||
    if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    if (this.connection.host !== this.author.id && !this.message.member.permissions.has("manageChannels")) return "Only the current voice session host can loop the music!";
 | 
			
		||||
    const object = this.connection;
 | 
			
		||||
    object.loop = !object.loop;
 | 
			
		||||
    players.set(this.message.channel.guild.id, object);
 | 
			
		||||
    players.set(this.channel.guild.id, object);
 | 
			
		||||
    return object.loop ? "🔊 The player is now looping." : "🔊 The player is no longer looping.";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,9 +4,9 @@ import MusicCommand from "../../classes/musicCommand.js";
 | 
			
		|||
 | 
			
		||||
class NowPlayingCommand extends MusicCommand {
 | 
			
		||||
  async run() {
 | 
			
		||||
    if (!this.message.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.message.member.voiceState.channelID) return "You need to be in a voice channel first!";
 | 
			
		||||
    if (!this.message.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    const player = this.connection.player;
 | 
			
		||||
    if (!player) return "I'm not playing anything!";
 | 
			
		||||
    const track = await Rest.decode(player.node, player.track);
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +28,7 @@ class NowPlayingCommand extends MusicCommand {
 | 
			
		|||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: "💬 Channel:",
 | 
			
		||||
          value: this.message.channel.guild.channels.get(this.message.member.voiceState.channelID).name
 | 
			
		||||
          value: this.channel.guild.channels.get(this.message.member.voiceState.channelID).name
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: `${"▬".repeat(parts)}🔘${"▬".repeat(10 - parts)}`,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,10 +2,10 @@ import MusicCommand from "../../classes/musicCommand.js";
 | 
			
		|||
 | 
			
		||||
class PauseCommand extends MusicCommand {
 | 
			
		||||
  async run() {
 | 
			
		||||
    if (!this.message.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.message.member.voiceState.channelID) return "You need to be in a voice channel first!";
 | 
			
		||||
    if (!this.message.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    if (this.connection.host !== this.message.author.id && !this.message.member.permissions.has("manageChannels")) return "Only the current voice session host can pause/resume the music!";
 | 
			
		||||
    if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    if (this.connection.host !== this.author.id && !this.message.member.permissions.has("manageChannels")) return "Only the current voice session host can pause/resume the music!";
 | 
			
		||||
    const player = this.connection.player;
 | 
			
		||||
    await player.pause(!player.paused ? true : false);
 | 
			
		||||
    return `🔊 The player has been ${player.paused ? "paused" : "resumed"}.`;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,10 +6,10 @@ import MusicCommand from "../../classes/musicCommand.js";
 | 
			
		|||
 | 
			
		||||
class QueueCommand extends MusicCommand {
 | 
			
		||||
  async run() {
 | 
			
		||||
    if (!this.message.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.message.member.voiceState.channelID) return "You need to be in a voice channel first!";
 | 
			
		||||
    if (!this.message.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    if (!this.message.channel.permissionsOf(this.client.user.id).has("embedLinks")) return "I don't have the `Embed Links` permission!";
 | 
			
		||||
    if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    if (!this.channel.permissionsOf(this.client.user.id).has("embedLinks")) return "I don't have the `Embed Links` permission!";
 | 
			
		||||
    const player = this.connection;
 | 
			
		||||
    //const tracks = await Rest.decode(player.player.node, queue);
 | 
			
		||||
    const tracks = await fetch(`http://${player.player.node.host}:${player.player.node.port}/decodetracks`, { method: "POST", body: JSON.stringify(this.queue), headers: { Authorization: player.player.node.password, "Content-Type": "application/json" } }).then(res => res.json());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,15 +4,15 @@ import MusicCommand from "../../classes/musicCommand.js";
 | 
			
		|||
 | 
			
		||||
class RemoveCommand extends MusicCommand {
 | 
			
		||||
  async run() {
 | 
			
		||||
    if (!this.message.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.message.member.voiceState.channelID) return "You need to be in a voice channel first!";
 | 
			
		||||
    if (!this.message.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    if (this.connection.host !== this.message.author.id) return "Only the current voice session host can remove songs from the queue!";
 | 
			
		||||
    if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    if (this.connection.host !== this.author.id) return "Only the current voice session host can remove songs from the queue!";
 | 
			
		||||
    const pos = parseInt(this.args[0]);
 | 
			
		||||
    if (isNaN(pos) || pos > this.queue.length || pos < 1) return "That's not a valid position!";
 | 
			
		||||
    const removed = this.queue.splice(pos, 1);
 | 
			
		||||
    const track = await Rest.decode(this.connection.player.node, removed[0]);
 | 
			
		||||
    queues.set(this.message.channel.guild.id, this.queue);
 | 
			
		||||
    queues.set(this.channel.guild.id, this.queue);
 | 
			
		||||
    return `🔊 The song \`${track.title ? track.title : "(blank)"}\` has been removed from the queue.`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,10 +3,10 @@ import MusicCommand from "../../classes/musicCommand.js";
 | 
			
		|||
 | 
			
		||||
class SeekCommand extends MusicCommand {
 | 
			
		||||
  async run() {
 | 
			
		||||
    if (!this.message.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.message.member.voiceState.channelID) return "You need to be in a voice channel first!";
 | 
			
		||||
    if (!this.message.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    if (this.connection.host !== this.message.author.id) return "Only the current voice session host can seek the music!";
 | 
			
		||||
    if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    if (this.connection.host !== this.author.id) return "Only the current voice session host can seek the music!";
 | 
			
		||||
    const player = this.connection.player;
 | 
			
		||||
    const track = await Rest.decode(player.node, player.track);
 | 
			
		||||
    if (!track.isSeekable) return "This track isn't seekable!";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,13 +3,13 @@ import MusicCommand from "../../classes/musicCommand.js";
 | 
			
		|||
 | 
			
		||||
class ShuffleCommand extends MusicCommand {
 | 
			
		||||
  async run() {
 | 
			
		||||
    if (!this.message.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.message.member.voiceState.channelID) return "You need to be in a voice channel first!";
 | 
			
		||||
    if (!this.message.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    if (this.connection.host !== this.message.author.id) return "Only the current voice session host can shuffle the music!";
 | 
			
		||||
    if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    if (this.connection.host !== this.author.id) return "Only the current voice session host can shuffle the music!";
 | 
			
		||||
    const object = this.connection;
 | 
			
		||||
    object.shuffle = !object.shuffle;
 | 
			
		||||
    players.set(this.message.channel.guild.id, object);
 | 
			
		||||
    players.set(this.channel.guild.id, object);
 | 
			
		||||
    return object.shuffle ? "🔊 The player is now shuffling." : "🔊 The player is no longer shuffling.";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,27 +3,27 @@ import MusicCommand from "../../classes/musicCommand.js";
 | 
			
		|||
 | 
			
		||||
class SkipCommand extends MusicCommand {
 | 
			
		||||
  async run() {
 | 
			
		||||
    if (!this.message.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.message.member.voiceState.channelID) return "You need to be in a voice channel first!";
 | 
			
		||||
    if (!this.message.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    const player = this.connection;
 | 
			
		||||
    if (player.host !== this.message.author.id && !this.message.member.permissions.has("manageChannels")) {
 | 
			
		||||
      const votes = skipVotes.get(this.message.channel.guild.id) ?? { count: 0, ids: [], max: Math.min(3, player.voiceChannel.voiceMembers.filter((i) => i.id !== this.client.user.id && !i.bot).length) };
 | 
			
		||||
      if (votes.ids.includes(this.message.author.id)) return "You've already voted to skip!";
 | 
			
		||||
    if (player.host !== this.author.id && !this.message.member.permissions.has("manageChannels")) {
 | 
			
		||||
      const votes = skipVotes.get(this.channel.guild.id) ?? { count: 0, ids: [], max: Math.min(3, player.voiceChannel.voiceMembers.filter((i) => i.id !== this.client.user.id && !i.bot).length) };
 | 
			
		||||
      if (votes.ids.includes(this.author.id)) return "You've already voted to skip!";
 | 
			
		||||
      const newObject = {
 | 
			
		||||
        count: votes.count + 1,
 | 
			
		||||
        ids: [...votes.ids, this.message.author.id].filter(item => !!item),
 | 
			
		||||
        ids: [...votes.ids, this.author.id].filter(item => !!item),
 | 
			
		||||
        max: votes.max
 | 
			
		||||
      };
 | 
			
		||||
      if (votes.count + 1 === votes.max) {
 | 
			
		||||
        await player.player.stop(this.message.channel.guild.id);
 | 
			
		||||
        skipVotes.set(this.message.channel.guild.id, { count: 0, ids: [], max: Math.min(3, player.voiceChannel.voiceMembers.filter((i) => i.id !== this.client.user.id && !i.bot).length) });
 | 
			
		||||
        await player.player.stop(this.channel.guild.id);
 | 
			
		||||
        skipVotes.set(this.channel.guild.id, { count: 0, ids: [], max: Math.min(3, player.voiceChannel.voiceMembers.filter((i) => i.id !== this.client.user.id && !i.bot).length) });
 | 
			
		||||
      } else {
 | 
			
		||||
        skipVotes.set(this.message.channel.guild.id, newObject);
 | 
			
		||||
        skipVotes.set(this.channel.guild.id, newObject);
 | 
			
		||||
        return `🔊 Voted to skip song (${votes.count + 1}/${votes.max} people have voted).`;
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      await player.player.stop(this.message.channel.guild.id);
 | 
			
		||||
      await player.player.stop(this.channel.guild.id);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,19 +3,19 @@ import MusicCommand from "../../classes/musicCommand.js";
 | 
			
		|||
 | 
			
		||||
class StopCommand extends MusicCommand {
 | 
			
		||||
  async run() {
 | 
			
		||||
    if (!this.message.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.message.member.voiceState.channelID) return "You need to be in a voice channel first!";
 | 
			
		||||
    if (!this.message.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
 | 
			
		||||
    if (!this.connection) {
 | 
			
		||||
      await manager.leave(this.message.channel.guild.id);
 | 
			
		||||
      await manager.leave(this.channel.guild.id);
 | 
			
		||||
      return "🔊 The current voice channel session has ended.";
 | 
			
		||||
    }
 | 
			
		||||
    if (this.connection.host !== this.message.author.id && !this.message.member.permissions.has("manageChannels")) return "Only the current voice session host can stop the music!";
 | 
			
		||||
    await manager.leave(this.message.channel.guild.id);
 | 
			
		||||
    if (this.connection.host !== this.author.id && !this.message.member.permissions.has("manageChannels")) return "Only the current voice session host can stop the music!";
 | 
			
		||||
    await manager.leave(this.channel.guild.id);
 | 
			
		||||
    const connection = this.connection.player;
 | 
			
		||||
    await connection.destroy();
 | 
			
		||||
    players.delete(this.message.channel.guild.id);
 | 
			
		||||
    queues.delete(this.message.channel.guild.id);
 | 
			
		||||
    players.delete(this.channel.guild.id);
 | 
			
		||||
    queues.delete(this.channel.guild.id);
 | 
			
		||||
    return "🔊 The current voice channel session has ended.";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,37 +6,37 @@ import Command from "../../classes/command.js";
 | 
			
		|||
class TagsCommand extends Command {
 | 
			
		||||
  // todo: find a way to split this into subcommands
 | 
			
		||||
  async run() {
 | 
			
		||||
    if (!this.message.channel.guild) return "This command only works in servers!";
 | 
			
		||||
    if (!this.channel.guild) return "This command only works in servers!";
 | 
			
		||||
 | 
			
		||||
    if (this.args.length === 0) return "You need to provide the name of the tag you want to view!";
 | 
			
		||||
    const blacklist = ["create", "add", "edit", "remove", "delete", "list", "random", "own", "owner"];
 | 
			
		||||
    if (this.args[0].toLowerCase() === "create" || this.args[0].toLowerCase() === "add") {
 | 
			
		||||
      if (this.args[1] === undefined) return "You need to provide the name of the tag you want to add!";
 | 
			
		||||
      if (blacklist.includes(this.args[1].toLowerCase())) return "You can't make a tag with that name!";
 | 
			
		||||
      const getResult = await database.getTag(this.message.channel.guild.id, this.args[1].toLowerCase());
 | 
			
		||||
      const getResult = await database.getTag(this.channel.guild.id, this.args[1].toLowerCase());
 | 
			
		||||
      if (getResult) return "This tag already exists!";
 | 
			
		||||
      const result = await this.setTag(this.args.slice(2).join(" "), this.args[1].toLowerCase(), this.message);
 | 
			
		||||
      if (result) return result;
 | 
			
		||||
      return `The tag \`${this.args[1].toLowerCase()}\` has been added!`;
 | 
			
		||||
    } else if (this.args[0].toLowerCase() === "delete" || this.args[0].toLowerCase() === "remove") {
 | 
			
		||||
      if (this.args[1] === undefined) return "You need to provide the name of the tag you want to delete!";
 | 
			
		||||
      const getResult = await database.getTag(this.message.channel.guild.id, this.args[1].toLowerCase());
 | 
			
		||||
      const getResult = await database.getTag(this.channel.guild.id, this.args[1].toLowerCase());
 | 
			
		||||
      if (!getResult) return "This tag doesn't exist!";
 | 
			
		||||
      const owners = process.env.OWNER.split(",");
 | 
			
		||||
      if (getResult.author !== this.message.author.id && !this.message.member.permissions.has("manageMessages") && !owners.includes(this.message.author.id)) return "You don't own this tag!";
 | 
			
		||||
      await database.removeTag(this.args[1].toLowerCase(), this.message.channel.guild);
 | 
			
		||||
      if (getResult.author !== this.author.id && !this.message.member.permissions.has("manageMessages") && !owners.includes(this.author.id)) return "You don't own this tag!";
 | 
			
		||||
      await database.removeTag(this.args[1].toLowerCase(), this.channel.guild);
 | 
			
		||||
      return `The tag \`${this.args[1].toLowerCase()}\` has been deleted!`;
 | 
			
		||||
    } else if (this.args[0].toLowerCase() === "edit") {
 | 
			
		||||
      if (this.args[1] === undefined) return "You need to provide the name of the tag you want to edit!";
 | 
			
		||||
      const getResult = await database.getTag(this.message.channel.guild.id, this.args[1].toLowerCase());
 | 
			
		||||
      const getResult = await database.getTag(this.channel.guild.id, this.args[1].toLowerCase());
 | 
			
		||||
      if (!getResult) return "This tag doesn't exist!";
 | 
			
		||||
      const owners = process.env.OWNER.split(",");
 | 
			
		||||
      if (getResult.author !== this.message.author.id && !this.message.member.permissions.has("manageMessages") && !owners.includes(this.message.author.id)) return "You don't own this tag!";
 | 
			
		||||
      if (getResult.author !== this.author.id && !this.message.member.permissions.has("manageMessages") && !owners.includes(this.author.id)) return "You don't own this tag!";
 | 
			
		||||
      await this.setTag(this.args.slice(2).join(" "), this.args[1].toLowerCase(), this.message, true);
 | 
			
		||||
      return `The tag \`${this.args[1].toLowerCase()}\` has been edited!`;
 | 
			
		||||
    } else if (this.args[0].toLowerCase() === "own" || this.args[0].toLowerCase() === "owner") {
 | 
			
		||||
      if (this.args[1] === undefined) return "You need to provide the name of the tag you want to check the owner of!";
 | 
			
		||||
      const getResult = await database.getTag(this.message.channel.guild.id, this.args[1].toLowerCase());
 | 
			
		||||
      const getResult = await database.getTag(this.channel.guild.id, this.args[1].toLowerCase());
 | 
			
		||||
      if (!getResult) return "This tag doesn't exist!";
 | 
			
		||||
      const user = await this.ipc.fetchUser(getResult.author);
 | 
			
		||||
      if (!user) {
 | 
			
		||||
| 
						 | 
				
			
			@ -50,8 +50,8 @@ class TagsCommand extends Command {
 | 
			
		|||
        return `This tag is owned by **${user.username}#${user.discriminator}** (\`${getResult.author}\`).`;
 | 
			
		||||
      }
 | 
			
		||||
    } else if (this.args[0].toLowerCase() === "list") {
 | 
			
		||||
      if (!this.message.channel.permissionsOf(this.client.user.id).has("embedLinks")) return "I don't have the `Embed Links` permission!";
 | 
			
		||||
      const tagList = await database.getTags(this.message.channel.guild.id);
 | 
			
		||||
      if (!this.channel.permissionsOf(this.client.user.id).has("embedLinks")) return "I don't have the `Embed Links` permission!";
 | 
			
		||||
      const tagList = await database.getTags(this.channel.guild.id);
 | 
			
		||||
      const embeds = [];
 | 
			
		||||
      const groups = Object.keys(tagList).map((item, index) => {
 | 
			
		||||
        return index % 15 === 0 ? Object.keys(tagList).slice(index, index + 15) : null;
 | 
			
		||||
| 
						 | 
				
			
			@ -68,8 +68,8 @@ class TagsCommand extends Command {
 | 
			
		|||
            },
 | 
			
		||||
            description: value.join("\n"),
 | 
			
		||||
            author: {
 | 
			
		||||
              name: this.message.author.username,
 | 
			
		||||
              icon_url: this.message.author.avatarURL
 | 
			
		||||
              name: this.author.username,
 | 
			
		||||
              icon_url: this.author.avatarURL
 | 
			
		||||
            }
 | 
			
		||||
          }]
 | 
			
		||||
        });
 | 
			
		||||
| 
						 | 
				
			
			@ -77,10 +77,10 @@ class TagsCommand extends Command {
 | 
			
		|||
      if (embeds.length === 0) return "I couldn't find any tags!";
 | 
			
		||||
      return paginator(this.client, this.message, embeds);
 | 
			
		||||
    } else if (this.args[0].toLowerCase() === "random") {
 | 
			
		||||
      const tagList = await database.getTags(this.message.channel.guild.id);
 | 
			
		||||
      const tagList = await database.getTags(this.channel.guild.id);
 | 
			
		||||
      return tagList[random(Object.keys(tagList))].content;
 | 
			
		||||
    } else {
 | 
			
		||||
      const getResult = await database.getTag(this.message.channel.guild.id, this.args[0].toLowerCase());
 | 
			
		||||
      const getResult = await database.getTag(this.channel.guild.id, this.args[0].toLowerCase());
 | 
			
		||||
      if (!getResult) return "This tag doesn't exist!";
 | 
			
		||||
      if (getResult.content.length > 2000) {
 | 
			
		||||
        return {
 | 
			
		||||
| 
						 | 
				
			
			@ -107,7 +107,7 @@ class TagsCommand extends Command {
 | 
			
		|||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static description = {
 | 
			
		||||
  /*static description = {
 | 
			
		||||
    default: "Gets a tag",
 | 
			
		||||
    add: "Adds a tag",
 | 
			
		||||
    delete: "Deletes a tag",
 | 
			
		||||
| 
						 | 
				
			
			@ -115,7 +115,8 @@ class TagsCommand extends Command {
 | 
			
		|||
    list: "Lists all tags in the server",
 | 
			
		||||
    random: "Gets a random tag",
 | 
			
		||||
    owner: "Gets the owner of a tag"
 | 
			
		||||
  };
 | 
			
		||||
  };*/
 | 
			
		||||
  static description = "Manage tags";
 | 
			
		||||
  static aliases = ["t", "tag", "ta"];
 | 
			
		||||
  static arguments = {
 | 
			
		||||
    default: ["[name]"],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,14 +17,14 @@ export default async (client, cluster, worker, ipc, interaction) => {
 | 
			
		|||
  const invoker = interaction.member ?? interaction.user;
 | 
			
		||||
 | 
			
		||||
  // actually run the command
 | 
			
		||||
  logger.log("log", `${invoker.username} (${invoker.id}) ran command ${command}`);
 | 
			
		||||
  logger.log("log", `${invoker.username} (${invoker.id}) ran slash command ${command}`);
 | 
			
		||||
  try {
 | 
			
		||||
    await database.addCount(command);
 | 
			
		||||
    // eslint-disable-next-line no-unused-vars
 | 
			
		||||
    const commandClass = new cmd(client, cluster, worker, ipc, { type: "application", interaction });
 | 
			
		||||
    const result = await commandClass.run();
 | 
			
		||||
    if (typeof result === "string" || (typeof result === "object" && result.embeds)) {
 | 
			
		||||
      await interaction.createMessage(result);
 | 
			
		||||
      await interaction[interaction.acknowledged ? "editOriginalMessage" : "createMessage"](result);
 | 
			
		||||
    } else if (typeof result === "object" && result.file) {
 | 
			
		||||
      let fileSize = 8388119;
 | 
			
		||||
      if (interaction.channel.guild) {
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +42,7 @@ export default async (client, cluster, worker, ipc, interaction) => {
 | 
			
		|||
          const filename = `${Math.random().toString(36).substring(2, 15)}.${result.name.split(".")[1]}`;
 | 
			
		||||
          await promises.writeFile(`${process.env.TEMPDIR}/${filename}`, result.file);
 | 
			
		||||
          const imageURL = `${process.env.TMP_DOMAIN || "https://tmp.projectlounge.pw"}/${filename}`;
 | 
			
		||||
          await interaction.createMessage({
 | 
			
		||||
          await interaction[interaction.acknowledged ? "editOriginalMessage" : "createMessage"]({
 | 
			
		||||
            embeds: [{
 | 
			
		||||
              color: 16711680,
 | 
			
		||||
              title: "Here's your image!",
 | 
			
		||||
| 
						 | 
				
			
			@ -56,30 +56,26 @@ export default async (client, cluster, worker, ipc, interaction) => {
 | 
			
		|||
            }]
 | 
			
		||||
          });
 | 
			
		||||
        } else {
 | 
			
		||||
          await interaction.createMessage("The resulting image was more than 8MB in size, so I can't upload it.");
 | 
			
		||||
          await interaction[interaction.acknowledged ? "editOriginalMessage" : "createMessage"]("The resulting image was more than 8MB in size, so I can't upload it.");
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        await interaction.createMessage({
 | 
			
		||||
          content: result.text ? result.text : undefined
 | 
			
		||||
        }, result);
 | 
			
		||||
        await interaction[interaction.acknowledged ? "editOriginalMessage" : "createMessage"](result.text ? result.text : {}, result);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    if (error.toString().includes("Request entity too large")) {
 | 
			
		||||
      await interaction.createMessage("The resulting file was too large to upload. Try again with a smaller image if possible.");
 | 
			
		||||
      await interaction[interaction.acknowledged ? "editOriginalMessage" : "createMessage"]("The resulting file was too large to upload. Try again with a smaller image if possible.");
 | 
			
		||||
    } else if (error.toString().includes("Job ended prematurely")) {
 | 
			
		||||
      await interaction.createMessage("Something happened to the image servers before I could receive the image. Try running your command again.");
 | 
			
		||||
      await interaction[interaction.acknowledged ? "editOriginalMessage" : "createMessage"]("Something happened to the image servers before I could receive the image. Try running your command again.");
 | 
			
		||||
    } else if (error.toString().includes("Timed out")) {
 | 
			
		||||
      await interaction.createMessage("The request timed out before I could download that image. Try uploading your image somewhere else or reducing its size.");
 | 
			
		||||
      await interaction[interaction.acknowledged ? "editOriginalMessage" : "createMessage"]("The request timed out before I could download that image. Try uploading your image somewhere else or reducing its size.");
 | 
			
		||||
    } else {
 | 
			
		||||
      logger.error(`Error occurred with slash command ${command} with arguments ${interaction.data.options}: ${error.toString()}`);
 | 
			
		||||
      logger.error(`Error occurred with slash command ${command} with arguments ${JSON.stringify(interaction.data.options)}: ${JSON.stringify(error)}`);
 | 
			
		||||
      try {
 | 
			
		||||
        await interaction.createMessage({
 | 
			
		||||
          content: "Uh oh! I ran into an error while running this command. Please report the content of the attached file at the following link or on the esmBot Support server: <https://github.com/esmBot/esmBot/issues>"
 | 
			
		||||
        }, [{
 | 
			
		||||
        await interaction[interaction.acknowledged ? "editOriginalMessage" : "createMessage"]("Uh oh! I ran into an error while running this command. Please report the content of the attached file at the following link or on the esmBot Support server: <https://github.com/esmBot/esmBot/issues>", {
 | 
			
		||||
          file: `Message: ${await clean(error)}\n\nStack Trace: ${await clean(error.stack)}`,
 | 
			
		||||
          name: "error.txt"
 | 
			
		||||
        }]);
 | 
			
		||||
        });
 | 
			
		||||
      } catch { /* silently ignore */ }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -85,7 +85,7 @@ export default async (client, cluster, worker, ipc, message) => {
 | 
			
		|||
  if (!cmd) return;
 | 
			
		||||
 | 
			
		||||
  // actually run the command
 | 
			
		||||
  log("log", `${message.author.username} (${message.author.id}) ran command ${command}`);
 | 
			
		||||
  log("log", `${message.author.username} (${message.author.id}) ran classic command ${command}`);
 | 
			
		||||
  const reference = {
 | 
			
		||||
    messageReference: {
 | 
			
		||||
      channelID: message.channel.id,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										15
									
								
								shard.js
									
										
									
									
									
								
							
							
						
						
									
										15
									
								
								shard.js
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -14,7 +14,7 @@ import { checkStatus, connect, status, connected } from "./utils/soundplayer.js"
 | 
			
		|||
// database stuff
 | 
			
		||||
import database from "./utils/database.js";
 | 
			
		||||
// command collections
 | 
			
		||||
import { paths } from "./utils/collections.js";
 | 
			
		||||
import { paths, info } from "./utils/collections.js";
 | 
			
		||||
// playing messages
 | 
			
		||||
const { messages } = JSON.parse(readFileSync(new URL("./messages.json", import.meta.url)));
 | 
			
		||||
// other stuff
 | 
			
		||||
| 
						 | 
				
			
			@ -35,11 +35,19 @@ class Shard extends BaseClusterWorker {
 | 
			
		|||
  async init() {
 | 
			
		||||
    // register commands and their info
 | 
			
		||||
    const soundStatus = await checkStatus();
 | 
			
		||||
    const commandArray = [];
 | 
			
		||||
    log("info", "Attempting to load commands...");
 | 
			
		||||
    for await (const commandFile of this.getFiles(resolve(dirname(fileURLToPath(import.meta.url)), "./commands/"))) {
 | 
			
		||||
      log("log", `Loading command from ${commandFile}...`);
 | 
			
		||||
      try {
 | 
			
		||||
        await load(commandFile, soundStatus);
 | 
			
		||||
        const name = await load(this.bot, this.clusterID, this.workerID, this.ipc, commandFile, soundStatus);
 | 
			
		||||
        const commandInfo = info.get(name);
 | 
			
		||||
        if (commandInfo && commandInfo.slashAllowed) commandArray.push({
 | 
			
		||||
          name,
 | 
			
		||||
          type: 1,
 | 
			
		||||
          description: commandInfo.description,
 | 
			
		||||
          options: commandInfo.flags
 | 
			
		||||
        });
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        error(`Failed to register command from ${commandFile}: ${e}`);
 | 
			
		||||
      }
 | 
			
		||||
| 
						 | 
				
			
			@ -47,6 +55,7 @@ class Shard extends BaseClusterWorker {
 | 
			
		|||
    log("info", "Finished loading commands.");
 | 
			
		||||
 | 
			
		||||
    await database.setup(this.ipc);
 | 
			
		||||
    await this.bot.bulkEditCommands(commandArray);
 | 
			
		||||
 | 
			
		||||
    // register events
 | 
			
		||||
    log("info", "Attempting to load events...");
 | 
			
		||||
| 
						 | 
				
			
			@ -71,7 +80,7 @@ class Shard extends BaseClusterWorker {
 | 
			
		|||
    this.ipc.register("reload", async (message) => {
 | 
			
		||||
      const path = paths.get(message);
 | 
			
		||||
      if (!path) return this.ipc.broadcast("reloadFail", { result: "I couldn't find that command!" });
 | 
			
		||||
      const result = await load(path, await checkStatus());
 | 
			
		||||
      const result = await load(this.bot, this.clusterID, this.workerID, this.ipc, path, await checkStatus());
 | 
			
		||||
      if (result) return this.ipc.broadcast("reloadFail", { result });
 | 
			
		||||
      return this.ipc.broadcast("reloadSuccess");
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,22 +4,28 @@ import { log } from "./logger.js";
 | 
			
		|||
let queryValue = 0;
 | 
			
		||||
 | 
			
		||||
// load command into memory
 | 
			
		||||
export async function load(command, soundStatus) {
 | 
			
		||||
export async function load(client, cluster, worker, ipc, command, soundStatus) {
 | 
			
		||||
  const { default: props } = await import(`${command}?v=${queryValue}`);
 | 
			
		||||
  queryValue++;
 | 
			
		||||
  if (props.requires.includes("sound") && soundStatus) return log("warn", `Failed to connect to some Lavalink nodes, skipped loading command ${command}...`);
 | 
			
		||||
  if (props.requires.includes("sound") && soundStatus) {
 | 
			
		||||
    log("warn", `Failed to connect to some Lavalink nodes, skipped loading command ${command}...`);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  const commandArray = command.split("/");
 | 
			
		||||
  const commandName = commandArray[commandArray.length - 1].split(".")[0];
 | 
			
		||||
  
 | 
			
		||||
  paths.set(commandName, command);
 | 
			
		||||
  commands.set(commandName, props);
 | 
			
		||||
 | 
			
		||||
  const propsInstance = new props(client, cluster, worker, ipc, {});
 | 
			
		||||
 | 
			
		||||
  info.set(commandName, {
 | 
			
		||||
    category: commandArray[commandArray.length - 2],
 | 
			
		||||
    description: props.description,
 | 
			
		||||
    aliases: props.aliases,
 | 
			
		||||
    params: props.arguments,
 | 
			
		||||
    flags: props.flags
 | 
			
		||||
    flags: propsInstance.flags ?? props.flags,
 | 
			
		||||
    slashAllowed: props.slashAllowed
 | 
			
		||||
  });
 | 
			
		||||
  
 | 
			
		||||
  if (props.aliases) {
 | 
			
		||||
| 
						 | 
				
			
			@ -28,5 +34,5 @@ export async function load(command, soundStatus) {
 | 
			
		|||
      paths.set(alias, command);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return false;
 | 
			
		||||
  return commandName;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -106,10 +106,10 @@ const checkImages = async (message, extraReturnTypes, video, sticker) => {
 | 
			
		|||
      // embeds can vary in types, we check for tenor gifs first
 | 
			
		||||
      if (message.embeds[0].type === "gifv") {
 | 
			
		||||
        type = await getImage(message.embeds[0].video.url, message.embeds[0].url, video, extraReturnTypes, true);
 | 
			
		||||
      // then we check for other image types
 | 
			
		||||
        // then we check for other image types
 | 
			
		||||
      } else if ((message.embeds[0].type === "video" || message.embeds[0].type === "image") && message.embeds[0].thumbnail) {
 | 
			
		||||
        type = await getImage(message.embeds[0].thumbnail.proxy_url, message.embeds[0].thumbnail.url, video, extraReturnTypes);
 | 
			
		||||
      // finally we check both possible image fields for "generic" embeds
 | 
			
		||||
        // finally we check both possible image fields for "generic" embeds
 | 
			
		||||
      } else if (message.embeds[0].type === "rich" || message.embeds[0].type === "article") {
 | 
			
		||||
        if (message.embeds[0].thumbnail) {
 | 
			
		||||
          type = await getImage(message.embeds[0].thumbnail.proxy_url, message.embeds[0].thumbnail.url, video, extraReturnTypes);
 | 
			
		||||
| 
						 | 
				
			
			@ -117,7 +117,7 @@ const checkImages = async (message, extraReturnTypes, video, sticker) => {
 | 
			
		|||
          type = await getImage(message.embeds[0].image.proxy_url, message.embeds[0].image.url, video, extraReturnTypes);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    // then check the attachments
 | 
			
		||||
      // then check the attachments
 | 
			
		||||
    } else if (message.attachments.length !== 0 && message.attachments[0].width) {
 | 
			
		||||
      type = await getImage(message.attachments[0].proxy_url, message.attachments[0].url, video);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -127,20 +127,34 @@ const checkImages = async (message, extraReturnTypes, video, sticker) => {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
// this checks for the latest message containing an image and returns the url of the image
 | 
			
		||||
export default async (client, cmdMessage, extraReturnTypes = false, video = false, sticker = false) => {
 | 
			
		||||
  // we start by checking if the message is a reply to another message
 | 
			
		||||
  if (cmdMessage.messageReference) {
 | 
			
		||||
    const replyMessage = await client.getMessage(cmdMessage.messageReference.channelID, cmdMessage.messageReference.messageID).catch(() => undefined);
 | 
			
		||||
    if (replyMessage) {
 | 
			
		||||
      const replyResult = await checkImages(replyMessage, extraReturnTypes, video, sticker);
 | 
			
		||||
      if (replyResult !== false) return replyResult;
 | 
			
		||||
export default async (client, cmdMessage, interaction, options, extraReturnTypes = false, video = false, sticker = false) => {
 | 
			
		||||
  // we start by determining whether or not we're dealing with an interaction or a message
 | 
			
		||||
  if (interaction) {
 | 
			
		||||
    // we can get a raw attachment or a URL in the interaction itself
 | 
			
		||||
    if (options) {
 | 
			
		||||
      if (options.image) {
 | 
			
		||||
        const result = await getImage(options.image.proxy_url, options.image.url, video);
 | 
			
		||||
        if (result !== false) return result;
 | 
			
		||||
      } else if (options.link) {
 | 
			
		||||
        const result = await getImage(options.link, options.link, video);
 | 
			
		||||
        if (result !== false) return result;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    // check if the message is a reply to another message
 | 
			
		||||
    if (cmdMessage.messageReference) {
 | 
			
		||||
      const replyMessage = await client.getMessage(cmdMessage.messageReference.channelID, cmdMessage.messageReference.messageID).catch(() => undefined);
 | 
			
		||||
      if (replyMessage) {
 | 
			
		||||
        const replyResult = await checkImages(replyMessage, extraReturnTypes, video, sticker);
 | 
			
		||||
        if (replyResult !== false) return replyResult;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // then we check the current message
 | 
			
		||||
    const result = await checkImages(cmdMessage, extraReturnTypes, video, sticker);
 | 
			
		||||
    if (result !== false) return result;
 | 
			
		||||
  }
 | 
			
		||||
  // then we check the current message
 | 
			
		||||
  const result = await checkImages(cmdMessage, extraReturnTypes, video, sticker);
 | 
			
		||||
  if (result !== false) return result;
 | 
			
		||||
  // if there aren't any replies then iterate over the last few messages in the channel
 | 
			
		||||
  const messages = await client.getMessages(cmdMessage.channel.id);
 | 
			
		||||
  // if there aren't any replies or interaction attachments then iterate over the last few messages in the channel
 | 
			
		||||
  const messages = await client.getMessages((interaction ? interaction : cmdMessage).channel.id);
 | 
			
		||||
  // iterate over each message
 | 
			
		||||
  for (const message of messages) {
 | 
			
		||||
    const result = await checkImages(message, extraReturnTypes, video, sticker);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue