commit 9956e43c8e0f2424eb6d19ee03b6fb83e6d00f42 Author: hydrabolt Date: Sat Apr 16 22:58:49 2016 +0100 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..d1ca39f79 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# Created by https://www.gitignore.io + +.tmp/ +.vscode/ + +### Node ### +# Logs +logs +*.log + +test/auth.json + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules +test/auth.json +examples/auth.json +docs/_build \ No newline at end of file diff --git a/.jscsrc b/.jscsrc new file mode 100644 index 000000000..d636656da --- /dev/null +++ b/.jscsrc @@ -0,0 +1,10 @@ +{ + "preset": "airbnb", + "validateIndentation": "\t", + "maximumLineLength": 140, + "maxErrors": 5000, + "disallowMultipleVarDecl": false, + "disallowSpacesInsideObjectBrackets": false, + "disallowMixedSpacesAndTabs": false, + "excludeFiles": [] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 000000000..2514699cb --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +

+ + discord.js
+
+

REWRITE

+

\ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 000000000..08a7c794e --- /dev/null +++ b/package.json @@ -0,0 +1,56 @@ +{ + "name": "discord.js", + "version": "7.0.0", + "description": "A way to interface with the Discord API", + "main": "./src/index", + "scripts": { + "test": "jscs src && node test/random" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/hydrabolt/discord.js.git" + }, + "keywords": [ + "discord", + "api", + "bot", + "client", + "node", + "discordapp" + ], + "author": "Amish Shah ", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/hydrabolt/discord.js/issues" + }, + "homepage": "https://github.com/hydrabolt/discord.js#readme", + "dependencies": { + "babel-plugin-transform-runtime": "^6.6.0", + "object.entries": "^1.0.3", + "object.values": "^1.0.3", + "superagent": "^1.5.0", + "unpipe": "^1.0.0", + "ws": "^0.8.1" + }, + "devDependencies": { + "babel-preset-es2015": "^6.6.0", + "babel-preset-stage-3": "^6.5.0", + "grunt": "^0.4.5", + "grunt-babel": "^6.0.0", + "grunt-browserify": "^4.0.1", + "grunt-contrib-uglify": "^0.11.0", + "grunt-jscs": "^2.8.0", + "jscs": "^2.11.0", + "load-grunt-tasks": "^3.3.0" + }, + "optionalDependencies": { + "node-opus": "^0.1.11" + }, + "engines": { + "node": ">=0.12.7" + }, + "browser": { + "./src/Util/TokenCacher.js": "./src/Util/TokenCacher-shim.js", + "./lib/Util/TokenCacher.js": "./lib/Util/TokenCacher-shim.js" + } +} diff --git a/src/client/Client.js b/src/client/Client.js new file mode 100644 index 000000000..634cc790f --- /dev/null +++ b/src/client/Client.js @@ -0,0 +1,34 @@ +'use strict'; + +const EventEmitter = require('events').EventEmitter; +const MergeDefault = require('../util/MergeDefault'); +const Constants = require('../util/Constants'); +const RESTManager = require('./rest/RestManager'); +const ClientDataStore = require('../structures/DataStore/ClientDataStore'); +const ClientManager = require('./ClientManager'); +const WebSocketManager = require('./websocket/WebSocketManager'); + +class Client extends EventEmitter{ + + constructor(options) { + super(); + this.options = MergeDefault(Constants.DefaultOptions, options); + this.rest = new RESTManager(this); + this.store = new ClientDataStore(this); + this.manager = new ClientManager(this); + this.ws = new WebSocketManager(this); + } + + login(email, password) { + if (password) { + // login with email and password + return this.rest.methods.LoginEmailPassword(email, password); + } else { + // login with token + return this.rest.methods.LoginToken(email); + } + } + +} + +module.exports = Client; diff --git a/src/client/ClientManager.js b/src/client/ClientManager.js new file mode 100644 index 000000000..00ffc5c69 --- /dev/null +++ b/src/client/ClientManager.js @@ -0,0 +1,34 @@ +'use strict'; + +const Constants = require('../util/Constants'); + +class ClientManager { + + constructor(client) { + this.client = client; + this.heartbeatInterval = null; + } + + connectToWebSocket(token, resolve, reject) { + this.client.store.token = token; + this.client.rest.methods.GetGateway() + .then(gateway => { + this.client.ws.connect(gateway); + this.client.once(Constants.Events.READY, () => resolve(token)); + }) + .catch(reject); + + setTimeout(() => reject(Constants.Errors.TOOK_TOO_LONG), 1000 * 15); + } + + setupKeepAlive(time) { + this.heartbeatInterval = setInterval(() => { + this.client.ws.send({ + op: Constants.OPCodes.HEARTBEAT, + d: Date.now(), + }); + }, time); + } +} + +module.exports = ClientManager; diff --git a/src/client/rest/RESTManager.js b/src/client/rest/RESTManager.js new file mode 100644 index 000000000..5cda94ccd --- /dev/null +++ b/src/client/rest/RESTManager.js @@ -0,0 +1,53 @@ +'use strict'; + +const request = require('superagent'); +const Constants = require('../../util/Constants'); +const UserAgentManager = require('./UserAgentManager'); +const RESTMethods = require('./RESTMethods'); + +class RESTManager{ + + constructor(client) { + this.client = client; + this.queue = []; + this.userAgentManager = new UserAgentManager(this); + this.methods = new RESTMethods(this); + } + + makeRequest(method, url, auth, data, file) { + /* + file is {file, name} + */ + let apiRequest = request[method](url); + + if (auth) { + if (this.client.store.token) { + apiRequest.set('authorization', this.client.store.token); + } else { + throw Constants.Errors.NO_TOKEN; + } + } + + if (data) { + apiRequest.send(data); + } + + if (file) { + apiRequest.attach('file', file.file, file.name); + } + + apiRequest.set('User-Agent', this.userAgentManager.userAgent); + + return new Promise((resolve, reject) => { + apiRequest.end((err, res) => { + if (err) { + reject(err); + } else { + resolve(res ? res.body || {} : {}); + } + }); + }); + } +}; + +module.exports = RESTManager; diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js new file mode 100644 index 000000000..228f6f7da --- /dev/null +++ b/src/client/rest/RESTMethods.js @@ -0,0 +1,37 @@ +'use strict'; + +const Constants = require('../../util/Constants'); + +class RESTMethods{ + constructor(restManager) { + this.rest = restManager; + } + + LoginEmailPassword(email, password) { + return new Promise((resolve, reject) => { + + this.rest.makeRequest('post', Constants.Endpoints.LOGIN, false, { email, password }) + .then(data => { + this.rest.client.manager.connectToWebSocket(data.token, resolve, reject); + }) + .catch(reject); + + }); + } + + LoginToken(token) { + return new Promise((resolve, reject) => { + this.rest.client.manager.connectToWebSocket(token, resolve, reject); + }); + } + + GetGateway() { + return new Promise((resolve, reject) => { + this.rest.makeRequest('get', Constants.Endpoints.GATEWAY, true) + .then(res => resolve(res.url)) + .catch(reject); + }); + } +} + +module.exports = RESTMethods; diff --git a/src/client/rest/UserAgentManager.js b/src/client/rest/UserAgentManager.js new file mode 100644 index 000000000..9fb075fca --- /dev/null +++ b/src/client/rest/UserAgentManager.js @@ -0,0 +1,24 @@ +'use strict'; + +const Constants = require('../../util/Constants'); + +class UserAgentManager{ + constructor(restManager) { + this.restManager = restManager; + this._userAgent = { + url: 'https://github.com/hydrabolt/discord.js', + version: Constants.Package.version, + }; + } + + set(info) { + this._userAgent.url = info.url || 'https://github.com/hydrabolt/discord.js'; + this._userAgent.version = info.version || Constants.Package.version; + } + + get userAgent() { + return `DiscordBot (${this._userAgent.url}, ${this._userAgent.version})`; + } +} + +module.exports = UserAgentManager; diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js new file mode 100644 index 000000000..ae9b8c550 --- /dev/null +++ b/src/client/websocket/WebSocketManager.js @@ -0,0 +1,79 @@ +'use strict'; + +const WebSocket = require('ws'); +const Constants = require('../../util/Constants'); +const zlib = require('zlib'); +const PacketManager = require('./packets/WebSocketPacketManager'); + +class WebSocketManager { + + constructor(client) { + this.client = client; + this.ws = null; + this.packetManager = new PacketManager(this); + this.emittedReady = false; + } + + connect(gateway) { + gateway += `/?v=${this.client.options.protocol_version}`; + this.ws = new WebSocket(gateway); + this.ws.onopen = () => this.EventOpen(); + this.ws.onclose = () => this.EventClose(); + this.ws.onmessage = (e) => this.EventMessage(e); + this.ws.onerror = (e) => this.EventError(e); + } + + send(data) { + this.ws.send(JSON.stringify(data)); + } + + EventOpen() { + let payload = this.client.options.ws; + payload.token = this.client.store.token; + + this.send({ + op: Constants.OPCodes.IDENTIFY, + d: payload, + }); + } + + EventClose() { + + } + + EventMessage(event) { + let packet; + try { + if (event.binary) { + event.data = zlib.inflateSync(event.data).toString(); + } + + packet = JSON.parse(event.data); + } catch (e) { + return this.EventError(Constants.Errors.BAD_WS_MESSAGE); + } + + this.packetManager.handle(packet); + } + + EventError(e) { + + } + + checkIfReady() { + if (!this.emittedReady) { + let unavailableCount = 0; + + for (let guildID in this.client.store.data.guilds) { + unavailableCount += this.client.store.data.guilds[guildID].available ? 0 : 1; + } + + if (unavailableCount === 0) { + this.client.emit(Constants.Events.READY); + this.emittedReady = true; + } + } + } +} + +module.exports = WebSocketManager; diff --git a/src/client/websocket/packets/WebSocketPacketManager.js b/src/client/websocket/packets/WebSocketPacketManager.js new file mode 100644 index 000000000..cb114bad6 --- /dev/null +++ b/src/client/websocket/packets/WebSocketPacketManager.js @@ -0,0 +1,44 @@ +'use strict'; + +const Constants = require('../../../util/Constants'); + +class WebSocketPacketManager { + + constructor(websocketManager) { + this.ws = websocketManager; + this.handlers = {}; + + this.register(Constants.WSEvents.READY, 'Ready'); + this.register(Constants.WSEvents.GUILD_CREATE, 'GuildCreate'); + this.register(Constants.WSEvents.GUILD_DELETE, 'GuildDelete'); + this.register(Constants.WSEvents.GUILD_UPDATE, 'GuildUpdate'); + this.register(Constants.WSEvents.GUILD_BAN_ADD, 'GuildBanAdd'); + this.register(Constants.WSEvents.GUILD_BAN_REMOVE, 'GuildBanRemove'); + this.register(Constants.WSEvents.GUILD_MEMBER_ADD, 'GuildMemberAdd'); + this.register(Constants.WSEvents.GUILD_MEMBER_REMOVE, 'GuildMemberRemove'); + this.register(Constants.WSEvents.GUILD_MEMBER_UPDATE, 'GuildMemberUpdate'); + this.register(Constants.WSEvents.CHANNEL_CREATE, 'ChannelCreate'); + this.register(Constants.WSEvents.CHANNEL_DELETE, 'ChannelDelete'); + this.register(Constants.WSEvents.CHANNEL_UPDATE, 'ChannelUpdate'); + } + + get client() { + return this.ws.client; + } + + register(event, handle) { + let Handler = require(`./handlers/${handle}`); + this.handlers[event] = new Handler(this); + } + + handle(packet) { + if (this.handlers[packet.t]) { + return this.handlers[packet.t].handle(packet); + } + + return false; + } + +} + +module.exports = WebSocketPacketManager; diff --git a/src/client/websocket/packets/handlers/AbstractHandler.js b/src/client/websocket/packets/handlers/AbstractHandler.js new file mode 100644 index 000000000..9ab096dd6 --- /dev/null +++ b/src/client/websocket/packets/handlers/AbstractHandler.js @@ -0,0 +1,14 @@ +'use strict'; + +class AbstractHandler { + + constructor(packetManager) { + this.packetManager = packetManager; + } + + handle(packet) { + + } +} + +module.exports = AbstractHandler; diff --git a/src/client/websocket/packets/handlers/ChannelCreate.js b/src/client/websocket/packets/handlers/ChannelCreate.js new file mode 100644 index 000000000..d938be358 --- /dev/null +++ b/src/client/websocket/packets/handlers/ChannelCreate.js @@ -0,0 +1,28 @@ +'use strict'; + +const AbstractHandler = require('./AbstractHandler'); +const Structure = name => require(`../../../../structures/${name}`); + +const ClientUser = Structure('ClientUser'); +const Guild = Structure('Guild'); +const DMChannel = Structure('DMChannel'); + +const Constants = require('../../../../util/Constants'); + +class ChannelCreateHandler extends AbstractHandler { + + constructor(packetManager) { + super(packetManager); + } + + handle(packet) { + let data = packet.d; + let client = this.packetManager.client; + + let channel = client.store.NewChannel(data); + + } + +}; + +module.exports = ChannelCreateHandler; diff --git a/src/client/websocket/packets/handlers/ChannelDelete.js b/src/client/websocket/packets/handlers/ChannelDelete.js new file mode 100644 index 000000000..e94d052a0 --- /dev/null +++ b/src/client/websocket/packets/handlers/ChannelDelete.js @@ -0,0 +1,31 @@ +'use strict'; + +const AbstractHandler = require('./AbstractHandler'); +const Structure = name => require(`../../../../structures/${name}`); + +const ClientUser = Structure('ClientUser'); +const Guild = Structure('Guild'); +const ServerChannel = Structure('ServerChannel'); + +const Constants = require('../../../../util/Constants'); + +class ChannelDeleteHandler extends AbstractHandler { + + constructor(packetManager) { + super(packetManager); + } + + handle(packet) { + let data = packet.d; + let client = this.packetManager.client; + + let channel = client.store.get('channels', data.id); + + if (channel) { + client.store.KillChannel(channel); + } + } + +}; + +module.exports = ChannelDeleteHandler; diff --git a/src/client/websocket/packets/handlers/ChannelUpdate.js b/src/client/websocket/packets/handlers/ChannelUpdate.js new file mode 100644 index 000000000..d44a15855 --- /dev/null +++ b/src/client/websocket/packets/handlers/ChannelUpdate.js @@ -0,0 +1,33 @@ +'use strict'; + +const AbstractHandler = require('./AbstractHandler'); +const Structure = name => require(`../../../../structures/${name}`); + +const ClientUser = Structure('ClientUser'); +const Guild = Structure('Guild'); +const ServerChannel = Structure('ServerChannel'); + +const Constants = require('../../../../util/Constants'); +const CloneObject = require('../../../../util/CloneObject'); + +class ChannelUpdateHandler extends AbstractHandler { + + constructor(packetManager) { + super(packetManager); + } + + handle(packet) { + let data = packet.d; + let client = this.packetManager.client; + + let channel = client.store.get('channels', data.id); + + if (channel) { + client.store.UpdateChannel(channel, data); + } + + } + +}; + +module.exports = ChannelUpdateHandler; diff --git a/src/client/websocket/packets/handlers/GuildBanAdd.js b/src/client/websocket/packets/handlers/GuildBanAdd.js new file mode 100644 index 000000000..7294b2fca --- /dev/null +++ b/src/client/websocket/packets/handlers/GuildBanAdd.js @@ -0,0 +1,33 @@ +'use strict'; + +// ##untested handler## + +const AbstractHandler = require('./AbstractHandler'); +const Structure = name => require(`../../../../structures/${name}`); + +const ClientUser = Structure('ClientUser'); +const Guild = Structure('Guild'); + +const Constants = require('../../../../util/Constants'); + +class GuildBanAddHandler extends AbstractHandler { + + constructor(packetManager) { + super(packetManager); + } + + handle(packet) { + let data = packet.d; + let client = this.packetManager.client; + + let guild = client.store.get('guilds', data.guild_id); + let user = client.store.get('users', data.user.id); + + if (guild && user) { + client.emit(Constants.Events.GUILD_BAN_ADD, guild, user); + } + } + +}; + +module.exports = GuildBanAddHandler; diff --git a/src/client/websocket/packets/handlers/GuildBanRemove.js b/src/client/websocket/packets/handlers/GuildBanRemove.js new file mode 100644 index 000000000..ec792f1bf --- /dev/null +++ b/src/client/websocket/packets/handlers/GuildBanRemove.js @@ -0,0 +1,33 @@ +'use strict'; + +// ##untested handler## + +const AbstractHandler = require('./AbstractHandler'); +const Structure = name => require(`../../../../structures/${name}`); + +const ClientUser = Structure('ClientUser'); +const Guild = Structure('Guild'); + +const Constants = require('../../../../util/Constants'); + +class GuildBanRemoveHandler extends AbstractHandler { + + constructor(packetManager) { + super(packetManager); + } + + handle(packet) { + let data = packet.d; + let client = this.packetManager.client; + + let guild = client.store.get('guilds', data.guild_id); + let user = client.store.get('users', data.user.id); + + if (guild && user) { + client.emit(Constants.Events.GUILD_BAN_REMOVE, guild, user); + } + } + +}; + +module.exports = GuildBanRemoveHandler; diff --git a/src/client/websocket/packets/handlers/GuildCreate.js b/src/client/websocket/packets/handlers/GuildCreate.js new file mode 100644 index 000000000..96777a79d --- /dev/null +++ b/src/client/websocket/packets/handlers/GuildCreate.js @@ -0,0 +1,38 @@ +'use strict'; + +const AbstractHandler = require('./AbstractHandler'); +const Structure = name => require(`../../../../structures/${name}`); + +const ClientUser = Structure('ClientUser'); +const Guild = Structure('Guild'); + +const Constants = require('../../../../util/Constants'); + +class GuildCreateHandler extends AbstractHandler { + + constructor(packetManager) { + super(packetManager); + } + + handle(packet) { + let data = packet.d; + let client = this.packetManager.client; + + let guild = client.store.get('guilds', data.id); + + if (guild) { + if (!guild.available && !data.unavailable) { + // a newly available guild + guild.setup(data); + this.packetManager.ws.checkIfReady(); + } + } else { + // a new guild + client.store.NewGuild(data); + } + + } + +}; + +module.exports = GuildCreateHandler; diff --git a/src/client/websocket/packets/handlers/GuildDelete.js b/src/client/websocket/packets/handlers/GuildDelete.js new file mode 100644 index 000000000..550628ac0 --- /dev/null +++ b/src/client/websocket/packets/handlers/GuildDelete.js @@ -0,0 +1,41 @@ +'use strict'; + +const AbstractHandler = require('./AbstractHandler'); +const Structure = name => require(`../../../../structures/${name}`); + +const ClientUser = Structure('ClientUser'); +const Guild = Structure('Guild'); + +const Constants = require('../../../../util/Constants'); + +class GuildDeleteHandler extends AbstractHandler { + + constructor(packetManager) { + super(packetManager); + } + + handle(packet) { + let data = packet.d; + let client = this.packetManager.client; + + let guild = client.store.get('guilds', data.id); + + if (guild) { + if (guild.available && data.unavailable) { + // guild is unavailable + guild.available = false; + client.emit(Constants.Events.GUILD_UNAVAILABLE, guild); + } else { + // delete guild + client.store.KillGuild(guild); + } + } else { + // it's not there! :( + client.emit('warn', 'guild deleted but not cached in first place. missed packet?'); + } + + } + +}; + +module.exports = GuildDeleteHandler; diff --git a/src/client/websocket/packets/handlers/GuildMemberAdd.js b/src/client/websocket/packets/handlers/GuildMemberAdd.js new file mode 100644 index 000000000..23a475f96 --- /dev/null +++ b/src/client/websocket/packets/handlers/GuildMemberAdd.js @@ -0,0 +1,32 @@ +'use strict'; + +// ##untested handler## + +const AbstractHandler = require('./AbstractHandler'); +const Structure = name => require(`../../../../structures/${name}`); + +const ClientUser = Structure('ClientUser'); +const Guild = Structure('Guild'); + +const Constants = require('../../../../util/Constants'); + +class GuildMemberAddHandler extends AbstractHandler { + + constructor(packetManager) { + super(packetManager); + } + + handle(packet) { + let data = packet.d; + let client = this.packetManager.client; + + let guild = client.store.get('guilds', data.guild_id); + + if (guild) { + guild._addMember(data); + } + } + +}; + +module.exports = GuildMemberAddHandler; diff --git a/src/client/websocket/packets/handlers/GuildMemberRemove.js b/src/client/websocket/packets/handlers/GuildMemberRemove.js new file mode 100644 index 000000000..53a20fa2a --- /dev/null +++ b/src/client/websocket/packets/handlers/GuildMemberRemove.js @@ -0,0 +1,33 @@ +'use strict'; + +// ##untested handler## + +const AbstractHandler = require('./AbstractHandler'); +const Structure = name => require(`../../../../structures/${name}`); + +const ClientUser = Structure('ClientUser'); +const Guild = Structure('Guild'); + +const Constants = require('../../../../util/Constants'); + +class GuildMemberRemoveHandler extends AbstractHandler { + + constructor(packetManager) { + super(packetManager); + } + + handle(packet) { + let data = packet.d; + let client = this.packetManager.client; + + let guild = client.store.get('guilds', data.guild_id); + let user = client.store.get('users', data.user.id); + + if (guild && user) { + guild._removeMember(user); + } + } + +}; + +module.exports = GuildMemberRemoveHandler; diff --git a/src/client/websocket/packets/handlers/GuildMemberUpdate.js b/src/client/websocket/packets/handlers/GuildMemberUpdate.js new file mode 100644 index 000000000..e54daaa8f --- /dev/null +++ b/src/client/websocket/packets/handlers/GuildMemberUpdate.js @@ -0,0 +1,33 @@ +'use strict'; + +// ##untested handler## + +const AbstractHandler = require('./AbstractHandler'); +const Structure = name => require(`../../../../structures/${name}`); + +const ClientUser = Structure('ClientUser'); +const Guild = Structure('Guild'); + +const Constants = require('../../../../util/Constants'); + +class GuildMemberUpdateHandler extends AbstractHandler { + + constructor(packetManager) { + super(packetManager); + } + + handle(packet) { + let data = packet.d; + let client = this.packetManager.client; + + let guild = client.store.get('guilds', data.guild_id); + let user = client.store.get('users', data.user.id); + + if (guild) { + guild._updateMember(user, data); + } + } + +}; + +module.exports = GuildMemberUpdateHandler; diff --git a/src/client/websocket/packets/handlers/GuildUpdate.js b/src/client/websocket/packets/handlers/GuildUpdate.js new file mode 100644 index 000000000..e74effbfc --- /dev/null +++ b/src/client/websocket/packets/handlers/GuildUpdate.js @@ -0,0 +1,32 @@ +'use strict'; + +const AbstractHandler = require('./AbstractHandler'); +const Structure = name => require(`../../../../structures/${name}`); + +const ClientUser = Structure('ClientUser'); +const Guild = Structure('Guild'); + +const Constants = require('../../../../util/Constants'); +const CloneObject = require('../../../../util/CloneObject'); + +class GuildUpdateHandler extends AbstractHandler { + + constructor(packetManager) { + super(packetManager); + } + + handle(packet) { + let data = packet.d; + let client = this.packetManager.client; + + let guild = client.store.get('guilds', data.id); + + if (guild) { + client.store.UpdateGuild(guild, data); + } + + } + +}; + +module.exports = GuildUpdateHandler; diff --git a/src/client/websocket/packets/handlers/Ready.js b/src/client/websocket/packets/handlers/Ready.js new file mode 100644 index 000000000..cd512a5e8 --- /dev/null +++ b/src/client/websocket/packets/handlers/Ready.js @@ -0,0 +1,36 @@ +'use strict'; + +const AbstractHandler = require('./AbstractHandler'); +const Structure = name => require(`../../../../structures/${name}`); + +const ClientUser = Structure('ClientUser'); +const Guild = Structure('Guild'); +const DMChannel = Structure('DMChannel'); + +class ReadyHandler extends AbstractHandler { + + constructor(packetManager) { + super(packetManager); + } + + handle(packet) { + let data = packet.d; + let client = this.packetManager.client; + + client.manager.setupKeepAlive(data.heartbeat_interval); + + client.store.user = client.store.add('users', new ClientUser(client, data.user)); + + for (let guild of data.guilds) { + client.store.NewGuild(guild); + } + + for (let privateDM of data.private_channels) { + client.store.NewChannel(privateDM); + } + + } + +}; + +module.exports = ReadyHandler; diff --git a/src/index.js b/src/index.js new file mode 100644 index 000000000..1c2103a4c --- /dev/null +++ b/src/index.js @@ -0,0 +1,15 @@ +'use strict'; + +const entries = require('object.entries'); +const values = require('object.values'); +const Client = require('./client/Client'); + +if (!Object.entries) { + entries.shim(); +} + +if (!Object.values) { + values.shim(); +} + +exports.Client = Client; diff --git a/src/structures/Channel.js b/src/structures/Channel.js new file mode 100644 index 000000000..ae9950c33 --- /dev/null +++ b/src/structures/Channel.js @@ -0,0 +1,16 @@ +'use strict'; + +class Channel { + constructor(client, data) { + this.client = client; + if (data) { + this.setup(data); + } + } + + setup(data) { + this.id = data.id; + } +} + +module.exports = Channel; diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js new file mode 100644 index 000000000..19b84d5b8 --- /dev/null +++ b/src/structures/ClientUser.js @@ -0,0 +1,17 @@ +'use strict'; + +const User = require('./User'); + +class ClientUser extends User { + constructor(client, data) { + super(client, data); + } + + setup(data) { + super.setup(data); + this.verified = data.verified; + this.email = data.email; + } +} + +module.exports = ClientUser; diff --git a/src/structures/DMChannel.js b/src/structures/DMChannel.js new file mode 100644 index 000000000..0db181e96 --- /dev/null +++ b/src/structures/DMChannel.js @@ -0,0 +1,17 @@ +'use strict'; + +const Channel = require('./Channel'); +const User = require('./User'); + +class DMChannel extends Channel{ + constructor(client, data) { + super(client, data); + } + + setup(data) { + this.recipient = this.client.store.add('users', new User(this.client, data.recipient)); + this.lastMessageID = data.last_message_id; + } +} + +module.exports = DMChannel; diff --git a/src/structures/Guild.js b/src/structures/Guild.js new file mode 100644 index 000000000..f230ad86b --- /dev/null +++ b/src/structures/Guild.js @@ -0,0 +1,91 @@ +'use strict'; + +const User = require('./User'); +const GuildDataStore = require('./datastore/GuildDataStore'); +const TextChannel = require('./TextChannel'); +const VoiceChannel = require('./VoiceChannel'); +const Constants = require('../Util/Constants'); + +class Guild { + constructor(client, data) { + this.client = client; + this.store = new GuildDataStore(); + + if (!data) { + return; + } + + if (data.unavailable) { + this.available = false; + this.id = data.id; + } else { + this.available = true; + this.setup(data); + } + } + + _addMember(guildUser) { + let user = this.client.store.NewUser(guildUser.user); + this.store.memberData[user.id] = { + deaf: guildUser.deaf, + mute: guildUser.mute, + joinDate: new Date(guildUser.joined_at), + roles: guildUser.roles, + }; + if (this.client.ws.emittedReady) { + this.client.emit(Constants.Events.GUILD_MEMBER_ADD, this, user); + } + } + + _updateMember(currentUser, newData) { + let oldRoles = this.store.memberData[currentUser.id].roles; + this.store.currentUser[currentUser.id].roles = newData.roles; + if (this.client.ws.emittedReady) { + this.client.emit(Constants.Events.GUILD_MEMBER_ROLES_UPDATE, this, oldRoles, this.store.memberData[currentUser.id].roles); + } + } + + _removeMember(guildUser) { + this.store.remove('members', guildUser); + if (this.client.ws.emittedReady) { + this.client.emit(Constants.Events.GUILD_MEMBER_REMOVE, this, guildUser); + } + } + + setup(data) { + this.id = data.id; + this.available = !data.unavailable; + this.splash = data.splash; + this.region = data.region; + this.ownerID = data.owner_id; + this.name = data.name; + this.memberCount = data.member_count; + this.large = data.large; + this.joinDate = new Date(data.joined_at); + this.icon = data.icon; + this.features = data.features; + this.emojis = data.emojis; + this.afkTimeout = data.afk_timeout; + this.afkChannelID = data.afk_channel_id; + this.embedEnabled = data.embed_enabled; + this.embedChannelID = data.embed_channel_id; + this.verificationLevel = data.verification_level; + this.features = data.features || []; + + if (data.members) { + this.store.clear('members'); + for (let guildUser of data.members) { + this._addMember(guildUser); + } + } + + if (data.channels) { + this.store.clear('channels'); + for (let channel of data.channels) { + this.client.store.NewChannel(channel, this); + } + } + } +} + +module.exports = Guild; diff --git a/src/structures/Message.js b/src/structures/Message.js new file mode 100644 index 000000000..30d7fc4ac --- /dev/null +++ b/src/structures/Message.js @@ -0,0 +1,9 @@ +'use strict'; + +class Message { + constructor() { + + } +} + +module.exports = Message; diff --git a/src/structures/ServerChannel.js b/src/structures/ServerChannel.js new file mode 100644 index 000000000..32294b0ab --- /dev/null +++ b/src/structures/ServerChannel.js @@ -0,0 +1,22 @@ +'use strict'; + +const Channel = require('./Channel'); + +class ServerChannel extends Channel{ + constructor(guild, data) { + super(guild.client, data); + this.guild = guild; + } + + setup(data) { + super.setup(data); + this.type = data.type; + this.topic = data.topic; + this.position = data.position; + this.permissionOverwrites = data.permission_overwrites; + this.name = data.name; + this.lastMessageID = data.last_message_id; + } +} + +module.exports = ServerChannel; diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js new file mode 100644 index 000000000..e8b2b6acc --- /dev/null +++ b/src/structures/TextChannel.js @@ -0,0 +1,13 @@ +'use strict'; + +const ServerChannel = require('./ServerChannel'); +const TextChannelDataStore = require('./datastore/TextChannelDataStore'); + +class TextChannel extends ServerChannel { + constructor(guild, data) { + super(guild, data); + this.store = new TextChannelDataStore(); + } +} + +module.exports = TextChannel; diff --git a/src/structures/User.js b/src/structures/User.js new file mode 100644 index 000000000..50487077f --- /dev/null +++ b/src/structures/User.js @@ -0,0 +1,20 @@ +'use strict'; + +class User { + constructor(client, data) { + this.client = client; + if (data) { + this.setup(data); + } + } + + setup(data) { + this.username = data.username; + this.id = data.id; + this.discriminator = data.discriminator; + this.avatar = data.avatar; + this.bot = Boolean(data.bot); + } +} + +module.exports = User; diff --git a/src/structures/VoiceChannel.js b/src/structures/VoiceChannel.js new file mode 100644 index 000000000..7a69e2b19 --- /dev/null +++ b/src/structures/VoiceChannel.js @@ -0,0 +1,16 @@ +'use strict'; + +const ServerChannel = require('./ServerChannel'); + +class VoiceChannel extends ServerChannel { + constructor(guild, data) { + super(guild, data); + } + + setup(data) { + super.setup(data); + this.bitrate = data.bitrate; + } +} + +module.exports = VoiceChannel; diff --git a/src/structures/datastore/AbstractDataStore.js b/src/structures/datastore/AbstractDataStore.js new file mode 100644 index 000000000..24bfec497 --- /dev/null +++ b/src/structures/datastore/AbstractDataStore.js @@ -0,0 +1,43 @@ +'use strict'; + +class AbstractDataStore{ + constructor() { + this.data = {}; + } + + register(name) { + this.data[name] = {}; + } + + add(location, object) { + if (this.data[location][object.id]) { + return this.data[location][object.id]; + } else { + return this.data[location][object.id] = object; + } + } + + clear(location) { + this.data[location] = {}; + } + + remove(location, object) { + let id = (typeof object === 'string' || object instanceof String) ? object : object.id; + if (this.data[location][id]) { + delete this.data[location][id]; + return true; + } else { + return false; + } + } + + get(location, value) { + return this.data[location][value]; + } + + getAsArray(location) { + return Object.values(this.data[location]); + } +} + +module.exports = AbstractDataStore; diff --git a/src/structures/datastore/ClientDataStore.js b/src/structures/datastore/ClientDataStore.js new file mode 100644 index 000000000..6b9a89488 --- /dev/null +++ b/src/structures/datastore/ClientDataStore.js @@ -0,0 +1,111 @@ +'use strict'; + +const AbstractDataStore = require('./AbstractDataStore'); +const Constants = require('../../util/Constants'); +const CloneObject = require('../../util/CloneObject'); +const Guild = require('../Guild'); +const User = require('../User'); +const DMChannel = require('../DMChannel'); +const TextChannel = require('../TextChannel'); +const VoiceChannel = require('../VoiceChannel'); +const ServerChannel = require('../ServerChannel'); + +class ClientDataStore extends AbstractDataStore{ + constructor(client) { + super(); + + this.client = client; + this.token = null; + this.session = null; + this.user = null; + + this.register('users'); + this.register('guilds'); + this.register('channels'); + } + + get pastReady() { + return this.client.ws.emittedReady; + } + + NewGuild(data) { + let already = this.get('guilds', data.id); + let guild = this.add('guilds', new Guild(this.client, data)); + if (this.pastReady && !already) { + this.client.emit(Constants.Events.GUILD_CREATE, guild); + } + + return guild; + } + + NewUser(data) { + return this.add('users', new User(this.client, data)); + } + + NewChannel(data, guild) { + let already = this.get('channels', data.id); + let channel; + if (data.is_private) { + channel = new DMChannel(this.client, data); + }else { + guild = guild || this.get('guilds', data.guild_id); + if (guild) { + if (data.type === 'text') { + channel = new TextChannel(guild, data); + guild.store.add('channels', channel); + }else if (data.type === 'voice') { + channel = new VoiceChannel(guild, data); + guild.store.add('channels', channel); + } + } + } + + if (channel) { + if (this.pastReady && !already) { + this.client.emit(Constants.Events.CHANNEL_CREATE, channel); + } + + return this.add('channels', channel); + } + } + + KillGuild(guild) { + let already = this.get('guilds', guilds.id); + this.remove('guilds', guild); + if (already && this.pastReady) { + this.client.emit(Constants.Events.GUILD_DELETE, guild); + } + } + + KillUser(user) { + this.remove('users', user); + } + + KillChannel(channel) { + let already = this.get('channels', channel.id); + this.remove('channels', channel); + if (channel instanceof ServerChannel) { + channel.guild.store.remove('channels', channel); + } + + if (already && this.pastReady) { + this.client.emit(Constants.Events.CHANNEL_DELETE, channel); + } + } + + UpdateGuild(currentGuild, newData) { + let oldGuild = CloneObject(currentGuild); + currentGuild.setup(newData); + if (this.pastReady) { + this.client.emit(Constants.Events.GUILD_UPDATE, oldGuild, currentGuild); + } + } + + UpdateChannel(currentChannel, newData) { + let oldChannel = CloneObject(currentChannel); + currentChannel.setup(newData); + this.client.emit(Constants.Events.CHANNEL_UPDATE, oldChannel, currentChannel); + } +} + +module.exports = ClientDataStore; diff --git a/src/structures/datastore/GuildDataStore.js b/src/structures/datastore/GuildDataStore.js new file mode 100644 index 000000000..fc541b33a --- /dev/null +++ b/src/structures/datastore/GuildDataStore.js @@ -0,0 +1,16 @@ +'use strict'; + +const AbstractDataStore = require('./AbstractDataStore'); + +class GuildDataStore extends AbstractDataStore{ + constructor() { + super(); + + this.memberData = {}; + + this.register('members'); + this.register('channels'); + } +} + +module.exports = GuildDataStore; diff --git a/src/structures/datastore/TextChannelDataStore.js b/src/structures/datastore/TextChannelDataStore.js new file mode 100644 index 000000000..08316431b --- /dev/null +++ b/src/structures/datastore/TextChannelDataStore.js @@ -0,0 +1,12 @@ +'use strict'; + +const AbstractDataStore = require('./AbstractDataStore'); + +class TextChannelDataStore extends AbstractDataStore{ + constructor() { + super(); + this.register('messages'); + } +} + +module.exports = TextChannelDataStore; diff --git a/src/util/CloneObject.js b/src/util/CloneObject.js new file mode 100644 index 000000000..158f9d189 --- /dev/null +++ b/src/util/CloneObject.js @@ -0,0 +1,7 @@ +'use strict'; +module.exports = function CloneObject(obj) { + var cloned = Object.create(obj); + Object.assign(cloned, obj); + + return cloned; +}; diff --git a/src/util/Constants.js b/src/util/Constants.js new file mode 100644 index 000000000..5adc1636c --- /dev/null +++ b/src/util/Constants.js @@ -0,0 +1,118 @@ +const DefaultOptions = exports.DefaultOptions = { + ws: { + large_threshold: 250, + compress: true, + properties: { + $os: process ? process.platform : 'discord.js', + $browser: 'discord.js', + $device: 'discord.js', + $referrer: '', + $referring_domain: '', + }, + }, + protocol_version: 4, +}; + +const Package = exports.Package = require('../../package.json'); + +const Errors = exports.Errors = { + NO_TOKEN: new Error('request to use token, but token was unavailable to the client'), + NO_BOT_ACCOUNT: new Error('you should ideally be using a bot account!'), + BAD_WS_MESSAGE: new Error('a bad message was received from the websocket - bad compression or not json'), + TOOK_TOO_LONG: new Error('something took too long to do'), +}; + +const API = 'https://discordapp.com/api'; + +const Endpoints = exports.Endpoints = { + // general endpoints + LOGIN: `${API}/auth/login`, + LOGOUT: `${API}/auth/logout`, + ME: `${API}/users/@me`, + ME_GUILD: (guildID) => `${Endpoints.ME}/guilds/${guildID}`, + GATEWAY: `${API}/gateway`, + USER_CHANNELS: (userID) => `${API}/users/${userID}/channels`, + AVATAR: (userID, avatar) => `${API}/users/${userID}/avatars/${avatar}.jpg`, + INVITE: (id) => `${API}/invite/${id}`, + + // guilds + GUILDS: `${API}/guilds`, + GUILD: (guildID) => `${Endpoints.GUILDS}/${guildID}`, + GUILD_ICON: (guildID, hash) => `${Endpoints.GUILD(guildID)}/icons/${hash}.jpg`, + GUILD_PRUNE: (guildID) => `${Endpoints.GUILD(guildID)}/prune`, + GUILD_EMBED: (guildID) => `${Endpoints.GUILD(guildID)}/embed`, + GUILD_INVITES: (guildID) => `${Endpoints.GUILD(guildID)}/invites`, + GUILD_ROLES: (guildID) => `${Endpoints.GUILD(guildID)}/roles`, + GUILD_BANS: (guildID) => `${Endpoints.GUILD(guildID)}/bans`, + GUILD_INTEGRATIONS: (guildID) => `${Endpoints.GUILD(guildID)}/integrations`, + GUILD_MEMBERS: (guildID) => `${Endpoints.GUILD(guildID)}/members`, + GUILD_CHANNELS: (guildID) => `${Endpoints.GUILD(guildID)}/channels`, + + // channels + CHANNELS: `${API}/channels`, + CHANNEL: (channelID) => `${Endpoints.CHANNELS}/${channelID}`, + CHANNEL_MESSAGES: (channelID) => `${Endpoints.CHANNEL(channelID)}/messages`, + CHANNEL_INVITES: (channelID) => `${Endpoints.CHANNEL(channelID)}/invites`, + CHANNEL_TYPING: (channelID) => `${Endpoints.CHANNEL(channelID)}/typing`, + CHANNEL_PERMISSIONS: (channelID) => `${Endpoints.CHANNEL(channelID)}/permissions`, + CHANNEL_MESSAGE: (channelID, messageID) => `${Endpoints.CHANNEL_MESSAGES(channelID)}/${messageID}`, +}; + +const OPCodes = exports.OPCodes = { + DISPATCH: 0, + HEARTBEAT: 1, + IDENTIFY: 2, + STATUS_UPDATE: 3, + VOICE_STATE_UPDATE: 4, + VOICE_GUILD_PING: 5, + RESUME: 6, + RECONNECT: 7, + REQUEST_GUILD_MEMBERS: 8, + INVALID_SESSION: 9, +}; + +const Events = exports.Events = { + READY: 'ready', + GUILD_CREATE: 'guildCreate', + GUILD_DELETE: 'guildDelete', + GUILD_UNAVAILABLE: 'guildUnavailable', + GUILD_AVAILABLE: 'guildAvailable', + GUILD_UPDATE: 'guildUpdate', + GUILD_BAN_ADD: 'guildBanAdd', + GUILD_BAN_REMOVE: 'guildBanRemove', + GUILD_MEMBER_ADD: 'guildMemberAdd', + GUILD_MEMBER_REMOVE: 'guildMemberRemove', + GUILD_MEMBER_ROLES_UPDATE: 'guildMemberRolesUpdate', + CHANNEL_CREATE: 'channelCreate', + CHANNEL_DELETE: 'channelDelete', + CHANNEL_UPDATE: 'channelUpdate', + WARN: 'warn', +}; + +const WSEvents = exports.WSEvents = { + CHANNEL_CREATE: 'CHANNEL_CREATE', + CHANNEL_DELETE: 'CHANNEL_DELETE', + CHANNEL_UPDATE: 'CHANNEL_UPDATE', + MESSAGE_CREATE: 'MESSAGE_CREATE', + MESSAGE_DELETE: 'MESSAGE_DELETE', + MESSAGE_UPDATE: 'MESSAGE_UPDATE', + PRESENCE_UPDATE: 'PRESENCE_UPDATE', + READY: 'READY', + GUILD_BAN_ADD: 'GUILD_BAN_ADD', + GUILD_BAN_REMOVE: 'GUILD_BAN_REMOVE', + GUILD_CREATE: 'GUILD_CREATE', + GUILD_DELETE: 'GUILD_DELETE', + GUILD_MEMBER_ADD: 'GUILD_MEMBER_ADD', + GUILD_MEMBER_REMOVE: 'GUILD_MEMBER_REMOVE', + GUILD_MEMBER_UPDATE: 'GUILD_MEMBER_UPDATE', + GUILD_MEMBERS_CHUNK: 'GUILD_MEMBERS_CHUNK', + GUILD_ROLE_CREATE: 'GUILD_ROLE_CREATE', + GUILD_ROLE_DELETE: 'GUILD_ROLE_DELETE', + GUILD_ROLE_UPDATE: 'GUILD_ROLE_UPDATE', + GUILD_UPDATE: 'GUILD_UPDATE', + TYPING: 'TYPING_START', + USER_UPDATE: 'USER_UPDATE', + VOICE_STATE_UPDATE: 'VOICE_STATE_UPDATE', + FRIEND_ADD: 'RELATIONSHIP_ADD', + FRIEND_REMOVE: 'RELATIONSHIP_REMOVE', +}; diff --git a/src/util/MergeDefault.js b/src/util/MergeDefault.js new file mode 100644 index 000000000..3da11d278 --- /dev/null +++ b/src/util/MergeDefault.js @@ -0,0 +1,19 @@ +'use strict'; + +module.exports = function merge(def, given) { + if (!given) { + return def; + } + + given = given || {}; + + for (var key in def) { + if (!given.hasOwnProperty(key)) { + given[key] = def[key]; + } else if (given[key] === Object(given[key])) { + given[key] = merge(def[key], given[key]); + } + } + + return given; +}; diff --git a/test/random.js b/test/random.js new file mode 100644 index 000000000..300bc25e9 --- /dev/null +++ b/test/random.js @@ -0,0 +1,39 @@ +'use strict'; + +const Discord = require('../'); + +let client = new Discord.Client(); + +client.login(require('./auth.json').token).then(token => console.log('ready!')).catch(console.log); + +client.on('ready', () => { +}); + +client.on('guildCreate', (guild) => { + console.log(guild); +}); +client.on('guildDelete', (guild) => { + console.log(guild); +}); +client.on('guildUpdate', (old, guild) => { + console.log(old.name, guild.name); +}); +client.on('channelCreate', channel => { + console.log(channel); +}); +client.on('channelDelete', channel => { + console.log(channel); +}); + +client.on('channelUpdate', (old, chan) => { + console.log('chan update', old.name, chan.name); +}); + +client.on('guildMemberAdd', (guild, user) => { + console.log('new guild member', user.username, 'in', guild.name); +}); + +client.on('guildMemberRemove', (guild, user) => { + console.log('dead guild member', user.username, 'in', guild.name); +}); +