Exemple #1
0
def gameday_roster_update(game):
    """ Gets the gameday rosters from the live feed endpoint.
        This is needed because in some instances a player is not included
        on the /teams/{id}/roster page for some reason.

    Args:
        game: Current Game object

    Returns:
        None
    """

    args = arguments.get_arguments()

    home_team = game.home_team
    away_team = game.away_team

    logging.info("Getting gameday rosters from Live Feed endpoint.")

    try:
        gameday_roster = livefeed.get_livefeed(game.game_id)
        all_players = gameday_roster.get("gameData").get("players")
        for player_id, player in all_players.items():
            try:
                team = player.get("currentTeam").get("name")
                if team == home_team.team_name:
                    home_team.gameday_roster[player_id] = player
                else:
                    away_team.gameday_roster[player_id] = player
            except Exception as e:
                logging.error("%s doesn't have a team - skipping.",
                              player["fullName"])
    except Exception as e:
        logging.error("Unable to get all players.")
        logging.error(e)
Exemple #2
0
def get_starters(game: Game):
    """ Uses the NHL Roster Report to get the starting lineup. """
    def get_players_name(score_rpt_row):
        """ Very specific function to only return the player's last name from the roster report. """
        player_name = score_rpt_row.find_all("td")[2].text
        return " ".join(
            player_name.replace(" (A)", "").replace(" (C)",
                                                    "").title().split()[1:])

    while not game.preview_socials.starters_sent:
        livefeed_resp = livefeed.get_livefeed(game.game_id)
        game.update_game(livefeed_resp)
        if game.game_state == GameState.LIVE.value:
            logging.info(
                "Game state switched to live - forget about the starters.")
            return

        roster_endpoint = f"/{game.season}/RO{game.game_id_html}.HTM"
        r = api.nhl_score_rpt(roster_endpoint)

        if not r:
            logging.warning(
                "Roster report is not available, something is wrong.")
            return

        try:
            soup = thirdparty.bs4_parse(r.content)
            team_headings = soup.find(
                "td", class_="teamHeading + border").find_parent("tr")
            data = team_headings.find_next("tr")

            rosters = data.find("tr").find_all("td", recursive=False)
            roster = rosters[
                0] if game.preferred_team.home_away == "away" else rosters[1]

            players = [x for x in roster.find_all("tr")]

            starters = list()
            for pos in [("L", "R", "C"), "D", "G"]:
                pos_all = [
                    x for x in players if x.find_all("td")[1].text in pos
                ]
                pos_start = [
                    get_players_name(x) for x in pos_all
                    if "bold" in x.find_all("td")[0]["class"]
                ]
                pos_start_str = " - ".join(pos_start)
                starters.append(pos_start_str)
        except Exception as e:
            logging.error(
                "Something happened while trying to get the starters - sleep for 20s & try again. %s",
                e,
            )
            time.sleep(20)
            continue

        if not starters:
            logging.info(
                "Starters not yet avialble from the roster report - sleep & try again."
            )
            time.sleep(20)
            continue

        starters_string = "\n".join(starters)
        starters_msg = (
            f"{utils.team_hashtag(game.preferred_team.team_name)} Starters:"
            f"\n\n{starters_string}")
        socialhandler.send(starters_msg, force_send=True, game_hashtag=True)
        game.preview_socials.starters_msg = starters_msg
        game.preview_socials.starters_sent = True
