Initial commit, based on VocalMaisBot code
This commit is contained in:
commit
807728e82a
|
|
@ -0,0 +1,7 @@
|
||||||
|
/venv/
|
||||||
|
.idea/
|
||||||
|
__pycache__/
|
||||||
|
|
||||||
|
token*
|
||||||
|
games.json*
|
||||||
|
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import discord
|
||||||
|
import discord.utils
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
import utils
|
||||||
|
|
||||||
|
logger = logging.getLogger("SecretBot")
|
||||||
|
|
||||||
|
|
||||||
|
class SecretBot(commands.Cog):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
@commands.command(help = "See if I'm alive")
|
||||||
|
async def ping(self, ctx: commands.Context):
|
||||||
|
await ctx.reply(":ping_pong:", mention_author = True)
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
bot = SecretBot()
|
||||||
|
bot.run(token)
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
discord.py
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
import discord
|
||||||
|
import discord.ext.commands as commands
|
||||||
|
from functools import singledispatch
|
||||||
|
|
||||||
|
|
||||||
|
class CheckFailDoNotNotify(commands.CheckFailure):
|
||||||
|
"""
|
||||||
|
A custom exception that we can raise inside command check functions if the check fails, but the global error handling should not notify the user about the error.
|
||||||
|
For example it can be used if the check function already sent the user a customised error message.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@singledispatch
|
||||||
|
def to_string(obj) -> str:
|
||||||
|
"""
|
||||||
|
Convert the given discord object to a string, for logging purposes.
|
||||||
|
|
||||||
|
See functools.singledispatch
|
||||||
|
"""
|
||||||
|
return str(obj)
|
||||||
|
|
||||||
|
|
||||||
|
@to_string.register(discord.Object)
|
||||||
|
@to_string.register(discord.abc.Snowflake)
|
||||||
|
def _(obj) -> str:
|
||||||
|
return f"<{obj.id}>"
|
||||||
|
|
||||||
|
|
||||||
|
@to_string.register(discord.abc.User)
|
||||||
|
def _(user) -> str:
|
||||||
|
return f"{user.name}#{user.discriminator}({user.id})"
|
||||||
|
|
||||||
|
|
||||||
|
@to_string.register(discord.Guild)
|
||||||
|
def _(guild) -> str:
|
||||||
|
return f"{guild.name}({guild.id})"
|
||||||
|
|
||||||
|
|
||||||
|
# Even though these types are all subclasses of discord.abc.GuildChannel, it does not work if we register that class directly
|
||||||
|
@to_string.register(discord.TextChannel)
|
||||||
|
@to_string.register(discord.VoiceChannel)
|
||||||
|
@to_string.register(discord.CategoryChannel)
|
||||||
|
def _(channel) -> str:
|
||||||
|
return f"{channel.name}({channel.id})"
|
||||||
|
|
||||||
|
|
||||||
|
@to_string.register(discord.Role)
|
||||||
|
def _(role) -> str:
|
||||||
|
return f"@{role.name}({role.id})"
|
||||||
Loading…
Reference in New Issue