feat(stats): New /profile command.

This commit is contained in:
2023-10-09 18:23:10 +03:00
parent d674dc4c8e
commit cfc9ea5ea9
3 changed files with 112 additions and 3 deletions

View File

@@ -8,14 +8,17 @@ from discord import app_commands as appcmds
from discord.ui.button import ButtonStyle
from meta import LionBot, LionCog, LionContext
from core.lion_guild import VoiceMode
from utils.lib import error_embed
from utils.ui import LeoUI, AButton, utc_now
from gui.base import CardMode
from wards import low_management_ward
from . import babel
from .data import StatsData
from .ui import ProfileUI, WeeklyMonthlyUI, LeaderboardUI
from .settings import StatisticsSettings, StatisticsConfigUI
from .graphics.profilestats import get_full_profile
_p = babel._p
@@ -43,7 +46,7 @@ class StatsCog(LionCog):
name=_p('cmd:me', "me"),
description=_p(
'cmd:me|desc',
"Display your personal profile and summary statistics."
"Edit your personal profile and see your statistics."
)
)
@appcmds.guild_only
@@ -53,6 +56,50 @@ class StatsCog(LionCog):
await ui.run(ctx.interaction)
await ui.wait()
@cmds.hybrid_command(
name=_p('cmd:profile', 'profile'),
description=_p(
'cmd:profile|desc',
"Display the target's profile and statistics summary."
)
)
@appcmds.rename(
member=_p('cmd:profile|param:member', "member")
)
@appcmds.describe(
member=_p(
'cmd:profile|param:member|desc', "Member to display profile for."
)
)
@appcmds.guild_only
async def profile_cmd(self, ctx: LionContext, member: Optional[discord.Member] = None):
if not ctx.guild:
return
if not ctx.interaction:
return
member = member if member is not None else ctx.author
if member.bot:
# TODO: Localise
await ctx.reply(
"Bots cannot have profiles!",
ephemeral=True
)
return
await ctx.interaction.response.defer(thinking=True)
# Ensure the lion exists
await self.bot.core.lions.fetch_member(member.guild.id, member.id, member=member)
if ctx.lguild.guild_mode.voice:
mode = CardMode.VOICE
else:
mode = CardMode.TEXT
profile_data = await get_full_profile(self.bot, member.id, member.guild.id, mode)
with profile_data:
file = discord.File(profile_data, 'profile.png')
await ctx.reply(file=file)
@cmds.hybrid_command(
name=_p('cmd:stats', "stats"),
description=_p(

View File

@@ -17,11 +17,11 @@ async def get_profile_card(bot: LionBot, userid: int, guildid: int):
ranks: Optional[RankCog] = bot.get_cog('RankCog')
stats: Optional[StatsCog] = bot.get_cog('StatsCog')
if ranks is None or stats is None:
return
raise ValueError("Cannot get profile card without ranks and stats cog loaded.")
guild = bot.get_guild(guildid)
if guild is None:
return
raise ValueError(f"Cannot get profile card without guild {guildid}")
lion = await bot.core.lions.fetch_member(guildid, userid)
luser = lion.luser

View File

@@ -0,0 +1,62 @@
import asyncio
from io import BytesIO
from PIL import Image
from meta import LionBot
from gui.base import CardMode
from .stats import get_stats_card
from .profile import get_profile_card
card_gap = 10
async def get_full_profile(bot: LionBot, userid: int, guildid: int, mode: CardMode) -> BytesIO:
"""
Render both profile and stats for the target member in the given mode.
Combines the resulting cards into a single image and returns the image data.
"""
# Prepare cards for rendering
get_tasks = (
asyncio.create_task(get_stats_card(bot, userid, guildid, mode), name='get-stats-for-combined'),
asyncio.create_task(get_profile_card(bot, userid, guildid), name='get-profile-for-combined'),
)
stats_card, profile_card = await asyncio.gather(*get_tasks)
# Render cards
render_tasks = (
asyncio.create_task(stats_card.render(), name='render-stats-for-combined'),
asyncio.create_task(profile_card.render(), name='render=profile-for-combined'),
)
# Load the card data into images
stats_data, profile_data = await asyncio.gather(*render_tasks)
with BytesIO(stats_data) as stats_stream, BytesIO(profile_data) as profile_stream:
with Image.open(stats_stream) as stats_image, Image.open(profile_stream) as profile_image:
# Create a new blank image of the correct dimenstions
stats_bbox = stats_image.getbbox(alpha_only=False)
profile_bbox = profile_image.getbbox(alpha_only=False)
if stats_bbox is None or profile_bbox is None:
# Should be impossible, image is already checked by GUI client
raise ValueError("Could not combine, empty stats or profile image.")
combined = Image.new(
'RGBA',
(
max(stats_bbox[2], profile_bbox[2]),
stats_bbox[3] + card_gap + profile_bbox[3]
),
color=None
)
with combined:
combined.alpha_composite(profile_image)
combined.alpha_composite(stats_image, (0, profile_bbox[3] + card_gap))
results = BytesIO()
combined.save(results, format='PNG', compress_type=3, compress_level=1)
results.seek(0)
return results