diff --git a/DOCUMENT.md b/DOCUMENT.md
index d04b5d0..c506018 100644
--- a/DOCUMENT.md
+++ b/DOCUMENT.md
@@ -230,18 +230,22 @@ And you can change the status 5 times every 20 seconds!
 
 ## Interaction
 
-Button Click (v1)
+Button Click
 
 ```js
-await Button.click(Message); // Message has button
+await Button.click(Message); // Message has button (v1)
+//
+await message.clickButton(buttonID); // Message has button (v2)
 ```
  
 
-Message Select Menu (v1)
+Message Select Menu
 
 ```js
-await MessageSelectMenu.select(Message, value); // Message has menu
+await MessageSelectMenu.select(Message, options); // Message has menu (v1)
 // value: ['value1', 'value2' , ...]
+await message.selectMenu(menuID, options) // If message has >= 2 menu
+await message.selectMenu(options) // If message has 1 menu
 ```
  
 
@@ -261,6 +265,7 @@ messageID: Message.id,
 await command.sendSlashCommand(Message, ['option1', 'option2']);
 // Eg: Slash /add role:123456789 user:987654321
 // value: ['123456789', '987654321']
+// Channel.sendSlashCommand(botID, commandName, options): Comming soon !
 ```
  
 
@@ -279,6 +284,7 @@ messageID: Message.id,
 author:  Message.author,
 */
 await command.sendContextMenu(Message);
+// Channel.sendContextMenu(botID, commandName): Comming soon !
 ```
  
 
diff --git a/package.json b/package.json
index 480699c..3822d9b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "discord.js-selfbot-v13",
-  "version": "1.1.5",
+  "version": "1.1.6",
   "description": "A unofficial discord.js fork for creating selfbots [Based on discord.js v13]",
   "main": "./src/index.js",
   "types": "./typings/index.d.ts",
