Automatic Election Result Updates with Pushover

My fire department has a small levy on the ballot today. I wanted to get updated election results on my phone. It turns out doing this with Pushover is really easy. Pushover allows you to push notifications to your phone and other devices.

Setting Up Pushover

If you don't already have one, create a Pushover account, install the app, and buy a license for at least one device. Then, login and click the 'Create New Application/API Token' link. Fill in the basic info and click <Create Application>. Each app has a free 7500 message a month. More than enough for me.

/images/pushover_create_app.thumbnail.png

Once your app is created you'll get an API Token/Key. You'll need this and your user key (which can be found in the top-right corner of your main account page) to push to your devices.

Getting The Election Results

The results are posted on my State's election website. Because I'd never used it before I decided to scrape the data with BeautifulSoup. The data is machine generated as a bunch of tables without useful IDs. I'm sure you could do it more gracefully than I did, but this worked and gave me some insight into the available tools in BeautifulSoup.

The data is also available for download via CSV or XML. Either of which would have been easier and more reliable. But again, I wanted an excuse to play with BS4.

Sending The Results

Sending the results is really easy. Pushover just needs a POST to its URL with the token, user, and message. Something like this:

data = {
    "token": PUSHOVER_APP_ID,
    "user": PUSHOVER_USER_KEY,
    "title": "Levy Results",
    "message": results
}
requests.post(PUSHOVER_URL, data=data)

There is more you can do with Pushover including HTML styling, sending links, and sending attachments including pictures which will be displayed in the notification. You can also set the priority and notification sound. Check out the API docs for details.

The Final Result

On my phone I get pushover notifications like this. There are still no results posted, once there are it will send both counts and percentages.

/images/pushover_election_results.thumbnail.png

Because this is a throwaway script I'm just using a while loop and having it sleep every 5 minutes between checks.

import datetime
import time

import requests
from bs4 import BeautifulSoup


PUSHOVER_APP_ID = "your_app_key"
PUSHOVER_USER_KEY = "your_user_key"
PUSHOVER_URL = "https://api.pushover.net/1/messages.json"


def get_current_page():
    return requests.get("https://results.vote.wa.gov/results/current/skagit/")


def get_last_update(html_doc):
    soup = BeautifulSoup(html_doc, 'html.parser')
    lu_label = soup.find(string="Last Tabulated")
    lu_text = lu_label.next_element.string
    try:
        """Format: 11/05/2019 1:18 PM"""
        return datetime.datetime.strptime(lu_text, "%m/%d/%Y %I:%M %p")
    except:
        return None


def get_results(html_doc):
    soup = BeautifulSoup(html_doc, 'html.parser')
    race_title_tr = soup.find(string=" FIRE DISTRICT 11 Local Proposition No. 1 - Property Tax Levy for Fire Protection and Emergency Medical Services").parent.parent.parent
    table_rows = race_title_tr.next_siblings
    results = "Measure | Votes | %"
    for row in table_rows:
        columns = row.findAll('td')
        for column in columns:
            results += column.get_text() + " "
        results += "\n"
    return results


def push_results(results):
    data = {
        "token": PUSHOVER_APP_ID,
        "user": PUSHOVER_USER_KEY,
        "title": "Levy Results",
        "message": results
    }
    requests.post(PUSHOVER_URL, data=data)


if __name__ == "__main__":
    latest_results = datetime.datetime(1, 1, 1)
    last_tabulated = None
    while True:
        html = get_current_page()
        if html.status_code == 200:
            last_tabulated = get_last_update(html.text)
        if last_tabulated is not None and last_tabulated > latest_results:
            latest_results = last_tabulated
            results = get_results(html.text)
            push_results(results)
        print(datetime.datetime.now())
        time.sleep(300)

Comments

Comments powered by Disqus