├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── package.json ├── src ├── RedisInterface.js └── index.js └── test └── index.js /.eslintignore: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "plugins": [ 4 | "import" 5 | ], 6 | "rules": { 7 | "no-underscore-dangle": 0 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 28 | node_modules 29 | 30 | # Environment 31 | .env 32 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | - "7" 5 | services: 6 | - redis-server 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2017, Will Nelson 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted, provided that the above 6 | copyright notice and this permission notice appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # discord.js-redis 2 | 3 | [![Build Status](https://travis-ci.org/spec-tacles/discord.js-redis.svg?branch=master)](https://travis-ci.org/spec-tacles/discord.js-redis) 4 | 5 | Integrates Discord.js caching with Redis. Stores users, guilds, channels, messages, and emojis in a hash set of IDs (keys are the plural of the type: e.g. `users`, `messages`, etc.). You can subscribe to channels named `[type]Set` and `[type]Delete` which will contain a payload of the resource ID. 6 | 7 | ## Example 8 | ```js 9 | const discord = require('discord.js'); 10 | const redis = require('discord.js-redis')({ 11 | host: '1.3.3.7' // these options can be found on redis.js.org 12 | }); 13 | 14 | const client = new discord.Client(); 15 | redis(client); 16 | 17 | client.login('token'); 18 | ``` 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discord.js-redis", 3 | "version": "0.1.0", 4 | "description": "Connects Discord.js with Redis.", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/some-plebs/discord.js-redis.git" 12 | }, 13 | "author": "Will Nelson ", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/some-plebs/discord.js-redis/issues" 17 | }, 18 | "homepage": "https://github.com/some-plebs/discord.js-redis#readme", 19 | "devDependencies": { 20 | "dotenv": "^4.0.0", 21 | "eslint": "^3.19.0", 22 | "eslint-config-airbnb-base": "^11.1.3", 23 | "eslint-plugin-import": "^2.2.0", 24 | "mocha": "^3.3.0" 25 | }, 26 | "dependencies": { 27 | "discord.js": "11.1.0", 28 | "redis": "^2.7.1", 29 | "tsubaki": "^1.1.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/RedisInterface.js: -------------------------------------------------------------------------------- 1 | const redis = require('redis'); 2 | const tsubaki = require('tsubaki'); 3 | 4 | tsubaki.promisifyAll(redis.RedisClient.prototype); 5 | tsubaki.promisifyAll(redis.Multi.prototype); 6 | 7 | module.exports = class RedisInterface { 8 | constructor(options = {}) { 9 | this.client = redis.createClient(options); 10 | } 11 | 12 | init(client) { 13 | const q = this.client.multi(); 14 | 15 | client.users.forEach(u => q.sadd('users', u.id)); 16 | client.guilds.forEach(g => q.sadd('guilds', g.id)); 17 | client.emojis.forEach(e => q.sadd('emojis', e.id)); 18 | client.channels.forEach(c => q.sadd('channels', c.id)); 19 | 20 | return this.client.flushallAsync().then(() => q.execAsync()); 21 | } 22 | 23 | addMember(member) { 24 | return this.client.sismemberAsync('users', member.id).then((is) => { 25 | if (is) return Promise.resolve(); 26 | return this.addUser(member.user); 27 | }); 28 | } 29 | 30 | addChannel(channel) { 31 | return this._addData('channels', channel.id); 32 | } 33 | 34 | removeChannel(channel) { 35 | return this._removeData('channels', channel.id); 36 | } 37 | 38 | addUser(user) { 39 | return this._addData('users', user.id); 40 | } 41 | 42 | removeUser(user) { 43 | return this._removeData('users', user.id); 44 | } 45 | 46 | addGuild(guild) { 47 | return this._addData('guilds', guild.id); 48 | } 49 | 50 | removeGuild(guild) { 51 | return this._removeData('guilds', guild.id); 52 | } 53 | 54 | addEmoji(emoji) { 55 | return this._addData('emojis', emoji.id); 56 | } 57 | 58 | removeEmoji(emoji) { 59 | return this._removeData('emojis', emoji.id); 60 | } 61 | 62 | addMessage(message) { 63 | return this._addData('messages', `${message.channel.id}:${message.id}`).then((res) => { 64 | const cache = message.client.options.messageCacheLifetime; 65 | if (cache) setTimeout(() => this.removeMessage(message), cache); 66 | return res; 67 | }); 68 | } 69 | 70 | removeMessage(message) { 71 | return this._removeData('messages', `${message.channel.id}:${message.id}`); 72 | } 73 | 74 | _addData(type, id) { 75 | return Promise.all([ 76 | this.client.saddAsync(type, id), 77 | this.client.publishAsync(`${type}Add`, id), 78 | ]); 79 | } 80 | 81 | _removeData(type, id) { 82 | return Promise.all([ 83 | this.client.sremAsync(type, id), 84 | this.client.publishAsync(`${type}Remove`, id), 85 | ]); 86 | } 87 | 88 | static clean(obj) { 89 | const out = {}; 90 | Object.keys(obj).forEach((key) => { 91 | if (!(obj[key] instanceof Object) && obj[key] !== null && typeof obj[key] !== 'undefined') out[key] = `${obj[key]}`; 92 | }); 93 | return out; 94 | } 95 | }; 96 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events'); 2 | const RedisInterface = require('./RedisInterface'); 3 | 4 | class RedisClient extends EventEmitter { 5 | constructor(client, options) { 6 | super(); 7 | 8 | this.discordClient = client; 9 | this.options = options; 10 | 11 | this.ready = false; 12 | this.on('ready', () => { this.ready = true; }); 13 | 14 | this.interface = new RedisInterface(this.options); 15 | this.client = this.interface.client; 16 | this.initialize(); 17 | } 18 | 19 | initialize() { 20 | const c = this.discordClient; 21 | 22 | if (c.readyTimestamp) this._ready(); 23 | else c.once('ready', this._ready.bind(this)); 24 | 25 | c.on('message', m => this.interface.addMessage(m)); 26 | c.on('messageDelete', m => this.interface.removeMessage(m)); 27 | c.on('messageDeleteBulk', (messages) => { 28 | const q = this.client.multi(); 29 | messages.forEach(m => q.sremAsync('messages', m.id)); 30 | return q.execAsync(); 31 | }); 32 | 33 | c.on('emojiCreate', e => this.interface.addEmoji(e)); 34 | c.on('emojiDelete', e => this.interface.removeEmoji(e)); 35 | 36 | c.on('channelCreate', ch => this.interface.addChannel(ch)); 37 | c.on('channelDelete', ch => this.interface.removeChannel(ch)); 38 | 39 | c.on('guildCreate', g => this.interface.addGuild(g)); 40 | c.on('guildDelete', g => this.interface.removeGuild(g)); 41 | 42 | c.on('guildMemberAdd', m => this.interface.addMember(m)); 43 | } 44 | 45 | _ready() { 46 | this.interface.init(this.discordClient).then(() => this.emit('ready', this)); 47 | } 48 | } 49 | 50 | module.exports = { RedisClient, RedisInterface }; 51 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | require('dotenv').config({ path: path.resolve(__dirname, '.env') }); 3 | 4 | const assert = require('assert'); 5 | const dRedis = require('../src/index.js'); 6 | const discord = require('discord.js'); 7 | 8 | let redis; 9 | let redisClient; 10 | let discordClient; 11 | 12 | describe('data storage', function() { 13 | const connectErrorListener = e => { throw e; }; 14 | before('initializes the redis interface', function() { 15 | redisClient = new dRedis.RedisClient(new discord.Client()); 16 | discordClient = redisClient.discordClient; 17 | redis = redisClient.client; 18 | redis.once('error', connectErrorListener); 19 | }); 20 | 21 | before('logs in to Discord', function() { 22 | return discordClient.login(process.env.DISCORD_TOKEN); 23 | }); 24 | 25 | before('connects to redis', function(done) { 26 | if(redisClient.ready) handleEnd() 27 | else redisClient.once('ready', handleEnd); 28 | 29 | function handleEnd() { 30 | redis.removeListener('error', connectErrorListener); 31 | done(); 32 | } 33 | }); 34 | 35 | it('contains all users', function() { 36 | return redis.smembersAsync('users').then(list => { 37 | assert.deepEqual(list.sort(), discordClient.users.map(u => u.id).sort()); 38 | }); 39 | }); 40 | 41 | it('contains all guilds', function() { 42 | return redis.smembersAsync('guilds').then(list => { 43 | assert.deepEqual(list.sort(), discordClient.guilds.map(g => g.id).sort()); 44 | }); 45 | }); 46 | 47 | it('contains all channels', function() { 48 | return redis.smembersAsync('channels').then(list => { 49 | assert.deepEqual(list.sort(), discordClient.channels.map(c => c.id).sort()); 50 | }); 51 | }); 52 | 53 | it('contains all emojis', function() { 54 | return redis.smembersAsync('emojis').then(list => { 55 | assert.deepEqual(list.sort(), discordClient.emojis.map(e => e.id).sort()); 56 | }); 57 | }); 58 | }); 59 | --------------------------------------------------------------------------------