| |
| 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 |
|
|
| |
| |
| |
| 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 = "" |
| |
| if char_data['constellation'] >= 6 and char_data['c6_gif_url']: |
| image_url = char_data['c6_gif_url'] |
| c6_star = "π [AWAKENED] " |
| else: |
| |
| 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) |
| |
| |
| 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) |
|
|
|
|
| |
| |
| |
| 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): |
| |
| await interaction.response.defer(ephemeral=True) |
| instance_id = int(self.values[0]) |
| await handle_card_inspect(interaction, instance_id, ephemeral=True) |
|
|
|
|
| |
| |
| |
| 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" |
| |
| |
| 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() |
| |
| |
| 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 |
|
|
| @discord.ui.button(label="βοΈ Prev", style=discord.ButtonStyle.secondary, custom_id="prev") |
| 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) |
|
|
| @discord.ui.button(label="Next βΆοΈ", style=discord.ButtonStyle.secondary, custom_id="next") |
| 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 |
|
|
| @app_commands.command(name="inventory", description="View your collected characters") |
| @app_commands.describe(series="Filter by Series (e.g. Genshin Impact)") |
| 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) |
| |
| |
| embed = await view.refresh_ui() |
| await interaction.followup.send(embed=embed, view=view) |
|
|
|
|
| su = app_commands.Group(name="su", description="Sueni exclusive commands") |
|
|
| @su.command(name="card", description="View all your cards, or filter by a specific series") |
| @app_commands.describe(series="Filter by Series (e.g. Genshin Impact)") |
| async def su_card(self, interaction: discord.Interaction, series: str = None): |
| await self.inventory.callback(self, interaction, series) |
|
|
|
|
| @app_commands.command(name="suinfo", description="Inspect ANY character card by its code") |
| @app_commands.describe(card_code="The alphanumeric code of the card") |
| 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.") |
|
|
| |
| await handle_card_inspect(interaction, instance_id, ephemeral=False) |
|
|
|
|
| async def setup(bot): |
| await bot.add_cog(InventoryCog(bot)) |
|
|