mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
adds new WebhookClient and allows you to fetch channel webhooks and such without being "over the top" (#768)
* start blocking out client * proto webhookclient * wee working webhooks * it's all working * run docs * fix jsdoc issues * add example for webhookClient * add example in the examples place * fix docs
This commit is contained in:
12
docs/custom/examples/webhook.js
Normal file
12
docs/custom/examples/webhook.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
Send a message using a webhook
|
||||
*/
|
||||
|
||||
// import the discord.js module
|
||||
const Discord = require('discord.js');
|
||||
|
||||
// create a new webhook
|
||||
const hook = new Discord.WebhookClient('webhook id', 'webhook token');
|
||||
|
||||
// send a message using the webhook
|
||||
hook.sendMessage('I am now alive!');
|
||||
10
docs/custom/webhook.js
Normal file
10
docs/custom/webhook.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const fs = require('fs');
|
||||
|
||||
module.exports = {
|
||||
category: 'Examples',
|
||||
name: 'Webhooks',
|
||||
data:
|
||||
`\`\`\`js
|
||||
${fs.readFileSync('./docs/custom/examples/webhook.js').toString('utf-8')}
|
||||
\`\`\``,
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
46
src/client/WebhookClient.js
Normal file
46
src/client/WebhookClient.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const Webhook = require('../structures/Webhook');
|
||||
const RESTManager = require('./rest/RESTManager');
|
||||
const ClientDataResolver = require('./ClientDataResolver');
|
||||
const mergeDefault = require('../util/MergeDefault');
|
||||
const Constants = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* The Webhook Client
|
||||
* @extends {Webhook}
|
||||
*/
|
||||
class WebhookClient extends Webhook {
|
||||
/**
|
||||
* @param {string} id The id of the webhook.
|
||||
* @param {string} token the token of the webhook.
|
||||
* @param {ClientOptions} [options] Options for the client
|
||||
* @example
|
||||
* // create a new webhook and send a message
|
||||
* let hook = new Discord.WebhookClient('1234', 'abcdef')
|
||||
* hook.sendMessage('This will send a message').catch(console.log)
|
||||
*/
|
||||
constructor(id, token, options) {
|
||||
super(null, id, token);
|
||||
|
||||
/**
|
||||
* The options the client was instantiated with
|
||||
* @type {ClientOptions}
|
||||
*/
|
||||
this.options = mergeDefault(Constants.DefaultOptions, options);
|
||||
|
||||
/**
|
||||
* The REST manager of the client
|
||||
* @type {RESTManager}
|
||||
* @private
|
||||
*/
|
||||
this.rest = new RESTManager(this);
|
||||
|
||||
/**
|
||||
* The Data Resolver of the Client
|
||||
* @type {ClientDataResolver}
|
||||
* @private
|
||||
*/
|
||||
this.resolver = new ClientDataResolver(this);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebhookClient;
|
||||
@@ -7,6 +7,7 @@ const User = requireStructure('User');
|
||||
const GuildMember = requireStructure('GuildMember');
|
||||
const Role = requireStructure('Role');
|
||||
const Invite = requireStructure('Invite');
|
||||
const Webhook = requireStructure('Webhook');
|
||||
|
||||
class RESTMethods {
|
||||
constructor(restManager) {
|
||||
@@ -537,6 +538,87 @@ class RESTMethods {
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
fetchGuildWebhooks(guild) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.rest.makeRequest('get', Constants.Endpoints.guildWebhooks(guild.id), true)
|
||||
.then(data => {
|
||||
const hooks = new Collection();
|
||||
for (const hook of data) {
|
||||
hooks.set(hook.id, new Webhook(this.rest.client, hook));
|
||||
}
|
||||
resolve(hooks);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
fetchChannelWebhooks(channel) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.rest.makeRequest('get', Constants.Endpoints.channelWebhooks(channel.id), true)
|
||||
.then(data => {
|
||||
const hooks = new Collection();
|
||||
for (const hook of data) {
|
||||
hooks.set(hook.id, new Webhook(this.rest.client, hook));
|
||||
}
|
||||
resolve(hooks);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
fetchWebhook(id, token) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.rest.makeRequest('get', Constants.Endpoints.webhook(id, token), require('util').isUndefined(token))
|
||||
.then(data => {
|
||||
resolve(new Webhook(this.rest.client, data));
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
createChannelWebhook(channel, name, avatar) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.rest.makeRequest('post', Constants.Endpoints.channelWebhooks(channel.id), true, {
|
||||
name: name, avatar: avatar,
|
||||
})
|
||||
.then(data => {
|
||||
resolve(new Webhook(this.rest.client, data));
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
deleteChannelWebhook(webhook) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.rest.makeRequest('delete', Constants.Endpoints.webhook(webhook.id, webhook.token), false)
|
||||
.then(resolve).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
editChannelWebhook(webhook, name, avatar) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.rest.makeRequest('patch', Constants.Endpoints.webhook(webhook.id, webhook.token), false, {
|
||||
name: name, avatar: avatar,
|
||||
})
|
||||
.then(data => {
|
||||
resolve(data);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
sendWebhookMessage(webhook, content, { avatarURL, tts, disableEveryone, embeds } = {}, file = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (typeof content !== 'undefined') content = this.rest.client.resolver.resolveString(content);
|
||||
|
||||
if (disableEveryone || (typeof disableEveryone === 'undefined' && this.rest.client.options.disableEveryone)) {
|
||||
content = content.replace('@everyone', '@\u200beveryone').replace('@here', '@\u200bhere');
|
||||
}
|
||||
|
||||
this.rest.makeRequest('post', `${Constants.Endpoints.webhook(webhook.id, webhook.token)}?wait=true`, false, {
|
||||
content: content, username: webhook.name, avatar_url: avatarURL, tts: tts, file: file, embeds: embeds,
|
||||
})
|
||||
.then(data => {
|
||||
resolve(data);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RESTMethods;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
Client: require('./client/Client'),
|
||||
WebhookClient: require('./client/WebhookClient'),
|
||||
Shard: require('./sharding/Shard'),
|
||||
ShardClientUtil: require('./sharding/ShardClientUtil'),
|
||||
ShardingManager: require('./sharding/ShardingManager'),
|
||||
@@ -28,6 +29,7 @@ module.exports = {
|
||||
TextChannel: require('./structures/TextChannel'),
|
||||
User: require('./structures/User'),
|
||||
VoiceChannel: require('./structures/VoiceChannel'),
|
||||
Webhook: require('./structures/Webhook'),
|
||||
|
||||
version: require('../package').version,
|
||||
};
|
||||
|
||||
@@ -622,6 +622,14 @@ class Guild {
|
||||
return this.client.rest.methods.deleteGuild(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all webhooks for the guild.
|
||||
* @returns {Collection<Webhook>}
|
||||
*/
|
||||
fetchWebhooks() {
|
||||
return this.client.rest.methods.fetchGuildWebhooks(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this Guild equals another Guild. It compares all properties, so for most operations
|
||||
* it is advisable to just compare `guild.id === guild2.id` as it is much faster and is often
|
||||
|
||||
@@ -57,6 +57,9 @@ class TextChannel extends GuildChannel {
|
||||
createCollector() { return; }
|
||||
awaitMessages() { return; }
|
||||
bulkDelete() { return; }
|
||||
fetchWebhook() { return; }
|
||||
fetchWebhooks() { return; }
|
||||
createWebhook() { return; }
|
||||
_cacheMessage() { return; }
|
||||
}
|
||||
|
||||
|
||||
184
src/structures/Webhook.js
Normal file
184
src/structures/Webhook.js
Normal file
@@ -0,0 +1,184 @@
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Represents a Webhook
|
||||
*/
|
||||
class Webhook {
|
||||
constructor(client, dataOrID, token) {
|
||||
if (client) {
|
||||
/**
|
||||
* The client that instantiated the Channel
|
||||
* @type {Client}
|
||||
*/
|
||||
this.client = client;
|
||||
Object.defineProperty(this, 'client', { enumerable: false, configurable: false });
|
||||
if (dataOrID) this.setup(dataOrID);
|
||||
} else {
|
||||
this.id = dataOrID;
|
||||
this.token = token;
|
||||
this.client = this;
|
||||
}
|
||||
}
|
||||
|
||||
setup(data) {
|
||||
/**
|
||||
* The name of the Webhook
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = data.name;
|
||||
|
||||
/**
|
||||
* The token for the Webhook
|
||||
* @type {string}
|
||||
*/
|
||||
this.token = data.token;
|
||||
|
||||
/**
|
||||
* The avatar for the Webhook
|
||||
* @type {string}
|
||||
*/
|
||||
this.avatar = data.avatar;
|
||||
|
||||
/**
|
||||
* The ID of the Webhook
|
||||
* @type {string}
|
||||
*/
|
||||
this.id = data.id;
|
||||
|
||||
/**
|
||||
* The guild the Webhook belongs to
|
||||
* @type {string}
|
||||
*/
|
||||
this.guild_id = data.guild_id;
|
||||
|
||||
/**
|
||||
* The channel the Webhook belongs to
|
||||
* @type {string}
|
||||
*/
|
||||
this.channel_id = data.channel_id;
|
||||
|
||||
/**
|
||||
* The owner of the Webhook
|
||||
* @type {User}
|
||||
*/
|
||||
if (data.user) this.owner = data.user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options that can be passed into sendMessage, sendTTSMessage, sendFile, sendCode
|
||||
* @typedef {Object} MessageOptions
|
||||
* @property {boolean} [tts=false] Whether or not the message should be spoken aloud
|
||||
* @property {boolean} [disableEveryone=this.options.disableEveryone] Whether or not @everyone and @here
|
||||
* should be replaced with plain-text
|
||||
*/
|
||||
|
||||
/**
|
||||
* Send a message with this webhook
|
||||
* @param {StringResolvable} content The content to send
|
||||
* @param {MessageOptions} [options={}] The options to provide
|
||||
* @returns {Promise<Message|Message[]>}
|
||||
* @example
|
||||
* // send a message
|
||||
* webook.sendMessage('hello!')
|
||||
* .then(message => console.log(`Sent message: ${message.content}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
sendMessage(content, options = {}) {
|
||||
return this.client.rest.methods.sendWebhookMessage(this, content, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a text-to-speech message with this webhook
|
||||
* @param {StringResolvable} content The content to send
|
||||
* @param {MessageOptions} [options={}] The options to provide
|
||||
* @returns {Promise<Message|Message[]>}
|
||||
* @example
|
||||
* // send a TTS message
|
||||
* webhook.sendTTSMessage('hello!')
|
||||
* .then(message => console.log(`Sent tts message: ${message.content}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
sendTTSMessage(content, options = {}) {
|
||||
Object.assign(options, { tts: true });
|
||||
return this.client.rest.methods.sendWebhookMessage(this, content, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a file with this webhook
|
||||
* @param {FileResolvable} attachment The file to send
|
||||
* @param {string} [fileName="file.jpg"] The name and extension of the file
|
||||
* @param {StringResolvable} [content] Text message to send with the attachment
|
||||
* @param {MessageOptions} [options] The options to provide
|
||||
* @returns {Promise<Message>}
|
||||
*/
|
||||
sendFile(attachment, fileName, content, options = {}) {
|
||||
if (!fileName) {
|
||||
if (typeof attachment === 'string') {
|
||||
fileName = path.basename(attachment);
|
||||
} else if (attachment && attachment.path) {
|
||||
fileName = path.basename(attachment.path);
|
||||
} else {
|
||||
fileName = 'file.jpg';
|
||||
}
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.resolver.resolveFile(attachment).then(file => {
|
||||
this.client.rest.methods.sendWebhookMessage(this, content, options, {
|
||||
file,
|
||||
name: fileName,
|
||||
}).then(resolve).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a code block with this webhook
|
||||
* @param {string} lang Language for the code block
|
||||
* @param {StringResolvable} content Content of the code block
|
||||
* @param {MessageOptions} options The options to provide
|
||||
* @returns {Promise<Message|Message[]>}
|
||||
*/
|
||||
sendCode(lang, content, options = {}) {
|
||||
if (options.split) {
|
||||
if (typeof options.split !== 'object') options.split = {};
|
||||
if (!options.split.prepend) options.split.prepend = `\`\`\`${lang ? lang : ''}\n`;
|
||||
if (!options.split.append) options.split.append = '\n```';
|
||||
}
|
||||
content = this.client.resolver.resolveString(content).replace(/```/g, '`\u200b``');
|
||||
return this.sendMessage(`\`\`\`${lang ? lang : ''}\n${content}\n\`\`\``, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the Webhook
|
||||
* @returns {Promise}
|
||||
*/
|
||||
delete() {
|
||||
return this.client.rest.methods.deleteChannelWebhook(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the Webhook.
|
||||
* @param {string} name The new name for the Webhook
|
||||
* @param {FileResolvable} avatar The new avatar for the Webhook.
|
||||
* @returns {Promise<Webhook>}
|
||||
*/
|
||||
edit(name, avatar) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (avatar) {
|
||||
this.client.resolver.resolveFile(avatar).then(file => {
|
||||
let base64 = new Buffer(file, 'binary').toString('base64');
|
||||
let dataURI = `data:;base64,${base64}`;
|
||||
this.client.rest.methods.editChannelWebhook(this, name, dataURI)
|
||||
.then(resolve).catch(reject);
|
||||
}).catch(reject);
|
||||
} else {
|
||||
this.client.rest.methods.editChannelWebhook(this, name)
|
||||
.then(data => {
|
||||
this.setup(data);
|
||||
}).catch(reject);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Webhook;
|
||||
@@ -329,6 +329,49 @@ class TextBasedChannel {
|
||||
this.messages.set(message.id, message);
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all webhooks for the channel.
|
||||
* @returns {Collection<Webhook>}
|
||||
*/
|
||||
fetchWebhooks() {
|
||||
return this.client.rest.methods.fetchChannelWebhooks(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a webhook by ID
|
||||
* @param {string} id The id of the webhook.
|
||||
* @returns {Promise<Webhook>}
|
||||
*/
|
||||
fetchWebhook(id) {
|
||||
return this.client.rest.methods.fetchWebhook(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a webhook for the channel.
|
||||
* @param {string} name The name of the webhook.
|
||||
* @param {FileResolvable=} avatar The avatar for the webhook.
|
||||
* @returns {Webhook} webhook The created webhook.
|
||||
* @example
|
||||
* channel.createWebhook('Snek', 'http://snek.s3.amazonaws.com/topSnek.png')
|
||||
* .then(webhook => console.log(`Created Webhook ${webhook}`))
|
||||
* .catch(console.log)
|
||||
*/
|
||||
createWebhook(name, avatar) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (avatar) {
|
||||
this.client.resolver.resolveFile(avatar).then(file => {
|
||||
let base64 = new Buffer(file, 'binary').toString('base64');
|
||||
let dataURI = `data:;base64,${base64}`;
|
||||
this.client.rest.methods.createChannelWebhook(this, name, dataURI)
|
||||
.then(resolve).catch(reject);
|
||||
}).catch(reject);
|
||||
} else {
|
||||
this.client.rest.methods.createChannelWebhook(this, name)
|
||||
.then(resolve).catch(reject);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
exports.applyToClass = (structure, full = false) => {
|
||||
@@ -345,6 +388,9 @@ exports.applyToClass = (structure, full = false) => {
|
||||
props.push('fetchPinnedMessages');
|
||||
props.push('createCollector');
|
||||
props.push('awaitMessages');
|
||||
props.push('fetchWebhooks');
|
||||
props.push('fetchWebhook');
|
||||
props.push('createWebhook');
|
||||
}
|
||||
for (const prop of props) applyProp(structure, prop);
|
||||
};
|
||||
|
||||
@@ -103,6 +103,10 @@ const Endpoints = exports.Endpoints = {
|
||||
channelTyping: (channelID) => `${Endpoints.channel(channelID)}/typing`,
|
||||
channelPermissions: (channelID) => `${Endpoints.channel(channelID)}/permissions`,
|
||||
channelMessage: (channelID, messageID) => `${Endpoints.channelMessages(channelID)}/${messageID}`,
|
||||
channelWebhooks: (channelID) => `${Endpoints.channel(channelID)}/webhooks`,
|
||||
|
||||
// webhooks
|
||||
webhook: (webhookID, token) => `${API}/webhooks/${webhookID}${token ? `/${token}` : ''}`,
|
||||
};
|
||||
|
||||
exports.Status = {
|
||||
@@ -242,7 +246,7 @@ const PermissionFlags = exports.PermissionFlags = {
|
||||
CHANGE_NICKNAME: 1 << 26,
|
||||
MANAGE_NICKNAMES: 1 << 27,
|
||||
MANAGE_ROLES_OR_PERMISSIONS: 1 << 28,
|
||||
|
||||
MANAGE_WEBHOOKS: 1 << 29,
|
||||
MANAGE_EMOJIS: 1 << 30,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user