diff --git a/src/cogs/dev.py b/src/cogs/dev.py index a084a50..2e08cb8 100644 --- a/src/cogs/dev.py +++ b/src/cogs/dev.py @@ -1,8 +1,11 @@ import asyncio import re import traceback +from contextlib import redirect_stdout from io import StringIO from pprint import pprint +from textwrap import indent +from typing import Union import discord from discord import TextChannel, PermissionOverwrite, Message, ChannelType @@ -35,13 +38,14 @@ COGS_SHORTCUTS = { } RE_QUERY = re.compile( - r"^! ?e(val)? (`{1,3}py(thon)?\n)?(?P.*?)\n?(`{1,3})?\n?$", re.DOTALL + r"^! ?e(val)?[ \n]+(`{1,3}(py(thon)?\n)?)?(?P.*?)\n?(`{1,3})?\n?$", re.DOTALL ) class DevCog(Cog, name="Dev tools"): def __init__(self, bot: CustomBot): self.bot = bot + self.eval_locals = {} @command(name="interrupt") @has_role(Role.DEV) @@ -225,57 +229,99 @@ class DevCog(Cog, name="Dev tools"): await channel.delete_messages(to_delete) await ctx.message.delete() - def eval(self, msg: Message) -> discord.Embed: + async def eval(self, msg: Message) -> discord.Embed: guild: discord.Guild = msg.guild roles = guild.roles members = guild.members + hugs_cog = self.bot.get_cog("Divers") + hugs = hugs_cog.hugs + channel: TextChannel = msg.channel + send = lambda text: asyncio.create_task(channel.send(text)) query = re.match(RE_QUERY, msg.content).group("query") if not query: raise TfjmError("No query found.") - if "\n" in query: + if any(word in query for word in ("=", "return", "await", ":", "\n")): lines = query.splitlines() - if "return" not in lines[-1] and "=" not in lines[-1]: + if ( + "return" not in lines[-1] + and "=" not in lines[-1] + and not lines[-1].startswith(" ") + ): lines[-1] = f"return {lines[-1]}" - query = "\n ".join(lines) - query = f"def q():\n {query}\nresp = q()" + query = "\n".join(lines) + full_query = f"""async def query(): + try: +{indent(query, " " * 8)} + finally: + self.eval_locals.update(locals()) +""" + else: + full_query = query + + globs = {**globals(), **locals(), **self.eval_locals} + stdout = StringIO() try: - if "\n" in query: - q = compile(query, filename="query.py", mode="exec") - globs = {**globals(), **locals()} - locs = {} - exec(query, globs, locs) - resp = locs["resp"] - else: - resp = eval(query, globals(), locals()) + with redirect_stdout(stdout): + if "\n" in full_query: + locs = {} + exec(full_query, globs, locs) + resp = await locs["query"]() + else: + resp = eval(query, globs) except Exception as e: tb = StringIO() traceback.print_tb(e.__traceback__, file=tb) - tb.seek(0) embed = discord.Embed(title=str(e), color=discord.Colour.red()) - embed.add_field(name="Query", value=f"```py\n{query}\n```", inline=False) embed.add_field( - name="Traceback", value=f"```py\n{tb.read()}```", inline=False + name="Query", value=f"```py\n{full_query}\n```", inline=False + ) + embed.add_field( + name="Traceback", value=self.to_field_value(tb), inline=False ) else: out = StringIO() pprint(resp, out) - out.seek(0) + embed = discord.Embed(title="Result", color=discord.Colour.green()) - embed.add_field(name="Query", value=f"```py\n{query}```", inline=False) - embed.add_field(name="Value", value=f"```py\n{out.read()}```", inline=False) + embed.add_field(name="Query", value=f"```py\n{full_query}```", inline=False) + + value = self.to_field_value(out) + if resp is not None and value: + embed.add_field(name="Value", value=value, inline=False) + + stdout = self.to_field_value(stdout) + if stdout: + embed.add_field(name="Standard output", value=stdout, inline=False) + embed.set_footer(text="You may edit your message.") return embed + def to_field_value(self, string: Union[str, StringIO]): + if isinstance(string, StringIO): + string.seek(0) + string = string.read() + + if not string: + return + + if len(string) > 1000: + string = string[:500] + "\n...\n" + string[-500:] + + return f"```py\n{string}```" + @command(name="eval", aliases=["e"]) @is_owner() async def eval_cmd(self, ctx: Context): - """""" - embed = self.eval(ctx.message) + """(dev) Evalue l'entrée.""" + + self.eval_locals["ctx"] = ctx + + embed = await self.eval(ctx.message) resp = await ctx.send(embed=embed) def check(before, after): @@ -289,7 +335,7 @@ class DevCog(Cog, name="Dev tools"): except asyncio.TimeoutError: break - embed = self.eval(after) + embed = await self.eval(after) await resp.edit(embed=embed) # Remove the "You may edit your message" diff --git a/src/cogs/misc.py b/src/cogs/misc.py index 54b4437..84acffa 100644 --- a/src/cogs/misc.py +++ b/src/cogs/misc.py @@ -8,6 +8,7 @@ import urllib from collections import defaultdict, Counter from dataclasses import dataclass, field from functools import partial +from itertools import groupby from math import log from operator import attrgetter, itemgetter from time import time @@ -177,7 +178,7 @@ class MiscCog(Cog, name="Divers"): @command(name="fan", aliases=["join", "adhere"], hidden=True) async def fan_club_cmd(self, ctx: Context, who: Member): - """Permet de rejoindre le fan-club d'Ananas ou Citron Vert.""" + """Permet de rejoindre un fan club existant.""" role_id = FAN_CLUBS.get(who.id, None) role = get(ctx.guild.roles, id=role_id) @@ -362,10 +363,6 @@ class MiscCog(Cog, name="Divers"): diffs[m.id].add(h.hugger) for m, d in diffs.items(): - if m == self.bot.user.id: - print(everyone_diff) - print(d) - print(everyone_diff.union(d)) stats[m] += len(everyone_diff.union(d)) * 42 + everyone_hugs top = sorted(list(stats.items()), key=itemgetter(1), reverse=True) @@ -392,10 +389,7 @@ class MiscCog(Cog, name="Divers"): await ctx.send(embed=embed) - async def send_hugs_stats_for(self, ctx: Context, who: Member): - embed = discord.Embed( - title=f"Câlins de {who.display_name}", color=discord.Colour.magenta() - ) + async def send_hugs_stats_for(self, ctx: Context, who: discord.Member): given = self.hugs_given(ctx, who.id) received = self.hugs_received(ctx, who.id) @@ -410,6 +404,26 @@ class MiscCog(Cog, name="Divers"): "Morceaux": (len(cut), 30), } + most_given = Counter(h.hugged for h in given).most_common(1) + most_received = Counter(h.hugger for h in received).most_common(1) + most_given = most_given[0] if most_given else (0, 0) + most_received = most_received[0] if most_received else (0, 0) + + embed = discord.Embed( + title=f"Câlins de {who.display_name}", + color=discord.Colour.magenta(), + description=( + f"On peut dire que {who.mention} est très câlin·e, avec un score de " + f"{self.score_for(ctx, who.id)}. Iel a beaucoup câliné " + f"{self.name_for(ctx, most_given[0])} " + f"*({most_given[1]} :heart:)* et " + f"s'est beaucoup fait câliner par {self.name_for(ctx, most_received[0])} " + f"*({most_received[1]} :heart:)* !" + ), + ) + user: discord.User = self.bot.get_user(who.id) + embed.set_thumbnail(url=user.avatar_url) + for f, (v, h_factor) in infos.items(): heart = self.heart_for_stat(v * h_factor) if f == "Morceaux": @@ -454,7 +468,13 @@ class MiscCog(Cog, name="Divers"): if memb is not None: name = memb.mention else: - name = ctx.guild.get_role(member_or_role_id).mention + role = ctx.guild.get_role(member_or_role_id) + if role is None: + name = getattr( + self.bot.get_user(member_or_role_id), "mention", "Personne" + ) + else: + name = role.name return name diff --git a/src/cogs/tirages.py b/src/cogs/tirages.py index f915f4b..c84bbc9 100644 --- a/src/cogs/tirages.py +++ b/src/cogs/tirages.py @@ -510,7 +510,7 @@ class TirageCog(Cog, name="Tirages"): @commands.command(name="dice-all", aliases=["da"]) @commands.has_role(Role.DEV) async def dice_all_cmd(self, ctx, *teams): - """(dev) Lance un dé pour chaque equipe afin de tester les tirages.""" + """(dev) Lance un dé pour chaque equipe en entrée.""" channel = ctx.channel.id if channel in self.tirages: for t in teams: diff --git a/src/constants.py b/src/constants.py index 3793ed0..c0a6f48 100644 --- a/src/constants.py +++ b/src/constants.py @@ -12,6 +12,7 @@ __all__ = [ "TEAMS_CHANNEL_CATEGORY", "DIEGO", "ANANAS", + "YOHANN", "FAN_CLUBS", "BOT", "TOURNOIS",