import sys import codecs import datetime import logging import pkg_resources import traceback from pkg_resources import DistributionNotFound import discord from discord.ext import commands from . import __version__ from .data_manager import storage_type from .utils.chat_formatting import inline, bordered from .rpc import initialize from colorama import Fore, Style, init log = logging.getLogger("red") sentry_log = logging.getLogger("red.sentry") init() INTRO = """ ______ _ ______ _ _ ______ _ | ___ \ | | | _ (_) | | | ___ \ | | | |_/ /___ __| | ______ | | | |_ ___ ___ ___ _ __ __| | | |_/ / ___ | |_ | // _ \/ _` | |______| | | | | / __|/ __/ _ \| '__/ _` | | ___ \/ _ \| __| | |\ \ __/ (_| | | |/ /| \__ \ (_| (_) | | | (_| | | |_/ / (_) | |_ \_| \_\___|\__,_| |___/ |_|___/\___\___/|_| \__,_| \____/ \___/ \__| """ def init_events(bot, cli_flags): @bot.event async def on_connect(): if bot.uptime is None: print("Connected to Discord. Getting ready...") @bot.event async def on_ready(): if bot.uptime is not None: return bot.uptime = datetime.datetime.utcnow() if cli_flags.no_cogs is False: print("Loading packages...") failed = [] packages = await bot.db.packages() for package in packages: try: spec = await bot.cog_mgr.find_cog(package) await bot.load_extension(spec) except Exception as e: log.exception("Failed to load package {}".format(package), exc_info=e) await bot.remove_loaded_package(package) if packages: print("Loaded packages: " + ", ".join(packages)) guilds = len(bot.guilds) users = len(set([m for m in bot.get_all_members()])) try: data = await bot.application_info() invite_url = discord.utils.oauth_url(data.id) except: if bot.user.bot: invite_url = "Could not fetch invite url" else: invite_url = None prefixes = await bot.db.prefix() lang = await bot.db.locale() red_version = __version__ red_pkg = pkg_resources.get_distribution("Red-DiscordBot") dpy_version = discord.__version__ INFO = [str(bot.user), "Prefixes: {}".format(', '.join(prefixes)), 'Language: {}'.format(lang), "Red Bot Version: {}".format(red_version), "Discord.py Version: {}".format(dpy_version), "Shards: {}".format(bot.shard_count)] if guilds: INFO.extend(("Servers: {}".format(guilds), "Users: {}".format(users))) else: print("Ready. I'm not in any server yet!") INFO.append('{} cogs with {} commands'.format(len(bot.cogs), len(bot.commands))) INFO2 = [] sentry = await bot.db.enable_sentry() mongo_enabled = storage_type() != "JSON" reqs_installed = { "voice": None, "docs": None, "test": None } for key in reqs_installed.keys(): reqs = [x.name for x in red_pkg._dep_map[key]] try: pkg_resources.require(reqs) except DistributionNotFound: reqs_installed[key] = False else: reqs_installed[key] = True options = ( ("Error Reporting", sentry), ("MongoDB", mongo_enabled), ("Voice", reqs_installed["voice"]), ("Docs", reqs_installed["docs"]), ("Tests", reqs_installed["test"]) ) on_symbol, off_symbol, ascii_border = _get_startup_screen_specs() for option, enabled in options: enabled = on_symbol if enabled else off_symbol INFO2.append("{} {}".format(enabled, option)) print(Fore.RED + INTRO) print(Style.RESET_ALL) print(bordered(INFO, INFO2, ascii_border=ascii_border)) if invite_url: print("\nInvite URL: {}\n".format(invite_url)) if bot.rpc_enabled: await initialize(bot) @bot.event async def on_error(event_method, *args, **kwargs): sentry_log.exception("Exception in {}".format(event_method)) @bot.event async def on_command_error(ctx, error): if isinstance(error, commands.MissingRequiredArgument): await ctx.send_help() elif isinstance(error, commands.BadArgument): await ctx.send_help() elif isinstance(error, commands.DisabledCommand): await ctx.send("That command is disabled.") elif isinstance(error, commands.CommandInvokeError): # Need to test if the following still works """ no_dms = "Cannot send messages to this user" is_help_cmd = ctx.command.qualified_name == "help" is_forbidden = isinstance(error.original, discord.Forbidden) if is_help_cmd and is_forbidden and error.original.text == no_dms: msg = ("I couldn't send the help message to you in DM. Either" " you blocked me or you disabled DMs in this server.") await ctx.send(msg) return """ log.exception("Exception in command '{}'" "".format(ctx.command.qualified_name), exc_info=error.original) sentry_log.exception("Exception in command '{}'" "".format(ctx.command.qualified_name), exc_info=error.original) message = ("Error in command '{}'. Check your console or " "logs for details." "".format(ctx.command.qualified_name)) exception_log = ("Exception in command '{}'\n" "".format(ctx.command.qualified_name)) exception_log += "".join(traceback.format_exception(type(error), error, error.__traceback__)) bot._last_exception = exception_log if not hasattr(ctx.cog, "_{0.command.cog_name}__error".format(ctx)): await ctx.send(inline(message)) elif isinstance(error, commands.CommandNotFound): pass elif isinstance(error, commands.CheckFailure): await ctx.send("⛔ You are not authorized to issue that command.") elif isinstance(error, commands.NoPrivateMessage): await ctx.send("That command is not available in DMs.") elif isinstance(error, commands.CommandOnCooldown): await ctx.send("This command is on cooldown. " "Try again in {:.2f}s" "".format(error.retry_after)) else: log.exception(type(error).__name__, exc_info=error) try: sentry_error = error.original except AttributeError: sentry_error = error sentry_log.exception("Unhandled command error.", exc_info=sentry_error) @bot.event async def on_message(message): bot.counter["messages_read"] += 1 await bot.process_commands(message) @bot.event async def on_resumed(): bot.counter["sessions_resumed"] += 1 @bot.event async def on_command(command): bot.counter["processed_commands"] += 1 def _get_startup_screen_specs(): """Get specs for displaying the startup screen on stdout. This is so we don't get encoding errors when trying to print unicode emojis to stdout (particularly with Windows Command Prompt). Returns ------- `tuple` Tuple in the form (`str`, `str`, `bool`) containing (in order) the on symbol, off symbol and whether or not the border should be pure ascii. """ encoder = codecs.getencoder(sys.stdout.encoding) check_mark = "\N{SQUARE ROOT}" try: encoder(check_mark) except UnicodeEncodeError: on_symbol = "[X]" off_symbol = "[ ]" else: on_symbol = check_mark off_symbol = "X" try: encoder('┌┐└┘─│') # border symbols except UnicodeEncodeError: ascii_border = True else: ascii_border = False return on_symbol, off_symbol, ascii_border