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 Dec 5, 2024
1 parent afbbc7d commit 715fdbe
Show file tree
Hide file tree
Showing 17 changed files with 134 additions and 291 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
4 changes: 3 additions & 1 deletion packages/discord.js/src/managers/UserManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ class UserManager extends CachedManager {
* @returns {Promise<Message>}
*/
async send(user, options) {
return (await this.createDM(user)).send(options);
const dmChannel = await this.createDM(user);

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

/**
Expand Down
8 changes: 0 additions & 8 deletions packages/discord.js/src/structures/BaseChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,6 @@ class BaseChannel extends Base {
return 'availableTags' in this;
}

/**
* Indicates whether this channel is sendable.
* @returns {boolean}
*/
isSendable() {
return 'send' in this;
}

toJSON(...props) {
return super.toJSON({ createdTimestamp: true }, ...props);
}
Expand Down
3 changes: 1 addition & 2 deletions packages/discord.js/src/structures/BaseGuildTextChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,6 @@ class BaseGuildTextChannel extends GuildChannel {
/* eslint-disable no-empty-function */
get lastMessage() {}
get lastPinAt() {}
send() {}
sendTyping() {}
createMessageCollector() {}
awaitMessages() {}
Expand All @@ -191,6 +190,6 @@ class BaseGuildTextChannel extends GuildChannel {
setNSFW() {}
}

TextBasedChannel.applyToClass(BaseGuildTextChannel, true);
TextBasedChannel.applyToClass(BaseGuildTextChannel);

module.exports = BaseGuildTextChannel;
3 changes: 1 addition & 2 deletions packages/discord.js/src/structures/BaseGuildVoiceChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,6 @@ class BaseGuildVoiceChannel extends GuildChannel {
// These are here only for documentation purposes - they are implemented by TextBasedChannel
/* eslint-disable no-empty-function */
get lastMessage() {}
send() {}
sendTyping() {}
createMessageCollector() {}
awaitMessages() {}
Expand All @@ -229,6 +228,6 @@ class BaseGuildVoiceChannel extends GuildChannel {
setNSFW() {}
}

TextBasedChannel.applyToClass(BaseGuildVoiceChannel, true, ['lastPinAt']);
TextBasedChannel.applyToClass(BaseGuildVoiceChannel, ['lastPinAt']);

module.exports = BaseGuildVoiceChannel;
3 changes: 1 addition & 2 deletions packages/discord.js/src/structures/DMChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ class DMChannel extends BaseChannel {
/* eslint-disable no-empty-function */
get lastMessage() {}
get lastPinAt() {}
send() {}
sendTyping() {}
createMessageCollector() {}
awaitMessages() {}
Expand All @@ -118,7 +117,7 @@ class DMChannel extends BaseChannel {
// Doesn't work on DM channels; setNSFW() {}
}

TextBasedChannel.applyToClass(DMChannel, true, [
TextBasedChannel.applyToClass(DMChannel, [
'bulkDelete',
'fetchWebhooks',
'createWebhook',
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;
55 changes: 1 addition & 54 deletions packages/discord.js/src/structures/Message.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ const {
ChannelType,
MessageType,
MessageFlags,
MessageReferenceType,
PermissionFlagsBits,
} = require('discord-api-types/v10');
const Attachment = require('./Attachment');
Expand All @@ -17,11 +16,10 @@ const ClientApplication = require('./ClientApplication');
const Embed = require('./Embed');
const InteractionCollector = require('./InteractionCollector');
const Mentions = require('./MessageMentions');
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 @@ -795,20 +793,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 @@ -937,43 +921,6 @@ class Message extends Base {
return this;
}

/**
* Options provided when sending a message as an inline reply.
* @typedef {BaseMessageCreateOptions} MessageReplyOptions
* @property {boolean} [failIfNotExists=this.client.options.failIfNotExists] Whether to error if the referenced
* message does not exist (creates a standard message in this case when false)
*/

/**
* Send an inline reply to this message.
* @param {string|MessagePayload|MessageReplyOptions} options The options to provide
* @returns {Promise<Message>}
* @example
* // Reply to a message
* message.reply('This is a reply!')
* .then(() => console.log(`Replied to message "${message.content}"`))
* .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, {
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);
}

/**
* Options for starting a thread on a message.
* @typedef {Object} StartThreadOptions
Expand Down
13 changes: 5 additions & 8 deletions packages/discord.js/src/structures/MessagePayload.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,15 +173,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 @@ -301,7 +298,7 @@ module.exports = MessagePayload;

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

Expand Down
3 changes: 1 addition & 2 deletions packages/discord.js/src/structures/ThreadChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,6 @@ class ThreadChannel extends BaseChannel {
/* eslint-disable no-empty-function */
get lastMessage() {}
get lastPinAt() {}
send() {}
sendTyping() {}
createMessageCollector() {}
awaitMessages() {}
Expand All @@ -607,6 +606,6 @@ class ThreadChannel extends BaseChannel {
// Doesn't work on Thread channels; setNSFW() {}
}

TextBasedChannel.applyToClass(ThreadChannel, true, ['fetchWebhooks', 'setRateLimitPerUser', 'setNSFW']);
TextBasedChannel.applyToClass(ThreadChannel, ['fetchWebhooks', 'setRateLimitPerUser', 'setNSFW']);

module.exports = ThreadChannel;
3 changes: 1 addition & 2 deletions packages/discord.js/src/structures/ThreadOnlyChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,7 @@ class ThreadOnlyChannel extends GuildChannel {
setRateLimitPerUser() {}
}

TextBasedChannel.applyToClass(ThreadOnlyChannel, true, [
'send',
TextBasedChannel.applyToClass(ThreadOnlyChannel, [
'lastMessage',
'lastPinAt',
'bulkDelete',
Expand Down
Loading

0 comments on commit 715fdbe

Please sign in to comment.