2021-11-05 11:12:02 +01:00
|
|
|
from dataclasses import dataclass, field
|
|
|
|
from datetime import datetime
|
|
|
|
from enum import Enum
|
|
|
|
import pickle
|
|
|
|
|
2021-10-26 21:05:45 +02:00
|
|
|
import disnake
|
2021-10-26 23:08:55 +02:00
|
|
|
from disnake import CategoryChannel, PermissionOverwrite, TextChannel
|
2021-10-26 21:05:45 +02:00
|
|
|
from disnake.ext import commands
|
|
|
|
import logging
|
|
|
|
|
|
|
|
from orochi.config import Config
|
|
|
|
|
|
|
|
bot = commands.Bot(command_prefix='!')
|
2021-11-05 11:12:02 +01:00
|
|
|
GAME: "Game"
|
|
|
|
|
|
|
|
|
|
|
|
class Room(Enum):
|
|
|
|
A = 'A'
|
|
|
|
B = 'B'
|
|
|
|
C = 'C'
|
|
|
|
|
|
|
|
|
|
|
|
class Vote(Enum):
|
|
|
|
ALLY = 'A'
|
|
|
|
BETRAY = 'B'
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
class Player:
|
|
|
|
name: str
|
|
|
|
private_channel_id: int = field(hash=False)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def round_votes(self):
|
|
|
|
for r in GAME.rounds:
|
|
|
|
for room in r.rooms:
|
|
|
|
for vote in room.votes:
|
|
|
|
if self in vote.players:
|
|
|
|
yield vote
|
|
|
|
|
|
|
|
@property
|
|
|
|
def score(self):
|
|
|
|
s = 3
|
|
|
|
|
|
|
|
for vote in self.round_votes:
|
|
|
|
room = vote.room
|
|
|
|
other_vote = room.vote1 if room.vote1 is not vote else room.vote2
|
|
|
|
match vote.vote, other_vote.vote:
|
|
|
|
case Vote.ALLY, Vote.ALLY:
|
|
|
|
s += 2
|
|
|
|
case Vote.ALLY, Vote.BETRAY:
|
|
|
|
s -= 2
|
|
|
|
case Vote.BETRAY, Vote.ALLY:
|
|
|
|
s += 3
|
|
|
|
case Vote.BETRAY, Vote.BETRAY:
|
|
|
|
pass
|
|
|
|
|
|
|
|
return s
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class RoundVote:
|
|
|
|
player1: Player
|
|
|
|
player2: Player | None
|
|
|
|
vote: Vote
|
|
|
|
timestamp: datetime
|
|
|
|
|
|
|
|
@property
|
|
|
|
def players(self):
|
|
|
|
return self.player1, self.player2
|
|
|
|
|
|
|
|
@property
|
|
|
|
def room(self):
|
|
|
|
for r in GAME.rounds:
|
|
|
|
for room in r.rooms:
|
|
|
|
if self in room.votes:
|
|
|
|
return room
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class RoundRoom:
|
|
|
|
room: Room
|
|
|
|
vote1: RoundVote
|
|
|
|
vote2: RoundVote
|
|
|
|
|
|
|
|
@property
|
|
|
|
def votes(self):
|
|
|
|
return self.vote1, self.vote2
|
|
|
|
|
|
|
|
@property
|
|
|
|
def round(self):
|
|
|
|
for r in GAME.rounds:
|
|
|
|
if self in r.rooms:
|
|
|
|
return r
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class Round:
|
|
|
|
round: int
|
|
|
|
room_a: RoundRoom
|
|
|
|
room_b: RoundRoom
|
|
|
|
room_c: RoundRoom
|
|
|
|
|
|
|
|
@property
|
|
|
|
def rooms(self):
|
|
|
|
return self.room_a, self.room_b, self.room_c
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class Game:
|
|
|
|
rounds: list[Round] = field(default_factory=list)
|
|
|
|
players: dict[str, Player] = field(default_factory=dict)
|
|
|
|
|
|
|
|
def register_player(self, name: str, vote_channel_id: int) -> Player:
|
|
|
|
player = Player(name, vote_channel_id)
|
|
|
|
self.players[name] = player
|
|
|
|
return player
|
|
|
|
|
|
|
|
def save(self, filename: str) -> None:
|
|
|
|
"""
|
|
|
|
Uses pickle to save the current state of the game.
|
|
|
|
"""
|
|
|
|
with open(filename, 'wb') as f:
|
|
|
|
pickle.dump(self, f)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def load(cls, filename: str) -> "Game | None":
|
|
|
|
"""
|
|
|
|
Reload the game from a saved file.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
with open(filename, 'rb') as f:
|
|
|
|
return pickle.load(f)
|
|
|
|
except FileNotFoundError:
|
|
|
|
return None
|
2021-10-26 21:05:45 +02:00
|
|
|
|
|
|
|
|
|
|
|
@bot.event
|
2021-10-26 23:08:55 +02:00
|
|
|
async def on_ready():
|
2021-11-05 11:12:02 +01:00
|
|
|
global GAME
|
|
|
|
|
2021-10-26 23:08:55 +02:00
|
|
|
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)
|
|
|
|
)
|
|
|
|
|
2021-11-05 11:12:02 +01:00
|
|
|
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')
|
|
|
|
|
2021-10-26 23:08:55 +02:00
|
|
|
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)
|
|
|
|
)
|
|
|
|
|
2021-11-08 14:01:43 +01:00
|
|
|
if not config.brother_channel:
|
|
|
|
channel: TextChannel = await secret_category.create_text_channel("doliprane")
|
|
|
|
config.brother_channel = channel.id
|
2021-10-26 23:08:55 +02:00
|
|
|
config.save()
|
|
|
|
|
2021-11-08 14:01:43 +01:00
|
|
|
brother_channel: TextChannel = await guild.fetch_channel(config.brother_channel)
|
|
|
|
if not brother_channel:
|
|
|
|
config.brother_channel = None
|
2021-10-26 23:08:55 +02:00
|
|
|
return await on_ready()
|
|
|
|
|
2021-11-08 14:01:43 +01:00
|
|
|
await brother_channel.edit(name="doliprane", category=secret_category, position=1,
|
|
|
|
topic="Des voix dans la tête ...")
|
|
|
|
await brother_channel.set_permissions(
|
2021-10-26 23:08:55 +02:00
|
|
|
guild.default_role, overwrite=PermissionOverwrite(read_message_history=False, read_messages=False)
|
|
|
|
)
|
2021-11-08 14:01:43 +01:00
|
|
|
await brother_channel.set_permissions(
|
2021-10-26 23:08:55 +02:00
|
|
|
philia, overwrite=PermissionOverwrite(read_message_history=True, read_messages=True)
|
|
|
|
)
|
|
|
|
|
2021-11-08 14:01:43 +01:00
|
|
|
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()
|
|
|
|
|
2021-10-26 23:08:55 +02:00
|
|
|
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()
|
2021-10-26 21:05:45 +02:00
|
|
|
|
|
|
|
|
2021-11-08 14:01:43 +01:00
|
|
|
@bot.command(help="Envoyer un message en tant qu'Orochi.")
|
2021-10-26 23:18:53 +02:00
|
|
|
@commands.has_permissions(administrator=True)
|
|
|
|
async def send(ctx: commands.Context, *, message: str):
|
|
|
|
await ctx.message.delete()
|
|
|
|
await ctx.send(message)
|
|
|
|
|
|
|
|
|
2021-11-08 14:01:43 +01:00
|
|
|
@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é.")
|
|
|
|
|
|
|
|
|
2021-10-26 21:05:45 +02:00
|
|
|
@bot.command()
|
|
|
|
async def vote(ctx: commands.Context):
|
|
|
|
view = Confirm()
|
|
|
|
await ctx.message.reply("plop", view=view)
|
|
|
|
await view.wait()
|
|
|
|
|
|
|
|
|
|
|
|
# Define a simple View that gives us a confirmation menu
|
|
|
|
class Confirm(disnake.ui.View):
|
|
|
|
@disnake.ui.button(label="S'allier", style=disnake.ButtonStyle.green)
|
|
|
|
async def confirm(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
|
|
|
|
self.clear_items()
|
|
|
|
await interaction.response.edit_message(content="Vous vous êtes allié.", view=self)
|
|
|
|
self.stop()
|
|
|
|
|
|
|
|
@disnake.ui.button(label="Trahir", style=disnake.ButtonStyle.red)
|
|
|
|
async def cancel(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
|
|
|
|
self.clear_items()
|
|
|
|
await interaction.response.edit_message(content="Vous avez trahi.", view=self)
|
|
|
|
self.stop()
|
|
|
|
|
|
|
|
|
|
|
|
def run():
|
2021-10-26 23:08:55 +02:00
|
|
|
config = Config.load()
|
2021-10-26 21:05:45 +02:00
|
|
|
|
|
|
|
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)
|