Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add ChannelManager#createMessage() #10559

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
10 changes: 0 additions & 10 deletions packages/discord.js/src/managers/UserManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,6 @@ class UserManager extends CachedManager {
return this._add(data, cache);
}

/**
* Sends a message to a user.
* @param {UserResolvable} user The UserResolvable to identify
* @param {string|MessagePayload|MessageCreateOptions} options The options to provide
* @returns {Promise<Message>}
*/
async send(user, options) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm...torn, it doesn't hurt to have this but I understand why we'd also remove it. 🤷

@discordjs/core do we keep it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a method for users. It should still be on the guild member and user class as a helper, though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naw, lets keep it at the manager level, otherwise why have we made client.channels.sendMessage

return (await this.createDM(user)).send(options);
}

/**
* Resolves a {@link UserResolvable} to a {@link User} object.
* @param {UserResolvable} user The UserResolvable to identify
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;
11 changes: 7 additions & 4 deletions packages/discord.js/src/structures/Message.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const {
ChannelType,
MessageType,
MessageFlags,
MessageReferenceType,
PermissionFlagsBits,
} = require('discord-api-types/v10');
const Attachment = require('./Attachment');
Expand Down Expand Up @@ -913,20 +914,22 @@ 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) {
data = options;
} else {
data = MessagePayload.create(this, options, {
reply: {
messageReference: this,
messageReference: {
messageId: this.id,
channelId: this.channelId,
guildId: this.guildId,
type: MessageReferenceType.Default,
failIfNotExists: options?.failIfNotExists ?? this.client.options.failIfNotExists,
},
});
}
return this.channel.send(data);
return this.client.channels.createMessage(this.channelId, data);
}

/**
Expand Down
17 changes: 10 additions & 7 deletions packages/discord.js/src/structures/MessagePayload.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,16 @@ class MessagePayload {
}

let message_reference;
if (typeof this.options.reply === 'object') {
const reference = this.options.reply.messageReference;
const message_id = this.isMessage ? (reference.id ?? reference) : this.target.messages.resolveId(reference);
if (message_id) {
if (this.options.messageReference) {
const reference = this.options.messageReference;

if (reference.messageId) {
message_reference = {
message_id,
fail_if_not_exists: this.options.reply.failIfNotExists ?? this.target.client.options.failIfNotExists,
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 @@ -292,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;
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 @@ -113,7 +112,7 @@ class TextBasedChannel {
/**
* The options for sending a message.
* @typedef {BaseMessageCreateOptions} MessageCreateOptions
* @property {ReplyOptions} [reply] The options for replying to a message
* @property {MessageReference|MessageResolvable} [messageReference] The options for a reference to a message
*/

/**
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
Loading