From 42dc3660fb761767a79b536b96952105ca2aed2a Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Fri, 15 Mar 2024 12:01:56 -0400 Subject: [PATCH 01/11] Add emoji watcher gag --- src/features/april-fools/im-watching-you.ts | 31 +++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/features/april-fools/im-watching-you.ts diff --git a/src/features/april-fools/im-watching-you.ts b/src/features/april-fools/im-watching-you.ts new file mode 100644 index 00000000..fd78bca2 --- /dev/null +++ b/src/features/april-fools/im-watching-you.ts @@ -0,0 +1,31 @@ +import { sleep } from "../../helpers/misc"; +import { ChannelHandlers } from "../../types"; + +const EMOJIS = [ + [0.02, "👀"], + [0.02, "🤔"], + [0.02, "❓"], +] as const; + +export default { + handleMessage: async ({ msg: maybeMessage }) => { + const msg = maybeMessage.partial + ? await maybeMessage.fetch() + : maybeMessage; + + for (const [chance, emoji] of EMOJIS) { + const random = Math.random(); + if (random < chance) { + const resp = await msg.react(emoji); + + // For added chaos, remove the reaction in ~30-60s + const duration = Math.floor(Math.random() * 30) + 30; + sleep(duration).then(() => { + resp.remove(); + }); + + return; + } + } + }, +} as ChannelHandlers; From 9a2cde39fe2858baca1c40beae222fa0ce92f303 Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Fri, 15 Mar 2024 12:04:58 -0400 Subject: [PATCH 02/11] Random message deletion --- src/features/april-fools/message-deletion.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/features/april-fools/message-deletion.ts diff --git a/src/features/april-fools/message-deletion.ts b/src/features/april-fools/message-deletion.ts new file mode 100644 index 00000000..18789db0 --- /dev/null +++ b/src/features/april-fools/message-deletion.ts @@ -0,0 +1,16 @@ +import { ChannelHandlers } from "../../types"; + +const CHANCE_TO_DELETE = 0.02; + +export default { + handleMessage: async ({ msg: maybeMessage }) => { + const msg = maybeMessage.partial + ? await maybeMessage.fetch() + : maybeMessage; + + const random = Math.random(); + if (random < CHANCE_TO_DELETE) { + return msg.delete(); + } + }, +} as ChannelHandlers; From 6f1621901e8c9769ef761aa0e7fe7b3d4cd544c9 Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Fri, 15 Mar 2024 12:09:01 -0400 Subject: [PATCH 03/11] Add typing status gag --- src/features/april-fools/typing-status.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/features/april-fools/typing-status.ts diff --git a/src/features/april-fools/typing-status.ts b/src/features/april-fools/typing-status.ts new file mode 100644 index 00000000..b1bd6c3e --- /dev/null +++ b/src/features/april-fools/typing-status.ts @@ -0,0 +1,19 @@ +import { ChannelHandlers } from "../../types"; + +const CHANCE_TO_TYPE = 0.02; + +export default { + handleMessage: async ({ msg: maybeMessage }) => { + const msg = maybeMessage.partial + ? await maybeMessage.fetch() + : maybeMessage; + + const random = Math.random(); + if (random > CHANCE_TO_TYPE) { + return; + } + + const channel = msg.channel; + await channel.sendTyping(); // This will start typing for 10 seconds (a Discord value, not customizable) + }, +} as ChannelHandlers; From f35861c77adf14b6ef81f43104b56e8f47931a3c Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Fri, 15 Mar 2024 12:18:24 -0400 Subject: [PATCH 04/11] Enforce american spellings --- src/features/april-fools/american-spelling.ts | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/features/april-fools/american-spelling.ts diff --git a/src/features/april-fools/american-spelling.ts b/src/features/april-fools/american-spelling.ts new file mode 100644 index 00000000..a9802308 --- /dev/null +++ b/src/features/april-fools/american-spelling.ts @@ -0,0 +1,59 @@ +import { ChannelHandlers } from "../../types"; + +const AMERICAN_SPELLING_MAP: Record = { + colour: "color", + flavour: "flavor", + honour: "honor", + humour: "humor", + labour: "labor", + neighbour: "neighbor", + rumour: "rumor", + savour: "savor", + valour: "valor", + behaviour: "behavior", + favour: "favor", + harbour: "harbor", + odour: "odor", + armour: "armor", +}; + +const SUPER_SARCASTIC_REPLIES_TO_BRITISH_SPELLING_WORDS = [ + "Oh, splendid, another dose of British charm! But I'm afraid we're strictly American today, darling.", + "Well, well, well, what do we have here? Looks like someone's trying to add a little extra 'u' to our lives. Nice try!", + "Oh, lovely attempt at being fancy! But sorry, old chap, we're sticking to our American roots today.", + "Ah, a bit of British flair, how quaint! But alas, it's not quite the style we're going for today.", + "Oh, bravo! Another British gem shining through. But sorry, it's strictly red, white, and blue today!", + "Well, aren't you just a rebel with your extra letters? Sorry, but we're cutting ties with the 'u' today.", + "Ah, the British invasion continues! But I'm afraid your linguistic imperialism won't fly here today.", + "Oh, delightful! Another nod to our friends across the pond. But sorry, we're staying true to our American roots today.", + "Splendid attempt at British sophistication! But sorry, we're keeping it simple and straightforward today.", + "Oh, look at you being all fancy with your British spellings! But I'm afraid it's strictly Yankee Doodle Dandy around here today.", +]; + +export default { + handleMessage: async ({ msg: maybeMessage }) => { + const msg = maybeMessage.partial + ? await maybeMessage.fetch() + : maybeMessage; + + const content = msg.content.toLowerCase(); + const hasBadWord = Object.keys(AMERICAN_SPELLING_MAP).find((word) => + content.match(new RegExp(`\\b${word}\\b`, "i")), + ); + + if (!hasBadWord) { + return; + } + + await msg.delete(); + + msg.channel.send( + SUPER_SARCASTIC_REPLIES_TO_BRITISH_SPELLING_WORDS[ + Math.floor( + Math.random() * + SUPER_SARCASTIC_REPLIES_TO_BRITISH_SPELLING_WORDS.length, + ) + ], + ); + }, +} as ChannelHandlers; From c3fb3aeef8ee470acb6c5746a269d65d55289fee Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Fri, 15 Mar 2024 13:49:50 -0400 Subject: [PATCH 05/11] chaos community vote --- .../april-fools/community-timeout-vote.ts | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 src/features/april-fools/community-timeout-vote.ts diff --git a/src/features/april-fools/community-timeout-vote.ts b/src/features/april-fools/community-timeout-vote.ts new file mode 100644 index 00000000..5c7525b6 --- /dev/null +++ b/src/features/april-fools/community-timeout-vote.ts @@ -0,0 +1,168 @@ +import { GuildMember, MessageReaction, TextChannel, User } from "discord.js"; +import { ChannelHandlers } from "../../types"; +import { sleep } from "../../helpers/misc"; + +const RECENT_CHATTERS = new Set(); + +let timedOutUserId: string | null = null; +let isProcessingPlea = false; +let isWorthy = true; +let dissenter: GuildMember | null = null; +let conformer: GuildMember | null = null; + +const getRandomReactorId = ( + reactions: MessageReaction, + botId: string, +): User | null => { + const users = reactions.users.cache; + const usersArray = Array.from(users.values()); + const randomUser = usersArray[Math.floor(Math.random() * usersArray.length)]; + + if (usersArray.length === 1 && randomUser.id === botId) { + return null; + } + + while (randomUser.id === botId) { + console.log("Trying to get a random user again"); + return getRandomReactorId(reactions, botId); + } + + return randomUser; +}; + +/** + * This feature allows the community to vote on whether a user should be timed out. + * If the user gets timed out, they have to write a pleading message in a certain channel and the AI gods have to deem them worthy enough to be untimedout. + * If they are deemed worthy, it randomly times one of the community voters out for an hour and they can't plead. + */ +export default { + handleMessage: async ({ msg: maybeMessage, bot }) => { + const msg = maybeMessage.partial + ? await maybeMessage.fetch() + : maybeMessage; + + if (msg.author.bot) { + return; + } + + if (timedOutUserId === msg.author.id) { + if (isProcessingPlea || !isWorthy) { + await msg.delete(); + return; + } + + isProcessingPlea = true; + await msg.reply("Let's see if the AI gods deem you worthy...."); + await sleep(3 + Math.floor(Math.random() * 7)); + isProcessingPlea = false; + isWorthy = Math.random() > 0.5; + + if (isWorthy) { + await msg.reply( + "The AI gods have deemed you worthy. You are free to go.", + ); + timedOutUserId = null; + + if (conformer) { + try { + await conformer.timeout(60 * 60 * 1000); + } catch (error) { + console.error(error); + } + const channel = msg.channel as TextChannel; + await channel.send(`Chaos reigns upon <@${conformer.id}> instead.`); + } + return; + } + + await msg.reply( + "The AI gods have deemed you unworthy. You will remain timed out.", + ); + + sleep(10 * 60).then(() => { + // If the person is still timed out, remove the timeout + // Otherwise, somebody else has been timed out so we just let it go + if (timedOutUserId === msg.author.id) { + timedOutUserId = null; + } + }); + + return; + } + + RECENT_CHATTERS.add(msg.author.id); + + if (RECENT_CHATTERS.size > 10) { + // Get a random user from recent chatters + const userIds = Array.from(RECENT_CHATTERS); + const randomUser = userIds[Math.floor(Math.random() * userIds.length)]; + const user = await msg.client.users.fetch(randomUser); + if (!user) return; + + const channel = msg.channel as TextChannel; + const message = await channel.send( + `Chaos is here. React with 👍 to time out <@${user.id}> or 👎 to let them live.`, + ); + + const filter = (reaction: MessageReaction, user: User) => + user.id !== message.author.id && user.id !== bot.user?.id; + + const collector = message.createReactionCollector({ + filter, + time: 1000 * 60, // 1 minute + }); + + await message.react("👍"); + await message.react("👎"); + + collector.on("end", async () => { + const yesReactions = message.reactions.cache.find( + (reaction) => reaction.emoji.name === "👍", + ); + const noReactions = message.reactions.cache.find( + (reaction) => reaction.emoji.name === "👎", + ); + + if (!yesReactions || !noReactions) { + return; + } + + const randomConformerId = bot?.user?.id + ? getRandomReactorId(yesReactions, bot.user.id) + : null; + conformer = randomConformerId + ? await message.guild?.members.fetch(randomConformerId) + : null; + + const randomDissenterId = bot?.user?.id + ? getRandomReactorId(noReactions, bot.user.id) + : null; + dissenter = randomDissenterId + ? await message.guild?.members.fetch(randomDissenterId) + : null; + + if ( + yesReactions?.count && + yesReactions.count > (noReactions?.count ?? 0) + ) { + await channel.send( + `The community has spoken. <@${user.id}> has been timed out. <@${user.id}>, your next message is your one attempt to plead your case. If the AI gods deem you worthy, you will be spared and chaos will reign on somebody else.`, + ); + + timedOutUserId = user.id; + } else { + await channel.send( + `The community has spared <@${user.id}>. Do not let this happen again.`, + ); + + if (dissenter) { + await dissenter.timeout(15 * 60 * 1000); + await channel.send(`Chaos reigns upon <@${dissenter.id}> instead.`); + } + } + }); + + RECENT_CHATTERS.clear(); + } + }, +} as ChannelHandlers; From 5582a583b7d264be6b54edd5491346d892bf1b2f Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Sat, 16 Mar 2024 11:10:56 -0400 Subject: [PATCH 06/11] init commands --- src/index.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/index.ts b/src/index.ts index e62278ce..2c783820 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,6 +29,11 @@ import { scheduleTask } from "./helpers/schedule"; import { discordToken } from "./helpers/env"; import { registerCommand, deployCommands } from "./helpers/deploy-commands"; import resumeReviewPdf from "./features/resume-review"; +import imWatchingYou from "./features/april-fools/im-watching-you"; +import messageDeletion from "./features/april-fools/message-deletion"; +import typingStatus from "./features/april-fools/typing-status"; +import americanSpelling from "./features/april-fools/american-spelling"; +import communityTimeoutVote from "./features/april-fools/community-timeout-vote"; export const bot = new discord.Client({ intents: [ @@ -185,6 +190,13 @@ const threadChannels = [CHANNELS.helpJs, CHANNELS.helpThreadsReact]; addHandler(threadChannels, autothread); addHandler(CHANNELS.resumeReview, resumeReviewPdf); +addHandler(CHANNELS.random, [ + imWatchingYou, + messageDeletion, + typingStatus, + americanSpelling, + communityTimeoutVote +]); bot.on("ready", () => { deployCommands(bot); From 146aec9b1ed87fd2b1c42ff7c82e204bfb129e78 Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Sat, 16 Mar 2024 11:12:49 -0400 Subject: [PATCH 07/11] remove message deletion --- src/features/april-fools/message-deletion.ts | 16 ---------------- src/index.ts | 2 -- 2 files changed, 18 deletions(-) delete mode 100644 src/features/april-fools/message-deletion.ts diff --git a/src/features/april-fools/message-deletion.ts b/src/features/april-fools/message-deletion.ts deleted file mode 100644 index 18789db0..00000000 --- a/src/features/april-fools/message-deletion.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ChannelHandlers } from "../../types"; - -const CHANCE_TO_DELETE = 0.02; - -export default { - handleMessage: async ({ msg: maybeMessage }) => { - const msg = maybeMessage.partial - ? await maybeMessage.fetch() - : maybeMessage; - - const random = Math.random(); - if (random < CHANCE_TO_DELETE) { - return msg.delete(); - } - }, -} as ChannelHandlers; diff --git a/src/index.ts b/src/index.ts index 2c783820..fd564242 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,7 +30,6 @@ import { discordToken } from "./helpers/env"; import { registerCommand, deployCommands } from "./helpers/deploy-commands"; import resumeReviewPdf from "./features/resume-review"; import imWatchingYou from "./features/april-fools/im-watching-you"; -import messageDeletion from "./features/april-fools/message-deletion"; import typingStatus from "./features/april-fools/typing-status"; import americanSpelling from "./features/april-fools/american-spelling"; import communityTimeoutVote from "./features/april-fools/community-timeout-vote"; @@ -192,7 +191,6 @@ addHandler(threadChannels, autothread); addHandler(CHANNELS.resumeReview, resumeReviewPdf); addHandler(CHANNELS.random, [ imWatchingYou, - messageDeletion, typingStatus, americanSpelling, communityTimeoutVote From 87579a60f74e407eb454e1db26675c10a7b4f1f8 Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Sat, 16 Mar 2024 11:14:19 -0400 Subject: [PATCH 08/11] reply then delete --- src/features/april-fools/american-spelling.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/features/april-fools/american-spelling.ts b/src/features/april-fools/american-spelling.ts index a9802308..9e981258 100644 --- a/src/features/april-fools/american-spelling.ts +++ b/src/features/april-fools/american-spelling.ts @@ -45,9 +45,7 @@ export default { return; } - await msg.delete(); - - msg.channel.send( + await msg.reply( SUPER_SARCASTIC_REPLIES_TO_BRITISH_SPELLING_WORDS[ Math.floor( Math.random() * @@ -55,5 +53,7 @@ export default { ) ], ); + + await msg.delete(); }, } as ChannelHandlers; From 7e18d013e76d37bc456685fafd16b181c0f3f6b8 Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Sat, 16 Mar 2024 11:15:56 -0400 Subject: [PATCH 09/11] extract consts --- src/features/april-fools/community-timeout-vote.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/features/april-fools/community-timeout-vote.ts b/src/features/april-fools/community-timeout-vote.ts index 5c7525b6..800a2a86 100644 --- a/src/features/april-fools/community-timeout-vote.ts +++ b/src/features/april-fools/community-timeout-vote.ts @@ -10,6 +10,10 @@ let isWorthy = true; let dissenter: GuildMember | null = null; let conformer: GuildMember | null = null; +const TIMEOUT_DURATION_MINS = 10; +const VOTING_DURATION_MINS = 2; +const NUM_RECENT_CHATTERS_TO_TRIGGER_CHAOS = 10; + const getRandomReactorId = ( reactions: MessageReaction, botId: string, @@ -65,7 +69,7 @@ export default { if (conformer) { try { - await conformer.timeout(60 * 60 * 1000); + await conformer.timeout(TIMEOUT_DURATION_MINS * 60 * 1000); } catch (error) { console.error(error); } @@ -79,7 +83,7 @@ export default { "The AI gods have deemed you unworthy. You will remain timed out.", ); - sleep(10 * 60).then(() => { + sleep(TIMEOUT_DURATION_MINS * 60).then(() => { // If the person is still timed out, remove the timeout // Otherwise, somebody else has been timed out so we just let it go if (timedOutUserId === msg.author.id) { @@ -92,7 +96,7 @@ export default { RECENT_CHATTERS.add(msg.author.id); - if (RECENT_CHATTERS.size > 10) { + if (RECENT_CHATTERS.size >= NUM_RECENT_CHATTERS_TO_TRIGGER_CHAOS) { // Get a random user from recent chatters const userIds = Array.from(RECENT_CHATTERS); const randomUser = userIds[Math.floor(Math.random() * userIds.length)]; @@ -109,7 +113,7 @@ export default { const collector = message.createReactionCollector({ filter, - time: 1000 * 60, // 1 minute + time: 1000 * 60 * VOTING_DURATION_MINS, }); await message.react("👍"); @@ -156,7 +160,7 @@ export default { ); if (dissenter) { - await dissenter.timeout(15 * 60 * 1000); + await dissenter.timeout(TIMEOUT_DURATION_MINS * 60 * 1000); await channel.send(`Chaos reigns upon <@${dissenter.id}> instead.`); } } From b64bff947182b6511d44c64a4ff937260489a415 Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Sat, 16 Mar 2024 11:51:21 -0400 Subject: [PATCH 10/11] add timeout --- src/features/april-fools/community-timeout-vote.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/features/april-fools/community-timeout-vote.ts b/src/features/april-fools/community-timeout-vote.ts index 800a2a86..dfe24012 100644 --- a/src/features/april-fools/community-timeout-vote.ts +++ b/src/features/april-fools/community-timeout-vote.ts @@ -83,6 +83,10 @@ export default { "The AI gods have deemed you unworthy. You will remain timed out.", ); + await msg.guild?.members.cache + .get(timedOutUserId) + ?.timeout(TIMEOUT_DURATION_MINS * 60 * 1000); + sleep(TIMEOUT_DURATION_MINS * 60).then(() => { // If the person is still timed out, remove the timeout // Otherwise, somebody else has been timed out so we just let it go From 2cce361df144b6c6c61624b66e86978dd4f91fcf Mon Sep 17 00:00:00 2001 From: Dylan Garcia Date: Sat, 16 Mar 2024 13:01:41 -0400 Subject: [PATCH 11/11] prettier --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index fd564242..1236b8a1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -193,7 +193,7 @@ addHandler(CHANNELS.random, [ imWatchingYou, typingStatus, americanSpelling, - communityTimeoutVote + communityTimeoutVote, ]); bot.on("ready", () => {