diff --git a/config.example.ts b/config.example.ts index 99a6951..d7a301c 100644 --- a/config.example.ts +++ b/config.example.ts @@ -1,6 +1,6 @@ export const config = { 'name': 'Sweeper Bot', // Name of the bot - 'version': '0.1.0', // Version of the bot + 'version': '0.2.0', // Version of the bot 'token': 'the_bot_token', // Discord API Token for this bot 'localtoken': 'local_testing_token', // Discord API Token for a secondary OPTIONAL testing bot, THIS MUST BE DIFFERENT FROM "token" 'prefix': 's!', // Prefix for all commands @@ -16,6 +16,7 @@ export const config = { 'reportChannel': 0n, // Discord channel ID where reports will be sent when using the built-in report command 'devServer': 0n, // Discord guild ID where testing of indev features/commands will be handled, used in conjuction with the DEVMODE bool in mod.ts 'ownerId': 0n, // Discord user ID of the bot owner + 'pollChannels': [], // List of Discord channel IDs that are to be managed by the pollReaction system }; export default config; diff --git a/flags.ts b/flags.ts index 94bf935..939f73a 100644 --- a/flags.ts +++ b/flags.ts @@ -3,4 +3,4 @@ export const DEVMODE = false; // DEBUG is used to toggle the cmdPrompt export const DEBUG = false; // LOCALMODE is used to run a differnt bot token for local testing -export const LOCALMODE = true; +export const LOCALMODE = false; diff --git a/src/events.ts b/src/events.ts index 9361475..eaa9b49 100644 --- a/src/events.ts +++ b/src/events.ts @@ -11,6 +11,7 @@ events.ready = eventHandlers.ready; events.guildCreate = eventHandlers.guildCreate; events.guildDelete = eventHandlers.guildDelete; events.messageCreate = eventHandlers.messageCreate; +events.messageUpdate = eventHandlers.messageUpdate; if (DEVMODE) { events.debug = eventHandlers.debug; diff --git a/src/events/_index.ts b/src/events/_index.ts index 2bdf3db..9b5bb3b 100644 --- a/src/events/_index.ts +++ b/src/events/_index.ts @@ -3,6 +3,7 @@ import { guildCreate } from './guildCreate.ts'; import { guildDelete } from './guildDelete.ts'; import { debug } from './debug.ts'; import { messageCreate } from './messageCreate.ts'; +import { messageUpdate } from './messageUpdate.ts'; export default { ready, @@ -10,4 +11,5 @@ export default { guildDelete, debug, messageCreate, + messageUpdate, }; diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index f027bb3..9275a31 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -10,6 +10,7 @@ import { Message, } from '../../deps.ts'; import commands from '../commands/_index.ts'; +import functions from '../functions/_index.ts'; import utils from '../utils.ts'; export const messageCreate = (bot: Bot, message: Message) => { @@ -23,6 +24,10 @@ export const messageCreate = (bot: Bot, message: Message) => { commands.handleMentions(bot, message); } + if (config.pollChannels.includes(message.channelId)) { + functions.pollReactions(bot, message); + } + // return as we are done handling this command return; } diff --git a/src/events/messageUpdate.ts b/src/events/messageUpdate.ts new file mode 100644 index 0000000..3ada013 --- /dev/null +++ b/src/events/messageUpdate.ts @@ -0,0 +1,23 @@ +import config from '../../config.ts'; +import { + // Discordeno deps + Bot, + // Discordeno deps + Message, +} from '../../deps.ts'; +import functions from '../functions/_index.ts'; + +export const messageUpdate = (bot: Bot, message: Message) => { + // Ignore all other bots + if (message.isFromBot) return; + + // Ignore all messages that are not commands + if (message.content.indexOf(config.prefix) !== 0) { + if (config.pollChannels.includes(message.channelId)) { + functions.pollReactions(bot, message, true); + } + + // return as we are done handling this command + return; + } +}; diff --git a/src/functions/_index.ts b/src/functions/_index.ts index ff8b4c5..b375b2d 100644 --- a/src/functions/_index.ts +++ b/src/functions/_index.ts @@ -1 +1,5 @@ -export default {}; +import { pollReactions } from "./pollReactions.ts"; + +export default { + pollReactions, +}; diff --git a/src/functions/pollReactions.ts b/src/functions/pollReactions.ts new file mode 100644 index 0000000..eb11096 --- /dev/null +++ b/src/functions/pollReactions.ts @@ -0,0 +1,42 @@ +import { + // Discordeno deps + Bot, + // Discordeno deps + Message, +} from '../../deps.ts'; +import utils from '../utils.ts'; + +export const pollReactions = async (bot: Bot, message: Message, update = false) => { + if (message.content.toLowerCase().includes('clan poll')) { + // Emoji RegExp + const unicodeEmojis = '(\\p{Emoji_Presentation}|\\p{Extended_Pictographic})'; + const unicodeEmojiRX = `(${unicodeEmojis}(\u200d${unicodeEmojis})*)`; + const discordEmojiRX = '(:[a-zA-Z\\d_]+:\\d+)'; + const allEmojiRX = new RegExp(`${unicodeEmojiRX}|${discordEmojiRX}`, 'gu'); + + // Get list of emojis in message + const allEmojis = message.content.match(allEmojiRX) || []; + + // If message was edited + if (update) { + // Get message to get reactions from it + const pollMsg = await bot.helpers.getMessage(message.channelId, message.id).catch((e: Error) => utils.commonLoggers.messageGetError('pollReactions.ts:23', message, e)); + + // If there are reactions, determine if we need to remove any + if (pollMsg?.reactions?.length) { + for (const reaction of pollMsg.reactions) { + if (reaction.emoji.name) { + // Make emoji name that matches our allEmojis array format + const emojiName = reaction.emoji.id ? `:${reaction.emoji.name}:${reaction.emoji.id}` : reaction.emoji.name; + if (!allEmojis.includes(emojiName)) { + bot.helpers.deleteReaction(message.channelId, message.id, emojiName).catch((e: Error) => utils.commonLoggers.reactionDeleteError('pollReactions.ts:32', message, e, emojiName)); + } + } + } + } + } + + // Finally, add all reactions to the message + bot.helpers.addReactions(message.channelId, message.id, allEmojis, true).catch((e: Error) => utils.commonLoggers.reactionAddError('pollReactions.ts:40', message, e, allEmojis.toString())); + } +}; diff --git a/src/utils.ts b/src/utils.ts index 9e06718..27197b6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -19,18 +19,27 @@ const jsonStringifyBig = (input: any) => { const genericLogger = (level: LT, message: string) => log(level, message); const messageEditError = (location: string, message: Message | string, err: Error) => genericLogger(LT.ERROR, `${location} | Failed to edit message: ${jsonStringifyBig(message)} | Error: ${err.name} - ${err.message}`); + const messageGetError = (location: string, message: Message | string, err: Error) => + genericLogger(LT.ERROR, `${location} | Failed to get message: ${jsonStringifyBig(message)} | Error: ${err.name} - ${err.message}`); const messageSendError = (location: string, message: Message | string, err: Error) => genericLogger(LT.ERROR, `${location} | Failed to send message: ${jsonStringifyBig(message)} | Error: ${err.name} - ${err.message}`); const messageDeleteError = (location: string, message: Message | string, err: Error) => genericLogger(LT.ERROR, `${location} | Failed to delete message: ${jsonStringifyBig(message)} | Error: ${err.name} - ${err.message}`); +const reactionAddError = (location: string, message: Message | string, err: Error, emoji: string) => + genericLogger(LT.ERROR, `${location} | Failed to add emoji (${emoji}) to message: ${jsonStringifyBig(message)} | Error: ${err.name} - ${err.message}`); +const reactionDeleteError = (location: string, message: Message | string, err: Error, emoji: string) => + genericLogger(LT.ERROR, `${location} | Failed to delete emoji (${emoji}) from message: ${jsonStringifyBig(message)} | Error: ${err.name} - ${err.message}`); const dbError = (location: string, type: string, err: Error) => genericLogger(LT.ERROR, `${location} | Failed to ${type} database | Error: ${err.name} - ${err.message}`); export default { commonLoggers: { dbError, + messageGetError, messageEditError, messageSendError, messageDeleteError, + reactionAddError, + reactionDeleteError, }, jsonStringifyBig, };