mirror of
https://gitlab.com/ddorn/tfjm-discord-bot.git
synced 2025-07-07 22:40:13 +02:00
Compare commits
26 Commits
924abea6c8
...
better-tir
Author | SHA1 | Date | |
---|---|---|---|
07d7d9be7a | |||
0fb3a3ca4a | |||
adcd719357 | |||
697f1caf91 | |||
53370fbcb6 | |||
ef8144a115 | |||
fc08c60230 | |||
56cf3f2f55 | |||
3551068a3f | |||
f7db834f3c | |||
860cbb563e | |||
5a402eaf09 | |||
8cba9db097 | |||
a632bdf088 | |||
d792a87be3 | |||
5c522f07e3 | |||
6815c33ea1 | |||
0f1bc903bf | |||
af1f2d1ae2 | |||
553d124b1a | |||
3dd50a75c3 | |||
a520abb90a | |||
a95c20c5b5 | |||
dfc8176084 | |||
cfdd55daa2 | |||
62256a98e8 |
5
bot.py
5
bot.py
@ -1,5 +1,4 @@
|
||||
from src import bot
|
||||
from src.constants import TOKEN
|
||||
from src import start
|
||||
|
||||
if __name__ == "__main__":
|
||||
bot.run(TOKEN)
|
||||
start()
|
||||
|
@ -1,12 +1,3 @@
|
||||
```
|
||||
- Quelle est votre principale qualité ?
|
||||
- Je suis très rapide en calcul mental.
|
||||
- 23 x 547 ?
|
||||
- 56
|
||||
- Mais c'est faux !
|
||||
- Oui mais c'est rapide !
|
||||
```
|
||||
---
|
||||
Why did the chicken cross the mobius strip?
|
||||
|| To get to the same side. ||
|
||||
---
|
||||
|
@ -1 +1 @@
|
||||
from src.tfjm_discord_bot import bot
|
||||
from src.tfjm_discord_bot import start
|
||||
|
334
src/base_tirage.py
Normal file
334
src/base_tirage.py
Normal file
@ -0,0 +1,334 @@
|
||||
import asyncio
|
||||
import random
|
||||
import sys
|
||||
import traceback
|
||||
from functools import wraps
|
||||
from pathlib import Path
|
||||
from pprint import pprint
|
||||
|
||||
from io import StringIO
|
||||
from typing import Type, Union, Dict, List
|
||||
|
||||
import discord
|
||||
import yaml
|
||||
|
||||
from src.constants import *
|
||||
|
||||
|
||||
class Event(asyncio.Event):
|
||||
def __init__(self, team: str, value: Union[bool, int, str]):
|
||||
super(Event, self).__init__()
|
||||
self.value = value
|
||||
self.team = team
|
||||
self.response = None
|
||||
|
||||
|
||||
class Team:
|
||||
yaml_tag = "Team"
|
||||
|
||||
def __init__(self, team_role):
|
||||
self.name = team_role.name
|
||||
self.mention = team_role.mention
|
||||
|
||||
self.accepted_problems = [None, None]
|
||||
self.rejected = [set(), set()]
|
||||
|
||||
def __str__(self):
|
||||
s = StringIO()
|
||||
pprint(self.__dict__, stream=s)
|
||||
s.seek(0)
|
||||
return s.read()
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
def coeff(self, round):
|
||||
if len(self.rejected[round]) <= MAX_REFUSE:
|
||||
return 2
|
||||
else:
|
||||
return 2 - 0.5 * (len(self.rejected[round]) - MAX_REFUSE)
|
||||
|
||||
def details(self, round):
|
||||
|
||||
info = {
|
||||
# "Accepté": self.accepted_problems[round],
|
||||
"Refusés": ", ".join(p[0] for p in self.rejected[round])
|
||||
if self.rejected[round]
|
||||
else "aucun",
|
||||
"Coefficient": self.coeff(round),
|
||||
# "Ordre passage": self.passage_order[round],
|
||||
}
|
||||
|
||||
width = max(map(len, info))
|
||||
|
||||
return "\n".join(f"`{n.rjust(width)}`: {v}" for n, v in info.items())
|
||||
|
||||
|
||||
#
|
||||
# return f""" - Accepté: {self.accepted_problems[round]}
|
||||
# - Refusés: {", ".join(p[0] for p in self.rejected[round]) if self.rejected[round] else "aucun"}
|
||||
# - Coefficient: {self.coeff(round)}
|
||||
# - Ordre au tirage: {self.tirage_order[round]}
|
||||
# - Ordre de passage: {self.passage_order[round]}
|
||||
# """
|
||||
|
||||
|
||||
class Poule(yaml.YAMLObject):
|
||||
yaml_tag = "Poule"
|
||||
|
||||
def __init__(self, poule, rnd):
|
||||
self.poule = poule
|
||||
self.rnd = rnd
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.poule}{self.rnd + 1}"
|
||||
|
||||
|
||||
class BaseTirage(yaml.YAMLObject):
|
||||
yaml_tag = "Tirage"
|
||||
|
||||
def __init__(self, *teams: discord.Role, fmt=(3, 3)):
|
||||
assert sum(fmt) == len(teams), "Different number of teams and format"
|
||||
|
||||
self.teams: Dict[str, Team] = {t.name: Team(t) for t in teams}
|
||||
self.format = fmt
|
||||
self.queue = asyncio.Queue()
|
||||
self.poules: Dict[Poule, List[str]] = {}
|
||||
"""A mapping between the poule and the list of teams in this poule."""
|
||||
|
||||
def availaible(self, pb, poule):
|
||||
pbs = [
|
||||
self.teams[team].accepted_problems[poule.rnd] for team in self.poules[poule]
|
||||
]
|
||||
|
||||
if len(self.poules[poule]) < 5:
|
||||
return pb not in pbs
|
||||
else:
|
||||
return pbs.count(pb) < 2
|
||||
|
||||
async def event(self, event: Event):
|
||||
event.set()
|
||||
await self.queue.put(event)
|
||||
await event.wait()
|
||||
return event.response
|
||||
|
||||
async def dice(self, trigram):
|
||||
return await self.event(Event(trigram, random.randint(1, 100)))
|
||||
|
||||
async def rproblem(self, trigram):
|
||||
team = self.teams[trigram]
|
||||
rnd = 0 if team.accepted_problems[0] is None else 1
|
||||
for poule, teams in self.poules.items():
|
||||
if trigram in teams and poule.rnd == rnd:
|
||||
break
|
||||
else:
|
||||
return await self.warn_wrong_team(None, trigram)
|
||||
|
||||
available = [
|
||||
pb
|
||||
for pb in PROBLEMS
|
||||
if pb not in team.accepted_problems and self.availaible(pb, poule)
|
||||
]
|
||||
return await self.event(Event(trigram, random.choice(available)))
|
||||
|
||||
async def accept(self, trigram, yes: bool):
|
||||
return await self.event(Event(trigram, yes))
|
||||
|
||||
async def next(self, typ, team=None):
|
||||
while True:
|
||||
event = await self.queue.get()
|
||||
if team is not None and event.team != team:
|
||||
await self.warn_wrong_team(team, event.team)
|
||||
elif not isinstance(event.value, typ):
|
||||
await self.warn_unwanted(typ, event.value)
|
||||
else:
|
||||
event.clear()
|
||||
return event
|
||||
event.clear()
|
||||
|
||||
async def run(self):
|
||||
|
||||
await self.info_start()
|
||||
|
||||
self.poules = await self.make_poules()
|
||||
|
||||
for poule in self.poules:
|
||||
await self.draw_poule(poule)
|
||||
|
||||
await self.info_finish()
|
||||
|
||||
async def get_dices(self, teams):
|
||||
dices = {t: None for t in teams}
|
||||
collisions = list(teams)
|
||||
while collisions:
|
||||
|
||||
for t in collisions:
|
||||
dices[t] = None
|
||||
|
||||
while None in dices.values():
|
||||
event = await self.next(int)
|
||||
|
||||
if event.team not in dices:
|
||||
await self.warn_wrong_team(None, event.team)
|
||||
elif dices[event.team] is None:
|
||||
dices[event.team] = event.value
|
||||
await self.info_dice(event.team, event.value)
|
||||
else:
|
||||
await self.warn_twice(int)
|
||||
|
||||
collisions = [t for t in teams if list(dices.values()).count(dices[t]) > 1]
|
||||
if collisions:
|
||||
await self.warn_colisions(collisions)
|
||||
|
||||
return dices
|
||||
|
||||
async def make_poules(self):
|
||||
poules = {}
|
||||
for rnd in (0, 1):
|
||||
await self.start_make_poule(rnd)
|
||||
|
||||
dices = await self.get_dices(self.teams)
|
||||
sorted_teams = sorted(self.teams, key=lambda t: dices[t])
|
||||
|
||||
idx = 0
|
||||
for i, qte in enumerate(self.format):
|
||||
letter = chr(ord("A") + i)
|
||||
poules[Poule(letter, rnd)] = sorted_teams[idx : idx + qte]
|
||||
idx += qte
|
||||
|
||||
await self.annonce_poules(poules)
|
||||
return poules
|
||||
|
||||
async def draw_poule(self, poule):
|
||||
|
||||
await self.start_draw_poule(poule)
|
||||
|
||||
# Trigrams in draw order
|
||||
trigrams = await self.draw_order(poule)
|
||||
|
||||
# Teams in draw order
|
||||
teams = [self.teams[tri] for tri in trigrams]
|
||||
current = 0
|
||||
while not all(team.accepted_problems[poule.rnd] for team in teams):
|
||||
team = teams[current]
|
||||
if team.accepted_problems[poule.rnd] is not None:
|
||||
# The team already accepted a problem
|
||||
current += 1
|
||||
current %= len(teams)
|
||||
continue
|
||||
|
||||
# Choose problem
|
||||
await self.start_select_pb(team)
|
||||
pevent = await self.next(str, team.name)
|
||||
# TODO: Add check for already selected / taken by someone else
|
||||
# This is not a bug for now, since it cannot happen yet
|
||||
await self.info_draw_pb(team, pevent.value, poule.rnd)
|
||||
|
||||
# Accept it
|
||||
accept = await self.next(bool, team.name)
|
||||
if accept.value:
|
||||
team.accepted_problems[poule.rnd] = pevent.value
|
||||
await self.info_accepted(
|
||||
team, pevent.value, self.availaible(pevent.value, poule)
|
||||
)
|
||||
else:
|
||||
await self.info_rejected(team, pevent.value, rnd=poule.rnd)
|
||||
team.rejected[poule.rnd].add(pevent.value)
|
||||
|
||||
current += 1
|
||||
current %= len(teams)
|
||||
|
||||
if len(teams) == 5:
|
||||
# We can determine the passage order only once problems are drawn.
|
||||
order = [self.teams[tri] for tri in self.poules[poule]]
|
||||
pbs = [team.accepted_problems[poule.rnd] for team in order]
|
||||
|
||||
doubles = []
|
||||
i = 0
|
||||
while i < len(order):
|
||||
team = order[i]
|
||||
if pbs.count(team.accepted_problems[poule.rnd]) == 2:
|
||||
# We pop the two with the same pb and add them to the doubles
|
||||
doubles.append(order.pop(i))
|
||||
other = next(
|
||||
filter(
|
||||
lambda t: team.accepted_problems[poule.rnd]
|
||||
== t.accepted_problems[poule.rnd],
|
||||
order,
|
||||
)
|
||||
)
|
||||
doubles.append(other)
|
||||
order.remove(other)
|
||||
else:
|
||||
i += 1
|
||||
# The conflicts
|
||||
order = doubles + order
|
||||
self.poules[poule] = order
|
||||
|
||||
await self.annonce_poule(poule)
|
||||
|
||||
async def draw_order(self, poule):
|
||||
await self.start_draw_order(poule)
|
||||
|
||||
teams = self.poules[poule]
|
||||
dices = await self.get_dices(teams)
|
||||
|
||||
order = sorted(teams, key=lambda t: dices[t], reverse=True)
|
||||
|
||||
await self.annonce_draw_order(order)
|
||||
return order
|
||||
|
||||
async def warn_unwanted(self, wanted: Type, got: Type):
|
||||
"""Called when a event of an unwanted type occurs."""
|
||||
|
||||
async def warn_wrong_team(self, expected, got):
|
||||
"""Called when a team that should not play now put an event"""
|
||||
|
||||
async def warn_colisions(self, collisions: List[str]):
|
||||
"""Called when there are collisions in a dice tirage."""
|
||||
|
||||
async def warn_twice(self, typ: Type):
|
||||
"""Called when an event appears once again and not wanted."""
|
||||
|
||||
async def start_make_poule(self, rnd):
|
||||
"""Called when it starts drawing the poules for round `rnd`"""
|
||||
|
||||
async def start_draw_poule(self, poule):
|
||||
"""Called when we start a poule."""
|
||||
|
||||
async def start_draw_order(self, poule):
|
||||
"""Called when we start to draw the order."""
|
||||
|
||||
async def start_select_pb(self, team):
|
||||
"""Called when a team needs to select a problem."""
|
||||
|
||||
async def annonce_poules(self, poules):
|
||||
"""Called when all poules are defined."""
|
||||
|
||||
async def annonce_draw_order(self, order):
|
||||
"""Called when the drawing order is defined."""
|
||||
|
||||
async def annonce_poule(self, poule):
|
||||
"""Called when the problems and order for a poule is known."""
|
||||
|
||||
async def info_start(self):
|
||||
"""Called at the start of the tirage."""
|
||||
|
||||
async def info_finish(self):
|
||||
"""Called when the tirage has ended."""
|
||||
|
||||
async def info_dice(self, team, dice):
|
||||
"""Called on a dice roll."""
|
||||
|
||||
async def info_draw_pb(self, team, pb, rnd):
|
||||
"""Called when a team draws a problem."""
|
||||
|
||||
async def info_accepted(self, team, pb, still_available):
|
||||
"""Called when a team accepts a problem."""
|
||||
|
||||
async def info_rejected(self, team, pb, rnd):
|
||||
"""Called when a team rejects a problem,
|
||||
before it is added to the rejected set."""
|
||||
|
||||
|
||||
def setup(_):
|
||||
pass
|
@ -2,7 +2,7 @@ import asyncio
|
||||
from pprint import pprint
|
||||
|
||||
import discord
|
||||
from discord import TextChannel, PermissionOverwrite, Message
|
||||
from discord import TextChannel, PermissionOverwrite, Message, ChannelType
|
||||
from discord.ext.commands import (
|
||||
command,
|
||||
has_role,
|
||||
@ -16,8 +16,10 @@ from ptpython.repl import embed
|
||||
|
||||
from src.constants import *
|
||||
from src.core import CustomBot
|
||||
from src.utils import fg
|
||||
|
||||
COGS_SHORTCUTS = {
|
||||
"bt": "src.base_tirage",
|
||||
"c": "src.constants",
|
||||
"d": "tirages",
|
||||
"e": "errors",
|
||||
@ -53,7 +55,7 @@ class DevCog(Cog, name="Dev tools"):
|
||||
|
||||
def send(msg, channel=None):
|
||||
if isinstance(channel, int):
|
||||
channel = get(ctx.guild.channels, id=channel)
|
||||
channel = self.bot.get_channel(channel)
|
||||
|
||||
channel = channel or ctx.channel
|
||||
asyncio.create_task(channel.send(msg))
|
||||
@ -202,6 +204,15 @@ class DevCog(Cog, name="Dev tools"):
|
||||
await channel.delete_messages(to_delete)
|
||||
await ctx.message.delete()
|
||||
|
||||
@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)
|
||||
|
||||
|
||||
def setup(bot: CustomBot):
|
||||
bot.add_cog(DevCog(bot))
|
||||
|
221
src/cogs/misc.py
221
src/cogs/misc.py
@ -1,16 +1,18 @@
|
||||
import asyncio
|
||||
import datetime
|
||||
import io
|
||||
import itertools
|
||||
import random
|
||||
import urllib
|
||||
from dataclasses import dataclass, field
|
||||
from operator import attrgetter
|
||||
from time import time
|
||||
from typing import List, Set
|
||||
from typing import List, Set, Union
|
||||
|
||||
import aiohttp
|
||||
import discord
|
||||
import yaml
|
||||
from discord import Guild
|
||||
from discord import Guild, Member
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import (
|
||||
Cog,
|
||||
@ -20,11 +22,16 @@ from discord.ext.commands import (
|
||||
CommandError,
|
||||
Group,
|
||||
group,
|
||||
MemberConverter,
|
||||
BadArgument,
|
||||
RoleConverter,
|
||||
)
|
||||
from discord.utils import get
|
||||
|
||||
from src.constants import *
|
||||
from src.constants import Emoji
|
||||
from src.core import CustomBot
|
||||
from src.errors import TfjmError
|
||||
from src.utils import has_role, start_time, send_and_bin
|
||||
|
||||
|
||||
@ -37,6 +44,7 @@ class Joke(yaml.YAMLObject):
|
||||
joker: int
|
||||
likes: Set[int] = field(default_factory=set)
|
||||
dislikes: Set[int] = field(default_factory=set)
|
||||
file: str = None
|
||||
|
||||
|
||||
class MiscCog(Cog, name="Divers"):
|
||||
@ -44,6 +52,7 @@ class MiscCog(Cog, name="Divers"):
|
||||
self.bot = bot
|
||||
self.show_hidden = False
|
||||
self.verify_checks = True
|
||||
self.computing = False
|
||||
|
||||
@command(
|
||||
name="choose",
|
||||
@ -94,15 +103,128 @@ class MiscCog(Cog, name="Divers"):
|
||||
|
||||
@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"))
|
||||
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
|
||||
) 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")
|
||||
)
|
||||
finally:
|
||||
self.computing = False
|
||||
|
||||
@command(hidden=True, aliases=["bang", "pan"])
|
||||
async def pew(self, ctx):
|
||||
await ctx.send("Tu t'es raté ! Kwaaack :duck:")
|
||||
|
||||
@command(aliases=["<3"])
|
||||
async def hug(self, ctx, who="everyone"):
|
||||
"""Fait un câlin à quelqu'un."""
|
||||
|
||||
if who != "everyone":
|
||||
try:
|
||||
who = await RoleConverter().convert(ctx, who)
|
||||
except BadArgument:
|
||||
try:
|
||||
who = await MemberConverter().convert(ctx, who)
|
||||
except BadArgument:
|
||||
return await ctx.send(f'Il n\'y a pas de "{who}". :man_shrugging:')
|
||||
else:
|
||||
who = ctx.guild.default_role
|
||||
who: Union[discord.Role, Member]
|
||||
|
||||
bonuses = [
|
||||
"C'est trop meuuuugnon !",
|
||||
"Ça remonte le moral ! :D",
|
||||
":hugging:",
|
||||
":smiling_face_with_3_hearts:",
|
||||
"Oh wiiii",
|
||||
"Iel se sent désormais prêt à travailler à fond sur les solutions de AQT",
|
||||
f"{who.mention} en redemande un !",
|
||||
"Le·a pauvre, iel est tout·e rouge !",
|
||||
"Hihi, il gratte ton pull en laine ! :sheep:",
|
||||
]
|
||||
|
||||
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 !"]
|
||||
|
||||
if who == ctx.author:
|
||||
msg = f"{who.mention} se fait un auto-calin !"
|
||||
bonuses += [
|
||||
"Mais c'est un peu ridicule...",
|
||||
"Mais iel a les bras trop courts ! :cactus:",
|
||||
"Il en faut peu pour être heureux :wink:",
|
||||
]
|
||||
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 !",
|
||||
]
|
||||
elif who == self.bot.user:
|
||||
bonuses += ["Je trouve ça très bienveillant <3"]
|
||||
else:
|
||||
msg = f"{ctx.author.mention} fait un gros câlin à {who.mention} !"
|
||||
bonuses += [
|
||||
f"Mais {who.mention} n'apprécie pas...",
|
||||
"Et ils s'en vont chasser des canards ensemble :wink:",
|
||||
"Oh ! Iel sent bon...",
|
||||
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.",
|
||||
]
|
||||
|
||||
bonus = random.choice(bonuses)
|
||||
|
||||
await ctx.send(f"{msg} {bonus}")
|
||||
|
||||
@command(aliases=["pong"])
|
||||
async def ping(self, ctx):
|
||||
"""Affiche la latence avec le bot."""
|
||||
ping = time()
|
||||
msg: discord.Message = await ctx.send("Pong !")
|
||||
pong = time()
|
||||
|
||||
await msg.edit(content=f"Pong ! Ça a pris {int(1000 * (pong - ping))}ms")
|
||||
|
||||
@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."""
|
||||
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 ?"
|
||||
)
|
||||
|
||||
# ---------------- Jokes ---------------- #
|
||||
|
||||
@ -119,35 +241,58 @@ class MiscCog(Cog, name="Divers"):
|
||||
with open(File.JOKES_V2, "w") as f:
|
||||
yaml.safe_dump_all(jokes, f)
|
||||
|
||||
@group(name="joke", hidden=True, invoke_without_command=True)
|
||||
async def joke(self, ctx):
|
||||
await ctx.message.delete()
|
||||
@group(name="joke", invoke_without_command=True)
|
||||
async def joke(self, ctx: Context, id: int = None):
|
||||
|
||||
m: discord.Message = ctx.message
|
||||
await m.delete()
|
||||
|
||||
jokes = self.load_jokes()
|
||||
joke_id = random.randrange(len(jokes))
|
||||
joke = jokes[joke_id]
|
||||
if id is not None:
|
||||
joke_id = id
|
||||
jokes = sorted(
|
||||
jokes, key=lambda j: len(j.likes) - len(j.dislikes), reverse=True
|
||||
)
|
||||
else:
|
||||
joke_id = random.randrange(len(jokes))
|
||||
|
||||
message: discord.Message = await ctx.send(joke.joke)
|
||||
try:
|
||||
joke = jokes[joke_id]
|
||||
except IndexError:
|
||||
raise TfjmError("Il n'y a pas de blague avec cet ID.")
|
||||
|
||||
if joke.file:
|
||||
file = discord.File(File.MEMES / joke.file)
|
||||
else:
|
||||
file = None
|
||||
|
||||
message: discord.Message = await ctx.send(joke.joke, file=file)
|
||||
|
||||
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", hidden=True)
|
||||
@joke.command(name="new")
|
||||
@send_and_bin
|
||||
async def new_joke(self, ctx: Context):
|
||||
"""Ajoute une blague pour le concours de blague."""
|
||||
jokes = self.load_jokes()
|
||||
joke_id = len(jokes)
|
||||
|
||||
author: discord.Member = ctx.author
|
||||
message: discord.Message = ctx.message
|
||||
|
||||
start = "!joke new "
|
||||
msg = message.content[len(start) :]
|
||||
msg = message.content[len("!joke new ") :]
|
||||
|
||||
joke = Joke(msg, ctx.author.id, set())
|
||||
|
||||
jokes = self.load_jokes()
|
||||
if message.attachments:
|
||||
file: discord.Attachment = message.attachments[0]
|
||||
joke.file = str(f"{joke_id}-{file.filename}")
|
||||
await file.save(File.MEMES / joke.file)
|
||||
|
||||
jokes.append(joke)
|
||||
self.save_jokes(jokes)
|
||||
joke_id = len(jokes) - 1
|
||||
await message.add_reaction(Emoji.PLUS_1)
|
||||
await message.add_reaction(Emoji.MINUS_1)
|
||||
|
||||
@ -163,9 +308,13 @@ class MiscCog(Cog, name="Divers"):
|
||||
start = time()
|
||||
end = start + 24 * 60 * 60
|
||||
while time() < end:
|
||||
reaction, user = await self.bot.wait_for(
|
||||
"reaction_add", check=check, timeout=end - time()
|
||||
)
|
||||
|
||||
try:
|
||||
reaction, user = await self.bot.wait_for(
|
||||
"reaction_add", check=check, timeout=end - time()
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
return
|
||||
|
||||
if user.id == BOT:
|
||||
continue
|
||||
@ -178,6 +327,28 @@ class MiscCog(Cog, name="Divers"):
|
||||
|
||||
self.save_jokes(jokes)
|
||||
|
||||
@joke.command(name="top", hidden=True)
|
||||
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 - "
|
||||
|
||||
embed.add_field(
|
||||
name=f"{i} - {who.display_name} - {len(joke.likes)}", value=text
|
||||
)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
# ----------------- Help ---------------- #
|
||||
|
||||
@command(name="help", aliases=["h"])
|
||||
@ -231,7 +402,7 @@ class MiscCog(Cog, name="Divers"):
|
||||
return await ctx.send(embed=embed)
|
||||
|
||||
async def send_command_help(self, ctx, args):
|
||||
name = " ".join(args)
|
||||
name = " ".join(args).strip("!")
|
||||
comm: Command = self.bot.get_command(name)
|
||||
if comm is None:
|
||||
return await ctx.send(
|
||||
|
@ -9,7 +9,7 @@ from discord.utils import get, find
|
||||
|
||||
from src.constants import *
|
||||
from src.core import CustomBot
|
||||
from src.utils import has_role, send_and_bin
|
||||
from src.utils import has_role, send_and_bin, french_join
|
||||
|
||||
Team = namedtuple("Team", ["name", "trigram", "tournoi", "secret", "status"])
|
||||
|
||||
@ -90,33 +90,30 @@ class TeamsCog(Cog, name="Teams"):
|
||||
return "C'est fait !"
|
||||
|
||||
@commands.command(name="tourist")
|
||||
@commands.has_any_role(*Role.ORGAS)
|
||||
@send_and_bin
|
||||
async def touriste_cmd(self, ctx: Context, poule, member: Member):
|
||||
async def touriste_cmd(self, ctx: Context, tournoi):
|
||||
"""
|
||||
(orga) Accepte quelqu'un comme touriste pour une certaine poule.
|
||||
Permet de voir et ecouter dans les salons d'un certain tournoi.
|
||||
|
||||
Exemple:
|
||||
`!tourist A Diego` - Ajoute Diego comme touriste dans la Poule A
|
||||
`!tourist Bordeaux-Nancy` - Donne les droits de spectateurs pour Bordeaux-Nancy
|
||||
"""
|
||||
|
||||
poule = f"Poule {poule}"
|
||||
tournoi = find(lambda r: r.name.startswith("Orga"), ctx.author.roles)
|
||||
tournoi_name = tournoi.name.partition(" ")[2]
|
||||
if ctx.author.top_role != ctx.guild.default_role:
|
||||
return f"{ctx.author.mention} tu as déjà un role, devenir spéctateur t'enlèverait des droits."
|
||||
|
||||
if tournoi not in TOURNOIS:
|
||||
return f"{tournoi} n'est pas un nom de tournoi. Possibilités: {french_join(TOURNOIS)}"
|
||||
|
||||
guild: discord.Guild = ctx.guild
|
||||
|
||||
poule_channel: VoiceChannel = get(
|
||||
guild.voice_channels, name=poule, category__name=tournoi_name
|
||||
)
|
||||
if poule_channel is None:
|
||||
return f"La poule '{poule}' n'existe pas à {tournoi_name}"
|
||||
|
||||
tournoi_role = get(guild.roles, name=tournoi)
|
||||
touriste_role = get(guild.roles, name=Role.TOURIST)
|
||||
region = get(guild.roles, name=tournoi_name)
|
||||
await member.add_roles(touriste_role, region)
|
||||
|
||||
await poule_channel.set_permissions(member, view_channel=True, connect=True)
|
||||
return f"{member.mention} à été ajouté comme spectateur dans la {poule} de {tournoi_name}"
|
||||
await ctx.author.add_roles(
|
||||
touriste_role, tournoi_role, reason="Demande via le bot."
|
||||
)
|
||||
|
||||
return f"{ctx.author.mention} à été ajouté comme spectateur dans à {tournoi}."
|
||||
|
||||
@group(name="team", invoke_without_command=True)
|
||||
async def team(self, ctx):
|
||||
@ -279,7 +276,9 @@ class TeamsCog(Cog, name="Teams"):
|
||||
channel_name,
|
||||
overwrites={
|
||||
guild.default_role: discord.PermissionOverwrite(read_messages=False),
|
||||
team_role: discord.PermissionOverwrite(read_messages=True),
|
||||
team_role: discord.PermissionOverwrite(
|
||||
read_messages=True, manage_channels=True
|
||||
),
|
||||
},
|
||||
category=team_channel_category,
|
||||
reason=f"{ctx.author.name} à demandé une channel pour son équipe.",
|
||||
|
1055
src/cogs/tirages.py
1055
src/cogs/tirages.py
File diff suppressed because it is too large
Load Diff
@ -3,25 +3,31 @@ from pathlib import Path
|
||||
from time import time
|
||||
|
||||
__all__ = [
|
||||
"TOKEN",
|
||||
"DISCORD_TOKEN",
|
||||
"TFJM_TOKEN",
|
||||
"Role",
|
||||
"PROBLEMS",
|
||||
"MAX_REFUSE",
|
||||
"ROUND_NAMES",
|
||||
"TEAMS_CHANNEL_CATEGORY",
|
||||
"DIEGO",
|
||||
"ANANAS",
|
||||
"FAN_CLUBS",
|
||||
"BOT",
|
||||
"TOURNOIS",
|
||||
"EMBED_COLOR",
|
||||
"FRACTAL_URL",
|
||||
"FRACTAL_COOLDOWN",
|
||||
"File",
|
||||
"Emoji",
|
||||
]
|
||||
|
||||
|
||||
TOKEN = os.environ.get("TFJM_DISCORD_TOKEN")
|
||||
DISCORD_TOKEN = os.environ.get("TFJM_DISCORD_TOKEN")
|
||||
TFJM_TOKEN = os.environ.get("TFJM_ORG_TOKEN")
|
||||
|
||||
if TOKEN is None:
|
||||
|
||||
if DISCORD_TOKEN is None:
|
||||
print("No token for the bot were found.")
|
||||
print("You need to set the TFJM_DISCORD_TOKEN variable in your environement")
|
||||
print("Or just run:")
|
||||
@ -32,10 +38,16 @@ if TOKEN is None:
|
||||
|
||||
GUILD = "690934836696973404"
|
||||
DIEGO = 430566197868625920 # Mon id
|
||||
ANANAS = 619132180408303616
|
||||
FAN_CLUBS = {
|
||||
DIEGO: 706586020841259078,
|
||||
ANANAS: 706586027535368223,
|
||||
}
|
||||
BOT = 703305132300959754
|
||||
TEAMS_CHANNEL_CATEGORY = "Channels d'équipes"
|
||||
TEAMS_CHANNEL_CATEGORY = "Channels d'équipes 2"
|
||||
EMBED_COLOR = 0xFFA500
|
||||
FRACTAL_URL = "https://thefractal.space/img/{seed}.png?size=1500"
|
||||
FRACTAL_URL = "https://thefractal.space/img/{seed}.png?size=1000"
|
||||
FRACTAL_COOLDOWN = 30 # seconds
|
||||
|
||||
ROUND_NAMES = ["premier tour", "deuxième tour"]
|
||||
TOURNOIS = [
|
||||
@ -44,8 +56,7 @@ TOURNOIS = [
|
||||
"Paris-Saclay",
|
||||
"Paris-Avignon-Est",
|
||||
"Tours",
|
||||
"Bordeaux",
|
||||
"Nancy",
|
||||
"Bordeaux-Nancy",
|
||||
"Rennes",
|
||||
]
|
||||
|
||||
@ -55,6 +66,7 @@ class Role:
|
||||
DEV = "dev"
|
||||
ORGA = "Orga"
|
||||
ORGAS = tuple(f"Orga {t}" for t in TOURNOIS)
|
||||
JURY = tuple(f"Jury {t}" for t in TOURNOIS)
|
||||
BENEVOLE = "Bénévole"
|
||||
CAPTAIN = "Capitaine"
|
||||
PARTICIPANT = "Participant"
|
||||
@ -77,6 +89,7 @@ class File:
|
||||
TEAMS = TOP_LEVEL / "data" / "teams"
|
||||
JOKES = TOP_LEVEL / "data" / "jokes"
|
||||
JOKES_V2 = TOP_LEVEL / "data" / "jokesv2"
|
||||
MEMES = TOP_LEVEL / "data" / "memes"
|
||||
|
||||
|
||||
with open(File.TOP_LEVEL / "data" / "problems") as f:
|
||||
|
14
src/core.py
14
src/core.py
@ -2,7 +2,7 @@ import asyncio
|
||||
import sys
|
||||
from importlib import reload
|
||||
|
||||
from discord import User, Message, Reaction, NotFound
|
||||
from discord import User, Message, Reaction, NotFound, Forbidden
|
||||
from discord.ext.commands import Bot
|
||||
|
||||
__all__ = ["CustomBot"]
|
||||
@ -60,8 +60,12 @@ class CustomBot(Bot):
|
||||
reaction, u = await bot.wait_for(
|
||||
"reaction_add", check=check, timeout=timeout
|
||||
)
|
||||
the_msg = get(msgs, id=reaction.message.id)
|
||||
await the_msg.delete()
|
||||
|
||||
the_msg: Message = get(msgs, id=reaction.message.id)
|
||||
try:
|
||||
await the_msg.delete()
|
||||
except NotFound:
|
||||
pass # message was deleted
|
||||
msgs.remove(the_msg)
|
||||
except asyncio.TimeoutError:
|
||||
pass
|
||||
@ -69,6 +73,6 @@ class CustomBot(Bot):
|
||||
for m in msgs:
|
||||
try:
|
||||
await m.clear_reaction(Emoji.BIN)
|
||||
except NotFound:
|
||||
# Message or reaction deleted
|
||||
except (NotFound, Forbidden):
|
||||
# Message or reaction deleted / in dm channel
|
||||
pass
|
||||
|
@ -5,7 +5,8 @@ from src.core import CustomBot
|
||||
|
||||
# We allow "! " to catch people that put a space in their commands.
|
||||
# It must be in first otherwise "!" always match first and the space is not recognised
|
||||
bot = CustomBot(("! ", "!"))
|
||||
from src.utils import fg
|
||||
|
||||
|
||||
# Global variable to hold the tirages.
|
||||
# We *want* it to be global so we can reload the tirages cog without
|
||||
@ -13,19 +14,23 @@ bot = CustomBot(("! ", "!"))
|
||||
tirages = {}
|
||||
|
||||
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
print(f"{bot.user} has connected to Discord!")
|
||||
def start():
|
||||
bot = CustomBot(("! ", "!"))
|
||||
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
print(f"{bot.user} has connected to Discord!")
|
||||
|
||||
bot.remove_command("help")
|
||||
bot.load_extension("src.cogs.dev")
|
||||
bot.load_extension("src.cogs.errors")
|
||||
bot.load_extension("src.cogs.misc")
|
||||
bot.load_extension("src.cogs.teams")
|
||||
bot.load_extension("src.cogs.tirages")
|
||||
bot.load_extension("src.utils")
|
||||
bot.remove_command("help")
|
||||
bot.load_extension("src.cogs.dev")
|
||||
bot.load_extension("src.cogs.errors")
|
||||
bot.load_extension("src.cogs.misc")
|
||||
bot.load_extension("src.cogs.teams")
|
||||
bot.load_extension("src.cogs.tirages")
|
||||
bot.load_extension("src.utils")
|
||||
|
||||
bot.run(DISCORD_TOKEN)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
bot.run(TOKEN)
|
||||
start()
|
||||
|
13
src/utils.py
13
src/utils.py
@ -4,6 +4,19 @@ import psutil
|
||||
from discord.ext.commands import Bot
|
||||
|
||||
|
||||
def fg(text, color: int = 0xFFA500):
|
||||
r = color >> 16
|
||||
g = color >> 8 & 0xFF
|
||||
b = color & 0xFF
|
||||
return f"\033[38;2;{r};{g};{b}m{text}\033[m"
|
||||
|
||||
|
||||
def french_join(l):
|
||||
l = list(l)
|
||||
start = ", ".join(l[:-1])
|
||||
return f"{start} et {l[-1]}"
|
||||
|
||||
|
||||
def has_role(member, role: str):
|
||||
"""Return whether the member has a role with this name."""
|
||||
|
||||
|
Reference in New Issue
Block a user