chore: remove news feed feature
Node.js CI / CI (pull_request) Failing after 11s
Security Scan and Upload / Security & DefectDojo Upload (pull_request) Successful in 50s

Removes the hourly RSS news posting functionality, including the
postNews module, rss interface, lastRssItems tracking, and the
rss-parser dependency.
This commit is contained in:
2026-03-09 13:11:55 -07:00
parent 1ebe240475
commit d2be204188
6 changed files with 1 additions and 227 deletions
-1
View File
@@ -20,7 +20,6 @@ export const ids = {
mentorshipGoalForum: "1400629118110011526",
mentorshipProjectForum: "1400616702265266186",
naomiDiscussionForum: "1408154690121633917",
news: "1407804798677418198",
partnershipRequests: "1451009066355654829",
policyIdeation: "1417294974046965842",
pressInquiries: "1451011543482368163",
-9
View File
@@ -21,7 +21,6 @@ import { checkRetroAchievements } from "./modules/checkAchievements.js";
import { getForumTagId } from "./modules/getForumTagId.js";
import { logMenteeJoin } from "./modules/logMenteeJoin.js";
import { logMenteeLeave } from "./modules/logMenteeLeave.js";
import { postFreeCodeCampNews, postHackerNews } from "./modules/postNews.js";
import { postProgressReminders } from "./modules/postProgressReminders.js";
import { processMentorshipRole } from "./modules/processMentorshipRole.js";
import { processUserGuildTag } from "./modules/processUserGuildTag.js";
@@ -62,10 +61,6 @@ const amari: Amari = {
}),
github: octokit,
githubApp: githubApp,
lastRssItems: {
freeCodeCamp: null,
hackerNews: null,
},
recentlyActiveChannels: new Set<string>(),
};
@@ -78,10 +73,6 @@ amari.discord.once(Events.ClientReady, () => {
);
void cacheData(amari);
analytics.startCron();
scheduleJob("post news", "0 * * * *", async() => {
await postFreeCodeCampNews(amari);
await postHackerNews(amari);
});
scheduleJob("check guild tags", "0 0 * * *", async() => {
await logger.log("debug", "Auditing guild tags.");
await cacheData(amari);
-4
View File
@@ -11,9 +11,5 @@ export interface Amari {
discord: Client;
github: App["octokit"];
githubApp: App;
lastRssItems: {
freeCodeCamp: string | null;
hackerNews: string | null;
};
recentlyActiveChannels: Set<string>;
}
-65
View File
@@ -1,65 +0,0 @@
/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/* eslint-disable @typescript-eslint/naming-convention -- Gonna have weird properties in this file. */
interface FreeCodeCampRSS {
items: Array<{
"creator": string;
"title": string;
"link": string;
"pubDate": string;
"content:encoded": string;
"dc:creator": string;
"content": string;
"contentSnippet": string;
"guid": string;
"categories": Array<string>;
"isoDate": Date;
}>;
feedUrl: string;
image: {
link: string;
url: string;
title: string;
};
paginationLinks: {
self: string;
};
title: string;
description: string;
generator: string;
link: string;
lastBuildDate: string;
ttl: string;
}
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 };
-146
View File
@@ -1,146 +0,0 @@
/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/* eslint-disable complexity -- These need a lot of logic. */
import { ChannelType } from "discord.js";
// eslint-disable-next-line @typescript-eslint/naming-convention -- Importing a class.
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, HackerNewsRSS } from "../interfaces/rss.js";
/**
* We are completely aware that the contents of this regular expression
* are a violation of our Code of Conduct. Unfortunately, this is necessary
* to allow us to filter out the RSS feeds for inappropriate content.
* We apologise for any distress or harm this line may cause.
*/
const naughtyRegex
// eslint-disable-next-line stylistic/max-len -- Required for filtering.
= /\b(?:harass(?:ment|ing|ed)?|bully(?:ing)?|discriminat(?:e|ion|ory)|deadnam(?:e|ing)|misgender(?:ing|ed)?|doxx?(?:ing)?|threat(?:en(?:s|ing|ed)?)?|intimidat(?:e|ion|ing)|spam|scam|fraud|phish(?:ing)?|malware|exploit|attack(?:s|ing)?|hate\s*speech|slur|racist|sexist|homophobic|transphobic|ableist|xenophobic|bigot(?:ry|ed)?|troll(?:ing)?|abuse|derogat(?:ory|ing)|offensive|vulgar|obscene|nsfw|porn(?:o|ography)?|sexual(?:ly)?\s*(?:harass|explicit|content)|gore|violent|illegal|pirat(?:e|ed|ing)|crack(?:ed|ing)?|warez|torrent(?:s|ing)?|copyright\s*violat|stolen|leak(?:ed|ing)?\s*(?:data|info|personal)|dox|privacy\s*violat|confidential|unauthorized|solicitation|advertis(?:e|ing|ement)|promot(?:e|ion|ing)|affiliate|referral|spam(?:ming)?|sell(?:ing)?|buy(?:ing)?|commercial|marketing|drug\s*deal(?:er|ing)?|narcotics?|cocaine|heroin|meth(?:amphetamine)?|fentanyl|opiates?|opioids?|carfentanil|mdma|ecstasy|lsd|psilocybin|mushrooms?\s*trip|ketamine|pcp|ghb|rohypnol|roofies?|xanax|percocet|oxyco(?:done|ntin)|vicodin|adderall|ritalin|controlled\s*substance|illicit\s*drug|street\s*drug|drug\s*traffick(?:ing)?|prescription\s*fraud|pill\s*mill|cannabis\s*(?!legal|dispensary)|marijuana\s*(?!legal|dispensary)|weed\s*(?!control|killer)|pot\s*dealer|dope|murder(?:ing|ed)?|kill(?:ing|ed)?\s*(?:someone|person|people)|assassinat(?:e|ion)|homicide|manslaughter|assault(?:ing|ed)?|battery|kidnap(?:ping)?|abduct(?:ion|ed)?|human\s*traffick(?:ing)?|sex\s*traffick(?:ing)?|child\s*abuse|rape|sexual\s*assault|molest(?:ation|ing|ed)?|pedophil(?:e|ia)|child\s*porn|cp\s*(?=\s|$)|csam|robbery|burgl(?:ar|ary)|theft|steal(?:ing)?|shoplifting|embezzl(?:e|ement|ing)|launder(?:ing)?\s*money|extortion|blackmail|bribery|arson|terrorism|terrorist|bomb(?:ing)?|explo(?:sive|ding)|weapon\s*deal|arms\s*traffick|firearm\s*(?=illegal|unregistered)|gun\s*(?=illegal|unregistered)|counterfe(?:it|ing)|forg(?:e|ery|ing)|identity\s*theft|tax\s*evasion|insider\s*trading)\b/i;
/**
* Used to filter out naughty words from RSS feeds.
* @param titleOrContent - The title or content to check.
* @returns True if the title or content is naughty, false if it is clean.
*/
const hasNaughtyWords = (titleOrContent: string): boolean => {
return naughtyRegex.test(titleOrContent);
};
/**
* Fetches the RSS feed from freeCodeCamp News and posts the latest updates.
* @param amari - Amari's instance.
*/
const postFreeCodeCampNews = async(amari: Amari): Promise<void> => {
try {
const parser = new Parser<FreeCodeCampRSS, FreeCodeCampRSS["items"]>();
const { items } = await parser.parseURL(
"https://www.freecodecamp.org/news/rss",
);
if (amari.lastRssItems.freeCodeCamp === null) {
amari.lastRssItems.freeCodeCamp = items[0]?.guid ?? null;
return;
}
const lastIndex = items.findIndex((item) => {
return item.guid === amari.lastRssItems.freeCodeCamp;
});
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.");
}
if (amari.lastRssItems.freeCodeCamp !== items[0]?.guid) {
amari.lastRssItems.freeCodeCamp = items[0]?.guid ?? null;
}
await Promise.all(
latestPosts.map(async(post) => {
if (
hasNaughtyWords(post.title)
|| hasNaughtyWords(post.contentSnippet)
|| hasNaughtyWords(post.content)
) {
return;
}
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 freecodecamp news module", error);
}
}
};
/**
* Fetches the RSS feed from HackerNews and posts the latest updates.
* @param amari - Amari's instance.
*/
const postHackerNews = async(amari: Amari): Promise<void> => {
try {
const parser = new Parser<HackerNewsRSS, HackerNewsRSS["items"]>();
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.");
}
if (amari.lastRssItems.hackerNews !== latestPosts[0]?.guid) {
amari.lastRssItems.hackerNews = latestPosts[0]?.guid ?? null;
}
await Promise.all(
latestPosts.map(async(post) => {
if (
hasNaughtyWords(post.title)
|| hasNaughtyWords(post.contentSnippet)
|| hasNaughtyWords(post.content)
) {
return;
}
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 };