Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nothing to see here #359

Merged
merged 11 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/features/april-fools/american-spelling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { ChannelHandlers } from "../../types";

const AMERICAN_SPELLING_MAP: Record<string, string> = {
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.reply(
SUPER_SARCASTIC_REPLIES_TO_BRITISH_SPELLING_WORDS[
Math.floor(
Math.random() *
SUPER_SARCASTIC_REPLIES_TO_BRITISH_SPELLING_WORDS.length,
)
],
);

await msg.delete();
},
} as ChannelHandlers;
176 changes: 176 additions & 0 deletions src/features/april-fools/community-timeout-vote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { GuildMember, MessageReaction, TextChannel, User } from "discord.js";
import { ChannelHandlers } from "../../types";
import { sleep } from "../../helpers/misc";

const RECENT_CHATTERS = new Set<string>();

let timedOutUserId: string | null = null;
let isProcessingPlea = false;
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,
): 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(TIMEOUT_DURATION_MINS * 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.",
);

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
if (timedOutUserId === msg.author.id) {
timedOutUserId = null;
}
});

return;
}

RECENT_CHATTERS.add(msg.author.id);

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)];
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 * VOTING_DURATION_MINS,
});

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(TIMEOUT_DURATION_MINS * 60 * 1000);
await channel.send(`Chaos reigns upon <@${dissenter.id}> instead.`);
}
}
});

RECENT_CHATTERS.clear();
}
},
} as ChannelHandlers;
31 changes: 31 additions & 0 deletions src/features/april-fools/im-watching-you.ts
Original file line number Diff line number Diff line change
@@ -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;
19 changes: 19 additions & 0 deletions src/features/april-fools/typing-status.ts
Original file line number Diff line number Diff line change
@@ -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;
10 changes: 10 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ 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 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: [
Expand Down Expand Up @@ -185,6 +189,12 @@ const threadChannels = [CHANNELS.helpJs, CHANNELS.helpThreadsReact];
addHandler(threadChannels, autothread);

addHandler(CHANNELS.resumeReview, resumeReviewPdf);
addHandler(CHANNELS.random, [
imWatchingYou,
typingStatus,
americanSpelling,
communityTimeoutVote,
]);

bot.on("ready", () => {
deployCommands(bot);
Expand Down
Loading