I was experimenting with the NHL API the other day. It was interesting and I didn’t want to make things really complicated, but something that I did want was to be able to update the sceeen in place without it scrolling for each update.

Curses is the obvious solution for this, but it seemed a little complicated for a simple little program. I did some digging and found Blessed which is a nice Python library that wraps up curses functionality in a much easier to use package.

It took me just a few minutes to add the in place updates to the code. I still want to figure out what to do with the games that are finalized or postponed, but it’s good enough for now.

Here is what I came up with:

from blessed import Terminal
from datetime import datetime
import jmespath
import json
import requests
from requests.exceptions import HTTPError
import sys
import time

BASE_URL = 'https://statsapi.web.nhl.com'
SLEEP_SEC = 5


def main():

    term = Terminal()
    print(term.clear)

    # get today's date in yyyy-mm-dd format
    today = datetime.today().strftime('%Y-%m-%d')

    active_games = get_active_games(today)
    if len(active_games) == 0:
        print("No active games")
        sys.exit()

    # loop through and get the live information for each game
    while True:
        for i, game_url in enumerate(active_games):
            res = get_url(game_url)
            # get the data
            away = jmespath.search("gameData.teams.away.name", res)
            home = jmespath.search("gameData.teams.home.name", res)
            current_play = jmespath.search("liveData.plays.allPlays[-1].result.event", res)
            desc = jmespath.search("liveData.plays.allPlays[-1].result.description", res)

            time = jmespath.search("liveData.plays.allPlays[-1].about.periodTimeRemaining", res)

            per = jmespath.search("liveData.plays.allPlays[-1].about.ordinalNum", res)
            goals_away = jmespath.search("liveData.plays.allPlays[-1].about.goals.away", res)
            goals_home = jmespath.search("liveData.plays.allPlays[-1].about.goals.home", res)
            status = jmespath.search("gameData.status.detailedState", res)
            # remove the games that are postponed or final
            if status == "Postponed" or  status == "Final":
                active_games.remove(game_url)
            else:
                # get the last play
                print(term.move_xy(0, i*3) + term.clear_eol
                        + f"{away} @ {home}: {goals_away}-{goals_home}")

                print(term.move_xy(0, i*3+1) + term.clear_eol
                        + f"\t{desc} at {time} in the {per}")

        # break if a keypress is detected and sleep before next loop
        with term.cbreak(), term.hidden_cursor():
            inp = term.inkey(timeout=SLEEP_SEC)
            if inp:
                break
        # stop the loop if there are no more games left
        if len(active_games) == 0:
            print("All active games complete")
            sys.exit()


def get_active_games(date):
    """ get_active_games returns array of games for date in yyyy-mm-dd format """

    calendar_url = BASE_URL + '/api/v1/schedule?date=' + date
    res = get_url(calendar_url)
    current_games = jmespath.search("dates[0].games[].link", res)

    # check the live feed for the status of the games
    active_games = []
    for link in current_games:
        game_url = BASE_URL + link
        res = get_url(game_url)
        # get the game status
        status = jmespath.search("gameData.status.detailedState", res)
        away = jmespath.search("gameData.teams.away.name", res)
        home = jmespath.search("gameData.teams.home.name", res)

        # remove the games that are postponed or final
        if status == "Postponed":
            #print(f"{away} at {home} postponed")
            pass
        elif status == "Final":
            away_goals = jmespath.search("liveData.linescore.teams.away.goals", res)
            home_goals = jmespath.search("liveData.linescore.teams.home.goals", res)
            #print(f"Final {away}:{away_goals} {home}:{home_goals}")
        else:
            active_games.append(game_url)

    return active_games


def get_url(url):
    """ get_url returns json string for a given url or prints an error """

    try:
        response = requests.get(url)
        response.raise_for_status()
    except HTTPError as http_err:
        print(f'HTTP error: {http_error}')
    except Exception as err:
        print('Error: {err}')
    else:
        return response.json()


if __name__ == "__main__":
    main()
python