orochi-discord/orochi/bot.py

313 lines
12 KiB
Python

import disnake
from disnake import CategoryChannel, PermissionOverwrite, TextChannel
from disnake.ext import commands
import logging
from orochi import http
from orochi.config import Config
from orochi.models import Game, GameState, Player, RoundVote, Vote
bot = commands.Bot(command_prefix='!')
@bot.event
async def on_ready():
config: Config = bot.config
logger = bot.logger
if config.guild is None:
config.save()
logger.error("The guild ID is missing")
exit(1)
guild = await bot.fetch_guild(config.guild)
if not guild:
logger.error("Unknown guild.")
exit(1)
if config.vote_category is None:
category = await guild.create_category("Votes")
config.vote_category = category.id
config.save()
if config.secret_category is None:
category = await guild.create_category("Conversation⋅s secrète⋅s")
config.secret_category = category.id
config.save()
vote_category: CategoryChannel = await guild.fetch_channel(config.vote_category)
if vote_category is None:
config.vote_category = None
return await on_ready()
secret_category: CategoryChannel = await guild.fetch_channel(config.secret_category)
if secret_category is None:
config.secret_category = None
return await on_ready()
await vote_category.set_permissions(
guild.default_role, overwrite=PermissionOverwrite(read_message_history=False, read_messages=False)
)
await secret_category.set_permissions(
guild.default_role, overwrite=PermissionOverwrite(read_message_history=False, read_messages=False)
)
for i, player in enumerate(Config.PLAYERS):
player_id = player.lower()
if player_id not in config.vote_channels:
channel: TextChannel = await vote_category.create_text_channel(player_id)
config.vote_channels[player_id] = channel.id
config.save()
channel: TextChannel = await guild.fetch_channel(config.vote_channels[player_id])
if channel is None:
del config.vote_channels[player_id]
return await on_ready()
await channel.edit(name=player_id, category=vote_category, position=i)
await channel.set_permissions(
guild.default_role, overwrite=PermissionOverwrite(read_message_history=False, read_messages=False)
)
if player_id not in config.player_roles:
role = await guild.create_role(name=player)
config.player_roles[player_id] = role.id
config.save()
guild = await bot.fetch_guild(guild.id) # update roles
role = guild.get_role(config.player_roles[player_id])
if role is None:
del config.player_roles[player_id]
config.save()
return await on_ready()
await channel.set_permissions(
role, overwrite=PermissionOverwrite(read_message_history=True, read_messages=True)
)
game = Game.load('game.save')
if not game:
game = Game()
for player in config.PLAYERS:
game.register_player(player, config.vote_channels[player.lower()])
game.save('game.save')
# Update private channel id if necessary
for player in list(game.players.values()):
if player.private_channel_id != config.vote_channels[player.name.lower()]:
game.register_player(player.name, config.vote_channels[player.name.lower()])
game.save('game.save')
# Setup first round if not exists
if not game.rounds:
game.rounds.append(game.default_first_round())
game.save('game.save')
if not config.telepathy_channel:
channel: TextChannel = await secret_category.create_text_channel("bigbrain")
config.telepathy_channel = channel.id
config.save()
telepathy_channel: TextChannel = await guild.fetch_channel(config.telepathy_channel)
if not telepathy_channel:
config.telepathy_channel = None
return await on_ready()
await telepathy_channel.edit(name="bigbrain", category=secret_category, position=0,
topic="Échanges télépathiques")
await telepathy_channel.set_permissions(
guild.default_role, overwrite=PermissionOverwrite(read_message_history=False, read_messages=False)
)
delphine = guild.get_role(config.player_roles['delphine'])
philia = guild.get_role(config.player_roles['philia'])
await telepathy_channel.set_permissions(
delphine, overwrite=PermissionOverwrite(read_message_history=True, read_messages=True)
)
await telepathy_channel.set_permissions(
philia, overwrite=PermissionOverwrite(read_message_history=True, read_messages=True)
)
if not config.brother_channel:
channel: TextChannel = await secret_category.create_text_channel("doliprane")
config.brother_channel = channel.id
config.save()
brother_channel: TextChannel = await guild.fetch_channel(config.brother_channel)
if not brother_channel:
config.brother_channel = None
return await on_ready()
await brother_channel.edit(name="doliprane", category=secret_category, position=1,
topic="Des voix dans la tête ...")
await brother_channel.set_permissions(
guild.default_role, overwrite=PermissionOverwrite(read_message_history=False, read_messages=False)
)
await brother_channel.set_permissions(
philia, overwrite=PermissionOverwrite(read_message_history=True, read_messages=True)
)
brother_channel_webhook = None
if config.brother_channel_webhook is not None:
try:
brother_channel_webhook = await bot.fetch_webhook(config.brother_channel_webhook)
except disnake.HTTPException | disnake.NotFound | disnake.Forbidden:
pass
if brother_channel_webhook is None:
brother_channel_webhook = await brother_channel.create_webhook(name="???")
config.brother_channel_webhook = brother_channel_webhook.id
config.save()
if not config.backdoor_channel:
channel: TextChannel = await secret_category.create_text_channel("backdoor")
config.backdoor_channel = channel.id
config.save()
backdoor_channel: TextChannel = await guild.fetch_channel(config.backdoor_channel)
if not backdoor_channel:
config.backdoor_channel = None
return await on_ready()
await backdoor_channel.edit(name="backdoor", category=secret_category, position=2,
topic="Panel d'administrati0n du jeu")
await backdoor_channel.set_permissions(
guild.default_role, overwrite=PermissionOverwrite(read_message_history=False, read_messages=False)
)
dan = guild.get_role(config.player_roles['dan'])
await backdoor_channel.set_permissions(
dan, overwrite=PermissionOverwrite(read_message_history=True, read_messages=True)
)
config.save()
@bot.command(help="Sauvegarde la partie")
@commands.has_permissions(administrator=True)
async def save(ctx: commands.Context):
Game.INSTANCE.save('game.save')
await ctx.reply("La partie a été sauvegardée.")
@bot.command(help="Recharger la partie")
@commands.has_permissions(administrator=True)
async def load(ctx: commands.Context):
Game.load('game.save')
await ctx.reply("La partie a été rechargée.")
@bot.command(help="Envoyer un message en tant qu'Orochi.")
@commands.has_permissions(administrator=True)
async def send(ctx: commands.Context, *, message: str):
await ctx.message.delete()
await ctx.send(message)
@bot.command(help="Envoyer un message à Philia par la pensée en tant que Brother.")
@commands.has_permissions(administrator=True)
async def brother(ctx: commands.Context, *, message: str):
webhook = await bot.fetch_webhook(bot.config.brother_channel_webhook)
await webhook.send(message)
await ctx.message.reply("Message envoyé.")
@bot.command(help="Ouvrir les votes")
async def open(ctx: commands.Context):
game: Game = Game.INSTANCE
current_round = game.rounds[-1]
if game.state == GameState.VOTING:
await ctx.reply("Les votes sont déjà ouverts.")
return
elif game.state == GameState.RESULTS:
await ctx.reply("Les votes viennent d'être fermés, merci de démarrer un nouveau tour avec !prepare.")
return
# Ensure that each room is configured
for room in current_round.rooms:
if room is None:
await ctx.reply("Les salles ne sont pas configurées.")
if len(list(room.players)) != 3:
await ctx.reply(f"La salle {room.room.value} ne contient pas trois joueurs, merci de la reconfigurer.")
# Send messages to players
for room in current_round.rooms:
votes = list(room.votes)
for i, vote in enumerate(votes):
players = list(vote.players)
other_vote = votes[1 - i]
for j, player in enumerate(players):
other_player = players[1 - j] if len(players) == 2 else None
view = VoteView(timeout=3600)
channel_id = player.private_channel_id
channel = bot.get_channel(channel_id)
message = "Les votes sont ouverts.\n"
message += f"Vous devez aller voter en salle **{room.room.value}**.\n"
if other_player:
message += f"Vous êtes allié⋅e avec **{other_player.name}**.\n"
message += f"Vous affrontez {' et '.join(f'**{adv.name}**' for adv in other_vote.players)}.\n"
message += "Bonne chance !"
await channel.send(message)
await channel.send("Pour voter, utilisez l'un des boutons ci-dessous. Vous pouvez appuyer "
"sur le bouton plusieurs fois, mais seul le premier vote sera enregistré.",
view=view)
game.state = GameState.VOTING
await ctx.reply("Les salles de vote sont ouvertes, les joueur⋅se⋅s peuvent désormais voter.")
class VoteView(disnake.ui.View):
@disnake.ui.button(label="S'allier", style=disnake.ButtonStyle.green)
async def confirm(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
await interaction.response.send_message("Votre vote a bien été pris en compte.", ephemeral=True)
self.vote(interaction, Vote.ALLY)
@disnake.ui.button(label="Trahir", style=disnake.ButtonStyle.red)
async def cancel(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
await interaction.response.send_message("Votre vote a bien été pris en compte.", ephemeral=True)
self.vote(interaction, Vote.BETRAY)
def vote(self, interaction: disnake.MessageInteraction, vote: Vote) -> None:
game = Game.INSTANCE
current_round = game.rounds[-1]
current_player: Player | None = None
for player in game.players.values():
if player.private_channel_id == interaction.channel_id:
current_player = player
break
current_vote: RoundVote | None = None
for room in current_round.rooms:
for v in room.votes:
if current_player in v.players:
current_vote = v
break
else:
continue
break
if current_vote.vote is None:
current_vote.vote = vote
game.save('game.save')
def run():
config = Config.load()
http.run_web_server()
logger = logging.getLogger('discord')
logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(filename='../discord.log', encoding='utf-8', mode='w')
handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s'))
logger.addHandler(handler)
bot.config = config
bot.logger = logger
bot.run(config.discord_token)