diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js
index 858d66300..5843e2908 100644
--- a/src/client/rest/RESTMethods.js
+++ b/src/client/rest/RESTMethods.js
@@ -22,6 +22,8 @@ const Guild = require('../../structures/Guild');
const VoiceRegion = require('../../structures/VoiceRegion');
const GuildAuditLogs = require('../../structures/GuildAuditLogs');
+const MessageFlags = require('../../util/MessageFlags');
+
class RESTMethods {
constructor(restManager) {
this.rest = restManager;
@@ -132,9 +134,11 @@ class RESTMethods {
});
}
- updateMessage(message, content, { embed, code, reply } = {}) {
+ updateMessage(message, content, { flags, embed, code, reply } = {}) {
if (typeof content !== 'undefined') content = this.client.resolver.resolveString(content);
+ if (typeof flags !== 'undefined') flags = MessageFlags.resolve(flags);
+
// Wrap everything in a code block
if (typeof code !== 'undefined' && (typeof code !== 'boolean' || code === true)) {
content = Util.escapeMarkdown(this.client.resolver.resolveString(content), true);
@@ -151,7 +155,7 @@ class RESTMethods {
if (embed instanceof RichEmbed) embed = embed._apiTransform();
return this.rest.makeRequest('patch', Endpoints.Message(message), true, {
- content, embed,
+ content, embed, flags,
}).then(data => this.client.actions.MessageUpdate.handle(data).updated);
}
diff --git a/src/index.js b/src/index.js
index 015e2b3ac..b5b19c75f 100644
--- a/src/index.js
+++ b/src/index.js
@@ -14,6 +14,7 @@ module.exports = {
Constants: require('./util/Constants'),
DiscordAPIError: require('./client/rest/DiscordAPIError'),
EvaluatedPermissions: require('./util/Permissions'),
+ MessageFlags: require('./util/MessageFlags'),
Permissions: require('./util/Permissions'),
Snowflake: require('./util/Snowflake'),
SnowflakeUtil: require('./util/Snowflake'),
diff --git a/src/structures/Message.js b/src/structures/Message.js
index 10eafe650..c71844f47 100644
--- a/src/structures/Message.js
+++ b/src/structures/Message.js
@@ -8,6 +8,7 @@ const Util = require('../util/Util');
const Collection = require('../util/Collection');
const Constants = require('../util/Constants');
const Permissions = require('../util/Permissions');
+const MessageFlags = require('../util/MessageFlags');
let GuildMember;
/**
@@ -77,7 +78,9 @@ class Message {
/**
* A random number or string used for checking message delivery
- * @type {string}
+ * This is only received after the message was sent successfully, and
+ * lost if re-fetched
+ * @type {?string}
*/
this.nonce = data.nonce;
@@ -85,7 +88,7 @@ class Message {
* Whether or not this message was sent by Discord, not actually a user (e.g. pin notifications)
* @type {boolean}
*/
- this.system = data.type === 6;
+ this.system = data.type !== 0;
/**
* A list of embeds in the message - e.g. YouTube Player
@@ -128,7 +131,7 @@ class Message {
* All valid mentions that the message contains
* @type {MessageMentions}
*/
- this.mentions = new Mentions(this, data.mentions, data.mention_roles, data.mention_everyone);
+ this.mentions = new Mentions(this, data.mentions, data.mention_roles, data.mention_everyone, data.mention_channels);
/**
* ID of the webhook that sent the message, if applicable
@@ -142,6 +145,30 @@ class Message {
*/
this.hit = typeof data.hit === 'boolean' ? data.hit : null;
+ /**
+ * Flags that are applied to the message
+ * @type {Readonly}
+ */
+ this.flags = new MessageFlags(data.flags).freeze();
+
+ /**
+ * Reference data sent in a crossposted message.
+ * @typedef {Object} MessageReference
+ * @property {string} channelID ID of the channel the message was crossposted from
+ * @property {?string} guildID ID of the guild the message was crossposted from
+ * @property {?string} messageID ID of the message that was crossposted
+ */
+
+ /**
+ * Message reference data
+ * @type {?MessageReference}
+ */
+ this.reference = data.message_reference ? {
+ channelID: data.message_reference.channel_id,
+ guildID: data.message_reference.guild_id,
+ messageID: data.message_reference.message_id,
+ } : null;
+
/**
* The previous versions of the message, sorted with the most recent first
* @type {Message[]}
@@ -188,8 +215,11 @@ class Message {
this,
'mentions' in data ? data.mentions : this.mentions.users,
'mentions_roles' in data ? data.mentions_roles : this.mentions.roles,
- 'mention_everyone' in data ? data.mention_everyone : this.mentions.everyone
+ 'mention_everyone' in data ? data.mention_everyone : this.mentions.everyone,
+ 'mention_channels' in data ? data.mention_channels : this.mentions.crosspostedChannels
);
+
+ this.flags = new MessageFlags('flags' in data ? data.flags : 0).freeze();
}
/**
@@ -384,6 +414,7 @@ class Message {
* @typedef {Object} MessageEditOptions
* @property {Object} [embed] An embed to be added/edited
* @property {string|boolean} [code] Language for optional codeblock formatting to apply
+ * @property {MessageFlagsResolvable} [flags] Message flags to apply
*/
/**
@@ -528,6 +559,23 @@ class Message {
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(undefined, { flags });
+ }
+
/**
* 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
@@ -542,12 +590,12 @@ class Message {
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;
+ 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 &&
diff --git a/src/structures/MessageMentions.js b/src/structures/MessageMentions.js
index 8c80906e8..47aff55c2 100644
--- a/src/structures/MessageMentions.js
+++ b/src/structures/MessageMentions.js
@@ -1,10 +1,11 @@
const Collection = require('../util/Collection');
+const { ChannelTypes } = require('../util/Constants');
/**
* Keeps track of mentions in a {@link Message}.
*/
class MessageMentions {
- constructor(message, users, roles, everyone) {
+ constructor(message, users, roles, everyone, crosspostedChannels) {
/**
* Whether `@everyone` or `@here` were mentioned
* @type {boolean}
@@ -87,6 +88,39 @@ class MessageMentions {
* @private
*/
this._channels = null;
+
+ /**
+ * Crossposted channel data.
+ * @typedef {Object} CrosspostedChannel
+ * @property {Snowflake} channelID ID of the mentioned channel
+ * @property {Snowflake} guildID ID of the guild that has the channel
+ * @property {string} type Type of the channel
+ * @property {string} name Name of the channel
+ */
+
+ if (crosspostedChannels) {
+ if (crosspostedChannels instanceof Collection) {
+ /**
+ * A collection of crossposted channels
+ * @type {Collection}
+ */
+ this.crosspostedChannels = new Collection(crosspostedChannels);
+ } else {
+ this.crosspostedChannels = new Collection();
+ const channelTypes = Object.keys(ChannelTypes);
+ for (const d of crosspostedChannels) {
+ const type = channelTypes[d.type];
+ this.crosspostedChannels.set(d.id, {
+ channelID: d.id,
+ guildID: d.guild_id,
+ type: type ? type.toLowerCase() : 'unknown',
+ name: d.name,
+ });
+ }
+ }
+ } else {
+ this.crosspostedChannels = new Collection();
+ }
}
/**
diff --git a/src/structures/User.js b/src/structures/User.js
index 43adb37df..86c2bc0ac 100644
--- a/src/structures/User.js
+++ b/src/structures/User.js
@@ -52,6 +52,13 @@ class User {
*/
this.bot = Boolean(data.bot);
+ /**
+ * Whether this is an Official Discord System user (part of the urgent message system)
+ * @type {?boolean}
+ * @name User#system
+ */
+ if (typeof data.system !== 'undefined') this.system = Boolean(data.system);
+
/**
* The ID of the last message sent by the user, if one was sent
* @type {?Snowflake}
diff --git a/src/util/Constants.js b/src/util/Constants.js
index 79b309439..008fb9c71 100644
--- a/src/util/Constants.js
+++ b/src/util/Constants.js
@@ -507,6 +507,7 @@ exports.WSEvents = {
* * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1
* * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2
* * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3
+ * * CHANNEL_FOLLOW_ADD
* @typedef {string} MessageType
*/
exports.MessageTypes = [
@@ -522,6 +523,7 @@ exports.MessageTypes = [
'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1',
'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2',
'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3',
+ 'CHANNEL_FOLLOW_ADD',
];
/**
diff --git a/src/util/MessageFlags.js b/src/util/MessageFlags.js
new file mode 100644
index 000000000..88c8014b8
--- /dev/null
+++ b/src/util/MessageFlags.js
@@ -0,0 +1,36 @@
+const BitField = require('./BitField');
+
+/**
+ * Data structure that makes it easy to interact with an {@link Message#flags} bitfield.
+ * @extends {BitField}
+ */
+class MessageFlags extends BitField {}
+
+/**
+ * Data that can be resolved to give a permission number. This can be:
+ * * A string (see {@link MessageFlags.FLAGS})
+ * * A message flag
+ * * An instance of MessageFlags
+ * * An array of MessageFlagsResolvable
+ * @typedef {string|number|MessageFlags|MessageFlagsResolvable[]} MessageFlagsResolvable
+ */
+
+/**
+ * Numeric message flags. All available properties:
+ * * `CROSSPOSTED`
+ * * `IS_CROSSPOST`
+ * * `SUPPRESS_EMBEDS`
+ * * `SOURCE_MESSAGE_DELETED`
+ * * `URGENT`
+ * @type {Object}
+ * @see {@link https://discordapp.com/developers/docs/resources/channel#message-object-message-flags}
+ */
+MessageFlags.FLAGS = {
+ CROSSPOSTED: 1 << 0,
+ IS_CROSSPOST: 1 << 1,
+ SUPPRESS_EMBEDS: 1 << 2,
+ SOURCE_MESSAGE_DELETED: 1 << 3,
+ URGENT: 1 << 4,
+};
+
+module.exports = MessageFlags;
diff --git a/typings/index.d.ts b/typings/index.d.ts
index f436b1347..de12e4f77 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -804,6 +804,7 @@ declare module 'discord.js' {
public editedTimestamp: number;
public readonly edits: Message[];
public embeds: MessageEmbed[];
+ public flags: Readonly;
public readonly guild: Guild;
public hit: boolean;
public id: Snowflake;
@@ -813,6 +814,7 @@ declare module 'discord.js' {
public readonly pinnable: boolean;
public pinned: boolean;
public reactions: Collection;
+ public reference: MessageReference | null;
public system: boolean;
public tts: boolean;
public type: string;
@@ -833,6 +835,7 @@ declare module 'discord.js' {
public react(emoji: string | Emoji | ReactionEmoji): Promise;
public reply(content?: StringResolvable, options?: MessageOptions): Promise;
public reply(options?: MessageOptions): Promise;
+ public suppressEmbeds(suppress?: boolean): Promise;
public toString(): string;
public unpin(): Promise;
}
@@ -940,6 +943,11 @@ declare module 'discord.js' {
public width: number;
}
+ export class MessageFlags extends BitField {
+ public static FLAGS: Record;
+ public static resolve(bit?: BitFieldResolvable): number;
+ }
+
export class MessageMentions {
private _channels: Collection;
private _client: Client;
@@ -948,6 +956,7 @@ declare module 'discord.js' {
private _members: Collection;
public readonly channels: Collection;
+ public crosspostedChannels: Collection;
public everyone: boolean;
public readonly members: Collection;
public roles: Collection;
@@ -1361,6 +1370,7 @@ declare module 'discord.js' {
public lastMessageID: string;
public readonly note: string;
public readonly presence: Presence;
+ public system?: boolean;
public readonly tag: string;
public username: string;
public addFriend(): Promise;
@@ -1849,6 +1859,13 @@ declare module 'discord.js' {
| number
| string;
+ type CrosspostedChannel = {
+ channelID: Snowflake;
+ guildID: Snowflake;
+ type: Channel["type"] | 'unknown';
+ name: string;
+ };
+
type DeconstructedSnowflake = {
timestamp: number;
date: Date;
@@ -2040,8 +2057,15 @@ declare module 'discord.js' {
type MessageEditOptions = {
embed?: RichEmbedOptions;
code?: string | boolean;
+ flags?: BitFieldResolvable;
};
+ type MessageFlagsString = 'CROSSPOSTED'
+ | 'IS_CROSSPOST'
+ | 'SUPRRESS_EMBEDS'
+ | 'SOURCE_MESSAGE_DELETED'
+ | 'URGENT';
+
type MessageNotifications = 'EVERYTHING'
| 'MENTIONS'
| 'NOTHING';
@@ -2100,6 +2124,12 @@ declare module 'discord.js' {
nsfw?: boolean;
};
+ type MessageReference = {
+ channelID: Snowflake;
+ guildID: Snowflake | null;
+ messageID: Snowflake | null;
+ };
+
type MessageSearchResult = {
totalResults: number;
messages: Message[][];