2020-05-01 15:08:05 +00:00
|
|
|
import asyncio
|
2020-05-18 16:27:53 +00:00
|
|
|
import re
|
|
|
|
import traceback
|
2020-05-19 15:11:35 +00:00
|
|
|
from contextlib import redirect_stdout
|
2020-05-18 16:27:53 +00:00
|
|
|
from io import StringIO
|
2020-04-28 10:41:26 +00:00
|
|
|
from pprint import pprint
|
2020-05-19 15:11:35 +00:00
|
|
|
from textwrap import indent
|
|
|
|
from typing import Union
|
2020-04-28 10:41:26 +00:00
|
|
|
|
2020-04-28 16:34:46 +00:00
|
|
|
import discord
|
2020-05-10 15:30:45 +00:00
|
|
|
from discord import TextChannel, PermissionOverwrite, Message, ChannelType
|
2020-05-05 11:39:44 +00:00
|
|
|
from discord.ext.commands import (
|
|
|
|
command,
|
|
|
|
has_role,
|
|
|
|
Bot,
|
|
|
|
Cog,
|
|
|
|
ExtensionNotLoaded,
|
|
|
|
Context,
|
2020-05-18 16:27:53 +00:00
|
|
|
is_owner,
|
2020-05-05 11:39:44 +00:00
|
|
|
)
|
2020-04-28 16:34:46 +00:00
|
|
|
from discord.utils import get
|
2020-05-01 15:08:05 +00:00
|
|
|
from ptpython.repl import embed
|
2020-04-28 10:41:26 +00:00
|
|
|
|
|
|
|
from src.constants import *
|
2020-04-30 15:26:33 +00:00
|
|
|
from src.core import CustomBot
|
2020-05-18 16:27:53 +00:00
|
|
|
from src.errors import TfjmError
|
2020-05-19 08:11:50 +00:00
|
|
|
from src.utils import fg, french_join
|
2020-04-28 10:41:26 +00:00
|
|
|
|
2020-04-30 15:26:33 +00:00
|
|
|
COGS_SHORTCUTS = {
|
2020-05-06 15:38:47 +00:00
|
|
|
"bt": "src.base_tirage",
|
2020-05-04 14:18:09 +00:00
|
|
|
"c": "src.constants",
|
2020-04-30 15:26:33 +00:00
|
|
|
"d": "tirages",
|
|
|
|
"e": "errors",
|
|
|
|
"m": "misc",
|
|
|
|
"t": "teams",
|
|
|
|
"u": "src.utils",
|
|
|
|
"v": "dev",
|
|
|
|
}
|
2020-04-29 12:43:51 +00:00
|
|
|
|
2020-05-18 16:27:53 +00:00
|
|
|
RE_QUERY = re.compile(
|
2020-05-19 15:11:35 +00:00
|
|
|
r"^! ?e(val)?[ \n]+(`{1,3}(py(thon)?\n)?)?(?P<query>.*?)\n?(`{1,3})?\n?$", re.DOTALL
|
2020-05-18 16:27:53 +00:00
|
|
|
)
|
|
|
|
|
2020-04-29 12:43:51 +00:00
|
|
|
|
2020-04-28 10:41:26 +00:00
|
|
|
class DevCog(Cog, name="Dev tools"):
|
2020-04-30 15:26:33 +00:00
|
|
|
def __init__(self, bot: CustomBot):
|
2020-04-28 10:41:26 +00:00
|
|
|
self.bot = bot
|
2020-05-19 15:11:35 +00:00
|
|
|
self.eval_locals = {}
|
2020-04-28 10:41:26 +00:00
|
|
|
|
|
|
|
@command(name="interrupt")
|
|
|
|
@has_role(Role.DEV)
|
|
|
|
async def interrupt_cmd(self, ctx):
|
|
|
|
"""
|
|
|
|
(dev) Ouvre une console là où un @dev m'a lancé. :warning:
|
|
|
|
|
|
|
|
A utiliser en dernier recours:
|
|
|
|
- le bot sera inactif pendant ce temps.
|
|
|
|
- toutes les commandes seront executées à sa reprise.
|
|
|
|
"""
|
|
|
|
|
|
|
|
await ctx.send(
|
|
|
|
"J'ai été arrêté et une console interactive a été ouverte là où je tourne. "
|
|
|
|
"Toutes les commandes rateront tant que cette console est ouverte.\n"
|
|
|
|
"Soyez rapides, je déteste les opérations à coeur ouvert... :confounded:"
|
|
|
|
)
|
|
|
|
|
|
|
|
# Utility functions
|
|
|
|
|
2020-05-01 15:08:05 +00:00
|
|
|
def send(msg, channel=None):
|
2020-05-04 14:16:06 +00:00
|
|
|
if isinstance(channel, int):
|
2020-05-11 11:32:04 +00:00
|
|
|
channel = self.bot.get_channel(channel)
|
2020-05-04 14:16:06 +00:00
|
|
|
|
2020-05-01 15:08:05 +00:00
|
|
|
channel = channel or ctx.channel
|
|
|
|
asyncio.create_task(channel.send(msg))
|
|
|
|
|
|
|
|
try:
|
|
|
|
await embed(
|
|
|
|
globals(), locals(), vi_mode=True, return_asyncio_coroutine=True
|
|
|
|
)
|
|
|
|
except EOFError:
|
|
|
|
pass
|
2020-04-28 10:41:26 +00:00
|
|
|
|
|
|
|
await ctx.send("Tout va mieux !")
|
|
|
|
|
2020-04-29 12:43:51 +00:00
|
|
|
def full_cog_name(self, name):
|
|
|
|
name = COGS_SHORTCUTS.get(name, name)
|
|
|
|
if not "." in name:
|
|
|
|
name = f"src.cogs.{name}"
|
|
|
|
|
|
|
|
return name
|
|
|
|
|
|
|
|
@command(
|
|
|
|
name="reload", aliases=["r"], usage=f"[{'|'.join(COGS_SHORTCUTS.values())}]"
|
|
|
|
)
|
2020-04-28 10:41:26 +00:00
|
|
|
@has_role(Role.DEV)
|
2020-04-29 19:25:05 +00:00
|
|
|
async def reload_cmd(self, ctx, name=None):
|
2020-04-28 16:34:46 +00:00
|
|
|
"""
|
2020-04-29 14:48:11 +00:00
|
|
|
(dev) Recharge une catégorie de commandes.
|
2020-04-28 16:34:46 +00:00
|
|
|
|
|
|
|
A utiliser quand le code change. Arguments
|
|
|
|
possibles: `teams`, `tirages`, `dev`.
|
|
|
|
"""
|
2020-04-28 10:41:26 +00:00
|
|
|
|
2020-04-30 15:26:33 +00:00
|
|
|
if name is None:
|
|
|
|
self.bot.reload()
|
|
|
|
await ctx.send(":tada: The bot was reloaded !")
|
|
|
|
return
|
|
|
|
|
2020-05-04 14:18:09 +00:00
|
|
|
name = self.full_cog_name(name)
|
2020-04-29 19:25:05 +00:00
|
|
|
|
2020-05-04 14:18:09 +00:00
|
|
|
try:
|
|
|
|
self.bot.reload_extension(name)
|
|
|
|
except ExtensionNotLoaded:
|
|
|
|
await ctx.invoke(self.load_cmd, name)
|
|
|
|
return
|
|
|
|
except:
|
|
|
|
await ctx.send(f":grimacing: **{name}** n'a pas pu être rechargée.")
|
|
|
|
raise
|
|
|
|
else:
|
|
|
|
await ctx.send(f":tada: L'extension **{name}** a bien été rechargée.")
|
2020-04-28 10:41:26 +00:00
|
|
|
|
2020-04-29 12:43:51 +00:00
|
|
|
@command(name="load", aliases=["l"])
|
|
|
|
@has_role(Role.DEV)
|
|
|
|
async def load_cmd(self, ctx, name):
|
|
|
|
"""
|
|
|
|
(dev) Ajoute une catégorie de commandes.
|
|
|
|
|
|
|
|
Permet d'ajouter dynamiquement un cog sans redémarrer le bot.
|
|
|
|
"""
|
|
|
|
name = self.full_cog_name(name)
|
|
|
|
|
|
|
|
try:
|
|
|
|
self.bot.load_extension(name)
|
|
|
|
except:
|
|
|
|
await ctx.send(f":grimacing: **{name}** n'a pas pu être chargée.")
|
|
|
|
raise
|
|
|
|
else:
|
|
|
|
await ctx.send(f":tada: L'extension **{name}** a bien été ajoutée !")
|
|
|
|
|
|
|
|
# noinspection PyUnreachableCode
|
2020-05-18 14:51:23 +00:00
|
|
|
@command(name="setup")
|
2020-04-28 18:08:57 +00:00
|
|
|
@has_role(Role.DEV)
|
2020-05-19 08:11:50 +00:00
|
|
|
async def setup_roles(self, ctx: Context, *teams: discord.Role):
|
2020-04-28 16:34:46 +00:00
|
|
|
"""
|
2020-04-29 14:48:11 +00:00
|
|
|
(dev) Commande temporaire pour setup le serveur.
|
2020-04-28 16:34:46 +00:00
|
|
|
"""
|
2020-05-19 08:11:50 +00:00
|
|
|
return
|
|
|
|
finalist = get(ctx.guild.roles, name=Role.FINALISTE)
|
|
|
|
assert finalist
|
|
|
|
|
|
|
|
for t in teams:
|
|
|
|
m: discord.Member
|
|
|
|
for m in t.members:
|
|
|
|
await m.add_roles(finalist)
|
|
|
|
|
|
|
|
await ctx.send(
|
|
|
|
f"{french_join(t.mention for t in teams)} ont été ajouté en finale !"
|
|
|
|
)
|
2020-04-28 16:34:46 +00:00
|
|
|
|
|
|
|
return
|
|
|
|
guild: discord.Guild = ctx.guild
|
|
|
|
nothing = PermissionOverwrite(read_messages=False)
|
|
|
|
see = PermissionOverwrite(read_messages=True)
|
2020-05-01 15:08:05 +00:00
|
|
|
# orga = get(guild.roles, name=f"Orga {t}")
|
|
|
|
|
|
|
|
for t in TOURNOIS[3:]:
|
|
|
|
jury = get(guild.roles, name=f"Jury {t}")
|
|
|
|
for p in "AB":
|
|
|
|
await guild.create_voice_channel(
|
|
|
|
f"blabla-jury-poule-{p}",
|
|
|
|
overwrites={guild.default_role: nothing, jury: see},
|
|
|
|
category=get(guild.categories, name=t),
|
|
|
|
)
|
2020-04-28 16:34:46 +00:00
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
aide: TextChannel = get(guild.text_channels, name="aide")
|
|
|
|
for t in TOURNOIS:
|
|
|
|
await aide.set_permissions(orga, overwrite=see)
|
|
|
|
await aide.set_permissions(jury, overwrite=see)
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
tournois = {
|
|
|
|
tournoi: get(guild.categories, name=tournoi) for tournoi in TOURNOIS
|
|
|
|
}
|
|
|
|
|
|
|
|
for ch in guild.text_channels:
|
|
|
|
print(repr(ch.category))
|
|
|
|
|
|
|
|
for tournoi, cat in tournois.items():
|
|
|
|
if tournoi == "Lyon":
|
|
|
|
continue
|
|
|
|
|
|
|
|
jury_channel: TextChannel = get(
|
|
|
|
guild.text_channels, category=cat, name="cro"
|
|
|
|
)
|
|
|
|
await jury_channel.delete()
|
|
|
|
# jury = get(guild.roles, name=f"Jury {tournoi}")
|
|
|
|
orga = get(guild.roles, name=f"Orga {tournoi}")
|
|
|
|
ov = {
|
|
|
|
guild.default_role: nothing,
|
|
|
|
# jury: see,
|
|
|
|
orga: see,
|
|
|
|
}
|
|
|
|
await guild.create_text_channel(
|
|
|
|
f"cro-{tournoi}", category=cat, overwrites=ov
|
|
|
|
)
|
|
|
|
|
|
|
|
await ctx.send(str(jury_channel))
|
|
|
|
|
2020-04-30 19:22:17 +00:00
|
|
|
@command(name="send")
|
2020-04-30 18:11:07 +00:00
|
|
|
@has_role(Role.DEV)
|
|
|
|
async def send_cmd(self, ctx, *msg):
|
2020-05-18 14:51:23 +00:00
|
|
|
"""(dev) Envoie un message."""
|
2020-04-30 18:11:07 +00:00
|
|
|
await ctx.message.delete()
|
|
|
|
await ctx.send(" ".join(msg))
|
|
|
|
|
2020-05-05 11:39:44 +00:00
|
|
|
@command(name="del")
|
|
|
|
@has_role(Role.CNO)
|
|
|
|
async def del_range_cmd(self, ctx: Context, id1: Message, id2: Message):
|
|
|
|
"""
|
|
|
|
(cno) Supprime les messages entre les deux IDs en argument.
|
|
|
|
"""
|
|
|
|
channel: TextChannel = id1.channel
|
|
|
|
to_delete = [
|
|
|
|
message async for message in channel.history(before=id1, after=id2)
|
|
|
|
] + [id1, id2]
|
|
|
|
await channel.delete_messages(to_delete)
|
|
|
|
await ctx.message.delete()
|
|
|
|
|
2020-05-19 15:11:35 +00:00
|
|
|
async def eval(self, msg: Message) -> discord.Embed:
|
2020-05-19 08:11:50 +00:00
|
|
|
guild: discord.Guild = msg.guild
|
|
|
|
roles = guild.roles
|
|
|
|
members = guild.members
|
2020-05-19 15:11:35 +00:00
|
|
|
hugs_cog = self.bot.get_cog("Divers")
|
|
|
|
hugs = hugs_cog.hugs
|
|
|
|
channel: TextChannel = msg.channel
|
|
|
|
send = lambda text: asyncio.create_task(channel.send(text))
|
2020-05-18 16:27:53 +00:00
|
|
|
|
|
|
|
query = re.match(RE_QUERY, msg.content).group("query")
|
|
|
|
|
|
|
|
if not query:
|
|
|
|
raise TfjmError("No query found.")
|
|
|
|
|
2020-05-19 15:11:35 +00:00
|
|
|
if any(word in query for word in ("=", "return", "await", ":", "\n")):
|
2020-05-18 16:27:53 +00:00
|
|
|
lines = query.splitlines()
|
2020-05-19 15:11:35 +00:00
|
|
|
if (
|
|
|
|
"return" not in lines[-1]
|
|
|
|
and "=" not in lines[-1]
|
|
|
|
and not lines[-1].startswith(" ")
|
|
|
|
):
|
2020-05-18 16:27:53 +00:00
|
|
|
lines[-1] = f"return {lines[-1]}"
|
2020-05-19 15:11:35 +00:00
|
|
|
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()
|
2020-05-18 16:27:53 +00:00
|
|
|
|
|
|
|
try:
|
2020-05-19 15:11:35 +00:00
|
|
|
with redirect_stdout(stdout):
|
|
|
|
if "\n" in full_query:
|
|
|
|
locs = {}
|
|
|
|
exec(full_query, globs, locs)
|
|
|
|
resp = await locs["query"]()
|
|
|
|
else:
|
|
|
|
resp = eval(query, globs)
|
2020-05-18 16:27:53 +00:00
|
|
|
except Exception as e:
|
|
|
|
tb = StringIO()
|
|
|
|
traceback.print_tb(e.__traceback__, file=tb)
|
|
|
|
|
|
|
|
embed = discord.Embed(title=str(e), color=discord.Colour.red())
|
|
|
|
embed.add_field(
|
2020-05-19 15:11:35 +00:00
|
|
|
name="Query", value=f"```py\n{full_query}\n```", inline=False
|
|
|
|
)
|
|
|
|
embed.add_field(
|
|
|
|
name="Traceback", value=self.to_field_value(tb), inline=False
|
2020-05-18 16:27:53 +00:00
|
|
|
)
|
|
|
|
else:
|
|
|
|
out = StringIO()
|
|
|
|
pprint(resp, out)
|
2020-05-19 15:11:35 +00:00
|
|
|
|
2020-05-18 16:27:53 +00:00
|
|
|
embed = discord.Embed(title="Result", color=discord.Colour.green())
|
2020-05-19 15:11:35 +00:00
|
|
|
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)
|
|
|
|
|
2020-05-19 08:11:50 +00:00
|
|
|
embed.set_footer(text="You may edit your message.")
|
|
|
|
return embed
|
|
|
|
|
2020-05-19 15:11:35 +00:00
|
|
|
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}```"
|
|
|
|
|
2020-05-19 08:11:50 +00:00
|
|
|
@command(name="eval", aliases=["e"])
|
|
|
|
@is_owner()
|
|
|
|
async def eval_cmd(self, ctx: Context):
|
2020-05-19 15:11:35 +00:00
|
|
|
"""(dev) Evalue l'entrée."""
|
|
|
|
|
|
|
|
self.eval_locals["ctx"] = ctx
|
|
|
|
|
|
|
|
embed = await self.eval(ctx.message)
|
2020-05-19 08:11:50 +00:00
|
|
|
resp = await ctx.send(embed=embed)
|
|
|
|
|
|
|
|
def check(before, after):
|
|
|
|
return after.id == ctx.message.id
|
|
|
|
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
before, after = await self.bot.wait_for(
|
|
|
|
"message_edit", check=check, timeout=600
|
|
|
|
)
|
|
|
|
except asyncio.TimeoutError:
|
|
|
|
break
|
|
|
|
|
2020-05-19 15:11:35 +00:00
|
|
|
embed = await self.eval(after)
|
2020-05-19 08:11:50 +00:00
|
|
|
await resp.edit(embed=embed)
|
|
|
|
|
|
|
|
# Remove the "You may edit your message"
|
|
|
|
embed.set_footer()
|
|
|
|
try:
|
|
|
|
await resp.edit(embed=embed)
|
|
|
|
except discord.NotFound:
|
|
|
|
pass
|
2020-05-18 16:27:53 +00:00
|
|
|
|
2020-05-10 15:30:45 +00:00
|
|
|
@Cog.listener()
|
|
|
|
async def on_message(self, msg: Message):
|
|
|
|
ch: TextChannel = msg.channel
|
|
|
|
if ch.type == ChannelType.private:
|
|
|
|
m = f"""{fg(msg.author.name)}: {msg.content}
|
|
|
|
MSG_ID: {fg(msg.id, 0x03A678)}
|
|
|
|
CHA_ID: {fg(msg.channel.id, 0x03A678)}"""
|
|
|
|
print(m)
|
|
|
|
|
2020-04-28 10:41:26 +00:00
|
|
|
|
2020-05-01 15:08:05 +00:00
|
|
|
def setup(bot: CustomBot):
|
2020-04-28 10:41:26 +00:00
|
|
|
bot.add_cog(DevCog(bot))
|