feat: rewrite to be more generic
Some checks failed
Code Analysis / SonarQube (push) Failing after 17s
Node.js CI / Lint and Test (push) Successful in 37s

This commit is contained in:
2025-06-27 17:04:32 -07:00
parent 1061c40721
commit 89e3e92fa6
6 changed files with 593 additions and 320 deletions

View File

@ -7,18 +7,109 @@
/**
* The channels to post standup reminders in.
*/
export const channels = [
"1326291362483671202",
"1329625299406684263",
"1326291172896931921",
"1326291132535013409",
"1326291241045856286",
"1326291186863833169",
"1326291226332496026",
"1326291379621597304",
"1326291254421749770",
"1326291160632655983",
"1326291146267430912",
"1326291198666866788",
"1326291214026408009",
export const channels: Array<{
channelId: string;
roleId: string;
name: string;
}> = [
{
channelId: "1382093555228606484",
name: "red-script",
roleId: "1382091453987029144",
},
{
channelId: "1382093583963656223",
name: "orange-bytes",
roleId: "1382091462707122186",
},
{
channelId: "1382093618017337574",
name: "yellow-packet",
roleId: "1382091466767208538",
},
{
channelId: "1382093654885138439",
name: "green-loop",
roleId: "1382091470877491364",
},
{
channelId: "1382093684925005935",
name: "blue-stack",
roleId: "1382091474560356393",
},
{
channelId: "1382093724200210522",
name: "purple-array",
roleId: "1382091477710012456",
},
{
channelId: "1382093753489162330",
name: "pink-protocol",
roleId: "1382091502255341568",
},
{
channelId: "1382093785973915820",
name: "cyan-branch",
roleId: "1382091520533856337",
},
{
channelId: "1382093811693387867",
name: "teal-cache",
roleId: "1382091525873336400",
},
{
channelId: "1382093843918225520",
name: "mint-syntax",
roleId: "1382091530428219603",
},
{
channelId: "1382093883722174605",
name: "lime-variable",
roleId: "1382091534538641458",
},
{
channelId: "1382093910259798046",
name: "rose-token",
roleId: "1382091538900979762",
},
{
channelId: "1382093973488799886",
name: "lavender-query",
roleId: "1382091542961066014",
},
{
channelId: "1382094002526097489",
name: "indigo-class",
roleId: "1382091547742568559",
},
{
channelId: "1382094051360116837",
name: "golden-index",
roleId: "1382091551982882936",
},
{
channelId: "1382094088739754096",
name: "silver-promise",
roleId: "1382091556613394443",
},
{
channelId: "1382094114467745912",
name: "bronze-thread",
roleId: "1382091560367292536",
},
{
channelId: "1382094146206044181",
name: "maroon-socket",
roleId: "1382091564498681907",
},
{
channelId: "1382094200211771522",
name: "violet-function",
roleId: "1382091568252719205",
},
{
channelId: "1382094311264616670",
name: "grey-database",
roleId: "1382091572602212622",
},
];

View File

@ -5,9 +5,12 @@
*/
import { Client, Events, GatewayIntentBits } from "discord.js";
import { scheduleJob } from "node-schedule";
import { channels } from "./config/channels.js";
import { standup } from "./modules/standup.js";
import { logger } from "./utils/logger.js";
const activeIds: Array<string> = [];
process.on("unhandledRejection", (error) => {
if (error instanceof Error) {
void logger.error("Unhandled Rejection", error);
@ -25,14 +28,35 @@ process.on("uncaughtException", (error) => {
});
const client = new Client({
intents: [ GatewayIntentBits.Guilds ],
intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages ],
});
client.on(Events.ClientReady, () => {
void logger.log("debug", "Bot is ready.");
scheduleJob("reminders", "0 6 * * 1-5", async() => {
await standup(client);
scheduleJob("reminders", "0 9 * * 1-5", async() => {
await standup(client, activeIds);
});
});
client.on(Events.MessageCreate, (message) => {
if (!message.channel.isThread() || !message.inGuild()) {
return;
}
if (message.channel.parentId === null) {
return;
}
const isInTeamChannel = channels.some((channel) => {
return channel.channelId === message.channel.parentId;
});
if (!isInTeamChannel) {
return;
}
if (message.author.bot) {
return;
}
if (!activeIds.includes(message.author.id)) {
activeIds.push(message.author.id);
}
});
await client.login(process.env.DISCORD_TOKEN);

View File

@ -3,28 +3,58 @@
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { ChannelType, type Client } from "discord.js";
import { AttachmentBuilder, ChannelType, type Client } from "discord.js";
import { channels } from "../config/channels.js";
import { logger } from "../utils/logger.js";
const emptyArray = (array: Array<unknown>): void => {
array.splice(0, array.length);
};
/**
* Posts a daily standup reminder in configured channels.
* @param client - The Discord client.
* @param activeIds - The list of user IDs who sent a progress update yesterday.
*/
export const standup = async(client: Client): Promise<void> => {
// eslint-disable-next-line max-lines-per-function -- shut up
export const standup = async(
client: Client,
activeIds: Array<string>,
): Promise<void> => {
try {
await client.users.
send("465650873650118659", {
content: `The following people sent a progress update yesterday:`,
files: [
new AttachmentBuilder(Buffer.from(activeIds.join("\n")), {
name: "ids.txt",
}),
],
}).
catch(() => {
void logger.log(
"warn",
"Failed to send progress update message to user.",
);
});
emptyArray(activeIds);
const mapped = await Promise.all(
channels.map(async(channel) => {
const fetched = await client.channels.fetch(channel).catch(() => {
void logger.log("warn", `Failed to fetch channel ${channel}.`);
return null;
});
const fetched = await client.channels.
fetch(channel.channelId).
catch(() => {
void logger.log("warn", `Failed to fetch channel ${channel.name}.`);
return null;
});
if (!fetched) {
await logger.log("warn", `Channel ${channel} not found.`);
await logger.log("warn", `Channel ${channel.name} not found.`);
return null;
}
if (fetched.type !== ChannelType.GuildText) {
await logger.log("warn", `Channel ${channel} is not a text channel.`);
await logger.log(
"warn",
`Channel ${channel.name} is not a text channel.`,
);
return null;
}
return fetched;
@ -35,12 +65,39 @@ export const standup = async(client: Client): Promise<void> => {
});
await Promise.all(
filtered.map(async(channel) => {
await channel.send(
// eslint-disable-next-line stylistic/max-len -- This message is too long to fit on one line.
"Good morning @everyone~! It's time for standup! Please share your goals for today.",
).catch(() => {
void logger.log("warn", `Failed to send standup reminder in channel ${channel.name}.`);
});
const sent = await channel.
send(
`Good morning <@&${
channels.find((c) => {
return c.channelId === channel.id;
})?.roleId ?? "692816967895220344"
}> It is time for your daily progress update. Please share the following in this thread:
1⃣ What did you accomplish yesterday?
2⃣ What are you working on today?
3⃣ Are there any blockers or issues you need help with?`,
).
catch(() => {
void logger.log(
"warn",
`Failed to send progress reminder in channel ${channel.name}.`,
);
return null;
});
if (sent) {
await sent.
startThread({
autoArchiveDuration: 1440,
name: `Daily Progress Update - ${new Date().toLocaleDateString()}`,
}).
catch(() => {
void logger.log(
"warn",
`Failed to start thread in channel ${channel.name}.`,
);
return null;
});
}
}),
);
} catch (error) {

View File

@ -7,6 +7,6 @@
import { Logger } from "@nhcarrigan/logger";
export const logger = new Logger(
"Standup Bot",
"Maribelle",
process.env.LOG_TOKEN ?? "",
);