diff --git a/BOT/botscript.py b/BOT/botscript.py index d97d2ea..7015078 100644 --- a/BOT/botscript.py +++ b/BOT/botscript.py @@ -3864,6 +3864,28 @@ async def player_stats(interaction: discord.Interaction, username: str = "", uid """ await collect_command_stats(interaction) lang = await guild_lang(interaction.guild.id) if interaction.guild else 'en' + + player_row = None + vehicle_rows: list = [] + + _VEHICLE_STATS_SQL = """ + SELECT + vehicle_internal, + vehicle, + SUM(ground_kills) as total_ground_kills, + SUM(air_kills) as total_air_kills, + SUM(assists) as total_assists, + SUM(captures) as total_captures, + SUM(deaths) as total_deaths, + SUM(CASE WHEN UPPER(victor_bool) = 'WIN' THEN 1 ELSE 0 END) as wins, + SUM(CASE WHEN UPPER(victor_bool) = 'LOSS' THEN 1 ELSE 0 END) as losses, + COUNT(*) as total_battles + FROM player_games_hist + WHERE UID = ? + GROUP BY vehicle_internal + ORDER BY total_battles DESC + """ + # Handle UID lookup if uid: await interaction.response.defer(thinking=True) @@ -3871,43 +3893,71 @@ async def player_stats(interaction: discord.Interaction, username: str = "", uid elif username: await interaction.response.defer(thinking=True) - # Search for player by username + # Search, then fetch all vehicle data in a single connection. try: async with aiosqlite.connect(SQ_BATTLES_DB_PATH) as db: db.row_factory = aiosqlite.Row - await db.create_function("ulower", 1, str.lower) + + # Exact match uses idx_pgh_nick; fall back to substring LIKE only if needed. async with db.execute( """ SELECT UID, MIN(nick) AS nick FROM player_games_hist - WHERE ulower(nick) LIKE ulower(?) + WHERE nick = ? COLLATE NOCASE GROUP BY UID ORDER BY nick LIMIT 25 """, - (f"%{username}%",), + (username,), ) as cursor: results = list(await cursor.fetchall()) + + if not results: + async with db.execute( + """ + SELECT UID, MIN(nick) AS nick + FROM player_games_hist + WHERE nick LIKE ? COLLATE NOCASE + GROUP BY UID + ORDER BY nick + LIMIT 25 + """, + (f"%{username}%",), + ) as cursor: + results = list(await cursor.fetchall()) + + if not results: + await interaction.followup.send( + t(lang, "player.no_players_found", username=username), + ephemeral=True + ) + return + elif len(results) > 1: + await interaction.followup.send( + t(lang, "player.multiple_matches"), + view=PlayerSelectViewForStats(results, interaction.user, lang=lang) + ) + return + + target_uid = results[0]["UID"] + + # Fetch vehicle stats in the same connection to avoid a second open/close. + async with db.execute( + "SELECT nick, squadron_name FROM player_games_hist WHERE UID = ? ORDER BY session_id DESC LIMIT 1", + (target_uid,) + ) as cursor: + player_row = await cursor.fetchone() + + if not player_row: + await interaction.followup.send(t(lang, "player.no_stats_found", uid=target_uid), ephemeral=True) + return + + async with db.execute(_VEHICLE_STATS_SQL, (target_uid,)) as cursor: + vehicle_rows = list(await cursor.fetchall()) except Exception as e: error_str = str(e)[:1800] if len(str(e)) > 1800 else str(e) await interaction.followup.send(t(lang, "common.database_error", error=error_str), ephemeral=True) return - - if not results: - await interaction.followup.send( - t(lang, "player.no_players_found", username=username), - ephemeral=True - ) - return - elif len(results) > 1: - # Multiple matches found - show dropdown - await interaction.followup.send( - t(lang, "player.multiple_matches"), - view=PlayerSelectViewForStats(results, interaction.user, lang=lang) - ) - return - - target_uid = results[0]["UID"] else: await interaction.response.send_message( t(lang, "player.must_provide_input"), @@ -3915,48 +3965,28 @@ async def player_stats(interaction: discord.Interaction, username: str = "", uid ) return - # Get vehicle stats for the player - try: - async with aiosqlite.connect(SQ_BATTLES_DB_PATH) as db: - db.row_factory = aiosqlite.Row + # UID path: fetch vehicle stats (username path already has them from above). + if uid: + try: + async with aiosqlite.connect(SQ_BATTLES_DB_PATH) as db: + db.row_factory = aiosqlite.Row - # Get player info - async with db.execute( - "SELECT nick, squadron_name FROM player_games_hist WHERE UID = ? ORDER BY session_id DESC LIMIT 1", - (target_uid,) - ) as cursor: - player_row = await cursor.fetchone() + async with db.execute( + "SELECT nick, squadron_name FROM player_games_hist WHERE UID = ? ORDER BY session_id DESC LIMIT 1", + (target_uid,) + ) as cursor: + player_row = await cursor.fetchone() - if not player_row: - await interaction.followup.send(t(lang, "player.no_stats_found", uid=target_uid), ephemeral=True) - return + if not player_row: + await interaction.followup.send(t(lang, "player.no_stats_found", uid=target_uid), ephemeral=True) + return - # Get aggregated vehicle stats - async with db.execute( - """ - SELECT - vehicle_internal, - vehicle, - SUM(ground_kills) as total_ground_kills, - SUM(air_kills) as total_air_kills, - SUM(assists) as total_assists, - SUM(captures) as total_captures, - SUM(deaths) as total_deaths, - SUM(CASE WHEN UPPER(victor_bool) = 'WIN' THEN 1 ELSE 0 END) as wins, - SUM(CASE WHEN UPPER(victor_bool) = 'LOSS' THEN 1 ELSE 0 END) as losses, - COUNT(*) as total_battles - FROM player_games_hist - WHERE UID = ? - GROUP BY vehicle_internal - ORDER BY total_battles DESC - """, - (target_uid,) - ) as cursor: - vehicle_rows = await cursor.fetchall() - except Exception as e: - error_str = str(e)[:1800] if len(str(e)) > 1800 else str(e) - await interaction.followup.send(t(lang, "common.database_error", error=error_str), ephemeral=True) - return + async with db.execute(_VEHICLE_STATS_SQL, (target_uid,)) as cursor: + vehicle_rows = list(await cursor.fetchall()) + except Exception as e: + error_str = str(e)[:1800] if len(str(e)) > 1800 else str(e) + await interaction.followup.send(t(lang, "common.database_error", error=error_str), ephemeral=True) + return if not vehicle_rows: await interaction.followup.send(t(lang, "player.no_vehicle_stats"), ephemeral=True) @@ -3989,6 +4019,7 @@ async def player_stats(interaction: discord.Interaction, username: str = "", uid 'win_rate': win_rate }) + assert player_row is not None player_info = { 'nick': player_row['nick'], 'squadron': player_row['squadron_name'], diff --git a/BOT/locales/en.json b/BOT/locales/en.json index c7602f3..1ff6e24 100644 --- a/BOT/locales/en.json +++ b/BOT/locales/en.json @@ -521,8 +521,8 @@ "points_update_desc": "# {old_total} -> {new_total} {chart}{wl_line}{placement_line}\n\n**Player Changes:**", "points_table_header": "Name Change Now\n", "wl_line": "\n**{squadron}** went **{wins}W-{losses}L** this session", - "placement_rose": "\n**{squadron}** rose to **{new_place}** from **{old_place}**", - "placement_fell": "\n**{squadron}** fell to **{new_place}** from **{old_place}**", + "placement_rose": "\n**{squadron}** moved up from **{old_place}** to **{new_place}**", + "placement_fell": "\n**{squadron}** moved down from **{old_place}** to **{new_place}**", "points_not_logged_title": "Points Not Logged", "points_not_logged_desc": "Use `/unlock` to subscribe to the **Standard** tier (or higher) to receive automatic points updates.", "server_not_upgraded_points_desc": "This server does not have an active Premium subscription.\n\n**Automatic updates will stop being sent to non-upgraded servers after .**\n\nUse `/unlock` to subscribe and keep receiving automatic updates.",