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

918 lines
32 KiB
Python
Raw Normal View History

2020-06-03 12:58:28 +00:00
import ast
2020-05-16 10:47:15 +00:00
import asyncio
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-06-03 12:58:28 +00:00
import operator as op
2020-04-28 18:25:27 +00:00
import random
2020-05-17 13:06:37 +00:00
import re
2020-06-03 12:58:28 +00:00
import traceback
import urllib
2020-06-03 12:58:28 +00:00
from collections import Counter, defaultdict
2020-05-04 14:16:06 +00:00
from dataclasses import dataclass, field
from functools import partial
import math
2020-05-18 14:13:03 +00:00
from operator import attrgetter, itemgetter
2020-04-29 14:27:40 +00:00
from time import time
2020-05-12 09:27:40 +00:00
from typing import List, Set, Union
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-05-12 09:27:40 +00:00
from discord import Guild, Member
2020-04-29 16:43:07 +00:00
from discord.ext import commands
2020-06-03 12:58:28 +00:00
from discord.ext.commands import (BadArgument, Cog, command, Command, CommandError, Context, Group, group, is_owner,
MemberConverter, RoleConverter)
2020-05-11 01:37:11 +00:00
from discord.utils import get
2020-04-28 18:25:27 +00:00
from src.constants import *
2020-04-30 15:26:33 +00:00
from src.core import CustomBot
2020-05-11 01:37:11 +00:00
from src.errors import TfjmError
2020-06-03 12:58:28 +00:00
from src.utils import has_role, send_and_bin, start_time
# supported operators
OPS = {
ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul,
ast.FloorDiv: op.floordiv, ast.Mod: op.mod,
ast.Div: op.truediv, ast.Pow: op.pow, ast.BitXor: op.xor,
ast.USub: op.neg, "abs": abs, "π": math.pi, "τ": math.tau,
"i": 1j,
2020-06-03 12:58:28 +00:00
}
2020-05-04 14:16:06 +00:00
for name in dir(math):
if not name.startswith("_"):
OPS[name] = getattr(math, name)
2020-05-04 14:16:06 +00:00
@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
2020-05-17 13:06:37 +00:00
HUG_RE = re.compile(r"^(?P<hugger>\d+) -> (?P<hugged>\d+) \| (?P<text>.*)$")
class Hug:
def __init__(self, hugger, hugged, text):
self.hugger = hugger
self.hugged = hugged
self.text = text
@classmethod
def from_str(cls, line: str):
match = HUG_RE.match(line)
if not match:
raise ValueError(f"'{line}' is not a valid hug format.")
hugger = int(match.group("hugger"))
hugged = int(match.group("hugged"))
text = match.group("text")
return cls(hugger, hugged, text)
def __repr__(self):
return f"{self.hugger} -> {self.hugged} | {self.text}"
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-05-12 14:02:10 +00:00
self.computing = False
2020-05-17 13:06:37 +00:00
self.hugs = self.get_hugs()
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):
2020-05-12 14:02:10 +00:00
if self.computing:
return await ctx.send("Il y a déjà une fractale en cours de calcul...")
try:
self.computing = True
await ctx.message.add_reaction(Emoji.CHECK)
msg: discord.Message = ctx.message
seed = msg.content[len("!fractal ") :]
seed = seed or str(random.randint(0, 1_000_000_000))
async with aiohttp.ClientSession() as session:
async with session.get(
FRACTAL_URL.format(seed=urllib.parse.quote(seed)), timeout=120
2020-05-12 14:02:10 +00:00
) as resp:
if resp.status != 200:
return await ctx.send(
"Il y a un problème pour calculer/télécharger l'image..."
)
data = io.BytesIO(await resp.read())
await ctx.send(
f"Seed: {seed}", file=discord.File(data, f"{seed}.png")
)
2020-05-12 14:02:10 +00:00
finally:
self.computing = False
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:")
2020-05-17 13:06:37 +00:00
@command(aliases=["pong"])
async def ping(self, ctx):
"""Affiche la latence avec le bot."""
msg: discord.Message = ctx.message
ping = msg.created_at.timestamp()
msg: discord.Message = await ctx.send("Pong !")
pong = time()
# 7200 is because we are UTC+2
delta = pong - ping - 7200
await msg.edit(content=f"Pong ! Ça a pris {int(1000 * (delta))}ms")
@command(name="fan", aliases=["join", "adhere"], hidden=True)
async def fan_club_cmd(self, ctx: Context, who: Member):
2020-05-19 15:11:35 +00:00
"""Permet de rejoindre un fan club existant."""
2020-05-17 13:06:37 +00:00
role_id = FAN_CLUBS.get(who.id, None)
role = get(ctx.guild.roles, id=role_id)
if role is not None:
await ctx.author.add_roles(role)
await ctx.send(f"Bienvenue au {role.mention} !! :tada:")
else:
await ctx.send(
f"{who.mention} n'a pas encore de fan club. Peut-être qu'un jour "
f"iel sera un membre influent du CNO ?"
)
2020-06-03 12:58:28 +00:00
@command(name="calc", aliases=["="])
async def calc_cmd(self, ctx, *args):
"""Effectue un calcul simple"""
with_tb = has_role(ctx.author, Role.DEV)
embed = self._calc(ctx.message.content, with_tb)
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
embed = self._calc(after.content, with_tb)
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
def _calc(self, query: str, with_tb=False):
for prefix in ("! ", "!", "calc", "="):
if query.startswith(prefix):
query = query[len(prefix):]
query = re.sub(r"\b((\d)+(\.\d+)?)(?P<name>[a-zA-Z]+)\b", r"\1*\4", query)
2020-06-03 12:58:28 +00:00
query = query.strip().strip("`")
ex = None
result = 42
try:
result = self._eval(ast.parse(query, mode='eval').body)
except Exception as e:
ex = e
if isinstance(result, complex):
if abs(result.imag) < 1e-12:
result = result.real
else:
r, i = result.real, result.imag
r = r if abs(int(r) - r) > 1e-12 else int(r)
i = i if abs(int(i) - i) > 1e-12 else int(i)
if not r:
result = f"{i if i != 1 else ''}i"
else:
result = f"{r}{i if i != 1 else '':+}i"
if isinstance(result, float):
result = round(result, 12)
embed = discord.Embed(title=discord.utils.escape_markdown(query), color=EMBED_COLOR)
# embed.add_field(name="Entrée", value=f"`{query}`", inline=False)
embed.add_field(name="Valeur", value=f"`{result}`", inline=False)
if ex and with_tb:
embed.add_field(name="Erreur", value=f"{ex.__class__.__name__}: {ex}", inline=False)
trace = io.StringIO()
traceback.print_exception(type(ex), ex, ex.__traceback__, file=trace)
trace.seek(0)
embed.add_field(name="Traceback", value=f"```\n{trace.read()}```")
embed.set_footer(text="You may edit your message")
return embed
def _eval(self, node):
if isinstance(node, ast.Num): # <number>
return node.n
elif isinstance(node, ast.BinOp): # <left> <operator> <right>
return OPS[type(node.op)](self._eval(node.left), self._eval(node.right))
elif isinstance(node, ast.UnaryOp): # <operator> <operand> e.g., -1
return OPS[type(node.op)](self._eval(node.operand))
elif isinstance(node, ast.Call):
if isinstance(node.func, ast.Name):
return OPS[node.func.id](*(self._eval(n) for n in node.args), **{k.arg: self._eval(k.value) for k in node.keywords})
elif isinstance(node, ast.Name):
return OPS[node.id]
fields = ", ".join(
f"{k}={getattr(node, k).__class__.__name__}" for k in node._fields
)
raise TypeError(f"Type de noeud non supporté: {node.__class__.__name__}({fields})")
2020-05-17 13:06:37 +00:00
# ----------------- Hugs ---------------- #
@command(aliases=["<3", "❤️", ":heart:", Emoji.RAINBOW_HEART])
2020-05-17 13:06:37 +00:00
async def hug(self, ctx: Context, who="everyone"):
"""Fait un câlin à quelqu'un. :heart:"""
2020-05-10 09:57:50 +00:00
2020-05-17 13:06:37 +00:00
if who == "everyone":
who = ctx.guild.default_role
elif who == "back":
return await self.hug_back(ctx)
else:
try:
who = await RoleConverter().convert(ctx, who)
except BadArgument:
try:
who = await MemberConverter().convert(ctx, who)
except BadArgument:
2020-05-18 14:13:03 +00:00
return await ctx.send(
discord.utils.escape_mentions(
f'Il n\'y a pas de "{who}". :man_shrugging:'
)
)
2020-05-12 09:27:40 +00:00
who: Union[discord.Role, Member]
2020-05-18 14:13:03 +00:00
bot_hug = who == self.bot.user
2020-05-10 09:57:50 +00:00
bonuses = [
"C'est trop meuuuugnon !",
"Ça remonte le moral ! :D",
":hugging:",
":smiling_face_with_3_hearts:",
"Oh wiiii",
# f"{'Je me sens' if bot_hug else 'Iel se sent'} désormais prêt à travailler à fond sur les solutions de AQT",
2020-05-18 14:13:03 +00:00
f"{who.mention} en redemande un !"
if not bot_hug
else "J'en veux un autre ! :heart_eyes:",
"Le·a pauvre, iel est tout·e rouge !"
if not bot_hug
2020-05-18 16:27:23 +00:00
else "Un robot ne peut pas rougir, mais je crois que... :blush:",
2020-05-12 09:27:40 +00:00
"Hihi, il gratte ton pull en laine ! :sheep:",
2020-05-10 09:57:50 +00:00
]
2020-05-12 09:27:40 +00:00
if (
isinstance(who, discord.Member)
and has_role(who, Role.JURY)
and has_role(ctx.author, Role.PARTICIPANT)
):
bonuses += ["Il s'agit surement là d'une tentative de corruption !"]
2020-05-17 13:06:37 +00:00
if has_role(ctx.author, Role.PRETRESSE_CALINS):
bonuses += [
"C'est le plus beau calin du monde :smiling_face_with_3_hearts: :smiling_face_with_3_hearts:",
2020-05-18 16:27:23 +00:00
f"{who.mention} est subjugué·e ! :smiling_face_with_3_hearts:",
2020-05-17 13:06:37 +00:00
]
if who.id == DIEGO:
bonuses += [
"Tiens... Ça sent le mojito... :lemon:",
2020-05-18 14:13:03 +00:00
":green_heart: :lemon: :green_heart:",
2020-05-17 13:06:37 +00:00
]
2020-05-18 14:13:03 +00:00
if who.id in FAN_CLUBS and not get(ctx.author.roles, id=FAN_CLUBS[who.id]):
2020-05-17 13:06:37 +00:00
bonuses += ["Tu devrais rejoindre son fan club :wink:"]
2020-05-10 09:57:50 +00:00
if who == ctx.author:
msg = f"{who.mention} se fait un auto-calin !"
bonuses += [
"Mais c'est un peu ridicule...",
2020-05-12 09:27:40 +00:00
"Mais iel a les bras trop courts ! :cactus:",
2020-05-10 09:57:50 +00:00
"Il en faut peu pour être heureux :wink:",
]
2020-05-12 09:27:40 +00:00
elif who == ctx.guild.default_role:
msg = f"{ctx.author.mention} fait un câlin a touuuut le monde !"
bonuses += [
"Ça fait beaucoup de gens pour un câlin !",
"Plus on est, plus on est calins !",
"C'est pas très COVID-19 tout ça !",
"Tout le monde est heureux maintenant !",
]
2020-05-18 14:13:03 +00:00
elif bot_hug:
2020-05-17 13:06:37 +00:00
msg = f"{ctx.author.mention} me fait un gros câliiiiin !"
2020-05-16 16:09:42 +00:00
bonuses += ["Je trouve ça très bienveillant <3"]
2020-05-10 09:57:50 +00:00
else:
msg = f"{ctx.author.mention} fait un gros câlin à {who.mention} !"
bonuses += [
f"Mais {who.mention} n'apprécie pas...",
2020-05-10 09:57:50 +00:00
"Et ils s'en vont chasser des canards ensemble :wink:",
2020-05-12 09:27:40 +00:00
"Oh ! Iel sent bon...",
2020-05-17 13:06:37 +00:00
"Et moi quand est ce que j'ai le droit à un calin ?",
2020-05-12 09:27:40 +00:00
f"{who.mention} a serré tellment fort qu'iel vous a coupé en deux :scream:",
f"{who.mention} propose à {ctx.author.mention} de se revoir autour d'une :pizza: !",
"Les drones du commissaire Winston passent par là et vous ordonnent d'arrêter.",
"Après ce beau moment de tendresse, ils décident d'aller discuter en créant des puzzles.",
f"{who.mention} se réfugie dans l'entrepôt d'Animath et bloque l'entrée avec un meuble.",
2020-05-10 09:57:50 +00:00
]
bonus = random.choice(bonuses)
2020-05-17 13:06:37 +00:00
text = f"{msg} {bonus}"
self.add_hug(ctx.author.id, who.id, text)
2020-05-10 09:57:50 +00:00
2020-05-17 13:06:37 +00:00
await ctx.send(text)
2020-05-11 01:56:05 +00:00
2020-05-18 16:27:23 +00:00
if bot_hug and random.random() > 0.9:
await asyncio.sleep(3.14159265358979323)
ctx.author = get(ctx.guild.members, id=self.bot.user.id)
await ctx.invoke(self.hug, "back")
2020-05-17 13:06:37 +00:00
async def hug_back(self, ctx: Context):
hugger = ctx.author.id
2020-05-11 01:56:05 +00:00
2020-05-17 13:06:37 +00:00
last_hug: Hug = get(reversed(self.hugs), hugged=hugger)
if not last_hug:
return await ctx.send(
f"Personne n'a jamais fait de calin à {ctx.author.mention}, il faut y remédier !"
)
2020-05-16 10:47:15 +00:00
2020-05-17 13:06:37 +00:00
if "coupé en deux" in last_hug.text:
return await ctx.send(
"Tu ne vas quand même pas faire un câlin à quelqu'un "
"que tu viens de couper en deux !"
2020-05-16 10:47:15 +00:00
)
2020-05-17 13:06:37 +00:00
await ctx.invoke(self.hug, str(last_hug.hugger))
2020-05-18 17:20:21 +00:00
@command(name="hug-stats", aliases=["hs"])
2020-05-17 13:06:37 +00:00
@commands.has_role(Role.PRETRESSE_CALINS)
2020-05-18 17:20:21 +00:00
async def hugs_stats_cmd(self, ctx: Context, who: Member = None):
2020-05-18 14:13:03 +00:00
"""(prêtresse des calins) Affiche qui est le plus câliné """
2020-05-18 17:20:21 +00:00
if who is None:
await self.send_all_hug_stats(ctx)
else:
await self.send_hugs_stats_for(ctx, who)
async def send_all_hug_stats(self, ctx):
2020-05-17 13:06:37 +00:00
medals = [
":first_place:",
":second_place:",
":third_place:",
":medal:",
":military_medal:",
]
ranks = ["Gros Nounours", "Petit Panda", "Ours en peluche"]
embed = discord.Embed(
2020-05-18 17:20:21 +00:00
title="Prix du plus câliné",
color=discord.Colour.magenta(),
description=f"Nombre de total de câlins : {len(self.hugs)} {Emoji.HEART}",
2020-05-17 13:06:37 +00:00
)
everyone = ctx.guild.default_role.id
everyone_hugs = 0
everyone_diff = set()
2020-05-17 13:06:37 +00:00
stats = Counter()
diffs = defaultdict(set)
2020-05-17 13:06:37 +00:00
for h in self.hugs:
if h.hugged == everyone:
everyone_hugs += 1
everyone_diff.add(h.hugger)
else:
if h.hugged != h.hugger:
stats[h.hugged] += 1
diffs[h.hugged].add(h.hugger)
role: discord.Role = get(ctx.guild.roles, id=h.hugged)
if role is not None:
for m in role.members:
if m.id != h.hugger:
stats[m.id] += 1
diffs[m.id].add(h.hugger)
for m, d in diffs.items():
stats[m] += len(everyone_diff.union(d)) * 42 + everyone_hugs
2020-05-18 14:13:03 +00:00
top = sorted(list(stats.items()), key=itemgetter(1), reverse=True)
2020-05-17 13:06:37 +00:00
for i in range(3):
m = medals[i]
r = ranks[i]
id, qte = top[i]
who = self.name_for(ctx, id)
embed.add_field(name=f"{m} - {r}", value=f"{who} : {qte} :heart:")
top4to7 = "\n ".join(
f"{medals[3]} {self.name_for(ctx, id)} : {qte} :orange_heart:"
for id, qte in top[3:8]
)
2020-05-18 16:27:23 +00:00
embed.add_field(name="Apprenti peluche", value=top4to7)
2020-05-17 13:06:37 +00:00
top8to13 = "\n".join(
f"{medals[4]} {self.name_for(ctx, id)} : {qte} :yellow_heart:"
for id, qte in top[8:13]
)
2020-05-18 16:27:23 +00:00
embed.add_field(name="Pelote de laine de canard", value=top8to13)
2020-05-18 17:20:21 +00:00
await ctx.send(embed=embed)
2020-05-19 15:11:35 +00:00
async def send_hugs_stats_for(self, ctx: Context, who: discord.Member):
2020-05-18 17:20:21 +00:00
given = self.hugs_given(ctx, who.id)
received = self.hugs_received(ctx, who.id)
auto = self.auto_hugs(ctx, who.id)
cut = [h for h in given if "coupé en deux" in h.text]
2020-05-18 17:20:21 +00:00
infos = {
"Câlins donnés": (len(given), 1),
"Câlins reçus": (len(received), 1),
"Personnes câlinées": (len(set(h.hugged for h in given)), 20),
"Câliné par": (len(set(h.hugger for h in received)), 30),
"Auto-câlins": ((len(auto)), 3),
"Morceaux": (len(cut), 30),
2020-05-18 17:20:21 +00:00
}
2020-05-19 15:11:35 +00:00
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":
v = 2 ** v
embed.add_field(name=f, value=f"{v} {heart}")
2020-05-18 17:20:21 +00:00
2020-05-17 13:06:37 +00:00
await ctx.send(embed=embed)
def ris(self, ctx: Context, id, role_or_member_id):
"""Whether the id is the same member or a member that has the given role."""
if id == role_or_member_id:
return True
member: Member = get(ctx.guild.members, id=id)
if member is None:
return False
role = get(member.roles, id=role_or_member_id)
return role is not None
2020-05-18 17:20:21 +00:00
def heart_for_stat(self, v):
hearts = [
":broken_heart:",
":green_heart:",
":yellow_heart:",
":orange_heart:",
":heart:",
":sparkling_heart:",
Emoji.RAINBOW_HEART,
2020-05-18 17:20:21 +00:00
]
if v <= 0:
return hearts[0]
elif v >= 5000:
2020-05-18 17:20:21 +00:00
return hearts[-1]
elif v >= 2000:
return hearts[-2]
2020-05-18 17:20:21 +00:00
else:
return hearts[len(str(v))]
2020-05-18 17:20:21 +00:00
2020-05-17 13:06:37 +00:00
def name_for(self, ctx, member_or_role_id):
memb = ctx.guild.get_member(member_or_role_id)
if memb is not None:
name = memb.mention
else:
2020-05-19 15:11:35 +00:00
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
2020-05-17 13:06:37 +00:00
return name
def score_for(self, ctx, member_id):
received = self.hugs_received(ctx, member_id)
diffs = set(h.hugger for h in received)
return 42 * len(diffs) + len(received)
def hugs_given(self, ctx, who_id):
eq = partial(self.ris, ctx, who_id)
return [h for h in self.hugs if eq(h.hugger) and not eq(h.hugged)]
def hugs_received(self, ctx, who_id):
eq = partial(self.ris, ctx, who_id)
return [h for h in self.hugs if eq(h.hugged) and not eq(h.hugger)]
def auto_hugs(self, ctx, who_id):
eq = partial(self.ris, ctx, who_id)
return [h for h in self.hugs if eq(h.hugged) and eq(h.hugger)]
2020-05-17 13:06:37 +00:00
def get_hugs(self):
File.HUGS.touch()
lines = File.HUGS.read_text().strip().splitlines()
return [Hug.from_str(l) for l in lines]
def add_hug(self, hugger: int, hugged: int, text):
File.HUGS.touch()
with open(File.HUGS, "a") as f:
f.write(f"{hugger} -> {hugged} | {text}\n")
self.hugs.append(Hug(hugger, hugged, text))
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)
2020-05-18 14:51:23 +00:00
@group(name="joke", invoke_without_command=True, case_insensitive=True)
2020-06-03 12:58:28 +00:00
async def joke(self, ctx: Context, id=None):
2020-05-18 15:13:13 +00:00
"""Fait discretement une blague aléatoire."""
2020-05-10 10:43:21 +00:00
m: discord.Message = ctx.message
await m.delete()
2020-05-04 14:16:06 +00:00
jokes = self.load_jokes()
2020-06-03 12:58:28 +00:00
if id is not None:
id = int(id)
2020-05-11 01:37:11 +00:00
joke_id = id
jokes = sorted(
jokes, key=lambda j: len(j.likes) - len(j.dislikes), reverse=True
)
else:
joke_id = random.randrange(len(jokes))
try:
joke = jokes[joke_id]
except IndexError:
raise TfjmError("Il n'y a pas de blague avec cet ID.")
2020-05-04 14:16:06 +00:00
2020-05-10 10:43:21 +00:00
if joke.file:
file = discord.File(File.MEMES / joke.file)
2020-05-10 10:43:21 +00:00
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(f"{joke_id}-{file.filename}")
await file.save(File.MEMES / joke.file)
2020-05-17 13:06:37 +00:00
elif not msg.strip():
return "Tu ne peux pas ajouter une blague vide..."
2020-05-10 10:43:21 +00:00
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()
2020-05-17 13:06:37 +00:00
end = start + 24 * 60 * 60 * 5 # 5 days
2020-05-04 14:16:06 +00:00
while time() < end:
try:
reaction, user = await self.bot.wait_for(
"reaction_add", check=check, timeout=end - time()
)
2020-05-16 10:47:15 +00:00
except asyncio.TimeoutError:
return
2020-05-04 14:16:06 +00:00
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)
2020-06-01 11:47:57 +00:00
@joke.command(name="top", hidden=True)
2020-06-03 12:58:28 +00:00
@commands.has_any_role(*Role.ORGAS)
2020-05-11 01:37:11 +00:00
async def best_jokes(self, ctx: Context):
"""Affiche le palmares des blagues."""
jokes = self.load_jokes()
s = sorted(jokes, key=lambda j: len(j.likes) - len(j.dislikes), reverse=True)
embed = discord.Embed(title="Palmares des blagues.")
for i, joke in enumerate(s[:10]):
who = get(ctx.guild.members, id=joke.joker)
text = joke.joke
if joke.file:
text += " - image non inclue - "
2020-06-01 11:43:49 +00:00
name = who.display_name if who else "Inconnu"
2020-05-11 01:37:11 +00:00
embed.add_field(
2020-06-03 12:58:28 +00:00
name=f"{i} - {name} - {len(joke.likes)} :heart: {len(joke.dislikes)} :broken_heart:", value=text, inline=False
2020-05-11 01:37:11 +00:00
)
await ctx.send(embed=embed)
# ----------------- 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,
)
2020-09-11 20:24:53 +00:00
cog_getter = attrgetter("cog_name")
commands = itertools.groupby(sorted(self.bot.walk_commands(), key=cog_getter), cog_getter)
2020-04-28 23:36:42 +00:00
for cat_name, cat in commands:
2020-05-18 15:13:13 +00:00
cat = {c.qualified_name: c for c in cat}
2020-04-28 23:36:42 +00:00
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))