* Remove GroupDMChannels

they sparked no joy

* Start partials for message deletion

* MessageUpdate partials

* Add partials as an opt-in client option

* Add fetch() to Message

* Message.author should never be undefined

* Fix channels being the wrong type

* Allow fetching channels

* Refactor and add reaction add partials

* Reaction remove partials

* Check for emoji first

* fix message fetching

janky

* User partials in audit logs

* refactor overwrite code

* guild member partials

* partials as a whitelist

* document GuildMember#fetch

* fix: check whether a structure is a partial, not whether cache is true

* typings: Updated for latest commit (#3075)

* partials: fix messageUpdate behaviour (now "old" message can be partial)

* partials: add warnings and docs

* partials: add partials to index.yml

* partials: tighten "partial" definitions

* partials: fix embed-only messages counting as partials
This commit is contained in:
Amish Shah
2019-02-13 17:39:39 +00:00
committed by GitHub
parent 8910fed729
commit 5c3f5d7048
35 changed files with 295 additions and 383 deletions

View File

@@ -12,6 +12,8 @@
path: voice.md path: voice.md
- name: Web builds - name: Web builds
path: web.md path: web.md
- name: Partials
path: partials.md
- name: Examples - name: Examples
files: files:
- name: Ping - name: Ping

61
docs/topics/partials.md Normal file
View File

@@ -0,0 +1,61 @@
# Partials
Partials allow you to receive events that contain uncached instances, providing structures that contain very minimal
data. For example, if you were to receive a `messageDelete` event with an uncached message, normally Discord.js would
discard the event. With partials, you're able to receive the event, with a Message object that contains just an ID.
## Opting in
Partials are opt-in, and you can enable them in the Client options by specifying [PartialTypes](../typedef/PartialType):
```js
// Accept partial messages and DM channels when emitting events
new Client({ partials: ['MESSAGE', 'CHANNEL'] });
```
## Usage & warnings
<warn>The only guaranteed data a partial structure can store is its ID. All other properties/methods should be
considered invalid/defunct while accessing a partial structure.</warn>
After opting-in with the above, you begin to allow partial messages and channels in your caches, so it's important
to check whether they're safe to access whenever you encounter them, whether it be in events or through normal cache
usage.
All instance of structures that you opted-in for will have a `partial` property. As you'd expect, this value is `true`
when the instance is partial. Partial structures are only guaranteed to contain an ID, any other properties and methods
no longer carry their normal type guarantees.
This means you have to take time to consider possible parts of your program that might need checks put in place to
prevent accessing partial data:
```js
client.on('messageDelete', message => {
console.log(`${message.id} was deleted!`);
// Partial messages do not contain any content so skip them
if (!message.partial) {
console.log(`It had content: "${message.content}"`);
}
})
// You can also try to upgrade partials to full instances:
client.on('messageReactionAdd', async (reaction, user) => {
// If a message gains a reaction and it is uncached, fetch and cache the message
// You should account for any errors while fetching, it could return API errors if the resource is missing
if (reaction.message.partial) await reaction.message.fetch();
// Now the message has been cached and is fully available:
console.log(`${reaction.message.author}'s message "${reaction.message.content}" gained a reaction!`);
});
```
<info>If a message is deleted and both the message and channel are uncached, you must enable both 'MESSAGE' and
'CHANNEL' in the client options to receive the messageDelete event.</info>
## Why?
This allows developers to listen to events that contain uncached data, which is useful if you're running a moderation
bot or any bot that relies on still receiving updates to resources you don't have cached -- message reactions are a
good example.
Currently, the only type of channel that can be uncached is a DM channel, there is no reason why guild channels should
not be cached.

View File

@@ -434,6 +434,9 @@ class Client extends BaseClient {
if (typeof options.disableEveryone !== 'boolean') { if (typeof options.disableEveryone !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'disableEveryone', 'a boolean'); throw new TypeError('CLIENT_INVALID_OPTION', 'disableEveryone', 'a boolean');
} }
if (!(options.partials instanceof Array)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'partials', 'an Array');
}
if (typeof options.restWsBridgeTimeout !== 'number' || isNaN(options.restWsBridgeTimeout)) { if (typeof options.restWsBridgeTimeout !== 'number' || isNaN(options.restWsBridgeTimeout)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'restWsBridgeTimeout', 'a number'); throw new TypeError('CLIENT_INVALID_OPTION', 'restWsBridgeTimeout', 'a number');
} }

View File

@@ -1,5 +1,7 @@
'use strict'; 'use strict';
const { PartialTypes } = require('../../util/Constants');
/* /*
ABOUT ACTIONS ABOUT ACTIONS
@@ -20,6 +22,27 @@ class GenericAction {
handle(data) { handle(data) {
return data; return data;
} }
getChannel(data) {
const id = data.channel_id || data.id;
return data.channel || (this.client.options.partials.includes(PartialTypes.CHANNEL) ?
this.client.channels.add({
id,
guild_id: data.guild_id,
}) :
this.client.channels.get(id));
}
getMessage(data, channel) {
const id = data.message_id || data.id;
return data.message || (this.client.options.partials.includes(PartialTypes.MESSAGE) ?
channel.messages.add({
id,
channel_id: channel.id,
guild_id: data.guild_id || (channel.guild ? channel.guild.id : null),
}) :
channel.messages.get(id));
}
} }
module.exports = GenericAction; module.exports = GenericAction;

View File

@@ -12,7 +12,7 @@ class ChannelCreateAction extends Action {
/** /**
* Emitted whenever a channel is created. * Emitted whenever a channel is created.
* @event Client#channelCreate * @event Client#channelCreate
* @param {DMChannel|GroupDMChannel|GuildChannel} channel The channel that was created * @param {DMChannel|GuildChannel} channel The channel that was created
*/ */
client.emit(Events.CHANNEL_CREATE, channel); client.emit(Events.CHANNEL_CREATE, channel);
} }

View File

@@ -19,7 +19,7 @@ class ChannelDeleteAction extends Action {
/** /**
* Emitted whenever a channel is deleted. * Emitted whenever a channel is deleted.
* @event Client#channelDelete * @event Client#channelDelete
* @param {DMChannel|GroupDMChannel|GuildChannel} channel The channel that was deleted * @param {DMChannel|GuildChannel} channel The channel that was deleted
*/ */
client.emit(Events.CHANNEL_DELETE, channel); client.emit(Events.CHANNEL_DELETE, channel);
} }

View File

@@ -6,11 +6,10 @@ const { Events } = require('../../util/Constants');
class MessageDeleteAction extends Action { class MessageDeleteAction extends Action {
handle(data) { handle(data) {
const client = this.client; const client = this.client;
const channel = client.channels.get(data.channel_id); const channel = this.getChannel(data);
let message; let message;
if (channel) { if (channel) {
message = channel.messages.get(data.id); message = this.getMessage(data, channel);
if (message) { if (message) {
channel.messages.delete(message.id); channel.messages.delete(message.id);
message.deleted = true; message.deleted = true;

View File

@@ -11,15 +11,19 @@ const Action = require('./Action');
class MessageReactionAdd extends Action { class MessageReactionAdd extends Action {
handle(data) { handle(data) {
if (!data.emoji) return false;
const user = data.user || this.client.users.get(data.user_id); const user = data.user || this.client.users.get(data.user_id);
if (!user) return false; if (!user) return false;
// Verify channel // Verify channel
const channel = data.channel || this.client.channels.get(data.channel_id); const channel = this.getChannel(data);
if (!channel || channel.type === 'voice') return false; if (!channel || channel.type === 'voice') return false;
// Verify message // Verify message
const message = data.message || channel.messages.get(data.message_id); const message = this.getMessage(data, channel);
if (!message) return false; if (!message) return false;
if (!data.emoji) return false;
// Verify reaction // Verify reaction
const reaction = message.reactions.add({ const reaction = message.reactions.add({
emoji: data.emoji, emoji: data.emoji,

View File

@@ -12,15 +12,19 @@ const { Events } = require('../../util/Constants');
class MessageReactionRemove extends Action { class MessageReactionRemove extends Action {
handle(data) { handle(data) {
if (!data.emoji) return false;
const user = this.client.users.get(data.user_id); const user = this.client.users.get(data.user_id);
if (!user) return false; if (!user) return false;
// Verify channel // Verify channel
const channel = this.client.channels.get(data.channel_id); const channel = this.getChannel(data);
if (!channel || channel.type === 'voice') return false; if (!channel || channel.type === 'voice') return false;
// Verify message // Verify message
const message = channel.messages.get(data.message_id); const message = this.getMessage(data, channel);
if (!message) return false; if (!message) return false;
if (!data.emoji) return false;
// Verify reaction // Verify reaction
const emojiID = data.emoji.id || decodeURIComponent(data.emoji.name); const emojiID = data.emoji.id || decodeURIComponent(data.emoji.name);
const reaction = message.reactions.get(emojiID); const reaction = message.reactions.get(emojiID);

View File

@@ -5,10 +5,12 @@ const { Events } = require('../../util/Constants');
class MessageReactionRemoveAll extends Action { class MessageReactionRemoveAll extends Action {
handle(data) { handle(data) {
const channel = this.client.channels.get(data.channel_id); // Verify channel
const channel = this.getChannel(data);
if (!channel || channel.type === 'voice') return false; if (!channel || channel.type === 'voice') return false;
const message = channel.messages.get(data.message_id); // Verify message
const message = this.getMessage(data, channel);
if (!message) return false; if (!message) return false;
message.reactions.clear(); message.reactions.clear();

View File

@@ -4,11 +4,10 @@ const Action = require('./Action');
class MessageUpdateAction extends Action { class MessageUpdateAction extends Action {
handle(data) { handle(data) {
const client = this.client; const channel = this.getChannel(data);
const channel = client.channels.get(data.channel_id);
if (channel) { if (channel) {
const message = channel.messages.get(data.id); const { id, channel_id, guild_id, author, timestamp, type } = data;
const message = this.getMessage({ id, channel_id, guild_id, author, timestamp, type }, channel);
if (message) { if (message) {
message.patch(data); message.patch(data);
return { return {

View File

@@ -14,7 +14,7 @@ module.exports = (client, { d: data }) => {
* Emitted whenever the pins of a channel are updated. Due to the nature of the WebSocket event, * Emitted whenever the pins of a channel are updated. Due to the nature of the WebSocket event,
* not much information can be provided easily here - you need to manually check the pins yourself. * not much information can be provided easily here - you need to manually check the pins yourself.
* @event Client#channelPinsUpdate * @event Client#channelPinsUpdate
* @param {DMChannel|GroupDMChannel|TextChannel} channel The channel that the pins update occured in * @param {DMChannel|TextChannel} channel The channel that the pins update occured in
* @param {Date} time The time of the pins update * @param {Date} time The time of the pins update
*/ */
client.emit(Events.CHANNEL_PINS_UPDATE, channel, time); client.emit(Events.CHANNEL_PINS_UPDATE, channel, time);

View File

@@ -8,8 +8,8 @@ module.exports = (client, packet) => {
/** /**
* Emitted whenever a channel is updated - e.g. name change, topic change. * Emitted whenever a channel is updated - e.g. name change, topic change.
* @event Client#channelUpdate * @event Client#channelUpdate
* @param {DMChannel|GroupDMChannel|GuildChannel} oldChannel The channel before the update * @param {DMChannel|GuildChannel} oldChannel The channel before the update
* @param {DMChannel|GroupDMChannel|GuildChannel} newChannel The channel after the update * @param {DMChannel|GuildChannel} newChannel The channel after the update
*/ */
client.emit(Events.CHANNEL_UPDATE, old, updated); client.emit(Events.CHANNEL_UPDATE, old, updated);
} }

View File

@@ -65,7 +65,6 @@ module.exports = {
Collector: require('./structures/interfaces/Collector'), Collector: require('./structures/interfaces/Collector'),
DMChannel: require('./structures/DMChannel'), DMChannel: require('./structures/DMChannel'),
Emoji: require('./structures/Emoji'), Emoji: require('./structures/Emoji'),
GroupDMChannel: require('./structures/GroupDMChannel'),
Guild: require('./structures/Guild'), Guild: require('./structures/Guild'),
GuildAuditLogs: require('./structures/GuildAuditLogs'), GuildAuditLogs: require('./structures/GuildAuditLogs'),
GuildChannel: require('./structures/GuildChannel'), GuildChannel: require('./structures/GuildChannel'),

View File

@@ -5,7 +5,7 @@ const Channel = require('../structures/Channel');
const { Events } = require('../util/Constants'); const { Events } = require('../util/Constants');
const kLru = Symbol('LRU'); const kLru = Symbol('LRU');
const lruable = ['group', 'dm']; const lruable = ['dm'];
/** /**
* Stores channels. * Stores channels.
@@ -54,6 +54,7 @@ class ChannelStore extends DataStore {
add(data, guild, cache = true) { add(data, guild, cache = true) {
const existing = this.get(data.id); const existing = this.get(data.id);
if (existing && existing.partial && cache) existing._patch(data);
if (existing) return existing; if (existing) return existing;
const channel = Channel.create(this.client, data, guild); const channel = Channel.create(this.client, data, guild);
@@ -85,11 +86,12 @@ class ChannelStore extends DataStore {
* .then(channel => console.log(channel.name)) * .then(channel => console.log(channel.name))
* .catch(console.error); * .catch(console.error);
*/ */
fetch(id, cache = true) { async fetch(id, cache = true) {
const existing = this.get(id); const existing = this.get(id);
if (existing) return Promise.resolve(existing); if (existing && !existing.partial) return existing;
return this.client.api.channels(id).get().then(data => this.add(data, null, cache)); const data = await this.client.api.channels(id).get();
return this.add(data, null, cache);
} }
/** /**

View File

@@ -18,6 +18,7 @@ class DataStore extends Collection {
add(data, cache = true, { id, extras = [] } = {}) { add(data, cache = true, { id, extras = [] } = {}) {
const existing = this.get(id || data.id); const existing = this.get(id || data.id);
if (existing && existing.partial && cache && existing._patch) existing._patch(data);
if (existing) return existing; if (existing) return existing;
const entry = this.holds ? new this.holds(this.client, data, ...extras) : data; const entry = this.holds ? new this.holds(this.client, data, ...extras) : data;

View File

@@ -180,7 +180,7 @@ class GuildMemberStore extends DataStore {
_fetchSingle({ user, cache }) { _fetchSingle({ user, cache }) {
const existing = this.get(user); const existing = this.get(user);
if (existing && existing.joinedTimestamp) return Promise.resolve(existing); if (existing && !existing.partial) return Promise.resolve(existing);
return this.client.api.guilds(this.guild.id).members(user).get() return this.client.api.guilds(this.guild.id).members(user).get()
.then(data => this.add(data, cache)); .then(data => this.add(data, cache));
} }

View File

@@ -40,6 +40,7 @@ class MessageStore extends DataStore {
* <info>The returned Collection does not contain reaction users of the messages if they were not cached. * <info>The returned Collection does not contain reaction users of the messages if they were not cached.
* Those need to be fetched separately in such a case.</info> * Those need to be fetched separately in such a case.</info>
* @param {Snowflake|ChannelLogsQueryOptions} [message] The ID of the message to fetch, or query parameters. * @param {Snowflake|ChannelLogsQueryOptions} [message] The ID of the message to fetch, or query parameters.
* @param {boolean} [cache=true] Whether to cache the message(s)
* @returns {Promise<Message>|Promise<Collection<Snowflake, Message>>} * @returns {Promise<Message>|Promise<Collection<Snowflake, Message>>}
* @example * @example
* // Get message * // Get message
@@ -57,8 +58,8 @@ class MessageStore extends DataStore {
* .then(messages => console.log(`${messages.filter(m => m.author.id === '84484653687267328').size} messages`)) * .then(messages => console.log(`${messages.filter(m => m.author.id === '84484653687267328').size} messages`))
* .catch(console.error); * .catch(console.error);
*/ */
fetch(message) { fetch(message, cache = true) {
return typeof message === 'string' ? this._fetchId(message) : this._fetchMany(message); return typeof message === 'string' ? this._fetchId(message, cache) : this._fetchMany(message, cache);
} }
/** /**
@@ -80,15 +81,17 @@ class MessageStore extends DataStore {
}); });
} }
async _fetchId(messageID) { async _fetchId(messageID, cache) {
const existing = this.get(messageID);
if (existing && !existing.partial) return existing;
const data = await this.client.api.channels[this.channel.id].messages[messageID].get(); const data = await this.client.api.channels[this.channel.id].messages[messageID].get();
return this.add(data); return this.add(data, cache);
} }
async _fetchMany(options = {}) { async _fetchMany(options = {}, cache) {
const data = await this.client.api.channels[this.channel.id].messages.get({ query: options }); const data = await this.client.api.channels[this.channel.id].messages.get({ query: options });
const messages = new Collection(); const messages = new Collection();
for (const message of data) messages.set(message.id, this.add(message)); for (const message of data) messages.set(message.id, this.add(message, cache));
return messages; return messages;
} }

View File

@@ -51,11 +51,11 @@ class UserStore extends DataStore {
* @param {boolean} [cache=true] Whether to cache the new user object if it isn't already * @param {boolean} [cache=true] Whether to cache the new user object if it isn't already
* @returns {Promise<User>} * @returns {Promise<User>}
*/ */
fetch(id, cache = true) { async fetch(id, cache = true) {
const existing = this.get(id); const existing = this.get(id);
if (existing) return Promise.resolve(existing); if (existing && !existing.partial) return existing;
const data = await this.client.api.users(id).get();
return this.client.api.users(id).get().then(data => this.add(data, cache)); return this.add(data, cache);
} }
} }

View File

@@ -330,7 +330,7 @@ module.exports = APIMessage;
/** /**
* A target for a message. * A target for a message.
* @typedef {TextChannel|DMChannel|GroupDMChannel|User|GuildMember|Webhook|WebhookClient} MessageTarget * @typedef {TextChannel|DMChannel|User|GuildMember|Webhook|WebhookClient} MessageTarget
*/ */
/** /**

View File

@@ -16,7 +16,6 @@ class Channel extends Base {
/** /**
* The type of the channel, either: * The type of the channel, either:
* * `dm` - a DM channel * * `dm` - a DM channel
* * `group` - a Group DM channel
* * `text` - a guild text channel * * `text` - a guild text channel
* * `voice` - a guild voice channel * * `voice` - a guild voice channel
* * `category` - a guild category channel * * `category` - a guild category channel
@@ -84,15 +83,20 @@ class Channel extends Base {
return this.client.api.channels(this.id).delete().then(() => this); return this.client.api.channels(this.id).delete().then(() => this);
} }
/**
* Fetches this channel.
* @returns {Promise<Channel>}
*/
fetch() {
return this.client.channels.fetch(this.id, true);
}
static create(client, data, guild) { static create(client, data, guild) {
const Structures = require('../util/Structures'); const Structures = require('../util/Structures');
let channel; let channel;
if (data.type === ChannelTypes.DM) { if (data.type === ChannelTypes.DM || (data.type !== ChannelTypes.GROUP && !data.guild_id && !guild)) {
const DMChannel = Structures.get('DMChannel'); const DMChannel = Structures.get('DMChannel');
channel = new DMChannel(client, data); channel = new DMChannel(client, data);
} else if (data.type === ChannelTypes.GROUP) {
const GroupDMChannel = Structures.get('GroupDMChannel');
channel = new GroupDMChannel(client, data);
} else { } else {
guild = guild || client.guilds.get(data.guild_id); guild = guild || client.guilds.get(data.guild_id);
if (guild) { if (guild) {

View File

@@ -164,42 +164,6 @@ class ClientUser extends Structures.get('User') {
setAFK(afk) { setAFK(afk) {
return this.setPresence({ afk }); return this.setPresence({ afk });
} }
/**
* An object containing either a user or access token, and an optional nickname.
* @typedef {Object} GroupDMRecipientOptions
* @property {UserResolvable} [user] User to add to the Group DM
* @property {string} [accessToken] Access token to use to add a user to the Group DM
* (only available if a bot is creating the DM)
* @property {string} [nick] Permanent nickname (only available if a bot is creating the DM)
* @property {string} [id] If no user resolvable is provided and you want to assign nicknames
* you must provide user ids instead
*/
/**
* Creates a Group DM.
* @param {GroupDMRecipientOptions[]} recipients The recipients
* @returns {Promise<GroupDMChannel>}
* @example
* // Create a Group DM with a token provided from OAuth
* client.user.createGroupDM([{
* user: '66564597481480192',
* accessToken: token
* }])
* .then(console.log)
* .catch(console.error);
*/
createGroupDM(recipients) {
const data = this.bot ? {
access_tokens: recipients.map(u => u.accessToken),
nicks: recipients.reduce((o, r) => {
if (r.nick) o[r.user ? r.user.id : r.id] = r.nick;
return o;
}, {}),
} : { recipients: recipients.map(u => this.client.users.resolveID(u.user || u.id)) };
return this.client.api.users('@me').channels.post({ data })
.then(res => this.client.channels.add(res));
}
} }
module.exports = ClientUser; module.exports = ClientUser;

View File

@@ -12,6 +12,8 @@ const MessageStore = require('../stores/MessageStore');
class DMChannel extends Channel { class DMChannel extends Channel {
constructor(client, data) { constructor(client, data) {
super(client, data); super(client, data);
// Override the channel type so partials have a known type
this.type = 'dm';
/** /**
* A collection containing the messages sent to this channel * A collection containing the messages sent to this channel
* @type {MessageStore<Snowflake, Message>} * @type {MessageStore<Snowflake, Message>}
@@ -23,11 +25,13 @@ class DMChannel extends Channel {
_patch(data) { _patch(data) {
super._patch(data); super._patch(data);
/** if (data.recipients) {
* The recipient on the other end of the DM /**
* @type {User} * The recipient on the other end of the DM
*/ * @type {User}
this.recipient = this.client.users.add(data.recipients[0]); */
this.recipient = this.client.users.add(data.recipients[0]);
}
/** /**
* The ID of the last message in the channel, if one was sent * The ID of the last message in the channel, if one was sent
@@ -42,6 +46,14 @@ class DMChannel extends Channel {
this.lastPinTimestamp = data.last_pin_timestamp ? new Date(data.last_pin_timestamp).getTime() : null; this.lastPinTimestamp = data.last_pin_timestamp ? new Date(data.last_pin_timestamp).getTime() : null;
} }
/**
* Whether this DMChannel is a partial
* @type {boolean}
*/
get partial() {
return !this.recipient;
}
/** /**
* When concatenated with a string, this automatically returns the recipient's mention instead of the * When concatenated with a string, this automatically returns the recipient's mention instead of the
* DMChannel object. * DMChannel object.

View File

@@ -1,245 +0,0 @@
'use strict';
const Channel = require('./Channel');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const Collection = require('../util/Collection');
const DataResolver = require('../util/DataResolver');
const MessageStore = require('../stores/MessageStore');
/*
{ type: 3,
recipients:
[ { username: 'Charlie',
id: '123',
discriminator: '6631',
avatar: '123' },
{ username: 'Ben',
id: '123',
discriminator: '2055',
avatar: '123' },
{ username: 'Adam',
id: '123',
discriminator: '2406',
avatar: '123' } ],
owner_id: '123',
name: null,
last_message_id: '123',
id: '123',
icon: null }
*/
/**
* Represents a Group DM on Discord.
* @extends {Channel}
* @implements {TextBasedChannel}
*/
class GroupDMChannel extends Channel {
constructor(client, data) {
super(client, data);
/**
* A collection containing the messages sent to this channel
* @type {MessageStore<Snowflake, Message>}
*/
this.messages = new MessageStore(this);
this._typing = new Map();
}
_patch(data) {
super._patch(data);
/**
* The name of this Group DM, can be null if one isn't set
* @type {string}
*/
this.name = data.name;
/**
* A hash of this Group DM icon
* @type {?string}
*/
this.icon = data.icon;
/**
* The user ID of this Group DM's owner
* @type {Snowflake}
*/
this.ownerID = data.owner_id;
/**
* If the DM is managed by an application
* @type {boolean}
*/
this.managed = data.managed;
/**
* Application ID of the application that made this Group DM, if applicable
* @type {?Snowflake}
*/
this.applicationID = data.application_id;
if (data.nicks) {
/**
* Nicknames for group members
* @type {?Collection<Snowflake, string>}
*/
this.nicks = new Collection(data.nicks.map(n => [n.id, n.nick]));
}
if (!this.recipients) {
/**
* A collection of the recipients of this DM, mapped by their ID
* @type {Collection<Snowflake, User>}
*/
this.recipients = new Collection();
}
if (data.recipients) {
for (const recipient of data.recipients) {
const user = this.client.users.add(recipient);
this.recipients.set(user.id, user);
}
}
/**
* The ID of the last message in the channel, if one was sent
* @type {?Snowflake}
*/
this.lastMessageID = data.last_message_id;
/**
* The timestamp when the last pinned message was pinned, if there was one
* @type {?number}
*/
this.lastPinTimestamp = data.last_pin_timestamp ? new Date(data.last_pin_timestamp).getTime() : null;
}
/**
* The owner of this Group DM
* @type {?User}
* @readonly
*/
get owner() {
return this.client.users.get(this.ownerID) || null;
}
/**
* Gets the URL to this Group DM's icon.
* @param {ImageURLOptions} [options={}] Options for the Image URL
* @returns {?string}
*/
iconURL({ format, size } = {}) {
if (!this.icon) return null;
return this.client.rest.cdn.GDMIcon(this.id, this.icon, format, size);
}
/**
* Whether this channel equals another channel. It compares all properties, so for most operations
* it is advisable to just compare `channel.id === channel2.id` as it is much faster and is often
* what most users need.
* @param {GroupDMChannel} channel Channel to compare with
* @returns {boolean}
*/
equals(channel) {
const equal = channel &&
this.id === channel.id &&
this.name === channel.name &&
this.icon === channel.icon &&
this.ownerID === channel.ownerID;
if (equal) {
return this.recipients.equals(channel.recipients);
}
return equal;
}
/**
* Edits this Group DM.
* @param {Object} data New data for this Group DM
* @param {string} [reason] Reason for editing this Group DM
* @returns {Promise<GroupDMChannel>}
*/
edit(data, reason) {
return this.client.api.channels[this.id].patch({
data: {
icon: data.icon,
name: data.name === null ? null : data.name || this.name,
},
reason,
}).then(() => this);
}
/**
* Sets a new icon for this Group DM.
* @param {Base64Resolvable|BufferResolvable} icon The new icon of this Group DM
* @returns {Promise<GroupDMChannel>}
*/
async setIcon(icon) {
return this.edit({ icon: await DataResolver.resolveImage(icon) });
}
/**
* Sets a new name for this Group DM.
* @param {string} name New name for this Group DM
* @returns {Promise<GroupDMChannel>}
*/
setName(name) {
return this.edit({ name });
}
/**
* Adds a user to this Group DM.
* @param {Object} options Options for this method
* @param {UserResolvable} options.user User to add to this Group DM
* @param {string} [options.accessToken] Access token to use to add the user to this Group DM
* @param {string} [options.nick] Permanent nickname to give the user
* @returns {Promise<GroupDMChannel>}
*/
addUser({ user, accessToken, nick }) {
const id = this.client.users.resolveID(user);
return this.client.api.channels[this.id].recipients[id].put({ nick, access_token: accessToken })
.then(() => this);
}
/**
* Removes a user from this Group DM.
* @param {UserResolvable} user User to remove
* @returns {Promise<GroupDMChannel>}
*/
removeUser(user) {
const id = this.client.users.resolveID(user);
return this.client.api.channels[this.id].recipients[id].delete()
.then(() => this);
}
/**
* When concatenated with a string, this automatically returns the channel's name instead of the
* GroupDMChannel object.
* @returns {string}
* @example
* // Logs: Hello from My Group DM!
* console.log(`Hello from ${channel}!`);
*/
toString() {
return this.name;
}
// These are here only for documentation purposes - they are implemented by TextBasedChannel
/* eslint-disable no-empty-function */
get lastMessage() {}
get lastPinAt() {}
send() {}
startTyping() {}
stopTyping() {}
get typing() {}
get typingCount() {}
createMessageCollector() {}
awaitMessages() {}
// Doesn't work on Group DMs; bulkDelete() {}
acknowledge() {}
_cacheMessage() {}
}
TextBasedChannel.applyToClass(GroupDMChannel, true, ['bulkDelete']);
module.exports = GroupDMChannel;

View File

@@ -5,7 +5,7 @@ const Integration = require('./Integration');
const GuildAuditLogs = require('./GuildAuditLogs'); const GuildAuditLogs = require('./GuildAuditLogs');
const Webhook = require('./Webhook'); const Webhook = require('./Webhook');
const VoiceRegion = require('./VoiceRegion'); const VoiceRegion = require('./VoiceRegion');
const { ChannelTypes, DefaultMessageNotifications, browser } = require('../util/Constants'); const { ChannelTypes, DefaultMessageNotifications, PartialTypes, browser } = require('../util/Constants');
const Collection = require('../util/Collection'); const Collection = require('../util/Collection');
const Util = require('../util/Util'); const Util = require('../util/Util');
const DataResolver = require('../util/DataResolver'); const DataResolver = require('../util/DataResolver');
@@ -341,7 +341,9 @@ class Guild extends Base {
* @readonly * @readonly
*/ */
get owner() { get owner() {
return this.members.get(this.ownerID) || null; return this.members.get(this.ownerID) || (this.client.options.partials.includes(PartialTypes.GUILD_MEMBER) ?
this.members.add({ user: { id: this.ownerID } }, true) :
null);
} }
/** /**

View File

@@ -4,6 +4,7 @@ const Collection = require('../util/Collection');
const Snowflake = require('../util/Snowflake'); const Snowflake = require('../util/Snowflake');
const Webhook = require('./Webhook'); const Webhook = require('./Webhook');
const Util = require('../util/Util'); const Util = require('../util/Util');
const PartialTypes = require('../util/Constants');
/** /**
* The target type of an entry, e.g. `GUILD`. Here are the available types: * The target type of an entry, e.g. `GUILD`. Here are the available types:
@@ -234,7 +235,7 @@ class GuildAuditLogs {
* Audit logs entry. * Audit logs entry.
*/ */
class GuildAuditLogsEntry { class GuildAuditLogsEntry {
constructor(logs, guild, data) { constructor(logs, guild, data) { // eslint-disable-line complexity
const targetType = GuildAuditLogs.targetType(data.action_type); const targetType = GuildAuditLogs.targetType(data.action_type);
/** /**
* The target type of this entry * The target type of this entry
@@ -264,7 +265,9 @@ class GuildAuditLogsEntry {
* The user that executed this entry * The user that executed this entry
* @type {User} * @type {User}
*/ */
this.executor = guild.client.users.get(data.user_id); this.executor = guild.client.options.partials.includes(PartialTypes.USER) ?
guild.client.users.add({ id: data.user_id }) :
guild.client.users.get(data.user_id);
/** /**
* An entry in the audit log representing a specific change. * An entry in the audit log representing a specific change.
@@ -329,8 +332,12 @@ class GuildAuditLogsEntry {
return o; return o;
}, {}); }, {});
this.target.id = data.target_id; this.target.id = data.target_id;
} else if ([Targets.USER, Targets.GUILD].includes(targetType)) { } else if (targetType === Targets.USER) {
this.target = guild.client[`${targetType.toLowerCase()}s`].get(data.target_id); this.target = guild.client.options.partials.includes(PartialTypes.USER) ?
guild.client.users.add({ id: data.target_id }) :
guild.client.users.get(data.target_id);
} else if (targetType === Targets.GUILD) {
this.target = guild.client.guilds.get(data.target_id);
} else if (targetType === Targets.WEBHOOK) { } else if (targetType === Targets.WEBHOOK) {
this.target = logs.webhooks.get(data.target_id) || this.target = logs.webhooks.get(data.target_id) ||
new Webhook(guild.client, new Webhook(guild.client,

View File

@@ -28,7 +28,7 @@ class GuildMember extends Base {
* The user that this guild member instance represents * The user that this guild member instance represents
* @type {User} * @type {User}
*/ */
this.user = {}; if (data.user) this.user = client.users.add(data.user, true);
/** /**
* The timestamp the member joined the guild at * The timestamp the member joined the guild at
@@ -79,6 +79,14 @@ class GuildMember extends Base {
return clone; return clone;
} }
/**
* Whether this GuildMember is a partial
* @type {boolean}
*/
get partial() {
return !this.joinedTimestamp;
}
/** /**
* A collection of roles that are applied to this member, mapped by the role ID * A collection of roles that are applied to this member, mapped by the role ID
* @type {GuildMemberRoleStore<Snowflake, Role>} * @type {GuildMemberRoleStore<Snowflake, Role>}
@@ -355,6 +363,14 @@ class GuildMember extends Base {
return this.guild.members.ban(this, options); return this.guild.members.ban(this, options);
} }
/**
* Fetches this GuildMember.
* @returns {Promise<GuildMember>}
*/
fetch() {
return this.guild.members.fetch(this.id, true);
}
/** /**
* When concatenated with a string, this automatically returns the user's mention instead of the GuildMember object. * When concatenated with a string, this automatically returns the user's mention instead of the GuildMember object.
* @returns {string} * @returns {string}

View File

@@ -24,7 +24,7 @@ class Message extends Base {
/** /**
* The channel that the message was sent in * The channel that the message was sent in
* @type {TextChannel|DMChannel|GroupDMChannel} * @type {TextChannel|DMChannel}
*/ */
this.channel = channel; this.channel = channel;
@@ -60,7 +60,7 @@ class Message extends Base {
* The author of the message * The author of the message
* @type {User} * @type {User}
*/ */
this.author = this.client.users.add(data.author, !data.webhook_id); this.author = data.author ? this.client.users.add(data.author, !data.webhook_id) : null;
/** /**
* Whether or not this message is pinned * Whether or not this message is pinned
@@ -90,17 +90,19 @@ class Message extends Base {
* A list of embeds in the message - e.g. YouTube Player * A list of embeds in the message - e.g. YouTube Player
* @type {MessageEmbed[]} * @type {MessageEmbed[]}
*/ */
this.embeds = data.embeds.map(e => new Embed(e)); this.embeds = (data.embeds || []).map(e => new Embed(e));
/** /**
* A collection of attachments in the message - e.g. Pictures - mapped by their ID * A collection of attachments in the message - e.g. Pictures - mapped by their ID
* @type {Collection<Snowflake, MessageAttachment>} * @type {Collection<Snowflake, MessageAttachment>}
*/ */
this.attachments = new Collection(); this.attachments = new Collection();
for (const attachment of data.attachments) { if (data.attachments) {
this.attachments.set(attachment.id, new MessageAttachment( for (const attachment of data.attachments) {
attachment.url, attachment.filename, attachment this.attachments.set(attachment.id, new MessageAttachment(
)); attachment.url, attachment.filename, attachment
));
}
} }
/** /**
@@ -167,6 +169,14 @@ class Message extends Base {
} }
} }
/**
* Whether or not this message is a partial
* @type {boolean}
*/
get partial() {
return typeof this.content !== 'string' || !this.author;
}
/** /**
* Updates the message. * Updates the message.
* @param {Object} data Raw Discord message update data * @param {Object} data Raw Discord message update data
@@ -472,6 +482,14 @@ class Message extends Base {
); );
} }
/**
* Fetch this message.
* @returns {Promise<Message>}
*/
fetch() {
return this.channel.messages.fetch(this.id, true);
}
/** /**
* Fetches the webhook used to create this message. * Fetches the webhook used to create this message.
* @returns {Promise<?Webhook>} * @returns {Promise<?Webhook>}

View File

@@ -15,7 +15,7 @@ const { Events } = require('../util/Constants');
*/ */
class MessageCollector extends Collector { class MessageCollector extends Collector {
/** /**
* @param {TextChannel|DMChannel|GroupDMChannel} channel The channel * @param {TextChannel|DMChannel} channel The channel
* @param {CollectorFilter} filter The filter to be applied to this collector * @param {CollectorFilter} filter The filter to be applied to this collector
* @param {MessageCollectorOptions} options The options to be applied to this collector * @param {MessageCollectorOptions} options The options to be applied to this collector
* @emits MessageCollector#message * @emits MessageCollector#message

View File

@@ -53,6 +53,8 @@ class User extends Base {
*/ */
if (typeof data.avatar !== 'undefined') this.avatar = data.avatar; if (typeof data.avatar !== 'undefined') this.avatar = data.avatar;
if (typeof data.bot !== 'undefined') this.bot = Boolean(data.bot);
/** /**
* The locale of the user's client (ISO 639-1) * The locale of the user's client (ISO 639-1)
* @type {?string} * @type {?string}
@@ -73,6 +75,14 @@ class User extends Base {
this.lastMessageChannelID = null; this.lastMessageChannelID = null;
} }
/**
* Whether this User is a partial
* @type {boolean}
*/
get partial() {
return typeof this.username !== 'string';
}
/** /**
* The timestamp the user was created at * The timestamp the user was created at
* @type {number} * @type {number}
@@ -228,6 +238,14 @@ class User extends Base {
return equal; return equal;
} }
/**
* Fetches this user.
* @returns {Promise<User>}
*/
fetch() {
return this.client.users.fetch(this.id, true);
}
/** /**
* When concatenated with a string, this automatically returns the user's mention instead of the User object. * When concatenated with a string, this automatically returns the user's mention instead of the User object.
* @returns {string} * @returns {string}

View File

@@ -21,6 +21,9 @@ const browser = exports.browser = typeof window !== 'undefined';
* @property {boolean} [fetchAllMembers=false] Whether to cache all guild members and users upon startup, as well as * @property {boolean} [fetchAllMembers=false] Whether to cache all guild members and users upon startup, as well as
* upon joining a guild (should be avoided whenever possible) * upon joining a guild (should be avoided whenever possible)
* @property {boolean} [disableEveryone=false] Default value for {@link MessageOptions#disableEveryone} * @property {boolean} [disableEveryone=false] Default value for {@link MessageOptions#disableEveryone}
* @property {PartialType[]} [partials] Structures allowed to be partial. This means events can be emitted even when
* they're missing all the data for a particular structure. See the "Partials" topic listed in the sidebar for some
* important usage information, as partials require you to put checks in place when handling data.
* @property {number} [restWsBridgeTimeout=5000] Maximum time permitted between REST responses and their * @property {number} [restWsBridgeTimeout=5000] Maximum time permitted between REST responses and their
* corresponding websocket events * corresponding websocket events
* @property {number} [restTimeOffset=500] Extra time in millseconds to wait before continuing to make REST * @property {number} [restTimeOffset=500] Extra time in millseconds to wait before continuing to make REST
@@ -44,6 +47,7 @@ exports.DefaultOptions = {
messageSweepInterval: 0, messageSweepInterval: 0,
fetchAllMembers: false, fetchAllMembers: false,
disableEveryone: false, disableEveryone: false,
partials: [],
restWsBridgeTimeout: 5000, restWsBridgeTimeout: 5000,
disabledEvents: [], disabledEvents: [],
retryLimit: 1, retryLimit: 1,
@@ -261,6 +265,23 @@ exports.Events = {
RAW: 'raw', RAW: 'raw',
}; };
/**
* The type of Structure allowed to be a partial:
* * USER
* * CHANNEL (only affects DMChannels)
* * GUILD_MEMBER
* * MESSAGE
* <warn>Partials require you to put checks in place when handling data, read the Partials topic listed in the
* sidebar for more information.</warn>
* @typedef {string} PartialType
*/
exports.PartialTypes = keyMirror([
'USER',
'CHANNEL',
'GUILD_MEMBER',
'MESSAGE',
]);
/** /**
* The type of a websocket message event, e.g. `MESSAGE_CREATE`. Here are the available events: * The type of a websocket message event, e.g. `MESSAGE_CREATE`. Here are the available events:
* * READY * * READY

View File

@@ -67,7 +67,6 @@ class Structures {
const structures = { const structures = {
GuildEmoji: require('../structures/GuildEmoji'), GuildEmoji: require('../structures/GuildEmoji'),
DMChannel: require('../structures/DMChannel'), DMChannel: require('../structures/DMChannel'),
GroupDMChannel: require('../structures/GroupDMChannel'),
TextChannel: require('../structures/TextChannel'), TextChannel: require('../structures/TextChannel'),
VoiceChannel: require('../structures/VoiceChannel'), VoiceChannel: require('../structures/VoiceChannel'),
CategoryChannel: require('../structures/CategoryChannel'), CategoryChannel: require('../structures/CategoryChannel'),

View File

@@ -395,7 +395,7 @@ class Util {
.replace(/@(everyone|here)/g, '@\u200b$1') .replace(/@(everyone|here)/g, '@\u200b$1')
.replace(/<@!?[0-9]+>/g, input => { .replace(/<@!?[0-9]+>/g, input => {
const id = input.replace(/<|!|>|@/g, ''); const id = input.replace(/<|!|>|@/g, '');
if (message.channel.type === 'dm' || message.channel.type === 'group') { if (message.channel.type === 'dm') {
const user = message.client.users.get(id); const user = message.client.users.get(id);
return user ? `@${user.username}` : input; return user ? `@${user.username}` : input;
} }
@@ -413,7 +413,7 @@ class Util {
return channel ? `#${channel.name}` : input; return channel ? `#${channel.name}` : input;
}) })
.replace(/<@&[0-9]+>/g, input => { .replace(/<@&[0-9]+>/g, input => {
if (message.channel.type === 'dm' || message.channel.type === 'group') return input; if (message.channel.type === 'dm') return input;
const role = message.guild.roles.get(input.replace(/<|@|>|&/g, '')); const role = message.guild.roles.get(input.replace(/<|@|>|&/g, ''));
return role ? `@${role.name}` : input; return role ? `@${role.name}` : input;
}); });

