Skip to content

Commit

Permalink
[Hockey] Fix detection of game finals, periodrecaps, and period starts.
Browse files Browse the repository at this point in the history
  • Loading branch information
TrustyJAID committed Nov 14, 2023
1 parent 9d2b72c commit d155046
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 56 deletions.
49 changes: 29 additions & 20 deletions hockey/api.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from __future__ import annotations

import json
from dataclasses import dataclass
from datetime import datetime, timezone
from enum import Enum
from pathlib import Path
from typing import Dict, List, Optional, Tuple, TypedDict

import aiohttp
Expand Down Expand Up @@ -231,7 +233,6 @@ def description(self, data: dict) -> str:
return description

def get_highlight(self, content: Optional[dict]) -> Optional[str]:
log.debug("Looking for highlight for goal")
if content is None:
return None
clip_id = None
Expand All @@ -241,14 +242,8 @@ def get_highlight(self, content: Optional[dict]) -> Optional[str]:
log.debug("ignoring period because it doesn't match.")
continue
for goal in period.get("goals", []):
log.debug(
"Checking if goal time in period matches. landing: %s - current %s",
goal.get("timeInPeriod", ""),
self.time_in_period,
)
if goal.get("timeInPeriod", "") == self.time_in_period:
clip_id = goal.get("highlightClip", None)
log.debug("Found highlight clip")
if clip_id is not None:
return VIDEO_URL.format(clip_id=clip_id)
return None
Expand Down Expand Up @@ -358,11 +353,12 @@ def from_nhle(cls, data: dict) -> Schedule:


class HockeyAPI:
def __init__(self):
def __init__(self, testing: bool = False):
self.session = aiohttp.ClientSession(
headers={"User-Agent": "Red-DiscordBot Trusty-cogs Hockey"}
)
self.base_url = None
self.testing = testing

async def close(self):
await self.session.close()
Expand All @@ -375,35 +371,35 @@ async def get_schedule(
team: Optional[str] = None,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None,
) -> dict:
) -> Schedule:
raise NotImplementedError

async def get_games_list(
self,
team: Optional[str] = None,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None,
) -> List[dict]:
) -> List[Game]:
raise NotImplementedError

async def get_games(
self,
team: Optional[str] = None,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None,
) -> List[dict]:
) -> List[Game]:
raise NotImplementedError

async def get_game_from_id(self, game_id: int) -> dict:
async def get_game_from_id(self, game_id: int) -> Game:
raise NotImplementedError

async def get_game_from_url(self, game_url: str) -> dict:
async def get_game_from_url(self, game_url: str) -> Game:
raise NotImplementedError


class StatsAPI(HockeyAPI):
def __init__(self):
super().__init__()
def __init__(self, testing: bool = False):
super().__init__(testing)
self.base_url = "https://statsapi.web.nhl.com"

