Skip to content

Commit

Permalink
feat: add ChannelManager#createMessage()
Browse files Browse the repository at this point in the history
  • Loading branch information
TAEMBO committed Jan 14, 2025
1 parent ff52ba3 commit fdc38c6
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 129 deletions.
50 changes: 50 additions & 0 deletions packages/discord.js/src/managers/ChannelManager.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
'use strict';

const process = require('node:process');
const { lazy } = require('@discordjs/util');
const { Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { BaseChannel } = require('../structures/BaseChannel');
const MessagePayload = require('../structures/MessagePayload');
const { createChannel } = require('../util/Channels');
const { ThreadChannelTypes } = require('../util/Constants');
const Events = require('../util/Events');

const getMessage = lazy(() => require('../structures/Message').Message);

let cacheWarningEmitted = false;

/**
Expand Down Expand Up @@ -123,6 +127,52 @@ class ChannelManager extends CachedManager {
const data = await this.client.rest.get(Routes.channel(id));
return this._add(data, null, { cache, allowUnknownGuild });
}

/**
* Creates a message in a channel.
* @param {TextChannelResolvable} channel The channel to send the message to
* @param {string|MessagePayload|MessageCreateOptions} options The options to provide
* @returns {Promise<Message>}
* @example
* // Send a basic message
* client.channels.createMessage(channel, 'hello!')
* .then(message => console.log(`Sent message: ${message.content}`))
* .catch(console.error);
* @example
* // Send a remote file
* client.channels.createMessage(channel, {
* files: ['https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048']
* })
* .then(console.log)
* .catch(console.error);
* @example
* // Send a local file
* client.channels.createMessage(channel, {
* files: [{
* attachment: 'entire/path/to/file.jpg',
* name: 'file.jpg',
* description: 'A description of the file'
* }]
* })
* .then(console.log)
* .catch(console.error);
*/
async createMessage(channel, options) {
let messagePayload;

if (options instanceof MessagePayload) {
messagePayload = options.resolveBody();
} else {
messagePayload = MessagePayload.create(this, options).resolveBody();
}

const resolvedChannelId = this.resolveId(channel);
const resolvedChannel = this.resolve(channel);
const { body, files } = await messagePayload.resolveFiles();
const data = await this.client.rest.post(Routes.channelMessages(resolvedChannelId), { body, files });

return resolvedChannel?.messages._add(data) ?? new (getMessage())(this.client, data);
}
}

module.exports = ChannelManager;
31 changes: 2 additions & 29 deletions packages/discord.js/src/managers/MessageManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

