You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

191 lines
6.5 KiB

#!/usr/bin/env python
import os
from datetime import datetime, timedelta
import requests
import bs4
from bs4 import BeautifulSoup, Tag
from init_api import get_service
import dotenv
import asyncio
import re
class Match:
def __init__(self, team1: str, team2: str, score1: int, score2: int,
raw_time: str, stage: str, roundName: str, vods: list[str]):
self.team1 = team1
self.team2 = team2
self.score1 = score1
self.score2 = score2
self.stage = stage
self.roundName = roundName
self.vods = vods
self.time = datetime(*list(map(lambda v: int(v), raw_time.split(","))))
self.eventId = None
def get_format(self) -> int:
if self.stage == "Play-In":
if self.roundName == "Qualifiers" or self.roundName == "EMEA vs NA":
return 5
return 3
if self.stage == "Swiss":
if self.roundName in ["Round 1", "Round 2"]:
return 1
if self.roundName == "Round 3" and self.time.day == 22:
return 1
return 3
if self.stage == "Knockout":
return 5
def event_data(self):
def shorten(link: str):
# remove all query parameters except t=
return re.sub(r'([?&](?!t=))([^&=]+=[^&=]+)', r'\1', link)
vods = "\r\n".join([f'Game {i + 1}: {shorten(link)}' for i, link in enumerate(self.vods)])
return {
"start": {
"dateTime": self.time.isoformat() + "Z"
},
"end": {
"dateTime": (self.time + timedelta(hours=self.get_format())).isoformat() + "Z"
},
"summary": f"{self.team1} - {self.team2}",
"description": f"{self.stage} {self.roundName} BO{self.get_format()}\n{self.score1} - {self.score2}\n{vods}"
}
async def update_event(self):
api = get_service()
api.events().update(
calendarId=os.environ["CALENDAR_ID"],
eventId=self.eventId,
body=self.event_data()
).execute()
print(self)
print(f"Event updated: {self.eventId}")
print("")
async def create_event(self):
api = get_service()
api.events().insert(
calendarId=os.environ["CALENDAR_ID"],
body=self.event_data()
).execute()
print(self)
print(f"Event created")
print("")
def is_in_calendar(self, calendar_events):
for event in calendar_events:
if datetime.fromisoformat(event["start"]["dateTime"][:-1]) == self.time:
self.eventId = event["id"]
return True
return False
def __repr__(self):
return f"{self.team1:>3} - {self.time} - {self.team2:<3} | VODs: {len(self.vods)}"
def fetch_matches():
sources = [
"https://lol.fandom.com/wiki/2023_Worlds_Qualifying_Series",
"https://lol.fandom.com/wiki/2023_Season_World_Championship/Play-In",
"https://lol.fandom.com/wiki/2023_Season_World_Championship/Main_Event"
]
stages = ["Play-In", "Swiss", "Knockout"]
matches: list[Match] = []
for src in sources:
fetched_data = requests.get(src).text
soup = BeautifulSoup(fetched_data, "html.parser")
match_lists: list[Tag] = soup.find_all(attrs={"class": "matchlist"})
vodsTable: Tag = soup.find(attrs={"id", "md-table"}).find("tbody")
for match_list in match_lists:
roundName = match_list.find("tr").find("th").find(recursive=False, string=True).text
raw_matches: list[Tag] = match_list.find_all(attrs={"class", "ml-row"})
# somehow identify stage...
stage = stages[0] if (src == sources[0] or src == sources[1]) else \
(stages[1] if "Round" in roundName else stages[2])
for raw_match in raw_matches:
raw_time = raw_match.find(attrs={"class", "TimeInLocal"}).text
scores = raw_match.find_all(attrs={"class", "matchlist-score"})
scores = [0, 0] if not scores else tuple(map(lambda v: int(v.text), scores))
score1 = scores[0]
score2 = scores[1]
team1 = raw_match.find(attrs={"class", "matchlist-team1"}).find(attrs={"class", "teamname"}).text
team2 = raw_match.find(attrs={"class", "matchlist-team2"}).find(attrs={"class", "teamname"}).text
vods = []
if "TBD" not in [team1, team2]:
def is_current_match_row(tr):
both = tr.find_all(attrs={"class", "teamname"})
if len(both) == 2:
return both[0].text == team1 and both[1].text == team2
return False
roundStartRow: Tag = vodsTable.find(string=roundName).parent.parent
currentRow: Tag = roundStartRow.find_next_sibling(is_current_match_row)
for i in range(5):
fullLink = currentRow.find("a", string="PB")
if fullLink is not None:
vods.append(fullLink["href"])
currentRow = currentRow.find_next_sibling("tr")
if currentRow is None or len(currentRow.contents) > 5:
break
else:
break
matches.append(Match(team1, team2, score1, score2, raw_time, stage, roundName, vods))
return matches
def get_calendar_events():
api = get_service()
start = "2023-04-01T00:00:00Z"
end = "2023-11-20T00:00:00Z"
calendarId = os.environ.get("CALENDAR_ID")
events = api.events().list(
calendarId=calendarId, timeMin=start, timeMax=end,
maxResults=2500, singleEvents=True,
orderBy='startTime'
).execute()["items"]
return events
def delete_calendar_events():
api = get_service()
calendarId = os.environ.get("CALENDAR_ID")
events = get_calendar_events()
for event in events:
api.events().delete(calendarId=calendarId, eventId=event["id"]).execute()
print("Deleted all calendar events")
async def update():
dotenv.load_dotenv()
events = get_calendar_events()
tasks = []
for match in fetch_matches():
if match.is_in_calendar(events):
tasks.append(match.update_event())
else:
tasks.append(match.create_event())
await asyncio.gather(*tasks)
if __name__ == "__main__":
asyncio.run(update())