gacha-bot2 / cogs /profile.py
Amit9011's picture
Update cogs/profile.py
e01dcb8 verified
Raw
History Blame Contribute Delete
7.93 kB
# cogs/profile.py
import discord
from discord import app_commands
from discord.ext import commands
import aiohttp
import asyncio
import logging
import io
from database.db import db
from services.achievement_service import TITLES
from services.profile_service import ProfileService
from utils.code_converter import decode_id, encode_id
from utils.cdn import DUMP_CHANNEL_ID
logger = logging.getLogger(__name__)
# The new endpoint on your Forge Space specifically for profiles
HF_PROFILE_API_URL = "https://amit9011-gacha-forge.hf.space/generate_profile"
class ProfileCog(commands.Cog):
def __init__(self, bot):
self.bot = bot
@app_commands.command(name="suprofile", description="View your massive 8-card showcase grid")
@app_commands.describe(user="The player you want to look up")
async def profile(self, interaction: discord.Interaction, user: discord.Member = None):
await interaction.response.defer(thinking=True)
target = user or interaction.user
if target.bot: return await interaction.followup.send("Bots don't play gacha games.")
# 1. Fetch user data
async with db.pool.acquire() as conn:
await conn.execute("INSERT INTO users (user_id) VALUES ($1) ON CONFLICT DO NOTHING", target.id)
user_data = await conn.fetchrow(
"SELECT *, (SELECT COUNT(*) FROM user_characters WHERE user_id = $1) as card_count FROM users WHERE user_id = $1",
target.id
)
if not user_data:
return await interaction.followup.send("This user hasn't started their journey yet.")
title_display = f" | {TITLES.get(user_data['equipped_title'], '')} {user_data['equipped_title']}" if user_data['equipped_title'] else ""
coins = user_data['coins'] or 0
dust = user_data['dust'] or 0
cards = user_data['card_count'] or 0
embed = discord.Embed(
title=f"πŸ‘€ {target.display_name}'s Showcase{title_display}",
description=f"πŸͺ™ **{coins:,}** Coins | πŸŽ‡ **{dust:,}** Dust | 🎴 **{cards:,}** Cards",
color=discord.Color.dark_theme()
)
dump_channel = self.bot.get_channel(DUMP_CHANNEL_ID)
if not dump_channel:
try: dump_channel = await self.bot.fetch_channel(DUMP_CHANNEL_ID)
except Exception as e: logger.error(f"Could not fetch Dump Channel: {e}")
# ⚑ CDN Cache Check
if user_data.get('profile_dump_msg_id') and dump_channel:
try:
msg = await dump_channel.fetch_message(user_data['profile_dump_msg_id'])
if msg.attachments:
embed.set_image(url=msg.attachments[0].url)
return await interaction.followup.send(embed=embed)
except discord.NotFound:
logger.warning(f"Profile cache message {user_data['profile_dump_msg_id']} not found. Rebuilding...")
except Exception as e:
logger.error(f"Failed to fetch profile image cache: {e}")
# βš™οΈ Rebuild Data Payload for the Forge Space
team_data = []
async with db.pool.acquire() as conn:
slots = [user_data.get(f'profile_slot_{i}') for i in range(1, 9)]
active_ids = [id for id in slots if id is not None]
if active_ids:
char_rows = await conn.fetch("""
SELECT uc.instance_id, uc.print_num, c.name, c.image_url, c.rarity, c.element, c.custom_frame_url
FROM user_characters uc
JOIN characters c ON uc.char_id = c.char_id
WHERE uc.instance_id = ANY($1::int[])
""", active_ids)
row_map = {row['instance_id']: row for row in char_rows}
for slot_id in slots:
if slot_id and slot_id in row_map:
# Send lightweight URLs and data to the Forge, let the Forge download the images
team_data.append({
"name": row_map[slot_id]['name'],
"print_num": row_map[slot_id]['print_num'],
"rarity": row_map[slot_id]['rarity'],
"element": row_map[slot_id]['element'] or "None",
"image_url": row_map[slot_id]['image_url'],
"custom_frame_url": row_map[slot_id]['custom_frame_url']
})
else:
team_data.append(None)
# πŸš€ OFF-LOAD CPU TO THE FORGE SPACE
payload = {
"username": target.display_name,
"team_data": team_data
}
try:
async with aiohttp.ClientSession() as session:
async with session.post(HF_PROFILE_API_URL, json=payload, timeout=45) as resp:
if resp.status != 200:
raise ValueError(f"Forge rejected the profile render: {await resp.text()}")
profile_bytes = await resp.read()
except Exception as e:
logger.error(f"Forge Profile Error: {e}")
return await interaction.followup.send("❌ The Cloud Forge is currently overloaded. Please try again in a moment.")
file = discord.File(fp=io.BytesIO(profile_bytes), filename="profile.webp")
# πŸ“¦ Backup to CDN and Cache the Message ID
dump_msg = await dump_channel.send(file=file)
async with db.pool.acquire() as conn:
await conn.execute("UPDATE users SET profile_dump_msg_id = $1 WHERE user_id = $2", dump_msg.id, target.id)
embed.set_image(url=dump_msg.attachments[0].url)
await interaction.followup.send(embed=embed)
display = app_commands.Group(name="sudisplay", description="Manage the 8 display cards on your /profile")
@display.command(name="set", description="Equip a cosmetic card to your profile grid")
@app_commands.choices(slot=[app_commands.Choice(name=f"Slot {i}", value=i) for i in range(1, 9)])
async def display_set(self, interaction: discord.Interaction, slot: app_commands.Choice[int], card_code: str):
await interaction.response.defer(thinking=True)
try:
instance_id = decode_id(card_code)
except ValueError:
return await interaction.followup.send("❌ Invalid code.")
try:
name = await ProfileService.set_display_card(interaction.user.id, slot.value, instance_id)
async with db.pool.acquire() as conn:
await conn.execute("UPDATE users SET profile_dump_msg_id = NULL WHERE user_id = $1", interaction.user.id)
await interaction.followup.send(f"πŸ–ΌοΈ **{name}** is now displayed in **Slot {slot.value}**! Your profile image will update shortly.")
except ValueError as e:
await interaction.followup.send(f"❌ {e}")
@display.command(name="remove", description="Remove a cosmetic card from your profile grid")
@app_commands.choices(slot=[app_commands.Choice(name=f"Slot {i}", value=i) for i in range(1, 9)])
async def display_remove(self, interaction: discord.Interaction, slot: app_commands.Choice[int]):
await interaction.response.defer(thinking=True)
try:
await ProfileService.remove_display_card(interaction.user.id, slot.value)
async with db.pool.acquire() as conn:
await conn.execute("UPDATE users SET profile_dump_msg_id = NULL WHERE user_id = $1", interaction.user.id)
await interaction.followup.send(f"βœ… **Slot {slot.value}** cleared. Your profile image will update shortly.")
except ValueError as e:
await interaction.followup.send(f"❌ {e}")
async def setup(bot):
await bot.add_cog(ProfileCog(bot))