diff --git a/src/client/WebhookClient.js b/src/client/WebhookClient.js index 884556eec247..da441b18d1d2 100644 --- a/src/client/WebhookClient.js +++ b/src/client/WebhookClient.js @@ -1,6 +1,7 @@ 'use strict'; const BaseClient = require('./BaseClient'); +const { Error } = require('../errors'); const Webhook = require('../structures/Webhook'); /** @@ -10,17 +11,37 @@ const Webhook = require('../structures/Webhook'); */ class WebhookClient extends BaseClient { /** - * @param {Snowflake} id The webhook's id - * @param {string} token Token of the webhook + * The data for the webhook client containing either an id and token or just a URL + * @typedef {Object} WebhookClientData + * @property {Snowflake} [id] The id of the webhook + * @property {string} [token] The token of the webhook + * @property {string} [url] The full url for the webhook client + */ + + /** + * @param {WebhookClientData} data The data of the webhook * @param {ClientOptions} [options] Options for the client * @example * // Create a new webhook and send a message - * const hook = new Discord.WebhookClient('1234', 'abcdef'); + * const hook = new Discord.WebhookClient({ id: '1234', token: 'abcdef' }); * hook.send('This will send a message').catch(console.error); */ - constructor(id, token, options) { + constructor(data, options) { super(options); Object.defineProperty(this, 'client', { value: this }); + let { id, token } = data; + + if ('url' in data) { + const url = data.url.match( + // eslint-disable-next-line no-useless-escape + /^https?:\/\/(?:canary|ptb)?\.?discord\.com\/api\/webhooks(?:\/v[0-9]\d*)?\/([^\/]+)\/([^\/]+)/i, + ); + + if (!url || url.length <= 1) throw new Error('WEBHOOK_URL_INVALID'); + + [, id, token] = url; + } + this.id = id; Object.defineProperty(this, 'token', { value: token, writable: true, configurable: true }); } diff --git a/src/errors/Messages.js b/src/errors/Messages.js index 91ad16c263ab..ed5b98e6814e 100644 --- a/src/errors/Messages.js +++ b/src/errors/Messages.js @@ -101,6 +101,7 @@ const Messages = { WEBHOOK_MESSAGE: 'The message was not sent by a webhook.', WEBHOOK_TOKEN_UNAVAILABLE: 'This action requires a webhook token, but none is available.', + WEBHOOK_URL_INVALID: 'The provided webhook URL is not valid.', MESSAGE_REFERENCE_MISSING: 'The message does not reference another message', EMOJI_TYPE: 'Emoji must be a string or GuildEmoji/ReactionEmoji', diff --git a/test/webhooktest.js b/test/webhooktest.js index 74950ba5f19b..e9cc5967650c 100644 --- a/test/webhooktest.js +++ b/test/webhooktest.js @@ -97,7 +97,7 @@ client.on('messageCreate', async message => { if (message.author.id !== owner) return; const match = message.content.match(/^do (.+)$/); const hooks = [ - { type: 'WebhookClient', hook: new WebhookClient(webhookChannel, webhookToken) }, + { type: 'WebhookClient', hook: new WebhookClient({ id: webhookChannel, token: webhookToken }) }, { type: 'TextChannel#fetchWebhooks', hook: await message.channel.fetchWebhooks().then(x => x.first()) }, { type: 'Guild#fetchWebhooks', hook: await message.guild.fetchWebhooks().then(x => x.first()) }, ]; diff --git a/typings/index.d.ts b/typings/index.d.ts index 281fa3a2e91c..d262f5f69c06 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1894,7 +1894,7 @@ export class Webhook extends WebhookMixin() { } export class WebhookClient extends WebhookMixin(BaseClient) { - public constructor(id: Snowflake, token: string, options?: WebhookClientOptions); + public constructor(data: WebhookClientData, options?: WebhookClientOptions); public client: this; public options: WebhookClientOptions; public token: string; @@ -4340,6 +4340,17 @@ export type VerificationLevel = keyof typeof VerificationLevels; export type VoiceBasedChannelTypes = 'GUILD_VOICE' | 'GUILD_STAGE_VOICE'; +export type WebhookClientData = WebhookClientDataIdWithToken | WebhookClientDataURL; + +export interface WebhookClientDataIdWithToken { + id: Snowflake; + token: string; +} + +export interface WebhookClientDataURL { + url: string; +} + export type WebhookClientOptions = Pick< ClientOptions, 'allowedMentions' | 'restTimeOffset' | 'restRequestTimeout' | 'retryLimit' | 'http'