Compare commits
19 Commits
b096248145
...
main
Author | SHA1 | Date | |
---|---|---|---|
ea14a2612c
|
|||
fa8a5da027
|
|||
61f8215a4a
|
|||
0f82a8aa7a
|
|||
b4c9607ce6
|
|||
8e67805eac
|
|||
830c5d7786
|
|||
e9c2ab95c5
|
|||
d25a233590
|
|||
67650bcd60
|
|||
e74184a4ce
|
|||
f6ea33d51c
|
|||
dfac15796e
|
|||
f66e5b5198
|
|||
9eb13560f2
|
|||
a3c71800af
|
|||
3cd8bf82dc
|
|||
3f0ba14166
|
|||
273dc88daa
|
340
bot.py
340
bot.py
@ -2,6 +2,7 @@
|
||||
|
||||
from collections import namedtuple
|
||||
import copy
|
||||
from datetime import datetime, timedelta
|
||||
from functools import partial
|
||||
import json
|
||||
from pathlib import Path
|
||||
@ -36,7 +37,7 @@ CANTONS = {
|
||||
"SH": "Schaffhouse",
|
||||
"SO": "Soleure",
|
||||
"SZ": "Schwytz",
|
||||
"TH": "Thurgovie",
|
||||
"TG": "Thurgovie",
|
||||
"TI": "Tessin",
|
||||
"UR": "Uri",
|
||||
"VD": "Vaud",
|
||||
@ -45,15 +46,18 @@ CANTONS = {
|
||||
"ZH": "Zurich",
|
||||
}
|
||||
|
||||
EQUIPES = ["rouge", "vert"]
|
||||
|
||||
CodeCanton = Literal["AG", "AI", "AR", "BE", "BL", "BS", "FR", "GE", "GL", "GR", "JU", "LU", "NE",
|
||||
"NW", "OW", "SG", "SH", "SO", "SZ", "TH", "TI", "UR", "VD", "VS", "ZG", "ZH"]
|
||||
"NW", "OW", "SG", "SH", "SO", "SZ", "TG", "TI", "UR", "VD", "VS", "ZG", "ZH"]
|
||||
Couleur = Literal["rouge", "vert"]
|
||||
|
||||
|
||||
intents = discord.Intents.default()
|
||||
intents.message_content = True
|
||||
|
||||
bot = commands.Bot(command_prefix='$', intents=intents)
|
||||
PREFIX = '!'
|
||||
bot = commands.Bot(command_prefix=PREFIX, intents=intents)
|
||||
|
||||
DATA_FILE = Path(__file__).parent / "data.json"
|
||||
if DATA_FILE.exists():
|
||||
@ -61,12 +65,13 @@ if DATA_FILE.exists():
|
||||
data = json.load(data_file)
|
||||
else:
|
||||
data = {
|
||||
'equipes': {'rouge': [], 'vert': []},
|
||||
'equipes': {equipe: [] for equipe in EQUIPES},
|
||||
'cantons': {code_canton: {'capture': None, 'verrouille': False} for code_canton in CANTONS.keys()},
|
||||
'defis': {
|
||||
'mains': {'rouge': [], 'vert': []},
|
||||
'mains': {equipe: [] for equipe in EQUIPES},
|
||||
'bonus': {equipe: 0 for equipe in EQUIPES},
|
||||
'tires_capture': [],
|
||||
'tires_competition': [],
|
||||
'tires_vol': [],
|
||||
}
|
||||
}
|
||||
with DATA_FILE.open('w') as data_file:
|
||||
@ -92,7 +97,7 @@ def generer_carte():
|
||||
cairosvg.svg2png(url='map.svg', write_to='map.png')
|
||||
|
||||
|
||||
@bot.command()
|
||||
@bot.command(brief="Affche la carte des cantons capturés.")
|
||||
async def carte(ctx: commands.Context):
|
||||
rouges = list(canton_code for canton_code, canton in data['cantons'].items()
|
||||
if canton['capture'] == "rouge")
|
||||
@ -114,7 +119,7 @@ async def carte(ctx: commands.Context):
|
||||
await ctx.send(message, file=discord.File(f, filename="battle4suisse.png"))
|
||||
|
||||
|
||||
@bot.command()
|
||||
@bot.command(brief=f"Capture un canton pour son équipe : {PREFIX}capturer CODE_CANTON [EQUIPE]")
|
||||
async def capturer(ctx: commands.Context, canton: CodeCanton, *, couleur: Couleur | None = None):
|
||||
if couleur is None:
|
||||
author_id = ctx.author.id
|
||||
@ -122,7 +127,7 @@ async def capturer(ctx: commands.Context, canton: CodeCanton, *, couleur: Couleu
|
||||
if author_id in membres_equipe:
|
||||
break
|
||||
else:
|
||||
raise commands.BadArgument("Vous n'appartez à aucune équipe. Merci de faire `$equipe [rouge|vert]`.")
|
||||
raise commands.BadArgument(f"Vous n'appartez à aucune équipe. Merci de faire `{PREFIX}equipe [{"|".join(EQUIPES)}]`.")
|
||||
data['cantons'][canton]['capture'] = couleur
|
||||
with DATA_FILE.open('w') as data_file:
|
||||
json.dump(data, data_file, indent=2)
|
||||
@ -138,7 +143,7 @@ async def capture_error(ctx, error):
|
||||
await ctx.send(str(error))
|
||||
|
||||
|
||||
@bot.command()
|
||||
@bot.command(brief=f"Verrouille un canton sur la carte pour son équipe : {PREFIX}verrouiller CODE_CANTON [EQUIPE]")
|
||||
async def verrouiller(ctx: commands.Context, canton: CodeCanton, *, couleur: Couleur | None = None):
|
||||
if couleur is None:
|
||||
author_id = ctx.author.id
|
||||
@ -146,7 +151,7 @@ async def verrouiller(ctx: commands.Context, canton: CodeCanton, *, couleur: Cou
|
||||
if author_id in membres_equipe:
|
||||
break
|
||||
else:
|
||||
raise commands.BadArgument("Vous n'appartez à aucune équipe. Merci de faire `$equipe [rouge|vert]`.")
|
||||
raise commands.BadArgument(f"Vous n'appartez à aucune équipe. Merci de faire `{PREFIX}equipe [{"|".join(EQUIPES)}]`.")
|
||||
data['cantons'][canton]['capture'] = couleur
|
||||
data['cantons'][canton]['verrouille'] = True
|
||||
with DATA_FILE.open('w') as data_file:
|
||||
@ -156,7 +161,7 @@ async def verrouiller(ctx: commands.Context, canton: CodeCanton, *, couleur: Cou
|
||||
return await carte(ctx)
|
||||
|
||||
|
||||
@bot.command()
|
||||
@bot.command(brief=f"Réinitialise l'état de capture d'un canton : {PREFIX}reset CODE_CANTON")
|
||||
async def reset(ctx: commands.Context, canton: CodeCanton):
|
||||
data['cantons'][canton]['capture'] = None
|
||||
data['cantons'][canton]['verrouille'] = False
|
||||
@ -166,7 +171,7 @@ async def reset(ctx: commands.Context, canton: CodeCanton):
|
||||
return await carte(ctx)
|
||||
|
||||
|
||||
@bot.command()
|
||||
@bot.command(brief=f"Rejoindre une équipe : {PREFIX}equipe EQUIPE")
|
||||
async def equipe(ctx: commands.Context, couleur: Couleur):
|
||||
author_id = ctx.author.id
|
||||
for membres_equipe in data['equipes'].values():
|
||||
@ -178,78 +183,327 @@ async def equipe(ctx: commands.Context, couleur: Couleur):
|
||||
await ctx.send(f"Équipe {couleur} rejointe")
|
||||
|
||||
|
||||
@bot.command()
|
||||
async def defis(ctx: commands.Context, *, type_defi: Literal['capture', 'competition'] = "capture"):
|
||||
@bot.command(brief=f"Affiche la liste des noms des défis : {PREFIX}defis [capture | vol]")
|
||||
async def defis(ctx: commands.Context, *, type_defi: Literal['capture', 'vol'] = "capture"):
|
||||
await ctx.send(f"Liste des défis de {type_defi} :\n" + "\n".join(f"* {defi['id']} : {defi['nom']}" for defi in DEFIS[type_defi]))
|
||||
|
||||
|
||||
@bot.command()
|
||||
async def description(ctx: commands.Context, type_defi: Literal['capture', 'competition'] = "capture"):
|
||||
@bot.command(brief=f"Affiche la description des défis")
|
||||
async def description(ctx: commands.Context, type_defi: Literal['capture', 'vol'] = "capture", id_defi: int | None = None):
|
||||
defis = DEFIS[type_defi]
|
||||
embeds = []
|
||||
for page in range((len(defis) - 1) // 25 + 1):
|
||||
defis_page = defis[page * 25:(page + 1) * 25]
|
||||
embed = discord.Embed(title=f"Description des défis", colour=discord.Colour.gold())
|
||||
embed.set_footer(f"Page {page}/{(len(defis) - 1) // 25 + 1}")
|
||||
for defi in defis_page:
|
||||
embed.add_field(name=f"{defi['nom']} (n°{defi['id']})", value=defi['description'], inline=False)
|
||||
if id_defi is not None:
|
||||
embed = discord.Embed(title=f"Description du défi {id_defi}", colour=discord.Colour.gold())
|
||||
try:
|
||||
defi = next(defi for defi in defis if defi['id'] == id_defi)
|
||||
except StopIteration:
|
||||
raise commands.BadArgument(f"Le défi de {type_defi} n°{id_defi} n'existe pas.")
|
||||
embed.add_field(name=f"{defi['nom']} {defi['bonus'] * ":star:"}", value=defi['description'], inline=False)
|
||||
embeds.append(embed)
|
||||
else:
|
||||
for page in range((len(defis) - 1) // 25 + 1):
|
||||
defis_page = defis[page * 25:(page + 1) * 25]
|
||||
embed = discord.Embed(title=f"Description des défis", colour=discord.Colour.gold())
|
||||
embed.set_footer(text=f"Page {page + 1}/{(len(defis) - 1) // 25 + 1}")
|
||||
for defi in defis_page:
|
||||
embed.add_field(name=f"{defi['nom']} {defi['bonus'] * ":star:"} (n°{defi['id']})", value=defi['description'], inline=False)
|
||||
embeds.append(embed)
|
||||
await ctx.send(embeds=embeds)
|
||||
|
||||
|
||||
@bot.command()
|
||||
async def tirage(ctx: commands.Context, nb_defis: int = 7):
|
||||
if data['defis']['mains']['rouge'] or data['defis']['mains']['vert']:
|
||||
async def tirage(ctx: commands.Context, nb_defis: int = 5):
|
||||
if any(data['defis']['mains'][equipe] for equipe in EQUIPES):
|
||||
raise commands.BadArgument("Les mains sont déjà initialisées")
|
||||
|
||||
defis_libres = copy.deepcopy(DEFIS['capture'])
|
||||
for equipe in ('rouge', 'vert'):
|
||||
for equipe in EQUIPES:
|
||||
for _i in range(nb_defis):
|
||||
defi = random.choice(defis_libres)
|
||||
defis_libres.remove(defi)
|
||||
data['defis']['mains'][equipe].append(defi['id'])
|
||||
data['defis']['tires_capture'].append(defi['id'])
|
||||
|
||||
main = data['defis']['mains'][equipe]
|
||||
embeds = []
|
||||
colour = discord.Color.red() if equipe == "rouge" else discord.Color.green()
|
||||
for id_defi in main:
|
||||
defi = next(defi for defi in DEFIS['capture'] if defi['id'] == id_defi)
|
||||
embed = discord.Embed(title=defi['nom'], description=defi['description'], colour=colour)
|
||||
embed.set_footer(text=f"Défi n°{defi['id']}")
|
||||
embeds.append(embed)
|
||||
for member_id in data['equipes'][equipe]:
|
||||
channel_dm = await bot.create_dm(namedtuple("User", "id")(member_id))
|
||||
await channel_dm.send("Vos défis en main :", embeds=embeds)
|
||||
await afficher_main(ctx, author_id=member_id)
|
||||
with DATA_FILE.open('w') as data_file:
|
||||
json.dump(data, data_file, indent=2)
|
||||
await ctx.send("Les mains de départ ont bien été tirées ! Le contenu vous a été envoyé en MP.")
|
||||
|
||||
|
||||
@bot.command()
|
||||
async def main(ctx: commands.Context):
|
||||
author_id = ctx.author.id
|
||||
async def vol(ctx: commands.Context):
|
||||
defi_vol = random.choice([defi for defi in DEFIS['vol'] if defi['id'] not in data['defis']['tires_vol']])
|
||||
data['defis']['tires_vol'].append(defi_vol['id'])
|
||||
embed = discord.Embed(title=defi_vol['nom'], description=defi_vol['description'])
|
||||
embed.set_footer(text=f"Défi de compétition n°{defi_vol['id']}")
|
||||
with DATA_FILE.open('w') as data_file:
|
||||
json.dump(data, data_file, indent=2)
|
||||
await ctx.send("@everyone Un canton est attaqué ! L'équipe vainqueure de ce défi conservera son contrôle jusqu'à la fin du jeu :", embed=embed)
|
||||
|
||||
|
||||
@bot.command()
|
||||
async def remiser(ctx: commands.Context, type_defi: Literal['capture', 'vol'] = "capture", id_defi: int | None = None):
|
||||
defis = DEFIS[type_defi]
|
||||
try:
|
||||
defi = next(defi for defi in defis if defi['id'] == id_defi)
|
||||
except StopIteration:
|
||||
raise commands.BadArgument(f"Le défi de {type_defi} n°{id_defi} n'existe pas.")
|
||||
if id_defi in data['defis'][f'tires_{type_defi}']:
|
||||
data['defis'][f'tires_{type_defi}'].remove(id_defi)
|
||||
with DATA_FILE.open('w') as data_file:
|
||||
json.dump(data, data_file, indent=2)
|
||||
await ctx.reply(f"Le défi de {type_defi} n°{id_defi} ({defi['nom']}) a été retiré de la défausse et pourra à nouveau être tiré au sort.")
|
||||
else:
|
||||
await ctx.reply(f"Le défi de {type_defi} n°{id_defi} ({defi['nom']}) n'était déjà pas dans la défausse.")
|
||||
|
||||
|
||||
class MainView(discord.ui.View):
|
||||
def __init__(self, ctx: commands.Context, user_id: int, defis: list[dict], timeout: float | None = 180.0):
|
||||
super().__init__(timeout=timeout)
|
||||
|
||||
async def terminer_defi(self, id_defi: int, user_id: int, interaction: discord.Interaction):
|
||||
await interaction.response.defer()
|
||||
await terminer(ctx, id_defi, user_id, interaction.channel)
|
||||
|
||||
for id_defi in defis:
|
||||
defi = next(defi for defi in DEFIS['capture'] if defi['id'] == id_defi)
|
||||
button = discord.ui.Button(style=discord.ButtonStyle.success, label=f"Terminer {defi['nom']}")
|
||||
button.callback = partial(terminer_defi, self, id_defi, user_id)
|
||||
self.add_item(button)
|
||||
|
||||
|
||||
|
||||
@bot.command(name="main")
|
||||
async def afficher_main(ctx: commands.Context, mode: Literal['public', 'prive'] = "prive", author_id: int | None = None):
|
||||
author_id = author_id or ctx.author.id
|
||||
for couleur, membres_equipe in data['equipes'].items():
|
||||
if author_id in membres_equipe:
|
||||
break
|
||||
else:
|
||||
raise commands.BadArgument("Vous n'appartez à aucune équipe. Merci de faire `$equipe [rouge|vert]`.")
|
||||
raise commands.BadArgument(f"Vous n'appartez à aucune équipe. Merci de faire `{PREFIX}equipe [{"|".join(EQUIPES)}]`.")
|
||||
|
||||
main = data['defis']['mains'][couleur]
|
||||
nb_bonus = data['defis']['bonus'][couleur]
|
||||
embeds = []
|
||||
colour = discord.Color.red() if couleur == "rouge" else discord.Color.green()
|
||||
for id_defi in main:
|
||||
defi = next(defi for defi in DEFIS['capture'] if defi['id'] == id_defi)
|
||||
embed = discord.Embed(title=defi['nom'], description=defi['description'], colour=colour)
|
||||
embed = discord.Embed(title=f"{defi['nom']} {defi['bonus'] * ":star:"}", description=defi['description'], colour=colour)
|
||||
embed.set_footer(text=f"Défi n°{defi['id']}")
|
||||
embeds.append(embed)
|
||||
channel_dm = await bot.create_dm(ctx.author)
|
||||
await channel_dm.send("Vos défis en main :", embeds=embeds)
|
||||
if mode == "public":
|
||||
await ctx.send(f"Défis de l'équipe **{couleur}** :", embeds=embeds)
|
||||
else:
|
||||
channel_dm = await bot.create_dm(namedtuple('User', 'id')(author_id))
|
||||
await channel_dm.send(f"Vous disposez de **{nb_bonus} bonus {nb_bonus * ":star:"}**.\nVos défis en main :", embeds=embeds, view=MainView(ctx, author_id, main))
|
||||
|
||||
|
||||
@bot.command()
|
||||
async def terminer(ctx: commands.Context, id_defi: int, author_id: int | None = None, channel: discord.abc.Messageable | None = None):
|
||||
if all(id_defi != defi['id'] for defi in DEFIS['capture']):
|
||||
raise commands.BadArgument(f"Erreur : Le défi {id_defi_1} n'existe pas")
|
||||
defi = next(defi for defi in DEFIS['capture'] if defi['id'] == id_defi)
|
||||
author_id = author_id or ctx.author.id
|
||||
for equipe, membres_equipe in data['equipes'].items():
|
||||
if author_id in membres_equipe:
|
||||
break
|
||||
else:
|
||||
raise commands.BadArgument(f"Vous n'appartez à aucune équipe. Merci de faire `{PREFIX}equipe [{"|".join(EQUIPES)}]`.")
|
||||
|
||||
main = data['defis']['mains'][equipe]
|
||||
if id_defi not in main:
|
||||
raise commands.BadArgument(f"Le défi {id_defi} n'est pas dans votre main. Faites `{PREFIX}main` pour afficher votre main.")
|
||||
|
||||
nouveau_defi = random.choice([defi for defi in DEFIS['capture'] if defi['id'] not in data['defis']['tires_capture']])
|
||||
main.remove(id_defi)
|
||||
main.append(nouveau_defi['id'])
|
||||
data['defis']['tires_capture'].append(nouveau_defi['id'])
|
||||
data['defis']['bonus'][equipe] += defi['bonus']
|
||||
with DATA_FILE.open('w') as data_file:
|
||||
json.dump(data, data_file, indent=2)
|
||||
|
||||
channel = channel or ctx
|
||||
await channel.send(f"Défi n°{id_defi} **{defi['nom']}** terminé ! Il est retiré de votre main.")
|
||||
await channel.send(f"Votre équipe gagne **{defi['bonus']} bonus**. Vous en possédez désormais {data['defis']['bonus'][equipe]}.")
|
||||
colour = discord.Color.red() if equipe == "rouge" else discord.Color.green()
|
||||
embed = discord.Embed(title=f"{nouveau_defi['nom']} {defi['bonus'] * ":star:"}", description=nouveau_defi['description'], colour=colour)
|
||||
embed.set_footer(text=f"Défi n°{nouveau_defi['id']}")
|
||||
await channel.send("**Votre nouveau défi en main :**", embed=embed)
|
||||
for member_id in data['equipes'][equipe]:
|
||||
await afficher_main(ctx, author_id=member_id)
|
||||
|
||||
|
||||
@bot.command()
|
||||
async def bonus(ctx: commands.Context, equipe: Couleur | None = None, nouvelle_valeur: int | None = None):
|
||||
if equipe is None:
|
||||
author_id = ctx.author.id
|
||||
for equipe, membres_equipe in data['equipes'].items():
|
||||
if author_id in membres_equipe:
|
||||
break
|
||||
else:
|
||||
raise commands.BadArgument(f"Vous n'appartez à aucune équipe. Merci de faire `{PREFIX}equipe [{"|".join(EQUIPES)}]`.")
|
||||
|
||||
nb_bonus = data['defis']['bonus'][equipe]
|
||||
if nouvelle_valeur is None:
|
||||
if nb_bonus >= 1:
|
||||
data['defis']['bonus'][equipe] -= 1
|
||||
await ctx.send(f"L'équipe **{equipe}** vient d'utiliser un bonus !")
|
||||
else:
|
||||
await ctx.reply(f"Vous n'avez plus de bonus.", ephemeral=True)
|
||||
else:
|
||||
data['defis']['bonus'][equipe] = nouvelle_valeur
|
||||
await ctx.send(f"L'équipe **{equipe}** a désormais **{nouvelle_valeur} bonus**, contre {nb_bonus} auparavant.")
|
||||
with DATA_FILE.open('w') as data_file:
|
||||
json.dump(data, data_file, indent=2)
|
||||
|
||||
|
||||
@bot.command()
|
||||
async def echange(ctx: commands.Context, id_defi_1: int, id_defi_2: int):
|
||||
if all(id_defi_1 != defi['id'] for defi in DEFIS['capture']):
|
||||
raise commands.BadArgument(f"Erreur : Le défi {id_defi_1} n'existe pas")
|
||||
if all(id_defi_2 != defi['id'] for defi in DEFIS['capture']):
|
||||
raise commands.BadArgument(f"Erreur : Le défi {id_defi_2} n'existe pas")
|
||||
defi_1 = next(defi for defi in DEFIS['capture'] if defi['id'] == id_defi_1)
|
||||
defi_2 = next(defi for defi in DEFIS['capture'] if defi['id'] == id_defi_2)
|
||||
|
||||
equipe_1, equipe_2 = None, None
|
||||
for equipe in EQUIPES:
|
||||
main = data['defis']['mains'][equipe]
|
||||
if id_defi_1 in main:
|
||||
equipe_1 = equipe
|
||||
if id_defi_2 in main:
|
||||
equipe_2 = equipe
|
||||
if equipe_1 is None and equipe_2 is None:
|
||||
raise commands.BadArgument("Erreur : Aucun des deux défis n'est fait par une équipe")
|
||||
elif equipe_1 == equipe_2:
|
||||
raise commands.BadArgument(f"Erreur : Les défis {id_defi_1} et {id_defi_2} sont tous les deux fait par la même équipe {equipe_1}")
|
||||
|
||||
if equipe_1 is not None:
|
||||
data['defis']['mains'][equipe_1].remove(id_defi_1)
|
||||
data['defis']['mains'][equipe_1].append(id_defi_2)
|
||||
else:
|
||||
tires_capture = data['defis']['tires_capture']
|
||||
if id_defi_1 not in tires_capture:
|
||||
tires_capture.append(id_defi_1)
|
||||
if id_defi_2 in tires_capture:
|
||||
tires_capture.remove(id_defi_2)
|
||||
|
||||
if equipe_2 is not None:
|
||||
data['defis']['mains'][equipe_2].remove(id_defi_2)
|
||||
data['defis']['mains'][equipe_2].append(id_defi_1)
|
||||
else:
|
||||
tires_capture = data['defis']['tires_capture']
|
||||
if id_defi_1 in tires_capture:
|
||||
tires_capture.remove(id_defi_1)
|
||||
if id_defi_2 not in tires_capture:
|
||||
tires_capture.append(id_defi_2)
|
||||
with DATA_FILE.open('w') as data_file:
|
||||
json.dump(data, data_file, indent=2)
|
||||
|
||||
if equipe_1 is not None and equipe_2 is not None:
|
||||
await ctx.send(f"Le défi **{defi_1['nom']}** de l'équipe **{equipe_1}** et le défi **{defi_2['nom']}** de l'équipe **{equipe_2}** ont été échangés !")
|
||||
else:
|
||||
await ctx.send(f"Les défis **{defi_1['nom']}** et **{defi_2['nom']}** ont été échangés !")
|
||||
|
||||
|
||||
@bot.command()
|
||||
async def melanger(ctx: commands.Context, nb_defis: int = 5):
|
||||
author_id = ctx.author.id
|
||||
for equipe, membres_equipe in data['equipes'].items():
|
||||
if author_id in membres_equipe:
|
||||
break
|
||||
else:
|
||||
raise commands.BadArgument(f"Vous n'appartez à aucune équipe. Merci de faire `{PREFIX}equipe [{"|".join(EQUIPES)}]`.")
|
||||
|
||||
main = data['defis']['mains'][equipe]
|
||||
|
||||
for _i in range(nb_defis):
|
||||
nouveau_defi = random.choice([defi for defi in DEFIS['capture'] if defi['id'] not in data['defis']['tires_capture']])
|
||||
main.append(nouveau_defi['id'])
|
||||
data['defis']['tires_capture'].append(nouveau_defi['id'])
|
||||
with DATA_FILE.open('w') as data_file:
|
||||
json.dump(data, data_file, indent=2)
|
||||
|
||||
await ctx.send(f"Main de l'équipe {equipe} mélangée !")
|
||||
for member_id in data['equipes'][equipe]:
|
||||
await afficher_main(ctx, author_id=member_id)
|
||||
|
||||
|
||||
@bot.command()
|
||||
async def de(ctx: commands.Context, nb_faces: int = 6):
|
||||
resultat = random.randint(1, nb_faces + 1)
|
||||
await ctx.reply(f":game_die: Résultat du dé à {nb_faces} faces : **{resultat}**")
|
||||
|
||||
|
||||
@bot.command()
|
||||
async def chronometre(ctx: commands.Context, minutes: int = 30, secondes: int = 0):
|
||||
fin = datetime.now() + timedelta(minutes=minutes, seconds=secondes)
|
||||
await ctx.send(f"Chronomètre lancé pour **{minutes:02d}:{secondes:02d}** (fin à <t:{int(fin.timestamp())}:T>)\nFin <t:{int(fin.timestamp())}:R>")
|
||||
|
||||
|
||||
@bot.command()
|
||||
async def debug(ctx: commands.Context, keys: str, *, set_value: str | None = None):
|
||||
keys = keys.split('.')
|
||||
parent = None
|
||||
out = data
|
||||
for key in keys:
|
||||
if key not in out:
|
||||
raise commands.BadArgument(f"Clé {key} absente du dictionnaire, valeurs possibles : {out.keys()}")
|
||||
parent = out
|
||||
last_key = key
|
||||
out = out[key]
|
||||
data_json = json.dumps(out, indent=2)
|
||||
if set_value is None:
|
||||
await ctx.reply(f"```json\n{data_json}\n```", ephemeral=True)
|
||||
else:
|
||||
new_data = json.loads(set_value)
|
||||
new_data_json = json.dumps(new_data, indent=2)
|
||||
parent[last_key] = new_data
|
||||
await ctx.reply(f"Anciennes données :\n```json\n{data_json}\n```Nouvelles données :\n```json\n{new_data_json}\n```\nFaites `{PREFIX}save` pour sauvegarder, ou `{PREFIX}` reload pour rollback.", ephemeral=True)
|
||||
|
||||
|
||||
@bot.command()
|
||||
async def reload(ctx: commands.Context):
|
||||
global data, DEFIS
|
||||
with DATA_FILE.open() as data_file:
|
||||
data = json.load(data_file)
|
||||
with DEFIS_FILE.open() as defis_file:
|
||||
DEFIS = json.load(defis_file)
|
||||
await ctx.reply("Configuration rechargée.", ephemeral=True)
|
||||
|
||||
|
||||
@bot.command()
|
||||
async def save(ctx: commands.Context):
|
||||
with DATA_FILE.open('w') as data_file:
|
||||
json.dump(data, data_file, indent=2)
|
||||
await ctx.reply("Configuration sauvegardée.", ephemeral=True)
|
||||
|
||||
|
||||
@carte.error
|
||||
@reset.error
|
||||
@equipe.error
|
||||
async def equipe_error(ctx, error):
|
||||
@defis.error
|
||||
@description.error
|
||||
@tirage.error
|
||||
@vol.error
|
||||
@remiser.error
|
||||
@afficher_main.error
|
||||
@terminer.error
|
||||
@bonus.error
|
||||
@echange.error
|
||||
@melanger.error
|
||||
@de.error
|
||||
@chronometre.error
|
||||
@debug.error
|
||||
@reload.error
|
||||
@save.error
|
||||
async def on_error(ctx, error):
|
||||
with DATA_FILE.open() as data_file:
|
||||
data = json.load(data_file)
|
||||
await ctx.send(str(error))
|
||||
if not isinstance(error, commands.BadArgument):
|
||||
raise error
|
||||
|
||||
|
||||
bot.run(DISCORD_TOKEN)
|
||||
|
309
defis.json
309
defis.json
@ -2,100 +2,317 @@
|
||||
"capture": [
|
||||
{
|
||||
"id": 1,
|
||||
"nom": "Défi 1",
|
||||
"description": "Défi 1"
|
||||
"nom": "Artisan⋅e chocolatièr⋅e",
|
||||
"description": "À partir de chocolat local, construis un pont.\n\nLa structure doit rester en place pendant au moins 15 secondes sans s'effondrer.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"nom": "Défi 2",
|
||||
"description": "Défi 2"
|
||||
"nom": "Manger (chez) les riches",
|
||||
"description": "Mange une spécialité locale du canton.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"nom": "Défi 3",
|
||||
"description": "Défi 3"
|
||||
"nom": "Non-alignement",
|
||||
"description": "Devant une institution locale, adresse une liste de 20 revendications géopolitiquement neutres.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"nom": "Défi 4",
|
||||
"description": "Défi 4"
|
||||
"nom": "Low five",
|
||||
"description": "Fais un low-five au point le plus bas d'altitude du canton.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"nom": "Défi 5",
|
||||
"description": "Défi 5"
|
||||
"nom": "Artiste GPS",
|
||||
"description": "Trace la croix du drapeau suisse à l'aide de ton tracé GPS.\n\nLa croix doit être ressemblante sans être nécessairement parfaite.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"nom": "Défi 6",
|
||||
"description": "Défi 6"
|
||||
"nom": "Prendre de la hauteur",
|
||||
"description": "Rends-toi sur le toit d'un bâtiment de manière légale.",
|
||||
"bonus": 1
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"nom": "Défi 7",
|
||||
"description": "Défi 7"
|
||||
"nom": "Marcher sur l'eau",
|
||||
"description": "Traverse un cours d'eau d'une rive à l'autre.\n\nLa distance traversée doit être d'au moins un mètre.\nIl n'est pas possible d'emprunter une structure existante pour le traverser, en particulier d'utiliser un pont.\n\nSi le cours d'eau fait la frontière entre 2 cantons, les deux cantons sont capturés.",
|
||||
"bonus": 1
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"nom": "Défi 8",
|
||||
"description": "Défi 8"
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"nom": "Défi 9",
|
||||
"description": "Défi 9"
|
||||
"nom": "Randonnée",
|
||||
"description": "Construis une tour de cailloux à plus de 800 mètres d'altitude.",
|
||||
"bonus": 1
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"nom": "Défi 10",
|
||||
"description": "Défi 10"
|
||||
"nom": "Panorama",
|
||||
"description": "Photographie taon partenaire de l'autre côté d'un lac.\n\nLe lac doit être reconnu comme tel sur les cartes, et l'étendue d'eau doit être pleinement visible sur l'image.",
|
||||
"bonus": 1
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"nom": "Défi 11",
|
||||
"description": "Défi 11"
|
||||
"nom": "Rageux⋅se",
|
||||
"description": "Auprès de la meilleure attraction touristique (musée, monument, paysage,…) du canton, fais une critique négative en 5 points en vidéo.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"nom": "Défi 12",
|
||||
"description": "Défi 12"
|
||||
"nom": "Non-binaire",
|
||||
"description": "Dans un lieu que l'on peut qualifier d'entre-deux (milieu d'un pont, mezzanine entre 2 étages,…), chante « The Code » de Nemo.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"nom": "Défi 13",
|
||||
"description": "Défi 13"
|
||||
"nom": "Démocratie",
|
||||
"description": "Organiser une votation citoyenne sur un sujet au choix. La votation doit récolter au moins 10 votant·es.",
|
||||
"bonus": 1
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"nom": "Défi 14",
|
||||
"description": "Défi 14"
|
||||
"nom": "Inaction climatique suisse",
|
||||
"description": "Nettoie 10 déchets jetés au sol d'un espace.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"nom": "Défi 15",
|
||||
"description": "Défi 15"
|
||||
"nom": "Révolution bovine",
|
||||
"description": "Parle à une vache dans un pré et tente de la syndicaliser en lui suggérant de se libérer de sa condition d'élevage.\n\nIl n'est pas nécessaire de se faire comprendre.",
|
||||
"bonus": 1
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"nom": "Défi 16",
|
||||
"description": "Défi 16"
|
||||
}
|
||||
],
|
||||
"competition": [
|
||||
{
|
||||
"id": 1,
|
||||
"nom": "Compétition 1",
|
||||
"description": "Compétition 1"
|
||||
"nom": "Génie incivil",
|
||||
"description": "Passe sous 3 ponts.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"nom": "Tourisme minimal",
|
||||
"description": "Va faire du tourisme dans le village le moins peuplé du canton disposant d'une gare à moins de 1 km.",
|
||||
"bonus": 1
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"nom": "Jeu de mots au menu",
|
||||
"description": "Trouve du riz cantonais.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"nom": "Patois",
|
||||
"description": "Demande un mot en romanche.",
|
||||
"bonus": 1
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"nom": "Du Jura aux Alpes",
|
||||
"description": "Transporte une pierre jurassienne dans les Alpes, ou inversement. En cas de succès, les cantons de départ et d'arrivée sont capturés, à moins que le canton de départ n'ait été capturé par l'équipe adverse entre temps, auquel cas c'est un échec.\n\nIl est possible de faire d'autres défis pendant le transport. Les points de départ et d'arrivée doivent être à au moins 500 mètres d'altitude pour le Jura et 700 mètres d'altitude pour les Alpes.",
|
||||
"bonus": 2
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"nom": "Du Rhin au Rhône",
|
||||
"description": "Touche le Rhône et le Rhin dans la même journée. En cas de succès, les cantons de départ et d'arrivée sont capturés, à moins que le canton d'origine n'ait été capturé avant l'accomplissement du défi, auquel cas c'est un échec.\n\nIl est possible de réaliser d'autres défis pendant l'accomplissement de celui-ci.",
|
||||
"bonus": 2
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"nom": "Pas moche, juste différent",
|
||||
"description": "Auprès du bâtiment le plus moche identifié selon ses propres goûts, rédige un poème qui en fait l'éloge et le traduire en français, allemand et italien.\n\nLe poème doit être rédigé manuscrit sur une feuille.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"nom": "Rançon",
|
||||
"description": "Écris « Bataille4Suisse » avec des papiers découpés et ré-agencés, tel un message de rançon.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"nom": "Moyenne",
|
||||
"description": "Rends-toi le plus proche possible du barycentre (point central) d'une commune qui a entre 4100 et 4300 habitant⋅es. De ce point de vue, choisir jusqu'à 10 sommets visibles et non cachés par des bâtiments, et trouver quel est le sommet d'altitude moyenne.",
|
||||
"bonus": 1
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"nom": "Helvetica",
|
||||
"description": "Trouve un texte écrit en police d'écriture Helvetica.\n\nIl peut être imprimé ou sur écran d'affichage, et doit être trouvé et ne peut pas être créé ou mis en cette police par les joueureuses ni à leur demande.",
|
||||
"bonus": 1
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"nom": "Terre en vue",
|
||||
"description": "Positionne-toi à au moins 10 km de tout lac.",
|
||||
"bonus": 1
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"nom": "Vue de haut",
|
||||
"description": "Rends-toi à la gare ferroviaire accessible la plus haute du canton.",
|
||||
"bonus": 1
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"nom": "Pi Day",
|
||||
"description": "Rends-toi dans une commune dont l'un de ses noms officiels contient la lettre P, et dessiner un cercle le plus ressemblant possible.",
|
||||
"bonus": 1
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"nom": "Quelle heure est-il ?",
|
||||
"description": "Trouve une montre ou une horloge à cadran qui indique une heure entre 10h10 et 10h15.\n\nIl n'est pas autorisé de régler l'heure ni de demander à la régler, et il ne peut pas s'agir d'une montre qu'on possède ou portée par un·e passant·e.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"nom": "Vers l'infini et au-delà",
|
||||
"description": "Photographie un sommet à au moins 2000 mètres d'altitude.\n\nUne photo où le sommet est caché par les nuages ou par la nuit est acceptée.",
|
||||
"bonus": 1
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"nom": "Traversée",
|
||||
"description": "Traverse un canton B depuis un canton A vers un canton C.\n\nLe canton B est capturé.",
|
||||
"bonus": 1
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"nom": "Promotion culturelle",
|
||||
"description": "Prends un flyer dans un lieu culturel, et en fais-en la promotion dans un lieu culturel similaire d'un autre canton.\n\nEn cas de succès du défi, les deux cantons sont capturés.",
|
||||
"bonus": 1
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"nom": "Placement de produit",
|
||||
"description": "Filme une pub de parfum.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"nom": "Polyglote",
|
||||
"description": "Trouve une personne qui parle français, une personne qui parle allemand, une personne qui parle italien.\n\nCes trois personnes ne sont pas nécessairement distinctes.",
|
||||
"bonus": 1
|
||||
},
|
||||
{
|
||||
"id": 35,
|
||||
"nom": "Cabotage",
|
||||
"description": "Dans le même canton, prends un train et descend au prochain arrêt sans changer de canton.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 36,
|
||||
"nom": "Cadeau !",
|
||||
"description": "Offre un cadeau à quelqu'un⋅e, que vous l .",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 37,
|
||||
"nom": "Culture",
|
||||
"description": "Trouve « Astérix chez les Helvètes » ou un livre sur le Nicaragua.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 38,
|
||||
"nom": "Mairiephilie",
|
||||
"description": "Dessine l'administration municipale d'une commune de ton canton devant elle.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 39,
|
||||
"nom": "Sing'in the rain",
|
||||
"description": "Chante « Sing'in the rain » sous la pluie ou sous la neige. S'il neige, remplace « rain » par « snow ».",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 40,
|
||||
"nom": "Touch the ~~grass~~ snow",
|
||||
"description": "Touche de la neige naturelle.",
|
||||
"bonus": 1
|
||||
}
|
||||
],
|
||||
"vol": [
|
||||
{
|
||||
"id": 2,
|
||||
"nom": "Compétition 2",
|
||||
"description": "Compétition 2"
|
||||
"nom": "Chocolat :chocolate_bar:",
|
||||
"description": "Photographie le plus de chocolats différents (marque/chocolatièr⋅e et nom) en 15 minutes.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"nom": "Compétition 3",
|
||||
"description": "Compétition 3"
|
||||
"nom": "Fromage :cheese:",
|
||||
"description": "Photographie le plus de fromages différents en 15 minutes.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"nom": "Vélo :bike:",
|
||||
"description": "Photographie le plus de vélos différents en 15 minutes.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"nom": "Mot aléatoire",
|
||||
"description": "Fais dire à un⋅e passant⋅e le mot aléatoire de ton choix le plus rapidement possible, sans donner le mot ni sa définition exacte.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"nom": "Luxe :money_with_wings:",
|
||||
"description": "Trouve un objet vendu le plus cher possible en 15 minutes.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"nom": "Bonne affaire :gift:",
|
||||
"description": "Trouve un objet vendu le moins cher possible en 15 minutes.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"nom": "Banques :bank:",
|
||||
"description": "Photographie le plus de banques en 15 minutes.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"nom": "Animaux :dog2:",
|
||||
"description": "Trouve le plus d'espèces animales en 15 minutes.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"nom": "Fuis ! :person_running:",
|
||||
"description": "Quitte ton canton le plus rapidement possible.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"nom": "Château-fort :european_castle:",
|
||||
"description": "Trouver la façade la plus imposante en 15 minutes.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"nom": "Cinéphile :cinema:",
|
||||
"description": "Trouve un cinéma le plus rapidement possible.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"nom": "Fièr⋅e :rainbow_flag:",
|
||||
"description": "Trouve un drapeau du parapluie LGBT le plus rapidement possible.\n\nIl ne peut pas être sur toi, ni fabriqué, ni sur un écran.",
|
||||
"bonus": 0
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"nom": "Panneaux",
|
||||
"description": "Trouve le plus le panneaux de signalisation possible en 15 minutes, qui ne sont pas des panneaux indiquant des directions.",
|
||||
"bonus": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Reference in New Issue
Block a user