fix some the signin and check recent but need to fix profile

This commit is contained in:
Rainy21
2026-04-23 00:36:34 -04:00
parent 0a972f6a3f
commit a8ddd363a7
13 changed files with 233 additions and 109 deletions
+114
View File
@@ -150,3 +150,117 @@ Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/discord/app_commands/commands.py", line 847, in _transform_arguments File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/discord/app_commands/commands.py", line 847, in _transform_arguments
raise CommandSignatureMismatch(self) from None raise CommandSignatureMismatch(self) from None
discord.app_commands.errors.CommandSignatureMismatch: The signature for command 'login' is different from the one provided by Discord. This can happen because either your code is out of date or you have not synced the commands with Discord, causing the mismatch in data. It is recommended to sync the command tree to fix this issue. discord.app_commands.errors.CommandSignatureMismatch: The signature for command 'login' is different from the one provided by Discord. This can happen because either your code is out of date or you have not synced the commands with Discord, causing the mismatch in data. It is recommended to sync the command tree to fix this issue.
2026-04-22 03:44:35,358 [WARNING] discord.client: PyNaCl is not installed, voice will NOT be supported
2026-04-22 03:44:35,358 [WARNING] discord.client: davey is not installed, voice will NOT be supported
2026-04-22 03:44:35,359 [INFO] discord.client: logging in using static token
2026-04-22 03:44:35,751 [INFO] bot: ✅ Loaded extension: commands.login
2026-04-22 03:44:35,751 [INFO] bot: ✅ Loaded extension: commands.profile
2026-04-22 03:44:35,752 [INFO] bot: ✅ Loaded extension: commands.recent
2026-04-22 03:44:35,884 [INFO] bot: 🔄 Slash commands synced to guild 1055571472611033160
2026-04-22 03:44:36,069 [INFO] bot: 🔄 Global slash commands synced
2026-04-22 03:44:36,466 [INFO] discord.gateway: Shard ID None has connected to Gateway (Session ID: b5af520b7608ba76313f4e32f6045ed4).
2026-04-22 03:44:38,469 [INFO] bot: ✅ Logged in as league of legend bot#3877 (ID: 1494467149186465862)
2026-04-22 03:44:38,470 [INFO] bot: Guilds : 1
2026-04-22 03:44:38,470 [INFO] bot: Ping : 47ms
2026-04-23 00:15:51,686 [WARNING] discord.client: PyNaCl is not installed, voice will NOT be supported
2026-04-23 00:15:51,688 [WARNING] discord.client: davey is not installed, voice will NOT be supported
2026-04-23 00:15:51,690 [INFO] discord.client: logging in using static token
2026-04-23 00:15:52,218 [INFO] bot: ✅ Loaded extension: commands.login
2026-04-23 00:15:52,220 [INFO] bot: ✅ Loaded extension: commands.profile
2026-04-23 00:15:52,223 [INFO] bot: ✅ Loaded extension: commands.recent
2026-04-23 00:15:52,333 [INFO] bot: 🔄 Slash commands synced to guild 1055571472611033160
2026-04-23 00:15:52,674 [INFO] bot: 🔄 Global slash commands synced
2026-04-23 00:15:53,798 [INFO] discord.gateway: Shard ID None has connected to Gateway (Session ID: c95d0a5504b0553dfd47d07f17ad9d72).
2026-04-23 00:15:55,804 [INFO] bot: ✅ Logged in as league of legend bot#3877 (ID: 1494467149186465862)
2026-04-23 00:15:55,807 [INFO] bot: Guilds : 1
2026-04-23 00:15:55,807 [INFO] bot: Ping : 39ms
2026-04-23 00:23:23,262 [WARNING] discord.client: PyNaCl is not installed, voice will NOT be supported
2026-04-23 00:23:23,263 [WARNING] discord.client: davey is not installed, voice will NOT be supported
2026-04-23 00:23:23,264 [INFO] discord.client: logging in using static token
2026-04-23 00:23:23,782 [INFO] bot: ✅ Loaded extension: commands.login
2026-04-23 00:23:23,784 [ERROR] bot: ❌ Failed to load commands.profile: Extension 'commands.profile' raised an error: NameError: name 'aiohttp' is not defined
2026-04-23 00:23:23,788 [ERROR] bot: ❌ Failed to load commands.recent: Extension 'commands.recent' raised an error: NameError: name 'aiohttp' is not defined
2026-04-23 00:23:23,926 [INFO] bot: 🔄 Slash commands synced to guild 1055571472611033160
2026-04-23 00:23:24,080 [INFO] bot: 🔄 Global slash commands synced
2026-04-23 00:23:24,693 [INFO] discord.gateway: Shard ID None has connected to Gateway (Session ID: 1fe2f819a2266ec9c68c874572e3ca7c).
2026-04-23 00:23:26,699 [INFO] bot: ✅ Logged in as league of legend bot#3877 (ID: 1494467149186465862)
2026-04-23 00:23:26,699 [INFO] bot: Guilds : 1
2026-04-23 00:23:26,700 [INFO] bot: Ping : 42ms
2026-04-23 00:25:58,295 [WARNING] discord.client: PyNaCl is not installed, voice will NOT be supported
2026-04-23 00:25:58,295 [WARNING] discord.client: davey is not installed, voice will NOT be supported
2026-04-23 00:25:58,296 [INFO] discord.client: logging in using static token
2026-04-23 00:25:58,617 [INFO] bot: ✅ Loaded extension: commands.login
2026-04-23 00:25:58,619 [ERROR] bot: ❌ Failed to load commands.profile: Extension 'commands.profile' raised an error: NameError: name 'aiohttp' is not defined
2026-04-23 00:25:58,622 [ERROR] bot: ❌ Failed to load commands.recent: Extension 'commands.recent' raised an error: NameError: name 'aiohttp' is not defined
2026-04-23 00:25:58,729 [INFO] bot: 🔄 Slash commands synced to guild 1055571472611033160
2026-04-23 00:25:58,910 [INFO] bot: 🔄 Global slash commands synced
2026-04-23 00:25:59,706 [INFO] discord.gateway: Shard ID None has connected to Gateway (Session ID: 6be4de7a798203f744639beaeeb3bc77).
2026-04-23 00:26:01,749 [INFO] bot: ✅ Logged in as league of legend bot#3877 (ID: 1494467149186465862)
2026-04-23 00:26:01,760 [INFO] bot: Guilds : 1
2026-04-23 00:26:01,761 [INFO] bot: Ping : 42ms
2026-04-23 00:29:04,989 [WARNING] discord.client: PyNaCl is not installed, voice will NOT be supported
2026-04-23 00:29:04,989 [WARNING] discord.client: davey is not installed, voice will NOT be supported
2026-04-23 00:29:04,990 [INFO] discord.client: logging in using static token
2026-04-23 00:29:05,366 [INFO] bot: ✅ Loaded extension: commands.login
2026-04-23 00:29:05,370 [INFO] bot: ✅ Loaded extension: commands.profile
2026-04-23 00:29:05,373 [INFO] bot: ✅ Loaded extension: commands.recent
2026-04-23 00:29:05,663 [INFO] bot: 🔄 Slash commands synced to guild 1055571472611033160
2026-04-23 00:29:05,860 [INFO] bot: 🔄 Global slash commands synced
2026-04-23 00:29:06,727 [INFO] discord.gateway: Shard ID None has connected to Gateway (Session ID: 1caf69eb498bb76c6a22762831c789e3).
2026-04-23 00:29:08,757 [INFO] bot: ✅ Logged in as league of legend bot#3877 (ID: 1494467149186465862)
2026-04-23 00:29:08,761 [INFO] bot: Guilds : 1
2026-04-23 00:29:08,762 [INFO] bot: Ping : 43ms
2026-04-23 00:29:22,375 [ERROR] discord.app_commands.tree: Ignoring exception in command 'profile'
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/discord/app_commands/commands.py", line 858, in _do_call
return await self._callback(self.binding, interaction, **params) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/simonzheng/Documents/vscode/lolBot/commands/profile.py", line 41, in profile
summoner_id = summoner["id"]
~~~~~~~~^^^^^^
KeyError: 'id'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/discord/app_commands/tree.py", line 1302, in _call
await command._invoke_with_namespace(interaction, namespace)
File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/discord/app_commands/commands.py", line 884, in _invoke_with_namespace
return await self._do_call(interaction, transformed_values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/discord/app_commands/commands.py", line 877, in _do_call
raise CommandInvokeError(self, e) from e
discord.app_commands.errors.CommandInvokeError: Command 'profile' raised an exception: KeyError: 'id'
2026-04-23 00:30:43,277 [WARNING] discord.client: PyNaCl is not installed, voice will NOT be supported
2026-04-23 00:30:43,278 [WARNING] discord.client: davey is not installed, voice will NOT be supported
2026-04-23 00:30:43,279 [INFO] discord.client: logging in using static token
2026-04-23 00:30:44,221 [INFO] bot: ✅ Loaded extension: commands.login
2026-04-23 00:30:44,227 [INFO] bot: ✅ Loaded extension: commands.profile
2026-04-23 00:30:44,231 [INFO] bot: ✅ Loaded extension: commands.recent
2026-04-23 00:30:44,745 [INFO] bot: 🔄 Slash commands synced to guild 1055571472611033160
2026-04-23 00:30:44,922 [INFO] bot: 🔄 Global slash commands synced
2026-04-23 00:30:45,429 [INFO] discord.gateway: Shard ID None has connected to Gateway (Session ID: 07f28f18598c25062c3300ff0d0bf20f).
2026-04-23 00:30:47,479 [INFO] bot: ✅ Logged in as league of legend bot#3877 (ID: 1494467149186465862)
2026-04-23 00:30:47,481 [INFO] bot: Guilds : 1
2026-04-23 00:30:47,481 [INFO] bot: Ping : 50ms
2026-04-23 00:31:43,436 [ERROR] discord.app_commands.tree: Ignoring exception in command 'profile'
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/discord/app_commands/commands.py", line 858, in _do_call
return await self._callback(self.binding, interaction, **params) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/simonzheng/Documents/vscode/lolBot/commands/profile.py", line 41, in profile
summoner_id = summoner["id"]
~~~~~~~~^^^^^^
KeyError: 'id'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/discord/app_commands/tree.py", line 1302, in _call
await command._invoke_with_namespace(interaction, namespace)
File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/discord/app_commands/commands.py", line 884, in _invoke_with_namespace
return await self._do_call(interaction, transformed_values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/discord/app_commands/commands.py", line 877, in _do_call
raise CommandInvokeError(self, e) from e
discord.app_commands.errors.CommandInvokeError: Command 'profile' raised an exception: KeyError: 'id'
Binary file not shown.
Binary file not shown.
Binary file not shown.
+21 -24
View File
@@ -1,11 +1,9 @@
import discord import discord
from discord import app_commands from discord import app_commands
from discord.ext import commands from discord.ext import commands
import aiohttp
import os import os
from database.db import save_user from database.db import save_user
from services.riot_api import get_account
RIOT_API_KEY = os.getenv("LEAGUEAPI")
class LoginCog(commands.Cog): class LoginCog(commands.Cog):
def __init__(self, bot): def __init__(self, bot):
@@ -26,41 +24,40 @@ class LoginCog(commands.Cog):
discord_id = str(interaction.user.id) discord_id = str(interaction.user.id)
url = ( # Use the service instead of direct API call
f"https://americas.api.riotgames.com/riot/account/v1/accounts" data = await get_account(game_name, tag_line)
f"/by-riot-id/{game_name}/{tag_line}"
)
headers = {"X-Riot-Token": RIOT_API_KEY}
async with aiohttp.ClientSession() as session: if data is None:
async with session.get(url, headers=headers) as resp: await interaction.followup.send("❌ Summoner not found. Check your Riot ID.", ephemeral=True)
if resp.status == 404: return
await interaction.followup.send("❌ Summoner not found. Check your Riot ID.", ephemeral=True)
return
if resp.status != 200:
await interaction.followup.send(f"❌ Riot API error ({resp.status}). Try again later.", ephemeral=True)
return
data = await resp.json() if isinstance(data, dict) and "error" in data:
puuid = data["puuid"] if data["error"] == 401:
await interaction.followup.send(
"❌ Riot API error (401): Unauthorized. "
"This usually means the bot's API key has expired. "
"Please notify the bot owner to refresh it at developer.riotgames.com.",
ephemeral=True
)
else:
await interaction.followup.send(
f"❌ Riot API error ({data['error']}). Try again later.",
ephemeral=True
)
return
puuid = data["puuid"]
save_user(discord_id, puuid, game_name, tag_line) save_user(discord_id, puuid, game_name, tag_line)
await interaction.followup.send( await interaction.followup.send(
f"✅ Linked **{game_name}#{tag_line}** to your Discord account!", f"✅ Linked **{game_name}#{tag_line}** to your Discord account!",
ephemeral=True ephemeral=True
) )
@app_commands.command(name="login", description="Link your Riot account") @app_commands.command(name="login", description="Link your Riot account")
@app_commands.describe(summoner="Your Riot ID (e.g. Name#TAG)") @app_commands.describe(summoner="Your Riot ID (e.g. Name#TAG)")
async def login(self, interaction: discord.Interaction, summoner: str): async def login(self, interaction: discord.Interaction, summoner: str):
await self._handle_login(interaction, summoner) await self._handle_login(interaction, summoner)
@app_commands.command(name="signin", description="Link your Riot account")
@app_commands.describe(summoner="Your Riot ID (e.g. Name#TAG)")
async def signin(self, interaction: discord.Interaction, summoner: str):
await self._handle_login(interaction, summoner)
async def setup(bot): async def setup(bot):
await bot.add_cog(LoginCog(bot)) await bot.add_cog(LoginCog(bot))
+32 -32
View File
@@ -1,13 +1,10 @@
import discord import discord
from discord import app_commands from discord import app_commands
from discord.ext import commands from discord.ext import commands
import aiohttp
import os import os
from database.db import get_user from database.db import get_user
from services.riot_api import get_summoner, get_rank
RIOT_API_KEY = os.getenv("LEAGUEAPI")
REGION = "na1"
class ProfileCog(commands.Cog): class ProfileCog(commands.Cog):
def __init__(self, bot): def __init__(self, bot):
@@ -29,28 +26,38 @@ class ProfileCog(commands.Cog):
return return
_, puuid, game_name, tag_line = user _, puuid, game_name, tag_line = user
headers = {"X-Riot-Token": RIOT_API_KEY}
async with aiohttp.ClientSession() as session: # 1. Get summoner data (level) via PUUID
# 1. Get summoner data (level) via PUUID summoner = await get_summoner(puuid)
summoner = await fetch(
session,
f"https://{REGION}.api.riotgames.com/lol/summoner/v4/summoners/by-puuid/{puuid}",
headers
)
if not summoner:
await interaction.followup.send("❌ Could not fetch summoner data.")
return
summoner_id = summoner["id"] if not summoner:
level = summoner["summonerLevel"] await interaction.followup.send("❌ Could not fetch summoner data (Summoner not found).")
return
if isinstance(summoner, dict) and "error" in summoner:
if summoner["error"] == 401:
await interaction.followup.send("❌ Riot API error (401): Unauthorized. API Key might be expired.")
else:
await interaction.followup.send(f"❌ Riot API error ({summoner['error']}).")
return
# Ensure 'id' exists before accessing it
if "id" not in summoner:
await interaction.followup.send("❌ Unexpected data format from Riot API.")
return
summoner_id = summoner["id"]
level = summoner.get("summonerLevel", "N/A")
# 2. Get rank & LP (League-v4)
leagues = await get_rank(summoner_id)
if leagues is None:
leagues = []
elif isinstance(leagues, dict) and "error" in leagues:
await interaction.followup.send(f"❌ Riot API error ({leagues['error']}).")
return
# 2. Get rank & LP (League-v4)
leagues = await fetch(
session,
f"https://{REGION}.api.riotgames.com/lol/league/v4/entries/by-summoner/{summoner_id}",
headers
)
# Parse ranked solo queue data # Parse ranked solo queue data
rank_info = get_solo_rank(leagues) rank_info = get_solo_rank(leagues)
@@ -81,16 +88,9 @@ class ProfileCog(commands.Cog):
await interaction.followup.send(embed=embed) await interaction.followup.send(embed=embed)
# ── Helpers ──────────────────────────────────────────────────────────────────── # ── Helpers ────────────────────────────────────────────────────────────────────
#try to get the information def get_solo_rank(leagues) -> dict:
async def fetch(session:aiohttp.ClientSession, url: str, headers: dict):
async with session.get(url, headers=headers) as resp:
if resp.status != 200:
return None
return await resp.json()
def get_solo_rank(leagues: list) -> dict:
"""Extract solo queue entry from league data.""" """Extract solo queue entry from league data."""
if not leagues: if not isinstance(leagues, list) or not leagues:
return {} return {}
for entry in leagues: for entry in leagues:
if entry.get("queueType") == "RANKED_SOLO_5x5": if entry.get("queueType") == "RANKED_SOLO_5x5":
+27 -46
View File
@@ -1,27 +1,25 @@
import discord import discord
from discord import app_commands from discord import app_commands
from discord.ext import commands from discord.ext import commands
import aiohttp import asyncio
import os
from database.db import get_user from database.db import get_user
from services.riot_api import get_match_ids, get_match, get_latest_patch
RIOT_API_KEY = os.getenv("LEAGUEAPI")
REGION = "na1"
CLUSTER = "americas"
CLUSTER_MAP = {
"na1": "americas", "br1": "americas", "la1": "americas", "la2": "americas",
"euw1": "europe", "eun1": "europe", "tr1": "europe", "ru": "europe",
"kr": "asia", "jp1": "asia",
}
class RecentCog(commands.Cog): class RecentCog(commands.Cog):
def __init__(self, bot) -> None: def __init__(self, bot) -> None:
self.bot = bot self.bot = bot
self.patch = "14.8.1"
async def _update_patch(self):
new_patch = await get_latest_patch()
if new_patch:
self.patch = new_patch
@app_commands.command(name = "recent", description="Show your last N matches") @app_commands.command(name = "recent", description="Show your last N matches")
@app_commands.describe(count="Number of matches to show (1–10, default 5)") @app_commands.describe(count="Number of matches to show (1–10, default 5)")
async def recent(self , interaction: discord.Interaction, count: int = 5): async def recent(self , interaction: discord.Interaction, count: int = 5):
await interaction.response.defer(ephemeral=False) await interaction.response.defer(ephemeral=False)
await self._update_patch()
count = max(1, min(count, 10)) # clamp between 1 and 10 count = max(1, min(count, 10)) # clamp between 1 and 10
discord_id = str(interaction.user.id) discord_id = str(interaction.user.id)
@@ -36,32 +34,28 @@ class RecentCog(commands.Cog):
#get user #get user
_, puuid, game_name, tag_line = user _, puuid, game_name, tag_line = user
cluster = CLUSTER_MAP.get(REGION, "americas")
headers = {"X-Riot-Token": RIOT_API_KEY}
#get user data # 1. Fetch match ID list
async with aiohttp.ClientSession() as session: match_ids = await get_match_ids(puuid, count=count)
# 1. Fetch match ID list
match_ids = await fetch( if match_ids is None:
session, await interaction.followup.send("❌ Could not fetch match history.")
f"https://{cluster}.api.riotgames.com/lol/match/v5/matches/by-puuid/{puuid}/ids?count={count}", return
headers
) if isinstance(match_ids, dict) and "error" in match_ids:
if not match_ids: await interaction.followup.send(f"❌ Riot API error ({match_ids['error']}).")
await interaction.followup.send("❌ Could not fetch match history.") return
return
# 2. Fetch each match in parallel (get_match handles caching internally)
match_data = await asyncio.gather(*[get_match(mid) for mid in match_ids])
# 2. Fetch each match in parallel
match_data = await fetch_all(session, [
f"https://{cluster}.api.riotgames.com/lol/match/v5/matches/{mid}"
for mid in match_ids
], headers)
embeds = [] embeds = []
for match in match_data: for match in match_data:
if match: if match and isinstance(match, dict) and "error" not in match:
embed = build_match_embed(match , puuid, game_name,tag_line) embed = build_match_embed(match , puuid, game_name, tag_line, self.patch)
if embed: if embed:
embeds.append(embed) embeds.append(embed)
if not embeds: if not embeds:
await interaction.followup.send("❌ No recent matches found.") await interaction.followup.send("❌ No recent matches found.")
return return
@@ -78,19 +72,6 @@ class RecentCog(commands.Cog):
# ── Helpers ──────────────────────────────────────────────────────────────────── # ── Helpers ────────────────────────────────────────────────────────────────────
async def fetch(session: aiohttp.ClientSession, url: str, headers: dict):
async with session.get(url, headers=headers) as resp:
if resp.status != 200:
return None
return await resp.json()
async def fetch_all(session: aiohttp.ClientSession, urls: list, headers: dict):
"""Fetch multiple URLs concurrently."""
import asyncio
return await asyncio.gather(*[fetch(session, url, headers) for url in urls])
def get_player(match: dict, puuid: str) -> dict: def get_player(match: dict, puuid: str) -> dict:
"""Find this player's participant data within the match.""" """Find this player's participant data within the match."""
participants = match["info"]["participants"] participants = match["info"]["participants"]
@@ -118,7 +99,7 @@ def queue_name(queue_id: int) -> str:
return queues.get(queue_id, "Other") return queues.get(queue_id, "Other")
def build_match_embed(match: dict, puuid: str, game_name: str, tag_line: str): def build_match_embed(match: dict, puuid: str, game_name: str, tag_line: str, patch: str):
player = get_player(match, puuid) player = get_player(match, puuid)
if not player: if not player:
return None return None
@@ -145,7 +126,7 @@ def build_match_embed(match: dict, puuid: str, game_name: str, tag_line: str):
embed.add_field(name="🌾 CS", value=f"{cs} ({cs_per_min}/min)", inline=True) embed.add_field(name="🌾 CS", value=f"{cs} ({cs_per_min}/min)", inline=True)
embed.add_field(name="👤 Player", value=f"{game_name}#{tag_line}", inline=True) embed.add_field(name="👤 Player", value=f"{game_name}#{tag_line}", inline=True)
embed.set_thumbnail( embed.set_thumbnail(
url=f"https://ddragon.leagueoflegends.com/cdn/14.8.1/img/champion/{champion}.png" url=f"https://ddragon.leagueoflegends.com/cdn/{patch}/img/champion/{champion}.png"
) )
return embed return embed
Binary file not shown.
+22
View File
@@ -11,6 +11,14 @@ cursor.execute("""
tag_line TEXT tag_line TEXT
) )
""") """)
cursor.execute("""
CREATE TABLE IF NOT EXISTS matches(
match_id TEXT PRIMARY KEY,
data TEXT NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit() conn.commit()
@@ -32,3 +40,17 @@ def save_user(discord_id: str, puuid: str, game_name: str, tag_line: str):
def get_user(discord_id: str): def get_user(discord_id: str):
cursor.execute("SELECT * FROM users WHERE discord_id = ?", (discord_id,)) cursor.execute("SELECT * FROM users WHERE discord_id = ?", (discord_id,))
return cursor.fetchone() return cursor.fetchone()
def save_match(match_id: str, data: str):
cursor.execute(
"INSERT OR REPLACE INTO matches(match_id, data) VALUES (?, ?)",
(match_id, data),
)
conn.commit()
def get_match(match_id: str):
cursor.execute("SELECT data FROM matches WHERE match_id = ?", (match_id,))
row = cursor.fetchone()
return row[0] if row else None
Binary file not shown.
+15 -5
View File
@@ -3,7 +3,9 @@ import asyncio
import os import os
import json import json
from dotenv import load_dotenv from dotenv import load_dotenv
from db import save_match, get_match as get_cached_match from database.db import save_match, get_match as get_cached_match
load_dotenv()
RIOT_API_KEY = os.getenv("LEAGUEAPI") RIOT_API_KEY = os.getenv("LEAGUEAPI")
REGION = os.getenv("REGION", "na1") REGION = os.getenv("REGION", "na1")
@@ -24,21 +26,29 @@ CLUSTER = CLUSTER_MAP.get(REGION, "americas")
async def riot_get(url: str, retries: int = 3) -> dict | list | None: async def riot_get(url: str, retries: int = 3) -> dict | list | None:
if not RIOT_API_KEY:
print("❌ RIOT_API_KEY not found in environment")
return {"error": 401, "message": "API Key missing"}
headers = {"X-Riot-Token": RIOT_API_KEY} headers = {"X-Riot-Token": RIOT_API_KEY}
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
for attempt in range(retries): for attempt in range(retries):
async with session.get(url, headers=headers) as resp: async with session.get(url, headers=headers) as resp:
if resp.status == 200: if resp.status == 200:
return await resp.json() return await resp.json()
elif resp.status == 492: elif resp.status == 429:
wait = int(resp.headers.get("Retry-After", 5)) wait = int(resp.headers.get("Retry-After", 5))
print(f"⚠️ Rate limited. Retrying in {wait}s...") print(f"⚠️ Rate limited. Retrying in {wait}s...")
await asyncio.sleep(wait) await asyncio.sleep(wait)
elif resp.status == 401:
print(f"❌ 401 Unauthorized: RIOT_API_KEY is invalid or expired.")
return {"error": 401, "message": "Unauthorized (Key expired/invalid)"}
elif resp.status == 404: elif resp.status == 404:
return None return None
else: else:
print(f"❌ Error {resp.status} (attempt {attempt + 1}): {url}") print(f"❌ Error {resp.status} (attempt {attempt + 1}): {url}")
await asyncio.sleep(1) if attempt < retries - 1:
await asyncio.sleep(1)
return None return None
@@ -80,9 +90,9 @@ async def get_match(match_id: str) -> dict | None:
if cached: if cached:
return json.loads(cached) return json.loads(cached)
data = await riot_get( data = await riot_get(
f"https://{CLUSTER}.api.riotgames.com" f"/lol/match/v5/matches/{match_id}" f"https://{CLUSTER}.api.riotgames.com/lol/match/v5/matches/{match_id}"
) )
if data: if data and isinstance(data, dict) and "error" not in data:
save_match(match_id, json.dumps(data)) save_match(match_id, json.dumps(data))
return data return data
BIN
View File
Binary file not shown.