diff --git a/src/index.js b/src/index.js index 916056fe0..77c56336d 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,7 @@ module.exports = { DataStore: require('./stores/DataStore'), DiscordAPIError: require('./rest/DiscordAPIError'), HTTPError: require('./rest/HTTPError'), + MessageFlags: require('./util/MessageFlags'), Permissions: require('./util/Permissions'), Speaking: require('./util/Speaking'), Snowflake: require('./util/Snowflake'), diff --git a/src/structures/Message.js b/src/structures/Message.js index 5b6cc4754..8a9df07ea 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -13,6 +13,7 @@ const Permissions = require('../util/Permissions'); const Base = require('./Base'); const { Error, TypeError } = require('../errors'); const APIMessage = require('./APIMessage'); +const MessageFlags = require('../util/MessageFlags'); /** * Represents a message on Discord. @@ -81,7 +82,9 @@ class Message extends Base { /** * 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; @@ -89,7 +92,7 @@ class Message extends Base { * 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 @@ -137,7 +140,7 @@ class Message extends Base { * 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 @@ -172,6 +175,30 @@ class Message extends Base { } else if (data.member && this.guild && this.author) { this.guild.members.add(Object.assign(data.member, { user: this.author })); } + + /** + * 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; } /** @@ -214,8 +241,11 @@ class Message extends Base { 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(); } /** diff --git a/src/structures/MessageMentions.js b/src/structures/MessageMentions.js index 610c8c127..9b17685f6 100644 --- a/src/structures/MessageMentions.js +++ b/src/structures/MessageMentions.js @@ -3,12 +3,13 @@ const Collection = require('../util/Collection'); const Util = require('../util/Util'); const GuildMember = require('./GuildMember'); +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) { /** * The client the message is from * @type {Client} @@ -86,6 +87,39 @@ class MessageMentions { * @private */ this._channels = null; + + /** + * Crossposted channel data. + * @typedef {Object} CrosspostedChannel + * @property {string} channelID ID of the mentioned channel + * @property {string} guildID ID of the guild that has the channel + * @property {string} type Type of the channel + * @property {string} name The 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/util/Constants.js b/src/util/Constants.js index 9d742e784..7bb3bb50e 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -386,6 +386,7 @@ exports.WSEvents = keyMirror([ * * 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 = [ @@ -401,6 +402,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..b1c86b7d6 --- /dev/null +++ b/src/util/MessageFlags.js @@ -0,0 +1,25 @@ +'use strict'; + +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with an {@link Message#flags} bitfield. + * @extends {BitField} + */ +class MessageFlags extends BitField {} + +/** + * Numeric message flags. All available properties: + * * `CROSSPOSTED` + * * `IS_CROSSPOST` + * * `SUPPRESS_EMBEDS` + * @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, +}; + +module.exports = MessageFlags; diff --git a/typings/index.d.ts b/typings/index.d.ts index 4f03ef11d..cb08e2ffe 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1,3 +1,14 @@ +declare enum ChannelType { + text, + dm, + voice, + group, + category, + news, + store, + unknown +} + declare module 'discord.js' { import BaseCollection from '@discordjs/collection'; import { EventEmitter } from 'events'; @@ -125,7 +136,7 @@ declare module 'discord.js' { public readonly createdTimestamp: number; public deleted: boolean; public id: Snowflake; - public type: 'dm' | 'text' | 'voice' | 'category' | 'news' | 'store' | 'unknown'; + public type: keyof typeof ChannelType; public delete(reason?: string): Promise; public fetch(): Promise; public toString(): string; @@ -914,12 +925,17 @@ declare module 'discord.js' { public toString(): string; } + export class MessageFlags extends BitField { + public static FLAGS: Record; + public static resolve(bit?: BitFieldResolvable): number; + } + export class Message extends Base { constructor(client: Client, data: object, channel: TextChannel | DMChannel); private _edits: Message[]; private patch(data: object): void; - public activity: GroupActivity | null; + public activity: MessageActivity | null; public application: ClientApplication | null; public attachments: Collection; public author: User | null; @@ -939,7 +955,7 @@ declare module 'discord.js' { public id: Snowflake; public readonly member: GuildMember | null; public mentions: MessageMentions; - public nonce: string; + public nonce: string | null; public readonly partial: boolean; public readonly pinnable: boolean; public pinned: boolean; @@ -949,6 +965,8 @@ declare module 'discord.js' { public type: MessageType; public readonly url: string; public webhookID: Snowflake | null; + public flags: Readonly; + public reference: MessageReference | null; public awaitReactions(filter: CollectorFilter, options?: AwaitReactionsOptions): Promise>; public createReactionCollector(filter: CollectorFilter, options?: ReactionCollectorOptions): ReactionCollector; public delete(options?: { timeout?: number, reason?: string }): Promise; @@ -1054,6 +1072,7 @@ declare module 'discord.js' { public readonly members: Collection | null; public roles: Collection; public users: Collection; + public crosspostedChannels: Collection; public toJSON(): object; public static CHANNELS_PATTERN: RegExp; @@ -1886,6 +1905,10 @@ declare module 'discord.js' { | 'LISTENING' | 'WATCHING'; + type MessageFlagsString = 'CROSSPOSTED' + | 'IS_CROSSPOST' + | 'SUPPRESS_EMBEDS'; + interface APIErrror { UNKNOWN_ACCOUNT: number; UNKNOWN_APPLICATION: number; @@ -2128,11 +2151,17 @@ declare module 'discord.js' { name?: string; } - interface GroupActivity { + interface MessageActivity { partyID: string; type: number; } + interface MessageReference { + channelID: string; + guildID: string; + messageID: string | null; + } + type GuildAuditLogsAction = keyof GuildAuditLogsActions; interface GuildAuditLogsActions { @@ -2196,7 +2225,7 @@ declare module 'discord.js' { interface GuildCreateChannelOptions { permissionOverwrites?: OverwriteResolvable[] | Collection; topic?: string; - type?: 'text' | 'voice' | 'category'; + type?: Exclude; nsfw?: boolean; parent?: ChannelResolvable; bitrate?: number; @@ -2373,7 +2402,8 @@ declare module 'discord.js' { | 'USER_PREMIUM_GUILD_SUBSCRIPTION' | 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1' | 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2' - | 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3'; + | 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3' + | 'CHANNEL_FOLLOW_ADD'; interface OverwriteData { allow?: PermissionResolvable; @@ -2611,5 +2641,12 @@ declare module 'discord.js' { type CloseEvent = { wasClean: boolean; code: number; reason: string; target: WebSocket; }; type ErrorEvent = { error: any, message: string, type: string, target: WebSocket; }; + interface CrosspostedChannel { + channelID: Snowflake; + guildID: Snowflake; + type: keyof typeof ChannelType; + name: string; + } + //#endregion }