Spaces:
Runtime error
Runtime error
| # 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 | |
| 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") | |
| 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}") | |
| 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)) | |