async def get_game_content(self, game_id: int):
Expand Down Expand Up @@ -495,7 +491,7 @@ async def get_games(
continue
return return_games_list

async def get_game_from_id(self, game_id: int) -> dict:
async def get_game_from_id(self, game_id: int) -> Game:
url = f"{self.base_url}/api/v1/game/{game_id}/feed/live"
async with self.session.get(url) as resp:
data = await resp.json()
Expand Down Expand Up @@ -694,8 +690,8 @@ async def to_game(self, data: dict, content: Optional[dict]) -> Game:


class NewAPI(HockeyAPI):
def __init__(self):
super().__init__()
def __init__(self, testing: bool = False):
super().__init__(testing)
self.base_url = "https://api-web.nhle.com/v1"

async def get_game_content(self, game_id: int):
Expand All @@ -711,6 +707,9 @@ def team_to_abbrev(self, team: str) -> Optional[str]:
return TEAMS.get(team_name, {}).get("tri_code", None)

async def schedule_now(self) -> Schedule:
if self.testing:
data = await self.load_testing_data("testschedule.json")
return Schedule.from_nhle(data)
async with self.session.get(f"{self.base_url}/schedule/now") as resp:
if resp.status != 200:
log.error("Error accessing the Schedule for now. %s", resp.status)
Expand Down Expand Up @@ -853,7 +852,16 @@ async def get_games(
return [await self.get_game_from_id(g.id) for g in schedule.days[0]]
return []

async def load_testing_data(self, file_name: str) -> dict:
path = Path(__file__).parent.resolve() / file_name
with path.open("r") as infile:
data = json.loads(infile.read())
return data

async def get_game_from_id(self, game_id: int) -> Game:
if self.testing:
data = await self.load_testing_data("testgame.json")
return await self.to_game(data)
data = await self.gamecenter_pbp(game_id)
try:
landing = await self.gamecenter_landing(game_id)
Expand Down Expand Up @@ -898,7 +906,8 @@ async def get_game_recap(self, game_id: int) -> Optional[str]:
async def to_game(self, data: dict, content: Optional[dict] = None) -> Game:
game_id = data["id"]
period = data.get("period", -1)
game_state = GameState.from_nhle(data["gameState"], period)
period_time_left = data.get("clock", {}).get("timeRemaining")
game_state = GameState.from_nhle(data["gameState"], period, period_time_left)
home_id = data.get("homeTeam", {}).get("id", -1)
home_team = TEAM_IDS.get(home_id, "Unknown Team")
away_id = data.get("awayTeam", {}).get("id", -1)
Expand All @@ -918,7 +927,7 @@ async def to_game(self, data: dict, content: Optional[dict] = None) -> Game:
first_star = None
second_star = None
third_star = None
period_time_left = data.get("clock", {}).get("timeRemaining")

recap_url = None
if content:
recap = content.get("summary", {}).get("gameVideo", {}).get("condensedGame")
Expand Down
69 changes: 40 additions & 29 deletions hockey/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ class GameState(Enum):
live_end_first = 6
live_end_second = 7
live_end_third = 8
final = 9
over = 9
final = 10
official_final = 11

def __str__(self):
return self.name.replace("_", " ").title()

def is_preview(self):
return self in (
Expand Down Expand Up @@ -66,13 +71,14 @@ def from_statsapi(cls, game_state: str) -> GameState:
}.get(game_state, GameState.unknown)

@classmethod
def from_nhle(cls, game_state: str, period: int) -> GameState:
if period == 2:
return GameState.live_end_first
elif period == 3 and game_state == "LIVE":
return GameState.live_end_second
if period > 3 and game_state in ["LIVE", "CRIT"]:
return GameState.live_end_third
def from_nhle(cls, game_state: str, period: int, remaining: Optional[str] = None) -> GameState:
if remaining and game_state not in ["CRIT", "OVER", "FINAL", "OFF"]:
if period == 1 and remaining == "00:00":
return GameState.live_end_first
elif period == 2 and remaining == "00:00":
return GameState.live_end_second
elif period == 3 and remaining == "00:00":
return GameState.live_end_third
return {
"FUT": GameState.preview,
"PRE": GameState.preview,
Expand All @@ -83,9 +89,9 @@ def from_nhle(cls, game_state: str, period: int) -> GameState:
# These previews are only my internal code, not sure if they'll be used
"LIVE": GameState.live,
"CRIT": GameState.live,
"OVER": GameState.final,
"OVER": GameState.over,
"FINAL": GameState.final,
"OFF": GameState.final,
"OFF": GameState.official_final,
}.get(game_state, GameState.unknown)


Expand Down Expand Up @@ -248,8 +254,6 @@ class Game:
away_score: int
game_start: datetime
goals: List[Goal]
home_goals: list
away_goals: list
home_abr: str
away_abr: str
period_ord: str
Expand Down Expand Up @@ -324,11 +328,11 @@ def __repr__(self):
)

@property
def home_goals(self):
def home_goals(self) -> List[Goal]:
return [g for g in self.goals if g.team_name == self.home_team]

@property
def away_goals(self):
def away_goals(self) -> List[Goal]:
return [g for g in self.goals if g.team_name == self.away_team]

@property
Expand Down Expand Up @@ -417,7 +421,7 @@ async def make_game_embed(
)
# timestamp = datetime.strptime(self.game_start, "%Y-%m-%dT%H:%M:%SZ")
title = "{away} @ {home} {state}".format(
away=self.away_team, home=self.home_team, state=self.game_state.name
away=self.away_team, home=self.home_team, state=str(self.game_state)
)
colour = (
int(TEAMS[self.home_team]["home"].replace("#", ""), 16)
Expand Down Expand Up @@ -565,7 +569,7 @@ async def game_state_embed(self) -> discord.Embed:
"""
# post_state = ["all", self.home_team, self.away_team]
# timestamp = datetime.strptime(self.game_start, "%Y-%m-%dT%H:%M:%SZ")
title = f"{self.away_team} @ {self.home_team} {self.game_state.name}"
title = f"{self.away_team} @ {self.home_team} {str(self.game_state)}"
em = discord.Embed(timestamp=self.game_start)
home_field = "{0} {1} {0}".format(self.home_emoji, self.home_team)
away_field = "{0} {1} {0}".format(self.away_emoji, self.away_team)
Expand Down Expand Up @@ -608,7 +612,7 @@ async def game_state_text(self) -> str:
time_string = f"<t:{self.timestamp}>"
em = (
f"{self.away_emoji}{self.away_team} @ {self.home_emoji}{self.home_team} "
f"{self.game_state.name}\n({time_string})"
f"{str(self.game_state)}\n({time_string})"
)
if not self.game_state.is_preview():
em = (
Expand Down Expand Up @@ -691,6 +695,9 @@ async def check_game_state(self, bot: Red, count: int = 0) -> bool:
home = await get_team(bot, self.home_team, self.game_start_str, self.game_id)
try:
old_game_state = GameState(home["game_state"])
log.trace(
"Old Game State for %s @ %s is %r", self.away_team, self.home_team, old_game_state
)
except ValueError:
old_game_state = GameState.unknown
# away = await get_team(self.away_team)
Expand Down Expand Up @@ -749,34 +756,38 @@ async def check_game_state(self, bot: Red, count: int = 0) -> bool:
# Check if there's goals only if there are goals
await self.check_team_goals(bot)
if end_first and old_game_state is not GameState.live_end_first:
log.debug("End of the first period")
log.debug("End of the first period %s @ %s", self.away_team, self.home_team)
await self.period_recap(bot, "1st")
await self.save_game_state(bot, "END1st")
if end_second and old_game_state is not GameState.live_end_second:
log.debug("End of the second period")
log.debug("End of the second period %s @ %s", self.away_team, self.home_team)
await self.period_recap(bot, "2nd")
await self.save_game_state(bot, "END2nd")
if end_third and old_game_state is not GameState.live_end_third:
log.debug("End of the third period")
log.debug("End of the third period %s @ %s", self.away_team, self.home_team)
await self.period_recap(bot, "3rd")
await self.save_game_state(bot, "END3rd")

if self.game_state is GameState.final:
if self.game_state.value > GameState.over.value:
if (self.home_score + self.away_score) != 0:
# Check if there's goals only if there are goals
await self.check_team_goals(bot)
if end_third and home["game_state"] not in ["LiveEND3rd", "FinalEND3rd"]:
log.debug("End of the third period")
if end_third and old_game_state not in [GameState.final]:
log.debug("End of the third period %s @ %s", self.away_team, self.home_team)
await self.period_recap(bot, "3rd")
await self.save_game_state(bot, "END3rd")

if (
self.first_star is not None
and self.second_star is not None
and self.third_star is not None
and len(self.home_goals) == self.home_score
and len(self.away_goals) == self.away_score
) or count >= 20:
(
self.first_star is not None
and self.second_star is not None
and self.third_star is not None
and len(self.home_goals) == self.home_score
and len(self.away_goals) == self.away_score
)
or count >= 20
or self.game_state is GameState.official_final
):
"""Final game state checks"""
if old_game_state is not self.game_state:
# Post game final data and check for next game
Expand Down
10 changes: 8 additions & 2 deletions hockey/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,13 @@ async def autocomplete(

def game_states_to_int(states: List[str]) -> List[int]:
ret = []
options = {"Preview": [1, 2, 3, 4], "Live": [5], "Final": [9], "Goal": [], "Recap": [6, 7, 8]}
options = {
"Preview": [1, 2, 3, 4],
"Live": [5],
"Final": [9, 10, 11],
"Goal": [],
"Periodrecap": [6, 7, 8],
}
for state in states:
ret += options.get(state, [])
return ret
Expand All @@ -549,7 +555,7 @@ async def check_to_post(
bot: Red,
channel: discord.TextChannel,
channel_data: dict,
post_state: str,
post_state: List[str],
game_state: str,
is_goal: bool = False,
) -> bool:
Expand Down
8 changes: 4 additions & 4 deletions hockey/hockey.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ async def game_check_loop(self) -> None:
continue
if schedule.days != []:
for game in schedule.days[0]:
if game.game_state is GameState.final:
if game.game_state.value >= GameState.final.value:
continue
if game.schedule_state != "OK":
continue
Expand Down Expand Up @@ -393,7 +393,7 @@ async def game_check_loop(self) -> None:
log.exception("Error checking game state: ")
posted_final = False
if (
game.game_state in [GameState.live]
game.game_state.is_live()
and not self.current_games[game_id]["disabled_buttons"]
):
log.verbose("Disabling buttons for %r", game)
Expand All @@ -409,7 +409,7 @@ async def game_check_loop(self) -> None:
game.home_score,
)

if game.game_state is GameState.final:
if game.game_state.value > GameState.over.value:
self.current_games[game_id]["count"] += 1
if posted_final:
try:
Expand All @@ -424,7 +424,7 @@ async def game_check_loop(self) -> None:
to_delete.append(link)
for link in to_delete:
del self.current_games[link]
if not self.TEST_LOOP:
if not self.api.testing:
await asyncio.sleep(60)
else:
await asyncio.sleep(10)
Expand Down
2 changes: 1 addition & 1 deletion hockey/pickems.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def __init__(
self.link = link
self._should_save: bool = True
# Start true so we save instantiated pickems
self.game_type: str = game_type
self.game_type: GameType = game_type
super().__init__(timeout=None)
disabled_buttons = datetime.now(tz=timezone.utc) > self.game_start
self.home_button = PickemsButton(
Expand Down

0 comments on commit d155046

Please sign in to comment.