How an automated email saved me from getting parking tickets

We are building automation using some APIs so we never get another parking ticket again.

My office is within walking distance of the Cubs stadium, Wrigley Field. Most street parking is permitted after 5 pm during night games. This leaves me in a predicament: I have to move my car before 5 pm on night games so I don’t get a ticket. Last year, someone in the office racked up $300 in parking tickets.

The first thing I thought to do was to add all the night games to my calendar, but there are tons of night games, and I was lazy, so the manual wasn’t gonna work. I could have scripted it to get all the night games and populate my calendar, but I ran into many Google Calendar issues around authentication.

So, I decided to write a cron job to check against an API to see whether or not there was a night game; if there was, I would send an automated email in the morning reminding myself not to park there.

Here is what we’ll be using to build this automated email system.

  1. Firebase Functions, to run a cron job daily to check for games.

  2. MySportsFeeds, a free service to get all of our Cubs data from.

  3. Moment.js and moment-timezone, because timezones suck.

  4. Axios, to make our HTTP request to MySportsFeeds.

  5. Nodemailer, to send out our email.

Shut up and code already.

You’ll need to install the Firebase CLI:

npm install -g firebase-tools

Then, create a new directory and initialize our Firebase project

mkdir cubs-automation
cd cubs-automation
firebase init

Go through the project setup and choose functions for the time being

Next, we’ll need to install a bunch of packages to use all this goodness.

cd functions

npm install axios moment moment-timezone nodemailer --save

At the top of your index.js file, you’ll need to load your dependencies

const functions = require("firebase-functions");
const nodemailer = require("nodemailer");
const axios = require("axios");
const moment = require("moment");
const momentTZ = require("moment-timezone");

Because I needed the function to run daily, I created a pubsub function to go off every day at 6:45 am.

exports.sendEmail = functions.pubsub
  .schedule("every monday, tuesday, wednesday, thursday, friday 06:45")
  .timeZone("America/Chicago")
  .onRun(async context => {
  
  
  })

We can use the pubsub cron jobs to do a check every weekday. You can read more about it here and here.

I’m in Chicago, so I chose that time zone.

const now = moment().format("YYYYMMDD");
    const mailList = [
     "[email protected]"
    ];
    const transporter = nodemailer.createTransport({
      service: "gmail",
      auth: {
        user: "********",
        pass: "********"
      }
    });

We get the current date and other options for nodemailer to email us.

try{
const GameData = await axios.get(
        `https://api.mysportsfeeds.com/v2.1/pull/mlb/2019-regular/games.json?date=${now}&team=chicago-cubs`,
        {
          auth: {
            username: "*********",
            password: "*********"
          }
        }
      );
      
      const { startTime } = GameData.data.games[0].schedule;
      const { id } = GameData.data.games[0].schedule.venue;
      const gameTime = momentTZ(startTime)
        .tz("America/Chicago")
        .format("HHmm");
      console.log(gameTime);
      const timeCheck = 1500;
      console.log(startTime);
      if (id === 133) {
        if (gameTime > timeCheck) {
        const mailOptions = {
            from: "[email protected]",
            to: mailList,
            subject: "CUBS EVENING GAME - PARK CAREFULLY!",
            html:'<p>dont park here</p>'
        }
        const mailSent = await transporter.sendMail(mailOptions);
          console.log(mailSent);
        }
        }
} catch(error) {
  console.log(error)
}
return true;
})

Note: In a production application, the worst thing you could do is save all of your authentication data in plain text as I have done here. The thing you should be doing is saving those as config variables. You can read more about that here.

Another note: You will run into issues with Gmail. You’ll have to allow your email to use unsecure applications, more on that here and here.

We’ll use a try/catch because no one likes promise hell. You’ll have to get your own API key from MySportsFeeds. Notice that I’m using the v2 of the API, v1 has different data types that won’t work using this code. We only really need the startTime and id of the game and venue. The startTime v2 comes in as UTC, which we have to format for our timezone and time format. We create a constant of timeCheck for the earliest an evening game would start. We create intTime so we can easily compare timeCheck to the gameTime. We’ll use parseInt to do so. We need to check to see if the game is at home. In this case, Wrigley Field has an id of 133. We then await for the transporter function to send out the email.

This has saved me from so many tickets. I wake up and instantly know where not to park.

Conclusion

I added everyone in the office to the list, and the parking tickets went to 0. So, don’t remember stuff; automate it to have more time to drink mimosas while you nurse a hangover.