View File

@@ -6,7 +6,7 @@ const ytdl = require('ytdl-core');
const prism = require('prism-media'); const prism = require('prism-media');
const fs = require('fs'); const fs = require('fs');
const client = new Discord.Client({ fetchAllMembers: false, apiRequestMethod: 'sequential' }); const client = new Discord.Client({ fetchAllMembers: false, partials: true, apiRequestMethod: 'sequential' });
const auth = require('./auth.js'); const auth = require('./auth.js');
@@ -34,6 +34,15 @@ client.on('presenceUpdate', (a, b) => {
console.log(a ? a.status : null, b.status, b.user.username); console.log(a ? a.status : null, b.status, b.user.username);
}); });
client.on('messageDelete', async (m) => {
if (m.channel.id != '80426989059575808') return;
console.log(m.channel.recipient);
console.log(m.channel.partial);
await m.channel.fetch();
console.log('\n\n\n\n');
console.log(m.channel);
});
client.on('message', m => { client.on('message', m => {
if (!m.guild) return; if (!m.guild) return;
if (m.author.id !== '66564597481480192') return; if (m.author.id !== '66564597481480192') return;

57
typings/index.d.ts vendored
View File

@@ -129,8 +129,9 @@ declare module 'discord.js' {
public readonly createdTimestamp: number; public readonly createdTimestamp: number;
public deleted: boolean; public deleted: boolean;
public id: Snowflake; public id: Snowflake;
public type: 'dm' | 'group' | 'text' | 'voice' | 'category' | 'unknown'; public type: 'dm' | 'text' | 'voice' | 'category' | 'unknown';
public delete(reason?: string): Promise<Channel>; public delete(reason?: string): Promise<Channel>;
public fetch(): Promise<Channel>;
public toString(): string; public toString(): string;
} }
@@ -264,7 +265,6 @@ declare module 'discord.js' {
export class ClientUser extends User { export class ClientUser extends User {
public mfaEnabled: boolean; public mfaEnabled: boolean;
public verified: boolean; public verified: boolean;
public createGroupDM(recipients: GroupDMRecipientOptions[]): Promise<GroupDMChannel>;
public setActivity(options?: ActivityOptions): Promise<Presence>; public setActivity(options?: ActivityOptions): Promise<Presence>;
public setActivity(name: string, options?: ActivityOptions): Promise<Presence>; public setActivity(name: string, options?: ActivityOptions): Promise<Presence>;
public setAFK(afk: boolean): Promise<Presence>; public setAFK(afk: boolean): Promise<Presence>;
@@ -360,6 +360,7 @@ declare module 'discord.js' {
constructor(client: Client, data?: object); constructor(client: Client, data?: object);
public messages: MessageStore; public messages: MessageStore;
public recipient: User; public recipient: User;
public readonly partial: boolean;
} }
export class Emoji extends Base { export class Emoji extends Base {
@@ -376,26 +377,6 @@ declare module 'discord.js' {
public toString(): string; public toString(): string;
} }
export class GroupDMChannel extends TextBasedChannel(Channel) {
constructor(client: Client, data?: object);
public applicationID: Snowflake;
public icon: string;
public managed: boolean;
public messages: MessageStore;
public name: string;
public nicks: Collection<Snowflake, string>;
public readonly owner: User;
public ownerID: Snowflake;
public recipients: Collection<Snowflake, User>;
public addUser(options: { user: UserResolvable, accessToken?: string, nick?: string }): Promise<GroupDMChannel>;
public edit (data: { icon?: string, name?: string }): Promise<GroupDMChannel>;
public equals(channel: GroupDMChannel): boolean;
public iconURL(options?: AvatarOptions): string;
public removeUser(user: UserResolvable): Promise<GroupDMChannel>;
public setIcon(icon: Base64Resolvable | BufferResolvable): Promise<GroupDMChannel>;
public setName(name: string): Promise<GroupDMChannel>;
}
export class Guild extends Base { export class Guild extends Base {
constructor(client: Client, data: object); constructor(client: Client, data: object);
private _sortedRoles(): Collection<Snowflake, Role>; private _sortedRoles(): Collection<Snowflake, Role>;
@@ -570,12 +551,14 @@ declare module 'discord.js' {
public readonly kickable: boolean; public readonly kickable: boolean;
public readonly manageable: boolean; public readonly manageable: boolean;
public nickname: string; public nickname: string;
public readonly partial: boolean;
public readonly permissions: Readonly<Permissions>; public readonly permissions: Readonly<Permissions>;
public readonly presence: Presence; public readonly presence: Presence;
public roles: GuildMemberRoleStore; public roles: GuildMemberRoleStore;
public user: User; public user: User;
public readonly voice: VoiceState; public readonly voice: VoiceState;
public ban(options?: BanOptions): Promise<GuildMember>; public ban(options?: BanOptions): Promise<GuildMember>;
public fetch(): Promise<GuildMember>;
public createDM(): Promise<DMChannel>; public createDM(): Promise<DMChannel>;
public deleteDM(): Promise<DMChannel>; public deleteDM(): Promise<DMChannel>;
public edit(data: GuildMemberEditData, reason?: string): Promise<GuildMember>; public edit(data: GuildMemberEditData, reason?: string): Promise<GuildMember>;
@@ -619,7 +602,7 @@ declare module 'discord.js' {
export class Invite extends Base { export class Invite extends Base {
constructor(client: Client, data: object); constructor(client: Client, data: object);
public channel: GuildChannel | GroupDMChannel; public channel: GuildChannel;
public code: string; public code: string;
public readonly createdAt: Date; public readonly createdAt: Date;
public createdTimestamp: number; public createdTimestamp: number;
@@ -640,7 +623,7 @@ declare module 'discord.js' {
} }
export class Message extends Base { export class Message extends Base {
constructor(client: Client, data: object, channel: TextChannel | DMChannel | GroupDMChannel); constructor(client: Client, data: object, channel: TextChannel | DMChannel);
private _edits: Message[]; private _edits: Message[];
private patch(data: object): void; private patch(data: object): void;
@@ -648,7 +631,7 @@ declare module 'discord.js' {
public application: ClientApplication; public application: ClientApplication;
public attachments: Collection<Snowflake, MessageAttachment>; public attachments: Collection<Snowflake, MessageAttachment>;
public author: User; public author: User;
public channel: TextChannel | DMChannel | GroupDMChannel; public channel: TextChannel | DMChannel;
public readonly cleanContent: string; public readonly cleanContent: string;
public content: string; public content: string;
public readonly createdAt: Date; public readonly createdAt: Date;
@@ -665,6 +648,7 @@ declare module 'discord.js' {
public readonly member: GuildMember; public readonly member: GuildMember;
public mentions: MessageMentions; public mentions: MessageMentions;
public nonce: string; public nonce: string;
public readonly partial: boolean;
public readonly pinnable: boolean; public readonly pinnable: boolean;
public pinned: boolean; public pinned: boolean;
public reactions: ReactionStore; public reactions: ReactionStore;
@@ -680,6 +664,7 @@ declare module 'discord.js' {
public edit(options: MessageEditOptions | MessageEmbed | APIMessage): Promise<Message>; public edit(options: MessageEditOptions | MessageEmbed | APIMessage): Promise<Message>;
public equals(message: Message, rawData: object): boolean; public equals(message: Message, rawData: object): boolean;
public fetchWebhook(): Promise<Webhook>; public fetchWebhook(): Promise<Webhook>;
public fetch(): Promise<Message>;
public pin(): Promise<Message>; public pin(): Promise<Message>;
public react(emoji: EmojiIdentifierResolvable): Promise<MessageReaction>; public react(emoji: EmojiIdentifierResolvable): Promise<MessageReaction>;
public reply(content?: StringResolvable, options?: MessageOptions | MessageAdditions): Promise<Message | Message[]>; public reply(content?: StringResolvable, options?: MessageOptions | MessageAdditions): Promise<Message | Message[]>;
@@ -706,7 +691,7 @@ declare module 'discord.js' {
} }
export class MessageCollector extends Collector<Snowflake, Message> { export class MessageCollector extends Collector<Snowflake, Message> {
constructor(channel: TextChannel | DMChannel | GroupDMChannel, filter: CollectorFilter, options?: MessageCollectorOptions); constructor(channel: TextChannel | DMChannel, filter: CollectorFilter, options?: MessageCollectorOptions);
public channel: Channel; public channel: Channel;
public options: MessageCollectorOptions; public options: MessageCollectorOptions;
public received: number; public received: number;
@@ -1078,6 +1063,7 @@ declare module 'discord.js' {
public readonly dmChannel: DMChannel; public readonly dmChannel: DMChannel;
public id: Snowflake; public id: Snowflake;
public locale: string; public locale: string;
public readonly partial: boolean;
public readonly presence: Presence; public readonly presence: Presence;
public readonly tag: string; public readonly tag: string;
public username: string; public username: string;
@@ -1086,6 +1072,7 @@ declare module 'discord.js' {
public deleteDM(): Promise<DMChannel>; public deleteDM(): Promise<DMChannel>;
public displayAvatarURL(options?: AvatarOptions): string; public displayAvatarURL(options?: AvatarOptions): string;
public equals(user: User): boolean; public equals(user: User): boolean;
public fetch(): Promise<User>;
public toString(): string; public toString(): string;
public typingDurationIn(channel: ChannelResolvable): number; public typingDurationIn(channel: ChannelResolvable): number;
public typingIn(channel: ChannelResolvable): boolean; public typingIn(channel: ChannelResolvable): boolean;
@@ -1385,7 +1372,7 @@ declare module 'discord.js' {
} }
export class MessageStore extends DataStore<Snowflake, Message, typeof Message, MessageResolvable> { export class MessageStore extends DataStore<Snowflake, Message, typeof Message, MessageResolvable> {
constructor(channel: TextChannel | DMChannel | GroupDMChannel, iterable?: Iterable<any>); constructor(channel: TextChannel | DMChannel, iterable?: Iterable<any>);
public fetch(message: Snowflake): Promise<Message>; public fetch(message: Snowflake): Promise<Message>;
public fetch(options?: ChannelLogsQueryOptions): Promise<Collection<Snowflake, Message>>; public fetch(options?: ChannelLogsQueryOptions): Promise<Collection<Snowflake, Message>>;
public fetchPinned(): Promise<Collection<Snowflake, Message>>; public fetchPinned(): Promise<Collection<Snowflake, Message>>;
@@ -1607,6 +1594,7 @@ declare module 'discord.js' {
messageSweepInterval?: number; messageSweepInterval?: number;
fetchAllMembers?: boolean; fetchAllMembers?: boolean;
disableEveryone?: boolean; disableEveryone?: boolean;
partials?: PartialTypes[];
restWsBridgeTimeout?: number; restWsBridgeTimeout?: number;
restTimeOffset?: number; restTimeOffset?: number;
restSweepInterval?: number; restSweepInterval?: number;
@@ -1676,7 +1664,6 @@ declare module 'discord.js' {
type Extendable = { type Extendable = {
GuildEmoji: typeof GuildEmoji; GuildEmoji: typeof GuildEmoji;
DMChannel: typeof DMChannel; DMChannel: typeof DMChannel;
GroupDMChannel: typeof GroupDMChannel;
TextChannel: typeof TextChannel; TextChannel: typeof TextChannel;
VoiceChannel: typeof VoiceChannel; VoiceChannel: typeof VoiceChannel;
CategoryChannel: typeof CategoryChannel; CategoryChannel: typeof CategoryChannel;
@@ -1711,13 +1698,6 @@ declare module 'discord.js' {
type: number; type: number;
}; };
type GroupDMRecipientOptions = {
user?: UserResolvable | Snowflake;
accessToken?: string;
nick?: string;
id?: Snowflake;
};
type GuildAuditLogsAction = keyof GuildAuditLogsActions; type GuildAuditLogsAction = keyof GuildAuditLogsActions;
type GuildAuditLogsActions = { type GuildAuditLogsActions = {
@@ -1934,7 +1914,7 @@ declare module 'discord.js' {
type MessageResolvable = Message | Snowflake; type MessageResolvable = Message | Snowflake;
type MessageTarget = TextChannel | DMChannel | GroupDMChannel | User | GuildMember | Webhook | WebhookClient; type MessageTarget = TextChannel | DMChannel | User | GuildMember | Webhook | WebhookClient;
type MessageType = 'DEFAULT' type MessageType = 'DEFAULT'
| 'RECIPIENT_ADD' | 'RECIPIENT_ADD'
@@ -2023,6 +2003,11 @@ declare module 'discord.js' {
desktop?: ClientPresenceStatus desktop?: ClientPresenceStatus
}; };
type PartialTypes = 'USER'
| 'CHANNEL'
| 'GUILD_MEMBER'
| 'MESSAGE';
type PresenceStatus = ClientPresenceStatus | 'offline'; type PresenceStatus = ClientPresenceStatus | 'offline';
type PresenceStatusData = ClientPresenceStatus | 'invisible'; type PresenceStatusData = ClientPresenceStatus | 'invisible';