generated from nhcarrigan/template
feat: rewrite to be more generic
This commit is contained in:
14
package.json
14
package.json
@ -14,18 +14,18 @@
|
|||||||
"author": "Naomi Carrigan",
|
"author": "Naomi Carrigan",
|
||||||
"license": "See license in LICENSE.md",
|
"license": "See license in LICENSE.md",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nhcarrigan/eslint-config": "5.1.0",
|
"@nhcarrigan/eslint-config": "5.2.0",
|
||||||
"@nhcarrigan/typescript-config": "4.0.0",
|
"@nhcarrigan/typescript-config": "4.0.0",
|
||||||
"@types/node": "22.13.1",
|
"@types/node": "24.0.6",
|
||||||
"@types/node-schedule": "2.1.7",
|
"@types/node-schedule": "2.1.7",
|
||||||
"@vitest/coverage-istanbul": "3.0.5",
|
"@vitest/coverage-istanbul": "3.2.4",
|
||||||
"eslint": "9.20.0",
|
"eslint": "9.30.0",
|
||||||
"typescript": "5.7.3",
|
"typescript": "5.8.3",
|
||||||
"vitest": "3.0.5"
|
"vitest": "3.2.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nhcarrigan/logger": "1.0.0",
|
"@nhcarrigan/logger": "1.0.0",
|
||||||
"discord.js": "14.18.0",
|
"discord.js": "14.21.0",
|
||||||
"node-schedule": "2.1.1"
|
"node-schedule": "2.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
663
pnpm-lock.yaml
generated
663
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -7,18 +7,109 @@
|
|||||||
/**
|
/**
|
||||||
* The channels to post standup reminders in.
|
* The channels to post standup reminders in.
|
||||||
*/
|
*/
|
||||||
export const channels = [
|
export const channels: Array<{
|
||||||
"1326291362483671202",
|
channelId: string;
|
||||||
"1329625299406684263",
|
roleId: string;
|
||||||
"1326291172896931921",
|
name: string;
|
||||||
"1326291132535013409",
|
}> = [
|
||||||
"1326291241045856286",
|
{
|
||||||
"1326291186863833169",
|
channelId: "1382093555228606484",
|
||||||
"1326291226332496026",
|
name: "red-script",
|
||||||
"1326291379621597304",
|
roleId: "1382091453987029144",
|
||||||
"1326291254421749770",
|
},
|
||||||
"1326291160632655983",
|
{
|
||||||
"1326291146267430912",
|
channelId: "1382093583963656223",
|
||||||
"1326291198666866788",
|
name: "orange-bytes",
|
||||||
"1326291214026408009",
|
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",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
30
src/index.ts
30
src/index.ts
@ -5,9 +5,12 @@
|
|||||||
*/
|
*/
|
||||||
import { Client, Events, GatewayIntentBits } from "discord.js";
|
import { Client, Events, GatewayIntentBits } from "discord.js";
|
||||||
import { scheduleJob } from "node-schedule";
|
import { scheduleJob } from "node-schedule";
|
||||||
|
import { channels } from "./config/channels.js";
|
||||||
import { standup } from "./modules/standup.js";
|
import { standup } from "./modules/standup.js";
|
||||||
import { logger } from "./utils/logger.js";
|
import { logger } from "./utils/logger.js";
|
||||||
|
|
||||||
|
const activeIds: Array<string> = [];
|
||||||
|
|
||||||
process.on("unhandledRejection", (error) => {
|
process.on("unhandledRejection", (error) => {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
void logger.error("Unhandled Rejection", error);
|
void logger.error("Unhandled Rejection", error);
|
||||||
@ -25,14 +28,35 @@ process.on("uncaughtException", (error) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
intents: [ GatewayIntentBits.Guilds ],
|
intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages ],
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on(Events.ClientReady, () => {
|
client.on(Events.ClientReady, () => {
|
||||||
void logger.log("debug", "Bot is ready.");
|
void logger.log("debug", "Bot is ready.");
|
||||||
scheduleJob("reminders", "0 6 * * 1-5", async() => {
|
scheduleJob("reminders", "0 9 * * 1-5", async() => {
|
||||||
await standup(client);
|
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);
|
await client.login(process.env.DISCORD_TOKEN);
|
||||||
|
@ -3,28 +3,58 @@
|
|||||||
* @license Naomi's Public License
|
* @license Naomi's Public License
|
||||||
* @author Naomi Carrigan
|
* @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 { channels } from "../config/channels.js";
|
||||||
import { logger } from "../utils/logger.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.
|
* Posts a daily standup reminder in configured channels.
|
||||||
* @param client - The Discord client.
|
* @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 {
|
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(
|
const mapped = await Promise.all(
|
||||||
channels.map(async(channel) => {
|
channels.map(async(channel) => {
|
||||||
const fetched = await client.channels.fetch(channel).catch(() => {
|
const fetched = await client.channels.
|
||||||
void logger.log("warn", `Failed to fetch channel ${channel}.`);
|
fetch(channel.channelId).
|
||||||
return null;
|
catch(() => {
|
||||||
});
|
void logger.log("warn", `Failed to fetch channel ${channel.name}.`);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
if (!fetched) {
|
if (!fetched) {
|
||||||
await logger.log("warn", `Channel ${channel} not found.`);
|
await logger.log("warn", `Channel ${channel.name} not found.`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (fetched.type !== ChannelType.GuildText) {
|
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 null;
|
||||||
}
|
}
|
||||||
return fetched;
|
return fetched;
|
||||||
@ -35,12 +65,39 @@ export const standup = async(client: Client): Promise<void> => {
|
|||||||
});
|
});
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
filtered.map(async(channel) => {
|
filtered.map(async(channel) => {
|
||||||
await channel.send(
|
const sent = await channel.
|
||||||
// eslint-disable-next-line stylistic/max-len -- This message is too long to fit on one line.
|
send(
|
||||||
"Good morning @everyone~! It's time for standup! Please share your goals for today.",
|
`Good morning <@&${
|
||||||
).catch(() => {
|
channels.find((c) => {
|
||||||
void logger.log("warn", `Failed to send standup reminder in channel ${channel.name}.`);
|
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) {
|
} catch (error) {
|
||||||
|
@ -7,6 +7,6 @@
|
|||||||
import { Logger } from "@nhcarrigan/logger";
|
import { Logger } from "@nhcarrigan/logger";
|
||||||
|
|
||||||
export const logger = new Logger(
|
export const logger = new Logger(
|
||||||
"Standup Bot",
|
"Maribelle",
|
||||||
process.env.LOG_TOKEN ?? "",
|
process.env.LOG_TOKEN ?? "",
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user