diff --git a/prod.env b/prod.env index 0cc413c..92cb311 100644 --- a/prod.env +++ b/prod.env @@ -4,4 +4,5 @@ GH_CLIENT_ID="op://Environment Variables - Naomi/Amari/gh client id" GH_CLIENT_SECRET="op://Environment Variables - Naomi/Amari/gh client secret" GH_PRIVATE_KEY="op://Environment Variables - Naomi/Amari/gh private key" GH_WEBHOOK_SECRET="op://Environment Variables - Naomi/Amari/gh webhook secret" -BASEROW_SECRET="op://Environment Variables - Naomi/Amari/baserow hook auth" \ No newline at end of file +BASEROW_SECRET="op://Environment Variables - Naomi/Amari/baserow hook auth" +BASEROW_TOKEN="op://Environment Variables - Naomi/Amari/baserow token" \ No newline at end of file diff --git a/src/config/ids.ts b/src/config/ids.ts index 185cb33..027a813 100644 --- a/src/config/ids.ts +++ b/src/config/ids.ts @@ -7,6 +7,7 @@ export const ids = { channels: { formSubmissions: "1410435042898874471", + general: "1385797320389431336", menteeChat: "1400589073613062204", mentorshipGoalForum: "1400629118110011526", mentorshipProjectForum: "1400616702265266186", diff --git a/src/index.ts b/src/index.ts index 6b87270..1722eba 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,8 @@ import { App } from "octokit"; import { ids } from "./config/ids.js"; import { handleMessageCreate } from "./events/handleMessageCreate.js"; import { cacheData } from "./modules/cacheData.js"; +import { logMenteeJoin } from "./modules/logMenteeJoin.js"; +import { logMenteeLeave } from "./modules/logMenteeLeave.js"; import { postFreeCodeCampNews, postHackerNews, @@ -107,5 +109,13 @@ amari.discord.on(Events.GuildMemberUpdate, (oldMember, updatedMember) => { void processMentorshipRole(amari, oldMember, updatedMember); }); +amari.discord.on(Events.GuildMemberAdd, (member) => { + void logMenteeJoin(amari, member); +}); + +amari.discord.on(Events.GuildMemberRemove, (member) => { + void logMenteeLeave(amari, member); +}); + await amari.discord.login(process.env.BOT_TOKEN); instantiateServer(amari); diff --git a/src/interfaces/baserow.ts b/src/interfaces/baserow.ts new file mode 100644 index 0000000..8d86563 --- /dev/null +++ b/src/interfaces/baserow.ts @@ -0,0 +1,45 @@ +/** + * @copyright NHCarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +/* eslint-disable @typescript-eslint/naming-convention -- Baserow returns this. */ + +export interface MentorshipRow { + count: number; + next: unknown; + previous: unknown; + results: Array<{ + "id": number; + "order": string; + "Email Address": string; + "First Name": string; + "Last Name": string; + "Company": string; + "Consent to Reply": { + id: number; + value: string; + color: string; + }; + "Status": { + id: number; + value: string; + color: string; + }; + "Focus": string; + "Current": string; + "Free": { + id: number; + value: string; + color: string; + }; + "Pronouns": string; + "Discord ID": string; + "Donation?": { + id: number; + value: string; + color: string; + }; + }>; +} diff --git a/src/modules/logMenteeJoin.ts b/src/modules/logMenteeJoin.ts new file mode 100644 index 0000000..b0998cb --- /dev/null +++ b/src/modules/logMenteeJoin.ts @@ -0,0 +1,51 @@ +/** + * @copyright NHCarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +import { ids } from "../config/ids.js"; +import { logger } from "../utils/logger.js"; +import type { Amari } from "../interfaces/amari.js"; +import type { MentorshipRow } from "../interfaces/baserow.js"; +import type { GuildMember } from "discord.js"; + +/** + * Run when a guild member joins. If the member has a mentorship application, + * suggest that they let Naomi know to onboard them. + * @param amari - Amari's instance. + * @param member - The member payload from Discord. + */ +export const logMenteeJoin = async( + amari: Amari, + member: GuildMember, +): Promise => { + const request = await fetch(`https://forms.nhcarrigan.com/api/database/rows/table/756/?user_field_names=true&search=${member.id}`, { headers: { + authorization: `Token ${process.env.BASEROW_TOKEN ?? "huh"}`, + } }); + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Fetch accepts no generic here. + const response = await request.json() as MentorshipRow; + + if (response.count <= 0) { + return; + } + + const channel = amari.discord.channels.cache.get(ids.channels.general) + ?? await amari.discord.channels.fetch(ids.channels.general); + + if (channel?.isSendable() !== true) { + await logger.log( + "warn", + "General channel does not exist or is not sendable.", + ); + return; + } + + await channel.send({ + content: `Hey <@${member.id}>~! + +Welcome to our community! It looks like you may have applied for our mentorship programme! + +If that is correct, you should ping Naomi to grant your role and begin onboarding! `, + }); +}; diff --git a/src/modules/logMenteeLeave.ts b/src/modules/logMenteeLeave.ts new file mode 100644 index 0000000..2bf1b0d --- /dev/null +++ b/src/modules/logMenteeLeave.ts @@ -0,0 +1,44 @@ +/** + * @copyright NHCarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +import { ids } from "../config/ids.js"; +import { logger } from "../utils/logger.js"; +import type { Amari } from "../interfaces/amari.js"; +import type { GuildMember, PartialGuildMember } from "discord.js"; + +/** + * Run when a guild member leaves. If the member had the mentorship role, + * notify Naomi. + * @param amari - Amari's instance. + * @param member - The member payload from Discord. + */ +export const logMenteeLeave = async( + amari: Amari, + member: GuildMember | PartialGuildMember, +): Promise => { + if (!member.roles.cache.has(ids.roles.mentorship)) { + return; + } + + const channel = amari.discord.channels.cache.get(ids.channels.menteeChat) + ?? await amari.discord.channels.fetch(ids.channels.menteeChat); + + if (channel?.isSendable() !== true) { + await logger.log( + "warn", + "Mentee Chat channel does not exist or is not sendable.", + ); + return; + } + + await channel.send({ + content: `Hey <@${ids.users.naomi}>~! + +<@${member.id}> (${member.user.displayName} - ${member.id}) has left the server. + +It seems they were part of the mentorship programme, so you may need to offboard them.`, + }); +};