How I got a Nintendo Switch using NodeJS

rgthree

Regis Gaughan, III

Posted on December 23, 2020

How I got a Nintendo Switch using NodeJS

Have you missed out on a hot holiday gift? Me too... until I used NodeJS to send my phone an alert to buy!


This holiday season I saw a deal for a Nintendo Switch bundle which was perfect for my brother and his family. It was on sale for $299 but, unfortunately, by the time I went to get it there were no more available and third-party scalpers were selling it for over $500. Bummer.

However, I noticed that Amazon’s stock would occasionally come available with that $299 price tag every now and then; but I was never lucky enough to check at the right time.

So what does a software engineer do? Write a script to monitor the availability and send a message to my phone when it’s available to buy. And, it actually worked!


GitHub logo rgthree / pricewatch

An Amazon Price Watcher with phone alerting via Telegram


How it works.

The script is three pieces that work together:

  1. An AmazonPriceChecker that fetches the Amazon product web page by its id and, using JSDOM, looks for the current price
  2. A TelegramBot that simply encapsulates making a simple request to alert my phone via my personal Telegram bot.
  3. And a main server file that ties it all together to run the check loop, check the price threshold, and utilize the bot to alert my phone.

Set up

For this project, we'll need very few dependencies.

  • If you haven't already, you'll need to install NodeJS
  • You'll also need TypeScript. I recommend installing globally:

    
     npm install -g typescript
    
    
  • You'll then need jsdom and node-fetch. You can create a package.json file similar to below in your project directory and run npm install:

    {
      "name": "rgthree-pricewatch",
      "description": "Watch prices.",
      "version": "0.0.1",
      "private": true,
      "dependencies": {
        "jsdom": "^16.4.0",
        "node-fetch": "^2.6.1"
      },
      "devDependencies": {
        "@types/jsdom": "11.0.4",
        "@types/node": "^12.12.2",
        "@types/node-fetch": "^2.5.7"
      }
    }
    
    package.json

The AmazonPriceChecker

All we need this to do is fetch the Amazon product's web page by its product id and, using JSDOM, look for the current price in the DOM, and return it if found along with the url of the product itself.

One thing to keep in mind is we’re fetching the web page with our server. We’ll override the User-Agent so it looks like a browser, but the response back will be raw HTML and possibly different than the markup we see when using Amazon as JavaScript is likely modifying the page substantially after that raw HTML come back.

So, to find how to scrape the price we'll use the view-source feature in a browser to see exactly what our script will see instead of the DevTools.

Luckily, it wasn't too hard to find Amazon is wrapping the price in an element with the id of priceblock_ourprice. (At least for the Nintendo; it's possible other products have different markup.)

All together, our AmazonPriceChecker looks like this:

import fetch from 'node-fetch';
import {JSDOM} from 'jsdom';

/**
 * Given a product id, will try to find the current price of the item on the
 * Amazon page.
 */
export class AmazonPriceChecker {

  private readonly url: string;

  constructor(id: string) {
    this.url = `https://www.amazon.com/gp/product/${id}`;
  }

  /**
   * Returns a payload of the url for the product and it's current price, if
   * found. If the price cannot be determined, it will be `NaN`.
   */
  async check() {
    try {
      const response = await this.fetchProductPage();
      const body = await response.text();
      const doc = new JSDOM(body).window.document;
      let price = Number(doc.querySelector('#priceblock_ourprice')?.textContent?.trim().replace('$',''));
      return {url: this.url, price};
    } catch(e) {
      throw new Error(e);
    }
  }

  private async fetchProductPage() {
    return await fetch(this.url, {
      method: 'get',
      headers: {
        'accept-language': 'en-US',
        'Accept': 'text/html,application/xhtml+xml',
        // Make our request look like a browser.
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.11 Safari/537.36',
      }
    });
  }
}
Enter fullscreen mode Exit fullscreen mode
amazon_watcher.ts

Our TelegramBot

For the next part of our script we want to alert our phone. I've previously written about how to create a personal Telegram Bot here:

All we need is our bot’s Api Key and the chat id our bot belongs to that we'll ping.

import fetch from 'node-fetch';

/**
 * Super simple telegram wrapper that sends messages to a bot specific chat.
 */
export class TelegramBot {

  constructor(private botApiKey: string, private chatId: string) {}

  async sendMessage(text: string) {
    return await fetch(`https://api.telegram.org/bot${this.botApiKey}/sendMessage?chat_id=${this.chatId}&text=${encodeURIComponent(text)}`);
  }
}
Enter fullscreen mode Exit fullscreen mode
telegrambot.ts

NOTE: You don't have to use Telegram to alert yourself. You can modify the code here to send an email, or trigger IFTTT in some way, etc. Options are up to you, I've chosen to use Telegram because I already had a personal bot setup to ping my phone :)

Tying it all together

Now that we have our two separate pieces, we'll tie them together in our main server file where we'll loop to check every two minutes.

import {AmazonPriceChecker} from './amazon_watcher';
import {TelegramBot} from './telegrambot';

const TELEGRAM_API_KEY = 'YOUR_API_KEY';
const TELEGRAM_CHAT_ID = 'YOUR_API_KEYCHAT_ID';

// The Amazon product id. The XXX in
// https://www.amazon.com/dp/XXX or https://www.amazon.com/gp/product/XXX
const AMAZON_PRODUCT_ID = 'B08KB652Q2';
const TARGET_PRICE = 300;

const MS_MINUTES = 1000 * 60;
const BASE_TIMEOUT = MS_MINUTES * 2;

const telegram = new TelegramBot(TELEGRAM_API_KEY, TELEGRAM_CHAT_ID);
const priceChecker = new AmazonPriceChecker(AMAZON_PRODUCT_ID);

/**
 * Checks the price with `priceChecker`, issues a message with `telegram` if
 * it meets our threshold, and schedules another check.
 */
async function check() {
  let timeout = BASE_TIMEOUT;
  try {
    const {url, price} = await priceChecker.check();

    if (price) {
      if (price <= TARGET_PRICE) {
        telegram.sendMessage(`Price is: ${price}. Checking again in ${timeout / MS_MINUTES} minutes. ${url}`);
      }
    } else {
      // If we can't parse the price, maybe something's wrong. We'll slow down
      // our interval a bit.
      timeout += MS_MINUTES * 5;
      telegram.sendMessage(`Could not parse price. Trying again in ${timeout / MS_MINUTES}. ${url}`);
    }

  } catch(e) {
    timeout += MS_MINUTES * 5;
    telegram.sendMessage(`There was an error fetching the price. Will check again in ${timeout / MS_MINUTES} minutes.`);
    console.error(e);
  }

  // Rinse & repeat.
  setTimeout(() => { check(); }, timeout);
}

// Start it!
check();

console.log('Checker Started. Stop with Ctrl + C.');
Enter fullscreen mode Exit fullscreen mode
server.ts

Start it up

First, run the TypeScript compiler which will generate JavaScript files from our neatly typed TypeScript files:

tsc
Enter fullscreen mode Exit fullscreen mode

And then run our server file with NodeJs:

node server.js
Enter fullscreen mode Exit fullscreen mode

And that's it! This triggered an alert on my phone in about 4 hours or so and I was able to open Amazon directly to the product page and get the Switch, which was good because when I checked again four minutes later it shot back up to $500!

Enhance!

This is just a base that worked for me. We can take this add more checkers for other online stores, different products, turn it into a full "bot" that actually buys the product, etc. Consider this just a starting point of where we can go from here. Maybe for next holiday season.

GitHub logo rgthree / pricewatch

An Amazon Price Watcher with phone alerting via Telegram

💖 💪 🙅 🚩
rgthree
Regis Gaughan, III

Posted on December 23, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

How I got a Nintendo Switch using NodeJS
javascript How I got a Nintendo Switch using NodeJS

December 23, 2020