From 186414e96acff1ed65e26dd1b1c2ce58210a0bd6 Mon Sep 17 00:00:00 2001 From: Naomi Carrigan Date: Wed, 20 Aug 2025 13:58:03 -0700 Subject: [PATCH] feat: pull from hackernews --- src/index.ts | 3 +++ src/interfaces/amari.ts | 1 + src/interfaces/rss.ts | 27 ++++++++++++++++++++++- src/modules/postNews.ts | 49 +++++++++++++++++++++++++++++++++++++++-- 4 files changed, 77 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 1fa75b7..e744839 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,7 @@ import { scheduleJob } from "node-schedule"; import { handleMessageCreate } from "./events/handleMessageCreate.js"; import { postFreeCodeCampNews, + postHackerNews, } from "./modules/postNews.js"; import { respondToDm } from "./modules/respondToDm.js"; import { instantiateServer } from "./server/serve.js"; @@ -26,6 +27,7 @@ const amari: Amari = { partials: [ Partials.Channel ] }), lastRssItems: { freeCodeCamp: null, + hackerNews: null, }, }; @@ -34,6 +36,7 @@ amari.discord.once(Events.ClientReady, () => { `Authenticated to Discord as ${amari.discord.user?.username ?? "unknown"}`); scheduleJob("post news", "0 * * * *", async() => { await postFreeCodeCampNews(amari); + await postHackerNews(amari); }); }); diff --git a/src/interfaces/amari.ts b/src/interfaces/amari.ts index ca7ab89..6fd727f 100644 --- a/src/interfaces/amari.ts +++ b/src/interfaces/amari.ts @@ -10,5 +10,6 @@ export interface Amari { discord: Client; lastRssItems: { freeCodeCamp: string | null; + hackerNews: string | null; }; } diff --git a/src/interfaces/rss.ts b/src/interfaces/rss.ts index 4c1567d..cd2dbba 100644 --- a/src/interfaces/rss.ts +++ b/src/interfaces/rss.ts @@ -37,4 +37,29 @@ interface FreeCodeCampRSS { ttl: string; } -export type { FreeCodeCampRSS }; +interface HackerNewsRSS { + items: Array<{ + "creator": string; + "title": string; + "link": string; + "pubDate": string; + "dc:creator": string; + "comments": string; + "content": string; + "contentSnippet": string; + "guid": string; + "isoDate": string; + }>; + feedUrl: string; + paginationLinks: { + self: string; + }; + title: string; + description: string; + generator: string; + link: string; + lastBuildDate: string; + docs: string; +} + +export type { FreeCodeCampRSS, HackerNewsRSS }; diff --git a/src/modules/postNews.ts b/src/modules/postNews.ts index 2a3d92d..ef5b2b4 100644 --- a/src/modules/postNews.ts +++ b/src/modules/postNews.ts @@ -10,7 +10,10 @@ import Parser from "rss-parser"; import { ids } from "../config/ids.js"; import { logger } from "../utils/logger.js"; import type { Amari } from "../interfaces/amari.js"; -import type { FreeCodeCampRSS } from "../interfaces/rss.js"; +import type { + FreeCodeCampRSS, + HackerNewsRSS, +} from "../interfaces/rss.js"; /** * Fetches the RSS feed from freeCodeCamp News and posts the latest updates. @@ -54,4 +57,46 @@ const postFreeCodeCampNews = async(amari: Amari): Promise => { } }; -export { postFreeCodeCampNews }; +/** + * Fetches the RSS feed from HackerNews and posts the latest updates. + * @param amari - Amari's instance. + */ +const postHackerNews = async(amari: Amari): Promise => { + try { + const parser = new Parser(); + const { items } + = await parser.parseURL("https://hnrss.org/newest?link=comments"); + if (amari.lastRssItems.hackerNews === null) { + amari.lastRssItems.hackerNews = items[0]?.guid ?? null; + return; + } + const lastIndex = items.findIndex((item) => { + return item.guid === amari.lastRssItems.hackerNews; + }); + const latestPosts + = lastIndex > -1 + ? items.slice(0, Math.min(lastIndex, 5)) + : items.slice(0, 5); + const channel + = amari.discord.channels.cache.get(ids.channels.news) + ?? await amari.discord.channels.fetch(ids.channels.news); + if (channel === null) { + throw new Error("Cannot find news channel."); + } + if (!channel.isSendable()) { + throw new Error("News channel is not sendable."); + } + await Promise.all(latestPosts.map(async(post) => { + const sent = await channel.send(post.link); + if (channel.type === ChannelType.GuildAnnouncement) { + await sent.crosspost(); + } + })); + } catch (error) { + if (error instanceof Error) { + await logger.error("post hackernews module", error); + } + } +}; + +export { postFreeCodeCampNews, postHackerNews };