fix some bug that doesn't let user to sign in and connect with discord, added some test

This commit is contained in:
Rainy21
2026-04-22 03:44:29 -04:00
parent c6257c5260
commit 0a972f6a3f
13 changed files with 262 additions and 18 deletions
+68
View File
@@ -82,3 +82,71 @@ Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/aiohttp/connector.py", line 1327, in _wrap_create_connection File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/aiohttp/connector.py", line 1327, in _wrap_create_connection
raise ClientConnectorCertificateError(req.connection_key, exc) from exc raise ClientConnectorCertificateError(req.connection_key, exc) from exc
aiohttp.client_exceptions.ClientConnectorCertificateError: Cannot connect to host discord.com:443 ssl:True [SSLCertVerificationError: (1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1018)')] aiohttp.client_exceptions.ClientConnectorCertificateError: Cannot connect to host discord.com:443 ssl:True [SSLCertVerificationError: (1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1018)')]
2026-04-22 00:50:03,180 [WARNING] discord.client: PyNaCl is not installed, voice will NOT be supported
2026-04-22 00:50:03,181 [WARNING] discord.client: davey is not installed, voice will NOT be supported
2026-04-22 00:50:03,182 [INFO] discord.client: logging in using static token
2026-04-22 00:50:03,704 [INFO] bot: ✅ Loaded extension: commands.login
2026-04-22 00:50:03,708 [ERROR] bot: ❌ Failed to load commands.profile: Extension 'commands.profile' raised an error: NameError: name 'discord' is not defined
2026-04-22 00:50:03,714 [INFO] bot: ✅ Loaded extension: commands.recent
2026-04-22 00:50:03,910 [INFO] bot: 🔄 Slash commands synced
2026-04-22 00:50:04,352 [WARNING] bot: ⚠️ Bot disconnected from Discord
2026-04-22 00:54:47,504 [WARNING] discord.client: PyNaCl is not installed, voice will NOT be supported
2026-04-22 00:54:47,505 [WARNING] discord.client: davey is not installed, voice will NOT be supported
2026-04-22 00:54:47,506 [INFO] discord.client: logging in using static token
2026-04-22 00:54:47,891 [INFO] bot: ✅ Loaded extension: commands.login
2026-04-22 00:54:47,899 [INFO] bot: ✅ Loaded extension: commands.profile
2026-04-22 00:54:47,900 [INFO] bot: ✅ Loaded extension: commands.recent
2026-04-22 00:54:48,183 [INFO] bot: 🔄 Slash commands synced
2026-04-22 00:54:48,576 [WARNING] bot: ⚠️ Bot disconnected from Discord
2026-04-22 01:02:58,262 [WARNING] discord.client: PyNaCl is not installed, voice will NOT be supported
2026-04-22 01:02:58,263 [WARNING] discord.client: davey is not installed, voice will NOT be supported
2026-04-22 01:02:58,264 [INFO] discord.client: logging in using static token
2026-04-22 01:02:58,661 [INFO] bot: ✅ Loaded extension: commands.login
2026-04-22 01:02:58,661 [INFO] bot: ✅ Loaded extension: commands.profile
2026-04-22 01:02:58,663 [INFO] bot: ✅ Loaded extension: commands.recent
2026-04-22 01:02:58,798 [INFO] bot: 🔄 Slash commands synced
2026-04-22 01:03:01,276 [INFO] discord.gateway: Shard ID None has connected to Gateway (Session ID: 8ffd3e5eaf4edabab0603b0294b5b94f).
2026-04-22 01:03:03,284 [INFO] bot: ✅ Logged in as league of legend bot#3877 (ID: 1494467149186465862)
2026-04-22 01:03:03,284 [INFO] bot: Guilds : 1
2026-04-22 01:03:03,285 [INFO] bot: Ping : 42ms
2026-04-22 01:03:32,882 [ERROR] discord.app_commands.tree: Ignoring exception in command tree
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/discord/app_commands/tree.py", line 1138, in wrapper
await self._call(interaction)
File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/discord/app_commands/tree.py", line 1270, in _call
command, options = self._get_app_command_options(data)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/discord/app_commands/tree.py", line 1174, in _get_app_command_options
raise CommandNotFound(name, parents)
discord.app_commands.errors.CommandNotFound: Application command 'ping' not found
2026-04-22 01:03:53,411 [ERROR] discord.app_commands.tree: Ignoring exception in command 'login'
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 883, in _invoke_with_namespace
transformed_values = await self._transform_arguments(interaction, namespace)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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
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:20:37,191 [WARNING] discord.client: PyNaCl is not installed, voice will NOT be supported
2026-04-22 03:20:37,192 [WARNING] discord.client: davey is not installed, voice will NOT be supported
2026-04-22 03:20:37,193 [INFO] discord.client: logging in using static token
2026-04-22 03:20:37,715 [INFO] bot: ✅ Loaded extension: commands.login
2026-04-22 03:20:37,726 [INFO] bot: ✅ Loaded extension: commands.profile
2026-04-22 03:20:37,731 [INFO] bot: ✅ Loaded extension: commands.recent
2026-04-22 03:20:37,899 [INFO] bot: 🔄 Slash commands synced
2026-04-22 03:20:38,668 [INFO] discord.gateway: Shard ID None has connected to Gateway (Session ID: c6e5a2c0cdf32e5c69cdbabeed40a2d4).
2026-04-22 03:20:40,672 [INFO] bot: ✅ Logged in as league of legend bot#3877 (ID: 1494467149186465862)
2026-04-22 03:20:40,674 [INFO] bot: Guilds : 1
2026-04-22 03:20:40,674 [INFO] bot: Ping : 45ms
2026-04-22 03:21:03,101 [ERROR] discord.app_commands.tree: Ignoring exception in command 'login'
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 883, in _invoke_with_namespace
transformed_values = await self._transform_arguments(interaction, namespace)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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
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.
+28 -11
View File
@@ -3,7 +3,7 @@ from discord.ext import commands, tasks
import asyncio import asyncio
import os import os
import logging import logging
from datetime import datetime from datetime import datetime, timezone
from dotenv import load_dotenv from dotenv import load_dotenv
load_dotenv() load_dotenv()
@@ -52,8 +52,19 @@ class LoLBot(commands.Bot):
except Exception as e: except Exception as e:
log.error(f"❌ Failed to load {ext}: {e}") log.error(f"❌ Failed to load {ext}: {e}")
# If SERVER_ID is provided, sync specifically to that guild for instant updates
guild_id = os.getenv("SERVER_ID")
if guild_id:
try:
guild = discord.Object(id=int(guild_id))
self.tree.copy_global_to(guild=guild)
await self.tree.sync(guild=guild)
log.info(f"🔄 Slash commands synced to guild {guild_id}")
except Exception as e:
log.error(f"❌ Failed to sync to guild {guild_id}: {e}")
await self.tree.sync() await self.tree.sync()
log.info("🔄 Slash commands synced") log.info("🔄 Global slash commands synced")
async def on_ready(self): async def on_ready(self):
log.info(f"✅ Logged in as {self.user} (ID: {self.user.id})") log.info(f"✅ Logged in as {self.user} (ID: {self.user.id})")
@@ -127,11 +138,17 @@ bot = LoLBot()
@bot.command(name="sync") @bot.command(name="sync")
@commands.is_owner() @commands.is_owner()
async def sync(ctx): async def sync(ctx, guild: str = None):
"""Force re-sync slash commands (owner only).""" """Force re-sync slash commands. Use 'guild' to sync to current guild."""
if guild == "guild":
bot.tree.copy_global_to(guild=ctx.guild)
await bot.tree.sync(guild=ctx.guild)
await ctx.send(f"✅ Slash commands synced to **{ctx.guild.name}**.")
log.info(f"🔄 Manual sync triggered for guild {ctx.guild.id}")
else:
await bot.tree.sync() await bot.tree.sync()
await ctx.send("✅ Slash commands synced.") await ctx.send("✅ Global slash commands synced (may take an hour).")
log.info("🔄 Manual sync triggered") log.info("🔄 Manual global sync triggered")
@bot.command(name="reload") @bot.command(name="reload")
@@ -147,17 +164,17 @@ async def reload(ctx, extension: str):
log.error(f"Reload failed for {extension}: {e}") log.error(f"Reload failed for {extension}: {e}")
@bot.command(name="ping") @bot.tree.command(name="ping", description="Check bot latency")
async def ping(ctx): async def ping(interaction: discord.Interaction):
"""Check bot latency.""" """Check bot latency."""
await ctx.send(f"🏓 Pong! `{round(bot.latency * 1000)}ms`") await interaction.response.send_message(f"🏓 Pong! `{round(bot.latency * 1000)}ms`")
@bot.command(name="status") @bot.command(name="status")
@commands.is_owner() @commands.is_owner()
async def status(ctx): async def status(ctx):
"""Show bot status info (owner only).""" """Show bot status info (owner only)."""
uptime = datetime.utcnow() - bot.start_time if hasattr(bot, "start_time") else "N/A" uptime = datetime.now(timezone.utc) - bot.start_time if hasattr(bot, "start_time") else "N/A"
embed = discord.Embed(title="🤖 Bot Status", color=0x1A78C2) embed = discord.Embed(title="🤖 Bot Status", color=0x1A78C2)
embed.add_field(name="📡 Ping", value=f"{round(bot.latency * 1000)}ms", inline=True) embed.add_field(name="📡 Ping", value=f"{round(bot.latency * 1000)}ms", inline=True)
embed.add_field(name="🏠 Guilds", value=str(len(bot.guilds)), inline=True) embed.add_field(name="🏠 Guilds", value=str(len(bot.guilds)), inline=True)
@@ -175,7 +192,7 @@ async def main():
return return
async with bot: async with bot:
bot.start_time = datetime.utcnow() bot.start_time = datetime.now(timezone.utc)
await bot.start(token) await bot.start(token)
+30
View File
@@ -0,0 +1,30 @@
2026-04-22 00:54:47,504 [WARNING] discord.client: PyNaCl is not installed, voice will NOT be supported
2026-04-22 00:54:47,505 [WARNING] discord.client: davey is not installed, voice will NOT be supported
/Users/simonzheng/Documents/vscode/lolBot/bot.py:178: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
bot.start_time = datetime.utcnow()
2026-04-22 00:54:47,506 [INFO] discord.client: logging in using static token
2026-04-22 00:54:47,891 [INFO] bot: ✅ Loaded extension: commands.login
2026-04-22 00:54:47,899 [INFO] bot: ✅ Loaded extension: commands.profile
2026-04-22 00:54:47,900 [INFO] bot: ✅ Loaded extension: commands.recent
2026-04-22 00:54:48,183 [INFO] bot: 🔄 Slash commands synced
2026-04-22 00:54:48,576 [WARNING] bot: ⚠️ Bot disconnected from Discord
Traceback (most recent call last):
File "/Users/simonzheng/Documents/vscode/lolBot/bot.py", line 182, in <module>
asyncio.run(main())
~~~~~~~~~~~^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/runners.py", line 194, in run
return runner.run(main)
~~~~~~~~~~^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/runners.py", line 118, in run
return self._loop.run_until_complete(task)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/base_events.py", line 720, in run_until_complete
return future.result()
~~~~~~~~~~~~~^^
File "/Users/simonzheng/Documents/vscode/lolBot/bot.py", line 179, in main
await bot.start(token)
File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/discord/client.py", line 851, in start
await self.connect(reconnect=reconnect)
File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/discord/client.py", line 775, in connect
raise PrivilegedIntentsRequired(exc.shard_id) from None
discord.errors.PrivilegedIntentsRequired: Shard ID None is requesting privileged intents that have not been explicitly enabled in the developer portal. It is recommended to go to https://discord.com/developers/applications/ and explicitly enable the privileged intents within your application's page. If this is not possible, then consider disabling the privileged intents instead.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+11 -3
View File
@@ -11,9 +11,7 @@ class LoginCog(commands.Cog):
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
@app_commands.command(name="login", description="Link your Riot account") async def _handle_login(self, interaction: discord.Interaction, summoner: str):
@app_commands.describe(summoner="Your Riot ID (e.g. Name#TAG)")
async def login(self, interaction: discord.Interaction, summoner: str):
await interaction.response.defer(ephemeral=True) await interaction.response.defer(ephemeral=True)
if "#" not in summoner: if "#" not in summoner:
@@ -53,6 +51,16 @@ class LoginCog(commands.Cog):
ephemeral=True ephemeral=True
) )
@app_commands.command(name="login", description="Link your Riot account")
@app_commands.describe(summoner="Your Riot ID (e.g. Name#TAG)")
async def login(self, interaction: discord.Interaction, summoner: str):
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))
+2 -1
View File
@@ -1,3 +1,4 @@
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 aiohttp
@@ -23,7 +24,7 @@ class ProfileCog(commands.Cog):
#check if user login #check if user login
if not user: if not user:
await interaction.followup.send( await interaction.followup.send(
"❌ You haven't linked your Riot account yet. Use `/login` first." "❌ You haven't linked your Riot account yet. Use `/signin` first."
) )
return return
+1 -1
View File
@@ -30,7 +30,7 @@ class RecentCog(commands.Cog):
#check if user #check if user
if not user: if not user:
await interaction.followup.send( await interaction.followup.send(
"❌ You haven't linked your account yet. Use `/login` first." "❌ You haven't linked your account yet. Use `/signin` first."
) )
return return
Binary file not shown.
+1 -1
View File
@@ -67,7 +67,7 @@ async def get_rank(summoner_id: str) -> dict | None:
) )
async def get_match_ids(puuid: str, cont: int = 5) -> dict | None: async def get_match_ids(puuid: str, count: int = 5) -> dict | None:
return await riot_get( return await riot_get(
f"https://{CLUSTER}.api.riotgames.com" f"https://{CLUSTER}.api.riotgames.com"
f"/lol/match/v5/matches/by-puuid/{puuid}/ids?count={count}" f"/lol/match/v5/matches/by-puuid/{puuid}/ids?count={count}"
+120
View File
@@ -0,0 +1,120 @@
import unittest
import asyncio
from unittest.mock import AsyncMock, MagicMock, patch
import discord
from commands.login import LoginCog
from commands.profile import ProfileCog
from commands.recent import RecentCog
class TestCommands(unittest.IsolatedAsyncioTestCase):
def setUp(self):
self.bot = MagicMock()
self.login_cog = LoginCog(self.bot)
self.profile_cog = ProfileCog(self.bot)
self.recent_cog = RecentCog(self.bot)
async def test_login_success(self):
interaction = AsyncMock(spec=discord.Interaction)
interaction.user.id = 12345
interaction.response.defer = AsyncMock()
interaction.followup.send = AsyncMock()
with patch("commands.login.save_user") as mock_save, \
patch("aiohttp.ClientSession.get") as mock_get:
# Mock Riot Account API response
mock_resp = AsyncMock()
mock_resp.status = 200
mock_resp.json.return_value = {"puuid": "fake_puuid"}
mock_get.return_value.__aenter__.return_value = mock_resp
await self.login_cog.login.callback(self.login_cog, interaction, "TestUser#TAG")
interaction.response.defer.assert_called_once()
mock_save.assert_called_once_with("12345", "fake_puuid", "TestUser", "TAG")
interaction.followup.send.assert_called_once()
self.assertIn("Linked **TestUser#TAG**", interaction.followup.send.call_args[0][0])
async def test_login_not_found(self):
interaction = AsyncMock(spec=discord.Interaction)
interaction.response.defer = AsyncMock()
interaction.followup.send = AsyncMock()
with patch("aiohttp.ClientSession.get") as mock_get:
mock_resp = AsyncMock()
mock_resp.status = 404
mock_get.return_value.__aenter__.return_value = mock_resp
await self.login_cog.login.callback(self.login_cog, interaction, "NonExistent#TAG")
interaction.followup.send.assert_called_once()
self.assertIn("Summoner not found", interaction.followup.send.call_args[0][0])
async def test_profile_not_linked(self):
interaction = AsyncMock(spec=discord.Interaction)
interaction.user.id = 12345
interaction.response.defer = AsyncMock()
interaction.followup.send = AsyncMock()
with patch("commands.profile.get_user", return_value=None):
await self.profile_cog.profile.callback(self.profile_cog, interaction)
interaction.followup.send.assert_called_once()
self.assertIn("haven't linked your Riot account", interaction.followup.send.call_args[0][0])
async def test_profile_success(self):
interaction = AsyncMock(spec=discord.Interaction)
interaction.user.id = 12345
interaction.response.defer = AsyncMock()
interaction.followup.send = AsyncMock()
# (discord_id, puuid, game_name, tag_line)
mock_user = ("12345", "fake_puuid", "TestUser", "TAG")
with patch("commands.profile.get_user", return_value=mock_user), \
patch("commands.profile.fetch") as mock_fetch:
# Mock Summoner Data
mock_fetch.side_effect = [
{"id": "summoner_id", "summonerLevel": 100}, # Summoner info
[{"queueType": "RANKED_SOLO_5x5", "tier": "GOLD", "rank": "I", "leaguePoints": 50, "wins": 10, "losses": 5}] # League info
]
await self.profile_cog.profile.callback(self.profile_cog, interaction)
interaction.followup.send.assert_called_once()
embed = interaction.followup.send.call_args[1]["embed"]
self.assertEqual(embed.title, "📊 TestUser#TAG")
self.assertIn("Gold I", embed.fields[1].value)
async def test_recent_success(self):
interaction = AsyncMock(spec=discord.Interaction)
interaction.user.id = 12345
interaction.response.defer = AsyncMock()
interaction.followup.send = AsyncMock()
mock_user = ("12345", "fake_puuid", "TestUser", "TAG")
with patch("commands.recent.get_user", return_value=mock_user), \
patch("commands.recent.fetch") as mock_fetch, \
patch("commands.recent.fetch_all") as mock_fetch_all:
mock_fetch.return_value = ["match1"]
mock_fetch_all.return_value = [{
"metadata": {"matchId": "match1"},
"info": {
"participants": [{"puuid": "fake_puuid", "win": True, "championName": "Lux", "kills": 10, "deaths": 2, "assists": 15, "totalMinionsKilled": 100, "neutralMinionsKilled": 10}],
"gameDuration": 1800,
"queueId": 420
}
}]
await self.recent_cog.recent.callback(self.recent_cog, interaction, count=1)
interaction.followup.send.assert_called_once()
embeds = interaction.followup.send.call_args[1]["embeds"]
self.assertEqual(len(embeds), 1)
self.assertIn("Victory — Lux", embeds[0].title)
if __name__ == "__main__":
unittest.main()
BIN
View File
Binary file not shown.