Exemple #3
0
def start_game_loop(game: Game):
    """The main game loop - tracks game state & calls all relevant functions.

    Args:
        game: Current Game object

    Returns:
        None
    """

    args = arguments.get_arguments()
    config = utils.load_config()

    # ------------------------------------------------------------------------------
    # START THE MAIN LOOP
    # ------------------------------------------------------------------------------

    while True:
        if game.game_state == GameState.PREVIEW.value:
            livefeed_resp = livefeed.get_livefeed(game.game_id)
            game.update_game(livefeed_resp)

            # If after the update_game() function runs, we have a Postponed Game
            # We should tweet it - this means it happened after the game was scheduled
            if game.game_state_code == GameStateCode.POSTPONED.value:
                logging.warning(
                    "This game was originally scheduled, but is now postponed."
                )
                social_msg = (
                    f"⚠️ The {game.preferred_team.team_name} game scheduled for today has been postponed."
                )
                socialhandler.send(social_msg)
                end_game_loop(game)

            if game.game_time_countdown > 0:
                logging.info(
                    "Game is in Preview state - send out all pregame information."
                )
                # The core game preview function should run only once
                if not game.preview_socials.core_sent:
                    preview.generate_game_preview(game)

                # The other game preview function should run every xxx minutes
                # until all pregame tweets are sent or its too close to game time
                sleep_time, last_sleep_before_live = preview.game_preview_others(
                    game)
                game.preview_socials.increment_counter()

                # If this is the last sleep before the game goes live, cut it by 5 minutes for starters function.
                if last_sleep_before_live:
                    logging.info(
                        "This is the last sleep before the game goes live - 5 minutes less & starters."
                    )
                    sleep_time = 0 if (sleep_time - 300) < 0 else sleep_time
                    time.sleep(sleep_time)
                    preview.get_starters(game)
                else:
                    time.sleep(sleep_time)

            else:
                logging.info(
                    "Game is in Preview state, but past game start time - sleep for a bit "
                    "& update game attributes so we detect when game goes live."
                )

                # Somehow we got here without the starting lineup - try again
                if not game.preview_socials.starters_sent:
                    preview.get_starters(game)

                sleep_time = config["script"]["pregame_sleep_time"]
                time.sleep(sleep_time)

        elif game.game_state == GameState.LIVE.value:
            try:
                logging.info("-" * 80)
                logging.info(
                    "Game is LIVE (loop #%s) - checking events after event Idx %s.",
                    game.live_loop_counter,
                    game.last_event_idx,
                )

                # On my development machine, this command starts the files for this game
                # python -m hockeygamebot --console --notweets --team 'Vancouver Canucks' --date '2019-09-17' --localdata
                if args.localdata:
                    logging.info(
                        "SIMULATION DETECTED - running a live game replay for Game %s (%s vs. %s).",
                        game.game_id,
                        game.home_team.team_name,
                        game.away_team.team_name,
                    )
                    directory = "/Users/mattdonders/Development/python/devils-goal-twitter-bitbucket/scratchpad/feed-samples"
                    for file in sorted(os.listdir(directory)):
                        filename = os.fsdecode(file)
                        if filename.endswith(".json"):
                            feed_json = os.path.join(directory, filename)
                            with open(feed_json) as json_file:
                                data = json.load(json_file)

                            # Logging (Temporarily) for Penalty Killed Tweets
                            logging.info(
                                "Current Period Info: %s - %s",
                                game.period.current_ordinal,
                                game.period.time_remaining,
                            )
                            logging.info(
                                "Pref On Ice: %s - %s",
                                len(game.preferred_team.onice),
                                game.preferred_team.onice,
                            )
                            logging.info(
                                "Other On Ice: %s - %s",
                                len(game.other_team.onice),
                                game.other_team.onice,
                            )

                            # Penalty Killed Status
                            penalty_situation = game.penalty_situation
                            if penalty_situation.penalty_killed:
                                logging.info(
                                    "***** PENALTY KILLED NOTIFICATION *****")
                                shots_taken = (
                                    penalty_situation.pp_team.shots -
                                    penalty_situation.pp_team_shots_start)
                                logging.info("PP Shots Taken: %s", shots_taken)
                                game.penalty_situation = PenaltySituation()

                            if game.penalty_situation.in_situation:
                                logging.info(
                                    "Current Penalty (In Situation): %s",
                                    vars(game.penalty_situation),
                                )

                            if not game.period.current_oneminute_sent:
                                live.minute_remaining_check(game)

                            live.live_loop(livefeed=data, game=game)
                            game.update_game(data)

                            time.sleep(0.1)

                # Non-Local Data
                livefeed_resp = livefeed.get_livefeed(game.game_id)
                # all_events = live.live_loop(livefeed=livefeed_resp, game=game)

                # Update all game attributes & check for goalie pulls
                game.update_game(livefeed_resp)
                game.goalie_pull_updater(livefeed_resp)

                # Logging (Temporarily) for Penalty Killed Tweets
                logging.info(
                    "Current Period Info: %s - %s",
                    game.period.current_ordinal,
                    game.period.time_remaining,
                )
                logging.info(
                    "Pref On Ice: %s - %s",
                    len(game.preferred_team.onice),
                    game.preferred_team.onice,
                )
                logging.info("Other On Ice: %s - %s",
                             len(game.other_team.onice), game.other_team.onice)

                # Penalty Killed Status
                penalty_situation = game.penalty_situation
                if penalty_situation.penalty_killed:
                    logging.info("***** PENALTY KILLED NOTIFICATION *****")
                    shots_taken = penalty_situation.pp_team.shots - penalty_situation.pp_team_shots_start
                    logging.info("PP Shots Taken: %s", shots_taken)
                    game.penalty_situation = PenaltySituation()

                if game.penalty_situation.in_situation:
                    logging.info("Current Penalty (In Situation): %s",
                                 vars(game.penalty_situation))

                if not game.period.current_oneminute_sent:
                    live.minute_remaining_check(game)

                # Pass the live feed response to the live loop (to parse events)
                live.live_loop(livefeed=livefeed_resp, game=game)
                # game_events = get_game_events(game_obj)
                # loop_game_events(game_events, game_obj)

            except Exception as error:
                logging.error(
                    "Uncaught exception in live game loop - see below error.")
                logging.error(error)

            # Perform any intermission score changes, charts & sleep
            if game.period.intermission:
                # Uncomment this tomorrow to test the function relocation
                live_sleep_time = live.intermission_loop(game)

            else:
                live_sleep_time = config["script"]["live_sleep_time"]
                logging.info(
                    "Sleeping for configured live game time (%ss).",
                    config["script"]["live_sleep_time"],
                )

            # Now increment the counter sleep for the calculated time above
            game.live_loop_counter += 1
            time.sleep(live_sleep_time)

        elif game.game_state == GameState.FINAL.value:
            logging.info(
                "Game is now over & 'Final' - run end of game functions with increased sleep time."
            )

            livefeed_resp = livefeed.get_livefeed(game.game_id)
            game.update_game(livefeed_resp)

            # If (for some reason) the bot was started after the end of the game
            # We need to re-run the live loop once to parse all of the events
            if not game.events:
                logging.info(
                    "Bot started after game ended, pass livefeed into event factory to fill events."
                )
                live.live_loop(livefeed=livefeed_resp, game=game)

            # shotmaps.generate_shotmaps(game=game)

            # Run all end of game / final functions
            if not game.final_socials.final_score_sent:
                final.final_score(livefeed=livefeed_resp, game=game)

            if not game.final_socials.three_stars_sent:
                final.three_stars(livefeed=livefeed_resp, game=game)

            if not game.final_socials.nst_linetool_sent:
                # thirdparty.nst_linetool(game=game, team=game.preferred_team)
                game.final_socials.nst_linetool_sent = True

            if not game.final_socials.shotmap_retweet:
                game.final_socials.shotmap_retweet = common.search_send_shotmap(
                    game=game)

            if not game.final_socials.hsc_sent:
                final.hockeystatcards(game=game)

            if not game.nst_charts.final_charts:
                logging.info(
                    "NST Charts not yet sent - check if it's ready for us to scrape."
                )
                nst_ready = nst.is_nst_ready(
                    game.preferred_team.short_name) if not args.date else True
                if nst_ready:
                    all_charts = nst.generate_all_charts(game=game)
                    # Chart at Position 0 is the Overview Chart & 1-4 are the existing charts
                    overview_chart = all_charts["overview"]
                    team_charts = all_charts["barcharts"]
                    scatter_charts = all_charts["scatters"]
                    shift_chart = all_charts["shift"]

                    overview_chart_msg = (
                        f"Team Overview stat percentages - 5v5 (SVA) at the "
                        f"end of the game (via @NatStatTrick).")

                    ov_social_ids = socialhandler.send(overview_chart_msg,
                                                       media=overview_chart,
                                                       game_hashtag=True)

                    charts_msg = (
                        f"Individual, on-ice, forward lines & defensive pairs at the "
                        f"end of the game (via @NatStatTrick).")
                    ind_social_ids = socialhandler.send(
                        charts_msg,
                        media=team_charts,
                        game_hashtag=True,
                        reply=ov_social_ids["twitter"],
                    )

                    charts_msg = f"Shift length breakdown at the end of the game (via @NatStatTrick)."
                    shift_social_ids = socialhandler.send(
                        charts_msg,
                        media=shift_chart,
                        game_hashtag=True,
                        reply=ind_social_ids["twitter"],
                    )

                    charts_msg = (
                        f"Quality vs. Quantity & Expected Goals Rate / 60 at the"
                        " end of the game (via @NatStatTrick).")
                    xg60_social_ids = socialhandler.send(
                        charts_msg,
                        media=scatter_charts,
                        game_hashtag=True,
                        reply=shift_social_ids["twitter"],
                    )
                    game.nst_charts.final_charts = True

            # If we have exceeded the number of retries, stop pinging NST
            if game.final_socials.retries_exeeded:
                game.final_socials.nst_linetool_sent = True

            if game.final_socials.all_social_sent:
                logging.info(
                    "All end of game socials sent or retries were exceeded - ending game!"
                )
                end_game_loop(game=game)

            # If all socials aren't sent or retry limit is not exceeded, sleep & check again.
            logging.info(
                "Final loop #%s done - sleep for %s seconds and check again.",
                game.final_socials.retry_count,
                config["script"]["final_sleep_time"],
            )

            game.final_socials.retry_count += 1
            time.sleep(config["script"]["final_sleep_time"])

        else:
            logging.warning(
                "Game State %s is unknown - sleep for 5 seconds and check again.",
                game.game_state)
            time.sleep(config["script"]["live_sleep_time"])