diff --git a/bot/champs.py b/bot/champs.py index 716e751..922c5c5 100644 --- a/bot/champs.py +++ b/bot/champs.py @@ -1,18 +1,49 @@ -from bot.utils import CHAMPS, VERSION +from dataclasses import dataclass +from bot.utils import get_version, get_champs +@dataclass class Champ: - def __init__(self, champ): - self.champ = CHAMPS[champ]['id'] - self.title = CHAMPS[self.champ]['title'] - self.img = f"https://ddragon.leagueoflegends.com/cdn/{VERSION}/img/champion/{CHAMPS[self.champ]['image']['full']}" - self.desc = CHAMPS[self.champ]['blurb'] - self.tags = ' '.join(CHAMPS[self.champ]['tags']) - self.stats = f'Health: {CHAMPS[self.champ]["stats"]["hp"]} \n \ - Move Speed: {CHAMPS[self.champ]["stats"]["movespeed"]} \n \ - Attack Damage: {CHAMPS[self.champ]["stats"]["attackdamage"]} \n \ - Attack Range: {CHAMPS[self.champ]["stats"]["attackrange"]} \n \ - Attack Speed: {CHAMPS[self.champ]["stats"]["attackspeed"]}' - self.url = f'https://www.op.gg/champion/{self.champ}/statistics' - self.real = True - self.name = CHAMPS[champ]['name'] - + """Model for representing a League of Legends champ""" + title: str + champ: str + name: str + img: str + desc: str + tags: str + stats: str + url: str + +class Champs: + """Singleton class to dynamically generates a champ upon request""" + def real_champ(self, name): + """Check if a champ is real""" + _name = name.lower().replace(' ', '') + for champ in get_champs(): + if champ.lower() == _name: + return champ + return None + + def get_champ(self, champ): + """Generate the Champ Model object""" + champ_id = get_champs()[champ]['id'] + title = get_champs()[champ_id]['title'] + img = f"https://ddragon.leagueoflegends.com/cdn/{get_version()}/img/champion/{get_champs()[champ_id]['image']['full']}" + desc = get_champs()[champ_id]['blurb'] + tags = ' '.join(get_champs()[champ_id]['tags']) + stats = f'Health: {get_champs()[champ_id]["stats"]["hp"]} \n \ + Move Speed: {get_champs()[champ_id]["stats"]["movespeed"]} \n \ + Attack Damage: {get_champs()[champ_id]["stats"]["attackdamage"]} \n \ + Attack Range: {get_champs()[champ_id]["stats"]["attackrange"]} \n \ + Attack Speed: {get_champs()[champ_id]["stats"]["attackspeed"]}' + url = f'https://www.op.gg/champion/{champ_id}/statistics' + name = get_champs()[champ_id]['name'] + return Champ( + title=title, + champ=champ_id, + name=name, + img=img, + desc=desc, + tags=tags, + stats=stats, + url=url + ) diff --git a/bot/players.py b/bot/players.py new file mode 100644 index 0000000..04cf531 --- /dev/null +++ b/bot/players.py @@ -0,0 +1,75 @@ +from dataclasses import dataclass +import requests +from riotwatcher import LolWatcher +from bot.utils import RIOT_API_KEY, get_version, get_champs + +@dataclass +class Player(): + """Model for representing League of Legends player""" + name: str + url: str + level: str + icon_img: str + ranksolo: str + rank5: str + win: str + champ: str + img: str + +class Players(): + """Singleton class to dynamically generates player data""" + def __init__(self): + self.riot = LolWatcher(RIOT_API_KEY) + + def get_player(self, name, region="na1", prefix="na"): + if prefix == "kr": prefix = "www" + try: + player_info = self.riot.summoner.by_name(region, name) + player_stats = self.riot.league.by_summoner(region, player_info['id']) + except Exception as e: + print(f'Unable to load plater data from Riot API: \n {e}') + return None + else: + _name = player_info['name'] + url = f'https://{prefix}.op.gg/summoner/userName={name}' + level = player_info['summonerLevel'] + icon_img = f"https://ddragon.leagueoflegends.com/cdn/{get_version()}/img/profileicon/{player_info['profileIconId']}.png" + try: + if len(player_stats) >= 2: + for i in player_stats: + if i["queueType"] == "RANKED_SOLO_5x5": + ranksolo = f'{i["tier"].lower().capitalize()} {i["rank"]} {i["leaguePoints"]} LP' + elif i["queueType"] == "RANKED_FLEX_SR": + rank5 = f'{i["tier"].lower().capitalize()} {i["rank"]} {i["leaguePoints"]} LP' + else: + ranksolo = f'{player_stats[0]["tier"].lower().capitalize()} {player_stats[0]["rank"]} {player_stats[0]["leaguePoints"]} LP' + rank5 = None + except IndexError: + ranksolo = 'Unranked' + rank5 = 'Unranked' + if ranksolo != 'Unranked': + win = f'{round((player_stats[0]["wins"] / (player_stats[0]["wins"] + player_stats[0]["losses"])) * 100)}%' + else: + win = 'N/A' + try: + mastery = requests.get(f'https://{region}.api.riotgames.com/lol/champion-mastery/v4/champion-masteries/by-summoner/{player_info["id"]}?api_key={RIOT_API_KEY}').json() + for champ in get_champs(): + if get_champs()[champ]['key'] == str(mastery[0]['championId']): + _champ = f"{get_champs()[champ]['name']} {mastery[0]['championPoints']}" + img = f"https://ddragon.leagueoflegends.com/cdn/{get_version()}/img/champion/{get_champs()[champ]['image']['full']}" + break + except: + champ = "N/A" + img = "N/A" + + return Player( + name=_name, + url=url, + level=level, + icon_img=icon_img, + ranksolo=ranksolo, + rank5=rank5, + win=win, + champ=_champ, + img=img + ) \ No newline at end of file diff --git a/bot/runemaster.py b/bot/runemaster.py index 8d7105e..bd39753 100644 --- a/bot/runemaster.py +++ b/bot/runemaster.py @@ -1,12 +1,15 @@ import re from discord import Client, Embed, File -from bot.screenshot import get_screenshot -from bot.champs import Champ -from bot.summoner import Summon -from bot.utils import TOKEN, VERSION, COMMANDS, REGIONS, TIERS, real_champ, real_region +from bot.screenshot import Browser +from bot.champs import Champs +from bot.players import Players +from bot.utils import TOKEN, COMMANDS, REGIONS, TIERS, real_region, get_version client = Client() +BROWSER = Browser() +CHAMPS = Champs() +PLAYERS = Players() @client.event async def on_ready(): @@ -14,183 +17,165 @@ async def on_ready(): print (f'{client.user} is connected to the following guilds:\n') for guild in client.guilds: print(f'{guild.name}(id: {guild.id})\n') - print(f'League of Legends Version {VERSION}') + print(f'League of Legends Version {get_version()}') @client.event async def on_message(message): if message.author == client.user: return - if re.search('^>>> ', message.content, flags=re.IGNORECASE): - return - if re.search('^> ', message.content, flags=re.IGNORECASE): - return if re.search('@RuneMaster', message.content): await message.channel.send('Ready for the Rift? type >commands for a list of all commands') - return -#region Generic commands - if re.search('^>hello', message.content, flags=re.IGNORECASE): - await message.channel.send('Hello Summoner') - return - if re.search('^>commands', message.content, flags=re.IGNORECASE): - response = Embed( - title = "__Runemaster Commands__", - description = "All commands start with a `>` and most commands will require an argument, usually this will be the name of a champion. If the champ has a space or a singlequote dont include them in the name. ex: DrMundo, Reksai", - ) - for command in COMMANDS.values(): - response.add_field(name=command['usage'], value=command['value'], inline=False) - await message.channel.send(embed=response) - return - if re.search('^>regions', message.content, flags=re.IGNORECASE): - desc = '\n'.join(REGIONS.values()) - response = Embed( - title = "__Regions__", - description = desc - ) - await message.channel.send(embed=response) - return - if re.search('^>tierlist|^>tiers', message.content, flags=re.IGNORECASE): - file = File('images/tierlist.png', filename='tierlist.png') - await message.channel.send(f"__Ranked Tier List__",file=file) - return - if re.search('^>oldtierlist|^>oldtiers', message.content, flags=re.IGNORECASE): - file = File('images/old_tierlist.png', filename='old_tierlist.png') - await message.channel.send(f"__Old Ranked Tier List__",file=file) - return -#endregion - if re.search('^>', message.content): + elif re.search('^>[a-zA-Z]', message.content, flags=re.IGNORECASE): _in = message.content.split(' ', 1) command = _in[0].lower() if len(_in) == 1: - await message.channel.send('Type `>commands` for a list of commands and how to use them.') - return - else: - args = _in[1].lower().strip() - - if command == '>help': - comm = args.lower() - if comm in COMMANDS: +#region Generic commands + if command == ">hello": + await message.channel.send('Hello Summoner') + elif command == ">commands": response = Embed( - title = f"__{comm.capitalize()} Help__", - description = f"{COMMANDS[comm]['usage']} \n {COMMANDS[comm]['value']}", - ) + title = "__Runemaster Commands__", + description = "All commands start with a `>` and most commands will require an argument, usually this will be the name of a champion. If the champ has a space or a singlequote dont include them in the name. ex: DrMundo, Reksai", + ) + for command in COMMANDS.values(): + response.add_field(name=command['usage'], value=command['value'], inline=False) await message.channel.send(embed=response) - return - else: - await message.channel.send("Command doesn't exist, type *>commands* for a list of commands") - -#region Champion related commands - elif command == '>info': - champ = real_champ(name=args) - if champ is not None: - info = Champ(champ=champ) + elif command == ">regions": + desc = '\n'.join(REGIONS.values()) response = Embed( - title = f"__{info.champ} | {info.title}__", - description = info.desc, - url=info.url + title = "__Regions__", + description = desc ) - response.set_image(url=info.img) - response.add_field(name="Tags", value= info.tags, inline=False) - response.add_field(name="Stats", value= info.stats, inline=False) await message.channel.send(embed=response) - elif champ is None: - await message.channel.send("That champ does not exist") - - elif command == '>runes': - await message.channel.send("Fetching Rune Data...") - name = real_champ(name=args) - if name is not None: - file = File(fp=await get_screenshot(name=name, action="runes"), filename=f'{name}.png') - await message.channel.send(f"__{args.capitalize()} Runes__",file=file) + elif command in (">tierlist", ">tiers"): + file = File('images/tierlist.png', filename='tierlist.png') + await message.channel.send(f"__Ranked Tier List__",file=file) + elif command in (">oldtierlist", ">oldtiers"): + file = File('images/tierlist.png', filename='tierlist.png') + await message.channel.send(f"__Ranked Tier List__",file=file) else: - await message.channel.send("That champ does not exist") - - elif command == '>build': - await message.channel.send("Fetching Build Data...") - name = real_champ(name=args) - if name is not None: - file = File(fp=await get_screenshot(name=name, action="build"), filename=f'{name}.png') - await message.channel.send(f"__{args.capitalize()} Build__",file=file) - else: - await message.channel.send("That champ does not exist") + await message.channel.send('Type `>commands` for a list of commands and how to use them.') +#endregion + else: + args = _in[1].lower().strip() + if command == '>help': + comm = args.lower() + if comm in COMMANDS: + response = Embed( + title = f"__{comm.capitalize()} Help__", + description = f"{COMMANDS[comm]['usage']} \n {COMMANDS[comm]['value']}", + ) + await message.channel.send(embed=response) + return + else: + await message.channel.send("Command doesn't exist, type *>commands* for a list of commands") + elif command == '>tier': + t = args.lower() + if t in TIERS: + file = File(TIERS[t], filename=f'tier.png') + await message.channel.send(f"__{t.capitalize()}__",file=file) + else: + await message.channel.send("That ranked tier does not exist, type >tiers for all the tiers.") + elif command == '>info': + champ = CHAMPS.real_champ(name=args) + if champ is not None: + info = CHAMPS.get_champ(champ=champ) + response = Embed( + title = f"__{info.champ} | {info.title}__", + description = info.desc, + url=info.url + ) + response.set_image(url=info.img) + response.add_field(name="Tags", value= info.tags, inline=False) + response.add_field(name="Stats", value= info.stats, inline=False) + await message.channel.send(embed=response) + else: + await message.channel.send("That champ does not exist") + elif command == '>runes': + await message.channel.send("Fetching Rune Data...") + name = CHAMPS.real_champ(name=args) + if name is not None: + file = File(fp=await BROWSER.get_cached_screenshot(name=name, action="runes"), filename=f'{name}.png') + await message.channel.send(f"__{args.capitalize()} Runes__",file=file) + else: + await message.channel.send("That champ does not exist") - elif command == '>skills' or command == '>abilities' or command == ">spells": - await message.channel.send("Fetching Skills Data...") - name = real_champ(name=args) - if name is not None: - file = File(fp=await get_screenshot(name=name, action="skills"), filename=f'{name}.png') - await message.channel.send(f"__{args.capitalize()} Skills__",file=file) - else: - await message.channel.send("That champ does not exist") + elif command == '>build': + await message.channel.send("Fetching Build Data...") + name = CHAMPS.real_champ(name=args) + if name is not None: + file = File(fp=await BROWSER.get_cached_screenshot(name=name, action="build"), filename=f'{name}.png') + await message.channel.send(f"__{args.capitalize()} Build__",file=file) + else: + await message.channel.send("That champ does not exist") - elif command == '>stats': - await message.channel.send("Fetching Stats Data...") - name = real_champ(name=args) - if name is not None: - file = File(fp=await get_screenshot(name=name, action="stats"), filename=f'{name}.png') - await message.channel.send(f"__{args.capitalize()} Stats__",file=file) - else: - await message.channel.send("That champ does not exist") -#endregion - - elif command == '>tier': - t = args.lower() - if t in TIERS: - file = File(TIERS[t], filename=f'tier.png') - await message.channel.send(f"__{t.capitalize()}__",file=file) - else: - await message.channel.send("That ranked tier does not exist") + elif command == '>skills' or command == '>abilities' or command == ">spells": + await message.channel.send("Fetching Skills Data...") + name = CHAMPS.real_champ(name=args) + if name is not None: + file = File(fp=await BROWSER.get_cached_screenshot(name=name, action="skills"), filename=f'{name}.png') + await message.channel.send(f"__{args.capitalize()} Skills__",file=file) + else: + await message.channel.send("That champ does not exist") -#region Summoner related commands - elif command == '>summon': - await message.channel.send("Fetching Summoner data...") - args = args.split(" ", 1) - if len(args) > 1: - reg = args[0].lower() - if real_region(region=reg): - player = Summon(name=args[1], region=REGIONS[reg], prefix=reg) + elif command == '>stats': + await message.channel.send("Fetching Stats Data...") + name = CHAMPS.real_champ(name=args) + if name is not None: + file = File(fp=await BROWSER.get_cached_screenshot(name=name, action="stats"), filename=f'{name}.png') + await message.channel.send(f"__{args.capitalize()} Stats__",file=file) else: - await message.channel.send("Region does not exist, type *>regions* for a list of regions") - else: - player = Summon(name=args[0]) - if player.real_player: - response = Embed( - title = f"__{player.name}__" , - url= player.url + await message.channel.send("That champ does not exist") + elif command == '>summon': + await message.channel.send("Fetching Summoner data...") + args = args.split(" ", 1) + if len(args) > 1: + reg = args[0].lower() + if real_region(region=reg): + player = PLAYERS.get_player(name=args[1], region=REGIONS[reg], prefix=reg) + else: + await message.channel.send("Region does not exist, type *>regions* for a list of regions") + else: + player = PLAYERS.get_player(name=args[0]) + if player is not None: + response = Embed( + title=f"__{player.name}__" , + url=player.url ) - response.set_thumbnail(url=f"https://ddragon.leagueoflegends.com/cdn/10.10.3216176/img/profileicon/{player.icon}.png") - response.add_field(name="Level:", value=player.level, inline=False) - response.add_field(name="Solo/Duo Rank:", value=player.ranksolo, inline=False) - if player.rank5 != None: - response.add_field(name="5v5 Flex Rank: ", value=player.rank5, inline=False) - response.add_field(name="Ranked Season Win %:", value=f'{player.win}', inline=True) - response.add_field(name="Highest Mastery :", value=player.champ, inline=False) - response.set_image(url=player.img) - await message.channel.send(embed=response) + response.set_thumbnail(url=player.icon_img) + response.add_field(name="Level: ", value=player.level, inline=False) + response.add_field(name="Solo/Duo Rank: ", value=player.ranksolo, inline=False) + if player.rank5 != None: + response.add_field(name="5v5 Flex Rank: ", value=player.rank5, inline=False) + response.add_field(name="Ranked Season Win %: ", value=player.win, inline=True) + response.add_field(name="Highest Mastery: ", value=player.champ, inline=False) + if player.img != "N/A": + response.set_image(url=player.img) + await message.channel.send(embed=response) + else: + await message.channel.send("That Summoner does not exist or is on a different region") + elif "matches" in command: + await message.channel.send("Fetching Player Match data...") + args = args.split(" ", 1) + if len(args) > 1: + reg = args[0].lower() + name = args[1] + if not real_region(region=reg): + await message.channel.send("Region does not exist, type *>regions* for a list of regions") + return + else: + reg = "na" + name = args[0] + if command == ">soloranked_matches": action = "soloranked_matches" + elif command == ">flexranked_matches": action = "flexranked_matches" + elif command == ">matches": action = "matches" + try: + file = File(fp=await BROWSER.get_screenshot(name=args[0], action=action, prefix=reg), filename=f'{args[0]}.png') + await message.channel.send(f'__{args[0]} Match History__',file=file) + except Exception as e: + print(e) + await message.channel.send("That Summoner does not exist") else: - await message.channel.send("That Summoner does not exist or is on a different region") + await message.channel.send('Type `>commands` for a list of commands and how to use them.') - elif "matches" in command: - await message.channel.send("Fetching Player Match data...") - args = args.split(" ", 1) - if len(args) > 1: - reg = args[0].lower() - name = args[1] - if not real_region(region=reg): - await message.channel.send("Region does not exist, type *>regions* for a list of regions") - return - else: - reg = "na" - name = args[0] - if command == ">soloranked_matches": action = "soloranked_matches" - elif command == ">flexranked_matches": action = "flexranked_matches" - elif command == ">matches": action = "matches" - try: - file = File(fp=await get_screenshot(name=args[0], action=action, prefix=reg), filename=f'{args[0]}.png') - await message.channel.send(f'__{args[0]} Match History__',file=file) - except Exception as e: - print(e) - await message.channel.send("That Summoner does not exist") - else: - await message.channel.send('Type `>commands` for a list of commands and how to use them.') -#endregion client.run(TOKEN) diff --git a/bot/screenshot.py b/bot/screenshot.py index 52b416a..3eb8fd4 100644 --- a/bot/screenshot.py +++ b/bot/screenshot.py @@ -1,51 +1,93 @@ from io import BytesIO -from bot.utils import BROWSER +from typing import Coroutine, Any +from copy import copy +from playwright.async_api import async_playwright, Page +from cachetools import TTLCache +from datetime import datetime, timedelta -async def get_screenshot(name:str, action:str, prefix=None) -> BytesIO: - try: - page = await BROWSER.__anext__() - except Exception as e: - print(f'Unable to get new page: \n {e}') - if prefix is None: +async def init_browser(): + playwright = await async_playwright().start() + browser = await playwright.chromium.launch() + while True: + yield await browser.new_page() + +class Browser: + """Singleton class to represent Playwright Browser for screenshots""" + def __init__(self): + self.__browser = init_browser() + self.__cache = TTLCache(maxsize=500, ttl=timedelta(hours=6), timer=datetime.now) + + async def __new_page(self) -> Coroutine[Any, Any, Page]: + """Creates and returns new page, caller assumes ownership and must close page""" + try: + return await self.__browser.__anext__() + except Exception as e: + print(f'Unable to get new page: \n {e}') + + async def get_cached_screenshot(self, name:str, action:str): + if name in self.__cache: + return copy(self.__cache[name]) + else: + img = await self.__cached_screenshot(name, action) + self.__cache[name] = copy(img) + return img + + async def __cached_screenshot(self, name:str, action:str) -> BytesIO: + """Cached screenshot generator, use only for immutable (mostly) data""" url = f'https://www.op.gg/champion/{name}/statistics' - else: + page = await self.__new_page() + try: + await page.goto(url) + if action == "runes": + await page.set_viewport_size({"width": 734, "height": 607}) + await page.click("body > div.l-wrap.l-wrap--champion > div.l-container > div > div.tabWrap._recognized > div.l-champion-statistics-content.tabItems > div.tabItem.Content.championLayout-overview > div > div.l-champion-statistics-content__main > div > table") + elif action == "build": + await page.set_viewport_size({"width": 734, "height": 667}) + await page.click("body > div.l-wrap.l-wrap--champion > div.l-container > div > div.tabWrap._recognized > div.l-champion-statistics-content.tabItems > div.tabItem.Content.championLayout-overview > div > div.l-champion-statistics-content__main > table:nth-child(2)") + elif action == "skills": + await page.set_viewport_size({"width": 734, "height": 340}) + await page.click("body > div.l-wrap.l-wrap--champion > div.l-container > div > div.tabWrap._recognized > div.l-champion-statistics-content.tabItems > div.tabItem.Content.championLayout-overview > div > div.l-champion-statistics-content__main > table.champion-overview__table.champion-overview__table--summonerspell") + elif action == "stats": + await page.set_viewport_size({"width": 1200, "height": 265}) + await page.click("body > div.l-wrap.l-wrap--champion > div.l-container > div > div.l-champion-statistics-header") + else: + return None + except Exception as e: + print(f'Error in Cached Screenshot at: {url} :\n {e}') + + screenshot_bytes = BytesIO(await page.screenshot()) + await page.close() + screenshot_bytes.seek(0) + return screenshot_bytes + + async def get_screenshot(self, name:str, action:str, prefix: str) -> BytesIO: + """Screenshot generator, use when data is ephemeral""" if prefix == "kr": prefix = "www" url = f'https://{prefix}.op.gg/summoner/userName={name}' - try: - await page.goto(url) - if action == "runes": - await page.set_viewport_size({"width": 734, "height": 607}) - await page.click("body > div.l-wrap.l-wrap--champion > div.l-container > div > div.tabWrap._recognized > div.l-champion-statistics-content.tabItems > div.tabItem.Content.championLayout-overview > div > div.l-champion-statistics-content__main > div > table") - elif action == "build": - await page.set_viewport_size({"width": 734, "height": 667}) - await page.click("body > div.l-wrap.l-wrap--champion > div.l-container > div > div.tabWrap._recognized > div.l-champion-statistics-content.tabItems > div.tabItem.Content.championLayout-overview > div > div.l-champion-statistics-content__main > table:nth-child(2)") - elif action == "skills": - await page.set_viewport_size({"width": 734, "height": 340}) - await page.click("body > div.l-wrap.l-wrap--champion > div.l-container > div > div.tabWrap._recognized > div.l-champion-statistics-content.tabItems > div.tabItem.Content.championLayout-overview > div > div.l-champion-statistics-content__main > table.champion-overview__table.champion-overview__table--summonerspell") - elif action == "stats": - await page.set_viewport_size({"width": 1200, "height": 265}) - await page.click("body > div.l-wrap.l-wrap--champion > div.l-container > div > div.l-champion-statistics-header") - elif action == "matches": - await page.set_viewport_size({"width": 690, "height": 1250}) - await page.click("#SummonerLayoutContent > div.tabItem.Content.SummonerLayoutContent.summonerLayout-summary > div.RealContent > div > div.Content") - elif action == "soloranked_matches": - await page.click("#right_gametype_soloranked > a") - await page.set_viewport_size({"width": 690, "height": 1250}) - await page.click("#SummonerLayoutContent > div.tabItem.Content.SummonerLayoutContent.summonerLayout-summary > div.RealContent > div > div.Content") - elif action == "flexranked_matches": - await page.click("#right_gametype_flexranked > a") - await page.set_viewport_size({"width": 690, "height": 1250}) - await page.click("#SummonerLayoutContent > div.tabItem.Content.SummonerLayoutContent.summonerLayout-summary > div.RealContent > div > div.Content") - elif action == "leaderboard": - await page.click("body > div.l-wrap.l-wrap--summoner > div.l-menu > ul > li:nth-child(6) > a") - await page.set_viewport_size({"width": 970, "height": 391}) - await page.click("body > div.l-wrap.l-wrap--ranking > div.l-container > div.LadderRankingLayoutWrap > div > div > div > div.ranking-highest") - else: - return None - except Exception as e: - print(f'Error in Screenshot at: {url} :\n {e}') + page = await self.__new_page() + try: + await page.goto(url) + if action == "matches": + await page.set_viewport_size({"width": 690, "height": 1250}) + await page.click("#SummonerLayoutContent > div.tabItem.Content.SummonerLayoutContent.summonerLayout-summary > div.RealContent > div > div.Content") + elif action == "soloranked_matches": + await page.click("#right_gametype_soloranked > a") + await page.set_viewport_size({"width": 690, "height": 1250}) + await page.click("#SummonerLayoutContent > div.tabItem.Content.SummonerLayoutContent.summonerLayout-summary > div.RealContent > div > div.Content") + elif action == "flexranked_matches": + await page.click("#right_gametype_flexranked > a") + await page.set_viewport_size({"width": 690, "height": 1250}) + await page.click("#SummonerLayoutContent > div.tabItem.Content.SummonerLayoutContent.summonerLayout-summary > div.RealContent > div > div.Content") + elif action == "leaderboard": + await page.click("body > div.l-wrap.l-wrap--summoner > div.l-menu > ul > li:nth-child(6) > a") + await page.set_viewport_size({"width": 970, "height": 391}) + await page.click("body > div.l-wrap.l-wrap--ranking > div.l-container > div.LadderRankingLayoutWrap > div > div > div > div.ranking-highest") + else: + return None + except Exception as e: + print(f'Error in Screenshot at: {url} :\n {e}') - screenshot_bytes = BytesIO(await page.screenshot()) - await page.close() - screenshot_bytes.seek(0) - return screenshot_bytes + screenshot_bytes = BytesIO(await page.screenshot()) + await page.close() + screenshot_bytes.seek(0) + return screenshot_bytes diff --git a/bot/summoner.py b/bot/summoner.py deleted file mode 100644 index 14ceefb..0000000 --- a/bot/summoner.py +++ /dev/null @@ -1,48 +0,0 @@ -import requests -from riotwatcher import LolWatcher -from bot.utils import RIOT_API_KEY - -lol_watcher = LolWatcher(RIOT_API_KEY) - -class Summon(): - def __init__(self, name, region="na1", prefix="na"): - if prefix == "kr": prefix = "www" - try: - player_info = lol_watcher.summoner.by_name(region, name) - player_stats = lol_watcher.league.by_summoner(region, player_info['id']) - self.real_player = True - except Exception as e: - self.real_player = False - if self.real_player: - self.name = player_info['name'] - self.icon = player_info['profileIconId'] - self.level = player_info['summonerLevel'] - response = requests.get('https://ddragon.leagueoflegends.com/cdn/10.10.3216176/data/en_US/champion.json').json()['data'] - try: - if len(player_stats) >= 2: - for i in player_stats: - if i["queueType"] == "RANKED_SOLO_5x5": - self.ranksolo = f'{i["tier"].lower().capitalize()} {i["rank"]} {i["leaguePoints"]} LP' - elif i["queueType"] == "RANKED_FLEX_SR": - self.rank5 = f'{i["tier"].lower().capitalize()} {i["rank"]} {i["leaguePoints"]} LP' - else: - self.ranksolo = f'{player_stats[0]["tier"].lower().capitalize()} {player_stats[0]["rank"]} {player_stats[0]["leaguePoints"]} LP' - self.rank5 = None - except IndexError: - self.ranksolo ='Unranked' - self.rank5 = 'Unranked' - if self.ranksolo != 'Unranked': - self.win = f'{round((player_stats[0]["wins"] / (player_stats[0]["wins"] + player_stats[0]["losses"])) * 100)}%' - else: - self.win = 'N/A' - try: - mastery = requests.get(f'https://{region}.api.riotgames.com/lol/champion-mastery/v4/champion-masteries/by-summoner/{player_info["id"]}?api_key={RIOT_API_KEY}').json() - for champ in response: - if response[champ]['key'] == str(mastery[0]['championId']): - self.champ = f"{response[champ]['name']} {mastery[0]['championPoints']}" - self.img = f"https://ddragon.leagueoflegends.com/cdn/10.10.3216176/img/champion/{response[champ]['image']['full']}" - break - except: - self.champ = "N/A" - self.champ = "./images/default.png" - self.url = f'https://{prefix}.op.gg/summoner/userName={name}' diff --git a/bot/utils.py b/bot/utils.py index d936717..22e9fab 100644 --- a/bot/utils.py +++ b/bot/utils.py @@ -2,7 +2,7 @@ import json import requests from dotenv import load_dotenv -from playwright.async_api import async_playwright +from cachetools.func import ttl_cache load_dotenv() @@ -13,8 +13,14 @@ REGIONS = json.load(f) with open('bot/commands.json') as f: COMMANDS = json.load(f) -VERSION = requests.get('https://ddragon.leagueoflegends.com/api/versions.json').json()[0] -CHAMPS = requests.get(f'https://ddragon.leagueoflegends.com/cdn/{VERSION}/data/en_US/champion.json').json()['data'] + +@ttl_cache(maxsize=1, ttl=86400) # 86400 : 24 hours +def get_version() -> str: + return requests.get('https://ddragon.leagueoflegends.com/api/versions.json').json()[0] + +@ttl_cache(maxsize=1, ttl=86400) # 86400 : 24 hours +def get_champs(): + return requests.get(f'https://ddragon.leagueoflegends.com/cdn/{get_version()}/data/en_US/champion.json').json()['data'] TOKEN = os.getenv('DISCORD_TOKEN') if TOKEN is None: @@ -24,20 +30,5 @@ if RIOT_API_KEY is None: raise EnvironmentError("RIOT_API_KEY env variable is not set") -async def startup(): - playwright = await async_playwright().start() - browser = await playwright.chromium.launch() - while True: - yield await browser.new_page() - -BROWSER = startup() - -def real_champ(name): - _name = name.lower().replace(' ', '') - for champ in CHAMPS: - if champ.lower() == _name: - return champ - return None - def real_region(region): return region.lower() in REGIONS diff --git a/images/unranked.png b/images/unranked.png new file mode 100644 index 0000000..8f799c7 Binary files /dev/null and b/images/unranked.png differ diff --git a/requirements.txt b/requirements.txt index 4d4e9fc..c2a9a21 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,6 @@ requests discord.py>=1.3.4 riotwatcher playwright -python-dotenv \ No newline at end of file +python-dotenv +cachetools>=5.0.0 +async-cache \ No newline at end of file diff --git a/run.sh b/run.sh old mode 100644 new mode 100755 index 3b062a4..f5fa0c4 --- a/run.sh +++ b/run.sh @@ -1,7 +1,7 @@ #!/bin/bash sudo apt install libnspr4 libatk1.0-0 libatk-bridge2.0-0 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2 libatspi2.0-0 libwebkit2gtk-4.0-dev\ -&& export PYTHONPATH=$PYTHONPATH:./bot \ +export PYTHONPATH=$PYTHONPATH:./bot \ && pip install -r requirements.txt \ && python -m playwright install chromium \ && python bot/runemaster.py