Improved error handling for MySQL connection issues and added support to PM users on certain errors

doc_update
Joey Hines 2018-08-19 11:37:59 -05:00
parent f801016a5b
commit 7532912d21
3 changed files with 43 additions and 26 deletions

View File

@ -7,6 +7,7 @@ def create_config(config):
'Status': '', 'Status': '',
'Prefix': '?', 'Prefix': '?',
'Bot_Mod': '' 'Bot_Mod': ''
'Error_Users: '''
} }
config['SQL'] = {'Dialect+Driver': 'mysql+mysqldb', config['SQL'] = {'Dialect+Driver': 'mysql+mysqldb',
'Username': '', 'Username': '',
@ -51,6 +52,7 @@ class Config:
self.prefix = self.config['Discord']['Prefix'] self.prefix = self.config['Discord']['Prefix']
self.dynmap_url = self.config['Minecraft']['Dynmap_Url'] self.dynmap_url = self.config['Minecraft']['Dynmap_Url']
self.bot_mod = self.config['Discord']['Bot_Mod'].split(',') self.bot_mod = self.config['Discord']['Bot_Mod'].split(',')
self.error_users = self.config['Discord']['Error_Users'].split(',')
self.count = int(self.config['Logging']['Count']) self.count = int(self.config['Logging']['Count'])
self.rotation_duration = int(self.config['Logging']['Rotation_Duration']) self.rotation_duration = int(self.config['Logging']['Rotation_Duration'])
self.special_name_list = dict(self.config.items('Special Names')) self.special_name_list = dict(self.config.items('Special Names'))

38
bot.py
View File

@ -4,15 +4,14 @@ import logging
from discord import Game from discord import Game
from discord.ext import commands from discord.ext import commands
from discord.utils import oauth_url from discord.utils import oauth_url
from sqlalchemy.exc import OperationalError
from BotConfig import * from BotConfig import bot_config
from BotErrors import * from BotErrors import *
from Commands import Commands from Commands import Commands
from DatabaseModels import Player from DatabaseModels import Player
from MinecraftAccountInfoGrabber import * from MinecraftAccountInfoGrabber import *
from pymysql.err import OperationalError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
description = ''' description = '''
@ -27,11 +26,7 @@ If have a suggestion or if something is borked, you can PM my ding dong of a cre
''' '''
bad_error_message = 'OOPSIE WOOPSIE!! Uwu We made a fucky wucky!! A wittle fucko boingo! The admins at our ' \ bad_error_message = 'OOPSIE WOOPSIE!! Uwu We made a fucky wucky!! A wittle fucko boingo! The admins at our ' \
'headquarters are working VEWY HAWD to fix this! (Error in command {}: {})' 'headquarters are working VEWY HAWD to fix this! (Error in command {})'
bot = commands.Bot(command_prefix=bot_config.prefix, description=description, case_insensitive=True)
bot_commands = Commands()
extensions = ['cogs.Add_Commands', extensions = ['cogs.Add_Commands',
'cogs.Delete_Commands', 'cogs.Delete_Commands',
@ -39,6 +34,13 @@ extensions = ['cogs.Add_Commands',
'cogs.Search_Commands', 'cogs.Search_Commands',
'cogs.Admin_Commands'] 'cogs.Admin_Commands']
bot = commands.Bot(command_prefix=bot_config.prefix, description=description, case_insensitive=True)
try:
bot_commands = Commands()
except OperationalError:
logger.info('Could not connect to MySQL server.')
@bot.event @bot.event
async def on_ready(): async def on_ready():
@ -54,7 +56,7 @@ async def on_command(command, ctx):
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
subcommand = "" subcommand = ""
else: else:
subcommand = ":"+ctx.invoked_subcommand subcommand = ":" + ctx.invoked_subcommand
logger.info("User %s, used command %s%s with context: %s", ctx.message.author, command, subcommand, ctx.args) logger.info("User %s, used command %s%s with context: %s", ctx.message.author, command, subcommand, ctx.args)
@ -69,7 +71,7 @@ async def on_command_error(error, ctx):
elif isinstance(error, commands.CommandOnCooldown): elif isinstance(error, commands.CommandOnCooldown):
return return
elif isinstance(error, commands.UserInputError): elif isinstance(error, commands.UserInputError):
error_str = 'Invalid syntax for **{}** you ding dong:'\ error_str = 'Invalid syntax for **{}** you ding dong:' \
.format(ctx.invoked_with, ctx.invoked_with) .format(ctx.invoked_with, ctx.invoked_with)
pages = bot.formatter.format_help_for(ctx, ctx.command) pages = bot.formatter.format_help_for(ctx, ctx.command)
@ -84,23 +86,31 @@ async def on_command_error(error, ctx):
elif isinstance(error.original, PlayerNotFound): elif isinstance(error.original, PlayerNotFound):
error_str = 'Make sure to use ?register first you ding dong.' error_str = 'Make sure to use ?register first you ding dong.'
elif isinstance(error.original, EntryNameNotUniqueError): elif isinstance(error.original, EntryNameNotUniqueError):
error_str = 'An entry in the database already has that name ding dong.' error_str = 'An entry in the database already has that name you ding dong.'
elif isinstance(error.original, DatabaseValueError): elif isinstance(error.original, DatabaseValueError):
error_str = 'Use a shorter name or a smaller value, dong ding.' error_str = 'Use a shorter name or a smaller value, dong ding.'
elif isinstance(error.original, NotOnServerError): elif isinstance(error.original, NotOnServerError):
error_str = 'Command needs to be run on 24CC. Run this command there whoever you are.'.format() error_str = 'Command needs to be run on 24CC. Run this command there whoever you are.'.format()
elif isinstance(error.original, OperationalError): elif isinstance(error.original, OperationalError):
await send_error_message('Error connecting to the MySQL server, is it offline?')
error_str = 'Database connection issue, looks like some admin has to fix something.'.format() error_str = 'Database connection issue, looks like some admin has to fix something.'.format()
else: else:
await send_error_message('Geoffrey encountered unhandled exception: {}. Context:'.format(error, ctx.args))
logger.error("Geoffrey encountered unhandled exception: %s", error) logger.error("Geoffrey encountered unhandled exception: %s", error)
error_str = bad_error_message.format(ctx.invoked_with, error) error_str = bad_error_message.format(ctx.invoked_with)
await bot.send_message(ctx.message.channel, '{} **Error Running Command:** {}'.format(ctx.message.author.mention, await bot.send_message(ctx.message.channel, '{} **Error Running Command:** {}'.format(ctx.message.author.mention,
error_str)) error_str))
async def send_error_message(msg):
for user_id in bot_config.error_users:
user = await bot.get_user_info(user_id)
await bot.send_message(user, msg)
async def username_update(): async def username_update():
session = None
await bot.wait_until_ready() await bot.wait_until_ready()
while not bot.is_closed: while not bot.is_closed:
session = bot_commands.interface.database.Session() session = bot_commands.interface.database.Session()
@ -118,6 +128,7 @@ async def username_update():
logger.info("Username lookup error.") logger.info("Username lookup error.")
session.rollback() session.rollback()
except OperationalError: except OperationalError:
await send_error_message('Error connecting to the MySQL server, is it offline?')
logger.info("MySQL connection error") logger.info("MySQL connection error")
finally: finally:
session.close() session.close()
@ -126,6 +137,7 @@ async def username_update():
def start_bot(): def start_bot():
try: try:
Commands()
for extension in extensions: for extension in extensions:
try: try:
bot.load_extension(extension) bot.load_extension(extension)

View File

@ -15,7 +15,7 @@ class Search_Commands:
@commands.command(pass_context=True) @commands.command(pass_context=True)
@commands.cooldown(5, 60, commands.BucketType.user) @commands.cooldown(5, 60, commands.BucketType.user)
async def find(self, ctx, * args): async def find(self, ctx, *args):
""" """
Finds all the locations and tunnels matching the search term Finds all the locations and tunnels matching the search term
?find [Search] ?find [Search]
@ -28,10 +28,11 @@ class Search_Commands:
result = bot_commands.find(search) result = bot_commands.find(search)
await self.bot.say('{}, The following entries match **{}**:\n{}'.format(ctx.message.author.mention, search, result)) await self.bot.say(
'{}, The following entries match **{}**:\n{}'.format(ctx.message.author.mention, search, result))
except LocationLookUpError: except LocationLookUpError:
await self.bot.say('{}, no matches to **{}** were found in the database.'.format(ctx.message.author.mention, search)) await self.bot.say(
'{}, no matches to **{}** were found in the database.'.format(ctx.message.author.mention, search))
@commands.command(pass_context=True) @commands.command(pass_context=True)
@commands.cooldown(5, 60, commands.BucketType.user) @commands.cooldown(5, 60, commands.BucketType.user)
@ -43,15 +44,15 @@ class Search_Commands:
try: try:
result = bot_commands.tunnel(player) result = bot_commands.tunnel(player)
await self.bot.say('{}, **{}** owns the following tunnels: \n{}'.format(ctx.message.author.mention, player, result)) await self.bot.say(
'{}, **{}** owns the following tunnels: \n{}'.format(ctx.message.author.mention, player, result))
except LocationLookUpError: except LocationLookUpError:
await self.bot.say('{}, no tunnels for **{}** were found in the database.' await self.bot.say('{}, no tunnels for **{}** were found in the database.'
.format(ctx.message.author.mention, player)) .format(ctx.message.author.mention, player))
@commands.command(pass_context=True) @commands.command(pass_context=True)
@commands.cooldown(5, 60, commands.BucketType.user) @commands.cooldown(5, 60, commands.BucketType.user)
async def find_around(self, ctx, x_pos: int, z_pos: int, * args): async def find_around(self, ctx, x_pos: int, z_pos: int, *args):
""" """
Finds all the locations around a certain point. Finds all the locations around a certain point.
The radius defaults to 200 blocks if no value is given. The radius defaults to 200 blocks if no value is given.
@ -84,10 +85,11 @@ class Search_Commands:
ctx.message.author.mention, radius, base_string)) ctx.message.author.mention, radius, base_string))
else: else:
await self.bot.say('{}, there are no locations within {} blocks of that point' await self.bot.say('{}, there are no locations within {} blocks of that point'
.format(ctx.message.author.mention, radius)) .format(ctx.message.author.mention, radius))
except ValueError: except ValueError:
await self.bot.say('{}, invalid radius, the radius must be a whole number.'.format(ctx.message.author.mention, await self.bot.say(
radius)) '{}, invalid radius, the radius must be a whole number.'.format(ctx.message.author.mention,
radius))
except InvalidDimError: except InvalidDimError:
await self.bot.say('{}, {} is an invalid dimension.'.format(ctx.message.author.mention, dimension)) await self.bot.say('{}, {} is an invalid dimension.'.format(ctx.message.author.mention, dimension))
@ -100,13 +102,14 @@ class Search_Commands:
""" """
try: try:
result = bot_commands.selling(item_name) result = bot_commands.selling(item_name)
await self.bot.say('{}, the following shops sell **{}**: \n{}'.format(ctx.message.author.mention, item_name, result)) await self.bot.say(
'{}, the following shops sell **{}**: \n{}'.format(ctx.message.author.mention, item_name, result))
except ItemNotFound: except ItemNotFound:
await self.bot.say('{}, no shop sells **{}**.'.format(ctx.message.author.mention, item_name)) await self.bot.say('{}, no shop sells **{}**.'.format(ctx.message.author.mention, item_name))
@commands.command(pass_context=True) @commands.command(pass_context=True)
@commands.cooldown(5, 60, commands.BucketType.user) @commands.cooldown(5, 60, commands.BucketType.user)
async def info(self, ctx, * args): async def info(self, ctx, *args):
""" """
Displays info about a location. Displays info about a location.
If the location is a shop, it displays the shop's inventory. If the location is a shop, it displays the shop's inventory.