feat: notify when naomi earns achievements
Node.js CI / Lint and Test (push) Successful in 48s

This commit is contained in:
2025-09-10 13:21:08 -07:00
parent bfaf757d3e
commit 996cde5e2f
6 changed files with 135 additions and 1 deletions
+1
View File
@@ -24,6 +24,7 @@
},
"dependencies": {
"@nhcarrigan/logger": "1.0.0",
"@retroachievements/api": "2.6.0",
"discord.js": "14.22.0",
"fastify": "5.5.0",
"node-schedule": "2.1.1",
+9
View File
@@ -11,6 +11,9 @@ importers:
'@nhcarrigan/logger':
specifier: 1.0.0
version: 1.0.0
'@retroachievements/api':
specifier: 2.6.0
version: 2.6.0
discord.js:
specifier: 14.22.0
version: 14.22.0
@@ -488,6 +491,10 @@ packages:
resolution: {integrity: sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
'@retroachievements/api@2.6.0':
resolution: {integrity: sha512-ra6tSHYRJ1Mdm25GtlwQUAQLfrad32hOfBwYRHQo+Fv+vjeki3jEtXX/KNqFGZK+5DKH6S1rSgVoGBdXVbNzkA==}
engines: {node: '>=16'}
'@rollup/rollup-android-arm-eabi@4.46.3':
resolution: {integrity: sha512-UmTdvXnLlqQNOCJnyksjPs1G4GqXNGW1LrzCe8+8QoaLhhDeTXYBgJ3k6x61WIhlHX2U+VzEJ55TtIjR/HTySA==}
cpu: [arm]
@@ -2819,6 +2826,8 @@ snapshots:
'@pkgr/core@0.1.2': {}
'@retroachievements/api@2.6.0': {}
'@rollup/rollup-android-arm-eabi@4.46.3':
optional: true
+2 -1
View File
@@ -5,4 +5,5 @@ 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"
BASEROW_TOKEN="op://Environment Variables - Naomi/Amari/baserow token"
BASEROW_TOKEN="op://Environment Variables - Naomi/Amari/baserow token"
RA_KEY="op://Environment Variables - Naomi/Amari/retroachievements key"
+1
View File
@@ -7,6 +7,7 @@
export const ids = {
channels: {
formSubmissions: "1410435042898874471",
gaming: "1385797656307175468",
general: "1385797320389431336",
menteeChat: "1400589073613062204",
mentorshipGoalForum: "1400629118110011526",
+4
View File
@@ -14,6 +14,7 @@ import { App } from "octokit";
import { ids } from "./config/ids.js";
import { handleMessageCreate } from "./events/handleMessageCreate.js";
import { cacheData } from "./modules/cacheData.js";
import { checkRetroAchievements } from "./modules/checkAchievements.js";
import { logMenteeJoin } from "./modules/logMenteeJoin.js";
import { logMenteeLeave } from "./modules/logMenteeLeave.js";
import {
@@ -72,6 +73,9 @@ amari.discord.once(Events.ClientReady, () => {
setInterval(() => {
amari.recentlyActiveChannels = new Set<string>();
}, 10 * 60 * 1000);
setInterval(() => {
void checkRetroAchievements(amari);
}, 10 * 60 * 1000);
});
amari.discord.on(Events.MessageCreate, (message) => {
+118
View File
@@ -0,0 +1,118 @@
/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import {
buildAuthorization,
getUserRecentAchievements,
type UserRecentAchievement,
} from "@retroachievements/api";
import {
MessageFlags,
TextDisplayBuilder,
ContainerBuilder,
SectionBuilder,
ThumbnailBuilder,
SeparatorBuilder,
SeparatorSpacingSize,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
type MessageActionRowComponentBuilder,
} from "discord.js";
import { ids } from "../config/ids.js";
import type { Amari } from "../interfaces/amari.js";
const username = "naomilgbt";
const webApiKey = process.env.RA_KEY;
const constructComponents = (
achievement: UserRecentAchievement,
): Array<
TextDisplayBuilder |
ContainerBuilder |
ActionRowBuilder<MessageActionRowComponentBuilder>> => {
return [
new TextDisplayBuilder().
setContent("Naomi has unlocked a new achievement!"),
new ContainerBuilder().
setAccentColor(16_758_493).
addSectionComponents(
new SectionBuilder().
setThumbnailAccessory(
new ThumbnailBuilder().
setURL(`https://retroachievements.org/${achievement.badgeUrl}`),
).
addTextDisplayComponents(
new TextDisplayBuilder().setContent(`# ${achievement.title}`),
new TextDisplayBuilder().setContent(achievement.description),
),
).
addSeparatorComponents(
new SeparatorBuilder().setSpacing(SeparatorSpacingSize.Small).
setDivider(true),
).
addSectionComponents(
new SectionBuilder().
setThumbnailAccessory(
new ThumbnailBuilder().
setURL(`https://retroachievements.org${achievement.gameIcon}`),
).
addTextDisplayComponents(
new TextDisplayBuilder().setContent(
`-# From ${achievement.gameTitle} on ${achievement.consoleName}`,
),
new TextDisplayBuilder().setContent(
`-# Worth ${achievement.points.toString()} points!`,
),
),
),
new ActionRowBuilder<MessageActionRowComponentBuilder>().
addComponents(
new ButtonBuilder().
setStyle(ButtonStyle.Link).
setLabel("See her profile").
setURL("https://retroachievements.org/user/naomilgbt"),
),
];
};
/**
* Checks for RA achievements earned in the last 10 minutes.
* If achievements are found, formats them and sends to Discord.
* @param amari - Amari's instance.
*/
export const checkRetroAchievements = async(
amari: Amari,
): Promise<void> => {
if (webApiKey === undefined) {
return;
}
const auth = buildAuthorization({ username, webApiKey });
const recentAchievements = await getUserRecentAchievements(auth, {
recentMinutes: 10,
username: username,
});
if (recentAchievements.length <= 0) {
return;
}
const channel = amari.discord.channels.cache.get(ids.channels.gaming)
?? await amari.discord.channels.fetch(ids.channels.gaming);
if (channel?.isSendable() !== true) {
return;
}
await Promise.all(recentAchievements.map(async(achievement) => {
await channel.send({
components: constructComponents(achievement),
flags: [ MessageFlags.IsComponentsV2 ],
});
}));
};