2022-04-16 10:44:43 +00:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
const { Collection } = require('@discordjs/collection');
|
2022-04-16 12:01:05 +00:00
|
|
|
const BigNumber = require('bignumber.js');
|
2022-04-16 10:44:43 +00:00
|
|
|
const CachedManager = require('./CachedManager');
|
|
|
|
const { TypeError, Error } = require('../errors');
|
|
|
|
const { Message } = require('../structures/Message');
|
|
|
|
const MessagePayload = require('../structures/MessagePayload');
|
|
|
|
const Util = require('../util/Util');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Manages API methods for Messages and holds their cache.
|
|
|
|
* @extends {CachedManager}
|
|
|
|
*/
|
|
|
|
class MessageManager extends CachedManager {
|
|
|
|
constructor(channel, iterable) {
|
|
|
|
super(channel.client, Message, iterable);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The channel that the messages belong to
|
|
|
|
* @type {TextBasedChannels}
|
|
|
|
*/
|
|
|
|
this.channel = channel;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The cache of Messages
|
|
|
|
* @type {Collection<Snowflake, Message>}
|
|
|
|
* @name MessageManager#cache
|
|
|
|
*/
|
|
|
|
|
|
|
|
_add(data, cache) {
|
|
|
|
return super._add(data, cache);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The parameters to pass in when requesting previous messages from a channel. `around`, `before` and
|
|
|
|
* `after` are mutually exclusive. All the parameters are optional.
|
|
|
|
* @typedef {Object} ChannelLogsQueryOptions
|
|
|
|
* @property {number} [limit=50] Number of messages to acquire
|
|
|
|
* @property {Snowflake} [before] The message's id to get the messages that were posted before it
|
|
|
|
* @property {Snowflake} [after] The message's id to get the messages that were posted after it
|
|
|
|
* @property {Snowflake} [around] The message's id to get the messages that were posted around it
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets a message, or messages, from this channel.
|
|
|
|
* <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>
|
|
|
|
* @param {Snowflake|ChannelLogsQueryOptions} [message] The id of the message to fetch, or query parameters.
|
|
|
|
* @param {BaseFetchOptions} [options] Additional options for this fetch
|
|
|
|
* @returns {Promise<Message|Collection<Snowflake, Message>>}
|
|
|
|
* @example
|
|
|
|
* // Get message
|
|
|
|
* channel.messages.fetch('99539446449315840')
|
|
|
|
* .then(message => console.log(message.content))
|
|
|
|
* .catch(console.error);
|
|
|
|
* @example
|
|
|
|
* // Get messages
|
|
|
|
* channel.messages.fetch({ limit: 10 })
|
|
|
|
* .then(messages => console.log(`Received ${messages.size} messages`))
|
|
|
|
* .catch(console.error);
|
|
|
|
* @example
|
|
|
|
* // Get messages and filter by user id
|
|
|
|
* channel.messages.fetch()
|
|
|
|
* .then(messages => console.log(`${messages.filter(m => m.author.id === '84484653687267328').size} messages`))
|
|
|
|
* .catch(console.error);
|
|
|
|
*/
|
|
|
|
fetch(message, { cache = true, force = false } = {}) {
|
|
|
|
return typeof message === 'string' ? this._fetchId(message, cache, force) : this._fetchMany(message, cache);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fetches the pinned messages of this channel and returns a collection of them.
|
|
|
|
* <info>The returned Collection does not contain any reaction data of the messages.
|
|
|
|
* Those need to be fetched separately.</info>
|
|
|
|
* @param {boolean} [cache=true] Whether to cache the message(s)
|
|
|
|
* @returns {Promise<Collection<Snowflake, Message>>}
|
|
|
|
* @example
|
|
|
|
* // Get pinned messages
|
|
|
|
* channel.messages.fetchPinned()
|
|
|
|
* .then(messages => console.log(`Received ${messages.size} messages`))
|
|
|
|
* .catch(console.error);
|
|
|
|
*/
|
|
|
|
async fetchPinned(cache = true) {
|
|
|
|
const data = await this.client.api.channels[this.channel.id].pins.get();
|
|
|
|
const messages = new Collection();
|
|
|
|
for (const message of data) messages.set(message.id, this._add(message, cache));
|
|
|
|
return messages;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Data that can be resolved to a Message object. This can be:
|
|
|
|
* * A Message
|
|
|
|
* * A Snowflake
|
|
|
|
* @typedef {Message|Snowflake} MessageResolvable
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resolves a {@link MessageResolvable} to a {@link Message} object.
|
|
|
|
* @method resolve
|
|
|
|
* @memberof MessageManager
|
|
|
|
* @instance
|
|
|
|
* @param {MessageResolvable} message The message resolvable to resolve
|
|
|
|
* @returns {?Message}
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resolves a {@link MessageResolvable} to a {@link Message} id.
|
|
|
|
* @method resolveId
|
|
|
|
* @memberof MessageManager
|
|
|
|
* @instance
|
|
|
|
* @param {MessageResolvable} message The message resolvable to resolve
|
|
|
|
* @returns {?Snowflake}
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Edits a message, even if it's not cached.
|
|
|
|
* @param {MessageResolvable} message The message to edit
|
|
|
|
* @param {string|MessageEditOptions|MessagePayload} options The options to edit the message
|
|
|
|
* @returns {Promise<Message>}
|
|
|
|
*/
|
|
|
|
async edit(message, options) {
|
|
|
|
const messageId = this.resolveId(message);
|
|
|
|
if (!messageId) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
|
|
|
|
|
|
|
let messagePayload;
|
|
|
|
if (options instanceof MessagePayload) {
|
|
|
|
messagePayload = await options.resolveData();
|
|
|
|
} else {
|
|
|
|
messagePayload = await MessagePayload.create(message instanceof Message ? message : this, options).resolveData();
|
|
|
|
}
|
|
|
|
const { data, files } = await messagePayload.resolveFiles();
|
|
|
|
const d = await this.client.api.channels[this.channel.id].messages[messageId].patch({ data, files });
|
|
|
|
|
|
|
|
const existing = this.cache.get(messageId);
|
|
|
|
if (existing) {
|
|
|
|
const clone = existing._clone();
|
|
|
|
clone._patch(d);
|
|
|
|
return clone;
|
|
|
|
}
|
|
|
|
return this._add(d);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Publishes a message in an announcement channel to all channels following it, even if it's not cached.
|
|
|
|
* @param {MessageResolvable} message The message to publish
|
|
|
|
* @returns {Promise<Message>}
|
|
|
|
*/
|
|
|
|
async crosspost(message) {
|
|
|
|
message = this.resolveId(message);
|
|
|
|
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
|
|
|
|
|
|
|
const data = await this.client.api.channels(this.channel.id).messages(message).crosspost.post();
|
|
|
|
return this.cache.get(data.id) ?? this._add(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Pins a message to the channel's pinned messages, even if it's not cached.
|
|
|
|
* @param {MessageResolvable} message The message to pin
|
2022-05-14 08:06:15 +00:00
|
|
|
* @param {string} [reason] Reason for pinning
|
2022-04-16 10:44:43 +00:00
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
2022-05-14 08:06:15 +00:00
|
|
|
async pin(message, reason) {
|
2022-04-16 10:44:43 +00:00
|
|
|
message = this.resolveId(message);
|
|
|
|
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
|
|
|
|
2022-05-14 08:06:15 +00:00
|
|
|
await this.client.api.channels(this.channel.id).pins(message).put({ reason });
|
2022-04-16 10:44:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unpins a message from the channel's pinned messages, even if it's not cached.
|
|
|
|
* @param {MessageResolvable} message The message to unpin
|
2022-05-14 08:06:15 +00:00
|
|
|
* @param {string} [reason] Reason for unpinning
|
2022-04-16 10:44:43 +00:00
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
2022-05-14 08:06:15 +00:00
|
|
|
async unpin(message, reason) {
|
2022-04-16 10:44:43 +00:00
|
|
|
message = this.resolveId(message);
|
|
|
|
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
|
|
|
|
2022-05-14 08:06:15 +00:00
|
|
|
await this.client.api.channels(this.channel.id).pins(message).delete({ reason });
|
2022-04-16 10:44:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a reaction to a message, even if it's not cached.
|
|
|
|
* @param {MessageResolvable} message The message to react to
|
|
|
|
* @param {EmojiIdentifierResolvable} emoji The emoji to react with
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
|
|
|
async react(message, emoji) {
|
|
|
|
message = this.resolveId(message);
|
|
|
|
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
|
|
|
|
|
|
|
emoji = Util.resolvePartialEmoji(emoji);
|
|
|
|
if (!emoji) throw new TypeError('EMOJI_TYPE', 'emoji', 'EmojiIdentifierResolvable');
|
|
|
|
|
|
|
|
const emojiId = emoji.id
|
|
|
|
? `${emoji.animated ? 'a:' : ''}${emoji.name}:${emoji.id}`
|
|
|
|
: encodeURIComponent(emoji.name);
|
|
|
|
|
|
|
|
// eslint-disable-next-line newline-per-chained-call
|
|
|
|
await this.client.api.channels(this.channel.id).messages(message).reactions(emojiId, '@me').put();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deletes a message, even if it's not cached.
|
|
|
|
* @param {MessageResolvable} message The message to delete
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
|
|
|
async delete(message) {
|
|
|
|
message = this.resolveId(message);
|
|
|
|
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
|
|
|
|
|
|
|
await this.client.api.channels(this.channel.id).messages(message).delete();
|
|
|
|
}
|
|
|
|
|
|
|
|
async _fetchId(messageId, cache, force) {
|
|
|
|
if (!force) {
|
|
|
|
const existing = this.cache.get(messageId);
|
|
|
|
if (existing && !existing.partial) return existing;
|
|
|
|
}
|
|
|
|
|
2022-04-16 12:01:05 +00:00
|
|
|
// Const data = await this.client.api.channels[this.channel.id].messages[messageId].get(); // Discord Block
|
2022-04-16 10:44:43 +00:00
|
|
|
// https://canary.discord.com/api/v9/guilds/809133733591384155/messages/search?channel_id=840225732902518825&max_id=957254525360697375&min_id=957254525360697373
|
|
|
|
const data = (
|
|
|
|
await this.client.api.guilds[this.channel.guild.id].messages.search.get({
|
|
|
|
query: {
|
|
|
|
channel_id: this.channel.id,
|
|
|
|
max_id: new BigNumber.BigNumber(messageId).plus(1).toString(),
|
|
|
|
min_id: new BigNumber.BigNumber(messageId).minus(1).toString(),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
).messages[0];
|
|
|
|
if (data) return this._add(data[0], cache);
|
|
|
|
else throw new Error('MESSAGE_ID_NOT_FOUND');
|
|
|
|
}
|
|
|
|
|
|
|
|
async _fetchMany(options = {}, cache) {
|
|
|
|
const data = await this.client.api.channels[this.channel.id].messages.get({ query: options });
|
|
|
|
const messages = new Collection();
|
|
|
|
for (const message of data) messages.set(message.id, this._add(message, cache));
|
|
|
|
return messages;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = MessageManager;
|