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: backport text-in-voice support to v13 #7999

Merged
merged 3 commits into from Jun 5, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/client/actions/WebhooksUpdate.js
Expand Up @@ -10,7 +10,7 @@ class WebhooksUpdate extends Action {
/**
* Emitted whenever a channel has its webhooks changed.
* @event Client#webhookUpdate
* @param {TextChannel|NewsChannel} channel The channel that had a webhook update
* @param {TextChannel|NewsChannel|VoiceChannel} channel The channel that had a webhook update
*/
if (channel) client.emit(Events.WEBHOOKS_UPDATE, channel);
}
Expand Down
40 changes: 2 additions & 38 deletions src/structures/BaseGuildTextChannel.js
Expand Up @@ -109,44 +109,6 @@ class BaseGuildTextChannel extends GuildChannel {
return this.edit({ type }, reason);
}

/**
* Fetches all webhooks for the channel.
* @returns {Promise<Collection<Snowflake, Webhook>>}
* @example
* // Fetch webhooks
* channel.fetchWebhooks()
* .then(hooks => console.log(`This channel has ${hooks.size} hooks`))
* .catch(console.error);
*/
fetchWebhooks() {
return this.guild.channels.fetchWebhooks(this.id);
}

/**
* Options used to create a {@link Webhook} in a {@link TextChannel} or a {@link NewsChannel}.
* @typedef {Object} ChannelWebhookCreateOptions
* @property {?(BufferResolvable|Base64Resolvable)} [avatar] Avatar for the webhook
* @property {string} [reason] Reason for creating the webhook
*/

/**
* Creates a webhook for the channel.
* @param {string} name The name of the webhook
* @param {ChannelWebhookCreateOptions} [options] Options for creating the webhook
* @returns {Promise<Webhook>} Returns the created Webhook
* @example
* // Create a webhook for the current channel
* channel.createWebhook('Snek', {
* avatar: 'https://i.imgur.com/mI8XcpG.jpg',
* reason: 'Needed a cool new Webhook'
* })
* .then(console.log)
* .catch(console.error)
*/
createWebhook(name, options = {}) {
return this.guild.channels.createWebhook(this.id, name, options);
}

