Webhook.js (8147B)
1 'use strict'; 2 3 const APIMessage = require('./APIMessage'); 4 const Channel = require('./Channel'); 5 const { WebhookTypes } = require('../util/Constants'); 6 const DataResolver = require('../util/DataResolver'); 7 const Snowflake = require('../util/Snowflake'); 8 9 /** 10 * Represents a webhook. 11 */ 12 class Webhook { 13 constructor(client, data) { 14 /** 15 * The client that instantiated the webhook 16 * @name Webhook#client 17 * @type {Client} 18 * @readonly 19 */ 20 Object.defineProperty(this, 'client', { value: client }); 21 if (data) this._patch(data); 22 } 23 24 _patch(data) { 25 /** 26 * The name of the webhook 27 * @type {string} 28 */ 29 this.name = data.name; 30 31 /** 32 * The token for the webhook 33 * @name Webhook#token 34 * @type {?string} 35 */ 36 Object.defineProperty(this, 'token', { value: data.token || null, writable: true, configurable: true }); 37 38 /** 39 * The avatar for the webhook 40 * @type {?string} 41 */ 42 this.avatar = data.avatar; 43 44 /** 45 * The ID of the webhook 46 * @type {Snowflake} 47 */ 48 this.id = data.id; 49 50 /** 51 * The type of the webhook 52 * @type {WebhookTypes} 53 */ 54 this.type = WebhookTypes[data.type]; 55 56 /** 57 * The guild the webhook belongs to 58 * @type {Snowflake} 59 */ 60 this.guildID = data.guild_id; 61 62 /** 63 * The channel the webhook belongs to 64 * @type {Snowflake} 65 */ 66 this.channelID = data.channel_id; 67 68 if (data.user) { 69 /** 70 * The owner of the webhook 71 * @type {?User|Object} 72 */ 73 this.owner = this.client.users ? this.client.users.cache.get(data.user.id) : data.user; 74 } else { 75 this.owner = null; 76 } 77 } 78 79 /** 80 * Options that can be passed into send. 81 * @typedef {Object} WebhookMessageOptions 82 * @property {string} [username=this.name] Username override for the message 83 * @property {string} [avatarURL] Avatar URL override for the message 84 * @property {boolean} [tts=false] Whether or not the message should be spoken aloud 85 * @property {string} [nonce=''] The nonce for the message 86 * @property {Object[]} [embeds] An array of embeds for the message 87 * @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content 88 * (see [here](https://discordapp.com/developers/docs/resources/channel#embed-object) for more details) 89 * @property {DisableMentionType} [disableMentions=this.client.options.disableMentions] Whether or not all mentions or 90 * everyone/here mentions should be sanitized to prevent unexpected mentions 91 * @property {FileOptions[]|string[]} [files] Files to send with the message 92 * @property {string|boolean} [code] Language for optional codeblock formatting to apply 93 * @property {boolean|SplitOptions} [split=false] Whether or not the message should be split into multiple messages if 94 * it exceeds the character limit. If an object is provided, these are the options for splitting the message. 95 */ 96 97 /** 98 * Sends a message with this webhook. 99 * @param {StringResolvable|APIMessage} [content=''] The content to send 100 * @param {WebhookMessageOptions|MessageAdditions} [options={}] The options to provide 101 * @returns {Promise<Message|Object>} 102 * @example 103 * // Send a basic message 104 * webhook.send('hello!') 105 * .then(message => console.log(`Sent message: ${message.content}`)) 106 * .catch(console.error); 107 * @example 108 * // Send a remote file 109 * webhook.send({ 110 * files: ['https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048'] 111 * }) 112 * .then(console.log) 113 * .catch(console.error); 114 * @example 115 * // Send a local file 116 * webhook.send({ 117 * files: [{ 118 * attachment: 'entire/path/to/file.jpg', 119 * name: 'file.jpg' 120 * }] 121 * }) 122 * .then(console.log) 123 * .catch(console.error); 124 * @example 125 * // Send an embed with a local image inside 126 * webhook.send('This is an embed', { 127 * embeds: [{ 128 * thumbnail: { 129 * url: 'attachment://file.jpg' 130 * } 131 * }], 132 * files: [{ 133 * attachment: 'entire/path/to/file.jpg', 134 * name: 'file.jpg' 135 * }] 136 * }) 137 * .then(console.log) 138 * .catch(console.error); 139 */ 140 async send(content, options) { 141 let apiMessage; 142 143 if (content instanceof APIMessage) { 144 apiMessage = content.resolveData(); 145 } else { 146 apiMessage = APIMessage.create(this, content, options).resolveData(); 147 if (Array.isArray(apiMessage.data.content)) { 148 return Promise.all(apiMessage.split().map(this.send.bind(this))); 149 } 150 } 151 152 const { data, files } = await apiMessage.resolveFiles(); 153 return this.client.api 154 .webhooks(this.id, this.token) 155 .post({ 156 data, 157 files, 158 query: { wait: true }, 159 auth: false, 160 }) 161 .then(d => { 162 const channel = this.client.channels ? this.client.channels.cache.get(d.channel_id) : undefined; 163 if (!channel) return d; 164 return channel.messages.add(d, false); 165 }); 166 } 167 168 /** 169 * Sends a raw slack message with this webhook. 170 * @param {Object} body The raw body to send 171 * @returns {Promise<boolean>} 172 * @example 173 * // Send a slack message 174 * webhook.sendSlackMessage({ 175 * 'username': 'Wumpus', 176 * 'attachments': [{ 177 * 'pretext': 'this looks pretty cool', 178 * 'color': '#F0F', 179 * 'footer_icon': 'http://snek.s3.amazonaws.com/topSnek.png', 180 * 'footer': 'Powered by sneks', 181 * 'ts': Date.now() / 1000 182 * }] 183 * }).catch(console.error); 184 */ 185 sendSlackMessage(body) { 186 return this.client.api 187 .webhooks(this.id, this.token) 188 .slack.post({ 189 query: { wait: true }, 190 auth: false, 191 data: body, 192 }) 193 .then(data => data.toString() === 'ok'); 194 } 195 196 /** 197 * Edits the webhook. 198 * @param {Object} options Options 199 * @param {string} [options.name=this.name] New name for this webhook 200 * @param {BufferResolvable} [options.avatar] New avatar for this webhook 201 * @param {ChannelResolvable} [options.channel] New channel for this webhook 202 * @param {string} [reason] Reason for editing this webhook 203 * @returns {Promise<Webhook>} 204 */ 205 async edit({ name = this.name, avatar, channel }, reason) { 206 if (avatar && typeof avatar === 'string' && !avatar.startsWith('data:')) { 207 avatar = await DataResolver.resolveImage(avatar); 208 } 209 if (channel) channel = channel instanceof Channel ? channel.id : channel; 210 const data = await this.client.api.webhooks(this.id, channel ? undefined : this.token).patch({ 211 data: { name, avatar, channel_id: channel }, 212 reason, 213 }); 214 215 this.name = data.name; 216 this.avatar = data.avatar; 217 this.channelID = data.channel_id; 218 return this; 219 } 220 221 /** 222 * Deletes the webhook. 223 * @param {string} [reason] Reason for deleting this webhook 224 * @returns {Promise} 225 */ 226 delete(reason) { 227 return this.client.api.webhooks(this.id, this.token).delete({ reason }); 228 } 229 /** 230 * The timestamp the webhook was created at 231 * @type {number} 232 * @readonly 233 */ 234 get createdTimestamp() { 235 return Snowflake.deconstruct(this.id).timestamp; 236 } 237 238 /** 239 * The time the webhook was created at 240 * @type {Date} 241 * @readonly 242 */ 243 get createdAt() { 244 return new Date(this.createdTimestamp); 245 } 246 247 /** 248 * The url of this webhook 249 * @type {string} 250 * @readonly 251 */ 252 get url() { 253 return this.client.options.http.api + this.client.api.webhooks(this.id, this.token); 254 } 255 256 /** 257 * A link to the webhook's avatar. 258 * @param {ImageURLOptions} [options={}] Options for the Image URL 259 * @returns {?string} 260 */ 261 avatarURL({ format, size } = {}) { 262 if (!this.avatar) return null; 263 return this.client.rest.cdn.Avatar(this.id, this.avatar, format, size); 264 } 265 266 static applyToClass(structure) { 267 for (const prop of ['send', 'sendSlackMessage', 'edit', 'delete', 'createdTimestamp', 'createdAt', 'url']) { 268 Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(Webhook.prototype, prop)); 269 } 270 } 271 } 272 273 module.exports = Webhook;