tfjm-discord-bot/src/cogs/misc.py

412 lines
13 KiB
Python
Raw Normal View History

2020-04-29 23:04:54 +00:00
import datetime
2020-05-01 15:08:05 +00:00
import io
2020-04-28 23:36:42 +00:00
import itertools
2020-04-28 18:25:27 +00:00
import random
2020-05-04 14:16:06 +00:00
from dataclasses import dataclass, field
2020-04-28 23:36:42 +00:00
from operator import attrgetter
2020-04-29 14:27:40 +00:00
from time import time
2020-05-04 14:16:06 +00:00
from typing import List, Set
2020-04-28 18:25:27 +00:00
2020-05-01 15:08:05 +00:00
import aiohttp
2020-04-28 19:03:35 +00:00
import discord
2020-05-04 14:16:06 +00:00
import yaml
2020-04-29 23:04:54 +00:00
from discord import Guild
2020-04-29 16:43:07 +00:00
from discord.ext import commands
2020-04-28 23:36:42 +00:00
from discord.ext.commands import (
Cog,
command,
Context,
Command,
CommandError,
Group,
2020-05-04 14:16:06 +00:00
group,
2020-04-28 23:36:42 +00:00
)
2020-04-28 18:25:27 +00:00
from src.constants import *
2020-04-29 23:04:54 +00:00
from src.constants import Emoji
2020-04-30 15:26:33 +00:00
from src.core import CustomBot
2020-05-04 14:16:06 +00:00
from src.utils import has_role, start_time, send_and_bin
@dataclass
class Joke(yaml.YAMLObject):
yaml_tag = "Joke"
yaml_dumper = yaml.SafeDumper
yaml_loader = yaml.SafeLoader
joke: str
joker: int
likes: Set[int] = field(default_factory=set)
dislikes: Set[int] = field(default_factory=set)
2020-05-10 10:43:21 +00:00
file: str = None
2020-04-28 18:25:27 +00:00
class MiscCog(Cog, name="Divers"):
2020-04-30 15:26:33 +00:00
def __init__(self, bot: CustomBot):
2020-04-28 19:03:35 +00:00
self.bot = bot
2020-04-28 23:36:42 +00:00
self.show_hidden = False
self.verify_checks = True
2020-04-28 19:03:35 +00:00
2020-04-28 18:25:27 +00:00
@command(
name="choose",
usage='choix1 choix2 "choix 3"...',
aliases=["choice", "choix", "ch"],
)
async def choose(self, ctx: Context, *args):
"""
Choisit une option parmi tous les arguments.
Pour les options qui contiennent une espace,
il suffit de mettre des guillemets (`"`) autour.
"""
choice = random.choice(args)
2020-04-30 18:11:07 +00:00
msg = await ctx.send(f"J'ai choisi... **{choice}**")
await self.bot.wait_for_bin(ctx.author, msg),
2020-04-28 18:25:27 +00:00
2020-04-29 14:27:40 +00:00
@command(name="status")
2020-04-29 16:43:07 +00:00
@commands.has_role(Role.CNO)
2020-04-29 14:27:40 +00:00
async def status_cmd(self, ctx: Context):
2020-04-29 16:43:07 +00:00
"""(cno) Affiche des informations à propos du serveur."""
2020-04-29 14:27:40 +00:00
guild: Guild = ctx.guild
embed = discord.Embed(title="État du serveur", color=EMBED_COLOR)
benevoles = [g for g in guild.members if has_role(g, Role.BENEVOLE)]
participants = [g for g in guild.members if has_role(g, Role.PARTICIPANT)]
no_role = [g for g in guild.members if g.top_role == guild.default_role]
2020-04-30 15:26:33 +00:00
uptime = datetime.timedelta(seconds=round(time() - start_time()))
2020-05-04 14:16:06 +00:00
text = len(guild.text_channels)
vocal = len(guild.voice_channels)
2020-04-29 14:27:40 +00:00
infos = {
"Bénévoles": len(benevoles),
"Participants": len(participants),
"Sans rôle": len(no_role),
"Total": len(guild.members),
2020-05-04 14:16:06 +00:00
"Salons texte": text,
"Salons vocaux": vocal,
2020-04-29 14:27:40 +00:00
"Bot uptime": uptime,
}
width = max(map(len, infos))
txt = "\n".join(
f"`{key.rjust(width)}`: {value}" for key, value in infos.items()
)
embed.add_field(name="Stats", value=txt)
await ctx.send(embed=embed)
2020-05-01 15:08:05 +00:00
@command(hidden=True)
async def fractal(self, ctx: Context):
await ctx.message.add_reaction(Emoji.CHECK)
seed = random.randint(0, 1_000_000_000)
async with aiohttp.ClientSession() as session:
async with session.get(FRACTAL_URL.format(seed=seed)) as resp:
if resp.status != 200:
return await ctx.send("Could not download file...")
data = io.BytesIO(await resp.read())
await ctx.send(file=discord.File(data, "cool_image.png"))
2020-05-10 09:57:50 +00:00
@command(hidden=True, aliases=["bang", "pan"])
async def pew(self, ctx):
await ctx.send("Tu t'es raté ! Kwaaack :duck:")
@command()
async def hug(self, ctx, who: discord.Member):
"""Fait un câlin à quelqu'un."""
bonuses = [
"C'est trop meuuuugnon !",
"Ça remonte le moral ! :D",
":hugging:",
":smiling_face_with_3_hearts:",
"Oh wiiii",
]
if who == ctx.author:
msg = f"{who.mention} se fait un auto-calin !"
bonuses += [
"Mais c'est un peu ridicule...",
"Mais il a les bras trop courts ! :cactus:",
"Il en faut peu pour être heureux :wink:",
]
else:
msg = f"{ctx.author.mention} fait un gros câlin à {who.mention} !"
bonuses += [
f"Mais {who.display_name} n'apprécie pas...",
"Et ils s'en vont chasser des canards ensemble :wink:",
]
bonus = random.choice(bonuses)
await ctx.send(f"{msg} {bonus}")
2020-05-04 14:16:06 +00:00
# ---------------- Jokes ---------------- #
def load_jokes(self) -> List[Joke]:
# Ensure it exists
File.JOKES_V2.touch()
with open(File.JOKES_V2) as f:
jokes = list(yaml.safe_load_all(f))
return jokes
def save_jokes(self, jokes):
File.JOKES_V2.touch()
with open(File.JOKES_V2, "w") as f:
yaml.safe_dump_all(jokes, f)
@group(name="joke", invoke_without_command=True)
2020-05-10 10:43:21 +00:00
async def joke(self, ctx: Context):
m: discord.Message = ctx.message
await m.delete()
2020-05-04 14:16:06 +00:00
jokes = self.load_jokes()
joke_id = random.randrange(len(jokes))
joke = jokes[joke_id]
2020-05-10 10:43:21 +00:00
if joke.file:
file = discord.File(joke.file)
else:
file = None
message: discord.Message = await ctx.send(joke.joke, file=file)
2020-05-04 14:16:06 +00:00
await message.add_reaction(Emoji.PLUS_1)
await message.add_reaction(Emoji.MINUS_1)
await self.wait_for_joke_reactions(joke_id, message)
@joke.command(name="new")
2020-05-04 14:16:06 +00:00
@send_and_bin
async def new_joke(self, ctx: Context):
"""Ajoute une blague pour le concours de blague."""
2020-05-10 10:43:21 +00:00
jokes = self.load_jokes()
joke_id = len(jokes)
2020-05-04 14:16:06 +00:00
author: discord.Member = ctx.author
message: discord.Message = ctx.message
2020-05-10 10:43:21 +00:00
msg = message.content[len("!joke new ") :]
2020-05-04 14:16:06 +00:00
joke = Joke(msg, ctx.author.id, set())
2020-05-10 10:43:21 +00:00
if message.attachments:
file: discord.Attachment = message.attachments[0]
joke.file = str(File.MEMES / f"{joke_id}-{file.filename}")
await file.save(joke.file)
2020-05-04 14:16:06 +00:00
jokes.append(joke)
self.save_jokes(jokes)
await message.add_reaction(Emoji.PLUS_1)
await message.add_reaction(Emoji.MINUS_1)
await self.wait_for_joke_reactions(joke_id, message)
async def wait_for_joke_reactions(self, joke_id, message):
def check(reaction: discord.Reaction, u):
return (message.id == reaction.message.id) and str(reaction.emoji) in (
Emoji.PLUS_1,
Emoji.MINUS_1,
)
start = time()
end = start + 24 * 60 * 60
while time() < end:
reaction, user = await self.bot.wait_for(
"reaction_add", check=check, timeout=end - time()
)
if user.id == BOT:
continue
jokes = self.load_jokes()
if str(reaction.emoji) == Emoji.PLUS_1:
jokes[joke_id].likes.add(user.id)
else:
jokes[joke_id].dislikes.add(user.id)
self.save_jokes(jokes)
# ----------------- Help ---------------- #
2020-04-29 11:42:49 +00:00
@command(name="help", aliases=["h"])
async def help_cmd(self, ctx: Context, *args):
2020-04-29 14:48:11 +00:00
"""Affiche des détails à propos d'une commande."""
2020-04-28 23:36:42 +00:00
2020-04-28 19:03:35 +00:00
if not args:
2020-04-30 18:11:07 +00:00
msg = await self.send_bot_help(ctx)
2020-04-28 19:03:35 +00:00
else:
2020-04-30 18:11:07 +00:00
msg = await self.send_command_help(ctx, args)
await self.bot.wait_for_bin(ctx.author, msg)
2020-04-28 19:03:35 +00:00
async def send_bot_help(self, ctx: Context):
2020-04-28 23:36:42 +00:00
embed = discord.Embed(
title="Aide pour le bot du TFJM²",
description="Ici est une liste des commandes utiles (ou pas) "
"durant le tournoi. Pour avoir plus de détails il "
"suffit d'écrire `!help COMMANDE` en remplacant `COMMANDE` "
"par le nom de la commande, par exemple `!help team channel`.",
color=0xFFA500,
)
commands = itertools.groupby(self.bot.walk_commands(), attrgetter("cog_name"))
for cat_name, cat in commands:
cat = {c.qualified_name: c for c in cat if not isinstance(c, Group)}
cat = await self.filter_commands(
ctx, list(cat.values()), sort=True, key=attrgetter("qualified_name")
)
if not cat:
continue
names = ["!" + c.qualified_name for c in cat]
width = max(map(len, names))
names = [name.rjust(width) for name in names]
short_help = [c.short_doc for c in cat]
lines = [f"`{n}` - {h}" for n, h in zip(names, short_help)]
if cat_name is None:
cat_name = "Autres"
c: Command
text = "\n".join(lines)
embed.add_field(name=cat_name, value=text, inline=False)
embed.set_footer(text="Suggestion ? Problème ? Envoie un message à @Diego")
2020-04-30 18:11:07 +00:00
return await ctx.send(embed=embed)
2020-04-28 23:36:42 +00:00
2020-04-28 23:57:14 +00:00
async def send_command_help(self, ctx, args):
name = " ".join(args).strip("!")
2020-04-28 23:57:14 +00:00
comm: Command = self.bot.get_command(name)
if comm is None:
2020-04-29 11:42:49 +00:00
return await ctx.send(
2020-04-28 23:57:14 +00:00
f"La commande `!{name}` n'existe pas. "
f"Utilise `!help` pour une liste des commandes."
)
2020-04-29 11:42:49 +00:00
elif isinstance(comm, Group):
return await self.send_group_help(ctx, comm)
2020-04-28 23:57:14 +00:00
embed = discord.Embed(
title=f"Aide pour la commande `!{comm.qualified_name}`",
description=comm.help,
color=0xFFA500,
)
if comm.aliases:
aliases = ", ".join(f"`{a}`" for a in comm.aliases)
embed.add_field(name="Alias", value=aliases, inline=True)
if comm.signature:
embed.add_field(
name="Usage", value=f"`!{comm.qualified_name} {comm.signature}`"
)
embed.set_footer(text="Suggestion ? Problème ? Envoie un message à @Diego")
2020-04-30 18:11:07 +00:00
return await ctx.send(embed=embed)
2020-04-28 23:57:14 +00:00
2020-04-29 11:42:49 +00:00
async def send_group_help(self, ctx, group: Group):
embed = discord.Embed(
title=f"Aide pour le groupe de commandes `!{group.qualified_name}`",
description=group.help,
color=0xFFA500,
)
comms = await self.filter_commands(ctx, group.commands, sort=True)
if not comms:
embed.add_field(
name="Désolé", value="Il n'y a aucune commande pour toi ici."
)
else:
names = ["!" + c.qualified_name for c in comms]
width = max(map(len, names))
just_names = [name.rjust(width) for name in names]
2020-04-29 11:42:49 +00:00
short_help = [c.short_doc for c in comms]
lines = [f"`{n}` - {h}" for n, h in zip(just_names, short_help)]
2020-04-29 11:42:49 +00:00
c: Command
text = "\n".join(lines)
embed.add_field(name="Sous-commandes", value=text, inline=False)
if group.aliases:
aliases = ", ".join(f"`{a}`" for a in group.aliases)
embed.add_field(name="Alias", value=aliases, inline=True)
if group.signature:
embed.add_field(
name="Usage", value=f"`!{group.qualified_name} {group.signature}`"
)
embed.add_field(
name="Plus d'aide",
value=f"Pour plus de détails sur une commande, "
f"il faut écrire `!help COMMANDE` en remplaçant "
f"COMMANDE par le nom de la commande qui t'intéresse.\n"
f"Exemple: `!help {random.choice(names)[1:]}`",
)
2020-04-29 11:42:49 +00:00
embed.set_footer(text="Suggestion ? Problème ? Envoie un message à @Diego")
2020-04-30 18:11:07 +00:00
return await ctx.send(embed=embed)
2020-04-29 11:42:49 +00:00
2020-04-28 23:36:42 +00:00
def _name(self, command: Command):
return f"`!{command.qualified_name}`"
async def filter_commands(self, ctx, commands, *, sort=False, key=None):
"""|coro|
Returns a filtered list of commands and optionally sorts them.
This takes into account the :attr:`verify_checks` and :attr:`show_hidden`
attributes.
Parameters
------------
commands: Iterable[:class:`Command`]
An iterable of commands that are getting filtered.
sort: :class:`bool`
Whether to sort the result.
key: Optional[Callable[:class:`Command`, Any]]
An optional key function to pass to :func:`py:sorted` that
takes a :class:`Command` as its sole parameter. If ``sort`` is
passed as ``True`` then this will default as the command name.
Returns
---------
List[:class:`Command`]
A list of commands that passed the filter.
"""
if sort and key is None:
2020-04-29 11:42:49 +00:00
key = lambda c: c.qualified_name
2020-04-28 23:36:42 +00:00
iterator = (
commands if self.show_hidden else filter(lambda c: not c.hidden, commands)
)
if not self.verify_checks:
# if we do not need to verify the checks then we can just
# run it straight through normally without using await.
return sorted(iterator, key=key) if sort else list(iterator)
# if we're here then we need to check every command if it can run
async def predicate(cmd):
try:
return await cmd.can_run(ctx)
except CommandError:
return False
ret = []
for cmd in iterator:
valid = await predicate(cmd)
if valid:
ret.append(cmd)
if sort:
ret.sort(key=key)
return ret
2020-04-28 19:03:35 +00:00
2020-04-28 18:25:27 +00:00
2020-04-30 18:11:07 +00:00
def setup(bot: CustomBot):
2020-04-28 19:03:35 +00:00
bot.add_cog(MiscCog(bot))