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 @@
+
+
+ 
+
+
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);
+});
+