├── reaction-role.json ├── index.js ├── src ├── Util.js ├── ReactionRole.js └── Manager.js ├── package.json └── README.md /reaction-role.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/Manager.js') -------------------------------------------------------------------------------- /src/Util.js: -------------------------------------------------------------------------------- 1 | const ReactionRoleManager = { 2 | storage : './reaction-role.json', 3 | } 4 | module.exports = { 5 | ReactionRoleManager 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discord-reaction-role", 3 | "version": "2.1.0", 4 | "description": "", 5 | "main": "index.js", 6 | "dependencies": { 7 | "discord.js": "^12.2.0", 8 | "easy-json-database": "^1.1.0", 9 | "events": "^3.1.0", 10 | "merge-options": "^2.0.0" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/Shadowv7/discord-reaction-role.git" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/Shadowv7/discord-reaction-role/issues" 18 | }, 19 | "devDependencies": {}, 20 | "scripts": { 21 | "test": "echo \"Error: no test specified\" && exit 1" 22 | }, 23 | "author": "", 24 | "license": "ISC" 25 | } 26 | -------------------------------------------------------------------------------- /src/ReactionRole.js: -------------------------------------------------------------------------------- 1 | const { EventEmitter } = require('events'); 2 | 3 | class ReactionRole extends EventEmitter { 4 | constructor(manager, options) { 5 | super(); 6 | 7 | this.client = manager.client; 8 | 9 | this.manager = manager; 10 | 11 | this.messageID = options.messageID; 12 | 13 | this.channelID = options.channelID; 14 | 15 | this.guildID = options.guildID; 16 | 17 | this.reaction = options.reaction; 18 | 19 | this.roleID = options.roleID; 20 | 21 | this.options = options; 22 | } 23 | get data(){ 24 | const baseData = { 25 | messageID: this.messageID, 26 | channelID: this.channelID, 27 | guildID: this.guildID, 28 | reaction: this.reaction, 29 | roleID: this.roleID 30 | }; 31 | if(this.options.messageID) baseData.messageID = this.options.messageID; 32 | if (this.options.channelID) baseData.channelID = this.options.channelID; 33 | if (this.options.guildID) baseData.guildID = this.options.guildID; 34 | if (this.options.reaction) baseData.reaction = this.options.reaction; 35 | if (this.options.roleID) baseData.roleID = this.options.roleID; 36 | return baseData; 37 | } 38 | } 39 | module.exports = ReactionRole; 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Discord Reaction Role 2 | [![downloadsBadge](https://img.shields.io/npm/dt/discord-reaction-role?style=for-the-badge)](https://npmjs.com/discord-reaction-role) 3 | [![versionBadge](https://img.shields.io/npm/v/discord-reaction-role?style=for-the-badge)](https://npmjs.com/discord-reaction-role) 4 | 5 | Discord Reaction Role is a powerful [Node.js](https://nodejs.org) module that allows you to easily create reactions roles ! 6 | 7 | ## Installation 8 | 9 | ``` 10 | npm i discord-reaction-role 11 | ``` 12 | 13 | ## Exemple 14 | 15 | ### Lunch of the module 16 | 17 | ```js 18 | const Discord = require("discord.js"), 19 | client = new Discord.Client(), 20 | settings = { 21 | prefix: "r!", 22 | token: "Your Discord Token" 23 | }; 24 | 25 | // Requires Manager from discord-reaction-role 26 | const ReactionRoleManager = require("discord-reaction-role"); 27 | // Starts updating currents reaction roles 28 | const manager = new ReactionRoleManager(client, { 29 | storage: "./reaction-role.json" 30 | }); 31 | // We now have a reactionRoleManager property to access the manager everywhere! 32 | client.reactionRoleManager = manager; 33 | 34 | client.on("ready", () => { 35 | console.log("I'm ready !"); 36 | }); 37 | 38 | client.login(settings.token); 39 | ``` 40 | 41 | * **client**: the discord client (your discord bot instance) 42 | * **options.storage**: the json file that will be used to store reaction roles 43 | 44 | ### Start 45 | 46 | ```js 47 | client.reactionRoleManager.create({ 48 | messageID: '706857963188387903', 49 | channel: message.channel, 50 | reaction: '✅', 51 | role: message.guild.roles.cache.get('675995543062839306') 52 | }) 53 | ``` 54 | 55 | ### Delete 56 | 57 | ```js 58 | client.reactionRoleManager.delete({ 59 | messageID: "707532556223905802", 60 | reaction: "✅", 61 | }); 62 | ``` 63 | 64 | ### Fetch the reaction role 65 | 66 | ```js 67 | // The list of all the reaction roles 68 | let allReactionRoles = client.reactionRoleManager.reactionRole; // [ {ReactionRole}, {ReactionRole} ] 69 | 70 | let onServer = client.reactionRoleManager.reactionRole.filter((rr) => rr.guildID === "1909282092"); 71 | ``` 72 | ## Events 73 | 74 | ### reactionRoleAdded 75 | 76 | ```js 77 | client.reactionRoleManager.on('reactionRoleAdded',(reactionRole,member,role,reaction) => { 78 | console.log(`${member.user.username} added his reaction \`${reaction}\` and won the role : ${role.name}`); 79 | }) 80 | ``` 81 | 82 | ### reactionRoleRemoved 83 | ```js 84 | client.reactionRoleManager.on("reactionRoleRemoved", (reactionRole, member, role, reaction) => { 85 | console.log(`${member.user.username} removed his reaction \`${reaction}\` and lost the role : ${role.name}`) 86 | }); 87 | ``` 88 | # Custom database 89 | An example with quick.db 90 | ```js 91 | const Discord = require('discord.js'); 92 | const ReactionRolesManager = require("./index"); 93 | const client = new Discord.Client(); 94 | 95 | const settings = { 96 | prefix: 'r!', 97 | token: 'Your bot token' 98 | }; 99 | 100 | const db = require("quick.db"); 101 | if (!db.get("reaction-role")) db.set("reaction-role", []); 102 | 103 | const reactionRoleManager = class extends ReactionRolesManager { 104 | async getAllReactionRoles() { 105 | return db.get("reaction-role"); 106 | } 107 | 108 | async saveReactionRole(messageID, reactionRoleData) { 109 | db.push("reaction-role", reactionRoleData); 110 | return true; 111 | } 112 | 113 | async deleteReactionRole(messageID,reaction){ 114 | const array = db.get("reaction-role").filter((r) => r.messageID !== messageID || r.reaction !== reaction) 115 | 116 | db.set("reaction-role", array) 117 | 118 | return true; 119 | } 120 | }; 121 | 122 | client.reactionRoleManager = new reactionRoleManager(client,{ 123 | storage: false 124 | }) 125 | 126 | client.login(settings.token); 127 | ``` 128 | # Credits 129 | 130 | Thanks to [Androz2091](https://github.com/Androz2091) for helping me on this project. 131 | -------------------------------------------------------------------------------- /src/Manager.js: -------------------------------------------------------------------------------- 1 | const { EventEmitter } = require('events'); 2 | const mergeOptions = require('merge-options'); 3 | const { writeFile, readFile, exists } = require('fs'); 4 | const { promisify } = require('util'); 5 | const writeFileAsync = promisify(writeFile); 6 | const existsAsync = promisify(exists); 7 | const readFileAsync = promisify(readFile); 8 | 9 | const { ReactionRoleManager } = require('./Util'); 10 | const ReactionRole = require('./ReactionRole') 11 | 12 | class ReactionRolesManager extends EventEmitter { 13 | constructor(client, options) { 14 | super(); 15 | 16 | this.client = client; 17 | 18 | this.options = mergeOptions(ReactionRoleManager, options); 19 | 20 | this.reactionRole = []; 21 | 22 | this.client.on("raw", async (packet) => { 23 | if ( 24 | !["MESSAGE_REACTION_ADD", "MESSAGE_REACTION_REMOVE"].includes(packet.t) 25 | ) 26 | return; 27 | 28 | if (this.reactionRole.some((g) => g.messageID === packet.d.message_id)) { 29 | const reactionRoleData = this.reactionRole.find( 30 | (g) => g.messageID === packet.d.message_id && g.reaction === packet.d.emoji.name || g.messageID === packet.d.message_id && g.reaction === packet.d.emoji.id) 31 | const reaction_role = new ReactionRole(this, reactionRoleData); 32 | const guild = this.client.guilds.cache.get(packet.d.guild_id); 33 | if (!guild) return; 34 | const role = guild.roles.cache.get(reaction_role.roleID); 35 | if (!role) return; 36 | const member = 37 | guild.members.cache.get(packet.d.user_id) || 38 | guild.members.fetch(packet.d.user_id); 39 | if (!member) return; 40 | const channel = guild.channels.cache.get(packet.d.channel_id); 41 | if (!channel) return; 42 | const message = 43 | channel.messages.cache.get(packet.d.message_id) || 44 | (await channel.messages.fetch(packet.d.message_id)); 45 | if (!message) return; 46 | if (packet.d.emoji.name !== reaction_role.reaction && packet.d.emoji.id !== reaction_role.reaction) return; 47 | const reaction = message.reactions.cache.get(reaction_role.reaction); 48 | if (!reaction) return; 49 | if (packet.t === "MESSAGE_REACTION_ADD") { 50 | member.roles.add(role); 51 | this.emit( 52 | "reactionRoleAdded", 53 | reaction_role, 54 | member, 55 | role, 56 | reaction_role.reaction 57 | ); 58 | 59 | } else { 60 | member.roles.remove(role); 61 | this.emit( 62 | "reactionRoleRemoved", 63 | reaction_role, 64 | member, 65 | role, 66 | reaction_role.reaction 67 | ); 68 | 69 | } 70 | } 71 | }); 72 | 73 | this._init(); 74 | } 75 | 76 | create(options = {}) { 77 | return new Promise(async (resolve, reject) => { 78 | if (!this.ready) { 79 | return reject("The manager is not ready yet."); 80 | } 81 | if (!options.channel) { 82 | return reject( 83 | `channel is not a valid guildchannel. (val=${options.channel})` 84 | ); 85 | } 86 | if (!options.reaction) { 87 | return reject( 88 | `options.reaction is not a string. (val=${options.reaction})` 89 | ); 90 | } 91 | if (!options.messageID) { 92 | return reject( 93 | `options.messageID is not a string. (val=${options.messageID})` 94 | ); 95 | } 96 | if (!options.role) { 97 | return reject( 98 | `options.role is not a valid guildrole. (val=${options.role})` 99 | ); 100 | } 101 | if ( 102 | this.reactionRole.some( 103 | (g) => 104 | g.messageID === options.messageID && g.reaction === options.reaction 105 | ) 106 | ) { 107 | return reject(`you can't set 2 reaction roles with 1 emoji`); 108 | } 109 | let reactionrole = new ReactionRole(this, { 110 | messageID: options.messageID, 111 | channelID: options.channel.id, 112 | guildID: options.channel.guild.id, 113 | roleID: options.role.id, 114 | reaction: options.reaction, 115 | }); 116 | this.client.channels.cache 117 | .get(options.channel.id) 118 | .messages.fetch(options.messageID) 119 | .then((msg) => { 120 | msg.react(options.reaction); 121 | }); 122 | this.reactionRole.push(reactionrole.data); 123 | this.saveReactionRole(options.messageID, this.reactionRole); 124 | resolve(reactionrole); 125 | }); 126 | } 127 | 128 | delete(options = {}){ 129 | return new Promise(async (resolve, reject) => { 130 | if (!options.reaction) { 131 | return reject( 132 | `options.reaction is not a string. (val=${options.reaction})` 133 | ); 134 | } 135 | if (!options.messageID) { 136 | return reject( 137 | `options.messageID is not a string. (val=${options.messageID})` 138 | ); 139 | } 140 | this.deleteReactionRole(options.messageID,options.reaction) 141 | resolve() 142 | }) 143 | 144 | } 145 | 146 | async refreshStorage() { 147 | return true; 148 | } 149 | 150 | async getAllReactionRoles() { 151 | let storageExists = await existsAsync(this.options.storage); 152 | if (!storageExists) { 153 | await writeFileAsync(this.options.storage, "[]", "utf-8"); 154 | return []; 155 | } else { 156 | let storageContent = await readFileAsync(this.options.storage); 157 | try { 158 | let giveaways = await JSON.parse(storageContent); 159 | if (Array.isArray(giveaways)) { 160 | return giveaways; 161 | } else { 162 | console.log(storageContent, giveaways); 163 | throw new SyntaxError("The storage file is not properly formatted."); 164 | } 165 | } catch (e) { 166 | if (e.message === "Unexpected end of JSON input") { 167 | throw new SyntaxError( 168 | "The storage file is not properly formatted.", 169 | e 170 | ); 171 | } else { 172 | throw e; 173 | } 174 | } 175 | } 176 | } 177 | 178 | async saveReactionRole(_messageID, _reactionRoleData) { 179 | await writeFileAsync( 180 | this.options.storage, 181 | JSON.stringify(this.reactionRole), 182 | "utf-8" 183 | ); 184 | this.refreshStorage(); 185 | return; 186 | } 187 | 188 | async deleteReactionRole(messageID, reaction) { 189 | try { 190 | const rr = this.reactionRole.filter( 191 | (r) => r.messageID === messageID && r.reaction === reaction 192 | ); 193 | const channel = rr[0].channelID 194 | console.log(channel) 195 | this.client.channels.cache.get(channel).messages.fetch(messageID).then((msg) => { 196 | msg.reactions.cache.get(reaction).remove() 197 | }) 198 | this.reactionRole = this.reactionRole.filter( 199 | (rr) => rr.reaction !== reaction || rr.messageID !== messageID 200 | ); 201 | await writeFileAsync( 202 | this.options.storage, 203 | JSON.stringify(this.reactionRole), 204 | "utf-8" 205 | ); 206 | this.refreshStorage(); 207 | return; 208 | } catch (e){ 209 | return console.log(`Error : ${e}`) 210 | } 211 | } 212 | 213 | async _init() { 214 | this.reactionRole = await this.getAllReactionRoles(); 215 | this.ready = true; 216 | } 217 | } 218 | module.exports = ReactionRolesManager; 219 | --------------------------------------------------------------------------------