/**
* Sets a new topic for the guild channel.
* @param {?string} topic The new topic for the guild channel
Expand Down Expand Up @@ -221,6 +183,8 @@ class BaseGuildTextChannel extends GuildChannel {
createMessageComponentCollector() {}
awaitMessageComponent() {}
bulkDelete() {}
fetchWebhooks() {}
createWebhook() {}
}

TextBasedChannel.applyToClass(BaseGuildTextChannel, true);
Expand Down
2 changes: 1 addition & 1 deletion src/structures/DMChannel.js
Expand Up @@ -96,6 +96,6 @@ class DMChannel extends Channel {
// Doesn't work on DM channels; bulkDelete() {}
}

TextBasedChannel.applyToClass(DMChannel, true, ['bulkDelete']);
TextBasedChannel.applyToClass(DMChannel, true, ['bulkDelete', 'fetchWebhooks', 'createWebhook']);

module.exports = DMChannel;
2 changes: 1 addition & 1 deletion src/structures/Message.js
Expand Up @@ -383,7 +383,7 @@ class Message extends Base {

/**
* The channel that the message was sent in
* @type {TextChannel|DMChannel|NewsChannel|ThreadChannel}
* @type {TextBasedChannel}
* @readonly
*/
get channel() {
Expand Down
2 changes: 1 addition & 1 deletion src/structures/MessagePayload.js
Expand Up @@ -277,7 +277,7 @@ module.exports = MessagePayload;

/**
* A target for a message.
* @typedef {TextChannel|DMChannel|User|GuildMember|Webhook|WebhookClient|Interaction|InteractionWebhook|
* @typedef {TextBasedChannel|User|GuildMember|Webhook|WebhookClient|Interaction|InteractionWebhook|
* Message|MessageManager} MessageTarget
*/

Expand Down
41 changes: 41 additions & 0 deletions src/structures/VoiceChannel.js
Expand Up @@ -2,6 +2,8 @@

const process = require('node:process');
const BaseGuildVoiceChannel = require('./BaseGuildVoiceChannel');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const MessageManager = require('../managers/MessageManager');
const { VideoQualityModes } = require('../util/Constants');
const Permissions = require('../util/Permissions');

Expand All @@ -12,6 +14,18 @@ let deprecationEmittedForEditable = false;
* @extends {BaseGuildVoiceChannel}
suneettipirneni marked this conversation as resolved.
Show resolved Hide resolved
*/
class VoiceChannel extends BaseGuildVoiceChannel {
constructor(guild, data, client) {
super(guild, data, client, false);

/**
* A manager of the messages sent to this channel
* @type {MessageManager}
*/
this.messages = new MessageManager(this);

this._patch(data);
}

_patch(data) {
super._patch(data);

Expand All @@ -24,6 +38,18 @@ class VoiceChannel extends BaseGuildVoiceChannel {
} else {
this.videoQualityMode ??= null;
}

if ('last_message_id' in data) {
/**
* The last message id sent in the channel, if one was sent
* @type {?Snowflake}
*/
this.lastMessageId = data.last_message_id;
}

if ('messages' in data) {
for (const message of data.messages) this.messages._add(message);
}
}

/**
Expand Down Expand Up @@ -112,6 +138,19 @@ class VoiceChannel extends BaseGuildVoiceChannel {
return this.edit({ videoQualityMode }, reason);
}

// These are here only for documentation purposes - they are implemented by TextBasedChannel
/* eslint-disable no-empty-function */
get lastMessage() {}
send() {}
sendTyping() {}
createMessageCollector() {}
awaitMessages() {}
createMessageComponentCollector() {}
awaitMessageComponent() {}
bulkDelete() {}
fetchWebhooks() {}
createWebhook() {}

/**
* Sets the RTC region of the channel.
* @name VoiceChannel#setRTCRegion
Expand All @@ -127,4 +166,6 @@ class VoiceChannel extends BaseGuildVoiceChannel {
*/
}

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

module.exports = VoiceChannel;
40 changes: 40 additions & 0 deletions src/structures/interfaces/TextBasedChannel.js
Expand Up @@ -330,6 +330,44 @@ class TextBasedChannel {
throw new TypeError('MESSAGE_BULK_DELETE_TYPE');
}

/**
* Fetches all webhooks for the channel.
* @returns {Promise<Collection<Snowflake, Webhook>>}
* @example
* // Fetch webhooks
* channel.fetchWebhooks()
* .then(hooks => console.log(`This channel has ${hooks.size} hooks`))
* .catch(console.error);
*/
fetchWebhooks() {
return this.guild.channels.fetchWebhooks(this.id);
}

/**
* Options used to create a {@link Webhook} in a guild text-based channel.
* @typedef {Object} ChannelWebhookCreateOptions
* @property {?(BufferResolvable|Base64Resolvable)} [avatar] Avatar for the webhook
* @property {string} [reason] Reason for creating the webhook
*/

/**
* Creates a webhook for the channel.
* @param {string} name The name of the webhook
* @param {ChannelWebhookCreateOptions} [options] Options for creating the webhook
* @returns {Promise<Webhook>} Returns the created Webhook
* @example
* // Create a webhook for the current channel
* channel.createWebhook('Snek', {
* avatar: 'https://i.imgur.com/mI8XcpG.jpg',
* reason: 'Needed a cool new Webhook'
* })
* .then(console.log)
* .catch(console.error)
*/
createWebhook(name, options = {}) {
return this.guild.channels.createWebhook(this.id, name, options);
}

static applyToClass(structure, full = false, ignore = []) {
const props = ['send'];
if (full) {
Expand All @@ -342,6 +380,8 @@ class TextBasedChannel {
'awaitMessages',
'createMessageComponentCollector',
'awaitMessageComponent',
'fetchWebhooks',
'createWebhook',
);
}
for (const prop of props) {
Expand Down
5 changes: 4 additions & 1 deletion src/util/Constants.js
Expand Up @@ -557,7 +557,8 @@ exports.ChannelTypes = createEnum([
* * TextChannel
* * NewsChannel
* * ThreadChannel
* @typedef {DMChannel|TextChannel|NewsChannel|ThreadChannel} TextBasedChannels
* * VoiceChannel
* @typedef {DMChannel|TextChannel|NewsChannel|ThreadChannel|VoiceChannel} TextBasedChannels
*/

/**
Expand All @@ -575,6 +576,7 @@ exports.ChannelTypes = createEnum([
* * GUILD_NEWS_THREAD
* * GUILD_PUBLIC_THREAD
* * GUILD_PRIVATE_THREAD
* * GUILD_VOICE
* @typedef {string} TextBasedChannelTypes
*/
exports.TextBasedChannelTypes = [
Expand All @@ -584,6 +586,7 @@ exports.TextBasedChannelTypes = [
'GUILD_NEWS_THREAD',
'GUILD_PUBLIC_THREAD',
'GUILD_PRIVATE_THREAD',
'GUILD_VOICE',
];

/**
Expand Down
18 changes: 8 additions & 10 deletions typings/index.d.ts
Expand Up @@ -433,12 +433,10 @@ export class BaseGuildEmoji extends Emoji {
export class BaseGuildTextChannel extends TextBasedChannelMixin(GuildChannel) {
protected constructor(guild: Guild, data?: RawGuildChannelData, client?: Client, immediatePatch?: boolean);
public defaultAutoArchiveDuration?: ThreadAutoArchiveDuration;
public messages: MessageManager;
public nsfw: boolean;
public threads: ThreadManager<AllowedThreadTypeForTextChannel | AllowedThreadTypeForNewsChannel>;
public topic: string | null;
public createInvite(options?: CreateInviteOptions): Promise<Invite>;
public createWebhook(name: string, options?: ChannelWebhookCreateOptions): Promise<Webhook>;
public fetchInvites(cache?: boolean): Promise<Collection<string, Invite>>;
public setDefaultAutoArchiveDuration(
defaultAutoArchiveDuration: ThreadAutoArchiveDuration | 'MAX',
Expand All @@ -448,11 +446,10 @@ export class BaseGuildTextChannel extends TextBasedChannelMixin(GuildChannel) {
public setTopic(topic: string | null, reason?: string): Promise<this>;
public setType(type: Pick<typeof ChannelTypes, 'GUILD_TEXT'>, reason?: string): Promise<TextChannel>;
public setType(type: Pick<typeof ChannelTypes, 'GUILD_NEWS'>, reason?: string): Promise<NewsChannel>;
public fetchWebhooks(): Promise<Collection<Snowflake, Webhook>>;
}

export class BaseGuildVoiceChannel extends GuildChannel {
protected constructor(guild: Guild, data?: RawGuildChannelData);
public constructor(guild: Guild, data?: RawGuildChannelData);
public readonly members: Collection<Snowflake, GuildMember>;
public readonly full: boolean;
public readonly joinable: boolean;
Expand Down Expand Up @@ -893,9 +890,8 @@ export class DiscordAPIError extends Error {
public requestData: HTTPErrorData;
}

export class DMChannel extends TextBasedChannelMixin(Channel, ['bulkDelete']) {
export class DMChannel extends TextBasedChannelMixin(Channel, ['bulkDelete', 'fetchWebhooks', 'createWebhook']) {
private constructor(client: Client, data?: RawDMChannelData);
public messages: MessageManager;
public recipient: User;
public type: 'DM';
public fetch(force?: boolean): Promise<this>;
Expand Down Expand Up @@ -2455,7 +2451,7 @@ export class TextInputComponent extends BaseMessageComponent {
public static resolveStyle(style: TextInputStyleResolvable): TextInputStyle;
}

export class ThreadChannel extends TextBasedChannelMixin(Channel) {
export class ThreadChannel extends TextBasedChannelMixin(Channel, ['fetchWebhooks', 'createWebhook']) {
private constructor(guild: Guild, data?: RawThreadChannelData, client?: Client, fromInteraction?: boolean);
public archived: boolean | null;
public readonly archivedAt: Date | null;
Expand All @@ -2477,7 +2473,6 @@ export class ThreadChannel extends TextBasedChannelMixin(Channel) {
public readonly sendable: boolean;
public memberCount: number | null;
public messageCount: number | null;
public messages: MessageManager;
public members: ThreadMemberManager;
public name: string;
public ownerId: Snowflake | null;
Expand Down Expand Up @@ -2655,7 +2650,7 @@ export class Formatters extends null {
public static userMention: typeof userMention;
}

export class VoiceChannel extends BaseGuildVoiceChannel {
export class VoiceChannel extends TextBasedChannelMixin(BaseGuildVoiceChannel, ['lastPinTimestamp', 'lastPinAt']) {
public videoQualityMode: VideoQualityMode | null;
/** @deprecated Use manageable instead */
public readonly editable: boolean;
Expand Down Expand Up @@ -3500,6 +3495,7 @@ export interface TextBasedChannelFields extends PartialTextBasedChannelFields {
readonly lastMessage: Message | null;
lastPinTimestamp: number | null;
readonly lastPinAt: Date | null;
messages: MessageManager;
awaitMessageComponent<T extends MessageComponentTypeResolvable = 'ACTION_ROW'>(
options?: AwaitMessageCollectorOptionsParams<T, true>,
): Promise<MappedInteractionTypes[T]>;
Expand All @@ -3512,6 +3508,8 @@ export interface TextBasedChannelFields extends PartialTextBasedChannelFields {
options?: MessageChannelCollectorOptionsParams<T, true>,
): InteractionCollector<MappedInteractionTypes[T]>;
createMessageCollector(options?: MessageCollectorOptions): MessageCollector;
createWebhook(name: string, options?: ChannelWebhookCreateOptions): Promise<Webhook>;
fetchWebhooks(): Promise<Collection<Snowflake, Webhook>>;
sendTyping(): Promise<void>;
}

Expand Down Expand Up @@ -4192,7 +4190,7 @@ export interface ClientEvents extends BaseClientEvents {
typingStart: [typing: Typing];
userUpdate: [oldUser: User | PartialUser, newUser: User];
voiceStateUpdate: [oldState: VoiceState, newState: VoiceState];
webhookUpdate: [channel: TextChannel | NewsChannel];
webhookUpdate: [channel: TextChannel | NewsChannel | VoiceChannel];
/** @deprecated Use interactionCreate instead */
interaction: [interaction: Interaction];
interactionCreate: [interaction: Interaction];
Expand Down
24 changes: 16 additions & 8 deletions typings/index.test-d.ts
Expand Up @@ -770,16 +770,18 @@ declare const guildMember: GuildMember;

// Test whether the structures implement send
expectType<TextBasedChannelFields['send']>(dmChannel.send);
expectType<ThreadChannel>(threadChannel);
expectType<NewsChannel>(newsChannel);
expectType<TextChannel>(textChannel);
expectType<TextBasedChannelFields['send']>(threadChannel.send);
expectType<TextBasedChannelFields['send']>(newsChannel.send);
expectType<TextBasedChannelFields['send']>(textChannel.send);
expectType<TextBasedChannelFields['send']>(voiceChannel.send);
expectAssignable<PartialTextBasedChannelFields>(user);
expectAssignable<PartialTextBasedChannelFields>(guildMember);

expectType<Message | null>(dmChannel.lastMessage);
expectType<Message | null>(threadChannel.lastMessage);
expectType<Message | null>(newsChannel.lastMessage);
expectType<Message | null>(textChannel.lastMessage);
expectType<Message | null>(voiceChannel.lastMessage);

expectDeprecated(storeChannel.clone());
expectDeprecated(categoryChannel.createChannel('Store', { type: 'GUILD_STORE' }));
Expand Down Expand Up @@ -1292,15 +1294,21 @@ declare const GuildBasedChannel: GuildBasedChannel;
declare const NonThreadGuildBasedChannel: NonThreadGuildBasedChannel;
declare const GuildTextBasedChannel: GuildTextBasedChannel;

expectType<DMChannel | PartialDMChannel | NewsChannel | TextChannel | ThreadChannel>(TextBasedChannel);
expectType<'DM' | 'GUILD_NEWS' | 'GUILD_TEXT' | 'GUILD_PUBLIC_THREAD' | 'GUILD_PRIVATE_THREAD' | 'GUILD_NEWS_THREAD'>(
TextBasedChannelTypes,
);
expectType<DMChannel | PartialDMChannel | NewsChannel | TextChannel | ThreadChannel | VoiceChannel>(TextBasedChannel);
expectType<
| 'DM'
| 'GUILD_NEWS'
| 'GUILD_TEXT'
| 'GUILD_PUBLIC_THREAD'
| 'GUILD_PRIVATE_THREAD'
| 'GUILD_NEWS_THREAD'
| 'GUILD_VOICE'
>(TextBasedChannelTypes);
expectType<StageChannel | VoiceChannel>(VoiceBasedChannel);
expectType<CategoryChannel | NewsChannel | StageChannel | StoreChannel | TextChannel | ThreadChannel | VoiceChannel>(
GuildBasedChannel,
);
expectType<CategoryChannel | NewsChannel | StageChannel | StoreChannel | TextChannel | VoiceChannel>(
NonThreadGuildBasedChannel,
);
expectType<NewsChannel | TextChannel | ThreadChannel>(GuildTextBasedChannel);
expectType<NewsChannel | TextChannel | ThreadChannel | VoiceChannel>(GuildTextBasedChannel);