feat(stats): New /profile command.
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
62
src/modules/statistics/graphics/profilestats.py
Normal file
62
src/modules/statistics/graphics/profilestats.py
Normal 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
|
||||
Reference in New Issue
Block a user