gacha-bot / cogs /inventory.py
Amit
Clean Cloud Deployment
732ca45
Raw
History Blame Contribute Delete
10.1 kB
# 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
@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)
# 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")
@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.")
# 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))