battle4suisse/bot.py

260 lines
9.6 KiB
Python
Executable File

#!/usr/bin/env python3
from collections import namedtuple
import copy
from functools import partial
import json
from pathlib import Path
import random
from typing import Literal
from xml.dom import minidom
import cairosvg
import discord
from discord.ext import commands
from config import *
CANTONS = {
"AG": "Argovie",
"AI": "Appenzell Rhodes-Intérieures",
"AR": "Appenzell Rhodes-Extérieures",
"BE": "Berne",
"BL": "Bâle-Campagne",
"BS": "Bâle-Ville",
"FR": "Fribourg",
"GE": "Genève",
"GL": "Glaris",
"GR": "Grisons",
"JU": "Jura",
"LU": "Lucerne",
"NE": "Neuchâtel",
"NW": "Nidwald",
"OW": "Obwald",
"SG": "Saint-Gall",
"SH": "Schaffhouse",
"SO": "Soleure",
"SZ": "Schwytz",
"TH": "Thurgovie",
"TI": "Tessin",
"UR": "Uri",
"VD": "Vaud",
"VS": "Valais",
"ZG": "Zoug",
"ZH": "Zurich",
}
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"]
Couleur = Literal["rouge", "vert"]
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix='$', intents=intents)
DATA_FILE = Path(__file__).parent / "data.json"
if DATA_FILE.exists():
with DATA_FILE.open() as data_file:
data = json.load(data_file)
else:
data = {
'equipes': {'rouge': [], 'vert': []},
'cantons': {code_canton: {'capture': None, 'verrouille': False} for code_canton in CANTONS.keys()},
'defis': {
'mains': {'rouge': [], 'vert': []},
'tires_capture': [],
'tires_competition': [],
}
}
with DATA_FILE.open('w') as data_file:
json.dump(data, data_file, indent=2)
DEFIS_FILE = Path(__file__).parent / "defis.json"
with DEFIS_FILE.open() as defis_file:
DEFIS = json.load(defis_file)
def generer_carte():
doc = minidom.parse("map_blank.svg")
for code_canton, data_canton in data['cantons'].items():
if data_canton['capture']:
path = next(e for e in doc.getElementsByTagName('path') if e.getAttribute('id') == code_canton)
couleur = data_canton['capture']
if data_canton['verrouille']:
path.setAttribute('fill', f"url(#verrouille-{couleur})")
else:
path.setAttribute('class', f"capture-{couleur}")
with open('map.svg', 'w') as f:
doc.writexml(f)
cairosvg.svg2png(url='map.svg', write_to='map.png')
@bot.command()
async def carte(ctx: commands.Context):
rouges = list(canton_code for canton_code, canton in data['cantons'].items()
if canton['capture'] == "rouge")
rouges_verrouilles = list(canton_code for canton_code, canton in data['cantons'].items()
if canton['capture'] == "rouge" and canton['verrouille'])
noms_rouges = ", ".join(code_canton + (":lock:" if code_canton in rouges_verrouilles else "") for code_canton in rouges)
verts = list(canton_code for canton_code, canton in data['cantons'].items()
if canton['capture'] == "vert")
verts_verrouilles = list(canton_code for canton_code, canton in data['cantons'].items()
if canton['capture'] == "vert" and canton['verrouille'])
noms_verts = ", ".join(code_canton + (":lock:" if code_canton in verts_verrouilles else "") for code_canton in verts)
libres = list(canton_code for canton_code, canton in data['cantons'].items()
if canton['capture'] is None)
message = f""":red_circle: Équipe rouge : **{len(rouges)} canton{"s" if len(rouges) > 1 else ""}** (dont **{len(rouges_verrouilles)} verrouillé{"s" if len(rouges_verrouilles) > 1 else ""}**) : {noms_rouges}
:green_circle: Équipe verte : **{len(verts)} canton{"s" if len(verts) > 1 else ""}** (dont **{len(verts_verrouilles)} verrouillé{"s" if len(verts_verrouilles) > 1 else ""}**) : {noms_verts}
:white_circle: **{len(libres)} canton{"s" if len(libres) > 1 else ""}** libre{"s" if len(libres) > 1 else ""} : {", ".join(libres)}"""
generer_carte()
with open('map.png', 'rb') as f:
await ctx.send(message, file=discord.File(f, filename="battle4suisse.png"))
@bot.command()
async def capturer(ctx: commands.Context, canton: CodeCanton, *, couleur: Couleur | None = None):
if couleur is None:
author_id = 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]`.")
data['cantons'][canton]['capture'] = couleur
with DATA_FILE.open('w') as data_file:
json.dump(data, data_file, indent=2)
await ctx.send(f"@everyone L'équipe {couleur} a capturé le canton de **{CANTONS[canton]}** !")
return await carte(ctx)
@capturer.error
async def capture_error(ctx, error):
if isinstance(error, commands.BadLiteralArgument):
await ctx.send(f"Canton inconnu : {error.argument}, valeurs possibles : {", ".join(error.literals)}")
else:
await ctx.send(str(error))
@bot.command()
async def verrouiller(ctx: commands.Context, canton: CodeCanton, *, couleur: Couleur | None = None):
if couleur is None:
author_id = 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]`.")
data['cantons'][canton]['capture'] = couleur
data['cantons'][canton]['verrouille'] = True
with DATA_FILE.open('w') as data_file:
json.dump(data, data_file, indent=2)
generer_carte()
await ctx.send(f"@everyone L'équipe {couleur} a capturé le canton de **{CANTONS[canton]}** !")
return await carte(ctx)
@bot.command()
async def reset(ctx: commands.Context, canton: CodeCanton):
data['cantons'][canton]['capture'] = None
data['cantons'][canton]['verrouille'] = False
with DATA_FILE.open('w') as data_file:
json.dump(data, data_file, indent=2)
generer_carte()
return await carte(ctx)
@bot.command()
async def equipe(ctx: commands.Context, couleur: Couleur):
author_id = ctx.author.id
for membres_equipe in data['equipes'].values():
if author_id in membres_equipe:
membres_equipe.remove(author_id)
data['equipes'][couleur].append(author_id)
with DATA_FILE.open('w') as data_file:
json.dump(data, data_file, indent=2)
await ctx.send(f"Équipe {couleur} rejointe")
@bot.command()
async def defis(ctx: commands.Context, *, type_defi: Literal['capture', 'competition'] = "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"):
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)
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']:
raise commands.BadArgument("Les mains sont déjà initialisées")
defis_libres = copy.deepcopy(DEFIS['capture'])
for equipe in ('rouge', 'vert'):
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'])
for member_id in data['equipes'][equipe]:
await 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: 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]`.")
main = data['defis']['mains'][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.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)
@bot.command()
async def debug(ctx: commands.Context, key: Literal['equipes', 'cantons', 'defis']):
data_json = json.dumps(data[key], indent=4)
await ctx.send(f"```json\n{data_json}\n```")
@carte.error
@reset.error
@equipe.error
@defis.error
@description.error
@tirage.error
@main.error
@debug.error
async def on_error(ctx, error):
await ctx.send(str(error))
bot.run(DISCORD_TOKEN)