Skip to content

Commit

Permalink
[Hockey] Make saving goal message IDs atomic
Browse files Browse the repository at this point in the history
- Fix display of shootouts missing shots on goal
- Add player sweater number to goal descriptions
  • Loading branch information
TrustyJAID committed Feb 25, 2024
1 parent d4b6385 commit 67a6090
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 37 deletions.
19 changes: 13 additions & 6 deletions hockey/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class GoalData(TypedDict):
strength_code: str
empty_net: bool
event: str
game_id: int
link: Optional[str]


Expand Down Expand Up @@ -260,7 +261,7 @@ def is_goal_or_shot(self) -> bool:
if self.type_code is GameEventTypeCode.GOAL:
return True
elif (
self.type_code is GameEventTypeCode.MISSED_SHOT
self.type_code in [GameEventTypeCode.MISSED_SHOT, GameEventTypeCode.SHOT_ON_GOAL]
and self.period_descriptor.get("periodType", None) == "SO"
):
return True
Expand All @@ -285,22 +286,25 @@ def description(self, data: dict) -> str:
if key in ["scoringPlayerId", "shootingPlayerId"]:
player = self.get_player(value, data)
player_name = player.name if player else _("Unknown")
player_num = f"#{player.sweaterNumber} " if player else ""
total = self.details.get("scoringPlayerTotal", 0)
description += f"{player_name} ({total}) {shot_type}"
description += f"{player_num}{player_name} ({total}) {shot_type}"

