From c00d2090146dbfa973253aa058eb38ccfa811521 Mon Sep 17 00:00:00 2001 From: Jacob Date: Sat, 1 Oct 2016 06:53:14 -0400 Subject: [PATCH] add webhooks v8 (#759) * add webhook structure and getChannelWebhooks as well as getServerWebhooks * add sendMessage * add the ability to edit create and delete hooks * remove server wide cache and add getter. --- lib/Client/Client.js | 57 +++++++++ lib/Client/InternalClient.js | 202 +++++++++++++++++++++++++------- lib/Client/Resolver/Resolver.js | 25 ++++ lib/Constants.js | 21 ++++ lib/Structures/Server.js | 14 +++ lib/Structures/TextChannel.js | 1 + lib/index.js | 5 + src/Client/Client.js | 48 ++++++++ src/Client/InternalClient.js | 135 ++++++++++++++++++++- src/Client/Resolver/Resolver.js | 20 ++++ src/Constants.js | 10 ++ src/Structures/Server.js | 13 ++ src/Structures/TextChannel.js | 1 + src/Structures/Webhook.js | 39 ++++++ src/index.js | 2 + 15 files changed, 548 insertions(+), 45 deletions(-) create mode 100644 src/Structures/Webhook.js diff --git a/lib/Client/Client.js b/lib/Client/Client.js index cf5b38410..951ab677b 100644 --- a/lib/Client/Client.js +++ b/lib/Client/Client.js @@ -1191,6 +1191,63 @@ var Client = (function (_EventEmitter) { return this.internal.removeFriend(user).then(dataCallback(callback), errorCallback(callback)); }; + Client.prototype.getServerWebhooks = function getServerWebhooks(guild) { + var callback = arguments.length <= 1 || arguments[1] === undefined ? function () /*err, {}*/{} : arguments[1]; + + return this.internal.getServerWebhooks(guild).then(dataCallback(callback), errorCallback(callback)); + }; + + Client.prototype.getChannelWebhooks = function getChannelWebhooks(channel) { + var callback = arguments.length <= 1 || arguments[1] === undefined ? function () /*err, {}*/{} : arguments[1]; + + return this.internal.getChannelWebhooks(channel).then(dataCallback(callback), errorCallback(callback)); + }; + + Client.prototype.sendWebhookMessage = function sendWebhookMessage(webhook, content) { + var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; + var callback = arguments.length <= 3 || arguments[3] === undefined ? function () /*err, {}*/{} : arguments[3]; + + if (typeof options === "function") { + // options is the callback + callback = options; + options = {}; + } + + return this.internal.sendWebhookMessage(webhook, content, options).then(dataCallback(callback), errorCallback(callback)); + }; + + Client.prototype.editWebhook = function editWebhook(webhook) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + var callback = arguments.length <= 2 || arguments[2] === undefined ? function () /*err, {}*/{} : arguments[2]; + + if (typeof options === "function") { + // options is the callback + callback = options; + options = {}; + } + + return this.internal.editWebhook(webhook, options).then(dataCallback(callback), errorCallback(callback)); + }; + + Client.prototype.createWebhook = function createWebhook(webhook) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + var callback = arguments.length <= 2 || arguments[2] === undefined ? function () /*err, {}*/{} : arguments[2]; + + if (typeof options === "function") { + // options is the callback + callback = options; + options = {}; + } + + return this.internal.createWebhook(webhook, options).then(dataCallback(callback), errorCallback(callback)); + }; + + Client.prototype.deleteWebhook = function deleteWebhook(webhook) { + var callback = arguments.length <= 1 || arguments[1] === undefined ? function () /*err, {}*/{} : arguments[1]; + + return this.internal.createWebhook(webhook).then(dataCallback(callback), errorCallback(callback)); + }; + // def getOAuthApplication Client.prototype.getOAuthApplication = function getOAuthApplication(appID) { diff --git a/lib/Client/InternalClient.js b/lib/Client/InternalClient.js index 5d2eb2daa..d408b9046 100644 --- a/lib/Client/InternalClient.js +++ b/lib/Client/InternalClient.js @@ -78,6 +78,10 @@ var _StructuresInvite = require("../Structures/Invite"); var _StructuresInvite2 = _interopRequireDefault(_StructuresInvite); +var _StructuresWebhook = require("../Structures/Webhook"); + +var _StructuresWebhook2 = _interopRequireDefault(_StructuresWebhook); + var _VoiceVoiceConnection = require("../Voice/VoiceConnection"); var _VoiceVoiceConnection2 = _interopRequireDefault(_VoiceVoiceConnection); @@ -1792,6 +1796,124 @@ var InternalClient = (function () { return this.apiRequest("delete", _Constants.Endpoints.FRIENDS + "/" + user.id, true); }; + InternalClient.prototype.getServerWebhooks = function getServerWebhooks(server) { + var _this36 = this; + + server = this.resolver.resolveServer(server); + + if (!server) { + return Promise.reject(new Error("Failed to resolve server")); + } + + return this.apiRequest("get", _Constants.Endpoints.SERVER_WEBHOOKS(server.id), true).then(function (res) { + return res.map(function (webhook) { + var channel = _this36.channels.get("id", webhook.channel_id); + return channel.webhooks.add(new _StructuresWebhook2["default"](webhook, server, channel, _this36.users.get("id", webhook.user.id))); + }); + }); + }; + + InternalClient.prototype.getChannelWebhooks = function getChannelWebhooks(channel) { + var _this37 = this; + + return this.resolver.resolveChannel(channel).then(function (channel) { + if (!channel) { + return Promise.reject(new Error("Failed to resolve channel")); + } + + return _this37.apiRequest("get", _Constants.Endpoints.CHANNEL_WEBHOOKS(channel.id), true).then(function (res) { + return res.map(function (webhook) { + return channel.webhooks.add(new _StructuresWebhook2["default"](webhook, _this37.servers.get("id", webhook.guild_id), channel, _this37.users.get("id", webhook.user.id))); + }); + }); + }); + }; + + InternalClient.prototype.editWebhook = function editWebhook(webhook) { + var _this38 = this; + + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + return this.resolver.resolveWebhook(webhook).then(function (webhook) { + if (!webhook) { + return Promise.reject(new Error(" Failed to resolve webhook")); + } + + if (options.hasOwnProperty("avatar")) { + options.avatar = _this38.resolver.resolveToBase64(options.avatar); + } + + return _this38.apiRequest("patch", _Constants.Endpoints.WEBHOOK(webhook.id), true, options).then(function (res) { + webhook.name = res.name; + webhook.avatar = res.hasOwnProperty('avatar') ? res.avatar : webhook.avatar; + }); + }); + }; + + InternalClient.prototype.createWebhook = function createWebhook(channel) { + var _this39 = this; + + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + return this.resolver.resolveChannel(channel).then(function (destination) { + if (!channel) { + return Promise.reject(new Error(" Failed to resolve channel")); + } + + if (options.hasOwnProperty("avatar")) { + options.avatar = _this39.resolver.resolveToBase64(options.avatar); + } + + return _this39.apiRequest("post", _Constants.Endpoints.CHANNEL_WEBHOOKS(destination.id), true, options).then(function (webhook) { + return channel.webhooks.add(new _StructuresWebhook2["default"](webhook, _this39.servers.get("id", webhook.guild_id), channel, _this39.users.get("id", webhook.user.id))); + }); + }); + }; + + InternalClient.prototype.deleteWebhook = function deleteWebhook(webhook) { + var _this40 = this; + + return this.resolver.resolveWebhook(webhook).then(function (webhook) { + if (!webhook) { + return Promise.reject(new Error(" Failed to resolve webhook")); + } + + return _this40.apiRequest("delete", _Constants.Endpoints.WEBHOOK(webhook.id), true).then(function () { + webhook.channel.webhooks.remove(webhook); + }); + }); + }; + + InternalClient.prototype.sendWebhookMessage = function sendWebhookMessage(webhook, _content) { + var _this41 = this; + + var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; + + return this.resolver.resolveWebhook(webhook).then(function (destination) { + var content = _this41.resolver.resolveString(_content); + + if (_this41.client.options.disableEveryone || options.disableEveryone) { + content = content.replace(/(@)(everyone|here)/g, "$1​$2"); + } + + if (!options.hasOwnProperty("username")) { + options.username = _this41.user.username; + } + + var slack = undefined; + if (options.hasOwnProperty("slack")) { + slack = options.slack; + delete options["slack"]; + } + + options.content = _content; + + return _this41.apiRequest("post", "" + _Constants.Endpoints.WEBHOOK_MESSAGE(destination.id, destination.token) + (slack ? "/slack" : "") + "?wait=true", true, options)["catch"](console.error).then(function (res) { + return destination.channel.messages.add(new _StructuresMessage2["default"](res, destination.channel, _this41.client)); + }); + }); + }; + //def getOAuthApplication InternalClient.prototype.getOAuthApplication = function getOAuthApplication(appID) { @@ -1818,7 +1940,7 @@ var InternalClient = (function () { }; InternalClient.prototype.createWS = function createWS(url) { - var _this36 = this; + var _this42 = this; if (this.websocket) { return false; @@ -1833,10 +1955,10 @@ var InternalClient = (function () { this.websocket.onopen = function () {}; this.websocket.onclose = function (event) { - _this36.websocket = null; - _this36.state = _ConnectionState2["default"].DISCONNECTED; + _this42.websocket = null; + _this42.state = _ConnectionState2["default"].DISCONNECTED; if (event && event.code) { - _this36.client.emit("warn", "WS close: " + event.code); + _this42.client.emit("warn", "WS close: " + event.code); var err; if (event.code === 4001) { err = new Error("Gateway received invalid OP code"); @@ -1851,7 +1973,7 @@ var InternalClient = (function () { }if (event.code === 4006 || event.code === 4009) { err = new Error("Invalid session"); } else if (event.code === 4007) { - _this36.sequence = 0; + _this42.sequence = 0; err = new Error("Invalid sequence number"); } else if (event.code === 4008) { err = new Error("Gateway connection was ratelimited"); @@ -1859,17 +1981,17 @@ var InternalClient = (function () { err = new Error("Invalid shard key"); } if (err) { - _this36.client.emit("error", err); + _this42.client.emit("error", err); } } - _this36.disconnected(_this36.client.options.autoReconnect); + _this42.disconnected(_this42.client.options.autoReconnect); }; this.websocket.onerror = function (e) { - _this36.client.emit("error", e); - _this36.websocket = null; - _this36.state = _ConnectionState2["default"].DISCONNECTED; - _this36.disconnected(_this36.client.options.autoReconnect); + _this42.client.emit("error", e); + _this42.websocket = null; + _this42.state = _ConnectionState2["default"].DISCONNECTED; + _this42.disconnected(_this42.client.options.autoReconnect); }; this.websocket.onmessage = function (e) { @@ -1882,54 +2004,54 @@ var InternalClient = (function () { try { packet = JSON.parse(e.data); } catch (e) { - _this36.client.emit("error", e); + _this42.client.emit("error", e); return; } - _this36.client.emit("raw", packet); + _this42.client.emit("raw", packet); if (packet.s) { - _this36.sequence = packet.s; + _this42.sequence = packet.s; } switch (packet.op) { case 0: - _this36.processPacket(packet); + _this42.processPacket(packet); break; case 1: - _this36.heartbeat(); + _this42.heartbeat(); break; case 7: - _this36.disconnected(true); + _this42.disconnected(true); break; case 9: - _this36.sessionID = null; - _this36.sequence = 0; - _this36.identify(); + _this42.sessionID = null; + _this42.sequence = 0; + _this42.identify(); break; case 10: - if (_this36.sessionID) { - _this36.resume(); + if (_this42.sessionID) { + _this42.resume(); } else { - _this36.identify(); + _this42.identify(); } - _this36.heartbeat(); - _this36.intervals.kai = setInterval(function () { - return _this36.heartbeat(); + _this42.heartbeat(); + _this42.intervals.kai = setInterval(function () { + return _this42.heartbeat(); }, packet.d.heartbeat_interval); break; case 11: - _this36.heartbeatAcked = true; + _this42.heartbeatAcked = true; break; default: - _this36.client.emit("unknown", packet); + _this42.client.emit("unknown", packet); break; } }; }; InternalClient.prototype.processPacket = function processPacket(packet) { - var _this37 = this; + var _this43 = this; var client = this.client; var data = packet.d; @@ -1954,37 +2076,37 @@ var InternalClient = (function () { data.guilds.forEach(function (server) { if (!server.unavailable) { - server = _this37.servers.add(new _StructuresServer2["default"](server, client)); + server = _this43.servers.add(new _StructuresServer2["default"](server, client)); if (client.options.bot === false) { - _this37.unsyncedGuilds++; - _this37.syncGuild(server.id); + _this43.unsyncedGuilds++; + _this43.syncGuild(server.id); } - if (_this37.client.options.forceFetchUsers && server.members && server.members.length < server.memberCount) { - _this37.getGuildMembers(server.id, Math.ceil(server.memberCount / 1000)); + if (_this43.client.options.forceFetchUsers && server.members && server.members.length < server.memberCount) { + _this43.getGuildMembers(server.id, Math.ceil(server.memberCount / 1000)); } } else { client.emit("debug", "server " + server.id + " was unavailable, could not create (ready)"); - _this37.unavailableServers.add(server); + _this43.unavailableServers.add(server); } }); data.private_channels.forEach(function (pm) { - _this37.private_channels.add(new _StructuresPMChannel2["default"](pm, client)); + _this43.private_channels.add(new _StructuresPMChannel2["default"](pm, client)); }); if (!data.user.bot) { // bots dont have friends data.relationships.forEach(function (friend) { if (friend.type === 1) { // is a friend - _this37.friends.add(new _StructuresUser2["default"](friend.user, client)); + _this43.friends.add(new _StructuresUser2["default"](friend.user, client)); } else if (friend.type === 2) { // incoming friend requests - _this37.blocked_users.add(new _StructuresUser2["default"](friend.user, client)); + _this43.blocked_users.add(new _StructuresUser2["default"](friend.user, client)); } else if (friend.type === 3) { // incoming friend requests - _this37.incoming_friend_requests.add(new _StructuresUser2["default"](friend.user, client)); + _this43.incoming_friend_requests.add(new _StructuresUser2["default"](friend.user, client)); } else if (friend.type === 4) { // outgoing friend requests - _this37.outgoing_friend_requests.add(new _StructuresUser2["default"](friend.user, client)); + _this43.outgoing_friend_requests.add(new _StructuresUser2["default"](friend.user, client)); } else { client.emit("warn", "unknown friend type " + friend.type); } diff --git a/lib/Client/Resolver/Resolver.js b/lib/Client/Resolver/Resolver.js index d875a87bc..a04c3497f 100644 --- a/lib/Client/Resolver/Resolver.js +++ b/lib/Client/Resolver/Resolver.js @@ -84,6 +84,10 @@ var _StructuresInvite = require("../../Structures/Invite"); var _StructuresInvite2 = _interopRequireDefault(_StructuresInvite); +var _StructuresWebhook = require("../../Structures/Webhook"); + +var _StructuresWebhook2 = _interopRequireDefault(_StructuresWebhook); + var _VoiceVoiceConnection = require("../../Voice/VoiceConnection"); var _VoiceVoiceConnection2 = _interopRequireDefault(_VoiceVoiceConnection); @@ -308,6 +312,27 @@ var Resolver = (function () { return null; }; + Resolver.prototype.resolveWebhook = function resolveWebhook(resource) { + /* + accepts a Webhook + */ + if (resource instanceof _StructuresWebhook2["default"]) { + return Promise.resolve(resource); + } + if (resource instanceof String || typeof resource === "string") { + var server = this.internal.servers.find(function (s) { + return s.webhooks.has("id", resource); + }); + if (server) { + return Promise.resolve(server.webhooks.get("id", resource)); + } + } + + var error = new Error("Could not resolve webhook"); + error.resource = resource; + return Promise.reject(error); + }; + Resolver.prototype.resolveMessage = function resolveMessage(resource) { // accepts a Message, PMChannel & TextChannel diff --git a/lib/Constants.js b/lib/Constants.js index bd6b2cb7a..5daca5ea8 100644 --- a/lib/Constants.js +++ b/lib/Constants.js @@ -64,6 +64,9 @@ var Endpoints = Constants.Endpoints = { SERVER_CHANNELS: function SERVER_CHANNELS(serverID) { return Endpoints.SERVER(serverID) + "/channels"; }, + SERVER_WEBHOOKS: function SERVER_WEBHOOKS(serverID) { + return Endpoints.SERVER(serverID) + "/webhooks"; + }, // channels CHANNELS: API + "/channels", @@ -91,6 +94,24 @@ var Endpoints = Constants.Endpoints = { CHANNEL_PIN: function CHANNEL_PIN(channelID, messageID) { return Endpoints.CHANNEL_PINS(channelID) + "/" + messageID; }, + CHANNEL_WEBHOOKS: function CHANNEL_WEBHOOKS(channelID) { + return Endpoints.CHANNEL(channelID) + "/webhooks"; + }, + + // webhooks + WEBHOOKS: API + "/webhooks", + WEBHOOK: function WEBHOOK(webhookID) { + return Endpoints.WEBHOOKS + "/" + webhookID; + }, + WEBHOOK_WITH_TOKEN: function WEBHOOK_WITH_TOKEN(webhookID, webhookToken) { + return Endpoints.WEBHOOKS + "/" + webhookToken; + }, + WEBHOOK_MESSAGE: function WEBHOOK_MESSAGE(webhookID, webhookToken) { + return Endpoints.WEBHOOK(webhookID) + "/" + webhookToken; + }, + WEBHOOK_MESSAGE_SLACK: function WEBHOOK_MESSAGE_SLACK(webhookID, webhookToken) { + return Endpoints.WEBHOOK_MESSAGE(webhookID, webhookToken) + "/slack"; + }, // friends FRIENDS: API + "/users/@me/relationships" diff --git a/lib/Structures/Server.js b/lib/Structures/Server.js index 5f66766b9..e23ed40a3 100644 --- a/lib/Structures/Server.js +++ b/lib/Structures/Server.js @@ -450,6 +450,20 @@ var Server = (function (_Equality) { }; _createClass(Server, [{ + key: "webhooks", + get: function get() { + return this.channels.map(function (c) { + return c.webhooks; + }).reduce(function (previousChannel, currentChannel) { + if (currentChannel) { + currentChannel.forEach(function (webhook) { + previousChannel.add(webhook); + }); + } + return previousChannel; + }, new _UtilCache2["default"]("id")); + } + }, { key: "createdAt", get: function get() { return new Date(+this.id / 4194304 + 1420070400000); diff --git a/lib/Structures/TextChannel.js b/lib/Structures/TextChannel.js index 74d807b6d..c8ea75fc4 100644 --- a/lib/Structures/TextChannel.js +++ b/lib/Structures/TextChannel.js @@ -30,6 +30,7 @@ var TextChannel = (function (_ServerChannel) { this.topic = data.topic; this.lastMessageID = data.last_message_id || data.lastMessageID; + this.webhooks = new _UtilCache2["default"]("id"); this.messages = new _UtilCache2["default"]("id", client.options.maxCachedMessages); } diff --git a/lib/index.js b/lib/index.js index f66bbebfc..e943ccb68 100644 --- a/lib/index.js +++ b/lib/index.js @@ -86,6 +86,10 @@ var _StructuresVoiceChannel = require("./Structures/VoiceChannel"); var _StructuresVoiceChannel2 = _interopRequireDefault(_StructuresVoiceChannel); +var _StructuresWebhook = require("./Structures/Webhook"); + +var _StructuresWebhook2 = _interopRequireDefault(_StructuresWebhook); + var _Constants = require("./Constants"); var _Constants2 = _interopRequireDefault(_Constants); @@ -108,6 +112,7 @@ exports["default"] = { TextChannel: _StructuresTextChannel2["default"], User: _StructuresUser2["default"], VoiceChannel: _StructuresVoiceChannel2["default"], + Webhook: _StructuresWebhook2["default"], Constants: _Constants2["default"], Cache: _UtilCacheJs2["default"] }; diff --git a/src/Client/Client.js b/src/Client/Client.js index 0af23245a..3a93d02fa 100755 --- a/src/Client/Client.js +++ b/src/Client/Client.js @@ -1177,6 +1177,54 @@ export default class Client extends EventEmitter { .then(dataCallback(callback), errorCallback(callback)); } + getServerWebhooks(guild, callback = (/*err, {}*/) => {}) { + return this.internal.getServerWebhooks(guild) + .then(dataCallback(callback), errorCallback(callback)); + } + + getChannelWebhooks(channel, callback = (/*err, {}*/) => {}) { + return this.internal.getChannelWebhooks(channel) + .then(dataCallback(callback), errorCallback(callback)); + } + + sendWebhookMessage(webhook, content, options = {}, callback = (/*err, {}*/) => {}) { + if (typeof options === "function") { + // options is the callback + callback = options; + options = {}; + } + + return this.internal.sendWebhookMessage(webhook, content, options) + .then(dataCallback(callback), errorCallback(callback)); + } + + editWebhook(webhook, options = {}, callback = (/*err, {}*/) => {}) { + if (typeof options === "function") { + // options is the callback + callback = options; + options = {}; + } + + return this.internal.editWebhook(webhook, options) + .then(dataCallback(callback), errorCallback(callback)); + } + + createWebhook(webhook, options = {}, callback = (/*err, {}*/) => {}) { + if (typeof options === "function") { + // options is the callback + callback = options; + options = {}; + } + + return this.internal.createWebhook(webhook, options) + .then(dataCallback(callback), errorCallback(callback)); + } + + deleteWebhook(webhook, callback = (/*err, {}*/) => {}) { + return this.internal.createWebhook(webhook) + .then(dataCallback(callback), errorCallback(callback)); + } + // def getOAuthApplication getOAuthApplication(appID, callback = (/*err, bans*/) => { }) { if (typeof appID === "function") { diff --git a/src/Client/InternalClient.js b/src/Client/InternalClient.js index 7970863e6..a53d8b5f9 100755 --- a/src/Client/InternalClient.js +++ b/src/Client/InternalClient.js @@ -21,6 +21,7 @@ import Server from "../Structures/Server"; import Message from "../Structures/Message"; import Role from "../Structures/Role"; import Invite from "../Structures/Invite"; +import Webhook from "../Structures/Webhook"; import VoiceConnection from "../Voice/VoiceConnection"; import TokenCacher from "../Util/TokenCacher"; @@ -58,7 +59,7 @@ export default class InternalClient { var promise = new Promise((res, rej) => { resolve = res; reject = rej; - }) + }); var buckets = []; var match = url.match(/\/channels\/([0-9]+)\/messages(\/[0-9]+)?$/); if(match) { @@ -311,7 +312,7 @@ export default class InternalClient { "self_deaf": false } }); - } + }; var joinVoice = () => { return new Promise((resolve, reject) => { @@ -349,7 +350,7 @@ export default class InternalClient { this.client.on("raw", check); joinSendWS(); }); - } + }; var existingServerConn = this.voiceConnections.get("server", channel.server); // same server connection if (existingServerConn) { @@ -1438,7 +1439,7 @@ export default class InternalClient { var options = { avatar: this.resolver.resolveToBase64(data.avatar) || this.user.avatar, username: data.username || this.user.username - } + }; if (this.email || data.email) { options.email = data.email || this.email; @@ -1507,7 +1508,7 @@ export default class InternalClient { position: (data.position ? data.position : channel.position), user_limit: (data.userLimit ? data.userLimit : channel.userLimit), bitrate: (data.bitrate ? data.bitrate : channel.bitrate ? channel.bitrate : undefined) - } + }; if (data.position < 0) { return Promise.reject(new Error("Position cannot be less than 0")); @@ -1566,6 +1567,130 @@ export default class InternalClient { return this.apiRequest("delete", `${Endpoints.FRIENDS}/${user.id}`, true); } + getServerWebhooks(server) { + server = this.resolver.resolveServer(server); + + if (!server) { + return Promise.reject(new Error("Failed to resolve server")); + } + + return this.apiRequest("get", Endpoints.SERVER_WEBHOOKS(server.id), true) + .then(res => res.map( + webhook => { + let channel = this.channels.get("id", webhook.channel_id); + return channel.webhooks.add(new Webhook( + webhook, + server, + channel, + this.users.get("id", webhook.user.id) + )) + } + )); + } + + getChannelWebhooks(channel) { + return this.resolver.resolveChannel(channel).then(channel => { + if (!channel) { + return Promise.reject(new Error("Failed to resolve channel")); + } + + return this.apiRequest("get", Endpoints.CHANNEL_WEBHOOKS(channel.id), true) + .then(res => res.map( + webhook => channel.webhooks.add(new Webhook( + webhook, + this.servers.get("id", webhook.guild_id), + channel, + this.users.get("id", webhook.user.id) + )) + )); + }) + } + + editWebhook(webhook, options = {}) { + return this.resolver.resolveWebhook(webhook).then(webhook => { + if (!webhook) { + return Promise.reject(new Error(" Failed to resolve webhook")) + } + + if (options.hasOwnProperty("avatar")) { + options.avatar = this.resolver.resolveToBase64(options.avatar); + } + + return this.apiRequest("patch", Endpoints.WEBHOOK(webhook.id), true, options) + .then(res => { + webhook.name = res.name; + webhook.avatar = res.hasOwnProperty('avatar') ? res.avatar : webhook.avatar; + }); + }) + } + + createWebhook(channel, options = {}) { + return this.resolver.resolveChannel(channel) + .then(destination => { + if (!channel) { + return Promise.reject(new Error(" Failed to resolve channel")) + } + + if (options.hasOwnProperty("avatar")) { + options.avatar = this.resolver.resolveToBase64(options.avatar); + } + + return this.apiRequest("post", Endpoints.CHANNEL_WEBHOOKS(destination.id), true, options) + .then(webhook => channel.webhooks.add(new Webhook( + webhook, + this.servers.get("id", webhook.guild_id), + channel, + this.users.get("id", webhook.user.id) + ))); + }); + } + + deleteWebhook(webhook) { + return this.resolver.resolveWebhook(webhook).then(webhook => { + if (!webhook) { + return Promise.reject(new Error(" Failed to resolve webhook")) + } + + return this.apiRequest("delete", Endpoints.WEBHOOK(webhook.id), true) + .then(() => { + webhook.channel.webhooks.remove(webhook); + }); + }) + } + + sendWebhookMessage(webhook, _content, options = {}) { + return this.resolver.resolveWebhook(webhook) + .then(destination => { + var content = this.resolver.resolveString(_content); + + if (this.client.options.disableEveryone || options.disableEveryone) { + content = content.replace(/(@)(everyone|here)/g, '$1\u200b$2'); + } + + if (!options.hasOwnProperty("username")) { + options.username = this.user.username; + } + + let slack; + if (options.hasOwnProperty("slack")) { + slack = options.slack; + delete options["slack"]; + } + + options.content = _content; + + return this.apiRequest( + "post", + `${Endpoints.WEBHOOK_MESSAGE(destination.id, destination.token)}${slack ? "/slack" : ""}?wait=true`, + true, + options + ) + .catch(console.error) + .then(res => destination.channel.messages.add(new Message(res, destination.channel, this.client))); + }); + + } + //def getOAuthApplication getOAuthApplication(appID) { appID = appID || "@me"; diff --git a/src/Client/Resolver/Resolver.js b/src/Client/Resolver/Resolver.js index aa7495ac1..dca726e27 100644 --- a/src/Client/Resolver/Resolver.js +++ b/src/Client/Resolver/Resolver.js @@ -43,6 +43,7 @@ import Role from "../../Structures/Role"; import Server from "../../Structures/Server"; import Message from "../../Structures/Message"; import Invite from "../../Structures/Invite"; +import Webhook from "../../Structures/Webhook"; import VoiceConnection from "../../Voice/VoiceConnection"; export default class Resolver { @@ -211,6 +212,25 @@ export default class Resolver { return null; } + resolveWebhook(resource) { + /* + accepts a Webhook + */ + if (resource instanceof Webhook) { + return Promise.resolve(resource); + } + if (resource instanceof String || typeof resource === "string") { + let server = this.internal.servers.find(s => s.webhooks.has("id", resource)); + if (server) { + return Promise.resolve(server.webhooks.get("id", resource)); + } + } + + var error = new Error("Could not resolve webhook"); + error.resource = resource; + return Promise.reject(error); + } + resolveMessage(resource) { // accepts a Message, PMChannel & TextChannel diff --git a/src/Constants.js b/src/Constants.js index 05e5fa8e7..c7dec0c71 100644 --- a/src/Constants.js +++ b/src/Constants.js @@ -33,6 +33,7 @@ const Endpoints = Constants.Endpoints = { SERVER_INTEGRATIONS: (serverID) => `${Endpoints.SERVER(serverID) }/integrations`, SERVER_MEMBERS: (serverID) => `${Endpoints.SERVER(serverID) }/members`, SERVER_CHANNELS: (serverID) => `${Endpoints.SERVER(serverID) }/channels`, + SERVER_WEBHOOKS: (serverID) => `${Endpoints.SERVER(serverID) }/webhooks`, // channels CHANNELS: `${API}/channels`, @@ -44,6 +45,15 @@ const Endpoints = Constants.Endpoints = { CHANNEL_MESSAGE: (channelID, messageID) => `${Endpoints.CHANNEL_MESSAGES(channelID)}/${messageID}`, CHANNEL_PINS: (channelID) => `${Endpoints.CHANNEL(channelID) }/pins`, CHANNEL_PIN: (channelID, messageID) => `${Endpoints.CHANNEL_PINS(channelID) }/${messageID}`, + CHANNEL_WEBHOOKS: (channelID) => `${Endpoints.CHANNEL(channelID) }/webhooks`, + + // webhooks + WEBHOOKS: `${API}/webhooks`, + WEBHOOK: (webhookID) => `${Endpoints.WEBHOOKS}/${webhookID}`, + WEBHOOK_WITH_TOKEN: (webhookID, webhookToken) => `${Endpoints.WEBHOOKS}/${webhookToken}`, + WEBHOOK_MESSAGE: (webhookID, webhookToken) => `${Endpoints.WEBHOOK(webhookID)}/${webhookToken}`, + WEBHOOK_MESSAGE_SLACK: (webhookID, webhookToken) => `${Endpoints.WEBHOOK_MESSAGE(webhookID, webhookToken)}/slack`, + // friends FRIENDS: `${API}/users/@me/relationships` diff --git a/src/Structures/Server.js b/src/Structures/Server.js index 4c47a0bda..f7a5b4c73 100644 --- a/src/Structures/Server.js +++ b/src/Structures/Server.js @@ -132,6 +132,19 @@ export default class Server extends Equality { } } + get webhooks() { + return this.channels + .map(c => c.webhooks) + .reduce((previousChannel, currentChannel) => { + if (currentChannel) { + currentChannel.forEach(webhook => { + previousChannel.add(webhook); + }) + } + return previousChannel; + }, new Cache("id")); + } + get createdAt() { return new Date((+this.id / 4194304) + 1420070400000); } diff --git a/src/Structures/TextChannel.js b/src/Structures/TextChannel.js index 30d64fe0c..dad93bc37 100755 --- a/src/Structures/TextChannel.js +++ b/src/Structures/TextChannel.js @@ -10,6 +10,7 @@ export default class TextChannel extends ServerChannel{ this.topic = data.topic; this.lastMessageID = data.last_message_id || data.lastMessageID; + this.webhooks = new Cache("id"); this.messages = new Cache("id", client.options.maxCachedMessages); } diff --git a/src/Structures/Webhook.js b/src/Structures/Webhook.js new file mode 100644 index 000000000..10c00607a --- /dev/null +++ b/src/Structures/Webhook.js @@ -0,0 +1,39 @@ +"use strict"; + +import {Endpoints} from "../Constants"; +/* example data +{ + id: '164585980739846145' + name: 'wlfSS', + roles: [ '135829612780322816' ], + require_colons: false, + managed: true, +} +*/ + +export default class Webhook { + constructor(data, server, channel, user) { + this.server = server; + this.channel = channel; + this.id = data.id; + this.user = user || data.user; + this.name = data.name; + this.avatar = data.avatar; + this.token = data.token + } + + get getURL() { + return `https://canary.discordapp.com/api/webhooks/${this.channel.id}/${this.token.id}`; + } + + toObject() { + let keys = ['id', 'name', 'avatar', 'token'], + obj = {}; + + for (let k of keys) { + obj[k] = this[k]; + } + + return obj; + } +} diff --git a/src/index.js b/src/index.js index 111c8854b..62f9e8f0b 100644 --- a/src/index.js +++ b/src/index.js @@ -43,6 +43,7 @@ import ServerChannel from "./Structures/ServerChannel"; import TextChannel from "./Structures/TextChannel"; import User from "./Structures/User"; import VoiceChannel from "./Structures/VoiceChannel"; +import Webhook from "./Structures/Webhook"; import Constants from "./Constants"; import Cache from "./Util/Cache.js"; @@ -60,6 +61,7 @@ export default { TextChannel, User, VoiceChannel, + Webhook, Constants, Cache };