Added GameFiles classes skeletons
This commit is contained in:
parent
807728e82a
commit
3673a10669
|
|
@ -0,0 +1,22 @@
|
||||||
|
from typing import Dict, Callable
|
||||||
|
|
||||||
|
|
||||||
|
class Game:
|
||||||
|
"""
|
||||||
|
Game state for one guild
|
||||||
|
"""
|
||||||
|
def __init__(self, config_dict: Dict, save_function: Callable):
|
||||||
|
self.config = config_dict
|
||||||
|
self.save_function = save_function
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def new_dict():
|
||||||
|
return {
|
||||||
|
"game_started": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
def is_started(self):
|
||||||
|
return self.config["game_started"]
|
||||||
|
|
||||||
|
async def start(self):
|
||||||
|
raise NotImplementedError("Start game")
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
import atexit
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Union, Dict, Callable, Tuple
|
||||||
|
import discord
|
||||||
|
from .Game import Game
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Functions used to convert between configuration versions, in a dictionary of: old_version -> converter
|
||||||
|
# Each converter takes the old configuration and returns the converted configuration and the new version
|
||||||
|
config_version_converters: Dict[Union[None, str], Callable[[Dict], Tuple[Dict, str]]] = {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class GamesFile:
|
||||||
|
"""
|
||||||
|
Wrapper around the configuration file for all games of all guilds
|
||||||
|
"""
|
||||||
|
version = "1.0"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def empty_config() -> Dict:
|
||||||
|
"""
|
||||||
|
:return: An empty configuration, used to initialise the configuration file
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"__version__": GamesFile.version,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, games_file_path: Union[str, Path]):
|
||||||
|
self.file_obj = None # File descriptor to the on-disk config file
|
||||||
|
self.config = None # In-memory configuration
|
||||||
|
|
||||||
|
# Loading configuration from disk
|
||||||
|
logger.debug("Loading games file")
|
||||||
|
games_file_path = Path(games_file_path).absolute()
|
||||||
|
if games_file_path.exists():
|
||||||
|
if not games_file_path.is_file():
|
||||||
|
raise ValueError(f"Games file {games_file_path} exists but is not a regular file")
|
||||||
|
|
||||||
|
self.file_obj = games_file_path.open("r+")
|
||||||
|
|
||||||
|
if games_file_path.stat().st_size == 0: # Config file is empty
|
||||||
|
logger.warning(f"games file {games_file_path} is empty, initialising an empty configuration")
|
||||||
|
self.config = self.empty_config()
|
||||||
|
self.save_to_file() # Initialise the file
|
||||||
|
else: # File is not empty
|
||||||
|
has_been_converted = self.reload_from_disk()
|
||||||
|
if has_been_converted:
|
||||||
|
self.save_to_file()
|
||||||
|
else: # File does not exist
|
||||||
|
self.file_obj = games_file_path.open("w+")
|
||||||
|
self.config = self.empty_config()
|
||||||
|
self.save_to_file() # Initialise the file
|
||||||
|
|
||||||
|
# Verifying that attributes have been initialised properly
|
||||||
|
assert self.file_obj is not None and self.config is not None
|
||||||
|
logger.debug("Games file successfully initialised")
|
||||||
|
|
||||||
|
atexit.register(self.save_to_file)
|
||||||
|
|
||||||
|
def reload_from_disk(self) -> bool:
|
||||||
|
"""
|
||||||
|
Reload the configuration from disk
|
||||||
|
|
||||||
|
:return: Whether the configuration had to be converted to a more recent version because the one on disk was an earlier version
|
||||||
|
:except json.JSONDecodeError: if file is not valid json
|
||||||
|
:except ValueError: if the version of the configuration in the file is not known or can not be converted to a more recent one
|
||||||
|
"""
|
||||||
|
logger.debug("Loading games file from disk")
|
||||||
|
self.file_obj.seek(0) # Moving to beginning of file
|
||||||
|
try:
|
||||||
|
self.config = json.load(self.file_obj)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
logger.critical(f"JSON Error when parsing games file: {e}")
|
||||||
|
raise e
|
||||||
|
# Checking configuration version and converting if needed
|
||||||
|
config_version = self.config["__version__"]
|
||||||
|
has_been_converted = False
|
||||||
|
if config_version != self.version:
|
||||||
|
logger.info(f"Games file is an older version, converting (file version: {config_version}, current: {self.version})...")
|
||||||
|
|
||||||
|
# Performing the conversion
|
||||||
|
while config_version != self.version:
|
||||||
|
if config_version in config_version_converters:
|
||||||
|
logger.debug(f"Converting from {config_version}")
|
||||||
|
self.config, config_version = config_version_converters[config_version](self.config)
|
||||||
|
logger.debug(f"Converted to {config_version}")
|
||||||
|
else:
|
||||||
|
logger.critical(f"Impossible to find converter to convert from {config_version}")
|
||||||
|
raise ValueError(f"Configuration loading: impossible to convert to current version")
|
||||||
|
has_been_converted = True
|
||||||
|
|
||||||
|
assert self.config["__version__"] == self.version
|
||||||
|
|
||||||
|
logger.debug("Loaded games file from disk")
|
||||||
|
return has_been_converted
|
||||||
|
|
||||||
|
def save_to_file(self, indent = 2) -> None:
|
||||||
|
"""
|
||||||
|
Save the configuration to disk
|
||||||
|
:param indent: Indentation to use for pretty-printing json
|
||||||
|
"""
|
||||||
|
logger.debug("Writing games file to disk")
|
||||||
|
self.file_obj.seek(0)
|
||||||
|
self.file_obj.truncate()
|
||||||
|
json.dump(self.config, self.file_obj, indent = indent)
|
||||||
|
self.file_obj.flush()
|
||||||
|
logger.debug("Written games file to disk")
|
||||||
|
|
||||||
|
def __getitem__(self, guild: discord.Guild) -> Game:
|
||||||
|
"""
|
||||||
|
Get the game information for one guild
|
||||||
|
"""
|
||||||
|
guild_id_str = str(guild.id)
|
||||||
|
if guild_id_str not in self.config:
|
||||||
|
self.config[guild_id_str] = Game.new_dict()
|
||||||
|
self.save_to_file()
|
||||||
|
return Game(self.config[guild_id_str], self.save_to_file)
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
from .Game import Game
|
||||||
|
from .GamesFile import GamesFile
|
||||||
26
SecretBot.py
26
SecretBot.py
|
|
@ -8,6 +8,8 @@ import discord
|
||||||
import discord.utils
|
import discord.utils
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
|
from GameFiles import GamesFile
|
||||||
|
from GameFiles import Game
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
logger = logging.getLogger("SecretBot")
|
logger = logging.getLogger("SecretBot")
|
||||||
|
|
@ -15,7 +17,7 @@ logger = logging.getLogger("SecretBot")
|
||||||
|
|
||||||
class SecretBot(commands.Cog):
|
class SecretBot(commands.Cog):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, games_file: GamesFile):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.bot = commands.Bot(
|
self.bot = commands.Bot(
|
||||||
"!",
|
"!",
|
||||||
|
|
@ -24,6 +26,7 @@ class SecretBot(commands.Cog):
|
||||||
allowed_mentions = discord.AllowedMentions.none(), # By default we do not allow mentions, as we will white-list them on each required message
|
allowed_mentions = discord.AllowedMentions.none(), # By default we do not allow mentions, as we will white-list them on each required message
|
||||||
)
|
)
|
||||||
self.bot.add_cog(self)
|
self.bot.add_cog(self)
|
||||||
|
self.games_file = games_file
|
||||||
|
|
||||||
def run(self, token):
|
def run(self, token):
|
||||||
self.bot.run(token)
|
self.bot.run(token)
|
||||||
|
|
@ -74,10 +77,28 @@ class SecretBot(commands.Cog):
|
||||||
raise utils.CheckFailDoNotNotify
|
raise utils.CheckFailDoNotNotify
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
async def check_is_administrator_or_owner(self, ctx: commands.Context):
|
||||||
|
"""
|
||||||
|
Verify if the user has administrator rights or if it is the bot's owner. Notifies the user and raises utils.CheckFailDoNotNotify if not.
|
||||||
|
"""
|
||||||
|
if not (ctx.author.guild_permissions.administrator or await self.bot.is_owner(ctx.author)):
|
||||||
|
await ctx.reply(f":dragon_face: You have no power here!")
|
||||||
|
raise utils.CheckFailDoNotNotify
|
||||||
|
|
||||||
@commands.command(help = "See if I'm alive")
|
@commands.command(help = "See if I'm alive")
|
||||||
async def ping(self, ctx: commands.Context):
|
async def ping(self, ctx: commands.Context):
|
||||||
await ctx.reply(":ping_pong:", mention_author = True)
|
await ctx.reply(":ping_pong:", mention_author = True)
|
||||||
|
|
||||||
|
@commands.command("StartGame")
|
||||||
|
async def start_game(self, ctx: commands.Context):
|
||||||
|
game = self.games_file[ctx.guild]
|
||||||
|
if game.is_started():
|
||||||
|
await ctx.reply(":x: a game is already running")
|
||||||
|
else:
|
||||||
|
await game.start()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
argparser = argparse.ArgumentParser(description = "Secret Hitler helper bot", formatter_class = argparse.ArgumentDefaultsHelpFormatter)
|
argparser = argparse.ArgumentParser(description = "Secret Hitler helper bot", formatter_class = argparse.ArgumentDefaultsHelpFormatter)
|
||||||
|
|
@ -108,5 +129,6 @@ if __name__ == '__main__':
|
||||||
with token_file_path.open("r") as token_file:
|
with token_file_path.open("r") as token_file:
|
||||||
token = token_file.readline().strip()
|
token = token_file.readline().strip()
|
||||||
|
|
||||||
bot = SecretBot()
|
games_file = GamesFile(ARGS.games_file)
|
||||||
|
bot = SecretBot(games_file)
|
||||||
bot.run(token)
|
bot.run(token)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue