SecretBot/SecretBot.py

135 lines
5.4 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import logging
import sys
from pathlib import Path
import discord
import discord.utils
from discord.ext import commands
from GameFiles import GamesFile
from GameFiles import Game
import utils
logger = logging.getLogger("SecretBot")
class SecretBot(commands.Cog):
def __init__(self, games_file: GamesFile):
super().__init__()
self.bot = commands.Bot(
"!",
help_command = commands.MinimalHelpCommand(),
intents = discord.Intents(guild_messages = True, guilds = True),
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.games_file = games_file
def run(self, token):
self.bot.run(token)
def get_command_usage_message(self, command: commands.Command, prepend = "Usage: ") -> str:
command_elements = [str(cmd) for cmd in command.parents] + [command.name]
return f"{prepend}`{self.command_prefix}{' '.join(command_elements)} {command.signature}`"
@commands.Cog.listener()
async def on_ready(self):
logger.info("Connected and ready!")
logger.info(f"Logged in as {utils.to_string(self.bot.user)}")
oauth_url = discord.utils.oauth_url(
self.bot.user.id,
discord.Permissions(manage_channels = True, read_messages = True, send_messages = True)
)
logger.info(f"You can invite the bot to your server using the following link: {oauth_url}")
@commands.Cog.listener()
async def on_command_error(self, ctx: commands.Context, error: commands.CommandError):
if isinstance(error, commands.CommandNotFound):
return await ctx.reply(f":x: The requested command does not exist")
elif isinstance(error, commands.MissingRequiredArgument):
return await ctx.reply(f":x: Missing required argument {error.param}. {self.get_command_usage_message(ctx.command)}")
elif isinstance(error, commands.TooManyArguments):
return await ctx.reply(f":x: too many arguments for command {ctx.command}. {self.get_command_usage_message(ctx.command)}")
elif isinstance(error, commands.BadArgument):
if hasattr(error, "argument"):
return await ctx.reply(f":x: I could not correctly understand argument {error.argument}. {self.get_command_usage_message(ctx.command)}")
else:
return await ctx.reply(f":x: I could not correctly understand one of the arguments. {self.get_command_usage_message(ctx.command)}")
elif isinstance(error, commands.ArgumentParsingError):
return await ctx.reply(f":x: Error when parsing the command: {error}")
elif isinstance(error, commands.CheckFailure):
if isinstance(error, utils.CheckFailDoNotNotify): # A check failed, but the check function asked that we do not notify the user
return
else:
return await ctx.reply(f":x: this command can not be run: {error}")
else:
logger.error(f"Error when running command '{ctx.message.content}' by {utils.to_string(ctx.author)} in {utils.to_string(ctx.guild)}", exc_info = error)
await ctx.reply(f":x: There was an error with the command :dizzy_face:")
def cog_check(self, ctx: commands.Context):
# We silently ignore messages from bots, since we do not want bots to command us
if ctx.author.bot or ctx.message.is_system():
logger.info(f"[{utils.to_string(ctx.guild)}] Ignored command message from bot or system: {ctx.message.content}")
raise utils.CheckFailDoNotNotify
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")
async def ping(self, ctx: commands.Context):
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__':
argparser = argparse.ArgumentParser(description = "Secret Hitler helper bot", formatter_class = argparse.ArgumentDefaultsHelpFormatter)
argparser.add_argument("-t", "--token-file", default = "token", help = "File where the discord bot token is stored")
argparser.add_argument("-g", "--games-file", default = "games.json", help = "File used to store game information so that the bot can be stopped safely")
argparser.add_argument("-v", "--verbose", action = "count", default = 0, help = "Specify once to print bot debug messages, specify twice to print discord.py debug messages as well")
ARGS = argparser.parse_args()
logging.basicConfig(
format = "%(asctime)s %(name)s [%(levelname)s] %(message)s"
)
if ARGS.verbose == 0:
logging.root.setLevel(logging.INFO)
elif ARGS.verbose == 1:
logging.root.setLevel(logging.DEBUG)
logging.getLogger("discord").setLevel(logging.INFO)
else:
logging.root.setLevel(logging.DEBUG)
logger.info(f"Using discord.py version {discord.__version__}")
token_file_path = Path(ARGS.token_file).absolute()
if not token_file_path.is_file():
logger.error(f"Token file {token_file_path} does not exist")
sys.exit(1)
with token_file_path.open("r") as token_file:
token = token_file.readline().strip()
games_file = GamesFile(ARGS.games_file)
bot = SecretBot(games_file)
bot.run(token)