diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..874773d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "eslint.validate": ["typescript"], +} diff --git a/src/modules/postNews.ts b/src/modules/postNews.ts index 421ca23..86c5477 100644 --- a/src/modules/postNews.ts +++ b/src/modules/postNews.ts @@ -12,10 +12,26 @@ 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"; +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. @@ -24,8 +40,9 @@ import type { const postFreeCodeCampNews = async(amari: Amari): Promise => { try { const parser = new Parser(); - const { items } - = await parser.parseURL("https://www.freecodecamp.org/news/rss"); + const { items } = await parser.parseURL( + "https://www.freecodecamp.org/news/rss", + ); if (amari.lastRssItems.freeCodeCamp === null) { amari.lastRssItems.freeCodeCamp = items[0]?.guid ?? null; return; @@ -49,12 +66,21 @@ const postFreeCodeCampNews = async(amari: Amari): Promise => { if (amari.lastRssItems.freeCodeCamp !== items[0]?.guid) { amari.lastRssItems.freeCodeCamp = items[0]?.guid ?? null; } - await Promise.all(latestPosts.map(async(post) => { - const sent = await channel.send(post.link); - if (channel.type === ChannelType.GuildAnnouncement) { - await sent.crosspost(); - } - })); + 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); @@ -69,8 +95,9 @@ const postFreeCodeCampNews = async(amari: Amari): Promise => { const postHackerNews = async(amari: Amari): Promise => { try { const parser = new Parser(); - const { items } - = await parser.parseURL("https://hnrss.org/newest?link=comments"); + const { items } = await parser.parseURL( + "https://hnrss.org/newest?link=comments", + ); if (amari.lastRssItems.hackerNews === null) { amari.lastRssItems.hackerNews = items[0]?.guid ?? null; return; @@ -94,12 +121,14 @@ const postHackerNews = async(amari: Amari): Promise => { if (amari.lastRssItems.hackerNews !== latestPosts[0]?.guid) { amari.lastRssItems.hackerNews = latestPosts[0]?.guid ?? null; } - await Promise.all(latestPosts.map(async(post) => { - const sent = await channel.send(post.link); - if (channel.type === ChannelType.GuildAnnouncement) { - await sent.crosspost(); - } - })); + 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);