Skip to content

ajay-gandhi/alfred

Repository files navigation

Alfred

Order food on Grubhub corporate from Slack

Alfred is a little server that takes orders through a Slack webhook integration, and places them on Grubhub. Alfred uses Grubhub Allocations so you can order with your company's money!

Todos

Things to do, in order of urgency:

  • Menu search functionality
  • Support for comments and quantity
  • Show person with most calls in global stats
  • Per-restaurant favorite
  • Adding tip to reach minimum
  • Allow appending to order
  • Volunteer for order pickup (also requires undoing feature)
  • Configurable time of delivery

Layout

There are two main data flows in this implementation of Alfred. One is synchronous; triggered by a message in Slack. The other is asynchronous; triggered by a cronjob on my server at a fixed time each day.

Synchronous

  1. Slack User sends a message
  2. [server.js] The message is posted to Alfred
  3. [df_parse.js] The message is sent to Dialogflow, which returns the intent and any arguments
  4. [commander.js] The server formats the action and delegates it
  5. [models/orders.js | models/users.js] The appropriate file persists the data to MongoDB
  6. [commander.js] The server returns confirmation text to Slack depending on the command

Asynchronous

  1. Cronjob At 5:30pm each weekday, a cronjob wakes up
  2. [perform.js] The data persisted earlier are read to input the order on Seamless
  3. [models/stats.js] Stats are recorded for the order
  4. [util/slack.js] A message is sent to Slack containing links to confirmations of orders
  5. [koa_confirmation_middleware.js] If a user visits the confirmation PDFs, they are authenticated with this module

Other asynchronous events:

  • Daily passwords: This is to protect the confirmation PDFs, which can contain sensitive information. A cronjob runs util/daily_tasks.js every morning, and basic HTTP auth with the new password is required using koa_confirmation_middleware.js. The new password is sent to Slack when the orders are put in. This script also clears any existing order data and removes the confirmation PDFs from the previous day.
  • Weekly scraping: Every week, menus are scraped from Seamless.

Getting started

Alfred requires Node.js v10.6.0+ (and npm) to run.

Installing the repo

Clone the repo into a directory of your choice.

Alfred uses a headless browser called Puppeteer to perform its interactions with Seamless. Because Puppeteer runs on Chromium, running npm install will also download a version of chromium-browser that is most likely to be compatible with your OS.

Because I'm running Alfred on a Raspberry Pi, the puppeteer npm package doesn't come bundled with a version of chromium that is compatible. Therefore, I installed chromium on my own, and gave puppeteer a path to my version. If you also need to do this, run npm install as follows so that you don't unnecessarily download a copy of chromium:

$ PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true npm install # I actually added this env var to my bash profile
$ sudo apt-get install chromium-browser
# scrape.js
const browser = await puppeteer.launch({
  executablePath: "/usr/bin/chromium-browser", # path to our installed version
  ...
});

Note: Because I need to install chromium manually, I'm using Puppeteer v1.0.0, which is compatible with the version of chromium I installed (65).

Adding private data

Add a file called private.json in the top level directory of the repo. The file should look something like this:

{
  "username": "[Grubhub email]",
  "password": "[Grubhub password]",
  "slackIncomingToken": "[token for authing incoming post from Slack]",
  "slackOutgoingUrl": "[url for slack incoming webhook (outgoing from our server)]",
  "slackChannel": "[name of slack channel]",
  "mySlackId": "[Slack ID of your account]",
  "confUsername": "[static username to access confirmations]",
  "dailyPassword": "[daily updated password to access confirmations, this is autogenerated]",
  "mongoSrv": "[MongoDB URL]",
  "mongoDbName": "[MongoDB DB name]"
}

The mySlackId is required to ensure that the allocation for your account is only used on orders in which you're participating. In order to find out my Slack ID, I entered my info through the Slack webhook, and then inspected the database where the Slack ID was stored.

Run the scraper

Running the scraper is a good way to test that you have everything working:

$ node scraper.js

The scraper will output messages as it moves along. Note that the scraper sets the time of its fake order to be 7pm, so you must run the scraper before 5pm (or change the time).

Run the server

Note that the server that runs only serves HTTP traffic - I highly recommend using SSL. I'm running Alfred behind a reverse proxy which handles the SSL portion.

Run the server:

$ node server.js [port]

Add crontabs

The last step is to add crontabs for Alfred to perform asynchronous functions. Add these crontabs by running crontab -e:

# This enters the orders at 3:30pm (15:30) each weekday
# The arguments tell Alfred to actually order (not a dry run) and to post the
# results to Slack (rather than logging to console), respectively
30 15 * * 1-5 /path/to/node /path/to/alfred/perform.js --actual --post

# This attempts to input the orders at 3pm (15:00) each weekday, and posts
# the results to the Slack channel. This gives people whose orders may not go
# through a chance to resolve any issues. Note that the --actual flag is
# omitted; this is a dry run
0 15 * * 1-5 /path/to/node /path/to/alfred/perform.js --post

# Regenerate a daily password each weekday at midnight
# Clear any existing order data
# Remove the confirmation PDFs from the previous day
0 2 * * 1-5 /path/to/node /path/to/alfred/util/daily_tasks.js

# Scrape menus every week to stay up-to-date
0 7 * * 1 /path/to/node /path/to/alfred/util/scrape.js

# Try again just for redundancy. The scraper will skip restaurants that were
# recently scraped, so this could do nothing
# Scrape menus every week to stay up-to-date
0 9 * * 1 /path/to/node /path/to/alfred/util/scrape.js

# Send a heads up that Alfred is taking orders
0 10 * * 1-5 /path/to/node /path/to/alfred/util/open.js

Koa Confirmation Middleware

This file (koa_confirmation_middleware.js) contains a very basic HTTP authentication setup. In order to access confirmation PDFs, you must enter the username configured in private.json with the daily password that is sent to Slack.

Tools

The scripts/ directory contains some useful scripts for working with data.

Dialogflow

Dialogflow is used to perform natural language processing. I added one Intent for each command, and added as many unique training phrases as I could imagine. I also set the priority of the "Regular Order" and "Stats" intents to High so that they get prioritized over setting favorites, getting info, etc. There's a helper script (scripts/generate.js) which will generate entities to be uploaded to Dialogflow from the scraped menu data.

If you setup a Dialogflow application to work with Alfred, be sure to download the credentials JSON file and pass it as an env variable as suggested.

Credits

About

Order from Grubhub corporate through Slack

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages