Spaces:
Runtime error
Runtime error
| # cogs/inventory.py | |
| import discord | |
| from discord import app_commands | |
| from discord.ext import commands | |
| import math | |
| import logging | |
| from database.db import db | |
| from utils.code_converter import encode_id, decode_id | |
| from utils.cdn import get_or_create_card_url | |
| logger = logging.getLogger(__name__) | |
| ITEMS_PER_PAGE = 10 | |
| # ========================================================== | |
| # 🔍 CORE INSPECT LOGIC (Used by Dropdown & Command) | |
| # ========================================================== | |
| async def handle_card_inspect(interaction: discord.Interaction, instance_id: int, ephemeral: bool = False): | |
| card_code = encode_id(instance_id) | |
| async with db.pool.acquire() as conn: | |
| char_data = await conn.fetchrow(""" | |
| SELECT uc.user_id, uc.level, uc.locked, uc.obtained_at, uc.print_num, uc.constellation, u.username AS owner_name, | |
| c.name, c.rarity, c.element, c.series, c.atk, c.def, c.image_url, c.c6_gif_url | |
| FROM user_characters uc | |
| JOIN characters c ON uc.char_id = c.char_id | |
| JOIN users u ON uc.user_id = u.user_id | |
| WHERE uc.instance_id = $1 | |
| """, instance_id) | |
| if not char_data: | |
| return await interaction.followup.send(f"❌ Could not find a character with code `{card_code}`.", ephemeral=ephemeral) | |
| c6_star = "" | |
| # C6 Awakened GIF Bypass | |
| if char_data['constellation'] >= 6 and char_data['c6_gif_url']: | |
| image_url = char_data['c6_gif_url'] | |
| c6_star = "🌟 [AWAKENED] " | |
| else: | |
| # Calls your new factory seamlessly! | |
| image_url = await get_or_create_card_url( | |
| bot=interaction.client, | |
| instance_id=instance_id, | |
| image_url=char_data['image_url'], | |
| char_name=char_data['name'], | |
| print_num=char_data['print_num'], | |
| rarity=char_data['rarity'], | |
| element=char_data['element'] | |
| ) | |
| embed = discord.Embed( | |
| title=f"{c6_star}{char_data['name']} (Lvl. {char_data['level']} | C{char_data['constellation']})", | |
| color=discord.Color.blurple() | |
| ) | |
| embed.add_field(name="👤 Owner", value=f"<@{char_data['user_id']}>", inline=False) | |
| embed.add_field(name="Rarity", value=char_data['rarity'], inline=True) | |
| embed.add_field(name="Element", value=char_data['element'] or "None", inline=True) | |
| embed.add_field(name="Series", value=char_data['series'] or "Unknown", inline=True) | |
| embed.add_field(name="Card Code", value=f"`{card_code}`", inline=True) | |
| # 🔥 FIXED: Actually scales the stats displayed based on their current level! | |
| level_mult = 1.0 + ((char_data['level'] - 1) * 0.15) | |
| f_atk = int(char_data['atk'] * level_mult) | |
| f_def = int(char_data['def'] * level_mult) | |
| embed.add_field(name="Stats", value=f"⚔️ {f_atk} | 🛡️ {f_def}", inline=True) | |
| embed.set_image(url=image_url) | |
| await interaction.followup.send(embed=embed, ephemeral=ephemeral) | |
| # ========================================================== | |
| # 🖱️ DYNAMIC DROPDOWN MENU | |
| # ========================================================== | |
| class CardInspectSelect(discord.ui.Select): | |
| def __init__(self, options): | |
| super().__init__(placeholder="🔍 Inspect a card from this page...", min_values=1, max_values=1, options=options) | |
| async def callback(self, interaction: discord.Interaction): | |
| # We defer ephemerally so the giant images don't clutter up the main channel! | |
| await interaction.response.defer(ephemeral=True) | |
| instance_id = int(self.values[0]) | |
| await handle_card_inspect(interaction, instance_id, ephemeral=True) | |
| # ========================================================== | |
| # 🎒 INVENTORY PAGINATOR | |
| # ========================================================== | |
| class InventoryPaginator(discord.ui.View): | |
| def __init__(self, user_id: int, total_pages: int, series_filter: str = None): | |
| super().__init__(timeout=120) | |
| self.user_id = user_id | |
| self.current_page = 1 | |
| self.total_pages = max(1, total_pages) | |
| self.series_filter = series_filter | |
| self.select_menu = None | |
| async def get_page_data(self) -> tuple[discord.Embed, list[discord.SelectOption]]: | |
| offset = (self.current_page - 1) * ITEMS_PER_PAGE | |
| query = """ | |
| SELECT uc.instance_id, c.name, c.rarity, uc.level, uc.locked, c.series, uc.constellation | |
| FROM user_characters uc | |
| JOIN characters c ON uc.char_id = c.char_id | |
| WHERE uc.user_id = $1 | |
| """ | |
| args = [self.user_id] | |
| if self.series_filter: | |
| query += " AND c.series ILIKE $2" | |
| args.append(f"%{self.series_filter}%") | |
| query += f" ORDER BY uc.obtained_at DESC LIMIT {ITEMS_PER_PAGE} OFFSET {offset}" | |
| async with db.pool.acquire() as conn: | |
| rows = await conn.fetch(query, *args) | |
| title = f"🎒 Inventory ({self.series_filter})" if self.series_filter else "🎒 Your Character Inventory" | |
| embed = discord.Embed(title=title, color=discord.Color.blurple()) | |
| desc = "" | |
| options = [] | |
| for row in rows: | |
| card_code = encode_id(row['instance_id']) | |
| lock = "🔒" if row['locked'] else "🔓" | |
| series_tag = f"[{row['series']}]" if row['series'] else "[Unknown Series]" | |
| desc += f"`{card_code}` | **{row['name']}** ({row['rarity']}) - Lvl {row['level']} | C{row['constellation']} {lock}\n└ *{series_tag}*\n" | |
| # Populate the Dropdown options securely | |
| opt_label = f"{row['name']} (Lvl {row['level']})"[:100] | |
| options.append(discord.SelectOption( | |
| label=opt_label, | |
| description=f"{row['rarity']} | Code: {card_code}", | |
| value=str(row['instance_id']) | |
| )) | |
| embed.description = desc if desc else "No characters found matching this criteria." | |
| embed.set_footer(text=f"Page {self.current_page} of {self.total_pages}") | |
| return embed, options | |
| async def refresh_ui(self, interaction: discord.Interaction = None): | |
| embed, options = await self.get_page_data() | |
| # Destroy the old dropdown and load the new one for the current page | |
| if self.select_menu: | |
| self.remove_item(self.select_menu) | |
| if options: | |
| self.select_menu = CardInspectSelect(options) | |
| self.add_item(self.select_menu) | |
| self.prev_button.disabled = self.current_page <= 1 | |
| self.next_button.disabled = self.current_page >= self.total_pages | |
| if interaction: | |
| await interaction.response.edit_message(embed=embed, view=self) | |
| return embed | |
| async def prev_button(self, interaction: discord.Interaction, button: discord.ui.Button): | |
| if interaction.user.id != self.user_id: | |
| return await interaction.response.send_message("Not your inventory!", ephemeral=True) | |
| self.current_page -= 1 | |
| await self.refresh_ui(interaction) | |
| async def next_button(self, interaction: discord.Interaction, button: discord.ui.Button): | |
| if interaction.user.id != self.user_id: | |
| return await interaction.response.send_message("Not your inventory!", ephemeral=True) | |
| self.current_page += 1 | |
| await self.refresh_ui(interaction) | |
| class InventoryCog(commands.Cog): | |
| def __init__(self, bot): | |
| self.bot = bot | |
| async def inventory(self, interaction: discord.Interaction, series: str = None): | |
| await interaction.response.defer(thinking=True) | |
| user_id = interaction.user.id | |
| async with db.pool.acquire() as conn: | |
| if series: | |
| total_items = await conn.fetchval("SELECT COUNT(*) FROM user_characters uc JOIN characters c ON uc.char_id = c.char_id WHERE uc.user_id = $1 AND c.series ILIKE $2", user_id, f"%{series}%") | |
| else: | |
| total_items = await conn.fetchval("SELECT COUNT(*) FROM user_characters WHERE user_id = $1", user_id) | |
| if total_items == 0: | |
| if series: | |
| return await interaction.followup.send(f"You don't own any cards from **{series}**.") | |
| return await interaction.followup.send("Your inventory is completely empty.") | |
| total_pages = math.ceil(total_items / ITEMS_PER_PAGE) | |
| view = InventoryPaginator(user_id, total_pages, series) | |
| # Build the UI on the first run | |
| embed = await view.refresh_ui() | |
| await interaction.followup.send(embed=embed, view=view) | |
| su = app_commands.Group(name="su", description="Sueni exclusive commands") | |
| async def su_card(self, interaction: discord.Interaction, series: str = None): | |
| await self.inventory.callback(self, interaction, series) | |
| async def suinfo(self, interaction: discord.Interaction, card_code: str): | |
| await interaction.response.defer(thinking=True) | |
| try: | |
| instance_id = decode_id(card_code) | |
| except ValueError: | |
| return await interaction.followup.send("❌ Invalid card code format.") | |
| # Delegate directly to our newly upgraded handler | |
| await handle_card_inspect(interaction, instance_id, ephemeral=False) | |
| async def setup(bot): | |
| await bot.add_cog(InventoryCog(bot)) | |