const { Collection } = require('@discordjs/collection');
const { makeURLSearchParams } = require('@discordjs/rest');
const { MessageReferenceType, Routes } = require('discord-api-types/v10');
const { Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors');
const { DiscordjsTypeError, ErrorCodes } = require('../errors');
const { Message } = require('../structures/Message');
const MessagePayload = require('../structures/MessagePayload');
const { MakeCacheOverrideSymbol } = require('../util/Symbols');
Expand Down Expand Up @@ -209,33 +209,6 @@ class MessageManager extends CachedManager {
return this.cache.get(data.id) ?? this._add(data);
}

/**
* Forwards a message to this manager's channel.
* @param {Message|MessageReference} reference The message to forward
* @returns {Promise<Message>}
*/
async forward(reference) {
if (!reference) throw new DiscordjsError(ErrorCodes.MessageReferenceMissing);
const message_id = this.resolveId(reference.messageId);
if (!message_id) throw new DiscordjsError(ErrorCodes.MessageReferenceMissing);
const channel_id = this.client.channels.resolveId(reference.channelId);
if (!channel_id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'channel', 'ChannelResolvable');
const guild_id = this.client.guilds.resolveId(reference.guildId);

const data = await this.client.rest.post(Routes.channelMessages(this.channel.id), {
body: {
message_reference: {
message_id,
channel_id,
guild_id,
type: MessageReferenceType.Forward,
},
},
});

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
Expand Down
34 changes: 16 additions & 18 deletions packages/discord.js/src/structures/GuildMember.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
const { PermissionFlagsBits } = require('discord-api-types/v10');
const Base = require('./Base');
const VoiceState = require('./VoiceState');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const { DiscordjsError, ErrorCodes } = require('../errors');
const GuildMemberRoleManager = require('../managers/GuildMemberRoleManager');
const { GuildMemberFlagsBitField } = require('../util/GuildMemberFlagsBitField');
const PermissionsBitField = require('../util/PermissionsBitField');

/**
* Represents a member of a guild on Discord.
* @implements {TextBasedChannel}
* @extends {Base}
*/
class GuildMember extends Base {
Expand Down Expand Up @@ -478,6 +476,22 @@ class GuildMember extends Base {
return this.guild.members.fetch({ user: this.id, cache: true, force });
}

/**
* Sends a message to this user.
* @param {string|MessagePayload|MessageCreateOptions} options The options to provide
* @returns {Promise<Message>}
* @example
* // Send a direct message
* guildMember.send('Hello!')
* .then(message => console.log(`Sent message: ${message.content} to ${guildMember.displayName}`))
* .catch(console.error);
*/
async send(options) {
const dmChannel = await this.createDM();

return this.client.channels.createMessage(dmChannel, options);
}

/**
* Whether this guild member equals another guild member. It compares all properties, so for most
* comparison it is advisable to just compare `member.id === member2.id` as it is significantly faster
Expand Down Expand Up @@ -529,20 +543,4 @@ class GuildMember extends Base {
}
}

/**
* Sends a message to this user.
* @method send
* @memberof GuildMember
* @instance
* @param {string|MessagePayload|MessageCreateOptions} options The options to provide
* @returns {Promise<Message>}
* @example
* // Send a direct message
* guildMember.send('Hello!')
* .then(message => console.log(`Sent message: ${message.content} to ${guildMember.displayName}`))
* .catch(console.error);
*/

TextBasedChannel.applyToClass(GuildMember);

exports.GuildMember = GuildMember;
41 changes: 23 additions & 18 deletions packages/discord.js/src/structures/Message.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const MessagePayload = require('./MessagePayload');
const { Poll } = require('./Poll.js');
const ReactionCollector = require('./ReactionCollector');
const { Sticker } = require('./Sticker');
const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors');
const { DiscordjsError, ErrorCodes } = require('../errors');
const ReactionManager = require('../managers/ReactionManager');
const { createComponent } = require('../util/Components');
const { NonSystemMessageTypes, MaxBulkDeletableMessageAge, UndeletableMessageTypes } = require('../util/Constants');
Expand Down Expand Up @@ -677,7 +677,11 @@ class Message extends Base {
* @readonly
*/
get editable() {
const precheck = Boolean(this.author.id === this.client.user.id && (!this.guild || this.channel?.viewable));
const precheck = Boolean(
this.author.id === this.client.user.id &&
(!this.guild || this.channel?.viewable) &&
this.reference?.type !== MessageReferenceType.Forward,
);

// Regardless of permissions thread messages cannot be edited if
// the thread is archived or the thread is locked and the bot does not have permission to manage threads.
Expand Down Expand Up @@ -767,20 +771,6 @@ class Message extends Base {
return message;
}

/**
* Forwards this message.
* @param {ChannelResolvable} channel The channel to forward this message to
* @returns {Promise<Message>}
*/
async forward(channel) {
const resolvedChannel = this.client.channels.resolve(channel);

if (!resolvedChannel) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'channel', 'ChannelResolvable');

const message = await resolvedChannel.messages.forward(this);
return message;
}

/**
* Whether the message is crosspostable by the client user
* @type {boolean}
Expand Down Expand Up @@ -927,7 +917,6 @@ class Message extends Base {
* .catch(console.error);
*/
reply(options) {
if (!this.channel) return Promise.reject(new DiscordjsError(ErrorCodes.ChannelNotCached));
let data;

if (options instanceof MessagePayload) {
Expand All @@ -943,7 +932,23 @@ class Message extends Base {
},
});
}
return this.channel.send(data);
return this.client.channels.createMessage(this.channelId, data);
}

/**
* Forwards this message.
* @param {TextChannelResolvable} channel The channel to forward this message to.
* @returns {Promise<Message>}
*/
forward(channel) {
return this.client.channels.createMessage(channel, {
messageReference: {
messageId: this.id,
channelId: this.channelId,
guildId: this.guildId,
type: MessageReferenceType.Forward,
},
});
}

/**
Expand Down
15 changes: 6 additions & 9 deletions packages/discord.js/src/structures/MessagePayload.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ class MessagePayload {
if (
// eslint-disable-next-line eqeqeq
this.options.flags != null ||
(this.isMessage && this.options.reply === undefined) ||
(this.isMessage && this.options.messageReference === undefined) ||
this.isMessageManager
) {
flags = new MessageFlagsBitField(this.options.flags).bitfield;
Expand All @@ -170,15 +170,12 @@ class MessagePayload {
let message_reference;
if (this.options.messageReference) {
const reference = this.options.messageReference;
const message_id = this.target.messages.resolveId(reference.messageId);
const channel_id = this.target.client.channels.resolveId(reference.channelId);
const guild_id = this.target.client.guilds.resolveId(reference.guildId);

if (message_id) {
if (reference.messageId) {
message_reference = {
message_id,
channel_id,
guild_id,
message_id: reference.messageId,
channel_id: reference.channelId,
guild_id: reference.guildId,
type: reference.type,
fail_if_not_exists: reference.failIfNotExists ?? this.target.client.options.failIfNotExists,
};
Expand Down Expand Up @@ -298,7 +295,7 @@ module.exports = MessagePayload;

/**
* A target for a message.
* @typedef {TextBasedChannels|User|GuildMember|Webhook|WebhookClient|BaseInteraction|InteractionWebhook|
* @typedef {TextBasedChannels|ChannelManager|Webhook|WebhookClient|BaseInteraction|InteractionWebhook|
* Message|MessageManager} MessageTarget
*/

Expand Down
34 changes: 16 additions & 18 deletions packages/discord.js/src/structures/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ const { userMention } = require('@discordjs/formatters');
const { calculateUserDefaultAvatarIndex } = require('@discordjs/rest');
const { DiscordSnowflake } = require('@sapphire/snowflake');
const Base = require('./Base');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const UserFlagsBitField = require('../util/UserFlagsBitField');

/**
* Represents a user on Discord.
* @implements {TextBasedChannel}
* @extends {Base}
*/
class User extends Base {
Expand Down Expand Up @@ -277,6 +275,22 @@ class User extends Base {
return this.client.users.deleteDM(this.id);
}

/**
* Sends a message to this user.
* @param {string|MessagePayload|MessageCreateOptions} options The options to provide
* @returns {Promise<Message>}
* @example
* // Send a direct message
* user.send('Hello!')
* .then(message => console.log(`Sent message: ${message.content} to ${user.tag}`))
* .catch(console.error);
*/
async send(options) {
const dmChannel = await this.createDM();

return this.client.channels.createMessage(dmChannel, options);
}

/**
* Checks if the user is equal to another.
* It compares id, username, discriminator, avatar, banner, accent color, and bot flags.
Expand Down Expand Up @@ -361,20 +375,4 @@ class User extends Base {
}
}

/**
* Sends a message to this user.
* @method send
* @memberof User
* @instance
* @param {string|MessagePayload|MessageCreateOptions} options The options to provide
* @returns {Promise<Message>}
* @example
* // Send a direct message
* user.send('Hello!')
* .then(message => console.log(`Sent message: ${message.content} to ${user.tag}`))
* .catch(console.error);
*/

TextBasedChannel.applyToClass(User);

module.exports = User;
25 changes: 3 additions & 22 deletions packages/discord.js/src/structures/interfaces/TextBasedChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const { DiscordjsTypeError, DiscordjsError, ErrorCodes } = require('../../errors
const { MaxBulkDeletableMessageAge } = require('../../util/Constants');
const InteractionCollector = require('../InteractionCollector');
const MessageCollector = require('../MessageCollector');
const MessagePayload = require('../MessagePayload');

/**
* Interface for classes that have text-channel-like features.
Expand Down Expand Up @@ -161,27 +160,8 @@ class TextBasedChannel {
* .then(console.log)
* .catch(console.error);
*/
async send(options) {
const User = require('../User');
const { GuildMember } = require('../GuildMember');

if (this instanceof User || this instanceof GuildMember) {
const dm = await this.createDM();
return dm.send(options);
}

let messagePayload;

if (options instanceof MessagePayload) {
messagePayload = options.resolveBody();
} else {
messagePayload = MessagePayload.create(this, options).resolveBody();
}

const { body, files } = await messagePayload.resolveFiles();
const d = await this.client.rest.post(Routes.channelMessages(this.id), { body, files });

return this.messages.cache.get(d.id) ?? this.messages._add(d);
send(options) {
return this.client.channels.createMessage(this, options);
}

/**
Expand Down Expand Up @@ -416,6 +396,7 @@ class TextBasedChannel {
'setNSFW',
);
}

for (const prop of props) {
if (ignore.includes(prop)) continue;
Object.defineProperty(
Expand Down
Loading

0 comments on commit fdc38c6

Please sign in to comment.