Overview

Let’s set up a script that alerts us every hour of new posts on the front page of Hacker News that have exceeded a certain point threshold.

Here’s how we’ll tackle it:

  1. Write the script to fetch all front page posts with a minimum upvote score
  2. Set up a notification to be sent to Discord whenever a new post is found
  3. Configure the schedule so that it “remembers” which posts have already been sent through

Fetching top stories from HN

We’ll use the Hacker News API endpoints to fetch the top page stories. Here’s what that function might look like:

const get = async (url) => fetch(url).then((r) => r.json());

const fetchStories = async ({ pages = 1, min = 100, exclude = [] } = {}) => {
  const storyIds = await get(
    `https://hacker-news.firebaseio.com/v0/topstories.json`
  );

  return Promise.all(
    storyIds
      // Filter out posts we've already seen
      .filter((id) => !exclude.includes(id))
      // Limit to the first page (30 posts per page)
      .slice(0, pages * 30)
      .map(async (id) => {
        const story = await get(
          `https://hacker-news.firebaseio.com/v0/item/${id}.json`
        );

        return story;
      })
  );
};

It’s basically an N+1 query, but no big deal. If we limit the results to the first page (i.e. the first 30 posts), the request is generally fast enough.

Triggering a Discord alert when new posts are found

First, let’s define two helper functions:

  • format: formats story data into Discord markdown
  • notify: sends an alert to Discord via webhook URL
const format = (results) => {
  return results
    .map((p) => `[${p.score} points] [${p.title}](${p.url})`)
    .join("\n");
};

const notify = async (content, webhook = process.env.DISCORD_WEBHOOK_URL) => {
  return fetch(webhook, {
    headers: { "content-type": "application/json" },
    method: "POST",
    body: JSON.stringify({ content }),
  });
};

Now, the simplest thing we can do is just send an alert to Discord whenever we find posts that meet the minimum score requirement set in the request body.

Here we handle that in a NextJS API endpoint:

export default async function handler(req, res) {
  const { min = 100, pages = 1 } = req.body;
  const stories = await fetchStories({ pages });
  // After we fetch the stories, we filter out the ones that
  // don't have the minimum score specified in the request body
  const results = stories.filter((s) => s.score >= min);

  if (results.length > 0) {
    // Send notification to Discord using the helpers defined above
    await notify(format(results));
  }

  return res.status(200).json({ results });
}

This is fine if we run the script every hour or two, and don’t mind seeing repeated links. But what if we want to avoid sending duplicates?

Keeping track of alert history

There are two ways we can keep track of which posts have been sent out already.

When you create a job on a schedule, the scheduler will pass in some metadata into the body of each request. Included in that metadata is the response from the previous run, which can be seen at req.body.$previous.

Also included in the metadata is the schedule’s state, which can be accessed at req.body.$state and set or updated in the response.