diff --git a/.env.example b/.env.example index 09a78dd..e7cf911 100644 --- a/.env.example +++ b/.env.example @@ -19,5 +19,11 @@ DISCORD_PATREON_CHANNEL = # Discord role ID to add to users when they first join DISCORD_MEMBER_ROLE = +# Discord channel IDs which should be checked for crossposting. Comma separated +DISCORD_CROSSPOST_CHECK_CHANNELS = + +# (OPTIONAL) Number of historical messages to check for crossposts. Default: 10 +# DISCORD_CROSSPOST_HISTORY_MESSAGES = 10 + # Sensitivity for the command trigger - defaults to 0.5 SIMILARITY_SENSITIVITY = diff --git a/modules/crosspost/index.js b/modules/crosspost/index.js new file mode 100644 index 0000000..8b12d43 --- /dev/null +++ b/modules/crosspost/index.js @@ -0,0 +1,38 @@ +const { MessageEmbed } = require('discord.js'); +const store = require("./store"); + +module.exports = function (client) { + client.on('message', async message => { + // Ignore bots, DMs, etc + if (!message.author || !message.channel || !message.guild) return; + + // Ensure we have crosspost checks enabled for this channel before doing anything + if (!store.channelEnabled(message.channel)) return; + + // Check to make sure the message is longer than 50 characters so that small messages like ok or thanks don't get caught + if (message.content.length < 50) return; + + // Check if the user has already recently posted a very similar message + let previousMessage = store.findMatch(message); + + if (previousMessage) { + // Remove the message from the cache so a double-warning isn't possible + store.removeMessage(previousMessage); + // Reply with warning message + await message.reply( + new MessageEmbed() + .setTitle(`<:crosspost:999440431521742928> It looks like you already posted that...`) + .setColor(`#94df03`) + .setURL(`https://discord.com/channels/${previousMessage.guild.id}/${previousMessage.channel.id}/${previousMessage.id}`) + .setDescription( + `<@${message.author.id}> sent the same message to <#${previousMessage.channel.id}> . + + It's best to ask your question in just a single channel otherwise it can cause confusion between those trying to help!` + ) + ); + } else { + // Else, log the message in case the user crossposts it in future + store.addMessage(message); + } + }); +}; diff --git a/modules/crosspost/store.js b/modules/crosspost/store.js new file mode 100644 index 0000000..65854ee --- /dev/null +++ b/modules/crosspost/store.js @@ -0,0 +1,38 @@ +let recentMessages = []; + +crosspostMessageStore = { + channelEnabled(channel) { + // If anything is empty or undefined then return false + if (!process.env.DISCORD_CROSSPOST_CHECK_CHANNELS || !channel) return false; + return process.env.DISCORD_CROSSPOST_CHECK_CHANNELS.split(",").includes(channel.id); + }, + + addMessage(message) { + recentMessages.push(message); + // Ensure we have the right amount of messages still in the cache + module.exports.cleanup(); + }, + + cleanup() { + recentMessages = recentMessages.slice(0 - parseInt(process.env.DISCORD_CROSSPOST_HISTORY_MESSAGES ?? 10)); + }, + + removeMessage(message) { + recentMessages = recentMessages.filter(m => !m.equals(message)); + }, + + findMatch(message) { + for (const possibleMatch of recentMessages) { + // Ensure match has the same author + if (!possibleMatch.author || !possibleMatch.author.equals(message.author)) continue; + // Ensure both messages have a non-empty content + if (!possibleMatch.content || !message.content || possibleMatch.content === "" || message.content === "") continue; + // Ensure they are in different channels + if (!possibleMatch.channel || !message.channel || message.channel.equals(possibleMatch.channel)) continue; + // If the content is the same, then this is a match - return it + if (possibleMatch.content.trim() === message.content.trim()) return possibleMatch; + } + } +}; + +module.exports = crosspostMessageStore;