From a6e06e9e98f9a834efb7753c71931e45b2fa32fd Mon Sep 17 00:00:00 2001 From: insert Date: Thu, 26 Jun 2025 21:32:29 -0400 Subject: [PATCH] refactor: further optimizations for live games --- cogs/liveupdate.py | 352 +++++++++++++++++++++++---------------------- 1 file changed, 182 insertions(+), 170 deletions(-) diff --git a/cogs/liveupdate.py b/cogs/liveupdate.py index a4d6c47..8446d2a 100644 --- a/cogs/liveupdate.py +++ b/cogs/liveupdate.py @@ -5,6 +5,7 @@ from datetime import datetime, timedelta, timezone import requests import asyncio import nextcord +import aiohttp import aiosqlite as sqlite3 from nextcord.ext import commands, application_checks, tasks from nextcord import TextInputStyle, IntegrationType @@ -15,66 +16,71 @@ async def livegameworker(self,serverid,userid,channelid,messageid,gameid,offset) begin = timeit.default_timer() lastserverid = serverid try: - channel = self.bot.get_channel(channelid) - message = await channel.fetch_message(messageid) - data = requests.get(f"https://mmolb.com/api/game/{gameid}/live?after={offset}").json() - if len(data["entries"]) > 0: - data = requests.get(f"https://mmolb.com/api/game/{gameid}/live?after={offset-((7-len(data["entries"]) if offset > 7 else offset))}").json() - basedata = requests.get(f"https://mmolb.com/api/game/{gameid}").json() - finalstr = "" - offsetadd = 0 - for i in data["entries"]: - finalstr += f"\n{i['message'].replace("", "**").replace("", "**")}" - offsetadd += 1 - if i["event"] == "Recordkeeping": - await self.bot.db.execute(f""" - DELETE from liveupdate WHERE messageid = {messageid} - """) - #await self.bot.db.commit() - color = tuple(int(basedata["HomeTeamColor"][i:i+2], 16) for i in (0, 2, 4)) - embed = nextcord.Embed(title=f"{basedata["AwayTeamName"]} {basedata["AwayTeamEmoji"]} **{data["entries"][-1]["away_score"]}** vs {basedata["HomeTeamName"]} {basedata["HomeTeamEmoji"]} **{data["entries"][-1]["home_score"]}**", - description=f"{"Bottom" if data["entries"][-1]["inning_side"] == 1 else "Top"} of the {data["entries"][-1]["inning"]}", - colour = nextcord.Color.from_rgb(color[0], color[1], color[2])) - embed.set_footer(text=gameid) - match data["entries"][-1]["balls"]: - case 1: - embed.add_field(name="Balls",value="🔴⭕⭕") - case 2: - embed.add_field(name="Balls",value="🔴🔴⭕") - case 3: - embed.add_field(name="Balls",value="🔴🔴🔴") - case _: - embed.add_field(name="Balls",value="⭕⭕⭕") - match data["entries"][-1]["strikes"]: - case 1: - embed.add_field(name="Strikes",value="🔴⭕") - case 2: - embed.add_field(name="Strikes",value="🔴🔴") - case _: - embed.add_field(name="Strikes",value="⭕⭕") - match data["entries"][-1]["outs"]: - case 1: - embed.add_field(name="Outs",value="🔴⭕") - case 2: - embed.add_field(name="Outs",value="🔴🔴") - case _: - embed.add_field(name="Outs",value="⭕⭕") - embed.add_field(name="Batting",value=data["entries"][-1]["batter"], inline=True) - embed.add_field(name="Pitching",value=data["entries"][-1]["pitcher"], inline=True) - embed.add_field(name="On Deck",value=data["entries"][-1]["on_deck"], inline=True) - embed.add_field(name=f"Last 7 events",value=finalstr,inline=False) - embed.set_thumbnail(f"https://insertapp.net/mmolbbot/assets/diamond_{data["entries"][-1]["on_1b"]}_{data["entries"][-1]["on_2b"]}_{data["entries"][-1]["on_3b"]}.png") - embed.set_footer(text=f"Embed too big? Need historical data? consider classic mode.") - await message.edit(content="",embed=embed) - await self.bot.db.execute(f""" - UPDATE liveupdate set offset = {offset+offsetadd} WHERE messageid = '{messageid}' - """) + async with aiohttp.ClientSession() as session: + channel = self.bot.get_channel(channelid) + message = await channel.fetch_message(messageid) + data = await session.get(f"https://mmolb.com/api/game/{gameid}/live?after={offset}") + data = await data.json() + if len(data["entries"]) > 0: + data = await session.get(f"https://mmolb.com/api/game/{gameid}/live?after={offset-((7-len(data["entries"]) if offset > 7 else offset))}") + data = await data.json() + basedata = await session.get(f"https://mmolb.com/api/game/{gameid}") + basedata = await basedata.json() + finalstr = "" + offsetadd = 0 + for i in data["entries"]: + finalstr += f"\n{i['message'].replace("", "**").replace("", "**")}" + offsetadd += 1 + if i["event"] == "Recordkeeping": + await self.bot.db.execute(f""" + DELETE from liveupdate WHERE messageid = {messageid} + """) + #await self.bot.db.commit() + color = tuple(int(basedata["HomeTeamColor"][i:i+2], 16) for i in (0, 2, 4)) + embed = nextcord.Embed(title=f"{basedata["AwayTeamName"]} {basedata["AwayTeamEmoji"]} **{data["entries"][-1]["away_score"]}** vs {basedata["HomeTeamName"]} {basedata["HomeTeamEmoji"]} **{data["entries"][-1]["home_score"]}**", + description=f"{"Bottom" if data["entries"][-1]["inning_side"] == 1 else "Top"} of the {data["entries"][-1]["inning"]}", + colour = nextcord.Color.from_rgb(color[0], color[1], color[2])) + embed.set_footer(text=gameid) + match data["entries"][-1]["balls"]: + case 1: + embed.add_field(name="Balls",value="🔴⭕⭕") + case 2: + embed.add_field(name="Balls",value="🔴🔴⭕") + case 3: + embed.add_field(name="Balls",value="🔴🔴🔴") + case _: + embed.add_field(name="Balls",value="⭕⭕⭕") + match data["entries"][-1]["strikes"]: + case 1: + embed.add_field(name="Strikes",value="🔴⭕") + case 2: + embed.add_field(name="Strikes",value="🔴🔴") + case _: + embed.add_field(name="Strikes",value="⭕⭕") + match data["entries"][-1]["outs"]: + case 1: + embed.add_field(name="Outs",value="🔴⭕") + case 2: + embed.add_field(name="Outs",value="🔴🔴") + case _: + embed.add_field(name="Outs",value="⭕⭕") + embed.add_field(name="Batting",value=data["entries"][-1]["batter"], inline=True) + embed.add_field(name="Pitching",value=data["entries"][-1]["pitcher"], inline=True) + embed.add_field(name="On Deck",value=data["entries"][-1]["on_deck"], inline=True) + embed.add_field(name=f"Last 7 events",value=finalstr,inline=False) + embed.set_thumbnail(f"https://insertapp.net/mmolbbot/assets/diamond_{data["entries"][-1]["on_1b"]}_{data["entries"][-1]["on_2b"]}_{data["entries"][-1]["on_3b"]}.png") + embed.set_footer(text=f"Embed too big? Need historical data? consider classic mode.") + await message.edit(content="",embed=embed) + await self.bot.db.execute(f""" + UPDATE liveupdate set offset = {offset+offsetadd} WHERE messageid = '{messageid}' + """) except Exception as e: await self.bot.db.execute(f""" DELETE from liveupdate WHERE messageid = {messageid} """) #await self.bot.db.commit() - await message.edit(f"An error occoured in this live update\n{e}") + if message: + await message.edit(f"An error occoured in this live update\n{e}") warning = self.bot.get_channel(1365478368555827270) await warning.send(e) except nextcord.Forbidden: @@ -90,34 +96,35 @@ async def classiclivegameworker(self,serverid,userid,channelid,gameid,offset): begin = timeit.default_timer() lastserverid = serverid try: - channel = self.bot.get_channel(channelid) - data = requests.get(f"https://mmolb.com/api/game/{gameid}/live?after={offset}").json() - if len(data["entries"]) > 0: - #data = requests.get(f"https://mmolb.com/api/game/{gameid}/live?after={offset-((7-len(data["entries"]) if offset > 7 else offset))}").json() - basedata = requests.get(f"https://mmolb.com/api/game/{gameid}").json() - finalstr = "" - offsetadd = 0 - maysend = False - for i in data["entries"]: - if "scores" in i['message'] or "homers" in i['message']: - finalstr += f"\n>>> {i['message'].replace(", ","\n").replace(". ","\n").replace("", "").replace("", "\n")}" - maysend = True - offsetadd += 1 - if i["event"] == "Recordkeeping": - await self.bot.db.execute(f""" - DELETE from liveupdate WHERE channelid = {channelid} AND gameid = '{gameid}' - """) - maysend = True - finalstr += f"\n> {i['message'].replace("", "**").replace("", "**")}" - #await self.bot.db.commit() - if maysend: - if data["entries"][-1]["inning_side"] == 1: - await channel.send(f"Bottom of the {data["entries"][-1]["inning"]} | {basedata["AwayTeamName"]} {basedata["AwayTeamEmoji"]} {data["entries"][-1]["away_score"]} vs {basedata["HomeTeamName"]} {basedata["HomeTeamEmoji"]} **{data["entries"][-1]["home_score"]}**{finalstr}") - else: - await channel.send(f"Top of the {data["entries"][-1]["inning"]} | {basedata["AwayTeamName"]} {basedata["AwayTeamEmoji"]} **{data["entries"][-1]["away_score"]}** vs {basedata["HomeTeamName"]} {basedata["HomeTeamEmoji"]} {data["entries"][-1]["home_score"]}{finalstr}") - await self.bot.db.execute(f""" - UPDATE liveupdate set offset = {offset+offsetadd} WHERE channelid = '{channelid}' AND gameid = '{gameid}' AND classic = 1 - """) + async with aiohttp.ClientSession() as session: + channel = self.bot.get_channel(channelid) + data = await session.get(f"https://mmolb.com/api/game/{gameid}/live?after={offset}") + data = await data.json() + if len(data["entries"]) > 0: + basedata = await session.get(f"https://mmolb.com/api/game/{gameid}") + basedata = await basedata.json() + finalstr = "\n>>> " + offsetadd = 0 + maysend = False + for i in data["entries"]: + if "scores" in i['message'] or "homers" in i['message']: + finalstr += f"{i['message'].replace(", ","\n").replace(". ","\n").replace("", "").replace("", "\n")}\n" + maysend = True + offsetadd += 1 + if i["event"] == "Recordkeeping": + await self.bot.db.execute(f""" + DELETE from liveupdate WHERE channelid = {channelid} AND gameid = '{gameid}' + """) + maysend = True + finalstr += f"{i['message'].replace("", "**").replace("", "**")}\n" + if maysend: + if data["entries"][-1]["inning_side"] == 1: + await channel.send(f"Bottom of the {data["entries"][-1]["inning"]} | {basedata["AwayTeamName"]} {basedata["AwayTeamEmoji"]} {data["entries"][-1]["away_score"]} vs {basedata["HomeTeamName"]} {basedata["HomeTeamEmoji"]} **{data["entries"][-1]["home_score"]}**{finalstr}") + else: + await channel.send(f"Top of the {data["entries"][-1]["inning"]} | {basedata["AwayTeamName"]} {basedata["AwayTeamEmoji"]} **{data["entries"][-1]["away_score"]}** vs {basedata["HomeTeamName"]} {basedata["HomeTeamEmoji"]} {data["entries"][-1]["home_score"]}{finalstr}") + await self.bot.db.execute(f""" + UPDATE liveupdate set offset = {offset+offsetadd} WHERE channelid = '{channelid}' AND gameid = '{gameid}' AND classic = 1 + """) except Exception as e: warning = self.bot.get_channel(1365478368555827270) await warning.send(e) @@ -129,6 +136,85 @@ async def classiclivegameworker(self,serverid,userid,channelid,gameid,offset): await self.bot.db.commit() await warning.send(f"Deleted {lastserverid} from the database due to 403 error") + +async def spotlightsubscriptionworker(self,serverid,channelid,classic,gameid,data): + try: + check = await self.bot.db.execute("SELECT serverid,userid,channelid,messageid,gameid,offset FROM liveupdate WHERE channelid = ? AND gameid = ? AND classic = ?", (channelid,gameid,classic)) + test = await check.fetchone() + if test is None: + channel = self.bot.get_channel(channelid) + if data["State"] == "Complete": + return + check = await self.bot.db.execute("SELECT serverid,userid,channelid,messageid,gameid,offset FROM liveupdate WHERE channelid = ? AND gameid = ? AND classic = ?", (channelid,gameid,classic)) + test = await check.fetchone() + if test is None: #no idea why it has to have two checks + if classic == 0: + message = await channel.send(content=f"{data["AwayTeamName"]} {data["AwayTeamEmoji"]} **{data["EventLog"][-1]["away_score"]}** vs {data["HomeTeamName"]} {data["HomeTeamEmoji"]} **{data["EventLog"][-1]["home_score"]}**") + await self.bot.db.execute(f""" + INSERT INTO liveupdate VALUES + ({channel.guild.id}, {self.bot.application_id}, {channelid}, {message.id}, "{gameid}", {len(data["EventLog"])}, 0) + """) + else: + await self.bot.db.execute(f""" + INSERT INTO liveupdate VALUES + ({channel.guild.id}, {self.bot.application_id}, {channelid}, NULL, "{gameid}", {len(data["EventLog"])}, 1) + """) + except sqlite3.OperationalError: + await self.bot.db.close() + self.checkspotlightsubscriptions.cancel() + self.checkteamsubscriptions.cancel() + warning = self.bot.get_channel(1365478368555827270) + await warning.send(f"<@{self.bot.owner_id}> database is locked!") + except Exception as e: + warning = self.bot.get_channel(1365478368555827270) + await warning.send(f"Ignoring exception in spotlight check: {e}") + print(e) + return + +async def teamsubscriptionworker(self,serverid,channelid,teamid,classic): + try: + async with aiohttp.ClientSession() as session: + game = await session.get(f"https://mmolb.com/api/game-by-team/{teamid}") + game = await game.json() + gameid = game["game_id"] + check = await self.bot.db.execute("SELECT serverid,userid,channelid,messageid,gameid,offset FROM liveupdate WHERE channelid = ? AND gameid = ? AND classic = ?", (channelid,gameid,classic)) + test = await check.fetchone() + if test is None: + data = await session.get(f"https://mmolb.com/api/game/{gameid}") + data = await data.json() + channel = self.bot.get_channel(channelid) + if data["State"] == "Complete": + return + check = await self.bot.db.execute("SELECT serverid,userid,channelid,messageid,gameid,offset FROM liveupdate WHERE channelid = ? AND gameid = ? AND classic = ?", (channelid,gameid,classic)) + test = await check.fetchone() + if test is None: #no idea why it has to have two checks + if classic == 0: + message = await channel.send(content=f"{data["AwayTeamName"]} {data["AwayTeamEmoji"]} **{data["EventLog"][-1]["away_score"]}** vs {data["HomeTeamName"]} {data["HomeTeamEmoji"]} **{data["EventLog"][-1]["home_score"]}**") + await self.bot.db.execute(f""" + INSERT INTO liveupdate VALUES + ({channel.guild.id}, {self.bot.application_id}, {channelid}, {message.id}, "{gameid}", {len(data["EventLog"])}, 0) + """) + else: + await self.bot.db.execute(f""" + INSERT INTO liveupdate VALUES + ({channel.guild.id}, {self.bot.application_id}, {channelid}, NULL, "{gameid}", {len(data["EventLog"])}, 1) + """) + except KeyError: + return + except sqlite3.OperationalError: + await self.bot.db.close() + self.checkspotlightsubscriptions.cancel() + self.checkteamsubscriptions.cancel() + warning = self.bot.get_channel(1365478368555827270) + await warning.send(f"<@{self.bot.owner_id}> database is locked!") + except Exception as e: + warning = self.bot.get_channel(1365478368555827270) + await warning.send(f"Ignoring exception in {teamid}: {e}") + print(e) + return + + + class liveupdate(commands.Cog): def __init__(self, bot: commands.Bot): @@ -408,14 +494,13 @@ class liveupdate(commands.Cog): try: begin = timeit.default_timer() await self.bot.wait_until_ready() - print("updating live games") res = await self.bot.db.execute("SELECT serverid,userid,channelid,messageid,gameid,offset FROM liveupdate WHERE classic = 0") res = await res.fetchall() worklist = [livegameworker(self,serverid,userid,channelid,messageid,gameid,offset) for [serverid,userid,channelid,messageid,gameid,offset] in res] res = await self.bot.db.execute("SELECT serverid,userid,channelid,gameid,offset FROM liveupdate WHERE classic = 1") res = await res.fetchall() worklist = worklist + [classiclivegameworker(self,serverid,userid,channelid,gameid,offset) for [serverid,userid,channelid,gameid,offset] in res] - await asyncio.gather(*worklist) + await asyncio.gather(*worklist,return_exceptions=True) await self.bot.db.commit() timings.append(timeit.default_timer()-begin) if len(timings) >= 12: @@ -436,51 +521,17 @@ class liveupdate(commands.Cog): async def checkspotlightsubscriptions(self): try: await self.bot.wait_until_ready() - print("refreshing spotlight subscriptions") - game = requests.get("https://mmolb.com/api/spotlight").json() - gameid = game["game_id"] + async with aiohttp.ClientSession() as session: + game = await session.get("https://mmolb.com/api/spotlight") + game = await game.json() + gameid = game["game_id"] + data = await session.get(f"https://mmolb.com/api/game/{gameid}") + data = await data.json() res = await self.bot.db.execute("SELECT serverid,channelid,classic FROM spotlightsubscriptions") res = await res.fetchall() - print(res) - for [serverid,channelid,classic] in res: - try: - check = await self.bot.db.execute("SELECT serverid,userid,channelid,messageid,gameid,offset FROM liveupdate WHERE channelid = ? AND gameid = ? AND classic = ?", (channelid,gameid,classic)) - test = await check.fetchone() - print(test) - if test is None: - print("True") - data = requests.get(f"https://mmolb.com/api/game/{gameid}").json() - channel = self.bot.get_channel(channelid) - if data["State"] == "Complete": - continue - check = await self.bot.db.execute("SELECT serverid,userid,channelid,messageid,gameid,offset FROM liveupdate WHERE channelid = ? AND gameid = ? AND classic = ?", (channelid,gameid,classic)) - test = await check.fetchone() - if test is None: #no idea why it has to have two checks - if classic == 0: - message = await channel.send(content=f"{data["AwayTeamName"]} {data["AwayTeamEmoji"]} **{data["EventLog"][-1]["away_score"]}** vs {data["HomeTeamName"]} {data["HomeTeamEmoji"]} **{data["EventLog"][-1]["home_score"]}**") - await self.bot.db.execute(f""" - INSERT INTO liveupdate VALUES - ({channel.guild.id}, {self.bot.application_id}, {channelid}, {message.id}, "{gameid}", {len(data["EventLog"])}, 0) - """) - else: - await self.bot.db.execute(f""" - INSERT INTO liveupdate VALUES - ({channel.guild.id}, {self.bot.application_id}, {channelid}, NULL, "{gameid}", {len(data["EventLog"])}, 1) - """) - await self.bot.db.commit() - else: - print("false") - except sqlite3.OperationalError: - await self.bot.db.close() - self.checkspotlightsubscriptions.cancel() - self.checkteamsubscriptions.cancel() - warning = self.bot.get_channel(1365478368555827270) - await warning.send(f"<@{self.bot.owner_id}> database is locked!") - except Exception as e: - warning = self.bot.get_channel(1365478368555827270) - await warning.send(f"Ignoring exception in spotlight check: {e}") - print(e) - continue + worklist = [spotlightsubscriptionworker(self,serverid,channelid,classic,gameid,data) for [serverid,channelid,classic] in res] + await asyncio.gather(*worklist,return_exceptions=True) + await self.bot.db.commit() except KeyError: pass except Exception as e: @@ -488,57 +539,18 @@ class liveupdate(commands.Cog): print(e) warning = self.bot.get_channel(1365478368555827270) await warning.send(e) - print(e) return @tasks.loop(seconds=120.0) async def checkteamsubscriptions(self): try: - print("refreshing team subscriptions") await self.bot.wait_until_ready() res = await self.bot.db.execute("SELECT serverid,channelid,teamid,classic FROM teamsubscriptions") res = await res.fetchall() - print(res) - for [serverid,channelid,teamid,classic] in res: - try: - game = requests.get(f"https://mmolb.com/api/game-by-team/{teamid}").json() - gameid = game["game_id"] - check = await self.bot.db.execute("SELECT serverid,userid,channelid,messageid,gameid,offset FROM liveupdate WHERE channelid = ? AND gameid = ? AND classic = ?", (channelid,gameid,classic)) - test = await check.fetchone() - if test is None: - data = requests.get(f"https://mmolb.com/api/game/{gameid}").json() - channel = self.bot.get_channel(channelid) - if data["State"] == "Complete": - continue - check = await self.bot.db.execute("SELECT serverid,userid,channelid,messageid,gameid,offset FROM liveupdate WHERE channelid = ? AND gameid = ? AND classic = ?", (channelid,gameid,classic)) - test = await check.fetchone() - if test is None: #no idea why it has to have two checks - if classic == 0: - message = await channel.send(content=f"{data["AwayTeamName"]} {data["AwayTeamEmoji"]} **{data["EventLog"][-1]["away_score"]}** vs {data["HomeTeamName"]} {data["HomeTeamEmoji"]} **{data["EventLog"][-1]["home_score"]}**") - await self.bot.db.execute(f""" - INSERT INTO liveupdate VALUES - ({channel.guild.id}, {self.bot.application_id}, {channelid}, {message.id}, "{gameid}", {len(data["EventLog"])}, 0) - """) - else: - await self.bot.db.execute(f""" - INSERT INTO liveupdate VALUES - ({channel.guild.id}, {self.bot.application_id}, {channelid}, NULL, "{gameid}", {len(data["EventLog"])}, 1) - """) - await self.bot.db.commit() - except KeyError: - continue - except sqlite3.OperationalError: - await self.bot.db.close() - self.checkspotlightsubscriptions.cancel() - self.checkteamsubscriptions.cancel() - warning = self.bot.get_channel(1365478368555827270) - await warning.send(f"<@{self.bot.owner_id}> database is locked!") - except Exception as e: - warning = self.bot.get_channel(1365478368555827270) - await warning.send(f"Ignoring exception in {teamid}: {e}") - print(e) - continue + worklist = [teamsubscriptionworker(self,serverid,channelid,teamid,classic) for [serverid,channelid,teamid,classic] in res] + await asyncio.gather(*worklist,return_exceptions=True) + await self.bot.db.commit() except Exception as e: #I know this is bad practice but these loops must be running warning = self.bot.get_channel(1365478368555827270)