From 4e3d5504658d10b94524d43236cae05b26d8e97c Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Thu, 26 Dec 2019 17:13:56 -0500 Subject: [PATCH 1/9] Add emoji moderation support --- features/emojiMod.js | 165 +++++++++++++++++++++++++++++++++++++++++++ index.js | 23 ++++-- 2 files changed, 184 insertions(+), 4 deletions(-) create mode 100644 features/emojiMod.js diff --git a/features/emojiMod.js b/features/emojiMod.js new file mode 100644 index 00000000..3bf0c6ea --- /dev/null +++ b/features/emojiMod.js @@ -0,0 +1,165 @@ +const staffRoles = ["mvp", "moderator", "admin", "admins"]; + +const isStaff = member => + (member.roles || []).some(role => + staffRoles.includes(role.name.toLowerCase()) + ); + +const config = { + // This is how many ️️warning reactions a post must get until it's considered an official warning + warningThreshold: 1, + // This is how many ️️warning reactions a post must get until mods are alerted + thumbsDownThreshold: 2, + // This is how many ️️warning reactions a post must get the message is deleted + deletionThreshold: Infinity +}; + +const warningMessages = {}; + +const thumbsDownEmojis = ["👎", "👎🏻", "👎🏼", "👎🏽", "👎🏾", "👎🏿"]; + +const reactionHandlers = { + "⚠️": (bot, reaction, message, member) => { + if (!isStaff(member)) { + return; + } + const usersWhoReacted = reaction.users.map(user => + message.guild.members.get(user.id) + ); + const numberOfTotalReactions = usersWhoReacted.length; + const numberOfStaffReactions = usersWhoReacted.filter(isStaff).length; + + const modLogChannel = bot.channels.find( + channel => + channel.name === "mod-log" || channel.id === "257930126145224704" + ); + + const userNames = usersWhoReacted + .filter(user => !isStaff(user)) + .map(member => member.user.username) + .join(", "); + + const staffNames = usersWhoReacted + .filter(isStaff) + .map(member => member.user.username) + .join(", "); + + let logMessage = ""; + const logMessageEnding = [ + "\n\n", + `\`${message.content}\``, + "\n\n", + `Link: https://discordapp.com/channels/${message.guild.id}/${message.channel.id}/${message.id}`, + "\n\n", + userNames && `Reactors: \`${userNames}\``, + staffNames && userNames && "\n", + staffNames && `Staff: \`${staffNames}\`` + ] + .filter(Boolean) + .join(""); + + if (numberOfTotalReactions >= config.warningThreshold) { + logMessage = `<@${message.author.id}> has met the warning threshold in <#${message.channel.id}> for the message:`; + } + + if (numberOfStaffReactions >= config.deletionThreshold) { + logMessage = `\`<@${message.author.id}> has met the deletion threshold in <#${message.channel.id}> for the message:`; + + message.delete(); + } + + if (logMessage) { + logMessage += logMessageEnding; + + if (warningMessages[message.id]) { + warningMessages[message.id].edit(logMessage); + } else { + modLogChannel.send(logMessage).then(warningMessage => { + warningMessages[message.id] = warningMessage; + }); + } + } + }, + "👎": (bot, reaction, message) => { + const reactions = thumbsDownEmojis.reduce( + (acc, emoji) => { + if (message.reactions.get(emoji)) { + acc.count += message.reactions.get(emoji).count; + + // todo: figure out how to do this + // acc.users.push(Object.values(message.reactions.get(emoji).users)); + } + + return acc; + }, + { + count: 0, + users: [] + } + ); + + const numberOfTotalReactions = reactions.count; + + const modLogChannel = bot.channels.find( + channel => channel.name === "mod-log" + ); + + let logMessage = ""; + const logMessageEnding = [ + "\n\n", + `\`${message.content}\``, + "\n\n", + `Link: https://discordapp.com/channels/${message.guild.id}/${message.channel.id}/${message.id}` + ] + .filter(Boolean) + .join(""); + + if (numberOfTotalReactions >= config.thumbsDownThreshold) { + logMessage = `@moderator - <@${message.author.id}> has met the warning threshold in <#${message.channel.id}> for the message:`; + } + + if (logMessage) { + logMessage += logMessageEnding; + + if (warningMessages[message.id]) { + warningMessages[message.id].edit(logMessage); + } else { + modLogChannel.send(logMessage).then(warningMessage => { + warningMessages[message.id] = warningMessage; + }); + } + } + } +}; + +const emojiMod = bot => ({ + handleReaction: ({ reaction, user }) => { + const { message } = reaction; + const { member } = message; + if (user.id === bot.user.id) { + return; + } + if (message.author.id === bot.user.id) { + return; + } + let emoji = reaction.emoji.toString(); + + if (thumbsDownEmojis.includes(emoji)) { + emoji = "👎"; + } + + const reactionHandler = reactionHandlers[emoji]; + if (reactionHandler) { + reactionHandler( + bot, + reaction, + message, + message.guild.members.get(user.id) + ); + } + } +}); + +module.exports = { + default: emojiMod +}; diff --git a/index.js b/index.js index 53b7367e..4c3d74b5 100644 --- a/index.js +++ b/index.js @@ -12,8 +12,9 @@ const autoban = require("./features/autoban").default; const commands = require("./features/commands").default; const witInvite = require("./features/wit-invite").default; const stats = require("./features/stats").default; +const emojiMod = require("./features/emojiMod").default; -const bot = new discord.Client(); +const bot = new discord.Client({ partials: ["MESSAGE", "CHANNEL"] }); bot.login(process.env.DISCORD_HASH); const channelHandlers = { @@ -94,8 +95,17 @@ channelHandlers.addHandler("479862475047567361", qna); // #general channelHandlers.addHandler("*", commands); // channelHandlers.addHandler('*', codeblock); channelHandlers.addHandler("*", autoban); +channelHandlers.addHandler("*", emojiMod(bot)); + +bot.on("messageReactionAdd", async (reaction, user) => { + if (reaction.message.partial) { + try { + await reaction.message.fetch(); + } catch (error) { + console.log("Something went wrong when fetching the message: ", error); + } + } -bot.on("messageReactionAdd", (reaction, user) => { channelHandlers.handleReaction(reaction, user); }); @@ -105,10 +115,15 @@ bot.on("message", msg => { }); logger.log("INI", "Bootstrap complete"); + bot.on("ready", () => { - logger.log("INI", "Bot connected to Discord server"); - bot.user.setActivity('for !commands', { type: "WATCHING" }); + Array.from(bot.guilds.values()).forEach(guild => { + logger.log("INI", `Bot connected to Discord server: ${guild.name}`); + }); + + bot.user.setActivity("for !commands", { type: "WATCHING" }); }); + bot.on("error", err => { try { logger.log("ERR", err.message); From 66d1a769ab2b8f295a4ac2204753e6d3408b7ebb Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Thu, 26 Dec 2019 17:14:06 -0500 Subject: [PATCH 2/9] Update discordjs library --- package-lock.json | 80 +++++++++++++++++++++++++++++------------------ package.json | 2 +- 2 files changed, 50 insertions(+), 32 deletions(-) diff --git a/package-lock.json b/package-lock.json index fd883a1f..c12db9a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,11 +4,24 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@discordjs/collection": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.3.tgz", + "integrity": "sha512-4ek19SmNcPI92942RkuBrZrBK8hg7nG+ae/skkNNDeOaUG+XvxTPkv/jPZVgXwVPDkU5EFsewsI+0n4dTwFvgA==" + }, "abab": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==" }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, "acorn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.5.tgz", @@ -170,15 +183,24 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "discord.js": { - "version": "11.4.2", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-11.4.2.tgz", - "integrity": "sha512-MDwpu0lMFTjqomijDl1Ed9miMQe6kB4ifKdP28QZllmLv/HVOJXhatRgjS8urp/wBlOfx+qAYSXcdI5cKGYsfg==", - "requires": { - "long": "^4.0.0", - "prism-media": "^0.0.3", - "snekfetch": "^3.6.4", - "tweetnacl": "^1.0.0", - "ws": "^4.0.0" + "version": "github:discordjs/discord.js#50ed3293a57aeb0f9c094f00d49ed9874dc5d87a", + "from": "github:discordjs/discord.js", + "requires": { + "@discordjs/collection": "^0.1.1", + "abort-controller": "^3.0.0", + "form-data": "^2.3.3", + "node-fetch": "^2.3.0", + "prism-media": "^1.0.0", + "setimmediate": "^1.0.5", + "tweetnacl": "^1.0.1", + "ws": "^7.2.0" + }, + "dependencies": { + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + } } }, "domexception": { @@ -230,6 +252,11 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -460,11 +487,6 @@ "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, "mime-db": { "version": "1.37.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", @@ -558,9 +580,9 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" }, "prism-media": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-0.0.3.tgz", - "integrity": "sha512-c9KkNifSMU/iXT8FFTaBwBMr+rdVcN+H/uNv1o+CuFeTThNZNTOrQ+RgXA1yL/DeLk098duAeRPP3QNPNbhxYQ==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.1.0.tgz", + "integrity": "sha512-W+oxjRyjtd7hw3pefNZuc7YEZ6VICORJvVNfCPs0+7CsJ43CqMjGAYGjPL3hQ82vw03EVra+CiX4zisqOBUUGw==" }, "psl": { "version": "1.1.31", @@ -670,10 +692,10 @@ "xmlchars": "^1.3.1" } }, - "snekfetch": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/snekfetch/-/snekfetch-3.6.4.tgz", - "integrity": "sha512-NjxjITIj04Ffqid5lqr7XdgwM7X61c/Dns073Ly170bPQHLm6jkmelye/eglS++1nfTWktpP6Y2bFXjdPlQqdw==" + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "source-map": { "version": "0.6.1", @@ -745,9 +767,9 @@ } }, "tweetnacl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.0.tgz", - "integrity": "sha1-cT2LgY2kIGh0C/aDhtBHnmb8ins=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.1.tgz", + "integrity": "sha512-kcoMoKTPYnoeS50tzoqjPY3Uv9axeuuFAZY9M/9zFnhoVvRfxz9K29IMPD7jGmt2c8SW7i3gT9WqDl2+nV7p4A==" }, "type-check": { "version": "0.3.2", @@ -837,13 +859,9 @@ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" }, "ws": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-4.1.0.tgz", - "integrity": "sha512-ZGh/8kF9rrRNffkLFV4AzhvooEclrOH0xaugmqGsIfFgOE/pIz4fMc4Ef+5HSQqTEug2S9JZIWDR47duDSLfaA==", - "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0" - } + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.1.tgz", + "integrity": "sha512-sucePNSafamSKoOqoNfBd8V0StlkzJKL2ZAhGQinCfNQ+oacw+Pk7lcdAElecBF2VkLNZRiIb5Oi1Q5lVUVt2A==" }, "xml-name-validator": { "version": "3.0.0", diff --git a/package.json b/package.json index 50f00dd1..f8a961d1 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "author": "", "license": "ISC", "dependencies": { - "discord.js": "^11.4.2", + "discord.js": "github:discordjs/discord.js", "dotenv": "^6.1.0", "fuse.js": "^3.3.0", "gists": "^2.0.0", From 37379927b8bc0388b143115e0644c6b85529aad0 Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Thu, 26 Dec 2019 17:14:19 -0500 Subject: [PATCH 3/9] Whitelist staff so Sonic stops complaining --- features/jobs.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/features/jobs.js b/features/jobs.js index 1d53a2e1..3ab041da 100644 --- a/features/jobs.js +++ b/features/jobs.js @@ -1,5 +1,10 @@ const cooldown = require("./cooldown").default; +const staffRoles = ["mvp", "moderator", "admin", "admins"]; + +const isStaff = member => + member.roles.some(role => staffRoles.includes(role.name.toLowerCase())); + const jobs = { tags: ["forhire", "for hire", "hiring", "remote", "local"], handleMessage: ({ msg, user }) => { @@ -9,6 +14,9 @@ const jobs = { }); if (!hasTags && msg.mentions.members.array().length === 0) { if (cooldown.hasCooldown(msg.author.id, "user.jobs")) return; + if (isStaff(msg.guild.members.get(msg.author.id))) { + return; + } cooldown.addCooldown(msg.author.id, "user.jobs"); msg.author @@ -21,7 +29,7 @@ I noticed that you've not added any tags - please consider adding some of the fo [INTERN] - this is an intern position, no experience required [REMOTE] - only remote work is possible [LOCAL] - only local work is possible (please remember to provide the country / city!) -[VISA] - Your company will help with the visa process in case of successfull hire +[VISA] - Your company will help with the visa process in case of successful hire Thank you :) From 5e1a77ae88e5543a35bd1ce909f87f1ede5a43ce Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Sat, 28 Dec 2019 17:23:38 -0500 Subject: [PATCH 4/9] Address Carl's CR --- features/emojiMod.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/emojiMod.js b/features/emojiMod.js index 3bf0c6ea..b06b9594 100644 --- a/features/emojiMod.js +++ b/features/emojiMod.js @@ -63,7 +63,7 @@ const reactionHandlers = { } if (numberOfStaffReactions >= config.deletionThreshold) { - logMessage = `\`<@${message.author.id}> has met the deletion threshold in <#${message.channel.id}> for the message:`; + logMessage = `<@${message.author.id}> has met the deletion threshold in <#${message.channel.id}> for the message:`; message.delete(); } From 0b69de321dd21e734b68f5ab5385658d39bd45ff Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Sat, 28 Dec 2019 21:31:46 -0500 Subject: [PATCH 5/9] Add 30 second cooldown for thumsdown emoji --- features/emojiMod.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/features/emojiMod.js b/features/emojiMod.js index b06b9594..84c089de 100644 --- a/features/emojiMod.js +++ b/features/emojiMod.js @@ -1,3 +1,5 @@ +const cooldown = require("./cooldown").default; + const staffRoles = ["mvp", "moderator", "admin", "admins"]; const isStaff = member => @@ -80,7 +82,12 @@ const reactionHandlers = { } } }, - "👎": (bot, reaction, message) => { + "👎": (bot, reaction, message, member) => { + if (cooldown.hasCooldown(member.id, "thumbsdown")) { + return; + } + cooldown.addCooldown(member.id, "thumbsdown"); + const reactions = thumbsDownEmojis.reduce( (acc, emoji) => { if (message.reactions.get(emoji)) { From b01a1d0fcfbee3db8013602fffee5b594a8f4a83 Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Sat, 28 Dec 2019 21:32:38 -0500 Subject: [PATCH 6/9] Change cooldown to 20 minutes --- features/emojiMod.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/emojiMod.js b/features/emojiMod.js index 84c089de..09ea9472 100644 --- a/features/emojiMod.js +++ b/features/emojiMod.js @@ -86,7 +86,7 @@ const reactionHandlers = { if (cooldown.hasCooldown(member.id, "thumbsdown")) { return; } - cooldown.addCooldown(member.id, "thumbsdown"); + cooldown.addCooldown(member.id, "thumbsdown", 20 * 60); const reactions = thumbsDownEmojis.reduce( (acc, emoji) => { From 70051c4f548090fb9172c0101ac7c8f21f5e1761 Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Sat, 28 Dec 2019 21:42:01 -0500 Subject: [PATCH 7/9] Change cooldown to 1 minute and disable for staff --- features/emojiMod.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/features/emojiMod.js b/features/emojiMod.js index 09ea9472..7adf0a28 100644 --- a/features/emojiMod.js +++ b/features/emojiMod.js @@ -83,10 +83,14 @@ const reactionHandlers = { } }, "👎": (bot, reaction, message, member) => { + if (isStaff(member)) { + return; + } + if (cooldown.hasCooldown(member.id, "thumbsdown")) { return; } - cooldown.addCooldown(member.id, "thumbsdown", 20 * 60); + cooldown.addCooldown(member.id, "thumbsdown", 60); // 1 minute const reactions = thumbsDownEmojis.reduce( (acc, emoji) => { From 4d18fee152a83347eb2cca5a555dbc70f4924e98 Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Sat, 28 Dec 2019 21:44:37 -0500 Subject: [PATCH 8/9] Remove staff guard --- features/emojiMod.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/features/emojiMod.js b/features/emojiMod.js index 7adf0a28..4031c6b2 100644 --- a/features/emojiMod.js +++ b/features/emojiMod.js @@ -83,10 +83,6 @@ const reactionHandlers = { } }, "👎": (bot, reaction, message, member) => { - if (isStaff(member)) { - return; - } - if (cooldown.hasCooldown(member.id, "thumbsdown")) { return; } From f2110d7fc5f5744265c65f27d7d600af76321278 Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Sat, 28 Dec 2019 21:50:40 -0500 Subject: [PATCH 9/9] Fix moderator tag --- features/emojiMod.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/emojiMod.js b/features/emojiMod.js index 4031c6b2..b4085ad6 100644 --- a/features/emojiMod.js +++ b/features/emojiMod.js @@ -122,7 +122,7 @@ const reactionHandlers = { .join(""); if (numberOfTotalReactions >= config.thumbsDownThreshold) { - logMessage = `@moderator - <@${message.author.id}> has met the warning threshold in <#${message.channel.id}> for the message:`; + logMessage = `<@&102870499406647296> - <@${message.author.id}> has met the warning threshold in <#${message.channel.id}> for the message:`; } if (logMessage) {