if key == "assist1PlayerId":
player = self.get_player(value, data)
player_name = player.name if player else _("Unknown")
player_num = f"#{player.sweaterNumber} " if player else ""
total = self.details.get("assist1PlayerTotal", 0)
description += _(" assists: {player_name} ({total})").format(
player_name=player_name, total=total
description += _(" assists: {player_num}{player_name} ({total})").format(
player_num=player_num, player_name=player_name, total=total
)
if key == "assist2PlayerId":
player = self.get_player(value, data)
player_name = player.name if player else _("Unknown")
player_num = f"#{player.sweaterNumber} " if player else ""
total = self.details.get("assist2PlayerTotal", 0)
description += _(", {player_name} ({total})").format(
player_name=player_name, total=total
description += _(", {player_num}{player_name} ({total})").format(
player_num=player_num, player_name=player_name, total=total
)

return description
Expand Down Expand Up @@ -372,6 +376,7 @@ def to_goal(self, data: dict, content: Optional[dict] = None) -> Goal:
period_ord = ORDINALS.get(self.period)
home = data["homeTeam"]["id"] == team_id
away_sog, home_sog = self.get_sog(data)
game_id = data.get("id", -1)
return Goal(
goal_id=self.id,
team=team,
Expand All @@ -394,6 +399,8 @@ def to_goal(self, data: dict, content: Optional[dict] = None) -> Goal:
assisters=assisters,
home_shots=home_sog,
away_shots=away_sog,
game_id=game_id,
type_code=self.type_code,
)


Expand Down
22 changes: 11 additions & 11 deletions hockey/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,11 @@ def away_emoji(self) -> Union[discord.PartialEmoji, str]:

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

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

@property
def recap_url(self):
Expand Down Expand Up @@ -321,6 +321,12 @@ def gameflow_url(
diff = "cfdiff" if corsi else "xgdiff"
return f"{base_url}{self.season}-{str(self.game_id)[5:]}-{diff}-{strength}.png"

def get_goal_from_id(self, goal_id: int) -> Optional[Goal]:
for goal in self.goals:
if goal.goal_id == goal_id:
return goal
return None

async def make_game_embed(
self,
include_plays: bool = False,
Expand Down Expand Up @@ -894,7 +900,7 @@ async def check_team_goals(self, bot: Red) -> None:
# all_data = await get_team("all")
team_list = await bot.get_cog("Hockey").config.teams()
# post_state = ["all", self.home_team, self.away_team]

cog = bot.get_cog("Hockey")
# home_goal_ids = [goal.goal_id for goal in self.home_goals]
# away_goal_ids = [goal.goal_id for goal in self.away_goals]

Expand All @@ -912,14 +918,14 @@ async def check_team_goals(self, bot: Red) -> None:
bot.dispatch("hockey_goal", self, goal)
# goal.home_shots = self.home_shots
# goal.away_shots = self.away_shots
msg_list = await goal.post_team_goal(bot, self)
team_list.remove(team_data[goal.team_name])
team_data[goal.team_name]["goal_id"][goal.goal_id] = {
"goal": goal.to_json(),
"messages": msg_list,
"messages": [],
}
team_list.append(team_data[goal.team_name])
await bot.get_cog("Hockey").config.teams.set(team_list)
asyncio.create_task(goal.post_team_goal(bot, self))
continue
if str(goal.goal_id) in team_data[goal.team_name]["goal_id"]:
# attempts to edit the goal if the scorers have changed
Expand All @@ -931,12 +937,6 @@ async def check_team_goals(self, bot: Red) -> None:
# Shots should not update as the game continues
bot.dispatch("hockey_goal_edit", self, goal)
old_msgs = team_data[goal.team_name]["goal_id"][str(goal.goal_id)]["messages"]
team_list.remove(team_data[goal.team_name])
team_data[goal.team_name]["goal_id"][str(goal.goal_id)][
"goal"
] = goal.to_json()
team_list.append(team_data[goal.team_name])
await bot.get_cog("Hockey").config.teams.set(team_list)
if old_msgs:
asyncio.create_task(goal.edit_team_goal(bot, self, old_msgs))
# attempts to delete the goal if it was called back
Expand Down
62 changes: 43 additions & 19 deletions hockey/goal.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
from .helper import Team, check_to_post, get_channel_obj, get_team

if TYPE_CHECKING:
from .api import GoalData, Player
from .api import GameEventTypeCode, GoalData, Player
from .game import Game
from .hockey import Hockey


_ = Translator("Hockey", __file__)
Expand Down Expand Up @@ -66,6 +67,8 @@ def __init__(self, **kwargs):
self.situation = kwargs.get("situation")
self.scorer: Player = kwargs.get("scorer")
self.assisters: List[Player] = kwargs.get("assisters")
self.game_id: int = kwargs.get("game_id")
self.type_code: GameEventTypeCode = kwargs.get("type_code")

def __repr__(self):
return "<Hockey Goal team={0.team_name} id={0.goal_id} >".format(self)
Expand Down Expand Up @@ -98,6 +101,7 @@ def to_json(self) -> dict:
"image": self.image,
"home_shots": self.home_shots,
"away_shots": self.away_shots,
"game_id": self.game_id,
}

@classmethod
Expand Down Expand Up @@ -214,6 +218,8 @@ async def post_team_goal(self, bot: Red, game_data: Game) -> List[Tuple[int, int
Creates embed and sends message if a team has scored a goal
"""
# scorer = self.headshots.format(goal["players"][0]["player"]["id"])
cog: Hockey = bot.get_cog("Hockey")
event = cog.get_goal_save_event(game_data.game_id, self.goal_id)
post_state = ["all", game_data.home_team, game_data.away_team]
msg_list = []
goal_embed = await self.goal_post_embed(game_data)
Expand All @@ -240,6 +246,12 @@ async def post_team_goal(self, bot: Red, game_data: Game) -> List[Tuple[int, int
continue
else:
msg_list.append(channel)
config = cog.config
async with config.teams() as teams:
for team in teams:
if team["team_name"] == self.team_name and team["game_id"] == game_data.game_id:
team["goal_id"][str(self.goal_id)]["messages"] = msg_list
event.set()
return msg_list

async def actually_post_goal(
Expand Down Expand Up @@ -397,8 +409,13 @@ async def edit_team_goal(
"""
# scorer = self.headshots.format(goal["players"][0]["player"]["id"])
# post_state = ["all", game_data.home_team, game_data.away_team]
em = await self.goal_post_embed(game_data)
text = await self.goal_post_text(game_data)
cog: Hockey = bot.get_cog("Hockey")
event = cog.get_goal_save_event(game_data.game_id, self.goal_id)
await event.wait()
updated_goal = cog.get_current_goal(game_data.game_id, self.goal_id)
event.clear()
em = await updated_goal.goal_post_embed(game_data)
text = await updated_goal.goal_post_text(game_data)
if og_msg is None:
return
async for guild_id, channel_id, message_id in AsyncIter(og_msg, delay=5, steps=5):
Expand All @@ -410,15 +427,20 @@ async def edit_team_goal(
continue
if channel.is_news():
asyncio.create_task(self.edit_goal(bot, channel, message_id, em, text))
# This is to prevent endlessly waiting incase someone
# decided to publish one of our messages we want to edit
# if we did bounded_gather here the gather would wait until
# rate limits are up for editing that one message
# in this case we can send off the task to do it's thing
# and forget about it. If one never finishes I don't care
else:
await self.edit_goal(bot, channel, message_id, em, text)
# This is to prevent endlessly waiting incase someone
# decided to publish one of our messages we want to edit
# if we did bounded_gather here the gather would wait until
# rate limits are up for editing that one message
# in this case we can send off the task to do it's thing
# and forget about it. If one never finishes I don't care

config = cog.config
async with config.teams() as teams:
for team in teams:
if team["team_name"] == self.team_name and team["game_id"] == game_data.game_id:
team["goal_id"][str(self.goal_id)]["goal"] = self.to_json()
event.set()
return

async def edit_goal(
Expand All @@ -430,7 +452,6 @@ async def edit_goal(
text: str,
) -> None:
try:

if channel.guild.me.is_timed_out():
return
try:
Expand Down Expand Up @@ -489,13 +510,15 @@ async def get_shootout_display(self, game: Game) -> Tuple[str, str]:
continue
if goal.scorer_id in game.home_roster:
scorer = game.home_roster[goal.scorer_id].name
if goal.event in ["Shot", "Missed Shot"]:
home_msg += miss.format(scorer=scorer)
if goal.event in ["Goal"]:
home_msg += score.format(scorer=scorer)
scorer_num = game.home_roster[goal.scorer_id].sweaterNumber
if goal.type_code.value in [506, 507]: # Shots on Goal and Missed Shots
home_msg += miss.format(scorer=f"#{scorer_num} {scorer}")
if goal.type_code.value in [505]:
home_msg += score.format(scorer=f"#{scorer_num} {scorer}")

for goal in game.away_goals:
scorer = ""
scorer_num = ""
if goal.period_ord != "SO":
continue
if goal.goal_id > self.goal_id:
Expand All @@ -508,10 +531,11 @@ async def get_shootout_display(self, game: Game) -> Tuple[str, str]:
continue
if goal.scorer_id in game.away_roster:
scorer = game.away_roster[goal.scorer_id].name
if goal.event in ["Shot", "Missed Shot"]:
away_msg += miss.format(scorer=scorer)
if goal.event in ["Goal"]:
away_msg += score.format(scorer=scorer)
scorer_num = game.away_roster[goal.scorer_id].sweaterNumber
if goal.type_code.value in [506, 507]: # Shots on Goal and Missed Shots
away_msg += miss.format(scorer=f"#{scorer_num} {scorer}")
if goal.type_code.value in [505]:
away_msg += score.format(scorer=f"#{scorer_num} {scorer}")

return home_msg, away_msg

Expand Down
26 changes: 25 additions & 1 deletion hockey/hockey.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from __future__ import annotations

import asyncio
import json
from abc import ABC
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any, Dict, List, Literal, Optional
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional

import aiohttp
import discord
Expand All @@ -26,6 +28,10 @@
from .pickems import Pickems
from .standings import Standings

if TYPE_CHECKING:
from .game import Game
from .goal import Goal

_ = Translator("Hockey", __file__)

log = getLogger("red.trusty-cogs.Hockey")
Expand Down Expand Up @@ -158,6 +164,7 @@ def __init__(self, bot):
self._repo = ""
self._commit = ""
self.api = NewAPI()
self.saving_goals = {}

def format_help_for_context(self, ctx: commands.Context) -> str:
"""
Expand Down Expand Up @@ -427,6 +434,7 @@ async def game_check_loop(self) -> None:
to_delete.append(link)
for link in to_delete:
del self.current_games[link]
del self.saving_goals[link]
if not self.api.testing:
await asyncio.sleep(60)
else:
Expand Down Expand Up @@ -526,6 +534,22 @@ async def change_custom_emojis(self, attachments: List[discord.Attachment]) -> N
with path.open("w") as outfile:
outfile.write(constants_string)

def get_current_game_data(self, game_id: int) -> Optional[Game]:
return self.current_games.get(game_id, {}).get("game")

def get_current_goal(self, game_id: int, goal_id: int) -> Optional[Goal]:
game = self.get_current_game_data(game_id)
if game:
return game.get_goal_from_id(goal_id)
return None

def get_goal_save_event(self, game_id: int, goal_id: int) -> asyncio.Event:
if game_id not in self.saving_goals:
self.saving_goals[game_id] = {}
if goal_id not in self.saving_goals[game_id]:
self.saving_goals[game_id][goal_id] = asyncio.Event()
return self.saving_goals[game_id][goal_id]

async def wait_for_file(self, ctx: commands.Context) -> None:
"""
Waits for the author to upload a file
Expand Down

0 comments on commit 67a6090

Please sign in to comment.