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: Allow webhooks to fetch, edit and delete messages in threads #6695

Merged
merged 10 commits into from Oct 2, 2021
53 changes: 46 additions & 7 deletions src/structures/Webhook.js
Expand Up @@ -105,6 +105,8 @@ class Webhook {
* @property {MessageAttachment[]} [attachments] Attachments to send with the message
* @property {MessageActionRow[]|MessageActionRowOptions[]} [components]
* Action rows containing interactive components for the message (buttons, select menus)
* @property {Snowflake} [threadId] The id of the thread this message belongs to
* <info>For interaction webhooks, this property is ignored</info>
*/

/**
Expand Down Expand Up @@ -235,18 +237,44 @@ class Webhook {
return this;
}

/**
* Options that can be passed into fetchMessage.
* @typedef {options} WebhookFetchMessageOptions
* @property {boolean} [cache=true] Whether to cache the message.
* @property {Snowflake} [threadId] The id of the thread this message belongs to.
* <info>For interaction webhooks, this property is ignored</info>
*/

/**
* Gets a message that was sent by this webhook.
* @param {Snowflake|'@original'} message The id of the message to fetch
* @param {boolean} [cache=true] Whether to cache the message
* @param {boolean|WebhookFetchMessageOptions} [cacheOrOptions=true|cacheOrOptions={}] If
* {@link ClientOptions#allowWebhookThreadFetching} is `true`, this will be {@link WebhookFetchMessageOptions}
* to enable fetching messages in threads. Otherwise, this will be the `cache` parameter.
* @returns {Promise<Message|APIMessage>} Returns the raw message data if the webhook was instantiated as a
* {@link WebhookClient} or if the channel is uncached, otherwise a {@link Message} will be returned
*/
async fetchMessage(message, cache = true) {
async fetchMessage(message, cacheOrOptions) {
Jiralite marked this conversation as resolved.
Show resolved Hide resolved
const options = { cache: true, threadId: undefined };

if (this.client.options.allowWebhookThreadFetching) {
options.cache = cacheOrOptions.cache ?? true;
options.threadId = cacheOrOptions.threadId;
} else {
options.cache = cacheOrOptions ?? true;
}

if (!this.token) throw new Error('WEBHOOK_TOKEN_UNAVAILABLE');

const data = await this.client.api.webhooks(this.id, this.token).messages(message).get();
return this.client.channels?.cache.get(data.channel_id)?.messages._add(data, cache) ?? data;
const data = await this.client.api
.webhooks(this.id, this.token)
.messages(message)
.get({
query: {
thread_id: options.threadId,
},
});
return this.client.channels?.cache.get(data.channel_id)?.messages._add(data, options.cache) ?? data;
}

/**
Expand All @@ -269,7 +297,13 @@ class Webhook {
const d = await this.client.api
.webhooks(this.id, this.token)
.messages(typeof message === 'string' ? message : message.id)
.patch({ data, files });
.patch({
data,
files,
query: {
thread_id: messagePayload.options.threadId,
},
});

const messageManager = this.client.channels?.cache.get(d.channel_id)?.messages;
if (!messageManager) return d;
Expand All @@ -294,15 +328,20 @@ class Webhook {
/**
* Delete a message that was sent by this webhook.
* @param {MessageResolvable|'@original'} message The message to delete
* @param {Snowflake} [threadId] The id of the thread this message belongs to
* @returns {Promise<void>}
*/
async deleteMessage(message) {
async deleteMessage(message, threadId) {
if (!this.token) throw new Error('WEBHOOK_TOKEN_UNAVAILABLE');

await this.client.api
.webhooks(this.id, this.token)
.messages(typeof message === 'string' ? message : message.id)
.delete();
.delete({
query: {
thread_id: threadId,
},
});
}

/**
Expand Down
3 changes: 3 additions & 0 deletions src/util/Options.js
Expand Up @@ -71,6 +71,8 @@
* @property {IntentsResolvable} intents Intents to enable for this connection
* @property {WebsocketOptions} [ws] Options for the WebSocket
* @property {HTTPOptions} [http] HTTP options
* @property {boolean} [allowWebhookThreadFetching=false] This changes {@link Webhook#fetchMessage}'s second parameter
* to accept {@link WebhookFetchMessageOptions} which allows the fetching of messages in threads.
*/

/**
Expand Down Expand Up @@ -142,6 +144,7 @@ class Options extends null {
invite: 'https://discord.gg',
template: 'https://discord.new',
},
allowWebhookThreadFetching: false,
};
}

Expand Down
19 changes: 14 additions & 5 deletions typings/index.d.ts
Expand Up @@ -2119,7 +2119,7 @@ export class WebhookClient extends WebhookMixin(BaseClient) {
message: MessageResolvable,
options: string | MessagePayload | WebhookEditMessageOptions,
): Promise<APIMessage>;
public fetchMessage(message: Snowflake, cache?: boolean): Promise<APIMessage>;
public fetchMessage(message: Snowflake, cacheOrOptions?: boolean | WebhookFetchMessageOptions): Promise<APIMessage>;
public send(options: string | MessagePayload | WebhookMessageOptions): Promise<APIMessage>;
}

Expand Down Expand Up @@ -2825,12 +2825,15 @@ export function WebhookMixin<T>(Base?: Constructable<T>): Constructable<T & Webh
export interface PartialWebhookFields {
id: Snowflake;
readonly url: string;
deleteMessage(message: MessageResolvable | APIMessage | '@original'): Promise<void>;
deleteMessage(message: MessageResolvable | APIMessage | '@original', threadId?: Snowflake): Promise<void>;
editMessage(
message: MessageResolvable | '@original',
options: string | MessagePayload | WebhookEditMessageOptions,
): Promise<Message | APIMessage>;
fetchMessage(message: Snowflake | '@original', cache?: boolean): Promise<Message | APIMessage>;
fetchMessage(
message: Snowflake | '@original',
cacheOrOptions?: boolean | WebhookFetchMessageOptions,
): Promise<Message | APIMessage>;
send(options: string | MessagePayload | WebhookMessageOptions): Promise<Message | APIMessage>;
}

Expand Down Expand Up @@ -3378,6 +3381,7 @@ export interface ClientOptions {
ws?: WebSocketOptions;
http?: HTTPOptions;
rejectOnRateLimit?: string[] | ((data: RateLimitData) => boolean | Promise<boolean>);
allowWebhookThreadFetching?: boolean;
}

export type ClientPresenceStatus = 'online' | 'idle' | 'dnd';
Expand Down Expand Up @@ -4779,7 +4783,7 @@ export interface WebhookClientDataURL {

export type WebhookClientOptions = Pick<
ClientOptions,
'allowedMentions' | 'restTimeOffset' | 'restRequestTimeout' | 'retryLimit' | 'http'
'allowedMentions' | 'restTimeOffset' | 'restRequestTimeout' | 'retryLimit' | 'http' | 'allowWebhookThreadFetching'
>;

export interface WebhookEditData {
Expand All @@ -4790,9 +4794,14 @@ export interface WebhookEditData {

export type WebhookEditMessageOptions = Pick<
WebhookMessageOptions,
'content' | 'embeds' | 'files' | 'allowedMentions' | 'components' | 'attachments'
'content' | 'embeds' | 'files' | 'allowedMentions' | 'components' | 'attachments' | 'threadId'
>;

export interface WebhookFetchMessageOptions {
cache?: boolean;
threadId?: Snowflake;
}

export interface WebhookMessageOptions extends Omit<MessageOptions, 'reply'> {
username?: string;
avatarURL?: string;
Expand Down