diff --git a/src/structures/Message.js b/src/structures/Message.js
index 918edf5..8da1100 100644
--- a/src/structures/Message.js
+++ b/src/structures/Message.js
@@ -33,185 +33,197 @@ let deprecationEmittedForDeleted = false;
  * @extends {Base}
  */
 class Message extends Base {
-  constructor(client, data) {
-    super(client);
+	constructor(client, data) {
+		super(client);
 
-    /**
-     * The id of the channel the message was sent in
-     * @type {Snowflake}
-     */
-    this.channelId = data.channel_id;
+		/**
+		 * The id of the channel the message was sent in
+		 * @type {Snowflake}
+		 */
+		this.channelId = data.channel_id;
 
-    /**
-     * The id of the guild the message was sent in, if any
-     * @type {?Snowflake}
-     */
-    this.guildId = data.guild_id ?? this.channel?.guild?.id ?? null;
+		/**
+		 * The id of the guild the message was sent in, if any
+		 * @type {?Snowflake}
+		 */
+		this.guildId = data.guild_id ?? this.channel?.guild?.id ?? null;
 
-    this._patch(data);
-  }
+		this._patch(data);
+	}
 
-  _patch(data) {
-    /**
-     * The message's id
-     * @type {Snowflake}
-     */
-    this.id = data.id;
+	_patch(data) {
+		/**
+		 * The message's id
+		 * @type {Snowflake}
+		 */
+		this.id = data.id;
 
-    /**
-     * The timestamp the message was sent at
-     * @type {number}
-     */
-    this.createdTimestamp = SnowflakeUtil.timestampFrom(this.id);
+		/**
+		 * The timestamp the message was sent at
+		 * @type {number}
+		 */
+		this.createdTimestamp = SnowflakeUtil.timestampFrom(this.id);
 
-    if ('type' in data) {
-      /**
-       * The type of the message
-       * @type {?MessageType}
-       */
-      this.type = MessageTypes[data.type];
+		if ('type' in data) {
+			/**
+			 * The type of the message
+			 * @type {?MessageType}
+			 */
+			this.type = MessageTypes[data.type];
 
-      /**
-       * Whether or not this message was sent by Discord, not actually a user (e.g. pin notifications)
-       * @type {?boolean}
-       */
-      this.system = SystemMessageTypes.includes(this.type);
-    } else {
-      this.system ??= null;
-      this.type ??= null;
-    }
+			/**
+			 * Whether or not this message was sent by Discord, not actually a user (e.g. pin notifications)
+			 * @type {?boolean}
+			 */
+			this.system = SystemMessageTypes.includes(this.type);
+		} else {
+			this.system ??= null;
+			this.type ??= null;
+		}
 
-    if ('content' in data) {
-      /**
-       * The content of the message
-       * @type {?string}
-       */
-      this.content = data.content;
-    } else {
-      this.content ??= null;
-    }
+		if ('content' in data) {
+			/**
+			 * The content of the message
+			 * @type {?string}
+			 */
+			this.content = data.content;
+		} else {
+			this.content ??= null;
+		}
 
-    if ('author' in data) {
-      /**
-       * The author of the message
-       * @type {?User}
-       */
-      this.author = this.client.users._add(data.author, !data.webhook_id);
-    } else {
-      this.author ??= null;
-    }
+		if ('author' in data) {
+			/**
+			 * The author of the message
+			 * @type {?User}
+			 */
+			this.author = this.client.users._add(data.author, !data.webhook_id);
+		} else {
+			this.author ??= null;
+		}
 
-    if ('pinned' in data) {
-      /**
-       * Whether or not this message is pinned
-       * @type {?boolean}
-       */
-      this.pinned = Boolean(data.pinned);
-    } else {
-      this.pinned ??= null;
-    }
+		if ('pinned' in data) {
+			/**
+			 * Whether or not this message is pinned
+			 * @type {?boolean}
+			 */
+			this.pinned = Boolean(data.pinned);
+		} else {
+			this.pinned ??= null;
+		}
 
-    if ('tts' in data) {
-      /**
-       * Whether or not the message was Text-To-Speech
-       * @type {?boolean}
-       */
-      this.tts = data.tts;
-    } else {
-      this.tts ??= null;
-    }
+		if ('tts' in data) {
+			/**
+			 * Whether or not the message was Text-To-Speech
+			 * @type {?boolean}
+			 */
+			this.tts = data.tts;
+		} else {
+			this.tts ??= null;
+		}
 
-    if ('nonce' in data) {
-      /**
-       * A random number or string used for checking message delivery
-       * This is only received after the message was sent successfully, and
-       * lost if re-fetched
-       * @type {?string}
-       */
-      this.nonce = data.nonce;
-    } else {
-      this.nonce ??= null;
-    }
+		if ('nonce' in data) {
+			/**
+			 * A random number or string used for checking message delivery
+			 * This is only received after the message was sent successfully, and
+			 * lost if re-fetched
+			 * @type {?string}
+			 */
+			this.nonce = data.nonce;
+		} else {
+			this.nonce ??= null;
+		}
 
-    if ('embeds' in data) {
-      /**
-       * A list of embeds in the message - e.g. YouTube Player
-       * @type {MessageEmbed[]}
-       */
-      this.embeds = data.embeds.map(e => new Embed(e, true));
-    } else {
-      this.embeds = this.embeds?.slice() ?? [];
-    }
+		if ('embeds' in data) {
+			/**
+			 * A list of embeds in the message - e.g. YouTube Player
+			 * @type {MessageEmbed[]}
+			 */
+			this.embeds = data.embeds.map((e) => new Embed(e, true));
+		} else {
+			this.embeds = this.embeds?.slice() ?? [];
+		}
 
-    if ('components' in data) {
-      /**
-       * A list of MessageActionRows in the message
-       * @type {MessageActionRow[]}
-       */
-      this.components = data.components.map(c => BaseMessageComponent.create(c, this.client));
-    } else {
-      this.components = this.components?.slice() ?? [];
-    }
+		if ('components' in data) {
+			/**
+			 * A list of MessageActionRows in the message
+			 * @type {MessageActionRow[]}
+			 */
+			this.components = data.components.map((c) =>
+				BaseMessageComponent.create(c, this.client),
+			);
+		} else {
+			this.components = this.components?.slice() ?? [];
+		}
 
-    if ('attachments' in data) {
-      /**
-       * A collection of attachments in the message - e.g. Pictures - mapped by their ids
-       * @type {Collection}
-       */
-      this.attachments = new Collection();
-      if (data.attachments) {
-        for (const attachment of data.attachments) {
-          this.attachments.set(attachment.id, new MessageAttachment(attachment.url, attachment.filename, attachment));
-        }
-      }
-    } else {
-      this.attachments = new Collection(this.attachments);
-    }
+		if ('attachments' in data) {
+			/**
+			 * A collection of attachments in the message - e.g. Pictures - mapped by their ids
+			 * @type {Collection}
+			 */
+			this.attachments = new Collection();
+			if (data.attachments) {
+				for (const attachment of data.attachments) {
+					this.attachments.set(
+						attachment.id,
+						new MessageAttachment(
+							attachment.url,
+							attachment.filename,
+							attachment,
+						),
+					);
+				}
+			}
+		} else {
+			this.attachments = new Collection(this.attachments);
+		}
 
-    if ('sticker_items' in data || 'stickers' in data) {
-      /**
-       * A collection of stickers in the message
-       * @type {Collection}
-       */
-      this.stickers = new Collection(
-        (data.sticker_items ?? data.stickers)?.map(s => [s.id, new Sticker(this.client, s)]),
-      );
-    } else {
-      this.stickers = new Collection(this.stickers);
-    }
+		if ('sticker_items' in data || 'stickers' in data) {
+			/**
+			 * A collection of stickers in the message
+			 * @type {Collection}
+			 */
+			this.stickers = new Collection(
+				(data.sticker_items ?? data.stickers)?.map((s) => [
+					s.id,
+					new Sticker(this.client, s),
+				]),
+			);
+		} else {
+			this.stickers = new Collection(this.stickers);
+		}
 
-    // Discord sends null if the message has not been edited
-    if (data.edited_timestamp) {
-      /**
-       * The timestamp the message was last edited at (if applicable)
-       * @type {?number}
-       */
-      this.editedTimestamp = new Date(data.edited_timestamp).getTime();
-    } else {
-      this.editedTimestamp ??= null;
-    }
+		// Discord sends null if the message has not been edited
+		if (data.edited_timestamp) {
+			/**
+			 * The timestamp the message was last edited at (if applicable)
+			 * @type {?number}
+			 */
+			this.editedTimestamp = new Date(data.edited_timestamp).getTime();
+		} else {
+			this.editedTimestamp ??= null;
+		}
 
-    if ('reactions' in data) {
-      /**
-       * A manager of the reactions belonging to this message
-       * @type {ReactionManager}
-       */
-      this.reactions = new ReactionManager(this);
-      if (data.reactions?.length > 0) {
-        for (const reaction of data.reactions) {
-          this.reactions._add(reaction);
-        }
-      }
-    } else {
-      this.reactions ??= new ReactionManager(this);
-    }
+		if ('reactions' in data) {
+			/**
+			 * A manager of the reactions belonging to this message
+			 * @type {ReactionManager}
+			 */
+			this.reactions = new ReactionManager(this);
+			if (data.reactions?.length > 0) {
+				for (const reaction of data.reactions) {
+					this.reactions._add(reaction);
+				}
+			}
+		} else {
+			this.reactions ??= new ReactionManager(this);
+		}
 
-    if (!this.mentions) {
-      /**
-       * All valid mentions that the message contains
-       * @type {MessageMentions}
-       */
-      if (!data.mentions)
+		if (!this.mentions) {
+			/**
+			 * All valid mentions that the message contains
+			 * @type {MessageMentions}
+			 */
+			if (!data.mentions)
 				this.mentions = new Mentions(
 					this,
 					data.mentions,
@@ -220,768 +232,860 @@ class Message extends Base {
 					data.mention_channels,
 					data.referenced_message?.author,
 				);
-      else data.mentions instanceof Mentions ? this.mentions = data.mentions : this.mentions = null;
-    } else {
-      this.mentions = new Mentions(
-        this,
-        data.mentions ?? this.mentions.users,
-        data.mention_roles ?? this.mentions.roles,
-        data.mention_everyone ?? this.mentions.everyone,
-        data.mention_channels ?? this.mentions.crosspostedChannels,
-        data.referenced_message?.author ?? this.mentions.repliedUser,
-      );
-    }
-
-    if ('webhook_id' in data) {
-      /**
-       * The id of the webhook that sent the message, if applicable
-       * @type {?Snowflake}
-       */
-      this.webhookId = data.webhook_id;
-    } else {
-      this.webhookId ??= null;
-    }
-
-    if ('application' in data) {
-      /**
-       * Supplemental application information for group activities
-       * @type {?ClientApplication}
-       */
-      this.groupActivityApplication = new ClientApplication(this.client, data.application);
-    } else {
-      this.groupActivityApplication ??= null;
-    }
-
-    if ('application_id' in data) {
-      /**
-       * The id of the application of the interaction that sent this message, if any
-       * @type {?Snowflake}
-       */
-      this.applicationId = data.application_id;
-    } else {
-      this.applicationId ??= null;
-    }
-
-    if ('activity' in data) {
-      /**
-       * Group activity
-       * @type {?MessageActivity}
-       */
-      this.activity = {
-        partyId: data.activity.party_id,
-        type: data.activity.type,
-      };
-    } else {
-      this.activity ??= null;
-    }
-
-    if ('thread' in data) {
-      this.client.channels._add(data.thread, this.guild);
-    }
-
-    if (this.member && data.member) {
-      this.member._patch(data.member);
-    } else if (data.member && this.guild && this.author) {
-      this.guild.members._add(Object.assign(data.member, { user: this.author }));
-    }
-
-    if ('flags' in data) {
-      /**
-       * Flags that are applied to the message
-       * @type {Readonly}
-       */
-      this.flags = new MessageFlags(data.flags).freeze();
-    } else {
-      this.flags = new MessageFlags(this.flags).freeze();
-    }
-
-    /**
-     * Reference data sent in a message that contains ids identifying the referenced message.
-     * This can be present in the following types of message:
-     * * Crossposted messages (IS_CROSSPOST {@link MessageFlags.FLAGS message flag})
-     * * CHANNEL_FOLLOW_ADD
-     * * CHANNEL_PINNED_MESSAGE
-     * * REPLY
-     * * THREAD_STARTER_MESSAGE
-     * @see {@link https://discord.com/developers/docs/resources/channel#message-types}
-     * @typedef {Object} MessageReference
-     * @property {Snowflake} channelId The channel's id the message was referenced
-     * @property {?Snowflake} guildId The guild's id the message was referenced
-     * @property {?Snowflake} messageId The message's id that was referenced
-     */
-
-    if ('message_reference' in data) {
-      /**
-       * Message reference data
-       * @type {?MessageReference}
-       */
-      this.reference = {
-        channelId: data.message_reference.channel_id,
-        guildId: data.message_reference.guild_id,
-        messageId: data.message_reference.message_id,
-      };
-    } else {
-      this.reference ??= null;
-    }
-
-    if (data.referenced_message) {
-      this.channel?.messages._add({ guild_id: data.message_reference?.guild_id, ...data.referenced_message });
-    }
-
-    /**
-     * Partial data of the interaction that a message is a reply to
-     * @typedef {Object} MessageInteraction
-     * @property {Snowflake} id The interaction's id
-     * @property {InteractionType} type The type of the interaction
-     * @property {string} commandName The name of the interaction's application command
-     * @property {User} user The user that invoked the interaction
-     */
-
-    if (data.interaction) {
-      /**
-       * Partial data of the interaction that this message is a reply to
-       * @type {?MessageInteraction}
-       */
-      this.interaction = {
-        id: data.interaction.id,
-        type: InteractionTypes[data.interaction.type],
-        commandName: data.interaction.name,
-        user: this.client.users._add(data.interaction.user),
-      };
-    } else {
-      this.interaction ??= null;
-    }
-  }
-
-  /**
-   * Whether or not the structure has been deleted
-   * @type {boolean}
-   * @deprecated This will be removed in the next major version, see https://github.com/discordjs/discord.js/issues/7091
-   */
-  get deleted() {
-    if (!deprecationEmittedForDeleted) {
-      deprecationEmittedForDeleted = true;
-      process.emitWarning(
-        'Message#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
-        'DeprecationWarning',
-      );
-    }
-
-    return deletedMessages.has(this);
-  }
-
-  set deleted(value) {
-    if (!deprecationEmittedForDeleted) {
-      deprecationEmittedForDeleted = true;
-      process.emitWarning(
-        'Message#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
-        'DeprecationWarning',
-      );
-    }
-
-    if (value) deletedMessages.add(this);
-    else deletedMessages.delete(this);
-  }
-
-  /**
-   * The channel that the message was sent in
-   * @type {TextChannel|DMChannel|NewsChannel|ThreadChannel}
-   * @readonly
-   */
-  get channel() {
-    return this.client.channels.resolve(this.channelId);
-  }
-
-  /**
-   * Whether or not this message is a partial
-   * @type {boolean}
-   * @readonly
-   */
-  get partial() {
-    return typeof this.content !== 'string' || !this.author;
-  }
-
-  /**
-   * Represents the author of the message as a guild member.
-   * Only available if the message comes from a guild where the author is still a member
-   * @type {?GuildMember}
-   * @readonly
-   */
-  get member() {
-    return this.guild?.members.resolve(this.author) ?? null;
-  }
-
-  /**
-   * The time the message was sent at
-   * @type {Date}
-   * @readonly
-   */
-  get createdAt() {
-    return new Date(this.createdTimestamp);
-  }
-
-  /**
-   * The time the message was last edited at (if applicable)
-   * @type {?Date}
-   * @readonly
-   */
-  get editedAt() {
-    return this.editedTimestamp ? new Date(this.editedTimestamp) : null;
-  }
-
-  /**
-   * The guild the message was sent in (if in a guild channel)
-   * @type {?Guild}
-   * @readonly
-   */
-  get guild() {
-    return this.client.guilds.resolve(this.guildId) ?? this.channel?.guild ?? null;
-  }
-
-  /**
-   * Whether this message has a thread associated with it
-   * @type {boolean}
-   * @readonly
-   */
-  get hasThread() {
-    return this.flags.has(MessageFlags.FLAGS.HAS_THREAD);
-  }
-
-  /**
-   * The thread started by this message
-   * This property is not suitable for checking whether a message has a thread,
-   * use {@link Message#hasThread} instead.
-   * @type {?ThreadChannel}
-   * @readonly
-   */
-  get thread() {
-    return this.channel?.threads?.resolve(this.id) ?? null;
-  }
-
-  /**
-   * The URL to jump to this message
-   * @type {string}
-   * @readonly
-   */
-  get url() {
-    return `https://discord.com/channels/${this.guildId ?? '@me'}/${this.channelId}/${this.id}`;
-  }
-
-  /**
-   * The message contents with all mentions replaced by the equivalent text.
-   * If mentions cannot be resolved to a name, the relevant mention in the message content will not be converted.
-   * @type {?string}
-   * @readonly
-   */
-  get cleanContent() {
-    // eslint-disable-next-line eqeqeq
-    return this.content != null ? Util.cleanContent(this.content, this.channel) : null;
-  }
-
-  /**
-   * Creates a reaction collector.
-   * @param {ReactionCollectorOptions} [options={}] Options to send to the collector
-   * @returns {ReactionCollector}
-   * @example
-   * // Create a reaction collector
-   * const filter = (reaction, user) => reaction.emoji.name === '👌' && user.id === 'someId';
-   * const collector = message.createReactionCollector({ filter, time: 15_000 });
-   * collector.on('collect', r => console.log(`Collected ${r.emoji.name}`));
-   * collector.on('end', collected => console.log(`Collected ${collected.size} items`));
-   */
-  createReactionCollector(options = {}) {
-    return new ReactionCollector(this, options);
-  }
-
-  /**
-   * An object containing the same properties as CollectorOptions, but a few more:
-   * @typedef {ReactionCollectorOptions} AwaitReactionsOptions
-   * @property {string[]} [errors] Stop/end reasons that cause the promise to reject
-   */
-
-  /**
-   * Similar to createReactionCollector but in promise form.
-   * Resolves with a collection of reactions that pass the specified filter.
-   * @param {AwaitReactionsOptions} [options={}] Optional options to pass to the internal collector
-   * @returns {Promise>}
-   * @example
-   * // Create a reaction collector
-   * const filter = (reaction, user) => reaction.emoji.name === '👌' && user.id === 'someId'
-   * message.awaitReactions({ filter, time: 15_000 })
-   *   .then(collected => console.log(`Collected ${collected.size} reactions`))
-   *   .catch(console.error);
-   */
-  awaitReactions(options = {}) {
-    return new Promise((resolve, reject) => {
-      const collector = this.createReactionCollector(options);
-      collector.once('end', (reactions, reason) => {
-        if (options.errors?.includes(reason)) reject(reactions);
-        else resolve(reactions);
-      });
-    });
-  }
-
-  /**
-   * @typedef {CollectorOptions} MessageComponentCollectorOptions
-   * @property {MessageComponentType} [componentType] The type of component to listen for
-   * @property {number} [max] The maximum total amount of interactions to collect
-   * @property {number} [maxComponents] The maximum number of components to collect
-   * @property {number} [maxUsers] The maximum number of users to interact
-   */
-
-  /**
-   * Creates a message component interaction collector.
-   * @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector
-   * @returns {InteractionCollector}
-   * @example
-   * // Create a message component interaction collector
-   * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
-   * const collector = message.createMessageComponentCollector({ filter, time: 15_000 });
-   * collector.on('collect', i => console.log(`Collected ${i.customId}`));
-   * collector.on('end', collected => console.log(`Collected ${collected.size} items`));
-   */
-  createMessageComponentCollector(options = {}) {
-    return new InteractionCollector(this.client, {
-      ...options,
-      interactionType: InteractionTypes.MESSAGE_COMPONENT,
-      message: this,
-    });
-  }
-
-  /**
-   * An object containing the same properties as CollectorOptions, but a few more:
-   * @typedef {Object} AwaitMessageComponentOptions
-   * @property {CollectorFilter} [filter] The filter applied to this collector
-   * @property {number} [time] Time to wait for an interaction before rejecting
-   * @property {MessageComponentType} [componentType] The type of component interaction to collect
-   */
-
-  /**
-   * Collects a single component interaction that passes the filter.
-   * The Promise will reject if the time expires.
-   * @param {AwaitMessageComponentOptions} [options={}] Options to pass to the internal collector
-   * @returns {Promise}
-   * @example
-   * // Collect a message component interaction
-   * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
-   * message.awaitMessageComponent({ filter, time: 15_000 })
-   *   .then(interaction => console.log(`${interaction.customId} was clicked!`))
-   *   .catch(console.error);
-   */
-  awaitMessageComponent(options = {}) {
-    const _options = { ...options, max: 1 };
-    return new Promise((resolve, reject) => {
-      const collector = this.createMessageComponentCollector(_options);
-      collector.once('end', (interactions, reason) => {
-        const interaction = interactions.first();
-        if (interaction) resolve(interaction);
-        else reject(new Error('INTERACTION_COLLECTOR_ERROR', reason));
-      });
-    });
-  }
-
-  /**
-   * Whether the message is editable by the client user
-   * @type {boolean}
-   * @readonly
-   */
-  get editable() {
-    const precheck = Boolean(
-      this.author.id === this.client.user.id && !deletedMessages.has(this) && (!this.guild || this.channel?.viewable),
-    );
-    // Regardless of permissions thread messages cannot be edited if
-    // the thread is locked.
-    if (this.channel?.isThread()) {
-      return precheck && !this.channel.locked;
-    }
-    return precheck;
-  }
-
-  /**
-   * Whether the message is deletable by the client user
-   * @type {boolean}
-   * @readonly
-   */
-  get deletable() {
-    if (deletedMessages.has(this)) {
-      return false;
-    }
-    if (!this.guild) {
-      return this.author.id === this.client.user.id;
-    }
-    // DMChannel does not have viewable property, so check viewable after proved that message is on a guild.
-    if (!this.channel?.viewable) {
-      return false;
-    }
-
-    const permissions = this.channel?.permissionsFor(this.client.user);
-    if (!permissions) return false;
-    // This flag allows deleting even if timed out
-    if (permissions.has(Permissions.FLAGS.ADMINISTRATOR, false)) return true;
-
-    return Boolean(
-      this.author.id === this.client.user.id ||
-        (permissions.has(Permissions.FLAGS.MANAGE_MESSAGES, false) &&
-          this.guild.me.communicationDisabledUntilTimestamp < Date.now()),
-    );
-  }
-
-  /**
-   * Whether the message is pinnable by the client user
-   * @type {boolean}
-   * @readonly
-   */
-  get pinnable() {
-    const { channel } = this;
-    return Boolean(
-      !this.system &&
-        !deletedMessages.has(this) &&
-        (!this.guild ||
-          (channel?.viewable &&
-            channel?.permissionsFor(this.client.user)?.has(Permissions.FLAGS.MANAGE_MESSAGES, false))),
-    );
-  }
-
-  /**
-   * Fetches the Message this crosspost/reply/pin-add references, if available to the client
-   * @returns {Promise}
-   */
-  async fetchReference() {
-    if (!this.reference) throw new Error('MESSAGE_REFERENCE_MISSING');
-    const { channelId, messageId } = this.reference;
-    const channel = this.client.channels.resolve(channelId);
-    if (!channel) throw new Error('GUILD_CHANNEL_RESOLVE');
-    const message = await channel.messages.fetch(messageId);
-    return message;
-  }
-
-  /**
-   * Whether the message is crosspostable by the client user
-   * @type {boolean}
-   * @readonly
-   */
-  get crosspostable() {
-    const bitfield =
-      Permissions.FLAGS.SEND_MESSAGES |
-      (this.author.id === this.client.user.id ? Permissions.defaultBit : Permissions.FLAGS.MANAGE_MESSAGES);
-    const { channel } = this;
-    return Boolean(
-      channel?.type === 'GUILD_NEWS' &&
-        !this.flags.has(MessageFlags.FLAGS.CROSSPOSTED) &&
-        this.type === 'DEFAULT' &&
-        channel.viewable &&
-        channel.permissionsFor(this.client.user)?.has(bitfield, false) &&
-        !deletedMessages.has(this),
-    );
-  }
-
-  /**
-   * Options that can be passed into {@link Message#edit}.
-   * @typedef {Object} MessageEditOptions
-   * @property {?string} [content] Content to be edited
-   * @property {MessageEmbed[]|APIEmbed[]} [embeds] Embeds to be added/edited
-   * @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content
-   * @property {MessageFlags} [flags] Which flags to set for the message. Only `SUPPRESS_EMBEDS` can be edited.
-   * @property {MessageAttachment[]} [attachments] An array of attachments to keep,
-   * all attachments will be kept if omitted
-   * @property {FileOptions[]|BufferResolvable[]|MessageAttachment[]} [files] Files to add to the message
-   * @property {MessageActionRow[]|MessageActionRowOptions[]} [components]
-   * Action rows containing interactive components for the message (buttons, select menus)
-   */
-
-  /**
-   * Edits the content of the message.
-   * @param {string|MessagePayload|MessageEditOptions} options The options to provide
-   * @returns {Promise}
-   * @example
-   * // Update the content of a message
-   * message.edit('This is my new content!')
-   *   .then(msg => console.log(`Updated the content of a message to ${msg.content}`))
-   *   .catch(console.error);
-   */
-  edit(options) {
-    if (!this.channel) return Promise.reject(new Error('CHANNEL_NOT_CACHED'));
-    return this.channel.messages.edit(this, options);
-  }
-
-  /**
-   * Publishes a message in an announcement channel to all channels following it.
-   * @returns {Promise}
-   * @example
-   * // Crosspost a message
-   * if (message.channel.type === 'GUILD_NEWS') {
-   *   message.crosspost()
-   *     .then(() => console.log('Crossposted message'))
-   *     .catch(console.error);
-   * }
-   */
-  crosspost() {
-    if (!this.channel) return Promise.reject(new Error('CHANNEL_NOT_CACHED'));
-    return this.channel.messages.crosspost(this.id);
-  }
-
-  /**
-   * Pins this message to the channel's pinned messages.
-   * @returns {Promise}
-   * @example
-   * // Pin a message
-   * message.pin()
-   *   .then(console.log)
-   *   .catch(console.error)
-   */
-  async pin() {
-    if (!this.channel) throw new Error('CHANNEL_NOT_CACHED');
-    await this.channel.messages.pin(this.id);
-    return this;
-  }
-
-  /**
-   * Unpins this message from the channel's pinned messages.
-   * @returns {Promise}
-   * @example
-   * // Unpin a message
-   * message.unpin()
-   *   .then(console.log)
-   *   .catch(console.error)
-   */
-  async unpin() {
-    if (!this.channel) throw new Error('CHANNEL_NOT_CACHED');
-    await this.channel.messages.unpin(this.id);
-    return this;
-  }
-
-  /**
-   * Adds a reaction to the message.
-   * @param {EmojiIdentifierResolvable} emoji The emoji to react with
-   * @returns {Promise}
-   * @example
-   * // React to a message with a unicode emoji
-   * message.react('🤔')
-   *   .then(console.log)
-   *   .catch(console.error);
-   * @example
-   * // React to a message with a custom emoji
-   * message.react(message.guild.emojis.cache.get('123456789012345678'))
-   *   .then(console.log)
-   *   .catch(console.error);
-   */
-  async react(emoji) {
-    if (!this.channel) throw new Error('CHANNEL_NOT_CACHED');
-    await this.channel.messages.react(this.id, emoji);
-
-    return this.client.actions.MessageReactionAdd.handle(
-      {
-        user: this.client.user,
-        channel: this.channel,
-        message: this,
-        emoji: Util.resolvePartialEmoji(emoji),
-      },
-      true,
-    ).reaction;
-  }
-
-  /**
-   * Deletes the message.
-   * @returns {Promise}
-   * @example
-   * // Delete a message
-   * message.delete()
-   *   .then(msg => console.log(`Deleted message from ${msg.author.username}`))
-   *   .catch(console.error);
-   */
-  async delete() {
-    if (!this.channel) throw new Error('CHANNEL_NOT_CACHED');
-    await this.channel.messages.delete(this.id);
-    return this;
-  }
-
-  /**
-   * Options provided when sending a message as an inline reply.
-   * @typedef {BaseMessageOptions} ReplyMessageOptions
-   * @property {boolean} [failIfNotExists=true] Whether to error if the referenced message
-   * does not exist (creates a standard message in this case when false)
-   * @property {StickerResolvable[]} [stickers=[]] Stickers to send in the message
-   */
-
-  /**
-   * Send an inline reply to this message.
-   * @param {string|MessagePayload|ReplyMessageOptions} options The options to provide
-   * @returns {Promise}
-   * @example
-   * // Reply to a message
-   * message.reply('This is a reply!')
-   *   .then(() => console.log(`Replied to message "${message.content}"`))
-   *   .catch(console.error);
-   */
-  reply(options) {
-    if (!this.channel) return Promise.reject(new Error('CHANNEL_NOT_CACHED'));
-    let data;
-
-    if (options instanceof MessagePayload) {
-      data = options;
-    } else {
-      data = MessagePayload.create(this, options, {
-        reply: {
-          messageReference: this,
-          failIfNotExists: options?.failIfNotExists ?? this.client.options.failIfNotExists,
-        },
-      });
-    }
-    return this.channel.send(data);
-  }
-
-  /**
-   * A number that is allowed to be the duration (in minutes) of inactivity after which a thread is automatically
-   * archived. This can be:
-   * * `60` (1 hour)
-   * * `1440` (1 day)
-   * * `4320` (3 days) This is only available when the guild has the `THREE_DAY_THREAD_ARCHIVE` feature.
-   * * `10080` (7 days) This is only available when the guild has the `SEVEN_DAY_THREAD_ARCHIVE` feature.
-   * * `'MAX'` Based on the guild's features
-   * @typedef {number|string} ThreadAutoArchiveDuration
-   */
-
-  /**
-   * Options for starting a thread on a message.
-   * @typedef {Object} StartThreadOptions
-   * @property {string} name The name of the new thread
-   * @property {ThreadAutoArchiveDuration} [autoArchiveDuration=this.channel.defaultAutoArchiveDuration] The amount of
-   * time (in minutes) after which the thread should automatically archive in case of no recent activity
-   * @property {string} [reason] Reason for creating the thread
-   * @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the thread in seconds
-   */
-
-  /**
-   * Create a new public thread from this message
-   * @see ThreadManager#create
-   * @param {StartThreadOptions} [options] Options for starting a thread on this message
-   * @returns {Promise}
-   */
-  startThread(options = {}) {
-    if (!this.channel) return Promise.reject(new Error('CHANNEL_NOT_CACHED'));
-    if (!['GUILD_TEXT', 'GUILD_NEWS'].includes(this.channel.type)) {
-      return Promise.reject(new Error('MESSAGE_THREAD_PARENT'));
-    }
-    if (this.hasThread) return Promise.reject(new Error('MESSAGE_EXISTING_THREAD'));
-    return this.channel.threads.create({ ...options, startMessage: this });
-  }
-
-  /**
-   * Fetch this message.
-   * @param {boolean} [force=true] Whether to skip the cache check and request the API
-   * @returns {Promise}
-   */
-  fetch(force = true) {
-    if (!this.channel) return Promise.reject(new Error('CHANNEL_NOT_CACHED'));
-    return this.channel.messages.fetch(this.id, { force });
-  }
-
-  /**
-   * Fetches the webhook used to create this message.
-   * @returns {Promise}
-   */
-  fetchWebhook() {
-    if (!this.webhookId) return Promise.reject(new Error('WEBHOOK_MESSAGE'));
-    if (this.webhookId === this.applicationId) return Promise.reject(new Error('WEBHOOK_APPLICATION'));
-    return this.client.fetchWebhook(this.webhookId);
-  }
-
-  /**
-   * Suppresses or unsuppresses embeds on a message.
-   * @param {boolean} [suppress=true] If the embeds should be suppressed or not
-   * @returns {Promise}
-   */
-  suppressEmbeds(suppress = true) {
-    const flags = new MessageFlags(this.flags.bitfield);
-
-    if (suppress) {
-      flags.add(MessageFlags.FLAGS.SUPPRESS_EMBEDS);
-    } else {
-      flags.remove(MessageFlags.FLAGS.SUPPRESS_EMBEDS);
-    }
-
-    return this.edit({ flags });
-  }
-
-  /**
-   * Removes the attachments from this message.
-   * @returns {Promise}
-   */
-  removeAttachments() {
-    return this.edit({ attachments: [] });
-  }
-
-  /**
-   * Resolves a component by a custom id.
-   * @param {string} customId The custom id to resolve against
-   * @returns {?MessageActionRowComponent}
-   */
-  resolveComponent(customId) {
-    return this.components.flatMap(row => row.components).find(component => component.customId === customId) ?? null;
-  }
-
-  /**
-   * Used mainly internally. Whether two messages are identical in properties. If you want to compare messages
-   * without checking all the properties, use `message.id === message2.id`, which is much more efficient. This
-   * method allows you to see if there are differences in content, embeds, attachments, nonce and tts properties.
-   * @param {Message} message The message to compare it to
-   * @param {APIMessage} rawData Raw data passed through the WebSocket about this message
-   * @returns {boolean}
-   */
-  equals(message, rawData) {
-    if (!message) return false;
-    const embedUpdate = !message.author && !message.attachments;
-    if (embedUpdate) return this.id === message.id && this.embeds.length === message.embeds.length;
-
-    let equal =
-      this.id === message.id &&
-      this.author.id === message.author.id &&
-      this.content === message.content &&
-      this.tts === message.tts &&
-      this.nonce === message.nonce &&
-      this.embeds.length === message.embeds.length &&
-      this.attachments.length === message.attachments.length;
-
-    if (equal && rawData) {
-      equal =
-        this.mentions.everyone === message.mentions.everyone &&
-        this.createdTimestamp === new Date(rawData.timestamp).getTime() &&
-        this.editedTimestamp === new Date(rawData.edited_timestamp).getTime();
-    }
-
-    return equal;
-  }
-
-  /**
-   * Whether this message is from a guild.
-   * @returns {boolean}
-   */
-  inGuild() {
-    return Boolean(this.guildId);
-  }
-
-  /**
-   * When concatenated with a string, this automatically concatenates the message's content instead of the object.
-   * @returns {string}
-   * @example
-   * // Logs: Message: This is a message!
-   * console.log(`Message: ${message}`);
-   */
-  toString() {
-    return this.content;
-  }
-
-  toJSON() {
-    return super.toJSON({
-      channel: 'channelId',
-      author: 'authorId',
-      groupActivityApplication: 'groupActivityApplicationId',
-      guild: 'guildId',
-      cleanContent: true,
-      member: false,
-      reactions: false,
-    });
-  }
+			else
+				data.mentions instanceof Mentions
+					? (this.mentions = data.mentions)
+					: (this.mentions = null);
+		} else {
+			this.mentions = new Mentions(
+				this,
+				data.mentions ?? this.mentions.users,
+				data.mention_roles ?? this.mentions.roles,
+				data.mention_everyone ?? this.mentions.everyone,
+				data.mention_channels ?? this.mentions.crosspostedChannels,
+				data.referenced_message?.author ?? this.mentions.repliedUser,
+			);
+		}
+
+		if ('webhook_id' in data) {
+			/**
+			 * The id of the webhook that sent the message, if applicable
+			 * @type {?Snowflake}
+			 */
+			this.webhookId = data.webhook_id;
+		} else {
+			this.webhookId ??= null;
+		}
+
+		if ('application' in data) {
+			/**
+			 * Supplemental application information for group activities
+			 * @type {?ClientApplication}
+			 */
+			this.groupActivityApplication = new ClientApplication(
+				this.client,
+				data.application,
+			);
+		} else {
+			this.groupActivityApplication ??= null;
+		}
+
+		if ('application_id' in data) {
+			/**
+			 * The id of the application of the interaction that sent this message, if any
+			 * @type {?Snowflake}
+			 */
+			this.applicationId = data.application_id;
+		} else {
+			this.applicationId ??= null;
+		}
+
+		if ('activity' in data) {
+			/**
+			 * Group activity
+			 * @type {?MessageActivity}
+			 */
+			this.activity = {
+				partyId: data.activity.party_id,
+				type: data.activity.type,
+			};
+		} else {
+			this.activity ??= null;
+		}
+
+		if ('thread' in data) {
+			this.client.channels._add(data.thread, this.guild);
+		}
+
+		if (this.member && data.member) {
+			this.member._patch(data.member);
+		} else if (data.member && this.guild && this.author) {
+			this.guild.members._add(
+				Object.assign(data.member, { user: this.author }),
+			);
+		}
+
+		if ('flags' in data) {
+			/**
+			 * Flags that are applied to the message
+			 * @type {Readonly}
+			 */
+			this.flags = new MessageFlags(data.flags).freeze();
+		} else {
+			this.flags = new MessageFlags(this.flags).freeze();
+		}
+
+		/**
+		 * Reference data sent in a message that contains ids identifying the referenced message.
+		 * This can be present in the following types of message:
+		 * * Crossposted messages (IS_CROSSPOST {@link MessageFlags.FLAGS message flag})
+		 * * CHANNEL_FOLLOW_ADD
+		 * * CHANNEL_PINNED_MESSAGE
+		 * * REPLY
+		 * * THREAD_STARTER_MESSAGE
+		 * @see {@link https://discord.com/developers/docs/resources/channel#message-types}
+		 * @typedef {Object} MessageReference
+		 * @property {Snowflake} channelId The channel's id the message was referenced
+		 * @property {?Snowflake} guildId The guild's id the message was referenced
+		 * @property {?Snowflake} messageId The message's id that was referenced
+		 */
+
+		if ('message_reference' in data) {
+			/**
+			 * Message reference data
+			 * @type {?MessageReference}
+			 */
+			this.reference = {
+				channelId: data.message_reference.channel_id,
+				guildId: data.message_reference.guild_id,
+				messageId: data.message_reference.message_id,
+			};
+		} else {
+			this.reference ??= null;
+		}
+
+		if (data.referenced_message) {
+			this.channel?.messages._add({
+				guild_id: data.message_reference?.guild_id,
+				...data.referenced_message,
+			});
+		}
+
+		/**
+		 * Partial data of the interaction that a message is a reply to
+		 * @typedef {Object} MessageInteraction
+		 * @property {Snowflake} id The interaction's id
+		 * @property {InteractionType} type The type of the interaction
+		 * @property {string} commandName The name of the interaction's application command
+		 * @property {User} user The user that invoked the interaction
+		 */
+
+		if (data.interaction) {
+			/**
+			 * Partial data of the interaction that this message is a reply to
+			 * @type {?MessageInteraction}
+			 */
+			this.interaction = {
+				id: data.interaction.id,
+				type: InteractionTypes[data.interaction.type],
+				commandName: data.interaction.name,
+				user: this.client.users._add(data.interaction.user),
+			};
+		} else {
+			this.interaction ??= null;
+		}
+	}
+
+	/**
+	 * Whether or not the structure has been deleted
+	 * @type {boolean}
+	 * @deprecated This will be removed in the next major version, see https://github.com/discordjs/discord.js/issues/7091
+	 */
+	get deleted() {
+		if (!deprecationEmittedForDeleted) {
+			deprecationEmittedForDeleted = true;
+			process.emitWarning(
+				'Message#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
+				'DeprecationWarning',
+			);
+		}
+
+		return deletedMessages.has(this);
+	}
+
+	set deleted(value) {
+		if (!deprecationEmittedForDeleted) {
+			deprecationEmittedForDeleted = true;
+			process.emitWarning(
+				'Message#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
+				'DeprecationWarning',
+			);
+		}
+
+		if (value) deletedMessages.add(this);
+		else deletedMessages.delete(this);
+	}
+
+	/**
+	 * The channel that the message was sent in
+	 * @type {TextChannel|DMChannel|NewsChannel|ThreadChannel}
+	 * @readonly
+	 */
+	get channel() {
+		return this.client.channels.resolve(this.channelId);
+	}
+
+	/**
+	 * Whether or not this message is a partial
+	 * @type {boolean}
+	 * @readonly
+	 */
+	get partial() {
+		return typeof this.content !== 'string' || !this.author;
+	}
+
+	/**
+	 * Represents the author of the message as a guild member.
+	 * Only available if the message comes from a guild where the author is still a member
+	 * @type {?GuildMember}
+	 * @readonly
+	 */
+	get member() {
+		return this.guild?.members.resolve(this.author) ?? null;
+	}
+
+	/**
+	 * The time the message was sent at
+	 * @type {Date}
+	 * @readonly
+	 */
+	get createdAt() {
+		return new Date(this.createdTimestamp);
+	}
+
+	/**
+	 * The time the message was last edited at (if applicable)
+	 * @type {?Date}
+	 * @readonly
+	 */
+	get editedAt() {
+		return this.editedTimestamp ? new Date(this.editedTimestamp) : null;
+	}
+
+	/**
+	 * The guild the message was sent in (if in a guild channel)
+	 * @type {?Guild}
+	 * @readonly
+	 */
+	get guild() {
+		return (
+			this.client.guilds.resolve(this.guildId) ?? this.channel?.guild ?? null
+		);
+	}
+
+	/**
+	 * Whether this message has a thread associated with it
+	 * @type {boolean}
+	 * @readonly
+	 */
+	get hasThread() {
+		return this.flags.has(MessageFlags.FLAGS.HAS_THREAD);
+	}
+
+	/**
+	 * The thread started by this message
+	 * This property is not suitable for checking whether a message has a thread,
+	 * use {@link Message#hasThread} instead.
+	 * @type {?ThreadChannel}
+	 * @readonly
+	 */
+	get thread() {
+		return this.channel?.threads?.resolve(this.id) ?? null;
+	}
+
+	/**
+	 * The URL to jump to this message
+	 * @type {string}
+	 * @readonly
+	 */
+	get url() {
+		return `https://discord.com/channels/${this.guildId ?? '@me'}/${
+			this.channelId
+		}/${this.id}`;
+	}
+
+	/**
+	 * The message contents with all mentions replaced by the equivalent text.
+	 * If mentions cannot be resolved to a name, the relevant mention in the message content will not be converted.
+	 * @type {?string}
+	 * @readonly
+	 */
+	get cleanContent() {
+		// eslint-disable-next-line eqeqeq
+		return this.content != null
+			? Util.cleanContent(this.content, this.channel)
+			: null;
+	}
+
+	/**
+	 * Creates a reaction collector.
+	 * @param {ReactionCollectorOptions} [options={}] Options to send to the collector
+	 * @returns {ReactionCollector}
+	 * @example
+	 * // Create a reaction collector
+	 * const filter = (reaction, user) => reaction.emoji.name === '👌' && user.id === 'someId';
+	 * const collector = message.createReactionCollector({ filter, time: 15_000 });
+	 * collector.on('collect', r => console.log(`Collected ${r.emoji.name}`));
+	 * collector.on('end', collected => console.log(`Collected ${collected.size} items`));
+	 */
+	createReactionCollector(options = {}) {
+		return new ReactionCollector(this, options);
+	}
+
+	/**
+	 * An object containing the same properties as CollectorOptions, but a few more:
+	 * @typedef {ReactionCollectorOptions} AwaitReactionsOptions
+	 * @property {string[]} [errors] Stop/end reasons that cause the promise to reject
+	 */
+
+	/**
+	 * Similar to createReactionCollector but in promise form.
+	 * Resolves with a collection of reactions that pass the specified filter.
+	 * @param {AwaitReactionsOptions} [options={}] Optional options to pass to the internal collector
+	 * @returns {Promise>}
+	 * @example
+	 * // Create a reaction collector
+	 * const filter = (reaction, user) => reaction.emoji.name === '👌' && user.id === 'someId'
+	 * message.awaitReactions({ filter, time: 15_000 })
+	 *   .then(collected => console.log(`Collected ${collected.size} reactions`))
+	 *   .catch(console.error);
+	 */
+	awaitReactions(options = {}) {
+		return new Promise((resolve, reject) => {
+			const collector = this.createReactionCollector(options);
+			collector.once('end', (reactions, reason) => {
+				if (options.errors?.includes(reason)) reject(reactions);
+				else resolve(reactions);
+			});
+		});
+	}
+
+	/**
+	 * @typedef {CollectorOptions} MessageComponentCollectorOptions
+	 * @property {MessageComponentType} [componentType] The type of component to listen for
+	 * @property {number} [max] The maximum total amount of interactions to collect
+	 * @property {number} [maxComponents] The maximum number of components to collect
+	 * @property {number} [maxUsers] The maximum number of users to interact
+	 */
+
+	/**
+	 * Creates a message component interaction collector.
+	 * @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector
+	 * @returns {InteractionCollector}
+	 * @example
+	 * // Create a message component interaction collector
+	 * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
+	 * const collector = message.createMessageComponentCollector({ filter, time: 15_000 });
+	 * collector.on('collect', i => console.log(`Collected ${i.customId}`));
+	 * collector.on('end', collected => console.log(`Collected ${collected.size} items`));
+	 */
+	createMessageComponentCollector(options = {}) {
+		return new InteractionCollector(this.client, {
+			...options,
+			interactionType: InteractionTypes.MESSAGE_COMPONENT,
+			message: this,
+		});
+	}
+
+	/**
+	 * An object containing the same properties as CollectorOptions, but a few more:
+	 * @typedef {Object} AwaitMessageComponentOptions
+	 * @property {CollectorFilter} [filter] The filter applied to this collector
+	 * @property {number} [time] Time to wait for an interaction before rejecting
+	 * @property {MessageComponentType} [componentType] The type of component interaction to collect
+	 */
+
+	/**
+	 * Collects a single component interaction that passes the filter.
+	 * The Promise will reject if the time expires.
+	 * @param {AwaitMessageComponentOptions} [options={}] Options to pass to the internal collector
+	 * @returns {Promise}
+	 * @example
+	 * // Collect a message component interaction
+	 * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
+	 * message.awaitMessageComponent({ filter, time: 15_000 })
+	 *   .then(interaction => console.log(`${interaction.customId} was clicked!`))
+	 *   .catch(console.error);
+	 */
+	awaitMessageComponent(options = {}) {
+		const _options = { ...options, max: 1 };
+		return new Promise((resolve, reject) => {
+			const collector = this.createMessageComponentCollector(_options);
+			collector.once('end', (interactions, reason) => {
+				const interaction = interactions.first();
+				if (interaction) resolve(interaction);
+				else reject(new Error('INTERACTION_COLLECTOR_ERROR', reason));
+			});
+		});
+	}
+
+	/**
+	 * Whether the message is editable by the client user
+	 * @type {boolean}
+	 * @readonly
+	 */
+	get editable() {
+		const precheck = Boolean(
+			this.author.id === this.client.user.id &&
+				!deletedMessages.has(this) &&
+				(!this.guild || this.channel?.viewable),
+		);
+		// Regardless of permissions thread messages cannot be edited if
+		// the thread is locked.
+		if (this.channel?.isThread()) {
+			return precheck && !this.channel.locked;
+		}
+		return precheck;
+	}
+
+	/**
+	 * Whether the message is deletable by the client user
+	 * @type {boolean}
+	 * @readonly
+	 */
+	get deletable() {
+		if (deletedMessages.has(this)) {
+			return false;
+		}
+		if (!this.guild) {
+			return this.author.id === this.client.user.id;
+		}
+		// DMChannel does not have viewable property, so check viewable after proved that message is on a guild.
+		if (!this.channel?.viewable) {
+			return false;
+		}
+
+		const permissions = this.channel?.permissionsFor(this.client.user);
+		if (!permissions) return false;
+		// This flag allows deleting even if timed out
+		if (permissions.has(Permissions.FLAGS.ADMINISTRATOR, false)) return true;
+
+		return Boolean(
+			this.author.id === this.client.user.id ||
+				(permissions.has(Permissions.FLAGS.MANAGE_MESSAGES, false) &&
+					this.guild.me.communicationDisabledUntilTimestamp < Date.now()),
+		);
+	}
+
+	/**
+	 * Whether the message is pinnable by the client user
+	 * @type {boolean}
+	 * @readonly
+	 */
+	get pinnable() {
+		const { channel } = this;
+		return Boolean(
+			!this.system &&
+				!deletedMessages.has(this) &&
+				(!this.guild ||
+					(channel?.viewable &&
+						channel
+							?.permissionsFor(this.client.user)
+							?.has(Permissions.FLAGS.MANAGE_MESSAGES, false))),
+		);
+	}
+
+	/**
+	 * Fetches the Message this crosspost/reply/pin-add references, if available to the client
+	 * @returns {Promise}
+	 */
+	async fetchReference() {
+		if (!this.reference) throw new Error('MESSAGE_REFERENCE_MISSING');
+		const { channelId, messageId } = this.reference;
+		const channel = this.client.channels.resolve(channelId);
+		if (!channel) throw new Error('GUILD_CHANNEL_RESOLVE');
+		const message = await channel.messages.fetch(messageId);
+		return message;
+	}
+
+	/**
+	 * Whether the message is crosspostable by the client user
+	 * @type {boolean}
+	 * @readonly
+	 */
+	get crosspostable() {
+		const bitfield =
+			Permissions.FLAGS.SEND_MESSAGES |
+			(this.author.id === this.client.user.id
+				? Permissions.defaultBit
+				: Permissions.FLAGS.MANAGE_MESSAGES);
+		const { channel } = this;
+		return Boolean(
+			channel?.type === 'GUILD_NEWS' &&
+				!this.flags.has(MessageFlags.FLAGS.CROSSPOSTED) &&
+				this.type === 'DEFAULT' &&
+				channel.viewable &&
+				channel.permissionsFor(this.client.user)?.has(bitfield, false) &&
+				!deletedMessages.has(this),
+		);
+	}
+
+	/**
+	 * Options that can be passed into {@link Message#edit}.
+	 * @typedef {Object} MessageEditOptions
+	 * @property {?string} [content] Content to be edited
+	 * @property {MessageEmbed[]|APIEmbed[]} [embeds] Embeds to be added/edited
+	 * @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content
+	 * @property {MessageFlags} [flags] Which flags to set for the message. Only `SUPPRESS_EMBEDS` can be edited.
+	 * @property {MessageAttachment[]} [attachments] An array of attachments to keep,
+	 * all attachments will be kept if omitted
+	 * @property {FileOptions[]|BufferResolvable[]|MessageAttachment[]} [files] Files to add to the message
+	 * @property {MessageActionRow[]|MessageActionRowOptions[]} [components]
+	 * Action rows containing interactive components for the message (buttons, select menus)
+	 */
+
+	/**
+	 * Edits the content of the message.
+	 * @param {string|MessagePayload|MessageEditOptions} options The options to provide
+	 * @returns {Promise}
+	 * @example
+	 * // Update the content of a message
+	 * message.edit('This is my new content!')
+	 *   .then(msg => console.log(`Updated the content of a message to ${msg.content}`))
+	 *   .catch(console.error);
+	 */
+	edit(options) {
+		if (!this.channel) return Promise.reject(new Error('CHANNEL_NOT_CACHED'));
+		return this.channel.messages.edit(this, options);
+	}
+
+	/**
+	 * Publishes a message in an announcement channel to all channels following it.
+	 * @returns {Promise}
+	 * @example
+	 * // Crosspost a message
+	 * if (message.channel.type === 'GUILD_NEWS') {
+	 *   message.crosspost()
+	 *     .then(() => console.log('Crossposted message'))
+	 *     .catch(console.error);
+	 * }
+	 */
+	crosspost() {
+		if (!this.channel) return Promise.reject(new Error('CHANNEL_NOT_CACHED'));
+		return this.channel.messages.crosspost(this.id);
+	}
+
+	/**
+	 * Pins this message to the channel's pinned messages.
+	 * @returns {Promise}
+	 * @example
+	 * // Pin a message
+	 * message.pin()
+	 *   .then(console.log)
+	 *   .catch(console.error)
+	 */
+	async pin() {
+		if (!this.channel) throw new Error('CHANNEL_NOT_CACHED');
+		await this.channel.messages.pin(this.id);
+		return this;
+	}
+
+	/**
+	 * Unpins this message from the channel's pinned messages.
+	 * @returns {Promise}
+	 * @example
+	 * // Unpin a message
+	 * message.unpin()
+	 *   .then(console.log)
+	 *   .catch(console.error)
+	 */
+	async unpin() {
+		if (!this.channel) throw new Error('CHANNEL_NOT_CACHED');
+		await this.channel.messages.unpin(this.id);
+		return this;
+	}
+
+	/**
+	 * Adds a reaction to the message.
+	 * @param {EmojiIdentifierResolvable} emoji The emoji to react with
+	 * @returns {Promise}
+	 * @example
+	 * // React to a message with a unicode emoji
+	 * message.react('🤔')
+	 *   .then(console.log)
+	 *   .catch(console.error);
+	 * @example
+	 * // React to a message with a custom emoji
+	 * message.react(message.guild.emojis.cache.get('123456789012345678'))
+	 *   .then(console.log)
+	 *   .catch(console.error);
+	 */
+	async react(emoji) {
+		if (!this.channel) throw new Error('CHANNEL_NOT_CACHED');
+		await this.channel.messages.react(this.id, emoji);
+
+		return this.client.actions.MessageReactionAdd.handle(
+			{
+				user: this.client.user,
+				channel: this.channel,
+				message: this,
+				emoji: Util.resolvePartialEmoji(emoji),
+			},
+			true,
+		).reaction;
+	}
+
+	/**
+	 * Deletes the message.
+	 * @returns {Promise}
+	 * @example
+	 * // Delete a message
+	 * message.delete()
+	 *   .then(msg => console.log(`Deleted message from ${msg.author.username}`))
+	 *   .catch(console.error);
+	 */
+	async delete() {
+		if (!this.channel) throw new Error('CHANNEL_NOT_CACHED');
+		await this.channel.messages.delete(this.id);
+		return this;
+	}
+
+	/**
+	 * Options provided when sending a message as an inline reply.
+	 * @typedef {BaseMessageOptions} ReplyMessageOptions
+	 * @property {boolean} [failIfNotExists=true] Whether to error if the referenced message
+	 * does not exist (creates a standard message in this case when false)
+	 * @property {StickerResolvable[]} [stickers=[]] Stickers to send in the message
+	 */
+
+	/**
+	 * Send an inline reply to this message.
+	 * @param {string|MessagePayload|ReplyMessageOptions} options The options to provide
+	 * @returns {Promise}
+	 * @example
+	 * // Reply to a message
+	 * message.reply('This is a reply!')
+	 *   .then(() => console.log(`Replied to message "${message.content}"`))
+	 *   .catch(console.error);
+	 */
+	reply(options) {
+		if (!this.channel) return Promise.reject(new Error('CHANNEL_NOT_CACHED'));
+		let data;
+
+		if (options instanceof MessagePayload) {
+			data = options;
+		} else {
+			data = MessagePayload.create(this, options, {
+				reply: {
+					messageReference: this,
+					failIfNotExists:
+						options?.failIfNotExists ?? this.client.options.failIfNotExists,
+				},
+			});
+		}
+		return this.channel.send(data);
+	}
+
+	/**
+	 * A number that is allowed to be the duration (in minutes) of inactivity after which a thread is automatically
+	 * archived. This can be:
+	 * * `60` (1 hour)
+	 * * `1440` (1 day)
+	 * * `4320` (3 days) This is only available when the guild has the `THREE_DAY_THREAD_ARCHIVE` feature.
+	 * * `10080` (7 days) This is only available when the guild has the `SEVEN_DAY_THREAD_ARCHIVE` feature.
+	 * * `'MAX'` Based on the guild's features
+	 * @typedef {number|string} ThreadAutoArchiveDuration
+	 */
+
+	/**
+	 * Options for starting a thread on a message.
+	 * @typedef {Object} StartThreadOptions
+	 * @property {string} name The name of the new thread
+	 * @property {ThreadAutoArchiveDuration} [autoArchiveDuration=this.channel.defaultAutoArchiveDuration] The amount of
+	 * time (in minutes) after which the thread should automatically archive in case of no recent activity
+	 * @property {string} [reason] Reason for creating the thread
+	 * @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the thread in seconds
+	 */
+
+	/**
+	 * Create a new public thread from this message
+	 * @see ThreadManager#create
+	 * @param {StartThreadOptions} [options] Options for starting a thread on this message
+	 * @returns {Promise}
+	 */
+	startThread(options = {}) {
+		if (!this.channel) return Promise.reject(new Error('CHANNEL_NOT_CACHED'));
+		if (!['GUILD_TEXT', 'GUILD_NEWS'].includes(this.channel.type)) {
+			return Promise.reject(new Error('MESSAGE_THREAD_PARENT'));
+		}
+		if (this.hasThread)
+			return Promise.reject(new Error('MESSAGE_EXISTING_THREAD'));
+		return this.channel.threads.create({ ...options, startMessage: this });
+	}
+
+	/**
+	 * Fetch this message.
+	 * @param {boolean} [force=true] Whether to skip the cache check and request the API
+	 * @returns {Promise}
+	 */
+	fetch(force = true) {
+		if (!this.channel) return Promise.reject(new Error('CHANNEL_NOT_CACHED'));
+		return this.channel.messages.fetch(this.id, { force });
+	}
+
+	/**
+	 * Fetches the webhook used to create this message.
+	 * @returns {Promise}
+	 */
+	fetchWebhook() {
+		if (!this.webhookId) return Promise.reject(new Error('WEBHOOK_MESSAGE'));
+		if (this.webhookId === this.applicationId)
+			return Promise.reject(new Error('WEBHOOK_APPLICATION'));
+		return this.client.fetchWebhook(this.webhookId);
+	}
+
+	/**
+	 * Suppresses or unsuppresses embeds on a message.
+	 * @param {boolean} [suppress=true] If the embeds should be suppressed or not
+	 * @returns {Promise}
+	 */
+	suppressEmbeds(suppress = true) {
+		const flags = new MessageFlags(this.flags.bitfield);
+
+		if (suppress) {
+			flags.add(MessageFlags.FLAGS.SUPPRESS_EMBEDS);
+		} else {
+			flags.remove(MessageFlags.FLAGS.SUPPRESS_EMBEDS);
+		}
+
+		return this.edit({ flags });
+	}
+
+	/**
+	 * Removes the attachments from this message.
+	 * @returns {Promise}
+	 */
+	removeAttachments() {
+		return this.edit({ attachments: [] });
+	}
+
+	/**
+	 * Resolves a component by a custom id.
+	 * @param {string} customId The custom id to resolve against
+	 * @returns {?MessageActionRowComponent}
+	 */
+	resolveComponent(customId) {
+		return (
+			this.components
+				.flatMap((row) => row.components)
+				.find((component) => component.customId === customId) ?? null
+		);
+	}
+
+	/**
+	 * Used mainly internally. Whether two messages are identical in properties. If you want to compare messages
+	 * without checking all the properties, use `message.id === message2.id`, which is much more efficient. This
+	 * method allows you to see if there are differences in content, embeds, attachments, nonce and tts properties.
+	 * @param {Message} message The message to compare it to
+	 * @param {APIMessage} rawData Raw data passed through the WebSocket about this message
+	 * @returns {boolean}
+	 */
+	equals(message, rawData) {
+		if (!message) return false;
+		const embedUpdate = !message.author && !message.attachments;
+		if (embedUpdate)
+			return (
+				this.id === message.id && this.embeds.length === message.embeds.length
+			);
+
+		let equal =
+			this.id === message.id &&
+			this.author.id === message.author.id &&
+			this.content === message.content &&
+			this.tts === message.tts &&
+			this.nonce === message.nonce &&
+			this.embeds.length === message.embeds.length &&
+			this.attachments.length === message.attachments.length;
+
+		if (equal && rawData) {
+			equal =
+				this.mentions.everyone === message.mentions.everyone &&
+				this.createdTimestamp === new Date(rawData.timestamp).getTime() &&
+				this.editedTimestamp === new Date(rawData.edited_timestamp).getTime();
+		}
+
+		return equal;
+	}
+
+	/**
+	 * Whether this message is from a guild.
+	 * @returns {boolean}
+	 */
+	inGuild() {
+		return Boolean(this.guildId);
+	}
+
+	/**
+	 * When concatenated with a string, this automatically concatenates the message's content instead of the object.
+	 * @returns {string}
+	 * @example
+	 * // Logs: Message: This is a message!
+	 * console.log(`Message: ${message}`);
+	 */
+	toString() {
+		return this.content;
+	}
+
+	toJSON() {
+		return super.toJSON({
+			channel: 'channelId',
+			author: 'authorId',
+			groupActivityApplication: 'groupActivityApplicationId',
+			guild: 'guildId',
+			cleanContent: true,
+			member: false,
+			reactions: false,
+		});
+	}
+	// Added
+	/**
+	 * Click specific button [Suggestion: Dux#2925]
+	 * @param {String} buttonID Button ID
+	 * @returns {Promise}
+	 */
+	async clickButton(buttonID) {
+		if (typeof buttonID !== 'string')
+			throw new TypeError('BUTTON_ID_NOT_STRING');
+		if (!this.components[0]) throw new TypeError('MESSAGE_NO_COMPONENTS');
+		let button;
+		await Promise.all(
+			this.components.map(async (row) => {
+				await Promise.all(
+					row.components.map(async (interactionComponent) => {
+						if (
+							interactionComponent.type == 'BUTTON' &&
+							interactionComponent.customId == buttonID
+						) {
+							button = interactionComponent;
+						}
+					}),
+				);
+			}),
+		);
+		if (!button) throw new TypeError('BUTTON_NOT_FOUND');
+		else button.click(this);
+	}
+	/**
+	 * Select specific menu or First Menu
+	 * @param {String|Array} menuID Select Menu specific id or auto select first Menu
+	 * @param {Array} options Menu Options
+	 */
+	async selectMenu(menuID, options = []) {
+		if (!this.components[0]) throw new TypeError('MESSAGE_NO_COMPONENTS');
+		let menuFirst;
+		let menuCorrect;
+		let menuCount = 0;
+		await Promise.all(
+			this.components.map(async (row) => {
+				const firstElement = row.components[0]; // Because 1 row has only 1 menu;
+				if (firstElement.type == 'SELECT_MENU') {
+					menuCount++;
+					if (firstElement.customId == menuID) {
+						menuCorrect = firstElement;
+					} else if (!menuFirst) {
+						menuFirst = firstElement;
+					}
+				}
+			}),
+		);
+		if (menuCount == 0) throw new TypeError('MENU_NOT_FOUND');
+		if (!menuCorrect) {
+			if (menuCount == 1) menuCorrect = menuFirst;
+			else if (typeof menuID !== 'string') throw new TypeError('MENU_ID_NOT_STRING');
+			else throw new TypeError('MENU_ID_NOT_FOUND');
+		}
+		menuCorrect.select(this, Array.isArray(menuID) ? menuID : options);
+	}
 }
 
 exports.Message = Message;
diff --git a/typings/index.d.ts b/typings/index.d.ts
index a4bfc3d..3d5aa0c 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -1578,6 +1578,9 @@ export class Message extends Base {
   public toString(): string;
   public unpin(): Promise;
   public inGuild(): this is Message & this;
+  // Added
+  public clickButton(buttonID: String): Promise
+  public selectMenu(menuID: String | Array, options: Array): Promise
 }
 
 export class MessageActionRow extends BaseMessageComponent {