feat!: create forwards and add ChannelManager#createMessage() (#10559)

BREAKING CHANGE: `MessageCreateOptions` no longer accepts `forward` or `reply`. Use `messageReference` instead.

---------

Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
This commit is contained in:
TÆMBØ
2025-02-20 13:53:52 -08:00
committed by GitHub
parent 19d48f6d6c
commit 0e7bdb0728
15 changed files with 197 additions and 129 deletions

View File

@@ -1,13 +1,17 @@
'use strict'; 'use strict';
const process = require('node:process'); const process = require('node:process');
const { lazy } = require('@discordjs/util');
const { Routes } = require('discord-api-types/v10'); const { Routes } = require('discord-api-types/v10');
const { CachedManager } = require('./CachedManager.js'); const { CachedManager } = require('./CachedManager.js');
const { BaseChannel } = require('../structures/BaseChannel.js'); const { BaseChannel } = require('../structures/BaseChannel.js');
const { MessagePayload } = require('../structures/MessagePayload.js');
const { createChannel } = require('../util/Channels.js'); const { createChannel } = require('../util/Channels.js');
const { ThreadChannelTypes } = require('../util/Constants.js'); const { ThreadChannelTypes } = require('../util/Constants.js');
const { Events } = require('../util/Events.js'); const { Events } = require('../util/Events.js');
const getMessage = lazy(() => require('../structures/Message.js').Message);
let cacheWarningEmitted = false; let cacheWarningEmitted = false;
/** /**
@@ -123,6 +127,52 @@ class ChannelManager extends CachedManager {
const data = await this.client.rest.get(Routes.channel(id)); const data = await this.client.rest.get(Routes.channel(id));
return this._add(data, null, { cache, allowUnknownGuild }); 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://github.com/discordjs.png']
* })
* .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);
}
} }
exports.ChannelManager = ChannelManager; exports.ChannelManager = ChannelManager;

View File

@@ -191,6 +191,6 @@ class BaseGuildTextChannel extends GuildChannel {
setNSFW() {} setNSFW() {}
} }
TextBasedChannel.applyToClass(BaseGuildTextChannel, true); TextBasedChannel.applyToClass(BaseGuildTextChannel);
exports.BaseGuildTextChannel = BaseGuildTextChannel; exports.BaseGuildTextChannel = BaseGuildTextChannel;

View File

@@ -229,6 +229,6 @@ class BaseGuildVoiceChannel extends GuildChannel {
setNSFW() {} setNSFW() {}
} }
TextBasedChannel.applyToClass(BaseGuildVoiceChannel, true, ['lastPinAt']); TextBasedChannel.applyToClass(BaseGuildVoiceChannel, ['lastPinAt']);
exports.BaseGuildVoiceChannel = BaseGuildVoiceChannel; exports.BaseGuildVoiceChannel = BaseGuildVoiceChannel;

View File

@@ -118,7 +118,7 @@ class DMChannel extends BaseChannel {
// Doesn't work on DM channels; setNSFW() {} // Doesn't work on DM channels; setNSFW() {}
} }
TextBasedChannel.applyToClass(DMChannel, true, [ TextBasedChannel.applyToClass(DMChannel, [
'bulkDelete', 'bulkDelete',
'fetchWebhooks', 'fetchWebhooks',
'createWebhook', 'createWebhook',

View File

@@ -3,7 +3,6 @@
const { PermissionFlagsBits } = require('discord-api-types/v10'); const { PermissionFlagsBits } = require('discord-api-types/v10');
const { Base } = require('./Base.js'); const { Base } = require('./Base.js');
const { VoiceState } = require('./VoiceState.js'); const { VoiceState } = require('./VoiceState.js');
const { TextBasedChannel } = require('./interfaces/TextBasedChannel.js');
const { DiscordjsError, ErrorCodes } = require('../errors/index.js'); const { DiscordjsError, ErrorCodes } = require('../errors/index.js');
const { GuildMemberRoleManager } = require('../managers/GuildMemberRoleManager.js'); const { GuildMemberRoleManager } = require('../managers/GuildMemberRoleManager.js');
const { GuildMemberFlagsBitField } = require('../util/GuildMemberFlagsBitField.js'); const { GuildMemberFlagsBitField } = require('../util/GuildMemberFlagsBitField.js');
@@ -11,7 +10,6 @@ const { PermissionsBitField } = require('../util/PermissionsBitField.js');
/** /**
* Represents a member of a guild on Discord. * Represents a member of a guild on Discord.
* @implements {TextBasedChannel}
* @extends {Base} * @extends {Base}
*/ */
class GuildMember extends Base { class GuildMember extends Base {
@@ -476,6 +474,22 @@ class GuildMember extends Base {
return this.guild.members.fetch({ user: this.id, cache: true, force }); 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 * 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 * comparison it is advisable to just compare `member.id === member2.id` as it is significantly faster
@@ -527,20 +541,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; exports.GuildMember = GuildMember;

View File

@@ -8,6 +8,7 @@ const {
ChannelType, ChannelType,
MessageType, MessageType,
MessageFlags, MessageFlags,
MessageReferenceType,
PermissionFlagsBits, PermissionFlagsBits,
} = require('discord-api-types/v10'); } = require('discord-api-types/v10');
const { Attachment } = require('./Attachment.js'); const { Attachment } = require('./Attachment.js');
@@ -680,7 +681,11 @@ class Message extends Base {
* @readonly * @readonly
*/ */
get editable() { 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 // 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. // the thread is archived or the thread is locked and the bot does not have permission to manage threads.
@@ -915,21 +920,39 @@ class Message extends Base {
* .then(() => console.log(`Replied to message "${message.content}"`)) * .then(() => console.log(`Replied to message "${message.content}"`))
* .catch(console.error); * .catch(console.error);
*/ */
async reply(options) { reply(options) {
if (!this.channel) throw new DiscordjsError(ErrorCodes.ChannelNotCached);
let data; let data;
if (options instanceof MessagePayload) { if (options instanceof MessagePayload) {
data = options; data = options;
} else { } else {
data = MessagePayload.create(this, options, { data = MessagePayload.create(this, options, {
reply: { messageReference: {
messageReference: this, messageId: this.id,
channelId: this.channelId,
guildId: this.guildId ?? undefined,
type: MessageReferenceType.Default,
failIfNotExists: options?.failIfNotExists ?? this.client.options.failIfNotExists, failIfNotExists: options?.failIfNotExists ?? this.client.options.failIfNotExists,
}, },
}); });
} }
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 ?? undefined,
type: MessageReferenceType.Forward,
},
});
} }
/** /**

View File

@@ -151,7 +151,7 @@ class MessagePayload {
if ( if (
// eslint-disable-next-line eqeqeq // eslint-disable-next-line eqeqeq
this.options.flags != null || this.options.flags != null ||
(this.isMessage && this.options.reply === undefined) || (this.isMessage && this.options.messageReference === undefined) ||
this.isMessageManager this.isMessageManager
) { ) {
flags = new MessageFlagsBitField(this.options.flags).bitfield; flags = new MessageFlagsBitField(this.options.flags).bitfield;
@@ -168,13 +168,16 @@ class MessagePayload {
} }
let message_reference; let message_reference;
if (typeof this.options.reply === 'object') { if (this.options.messageReference) {
const reference = this.options.reply.messageReference; const reference = this.options.messageReference;
const message_id = this.isMessage ? (reference.id ?? reference) : this.target.messages.resolveId(reference);
if (message_id) { if (reference.messageId) {
message_reference = { message_reference = {
message_id, message_id: reference.messageId,
fail_if_not_exists: this.options.reply.failIfNotExists ?? this.target.client.options.failIfNotExists, channel_id: reference.channelId,
guild_id: reference.guildId,
type: reference.type,
fail_if_not_exists: reference.failIfNotExists ?? this.target.client.options.failIfNotExists,
}; };
} }
} }
@@ -292,7 +295,7 @@ exports.MessagePayload = MessagePayload;
/** /**
* A target for a message. * A target for a message.
* @typedef {TextBasedChannels|User|GuildMember|Webhook|WebhookClient|BaseInteraction|InteractionWebhook| * @typedef {TextBasedChannels|ChannelManager|Webhook|WebhookClient|BaseInteraction|InteractionWebhook|
* Message|MessageManager} MessageTarget * Message|MessageManager} MessageTarget
*/ */

View File

@@ -116,7 +116,7 @@ class PartialGroupDMChannel extends BaseChannel {
awaitMessageComponent() {} awaitMessageComponent() {}
} }
TextBasedChannel.applyToClass(PartialGroupDMChannel, true, [ TextBasedChannel.applyToClass(PartialGroupDMChannel, [
'bulkDelete', 'bulkDelete',
'send', 'send',
'sendTyping', 'sendTyping',

View File

@@ -598,6 +598,6 @@ class ThreadChannel extends BaseChannel {
// Doesn't work on Thread channels; setNSFW() {} // Doesn't work on Thread channels; setNSFW() {}
} }
TextBasedChannel.applyToClass(ThreadChannel, true, ['fetchWebhooks', 'setRateLimitPerUser', 'setNSFW']); TextBasedChannel.applyToClass(ThreadChannel, ['fetchWebhooks', 'setRateLimitPerUser', 'setNSFW']);
exports.ThreadChannel = ThreadChannel; exports.ThreadChannel = ThreadChannel;

View File

@@ -234,7 +234,7 @@ class ThreadOnlyChannel extends GuildChannel {
setRateLimitPerUser() {} setRateLimitPerUser() {}
} }
TextBasedChannel.applyToClass(ThreadOnlyChannel, true, [ TextBasedChannel.applyToClass(ThreadOnlyChannel, [
'send', 'send',
'lastMessage', 'lastMessage',
'lastPinAt', 'lastPinAt',

View File

@@ -4,12 +4,10 @@ const { userMention } = require('@discordjs/formatters');
const { calculateUserDefaultAvatarIndex } = require('@discordjs/rest'); const { calculateUserDefaultAvatarIndex } = require('@discordjs/rest');
const { DiscordSnowflake } = require('@sapphire/snowflake'); const { DiscordSnowflake } = require('@sapphire/snowflake');
const { Base } = require('./Base.js'); const { Base } = require('./Base.js');
const { TextBasedChannel } = require('./interfaces/TextBasedChannel.js');
const { UserFlagsBitField } = require('../util/UserFlagsBitField.js'); const { UserFlagsBitField } = require('../util/UserFlagsBitField.js');
/** /**
* Represents a user on Discord. * Represents a user on Discord.
* @implements {TextBasedChannel}
* @extends {Base} * @extends {Base}
*/ */
class User extends Base { class User extends Base {
@@ -277,6 +275,22 @@ class User extends Base {
return this.client.users.deleteDM(this.id); 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. * Checks if the user is equal to another.
* It compares id, username, discriminator, avatar, banner, accent color, and bot flags. * It compares id, username, discriminator, avatar, banner, accent color, and bot flags.
@@ -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);
exports.User = User; exports.User = User;

View File

@@ -172,7 +172,7 @@ class Webhook {
* @example * @example
* // Send a remote file * // Send a remote file
* webhook.send({ * webhook.send({
* files: ['https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048'] * files: ['https://github.com/discordjs.png']
* }) * })
* .then(console.log) * .then(console.log)
* .catch(console.error); * .catch(console.error);

View File

@@ -7,7 +7,6 @@ const { DiscordjsTypeError, DiscordjsError, ErrorCodes } = require('../../errors
const { MaxBulkDeletableMessageAge } = require('../../util/Constants.js'); const { MaxBulkDeletableMessageAge } = require('../../util/Constants.js');
const { InteractionCollector } = require('../InteractionCollector.js'); const { InteractionCollector } = require('../InteractionCollector.js');
const { MessageCollector } = require('../MessageCollector.js'); const { MessageCollector } = require('../MessageCollector.js');
const { MessagePayload } = require('../MessagePayload.js');
/** /**
* Interface for classes that have text-channel-like features. * Interface for classes that have text-channel-like features.
@@ -88,14 +87,6 @@ class TextBasedChannel {
* @property {PollData} [poll] The poll to send with the message * @property {PollData} [poll] The poll to send with the message
*/ */
/**
* Options for sending a message with a reply.
* @typedef {Object} ReplyOptions
* @property {MessageResolvable} messageReference The message to reply to (must be in the same channel and not system)
* @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)
*/
/** /**
* The options for sending a message. * The options for sending a message.
* @typedef {BaseMessageOptionsWithPoll} BaseMessageCreateOptions * @typedef {BaseMessageOptionsWithPoll} BaseMessageCreateOptions
@@ -110,10 +101,16 @@ class TextBasedChannel {
* <info>Only `MessageFlags.SuppressEmbeds` and `MessageFlags.SuppressNotifications` can be set.</info> * <info>Only `MessageFlags.SuppressEmbeds` and `MessageFlags.SuppressNotifications` can be set.</info>
*/ */
/**
* @typedef {MessageReference} MessageReferenceOptions
* @property {boolean} [failIfNotExists=this.client.options.failIfNotExists] Whether to error if the
* referenced message doesn't exist instead of sending as a normal (non-reply) message
*/
/** /**
* The options for sending a message. * The options for sending a message.
* @typedef {BaseMessageCreateOptions} MessageCreateOptions * @typedef {BaseMessageCreateOptions} MessageCreateOptions
* @property {ReplyOptions} [reply] The options for replying to a message * @property {MessageReferenceOptions} [messageReference] The options for a reference to a message
*/ */
/** /**
@@ -145,7 +142,7 @@ class TextBasedChannel {
* @example * @example
* // Send a remote file * // Send a remote file
* channel.send({ * channel.send({
* files: ['https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048'] * files: ['https://github.com/discordjs.png']
* }) * })
* .then(console.log) * .then(console.log)
* .catch(console.error); * .catch(console.error);
@@ -161,27 +158,8 @@ class TextBasedChannel {
* .then(console.log) * .then(console.log)
* .catch(console.error); * .catch(console.error);
*/ */
async send(options) { send(options) {
const { User } = require('../User.js'); return this.client.channels.createMessage(this, options);
const { GuildMember } = require('../GuildMember.js');
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);
} }
/** /**
@@ -386,24 +364,23 @@ class TextBasedChannel {
return this.edit({ nsfw, reason }); return this.edit({ nsfw, reason });
} }
static applyToClass(structure, full = false, ignore = []) { static applyToClass(structure, ignore = []) {
const props = ['send']; const props = [
if (full) { 'lastMessage',
props.push( 'lastPinAt',
'lastMessage', 'bulkDelete',
'lastPinAt', 'sendTyping',
'bulkDelete', 'createMessageCollector',
'sendTyping', 'awaitMessages',
'createMessageCollector', 'createMessageComponentCollector',
'awaitMessages', 'awaitMessageComponent',
'createMessageComponentCollector', 'fetchWebhooks',
'awaitMessageComponent', 'createWebhook',
'fetchWebhooks', 'setRateLimitPerUser',
'createWebhook', 'setNSFW',
'setRateLimitPerUser', 'send',
'setNSFW', ];
);
}
for (const prop of props) { for (const prop of props) {
if (ignore.includes(prop)) continue; if (ignore.includes(prop)) continue;
Object.defineProperty( Object.defineProperty(

View File

@@ -2175,6 +2175,9 @@ export class Message<InGuild extends boolean = boolean> extends Base {
public reply( public reply(
options: string | MessagePayload | MessageReplyOptions, options: string | MessagePayload | MessageReplyOptions,
): Promise<OmitPartialGroupDMChannel<Message<InGuild>>>; ): Promise<OmitPartialGroupDMChannel<Message<InGuild>>>;
public forward(
channel: Exclude<TextBasedChannelResolvable, PartialGroupDMChannel>,
): Promise<OmitPartialGroupDMChannel<Message>>;
public resolveComponent(customId: string): MessageActionRowComponent | null; public resolveComponent(customId: string): MessageActionRowComponent | null;
public startThread(options: StartThreadOptions): Promise<PublicThreadChannel<false>>; public startThread(options: StartThreadOptions): Promise<PublicThreadChannel<false>>;
public suppressEmbeds(suppress?: boolean): Promise<OmitPartialGroupDMChannel<Message<InGuild>>>; public suppressEmbeds(suppress?: boolean): Promise<OmitPartialGroupDMChannel<Message<InGuild>>>;
@@ -4026,6 +4029,10 @@ export class CategoryChannelChildManager extends DataManager<Snowflake, Category
export class ChannelManager extends CachedManager<Snowflake, Channel, ChannelResolvable> { export class ChannelManager extends CachedManager<Snowflake, Channel, ChannelResolvable> {
private constructor(client: Client<true>, iterable: Iterable<RawChannelData>); private constructor(client: Client<true>, iterable: Iterable<RawChannelData>);
public createMessage(
channel: Exclude<TextBasedChannelResolvable, PartialGroupDMChannel>,
options: string | MessagePayload | MessageCreateOptions,
): Promise<OmitPartialGroupDMChannel<Message>>;
public fetch(id: Snowflake, options?: FetchChannelOptions): Promise<Channel | null>; public fetch(id: Snowflake, options?: FetchChannelOptions): Promise<Channel | null>;
} }
@@ -6332,7 +6339,7 @@ export interface MessageCreateOptions extends BaseMessageOptionsWithPoll {
tts?: boolean; tts?: boolean;
nonce?: string | number; nonce?: string | number;
enforceNonce?: boolean; enforceNonce?: boolean;
reply?: ReplyOptions; messageReference?: MessageReferenceOptions;
stickers?: readonly StickerResolvable[]; stickers?: readonly StickerResolvable[];
flags?: flags?:
| BitFieldResolvable< | BitFieldResolvable<
@@ -6365,6 +6372,10 @@ export interface MessageReference {
type: MessageReferenceType; type: MessageReferenceType;
} }
export interface MessageReferenceOptions extends MessageReference {
failIfNotExists?: boolean;
}
export type MessageResolvable = Message | Snowflake; export type MessageResolvable = Message | Snowflake;
export interface BaseSelectMenuComponentData extends BaseComponentData { export interface BaseSelectMenuComponentData extends BaseComponentData {
@@ -6437,15 +6448,14 @@ export interface TextInputComponentData extends BaseComponentData {
} }
export type MessageTarget = export type MessageTarget =
| ChannelManager
| Interaction | Interaction
| InteractionWebhook | InteractionWebhook
| TextBasedChannel
| User
| GuildMember
| Webhook<WebhookType.Incoming>
| WebhookClient
| Message | Message
| MessageManager; | MessageManager
| TextBasedChannel
| Webhook<WebhookType.Incoming>
| WebhookClient;
export interface MultipleShardRespawnOptions { export interface MultipleShardRespawnOptions {
shardDelay?: number; shardDelay?: number;
@@ -6609,12 +6619,7 @@ export interface ReactionCollectorOptions extends CollectorOptions<[MessageReact
maxUsers?: number; maxUsers?: number;
} }
export interface ReplyOptions { export interface MessageReplyOptions extends Omit<MessageCreateOptions, 'messageReference'> {
messageReference: MessageResolvable;
failIfNotExists?: boolean;
}
export interface MessageReplyOptions extends Omit<MessageCreateOptions, 'reply'> {
failIfNotExists?: boolean; failIfNotExists?: boolean;
} }
@@ -6783,26 +6788,26 @@ export type Channel =
export type TextBasedChannel = Exclude<Extract<Channel, { type: TextChannelType }>, ForumChannel | MediaChannel>; export type TextBasedChannel = Exclude<Extract<Channel, { type: TextChannelType }>, ForumChannel | MediaChannel>;
export type SendableChannels = Extract<Channel, { send: (...args: any[]) => any }>;
export type TextBasedChannels = TextBasedChannel; export type TextBasedChannels = TextBasedChannel;
export type TextBasedChannelTypes = TextBasedChannel['type']; export type TextBasedChannelTypes = TextBasedChannel['type'];
export type GuildTextBasedChannelTypes = Exclude<TextBasedChannelTypes, ChannelType.DM | ChannelType.GroupDM>; export type GuildTextBasedChannelTypes = Exclude<TextBasedChannelTypes, ChannelType.DM | ChannelType.GroupDM>;
export type SendableChannelTypes = SendableChannels['type'];
export type VoiceBasedChannel = Extract<Channel, { bitrate: number }>; export type VoiceBasedChannel = Extract<Channel, { bitrate: number }>;
export type GuildBasedChannel = Extract<Channel, { guild: Guild }>; export type GuildBasedChannel = Extract<Channel, { guild: Guild }>;
export type SendableChannels = Extract<Channel, { send: (...args: any[]) => any }>;
export type CategoryChildChannel = Exclude<Extract<Channel, { parent: CategoryChannel | null }>, CategoryChannel>; export type CategoryChildChannel = Exclude<Extract<Channel, { parent: CategoryChannel | null }>, CategoryChannel>;
export type NonThreadGuildBasedChannel = Exclude<GuildBasedChannel, AnyThreadChannel>; export type NonThreadGuildBasedChannel = Exclude<GuildBasedChannel, AnyThreadChannel>;
export type GuildTextBasedChannel = Extract<GuildBasedChannel, TextBasedChannel>; export type GuildTextBasedChannel = Extract<GuildBasedChannel, TextBasedChannel>;
export type SendableChannelTypes = SendableChannels['type'];
export type TextChannelResolvable = Snowflake | TextChannel; export type TextChannelResolvable = Snowflake | TextChannel;
export type TextBasedChannelResolvable = Snowflake | TextBasedChannel; export type TextBasedChannelResolvable = Snowflake | TextBasedChannel;
@@ -6893,7 +6898,8 @@ export interface WebhookFetchMessageOptions {
threadId?: Snowflake; threadId?: Snowflake;
} }
export interface WebhookMessageCreateOptions extends Omit<MessageCreateOptions, 'nonce' | 'reply' | 'stickers'> { export interface WebhookMessageCreateOptions
extends Omit<MessageCreateOptions, 'nonce' | 'messageReference' | 'stickers'> {
username?: string; username?: string;
avatarURL?: string; avatarURL?: string;
threadId?: Snowflake; threadId?: Snowflake;

View File

@@ -432,12 +432,20 @@ client.on('messageCreate', async message => {
assertIsMessage(channel.send({})); assertIsMessage(channel.send({}));
assertIsMessage(channel.send({ embeds: [] })); assertIsMessage(channel.send({ embeds: [] }));
assertIsMessage(client.channels.createMessage(channel, 'string'));
assertIsMessage(client.channels.createMessage(channel, {}));
assertIsMessage(client.channels.createMessage(channel, { embeds: [] }));
const attachment = new AttachmentBuilder('file.png'); const attachment = new AttachmentBuilder('file.png');
const embed = new EmbedBuilder(); const embed = new EmbedBuilder();
assertIsMessage(channel.send({ files: [attachment] })); assertIsMessage(channel.send({ files: [attachment] }));
assertIsMessage(channel.send({ embeds: [embed] })); assertIsMessage(channel.send({ embeds: [embed] }));
assertIsMessage(channel.send({ embeds: [embed], files: [attachment] })); assertIsMessage(channel.send({ embeds: [embed], files: [attachment] }));
assertIsMessage(client.channels.createMessage(channel, { files: [attachment] }));
assertIsMessage(client.channels.createMessage(channel, { embeds: [embed] }));
assertIsMessage(client.channels.createMessage(channel, { embeds: [embed], files: [attachment] }));
if (message.inGuild()) { if (message.inGuild()) {
expectAssignable<Message<true>>(message); expectAssignable<Message<true>>(message);
const component = await message.awaitMessageComponent({ componentType: ComponentType.Button }); const component = await message.awaitMessageComponent({ componentType: ComponentType.Button });
@@ -467,8 +475,13 @@ client.on('messageCreate', async message => {
// @ts-expect-error // @ts-expect-error
channel.send(); channel.send();
// @ts-expect-error // @ts-expect-error
client.channels.createMessage();
// @ts-expect-error
channel.send({ another: 'property' }); channel.send({ another: 'property' });
// @ts-expect-error
client.channels.createMessage({ another: 'property' });
// @ts-expect-error
client.channels.createMessage('string');
// Check collector creations. // Check collector creations.
// Verify that buttons interactions are inferred. // Verify that buttons interactions are inferred.
@@ -647,7 +660,7 @@ client.on('messageCreate', async message => {
const embedData = { description: 'test', color: 0xff0000 }; const embedData = { description: 'test', color: 0xff0000 };
channel.send({ client.channels.createMessage(channel, {
components: [row, rawButtonsRow, buttonsRow, rawStringSelectMenuRow, stringSelectRow], components: [row, rawButtonsRow, buttonsRow, rawStringSelectMenuRow, stringSelectRow],
embeds: [embed, embedData], embeds: [embed, embedData],
}); });
@@ -1334,7 +1347,7 @@ client.on('guildCreate', async g => {
], ],
}); });
channel.send({ components: [row, row2] }); client.channels.createMessage(channel, { components: [row, row2] });
} }
channel.setName('foo').then(updatedChannel => { channel.setName('foo').then(updatedChannel => {
@@ -2704,7 +2717,7 @@ declare const sku: SKU;
}); });
} }
await textChannel.send({ await client.channels.createMessage('123', {
poll: { poll: {
question: { question: {
text: 'Question', text: 'Question',