Skip to content

Commit

Permalink
[Hockey] Fix hockeyevents to work with the new API
Browse files Browse the repository at this point in the history
- Update recently added hockeyevents command to work with the new API.
 - Implement a confirmation and cooldown to prevent abuse from heavy discord API requests this command creates. this requires updating minimum bot version to 3.5.3.
- Fix typehints on PlayerFinder since it now returns the SearchPlayer.
- Change TeamData to Team object to potentially utilize in constants data later.
  • Loading branch information
TrustyJAID committed Dec 25, 2023
1 parent ef74ef5 commit 290f1ba
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 86 deletions.
3 changes: 3 additions & 0 deletions hockey/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,9 @@ def __init__(self, days: List[List[ScheduledGame]]):
def from_statsapi(cls, data: dict) -> Schedule:
raise NotImplementedError

def remaining(self) -> List[ScheduledGame]:
return [g for g in self.games if g.game_state.value < GameState.live.value]

@classmethod
def from_nhle(cls, data: dict) -> Schedule:
days = []
Expand Down
53 changes: 50 additions & 3 deletions hockey/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import json
import re
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from enum import Enum
from typing import (
Expand All @@ -28,12 +29,12 @@
from redbot.core.utils import AsyncIter

from .constants import TEAMS
from .player import SimplePlayer
from .teamentry import TeamEntry

if TYPE_CHECKING:
from .game import Game, GameState
from .hockey import Hockey
from .player import SearchPlayer


_ = Translator("Hockey", __file__)
Expand Down Expand Up @@ -63,6 +64,52 @@
VERSUS_RE = re.compile(r"vs\.?|versus", flags=re.I)


@dataclass
class Team:
id: int
name: str
emoji: Union[discord.PartialEmoji, str]
logo: str
home_colour: str
away_colour: str
division: str
conference: str
tri_code: str
nickname: List[str]
team_url: str
timezone: str
active: bool
invite: Optional[str] = None

def __str__(self):
return self.name

@property
def colour(self) -> int:
return int(self.home_colour.replace("#", ""), 16)

@classmethod
def from_json(cls, data: dict) -> Team:
return cls(
id=data.get("id", 0),
name=data.get("name", _("Unknown Team")),
emoji=discord.PartialEmoji.from_str(data.get("emoji", "")),
logo=data.get(
"logo", "https://cdn.bleacherreport.net/images/team_logos/328x328/nhl.png"
),
home_colour=data.get("home", "#000000"),
away_colour=data.get("away", "#ffffff"),
division=data.get("division", _("unknown")),
conference=data.get("conference", _("unknown")),
tri_code=data.get("tri_code", ""),
nickname=data.get("nickname", []),
team_url=data.get("team_url", ""),
timezone=data.get("timezone", "US/Pacific"),
active=data.get("active", False),
invite=data.get("invite"),
)


class Broadcast(NamedTuple):
id: int
name: str
Expand Down Expand Up @@ -193,7 +240,7 @@ async def autocomplete(

class PlayerFinder(discord.app_commands.Transformer):
@classmethod
async def convert(cls, ctx: Context, argument: str) -> List[SimplePlayer]:
async def convert(cls, ctx: Context, argument: str) -> List[SearchPlayer]:
cog = ctx.bot.get_cog("Hockey")
players = await cog.api.search_player(argument)
ret = []
Expand All @@ -206,7 +253,7 @@ async def convert(cls, ctx: Context, argument: str) -> List[SimplePlayer]:

async def transform(
self, interaction: discord.Interaction, argument: str
) -> List[SimplePlayer]:
) -> List[SearchPlayer]:
ctx = await interaction.client.get_context(interaction)
return await self.convert(ctx, argument)

Expand Down
128 changes: 67 additions & 61 deletions hockey/hockeyset.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from redbot.core.data_manager import cog_data_path
from redbot.core.i18n import Translator
from redbot.core.utils.chat_formatting import bold, humanize_list
from redbot.core.utils.views import ConfirmView

from .abc import HockeyMixin
from .constants import TEAMS
Expand Down Expand Up @@ -131,26 +132,36 @@ async def hockey_events(self, ctx: commands.Context):
@commands.admin_or_permissions(manage_guild=True)
@commands.guild_only()
@commands.max_concurrency(1, commands.BucketType.guild)
@commands.cooldown(1, 3600, commands.BucketType.guild)
async def set_team_events(self, ctx: commands.Context, team: TeamFinder):
"""
Create a scheduled server event for all games in the season for one team.
This command can take a while to complete.
"""
start = datetime.now()
end = start + timedelta(days=350)
try:
data = await self.api.get_schedule(
team, start.strftime("%Y-%m-%d"), end.strftime("%Y-%m-%d")
)
data = await self.api.club_schedule_season(team)
except aiohttp.ClientConnectorError:
await ctx.send(
_("There's an issue accessing the NHL API at the moment. Try again later.")
)
log.exception("Error accessing NHL API")
return
number_of_games = str(len(data.get("dates", [])))
await ctx.send(f"Creating events for {number_of_games} games.")
games = data.remaining()
number_of_games = str(len(games))
view = ConfirmView(ctx.author)
view.message = await ctx.send(
_(
"This will create {number_of_games} discord events for the remaining {team} games."
"This can take a long time to complate. Are you sure you want to run this?"
).format(number_of_games=number_of_games, team=team),
view=view,
)
await view.wait()
if not view.result:
ctx.command.reset_cooldown(ctx)
await ctx.send(_("Okay I will not create the events."))
return
images_path = cog_data_path(self) / "teamlogos"
if not os.path.isdir(images_path):
os.mkdir(images_path)
Expand All @@ -159,62 +170,57 @@ async def set_team_events(self, ctx: commands.Context, team: TeamFinder):
event_id = re.search(r"\n(\d{6,})", event.description)
existing_events[event_id.group(1)] = event
async with ctx.typing():
for date in data["dates"]:
for game in date["games"]:
start = datetime.strptime(game["gameDate"], "%Y-%m-%dT%H:%M:%SZ").replace(
tzinfo=timezone.utc
)
end = start + timedelta(hours=3)
away = game["teams"]["away"]["team"]["name"]
home = game["teams"]["home"]["team"]["name"]
image_team = away if team == home else home
image_file = images_path / f"{image_team}.png"
if not os.path.isfile(image_file):
async with self.session.get(TEAMS[image_team]["logo"]) as resp:
image = await resp.read()
with image_file.open("wb") as outfile:
outfile.write(image)
image = open(image_file, "rb")
name = f"{away} @ {home}"
broadcasts = humanize_list(
[b.get("name", "Unknown") for b in game.get("broadcasts", [])]
)
description = name
if broadcasts:
description += f"\nBroadcasts: {broadcasts}"
game_id = str(game["gamePk"])
description += f"\n\n{game_id}"
if game_id in existing_events:
try:
if existing_events[game_id].start_time != start:
await existing_events[game_id].edit(
start_time=start, end_time=end, reason="Start time changed"
)
if existing_events[game_id].description != description:
await existing_events[game_id].edit(
description=description, reason="Description has changed"
)
except Exception:
# I don't care if these don't edit properly
pass
continue
for game in games:
start = game.game_start
end = start + timedelta(hours=3)
home = game.home_team
away = game.away_team
image_team = away if team == home else home
image_file = images_path / f"{image_team}.png"
if not os.path.isfile(image_file):
async with self.session.get(TEAMS[image_team]["logo"]) as resp:
image = await resp.read()
with image_file.open("wb") as outfile:
outfile.write(image)
image = open(image_file, "rb")
name = f"{away} @ {home}"
broadcasts = humanize_list([b.get("network", "Unknown") for b in game.broadcasts])
description = name
if broadcasts:
description += f"\nBroadcasts: {broadcasts}"
game_id = str(game.id)
description += f"\n\n{game_id}"
if game_id in existing_events:
try:
await ctx.guild.create_scheduled_event(
name=f"{away} @ {home}",
description=description,
start_time=start,
location=game.get("venue", {}).get("name", "Unknown place"),
end_time=end,
entity_type=discord.EntityType.external,
image=image.read(),
privacy_level=discord.PrivacyLevel.guild_only,
)
if existing_events[game_id].start_time != start:
await existing_events[game_id].edit(
start_time=start, end_time=end, reason="Start time changed"
)
if existing_events[game_id].description != description:
await existing_events[game_id].edit(
description=description, reason="Description has changed"
)
except Exception:
log.exception(
"Error creating scheduled event in %s for team %s", ctx.guild.id, team
)
image.close()
await asyncio.sleep(1)
# I don't care if these don't edit properly
pass
continue
try:
await ctx.guild.create_scheduled_event(
name=f"{away} @ {home}",
description=description,
start_time=start,
location=game.venue,
end_time=end,
entity_type=discord.EntityType.external,
image=image.read(),
privacy_level=discord.PrivacyLevel.guild_only,
)
except Exception:
log.exception(
"Error creating scheduled event in %s for team %s", ctx.guild.id, team
)
image.close()
await asyncio.sleep(1)
await ctx.send(f"Finished creating events for {number_of_games} games.")

@hockey_slash.command(name="global")
Expand Down
2 changes: 1 addition & 1 deletion hockey/info.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"hidden": false,
"install_msg": "Use `[p]hockey`, `[p]hockeyset` and `[p]gdc` to see what commands are available.",
"max_bot_version": "0.0.0",
"min_bot_version": "3.5.0",
"min_bot_version": "3.5.3",
"min_python_version": [
3,
7,
Expand Down
25 changes: 4 additions & 21 deletions hockey/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from tabulate import tabulate

from .constants import HEADSHOT_URL, TEAMS
from .helper import Team

_ = Translator("Hockey", __file__)

Expand Down Expand Up @@ -343,15 +344,6 @@ def from_json(cls, data: dict) -> CareerStats:
)


@dataclass
class TeamData:
id: int
name: str
emoji: Union[discord.PartialEmoji, str]
logo: str
colour: int


@dataclass
class SeasonStats(Stats):
season: Optional[int] = None
Expand Down Expand Up @@ -578,24 +570,15 @@ def description(self) -> str:
msg += " | ".join(links)
return msg

def get_team_data(self, *, team_name: Optional[str] = None) -> TeamData:
def get_team_data(self, *, team_name: Optional[str] = None) -> Team:
try:
team_id = self.currentTeamId
if team_name is None:
log.verbose("SimplePlayer team_id: %s", team_id)
team_name = [name for name, team in TEAMS.items() if team["id"] == team_id][0]
else:
team_id = TEAMS[team_name]["id"]
colour = int(TEAMS[team_name]["home"].replace("#", ""), 16)
logo = TEAMS[team_name]["logo"]
emoji = discord.PartialEmoji.from_str(TEAMS[team_name]["emoji"])
return Team.from_json(TEAMS[team_name])
except (IndexError, KeyError):
team_name = _("No Team")
team_id = 0
colour = 0xFFFFFF
logo = "https://cdn.bleacherreport.net/images/team_logos/328x328/nhl.png"
emoji = ""
return TeamData(id=team_id, name=team_name, emoji=emoji, logo=logo, colour=colour)
return Team.from_json({})

def get_embed(self, season: Optional[str] = None) -> discord.Embed:
team_data = self.get_team_data()
Expand Down

0 comments on commit 290f1ba

Please sign in to comment.