From b88b5a2601f56bda985729352d24842f087a8ade Mon Sep 17 00:00:00 2001 From: Michael H Date: Mon, 14 May 2018 15:33:24 -0400 Subject: [PATCH] [V3] Update code standards (black code format pass) (#1650) * ran black: code formatter against `redbot/` with `-l 99` * badge --- README.rst | 5 + redbot/__init__.py | 8 +- redbot/__main__.py | 51 +- redbot/cogs/admin/admin.py | 91 +- redbot/cogs/admin/announcer.py | 10 +- redbot/cogs/admin/converters.py | 5 +- redbot/cogs/admin/locales/regen_messages.py | 8 +- redbot/cogs/alias/alias.py | 181 ++- redbot/cogs/alias/alias_entry.py | 13 +- redbot/cogs/alias/locales/regen_messages.py | 10 +- redbot/cogs/audio/__init__.py | 9 +- redbot/cogs/audio/audio.py | 1178 ++++++++++------- redbot/cogs/audio/locales/regen_messages.py | 10 +- redbot/cogs/audio/manager.py | 33 +- redbot/cogs/bank/bank.py | 28 +- redbot/cogs/bank/errors.py | 3 +- redbot/cogs/bank/locales/regen_messages.py | 10 +- redbot/cogs/cleanup/cleanup.py | 151 ++- redbot/cogs/cleanup/locales/regen_messages.py | 10 +- redbot/cogs/customcom/customcom.py | 205 ++- .../cogs/customcom/locales/regen_messages.py | 10 +- redbot/cogs/dataconverter/core_specs.py | 111 +- redbot/cogs/dataconverter/dataconverter.py | 34 +- .../dataconverter/locales/regen_messages.py | 8 +- redbot/cogs/downloader/checks.py | 13 +- redbot/cogs/downloader/converters.py | 5 +- redbot/cogs/downloader/downloader.py | 82 +- redbot/cogs/downloader/errors.py | 16 +- redbot/cogs/downloader/installable.py | 29 +- redbot/cogs/downloader/json_mixins.py | 4 +- .../cogs/downloader/locales/regen_messages.py | 10 +- redbot/cogs/downloader/log.py | 2 +- redbot/cogs/downloader/repo_manager.py | 170 +-- redbot/cogs/economy/economy.py | 273 ++-- redbot/cogs/economy/locales/regen_messages.py | 10 +- redbot/cogs/filter/filter.py | 66 +- redbot/cogs/filter/locales/regen_messages.py | 10 +- redbot/cogs/general/general.py | 152 +-- redbot/cogs/general/locales/regen_messages.py | 10 +- redbot/cogs/image/image.py | 28 +- redbot/cogs/image/locales/regen_messages.py | 10 +- redbot/cogs/mod/checks.py | 6 + redbot/cogs/mod/locales/regen_messages.py | 10 +- redbot/cogs/mod/mod.py | 564 +++++--- redbot/cogs/modlog/locales/regen_messages.py | 10 +- redbot/cogs/modlog/modlog.py | 33 +- redbot/cogs/reports/reports.py | 156 +-- redbot/cogs/streams/errors.py | 2 +- redbot/cogs/streams/locales/regen_messages.py | 10 +- redbot/cogs/streams/streams.py | 202 ++- redbot/cogs/streams/streamtypes.py | 127 +- redbot/cogs/trivia/locales/regen_messages.py | 10 +- redbot/cogs/trivia/session.py | 51 +- redbot/cogs/trivia/trivia.py | 196 +-- redbot/cogs/warnings/helpers.py | 55 +- .../cogs/warnings/locales/regen_messages.py | 9 +- redbot/cogs/warnings/warnings.py | 72 +- redbot/core/__init__.py | 15 +- redbot/core/bank.py | 81 +- redbot/core/bot.py | 71 +- redbot/core/checks.py | 46 +- redbot/core/cli.py | 164 ++- redbot/core/cog_manager.py | 55 +- redbot/core/commands/commands.py | 5 +- redbot/core/commands/context.py | 23 +- redbot/core/config.py | 113 +- redbot/core/core_commands.py | 314 ++--- redbot/core/data_manager.py | 88 +- redbot/core/dev_commands.py | 105 +- redbot/core/drivers/__init__.py | 2 + redbot/core/drivers/red_base.py | 1 + redbot/core/drivers/red_json.py | 13 +- redbot/core/drivers/red_mongo.py | 53 +- redbot/core/events.py | 73 +- redbot/core/help_formatter.py | 135 +- redbot/core/i18n.py | 60 +- redbot/core/json_io.py | 9 +- redbot/core/locales/regen_messages.py | 12 +- redbot/core/modlog.py | 206 +-- redbot/core/rpc.py | 15 +- redbot/core/sentry.py | 10 +- redbot/core/utils/__init__.py | 3 +- redbot/core/utils/antispam.py | 14 +- redbot/core/utils/chat_formatting.py | 69 +- redbot/core/utils/data_converter.py | 2 +- redbot/core/utils/menus.py | 79 +- redbot/core/utils/mod.py | 45 +- redbot/core/utils/tunnel.py | 67 +- redbot/launcher.py | 194 +-- redbot/setup.py | 125 +- 90 files changed, 3629 insertions(+), 3223 deletions(-) diff --git a/README.rst b/README.rst index d821114c8..8c0b7d3f7 100644 --- a/README.rst +++ b/README.rst @@ -14,6 +14,11 @@ :target: https://www.patreon.com/Red_Devs :alt: Patreon +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/ambv/black + :alt: Code style: black + + ******************** Red - Discord Bot v3 ******************** diff --git a/redbot/__init__.py b/redbot/__init__.py index edf21f713..5e0c8a781 100644 --- a/redbot/__init__.py +++ b/redbot/__init__.py @@ -5,7 +5,9 @@ import discord # Let's do all the dumb version checking in one place. if discord.version_info.major < 1: - print("You are not running the rewritten version of discord.py.\n\n" - "In order to use Red v3 you MUST be running d.py version" - " >= 1.0.0.") + print( + "You are not running the rewritten version of discord.py.\n\n" + "In order to use Red v3 you MUST be running d.py version" + " >= 1.0.0." + ) sys.exit(1) diff --git a/redbot/__main__.py b/redbot/__main__.py index 1db1a7a52..04e373cbd 100644 --- a/redbot/__main__.py +++ b/redbot/__main__.py @@ -40,24 +40,25 @@ def init_loggers(cli_flags): logger = logging.getLogger("red") red_format = logging.Formatter( - '%(asctime)s %(levelname)s %(module)s %(funcName)s %(lineno)d: ' - '%(message)s', - datefmt="[%d/%m/%Y %H:%M]") + "%(asctime)s %(levelname)s %(module)s %(funcName)s %(lineno)d: " "%(message)s", + datefmt="[%d/%m/%Y %H:%M]", + ) stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler.setFormatter(red_format) if cli_flags.debug: - os.environ['PYTHONASYNCIODEBUG'] = '1' + os.environ["PYTHONASYNCIODEBUG"] = "1" logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.WARNING) from redbot.core.data_manager import core_data_path - logfile_path = core_data_path() / 'red.log' + + logfile_path = core_data_path() / "red.log" fhandler = logging.handlers.RotatingFileHandler( - filename=str(logfile_path), encoding='utf-8', mode='a', - maxBytes=10**7, backupCount=5) + filename=str(logfile_path), encoding="utf-8", mode="a", maxBytes=10 ** 7, backupCount=5 + ) fhandler.setFormatter(red_format) logger.addHandler(fhandler) @@ -76,15 +77,17 @@ async def _get_prefix_and_token(red, indict): :param indict: :return: """ - indict['token'] = await red.db.token() - indict['prefix'] = await red.db.prefix() - indict['enable_sentry'] = await red.db.enable_sentry() + indict["token"] = await red.db.token() + indict["prefix"] = await red.db.prefix() + indict["enable_sentry"] = await red.db.enable_sentry() def list_instances(): if not config_file.exists(): - print("No instances have been configured! Configure one " - "using `redbot-setup` before trying to run the bot!") + print( + "No instances have been configured! Configure one " + "using `redbot-setup` before trying to run the bot!" + ) sys.exit(1) else: data = JsonIO(config_file)._load_json() @@ -118,29 +121,30 @@ def main(): loop = asyncio.get_event_loop() tmp_data = {} loop.run_until_complete(_get_prefix_and_token(red, tmp_data)) - token = os.environ.get("RED_TOKEN", tmp_data['token']) - prefix = cli_flags.prefix or tmp_data['prefix'] + token = os.environ.get("RED_TOKEN", tmp_data["token"]) + prefix = cli_flags.prefix or tmp_data["prefix"] if token is None or not prefix: if cli_flags.no_prompt is False: - new_token = interactive_config(red, token_set=bool(token), - prefix_set=bool(prefix)) + new_token = interactive_config(red, token_set=bool(token), prefix_set=bool(prefix)) if new_token: token = new_token else: log.critical("Token and prefix must be set in order to login.") sys.exit(1) loop.run_until_complete(_get_prefix_and_token(red, tmp_data)) - if tmp_data['enable_sentry']: + if tmp_data["enable_sentry"]: red.enable_sentry() cleanup_tasks = True try: loop.run_until_complete(red.start(token, bot=not cli_flags.not_bot)) except discord.LoginFailure: cleanup_tasks = False # No login happened, no need for this - log.critical("This token doesn't seem to be valid. If it belongs to " - "a user account, remember that the --not-bot flag " - "must be used. For self-bot functionalities instead, " - "--self-bot") + log.critical( + "This token doesn't seem to be valid. If it belongs to " + "a user account, remember that the --not-bot flag " + "must be used. For self-bot functionalities instead, " + "--self-bot" + ) db_token = red.db.token() if db_token and not cli_flags.no_prompt: print("\nDo you want to reset the token? (y/n)") @@ -159,12 +163,11 @@ def main(): rpc.clean_up() if cleanup_tasks: pending = asyncio.Task.all_tasks(loop=red.loop) - gathered = asyncio.gather( - *pending, loop=red.loop, return_exceptions=True) + gathered = asyncio.gather(*pending, loop=red.loop, return_exceptions=True) gathered.cancel() sys.exit(red._shutdown_mode.value) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/redbot/cogs/admin/admin.py b/redbot/cogs/admin/admin.py index 5e0317674..08f0fb864 100644 --- a/redbot/cogs/admin/admin.py +++ b/redbot/cogs/admin/admin.py @@ -40,18 +40,16 @@ RUNNING_ANNOUNCEMENT = ( class Admin: - def __init__(self, config=Config): - self.conf = config.get_conf(self, 8237492837454039, - force_registration=True) - self.conf.register_global( - serverlocked=False - ) + def __init__(self, config=Config): + self.conf = config.get_conf(self, 8237492837454039, force_registration=True) + + self.conf.register_global(serverlocked=False) self.conf.register_guild( announce_ignore=False, announce_channel=None, # Integer ID - selfroles=[] # List of integer ID's + selfroles=[], # List of integer ID's ) self.__current_announcer = None @@ -63,8 +61,7 @@ class Admin: pass @staticmethod - async def complain(ctx: commands.Context, message: str, - **kwargs): + async def complain(ctx: commands.Context, message: str, **kwargs): await ctx.send(message.format(**kwargs)) def is_announcing(self) -> bool: @@ -78,8 +75,7 @@ class Admin: return self.__current_announcer.active or False @staticmethod - def pass_heirarchy_check(ctx: commands.Context, - role: discord.Role) -> bool: + def pass_heirarchy_check(ctx: commands.Context, role: discord.Role) -> bool: """ Determines if the bot has a higher role than the given one. :param ctx: @@ -89,8 +85,7 @@ class Admin: return ctx.guild.me.top_role > role @staticmethod - def pass_user_heirarchy_check(ctx: commands.Context, - role: discord.Role) -> bool: + def pass_user_heirarchy_check(ctx: commands.Context, role: discord.Role) -> bool: """ Determines if a user is allowed to add/remove/edit the given role. :param ctx: @@ -99,43 +94,40 @@ class Admin: """ return ctx.author.top_role > role - async def _addrole(self, ctx: commands.Context, member: discord.Member, - role: discord.Role): + async def _addrole(self, ctx: commands.Context, member: discord.Member, role: discord.Role): try: await member.add_roles(role) except discord.Forbidden: if not self.pass_heirarchy_check(ctx, role): - await self.complain(ctx, HIERARCHY_ISSUE, role=role, - member=member) + await self.complain(ctx, HIERARCHY_ISSUE, role=role, member=member) else: await self.complain(ctx, GENERIC_FORBIDDEN) else: - await ctx.send("I successfully added {role.name} to" - " {member.display_name}".format( - role=role, member=member - )) + await ctx.send( + "I successfully added {role.name} to" + " {member.display_name}".format(role=role, member=member) + ) - async def _removerole(self, ctx: commands.Context, member: discord.Member, - role: discord.Role): + async def _removerole(self, ctx: commands.Context, member: discord.Member, role: discord.Role): try: await member.remove_roles(role) except discord.Forbidden: if not self.pass_heirarchy_check(ctx, role): - await self.complain(ctx, HIERARCHY_ISSUE, role=role, - member=member) + await self.complain(ctx, HIERARCHY_ISSUE, role=role, member=member) else: await self.complain(ctx, GENERIC_FORBIDDEN) else: - await ctx.send("I successfully removed {role.name} from" - " {member.display_name}".format( - role=role, member=member - )) + await ctx.send( + "I successfully removed {role.name} from" + " {member.display_name}".format(role=role, member=member) + ) @commands.command() @commands.guild_only() @checks.admin_or_permissions(manage_roles=True) - async def addrole(self, ctx: commands.Context, rolename: discord.Role, *, - user: MemberDefaultAuthor=None): + async def addrole( + self, ctx: commands.Context, rolename: discord.Role, *, user: MemberDefaultAuthor = None + ): """ Adds a role to a user. If user is left blank it defaults to the author of the command. @@ -151,8 +143,9 @@ class Admin: @commands.command() @commands.guild_only() @checks.admin_or_permissions(manage_roles=True) - async def removerole(self, ctx: commands.Context, rolename: discord.Role, *, - user: MemberDefaultAuthor=None): + async def removerole( + self, ctx: commands.Context, rolename: discord.Role, *, user: MemberDefaultAuthor = None + ): """ Removes a role from a user. If user is left blank it defaults to the author of the command. @@ -173,9 +166,10 @@ class Admin: if ctx.invoked_subcommand is None: await ctx.send_help() - @editrole.command(name="colour", aliases=["color", ]) - async def editrole_colour(self, ctx: commands.Context, role: discord.Role, - value: discord.Colour): + @editrole.command(name="colour", aliases=["color"]) + async def editrole_colour( + self, ctx: commands.Context, role: discord.Role, value: discord.Colour + ): """Edits a role's colour Use double quotes if the role contains spaces. @@ -185,8 +179,7 @@ class Admin: !editrole colour \"The Transistor\" #ff0000 !editrole colour Test #ff9900""" author = ctx.author - reason = "{}({}) changed the colour of role '{}'".format( - author.name, author.id, role.name) + reason = "{}({}) changed the colour of role '{}'".format(author.name, author.id, role.name) if not self.pass_user_heirarchy_check(ctx, role): await self.complain(ctx, USER_HIERARCHY_ISSUE) @@ -211,7 +204,8 @@ class Admin: author = ctx.message.author old_name = role.name reason = "{}({}) changed the name of role '{}' to '{}'".format( - author.name, author.id, old_name, name) + author.name, author.id, old_name, name + ) if not self.pass_user_heirarchy_check(ctx, role): await self.complain(ctx, USER_HIERARCHY_ISSUE) @@ -240,8 +234,7 @@ class Admin: await ctx.send("The announcement has begun.") else: prefix = ctx.prefix - await self.complain(ctx, RUNNING_ANNOUNCEMENT, - prefix=prefix) + await self.complain(ctx, RUNNING_ANNOUNCEMENT, prefix=prefix) @announce.command(name="cancel") @checks.is_owner() @@ -259,7 +252,7 @@ class Admin: @announce.command(name="channel") @commands.guild_only() @checks.guildowner_or_permissions(administrator=True) - async def announce_channel(self, ctx, *, channel: discord.TextChannel=None): + async def announce_channel(self, ctx, *, channel: discord.TextChannel = None): """ Changes the channel on which the bot makes announcements. """ @@ -267,14 +260,12 @@ class Admin: channel = ctx.channel await self.conf.guild(ctx.guild).announce_channel.set(channel.id) - await ctx.send("The announcement channel has been set to {}".format( - channel.mention - )) + await ctx.send("The announcement channel has been set to {}".format(channel.mention)) @announce.command(name="ignore") @commands.guild_only() @checks.guildowner_or_permissions(administrator=True) - async def announce_ignore(self, ctx, *, guild: discord.Guild=None): + async def announce_ignore(self, ctx, *, guild: discord.Guild = None): """ Toggles whether the announcements will ignore the given server. Defaults to the current server if none is provided. @@ -287,9 +278,7 @@ class Admin: verb = "will" if ignored else "will not" - await ctx.send("The server {} {} receive announcements.".format( - guild.name, verb - )) + await ctx.send("The server {} {} receive announcements.".format(guild.name, verb)) async def _valid_selfroles(self, guild: discord.Guild) -> Tuple[discord.Role]: """ @@ -384,8 +373,10 @@ class Admin: await ctx.send("The bot {} serverlocked.".format(verb)) -# region Event Handlers + # region Event Handlers async def on_guild_join(self, guild: discord.Guild): if await self._serverlock_check(guild): return + + # endregion diff --git a/redbot/cogs/admin/announcer.py b/redbot/cogs/admin/announcer.py index 547175ae2..09272080f 100644 --- a/redbot/cogs/admin/announcer.py +++ b/redbot/cogs/admin/announcer.py @@ -5,9 +5,8 @@ from discord.ext import commands class Announcer: - def __init__(self, ctx: commands.Context, - message: str, - config=None): + + def __init__(self, ctx: commands.Context, message: str, config=None): """ :param ctx: :param message: @@ -65,10 +64,7 @@ class Announcer: try: await channel.send(self.message) except discord.Forbidden: - await bot_owner.send("I could not announce to server: {}".format( - g.id - )) + await bot_owner.send("I could not announce to server: {}".format(g.id)) await asyncio.sleep(0.5) self.active = False - diff --git a/redbot/cogs/admin/converters.py b/redbot/cogs/admin/converters.py index ca12b9734..ac446f067 100644 --- a/redbot/cogs/admin/converters.py +++ b/redbot/cogs/admin/converters.py @@ -3,6 +3,7 @@ from discord.ext import commands class MemberDefaultAuthor(commands.Converter): + async def convert(self, ctx: commands.Context, arg: str) -> discord.Member: member_converter = commands.MemberConverter() try: @@ -16,6 +17,7 @@ class MemberDefaultAuthor(commands.Converter): class SelfRole(commands.Converter): + async def convert(self, ctx: commands.Context, arg: str) -> discord.Role: admin = ctx.command.instance if admin is None: @@ -28,6 +30,5 @@ class SelfRole(commands.Converter): role = await role_converter.convert(ctx, arg) if role.id not in selfroles: - raise commands.BadArgument("The provided role is not a valid" - " selfrole.") + raise commands.BadArgument("The provided role is not a valid" " selfrole.") return role diff --git a/redbot/cogs/admin/locales/regen_messages.py b/redbot/cogs/admin/locales/regen_messages.py index 16fa408f4..c50b94a5b 100644 --- a/redbot/cogs/admin/locales/regen_messages.py +++ b/redbot/cogs/admin/locales/regen_messages.py @@ -1,14 +1,10 @@ import subprocess -TO_TRANSLATE = [ - '../admin.py' -] +TO_TRANSLATE = ["../admin.py"] def regen_messages(): - subprocess.run( - ['pygettext', '-n'] + TO_TRANSLATE - ) + subprocess.run(["pygettext", "-n"] + TO_TRANSLATE) if __name__ == "__main__": diff --git a/redbot/cogs/alias/alias.py b/redbot/cogs/alias/alias.py index f6d4b239a..2741bbd46 100644 --- a/redbot/cogs/alias/alias.py +++ b/redbot/cogs/alias/alias.py @@ -26,14 +26,9 @@ class Alias: and append them to the stored alias """ - default_global_settings = { - "entries": [] - } + default_global_settings = {"entries": []} - default_guild_settings = { - "enabled": False, - "entries": [] # Going to be a list of dicts - } + default_guild_settings = {"enabled": False, "entries": []} # Going to be a list of dicts def __init__(self, bot: Red): self.bot = bot @@ -49,14 +44,17 @@ class Alias: return (AliasEntry.from_json(d) for d in (await self._aliases.entries())) async def loaded_aliases(self, guild: discord.Guild) -> Generator[AliasEntry, None, None]: - return (AliasEntry.from_json(d, bot=self.bot) - for d in (await self._aliases.guild(guild).entries())) + return ( + AliasEntry.from_json(d, bot=self.bot) + for d in (await self._aliases.guild(guild).entries()) + ) async def loaded_global_aliases(self) -> Generator[AliasEntry, None, None]: return (AliasEntry.from_json(d, bot=self.bot) for d in (await self._aliases.entries())) - async def is_alias(self, guild: discord.Guild, alias_name: str, - server_aliases: Iterable[AliasEntry]=()) -> (bool, AliasEntry): + async def is_alias( + self, guild: discord.Guild, alias_name: str, server_aliases: Iterable[AliasEntry] = () + ) -> (bool, AliasEntry): if not server_aliases: server_aliases = await self.unloaded_aliases(guild) @@ -76,10 +74,11 @@ class Alias: @staticmethod def is_valid_alias_name(alias_name: str) -> bool: - return not bool(search(r'\s', alias_name)) and alias_name.isprintable() + return not bool(search(r"\s", alias_name)) and alias_name.isprintable() - async def add_alias(self, ctx: commands.Context, alias_name: str, - command: Tuple[str], global_: bool=False) -> AliasEntry: + async def add_alias( + self, ctx: commands.Context, alias_name: str, command: Tuple[str], global_: bool = False + ) -> AliasEntry: alias = AliasEntry(alias_name, command, ctx.author, global_=global_) if global_: @@ -93,8 +92,9 @@ class Alias: return alias - async def delete_alias(self, ctx: commands.Context, alias_name: str, - global_: bool=False) -> bool: + async def delete_alias( + self, ctx: commands.Context, alias_name: str, global_: bool = False + ) -> bool: if global_: settings = self._aliases else: @@ -120,16 +120,15 @@ class Alias: """ content = message.content prefix_list = await self.bot.command_prefix(self.bot, message) - prefixes = sorted(prefix_list, - key=lambda pfx: len(pfx), - reverse=True) + prefixes = sorted(prefix_list, key=lambda pfx: len(pfx), reverse=True) for p in prefixes: if content.startswith(p): return p raise ValueError(_("No prefix found.")) - def get_extra_args_from_alias(self, message: discord.Message, prefix: str, - alias: AliasEntry) -> str: + def get_extra_args_from_alias( + self, message: discord.Message, prefix: str, alias: AliasEntry + ) -> str: """ When an alias is executed by a user in chat this function tries to get any extra arguments passed in with the call. @@ -143,8 +142,9 @@ class Alias: extra = message.content[known_content_length:].strip() return extra - async def maybe_call_alias(self, message: discord.Message, - aliases: Iterable[AliasEntry]=None): + async def maybe_call_alias( + self, message: discord.Message, aliases: Iterable[AliasEntry] = None + ): try: prefix = await self.get_prefix(message) except ValueError: @@ -155,13 +155,14 @@ class Alias: except IndexError: return False - is_alias, alias = await self.is_alias(message.guild, potential_alias, server_aliases=aliases) + is_alias, alias = await self.is_alias( + message.guild, potential_alias, server_aliases=aliases + ) if is_alias: await self.call_alias(message, prefix, alias) - async def call_alias(self, message: discord.Message, prefix: str, - alias: AliasEntry): + async def call_alias(self, message: discord.Message, prefix: str, alias: AliasEntry): new_message = copy(message) args = self.get_extra_args_from_alias(message, prefix, alias) @@ -181,83 +182,118 @@ class Alias: """ Manage global aliases. """ - if ctx.invoked_subcommand is None or \ - isinstance(ctx.invoked_subcommand, commands.Group): + if ctx.invoked_subcommand is None or isinstance(ctx.invoked_subcommand, commands.Group): await ctx.send_help() @alias.command(name="add") @commands.guild_only() - async def _add_alias(self, ctx: commands.Context, - alias_name: str, *, command): + async def _add_alias(self, ctx: commands.Context, alias_name: str, *, command): """ Add an alias for a command. """ -# region Alias Add Validity Checking + # region Alias Add Validity Checking is_command = self.is_command(alias_name) if is_command: - await ctx.send(_("You attempted to create a new alias" - " with the name {} but that" - " name is already a command on this bot.").format(alias_name)) + await ctx.send( + _( + "You attempted to create a new alias" + " with the name {} but that" + " name is already a command on this bot." + ).format( + alias_name + ) + ) return is_alias, something_useless = await self.is_alias(ctx.guild, alias_name) if is_alias: - await ctx.send(_("You attempted to create a new alias" - " with the name {} but that" - " alias already exists on this server.").format(alias_name)) + await ctx.send( + _( + "You attempted to create a new alias" + " with the name {} but that" + " alias already exists on this server." + ).format( + alias_name + ) + ) return is_valid_name = self.is_valid_alias_name(alias_name) if not is_valid_name: - await ctx.send(_("You attempted to create a new alias" - " with the name {} but that" - " name is an invalid alias name. Alias" - " names may not contain spaces.").format(alias_name)) + await ctx.send( + _( + "You attempted to create a new alias" + " with the name {} but that" + " name is an invalid alias name. Alias" + " names may not contain spaces." + ).format( + alias_name + ) + ) return -# endregion + # endregion # At this point we know we need to make a new alias # and that the alias name is valid. await self.add_alias(ctx, alias_name, command) - await ctx.send(_("A new alias with the trigger `{}`" - " has been created.").format(alias_name)) + await ctx.send( + _("A new alias with the trigger `{}`" " has been created.").format(alias_name) + ) @global_.command(name="add") - async def _add_global_alias(self, ctx: commands.Context, - alias_name: str, *, command): + async def _add_global_alias(self, ctx: commands.Context, alias_name: str, *, command): """ Add a global alias for a command. """ -# region Alias Add Validity Checking + # region Alias Add Validity Checking is_command = self.is_command(alias_name) if is_command: - await ctx.send(_("You attempted to create a new global alias" - " with the name {} but that" - " name is already a command on this bot.").format(alias_name)) + await ctx.send( + _( + "You attempted to create a new global alias" + " with the name {} but that" + " name is already a command on this bot." + ).format( + alias_name + ) + ) return is_alias, something_useless = await self.is_alias(ctx.guild, alias_name) if is_alias: - await ctx.send(_("You attempted to create a new global alias" - " with the name {} but that" - " alias already exists on this server.").format(alias_name)) + await ctx.send( + _( + "You attempted to create a new global alias" + " with the name {} but that" + " alias already exists on this server." + ).format( + alias_name + ) + ) return is_valid_name = self.is_valid_alias_name(alias_name) if not is_valid_name: - await ctx.send(_("You attempted to create a new global alias" - " with the name {} but that" - " name is an invalid alias name. Alias" - " names may not contain spaces.").format(alias_name)) + await ctx.send( + _( + "You attempted to create a new global alias" + " with the name {} but that" + " name is an invalid alias name. Alias" + " names may not contain spaces." + ).format( + alias_name + ) + ) return -# endregion + # endregion await self.add_alias(ctx, alias_name, command, global_=True) - await ctx.send(_("A new global alias with the trigger `{}`" - " has been created.").format(alias_name)) + await ctx.send( + _("A new global alias with the trigger `{}`" " has been created.").format(alias_name) + ) @alias.command(name="help") @commands.guild_only() @@ -280,8 +316,11 @@ class Alias: is_alias, alias = await self.is_alias(ctx.guild, alias_name) if is_alias: - await ctx.send(_("The `{}` alias will execute the" - " command `{}`").format(alias_name, alias.command)) + await ctx.send( + _("The `{}` alias will execute the" " command `{}`").format( + alias_name, alias.command + ) + ) else: await ctx.send(_("There is no alias with the name `{}`").format(alias_name)) @@ -299,8 +338,9 @@ class Alias: return if await self.delete_alias(ctx, alias_name): - await ctx.send(_("Alias with the name `{}` was successfully" - " deleted.").format(alias_name)) + await ctx.send( + _("Alias with the name `{}` was successfully" " deleted.").format(alias_name) + ) else: await ctx.send(_("Alias with name `{}` was not found.").format(alias_name)) @@ -317,8 +357,9 @@ class Alias: return if await self.delete_alias(ctx, alias_name, global_=True): - await ctx.send(_("Alias with the name `{}` was successfully" - " deleted.").format(alias_name)) + await ctx.send( + _("Alias with the name `{}` was successfully" " deleted.").format(alias_name) + ) else: await ctx.send(_("Alias with name `{}` was not found.").format(alias_name)) @@ -328,7 +369,9 @@ class Alias: """ Lists the available aliases on this server. """ - names = [_("Aliases:"), ] + sorted(["+ " + a.name for a in (await self.unloaded_aliases(ctx.guild))]) + names = [_("Aliases:")] + sorted( + ["+ " + a.name for a in (await self.unloaded_aliases(ctx.guild))] + ) if len(names) == 0: await ctx.send(_("There are no aliases on this server.")) else: @@ -339,7 +382,9 @@ class Alias: """ Lists the available global aliases on this bot. """ - names = [_("Aliases:"), ] + sorted(["+ " + a.name for a in await self.unloaded_global_aliases()]) + names = [_("Aliases:")] + sorted( + ["+ " + a.name for a in await self.unloaded_global_aliases()] + ) if len(names) == 0: await ctx.send(_("There are no aliases on this server.")) else: diff --git a/redbot/cogs/alias/alias_entry.py b/redbot/cogs/alias/alias_entry.py index 880b55a15..48fa40f12 100644 --- a/redbot/cogs/alias/alias_entry.py +++ b/redbot/cogs/alias/alias_entry.py @@ -5,8 +5,10 @@ from redbot.core import commands class AliasEntry: - def __init__(self, name: str, command: Tuple[str], - creator: discord.Member, global_: bool=False): + + def __init__( + self, name: str, command: Tuple[str], creator: discord.Member, global_: bool = False + ): super().__init__() self.has_real_data = False self.name = name @@ -43,13 +45,12 @@ class AliasEntry: "creator": creator, "guild": guild, "global": self.global_, - "uses": self.uses + "uses": self.uses, } @classmethod - def from_json(cls, data: dict, bot: commands.Bot=None): - ret = cls(data["name"], data["command"], - data["creator"], global_=data["global"]) + def from_json(cls, data: dict, bot: commands.Bot = None): + ret = cls(data["name"], data["command"], data["creator"], global_=data["global"]) if bot: ret.has_real_data = True diff --git a/redbot/cogs/alias/locales/regen_messages.py b/redbot/cogs/alias/locales/regen_messages.py index f99e84e5e..a0ba4ff3b 100644 --- a/redbot/cogs/alias/locales/regen_messages.py +++ b/redbot/cogs/alias/locales/regen_messages.py @@ -1,15 +1,11 @@ import subprocess -TO_TRANSLATE = [ - '../alias.py' -] +TO_TRANSLATE = ["../alias.py"] def regen_messages(): - subprocess.run( - ['pygettext', '-n'] + TO_TRANSLATE - ) + subprocess.run(["pygettext", "-n"] + TO_TRANSLATE) if __name__ == "__main__": - regen_messages() \ No newline at end of file + regen_messages() diff --git a/redbot/cogs/audio/__init__.py b/redbot/cogs/audio/__init__.py index 3cb8950e8..cd27a578c 100644 --- a/redbot/cogs/audio/__init__.py +++ b/redbot/cogs/audio/__init__.py @@ -9,9 +9,10 @@ from redbot.core.data_manager import cog_data_path import redbot.core LAVALINK_DOWNLOAD_URL = ( - "https://github.com/Cog-Creators/Red-DiscordBot/" - "releases/download/{}/Lavalink.jar" -).format(redbot.core.__version__) + "https://github.com/Cog-Creators/Red-DiscordBot/" "releases/download/{}/Lavalink.jar" +).format( + redbot.core.__version__ +) LAVALINK_DOWNLOAD_DIR = cog_data_path(raw_name="Audio") LAVALINK_JAR_FILE = LAVALINK_DOWNLOAD_DIR / "Lavalink.jar" @@ -21,7 +22,7 @@ BUNDLED_APP_YML_FILE = Path(__file__).parent / "application.yml" async def download_lavalink(session): - with LAVALINK_JAR_FILE.open(mode='wb') as f: + with LAVALINK_JAR_FILE.open(mode="wb") as f: async with session.get(LAVALINK_DOWNLOAD_URL) as resp: while True: chunk = await resp.content.read(512) diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index 0eaec118e..5932a4bee 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -20,18 +20,19 @@ __author__ = ["aikaterna", "billy/bollo/ati"] @cog_i18n(_) class Audio: + def __init__(self, bot): self.bot = bot self.config = Config.get_conf(self, 2711759130, force_registration=True) default_global = { - "host": 'localhost', - "rest_port": '2333', - "ws_port": '2332', - "password": 'youshallnotpass', + "host": "localhost", + "rest_port": "2333", + "ws_port": "2332", + "password": "youshallnotpass", "status": False, - "current_build": [3, 0, 0, 'alpha', 0], - "use_external_lavalink": False + "current_build": [3, 0, 0, "alpha", 0], + "use_external_lavalink": False, } default_guild = { @@ -45,7 +46,7 @@ class Audio: "shuffle": False, "volume": 100, "vote_enabled": False, - "vote_percent": 0 + "vote_percent": 0, } self.config.register_guild(**default_guild) @@ -60,7 +61,12 @@ class Audio: ws_port = await self.config.ws_port() await lavalink.initialize( - bot=self.bot, host=host, password=password, rest_port=rest_port, ws_port=ws_port, timeout=60 + bot=self.bot, + host=host, + password=password, + rest_port=rest_port, + ws_port=ws_port, + timeout=60, ) lavalink.register_event_listener(self.event_handler) @@ -75,65 +81,86 @@ class Audio: playing_servers = 0 if event_type == lavalink.LavalinkEvents.TRACK_START: - playing_song = player.fetch('playing_song') - requester = player.fetch('requester') - player.store('prev_song', playing_song) - player.store('prev_requester', requester) - player.store('playing_song', player.current.uri) - player.store('requester', player.current.requester) + playing_song = player.fetch("playing_song") + requester = player.fetch("requester") + player.store("prev_song", playing_song) + player.store("prev_requester", requester) + player.store("playing_song", player.current.uri) + player.store("requester", player.current.requester) self.skip_votes[player.channel.guild] = [] if event_type == lavalink.LavalinkEvents.TRACK_START and notify: - notify_channel = player.fetch('channel') + notify_channel = player.fetch("channel") if notify_channel: notify_channel = self.bot.get_channel(notify_channel) - if player.fetch('notify_message') is not None: + if player.fetch("notify_message") is not None: try: - await player.fetch('notify_message').delete() + await player.fetch("notify_message").delete() except discord.errors.NotFound: pass - embed = discord.Embed(colour=notify_channel.guild.me.top_role.colour, title='Now Playing', - description='**[{}]({})**'.format(player.current.title, player.current.uri)) + embed = discord.Embed( + colour=notify_channel.guild.me.top_role.colour, + title="Now Playing", + description="**[{}]({})**".format(player.current.title, player.current.uri), + ) notify_message = await notify_channel.send(embed=embed) - player.store('notify_message', notify_message) + player.store("notify_message", notify_message) if event_type == lavalink.LavalinkEvents.TRACK_START and status: if playing_servers == 0: await self.bot.change_presence(activity=None) if playing_servers == 1: - await self.bot.change_presence(activity=discord.Activity(name=get_single_title, - type=discord.ActivityType.listening)) + await self.bot.change_presence( + activity=discord.Activity( + name=get_single_title, type=discord.ActivityType.listening + ) + ) if playing_servers > 1: await self.bot.change_presence( - activity=discord.Activity(name='music in {} servers'.format(playing_servers), - type=discord.ActivityType.playing)) + activity=discord.Activity( + name="music in {} servers".format(playing_servers), + type=discord.ActivityType.playing, + ) + ) if event_type == lavalink.LavalinkEvents.QUEUE_END and notify: - notify_channel = player.fetch('channel') + notify_channel = player.fetch("channel") if notify_channel: notify_channel = self.bot.get_channel(notify_channel) - embed = discord.Embed(colour=notify_channel.guild.me.top_role.colour, title='Queue ended.') + embed = discord.Embed( + colour=notify_channel.guild.me.top_role.colour, title="Queue ended." + ) await notify_channel.send(embed=embed) if event_type == lavalink.LavalinkEvents.QUEUE_END and status: if playing_servers == 0: await self.bot.change_presence(activity=None) if playing_servers == 1: - await self.bot.change_presence(activity=discord.Activity(name=get_single_title, - type=discord.ActivityType.listening)) + await self.bot.change_presence( + activity=discord.Activity( + name=get_single_title, type=discord.ActivityType.listening + ) + ) if playing_servers > 1: await self.bot.change_presence( - activity=discord.Activity(name='music in {} servers'.format(playing_servers), - type=discord.ActivityType.playing)) + activity=discord.Activity( + name="music in {} servers".format(playing_servers), + type=discord.ActivityType.playing, + ) + ) if event_type == lavalink.LavalinkEvents.TRACK_EXCEPTION: - message_channel = player.fetch('channel') + message_channel = player.fetch("channel") if message_channel: message_channel = self.bot.get_channel(message_channel) - embed = discord.Embed(colour=message_channel.guild.me.top_role.colour, title='Track Error', - description='{}\n**[{}]({})**'.format(extra, player.current.title, - player.current.uri)) - embed.set_footer(text='Skipping...') + embed = discord.Embed( + colour=message_channel.guild.me.top_role.colour, + title="Track Error", + description="{}\n**[{}]({})**".format( + extra, player.current.title, player.current.uri + ), + ) + embed.set_footer(text="Skipping...") await message_channel.send(embed=embed) await player.skip() @@ -150,23 +177,25 @@ class Audio: """Toggle DJ mode (users need a role to use audio commands).""" dj_role_id = await self.config.guild(ctx.guild).dj_role() if dj_role_id is None: - await self._embed_msg(ctx, 'Please set a role to use with DJ mode. Enter the role name now.') + await self._embed_msg( + ctx, "Please set a role to use with DJ mode. Enter the role name now." + ) def check(m): return m.author == ctx.author try: - dj_role = await ctx.bot.wait_for('message', timeout=15.0, check=check) + dj_role = await ctx.bot.wait_for("message", timeout=15.0, check=check) dj_role_obj = discord.utils.get(ctx.guild.roles, name=dj_role.content) if dj_role_obj is None: - return await self._embed_msg(ctx, 'No role with that name.') + return await self._embed_msg(ctx, "No role with that name.") await ctx.invoke(self.role, dj_role_obj) except asyncio.TimeoutError: - return await self._embed_msg(ctx, 'No role entered, try again later.') + return await self._embed_msg(ctx, "No role entered, try again later.") dj_enabled = await self.config.guild(ctx.guild).dj_enabled() await self.config.guild(ctx.guild).dj_enabled.set(not dj_enabled) - await self._embed_msg(ctx, 'DJ role enabled: {}.'.format(not dj_enabled)) + await self._embed_msg(ctx, "DJ role enabled: {}.".format(not dj_enabled)) @audioset.command() @checks.admin_or_permissions(manage_roles=True) @@ -175,21 +204,25 @@ class Audio: await self.config.guild(ctx.guild).dj_role.set(role_name.id) dj_role_id = await self.config.guild(ctx.guild).dj_role() dj_role_obj = discord.utils.get(ctx.guild.roles, id=dj_role_id) - await self._embed_msg(ctx, 'DJ role set to: {}.'.format(dj_role_obj.name)) + await self._embed_msg(ctx, "DJ role set to: {}.".format(dj_role_obj.name)) @audioset.command() @checks.mod_or_permissions(administrator=True) async def jukebox(self, ctx, price: int): """Set a price for queueing songs for non-mods. 0 to disable.""" if price < 0: - return await self._embed_msg(ctx, 'Can\'t be less than zero.') + return await self._embed_msg(ctx, "Can't be less than zero.") if price == 0: jukebox = False - await self._embed_msg(ctx, 'Jukebox mode disabled.') + await self._embed_msg(ctx, "Jukebox mode disabled.") else: jukebox = True - await self._embed_msg(ctx, 'Track queueing command price set to {} {}.'.format( - price, await bank.get_currency_name(ctx.guild))) + await self._embed_msg( + ctx, + "Track queueing command price set to {} {}.".format( + price, await bank.get_currency_name(ctx.guild) + ), + ) await self.config.guild(ctx.guild).jukebox_price.set(price) await self.config.guild(ctx.guild).jukebox.set(jukebox) @@ -200,38 +233,46 @@ class Audio: """Toggle song announcement and other bot messages.""" notify = await self.config.guild(ctx.guild).notify() await self.config.guild(ctx.guild).notify.set(not notify) - await self._embed_msg(ctx, 'Verbose mode on: {}.'.format(not notify)) + await self._embed_msg(ctx, "Verbose mode on: {}.".format(not notify)) @audioset.command() async def settings(self, ctx): """Show the current settings.""" data = await self.config.guild(ctx.guild).all() global_data = await self.config.all() - dj_role_obj = discord.utils.get(ctx.guild.roles, id=data['dj_role']) - dj_enabled = data['dj_enabled'] - jukebox = data['jukebox'] - jukebox_price = data['jukebox_price'] + dj_role_obj = discord.utils.get(ctx.guild.roles, id=data["dj_role"]) + dj_enabled = data["dj_enabled"] + jukebox = data["jukebox"] + jukebox_price = data["jukebox_price"] jarbuild = redbot.core.__version__ - vote_percent = data['vote_percent'] - msg = ('```ini\n' - '----Server Settings----\n') + vote_percent = data["vote_percent"] + msg = ("```ini\n" "----Server Settings----\n") if dj_enabled: - msg += 'DJ Role: [{}]\n'.format(dj_role_obj.name) + msg += "DJ Role: [{}]\n".format(dj_role_obj.name) if jukebox: - msg += 'Jukebox: [{0}]\n'.format(jukebox) - msg += 'Command price: [{0}]\n'.format(jukebox_price) - msg += ('Repeat: [{repeat}]\n' - 'Shuffle: [{shuffle}]\n' - 'Song notify msgs: [{notify}]\n' - 'Songs as status: [{status}]\n'.format(**global_data, **data)) + msg += "Jukebox: [{0}]\n".format(jukebox) + msg += "Command price: [{0}]\n".format(jukebox_price) + msg += ( + "Repeat: [{repeat}]\n" + "Shuffle: [{shuffle}]\n" + "Song notify msgs: [{notify}]\n" + "Songs as status: [{status}]\n".format(**global_data, **data) + ) if vote_percent > 0: - msg += ('Vote skip: [{vote_enabled}]\n' - 'Skip percentage: [{vote_percent}%]\n').format(**data) - msg += ('---Lavalink Settings---\n' - 'Cog version: [{}]\n' - 'Jar build: [{}]\n' - 'External server: [{use_external_lavalink}]```').format(__version__, jarbuild, **global_data) + msg += ( + "Vote skip: [{vote_enabled}]\n" "Skip percentage: [{vote_percent}%]\n" + ).format( + **data + ) + msg += ( + "---Lavalink Settings---\n" + "Cog version: [{}]\n" + "Jar build: [{}]\n" + "External server: [{use_external_lavalink}]```" + ).format( + __version__, jarbuild, **global_data + ) embed = discord.Embed(colour=ctx.guild.me.top_role.colour, description=msg) return await ctx.send(embed=embed) @@ -241,15 +282,17 @@ class Audio: async def vote(self, ctx, percent: int): """Percentage needed for non-mods to skip songs. 0 to disable.""" if percent < 0: - return await self._embed_msg(ctx, 'Can\'t be less than zero.') + return await self._embed_msg(ctx, "Can't be less than zero.") elif percent > 100: percent = 100 if percent == 0: enabled = False - await self._embed_msg(ctx, 'Voting disabled. All users can use queue management commands.') + await self._embed_msg( + ctx, "Voting disabled. All users can use queue management commands." + ) else: enabled = True - await self._embed_msg(ctx, 'Vote percentage set to {}%.'.format(percent)) + await self._embed_msg(ctx, "Vote percentage set to {}%.".format(percent)) await self.config.guild(ctx.guild).vote_percent.set(percent) await self.config.guild(ctx.guild).vote_enabled.set(enabled) @@ -260,7 +303,7 @@ class Audio: """Enables/disables songs' titles as status.""" status = await self.config.status() await self.config.status.set(not status) - await self._embed_msg(ctx, 'Song titles as status: {}.'.format(not status)) + await self._embed_msg(ctx, "Song titles as status: {}.".format(not status)) @commands.command() async def audiostats(self, ctx): @@ -269,20 +312,31 @@ class Audio: server_list = [] for p in lavalink.players: - connect_start = p.fetch('connect') - connect_dur = self._dynamic_time(int((datetime.datetime.utcnow() - connect_start).total_seconds())) + connect_start = p.fetch("connect") + connect_dur = self._dynamic_time( + int((datetime.datetime.utcnow() - connect_start).total_seconds()) + ) try: - server_list.append('{} [`{}`]: **[{}]({})**'.format(p.channel.guild.name, connect_dur, - p.current.title, p.current.uri)) + server_list.append( + "{} [`{}`]: **[{}]({})**".format( + p.channel.guild.name, connect_dur, p.current.title, p.current.uri + ) + ) except AttributeError: - server_list.append('{} [`{}`]: **{}**'.format(p.channel.guild.name, connect_dur, - 'Nothing playing.')) + server_list.append( + "{} [`{}`]: **{}**".format( + p.channel.guild.name, connect_dur, "Nothing playing." + ) + ) if server_num == 0: - servers = 'Not connected anywhere.' + servers = "Not connected anywhere." else: - servers = '\n'.join(server_list) - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Connected in {} servers:'.format(server_num), - description=servers) + servers = "\n".join(server_list) + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, + title="Connected in {} servers:".format(server_num), + description=servers, + ) await ctx.send(embed=embed) @commands.command() @@ -290,78 +344,84 @@ class Audio: """Bump a song number to the top of the queue.""" dj_enabled = await self.config.guild(ctx.guild).dj_enabled() if not self._player_check(ctx): - return await self._embed_msg(ctx, 'Nothing playing.') + return await self._embed_msg(ctx, "Nothing playing.") player = lavalink.get_player(ctx.guild.id) - if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not - await self._can_instaskip(ctx, ctx.author)): - return await self._embed_msg(ctx, 'You must be in the voice channel to bump a song.') + if ( + (not ctx.author.voice or ctx.author.voice.channel != player.channel) + and not await self._can_instaskip(ctx, ctx.author) + ): + return await self._embed_msg(ctx, "You must be in the voice channel to bump a song.") if dj_enabled: if not await self._can_instaskip(ctx, ctx.author): - return await self._embed_msg(ctx, 'You need the DJ role to bump songs.') + return await self._embed_msg(ctx, "You need the DJ role to bump songs.") if index > len(player.queue) or index < 1: - return await self._embed_msg(ctx, 'Song number must be greater than 1 and within the queue limit.') + return await self._embed_msg( + ctx, "Song number must be greater than 1 and within the queue limit." + ) bump_index = index - 1 bump_song = player.queue[bump_index] player.queue.insert(0, bump_song) removed = player.queue.pop(index) - await self._embed_msg(ctx, 'Moved {} to the top of the queue.'.format(removed.title)) + await self._embed_msg(ctx, "Moved {} to the top of the queue.".format(removed.title)) - @commands.command(aliases=['dc']) + @commands.command(aliases=["dc"]) async def disconnect(self, ctx): """Disconnect from the voice channel.""" dj_enabled = await self.config.guild(ctx.guild).dj_enabled() if self._player_check(ctx): if dj_enabled: if not await self._can_instaskip(ctx, ctx.author): - return await self._embed_msg(ctx, 'You need the DJ role to disconnect.') - if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx, ctx.author): - return await self._embed_msg(ctx, 'There are other people listening to music.') + return await self._embed_msg(ctx, "You need the DJ role to disconnect.") + if ( + not await self._can_instaskip(ctx, ctx.author) + and not await self._is_alone(ctx, ctx.author) + ): + return await self._embed_msg(ctx, "There are other people listening to music.") else: await lavalink.get_player(ctx.guild.id).stop() return await lavalink.get_player(ctx.guild.id).disconnect() - @commands.command(aliases=['np', 'n', 'song']) + @commands.command(aliases=["np", "n", "song"]) async def now(self, ctx): """Now playing.""" if not self._player_check(ctx): - return await self._embed_msg(ctx, 'Nothing playing.') - expected = ('⏮', '⏹', '⏸', '⏭') - emoji = { - 'prev': '⏮', - 'stop': '⏹', - 'pause': '⏸', - 'next': '⏭' - } + return await self._embed_msg(ctx, "Nothing playing.") + expected = ("⏮", "⏹", "⏸", "⏭") + emoji = {"prev": "⏮", "stop": "⏹", "pause": "⏸", "next": "⏭"} player = lavalink.get_player(ctx.guild.id) if player.current: arrow = await self._draw_time(ctx) pos = lavalink.utils.format_time(player.position) if player.current.is_stream: - dur = 'LIVE' + dur = "LIVE" else: dur = lavalink.utils.format_time(player.current.length) - song = '**[{}]({})**\nRequested by: **{}**\n\n{}`{}`/`{}`'.format( - player.current.title, player.current.uri, - player.current.requester, arrow, pos, dur + song = "**[{}]({})**\nRequested by: **{}**\n\n{}`{}`/`{}`".format( + player.current.title, player.current.uri, player.current.requester, arrow, pos, dur ) else: - song = 'Nothing.' + song = "Nothing." - if player.fetch('np_message') is not None: + if player.fetch("np_message") is not None: try: - await player.fetch('np_message').delete() + await player.fetch("np_message").delete() except discord.errors.NotFound: pass - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Now Playing', description=song) + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, title="Now Playing", description=song + ) message = await ctx.send(embed=embed) - player.store('np_message', message) + player.store("np_message", message) dj_enabled = await self.config.guild(ctx.guild).dj_enabled() vote_enabled = await self.config.guild(ctx.guild).vote_enabled() if dj_enabled or vote_enabled: - if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx, ctx.author): + if ( + not await self._can_instaskip(ctx, ctx.author) + and not await self._is_alone(ctx, ctx.author) + ): return if player.current: @@ -372,106 +432,123 @@ class Audio: return r.message.id == message.id and u == ctx.message.author try: - (r, u) = await self.bot.wait_for('reaction_add', check=check, timeout=10.0) + (r, u) = await self.bot.wait_for("reaction_add", check=check, timeout=10.0) except asyncio.TimeoutError: return await self._clear_react(message) reacts = {v: k for k, v in emoji.items()} react = reacts[r.emoji] - if react == 'prev': + if react == "prev": await self._clear_react(message) await ctx.invoke(self.prev) - elif react == 'stop': + elif react == "stop": await self._clear_react(message) await ctx.invoke(self.stop) - elif react == 'pause': + elif react == "pause": await self._clear_react(message) await ctx.invoke(self.pause) - elif react == 'next': + elif react == "next": await self._clear_react(message) await ctx.invoke(self.skip) - @commands.command(aliases=['resume']) + @commands.command(aliases=["resume"]) async def pause(self, ctx): """Pause and resume.""" dj_enabled = await self.config.guild(ctx.guild).dj_enabled() if not self._player_check(ctx): - return await self._embed_msg(ctx, 'Nothing playing.') + return await self._embed_msg(ctx, "Nothing playing.") player = lavalink.get_player(ctx.guild.id) - if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not - await self._can_instaskip(ctx, ctx.author)): - return await self._embed_msg(ctx, 'You must be in the voice channel to pause the music.') + if ( + (not ctx.author.voice or ctx.author.voice.channel != player.channel) + and not await self._can_instaskip(ctx, ctx.author) + ): + return await self._embed_msg( + ctx, "You must be in the voice channel to pause the music." + ) if dj_enabled: - if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx, ctx.author): - return await self._embed_msg(ctx, 'You need the DJ role to pause songs.') + if ( + not await self._can_instaskip(ctx, ctx.author) + and not await self._is_alone(ctx, ctx.author) + ): + return await self._embed_msg(ctx, "You need the DJ role to pause songs.") command = ctx.invoked_with - if player.current and not player.paused and command != 'resume': + if player.current and not player.paused and command != "resume": await player.pause() embed = discord.Embed( - colour=ctx.guild.me.top_role.colour, title='Track Paused', - description='**[{}]({})**'.format( - player.current.title, - player.current.uri - ) + colour=ctx.guild.me.top_role.colour, + title="Track Paused", + description="**[{}]({})**".format(player.current.title, player.current.uri), ) return await ctx.send(embed=embed) - if player.paused and command != 'pause': + if player.paused and command != "pause": await player.pause(False) embed = discord.Embed( colour=ctx.guild.me.top_role.colour, - title='Track Resumed', - description='**[{}]({})**'.format( - player.current.title, - player.current.uri - ) + title="Track Resumed", + description="**[{}]({})**".format(player.current.title, player.current.uri), ) return await ctx.send(embed=embed) - if player.paused and command == 'pause': - return await self._embed_msg(ctx, 'Track is paused.') - if player.current and command == 'resume': - return await self._embed_msg(ctx, 'Track is playing.') - await self._embed_msg(ctx, 'Nothing playing.') + if player.paused and command == "pause": + return await self._embed_msg(ctx, "Track is paused.") + if player.current and command == "resume": + return await self._embed_msg(ctx, "Track is playing.") + await self._embed_msg(ctx, "Nothing playing.") @commands.command() async def percent(self, ctx): """Queue percentage.""" if not self._player_check(ctx): - return await self._embed_msg(ctx, 'Nothing playing.') + return await self._embed_msg(ctx, "Nothing playing.") player = lavalink.get_player(ctx.guild.id) queue_tracks = player.queue - requesters = {'total': 0, 'users': {}} + requesters = {"total": 0, "users": {}} async def _usercount(req_username): - if req_username in requesters['users']: - requesters['users'][req_username]['songcount'] += 1 - requesters['total'] += 1 + if req_username in requesters["users"]: + requesters["users"][req_username]["songcount"] += 1 + requesters["total"] += 1 else: - requesters['users'][req_username] = {} - requesters['users'][req_username]['songcount'] = 1 - requesters['total'] += 1 + requesters["users"][req_username] = {} + requesters["users"][req_username]["songcount"] = 1 + requesters["total"] += 1 for track in queue_tracks: - req_username = '{}#{}'.format(track.requester.name, track.requester.discriminator) + req_username = "{}#{}".format(track.requester.name, track.requester.discriminator) await _usercount(req_username) try: - req_username = '{}#{}'.format(player.current.requester.name, player.current.requester.discriminator) + req_username = "{}#{}".format( + player.current.requester.name, player.current.requester.discriminator + ) await _usercount(req_username) except AttributeError: - return await self._embed_msg(ctx, 'Nothing in the queue.') + return await self._embed_msg(ctx, "Nothing in the queue.") - for req_username in requesters['users']: - percentage = float(requesters['users'][req_username]['songcount']) / float(requesters['total']) - requesters['users'][req_username]['percent'] = round(percentage * 100, 1) + for req_username in requesters["users"]: + percentage = float(requesters["users"][req_username]["songcount"]) / float( + requesters["total"] + ) + requesters["users"][req_username]["percent"] = round(percentage * 100, 1) - top_queue_users = heapq.nlargest(20, [(x, requesters['users'][x][y]) for x in requesters['users'] for y in - requesters['users'][x] if y == 'percent'], key=lambda x: x[1]) + top_queue_users = heapq.nlargest( + 20, + [ + (x, requesters["users"][x][y]) + for x in requesters["users"] + for y in requesters["users"][x] + if y == "percent" + ], + key=lambda x: x[1], + ) queue_user = ["{}: {:g}%".format(x[0], x[1]) for x in top_queue_users] - queue_user_list = '\n'.join(queue_user) - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Queued and playing songs:', - description=queue_user_list) + queue_user_list = "\n".join(queue_user) + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, + title="Queued and playing songs:", + description=queue_user_list, + ) await ctx.send(embed=embed) @commands.command() @@ -484,56 +561,72 @@ class Audio: try: await lavalink.connect(ctx.author.voice.channel) player = lavalink.get_player(ctx.guild.id) - player.store('connect', datetime.datetime.utcnow()) + player.store("connect", datetime.datetime.utcnow()) except AttributeError: - return await self._embed_msg(ctx, 'Connect to a voice channel first.') + return await self._embed_msg(ctx, "Connect to a voice channel first.") if dj_enabled: if not await self._can_instaskip(ctx, ctx.author): - return await self._embed_msg(ctx, 'You need the DJ role to queue songs.') + return await self._embed_msg(ctx, "You need the DJ role to queue songs.") player = lavalink.get_player(ctx.guild.id) - player.store('channel', ctx.channel.id) - player.store('guild', ctx.guild.id) + player.store("channel", ctx.channel.id) + player.store("guild", ctx.guild.id) await self._data_check(ctx) - if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not - await self._can_instaskip(ctx, ctx.author)): - return await self._embed_msg(ctx, 'You must be in the voice channel to use the play command.') + if ( + (not ctx.author.voice or ctx.author.voice.channel != player.channel) + and not await self._can_instaskip(ctx, ctx.author) + ): + return await self._embed_msg( + ctx, "You must be in the voice channel to use the play command." + ) if not await self._currency_check(ctx, jukebox_price): return if not query: - return await self._embed_msg(ctx, 'No songs to play.') - query = query.strip('<>') - if not query.startswith('http'): - query = 'ytsearch:{}'.format(query) + return await self._embed_msg(ctx, "No songs to play.") + query = query.strip("<>") + if not query.startswith("http"): + query = "ytsearch:{}".format(query) tracks = await player.get_tracks(query) if not tracks: - return await self._embed_msg(ctx, 'Nothing found.') + return await self._embed_msg(ctx, "Nothing found.") queue_duration = await self._queue_duration(ctx) queue_total_duration = lavalink.utils.format_time(queue_duration) before_queue_length = len(player.queue) + 1 - if 'list' in query and 'ytsearch:' not in query: + if "list" in query and "ytsearch:" not in query: for track in tracks: player.add(ctx.author, track) - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Playlist Enqueued', - description='Added {} tracks to the queue.'.format(len(tracks))) + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, + title="Playlist Enqueued", + description="Added {} tracks to the queue.".format(len(tracks)), + ) if not shuffle and queue_duration > 0: - embed.set_footer(text='{} until start of playlist playback: starts at #{} in queue'.format( - queue_total_duration, before_queue_length)) + embed.set_footer( + text="{} until start of playlist playback: starts at #{} in queue".format( + queue_total_duration, before_queue_length + ) + ) if not player.current: await player.play() else: single_track = tracks[0] player.add(ctx.author, single_track) - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Track Enqueued', - description='**[{}]({})**'.format(single_track.title, single_track.uri)) + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, + title="Track Enqueued", + description="**[{}]({})**".format(single_track.title, single_track.uri), + ) if not shuffle and queue_duration > 0: - embed.set_footer(text='{} until track playback: #{} in queue'.format( - queue_total_duration, before_queue_length)) + embed.set_footer( + text="{} until track playback: #{} in queue".format( + queue_total_duration, before_queue_length + ) + ) elif queue_duration > 0: - embed.set_footer(text='#{} in queue'.format(len(player.queue) + 1)) + embed.set_footer(text="#{} in queue".format(len(player.queue) + 1)) if not player.current: await player.play() await ctx.send(embed=embed) @@ -545,86 +638,98 @@ class Audio: if ctx.invoked_subcommand is None: await ctx.send_help() - @playlist.command(name='append') + @playlist.command(name="append") async def _playlist_append(self, ctx, playlist_name, *url): """Add a song URL, playlist link, or quick search to the end of a saved playlist.""" if not await self._playlist_check(ctx): return async with self.config.guild(ctx.guild).playlists() as playlists: try: - if (playlists[playlist_name]['author'] != ctx.author.id and not - await self._can_instaskip(ctx, ctx.author)): - return await self._embed_msg(ctx, 'You are not the author of that playlist.') + if ( + playlists[playlist_name]["author"] != ctx.author.id + and not await self._can_instaskip(ctx, ctx.author) + ): + return await self._embed_msg(ctx, "You are not the author of that playlist.") player = lavalink.get_player(ctx.guild.id) to_append = await self._playlist_tracks(ctx, player, url) if not to_append: return - track_list = playlists[playlist_name]['tracks'] + track_list = playlists[playlist_name]["tracks"] if track_list: - playlists[playlist_name]['tracks'] = track_list + to_append + playlists[playlist_name]["tracks"] = track_list + to_append else: - playlists[playlist_name]['tracks'] = to_append + playlists[playlist_name]["tracks"] = to_append except KeyError: - return await self._embed_msg(ctx, 'No playlist with that name.') - if playlists[playlist_name]['playlist_url'] is not None: - playlists[playlist_name]['playlist_url'] = None + return await self._embed_msg(ctx, "No playlist with that name.") + if playlists[playlist_name]["playlist_url"] is not None: + playlists[playlist_name]["playlist_url"] = None if len(to_append) == 1: - track_title = to_append[0]['info']['title'] - return await self._embed_msg(ctx, '{} appended to {}.'.format(track_title, playlist_name)) - await self._embed_msg(ctx, '{} tracks appended to {}.'.format(len(to_append), playlist_name)) + track_title = to_append[0]["info"]["title"] + return await self._embed_msg( + ctx, "{} appended to {}.".format(track_title, playlist_name) + ) + await self._embed_msg( + ctx, "{} tracks appended to {}.".format(len(to_append), playlist_name) + ) - @playlist.command(name='create') + @playlist.command(name="create") async def _playlist_create(self, ctx, playlist_name): """Create an empty playlist.""" dj_enabled = await self.config.guild(ctx.guild).dj_enabled() if dj_enabled: if not await self._can_instaskip(ctx, ctx.author): - return await self._embed_msg(ctx, 'You need the DJ role to save playlists.') + return await self._embed_msg(ctx, "You need the DJ role to save playlists.") async with self.config.guild(ctx.guild).playlists() as playlists: if playlist_name in playlists: - return await self._embed_msg(ctx, 'Playlist name already exists, try again with a different name.') + return await self._embed_msg( + ctx, "Playlist name already exists, try again with a different name." + ) playlist_list = self._to_json(ctx, None, None) playlists[playlist_name] = playlist_list - await self._embed_msg(ctx, 'Empty playlist {} created.'.format(playlist_name)) + await self._embed_msg(ctx, "Empty playlist {} created.".format(playlist_name)) - @playlist.command(name='delete') + @playlist.command(name="delete") async def _playlist_delete(self, ctx, playlist_name): """Delete a saved playlist.""" async with self.config.guild(ctx.guild).playlists() as playlists: try: - if (playlists[playlist_name]['author'] != ctx.author.id and not - await self._can_instaskip(ctx, ctx.author)): - return await self._embed_msg(ctx, 'You are not the author of that playlist.') + if ( + playlists[playlist_name]["author"] != ctx.author.id + and not await self._can_instaskip(ctx, ctx.author) + ): + return await self._embed_msg(ctx, "You are not the author of that playlist.") del playlists[playlist_name] except KeyError: - return await self._embed_msg(ctx, 'No playlist with that name.') - await self._embed_msg(ctx, '{} playlist deleted.'.format(playlist_name)) + return await self._embed_msg(ctx, "No playlist with that name.") + await self._embed_msg(ctx, "{} playlist deleted.".format(playlist_name)) - @playlist.command(name='info') + @playlist.command(name="info") async def _playlist_info(self, ctx, playlist_name): """Retrieve information from a saved playlist.""" playlists = await self.config.guild(ctx.guild).playlists.get_raw() try: - author_id = playlists[playlist_name]['author'] + author_id = playlists[playlist_name]["author"] except KeyError: - return await self._embed_msg(ctx, 'No playlist with that name.') + return await self._embed_msg(ctx, "No playlist with that name.") author_obj = self.bot.get_user(author_id) - playlist_url = playlists[playlist_name]['playlist_url'] + playlist_url = playlists[playlist_name]["playlist_url"] try: - track_len = len(playlists[playlist_name]['tracks']) + track_len = len(playlists[playlist_name]["tracks"]) except TypeError: track_len = 0 if playlist_url is None: - playlist_url = '**Custom playlist.**' + playlist_url = "**Custom playlist.**" else: - playlist_url = 'URL: <{}>'.format(playlist_url) - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Playlist info for {}:'.format(playlist_name), - description='Author: **{}**\n{}'.format(author_obj, - playlist_url)) - embed.set_footer(text='{} track(s)'.format(track_len)) + playlist_url = "URL: <{}>".format(playlist_url) + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, + title="Playlist info for {}:".format(playlist_name), + description="Author: **{}**\n{}".format(author_obj, playlist_url), + ) + embed.set_footer(text="{} track(s)".format(track_len)) await ctx.send(embed=embed) - @playlist.command(name='list') + @playlist.command(name="list") async def _playlist_list(self, ctx): """List saved playlists.""" playlists = await self.config.guild(ctx.guild).playlists.get_raw() @@ -632,78 +737,99 @@ class Audio: for playlist_name in playlists: playlist_list.append(playlist_name) abc_names = sorted(playlist_list, key=str.lower) - all_playlists = ', '.join(abc_names) - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Playlists for {}:'.format(ctx.guild.name), - description=all_playlists) + all_playlists = ", ".join(abc_names) + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, + title="Playlists for {}:".format(ctx.guild.name), + description=all_playlists, + ) await ctx.send(embed=embed) - @playlist.command(name='queue') + @playlist.command(name="queue") async def _playlist_queue(self, ctx, playlist_name=None): """Save the queue to a playlist.""" dj_enabled = await self.config.guild(ctx.guild).dj_enabled() if dj_enabled: if not await self._can_instaskip(ctx, ctx.author): - return await self._embed_msg(ctx, 'You need the DJ role to save playlists.') + return await self._embed_msg(ctx, "You need the DJ role to save playlists.") async with self.config.guild(ctx.guild).playlists() as playlists: if playlist_name in playlists: - return await self._embed_msg(ctx, 'Playlist name already exists, try again with a different name.') + return await self._embed_msg( + ctx, "Playlist name already exists, try again with a different name." + ) if not self._player_check(ctx): - return await self._embed_msg(ctx, 'Nothing playing.') + return await self._embed_msg(ctx, "Nothing playing.") player = lavalink.get_player(ctx.guild.id) tracklist = [] - np_song = self._track_creator(player, 'np') + np_song = self._track_creator(player, "np") tracklist.append(np_song) for track in player.queue: queue_idx = player.queue.index(track) track_obj = self._track_creator(player, queue_idx) tracklist.append(track_obj) if not playlist_name: - await self._embed_msg(ctx, 'Please enter a name for this playlist.') + await self._embed_msg(ctx, "Please enter a name for this playlist.") + def check(m): return m.author == ctx.author + try: - playlist_name_msg = await ctx.bot.wait_for('message', timeout=15.0, check=check) + playlist_name_msg = await ctx.bot.wait_for("message", timeout=15.0, check=check) playlist_name = str(playlist_name_msg.content) if len(playlist_name) > 20: - return await self._embed_msg(ctx, 'Try the command again with a shorter name.') + return await self._embed_msg(ctx, "Try the command again with a shorter name.") if playlist_name in playlists: - return await self._embed_msg(ctx, 'Playlist name already exists, try again with a different name.') + return await self._embed_msg( + ctx, "Playlist name already exists, try again with a different name." + ) except asyncio.TimeoutError: - return await self._embed_msg(ctx, 'No playlist name entered, try again later.') + return await self._embed_msg(ctx, "No playlist name entered, try again later.") playlist_list = self._to_json(ctx, None, tracklist) async with self.config.guild(ctx.guild).playlists() as playlists: playlists[playlist_name] = playlist_list - await self._embed_msg(ctx, 'Playlist {} saved from current queue: {} tracks added.'.format( - playlist_name, len(tracklist))) + await self._embed_msg( + ctx, + "Playlist {} saved from current queue: {} tracks added.".format( + playlist_name, len(tracklist) + ), + ) - @playlist.command(name='remove') + @playlist.command(name="remove") async def _playlist_remove(self, ctx, playlist_name, url): """Remove a song from a playlist by url.""" async with self.config.guild(ctx.guild).playlists() as playlists: try: - if (playlists[playlist_name]['author'] != ctx.author.id and not - await self._can_instaskip(ctx, ctx.author)): - return await self._embed_msg(ctx, 'You are not the author of that playlist.') + if ( + playlists[playlist_name]["author"] != ctx.author.id + and not await self._can_instaskip(ctx, ctx.author) + ): + return await self._embed_msg(ctx, "You are not the author of that playlist.") except KeyError: - return await self._embed_msg(ctx, 'No playlist with that name.') - track_list = playlists[playlist_name]['tracks'] - clean_list = [track for track in track_list if not url == track['info']['uri']] - if len(playlists[playlist_name]['tracks']) == len(clean_list): - return await self._embed_msg(ctx, 'URL not in playlist.') - del_count = len(playlists[playlist_name]['tracks']) - len(clean_list) + return await self._embed_msg(ctx, "No playlist with that name.") + track_list = playlists[playlist_name]["tracks"] + clean_list = [track for track in track_list if not url == track["info"]["uri"]] + if len(playlists[playlist_name]["tracks"]) == len(clean_list): + return await self._embed_msg(ctx, "URL not in playlist.") + del_count = len(playlists[playlist_name]["tracks"]) - len(clean_list) if not clean_list: del playlists[playlist_name] - return await self._embed_msg(ctx, 'No songs left, removing playlist.') - playlists[playlist_name]['tracks'] = clean_list - if playlists[playlist_name]['playlist_url'] is not None: - playlists[playlist_name]['playlist_url'] = None + return await self._embed_msg(ctx, "No songs left, removing playlist.") + playlists[playlist_name]["tracks"] = clean_list + if playlists[playlist_name]["playlist_url"] is not None: + playlists[playlist_name]["playlist_url"] = None if del_count > 1: - await self._embed_msg(ctx, '{} entries have been removed from the {} playlist.'.format( - del_count, playlist_name)) + await self._embed_msg( + ctx, + "{} entries have been removed from the {} playlist.".format( + del_count, playlist_name + ), + ) else: - await self._embed_msg(ctx, 'The track has been removed from the {} playlist.'.format(playlist_name)) + await self._embed_msg( + ctx, "The track has been removed from the {} playlist.".format(playlist_name) + ) - @playlist.command(name='save') + @playlist.command(name="save") async def _playlist_save(self, ctx, playlist_name, playlist_url): """Save a playlist from a url.""" if not await self._playlist_check(ctx): @@ -714,10 +840,12 @@ class Audio: if tracklist is not None: async with self.config.guild(ctx.guild).playlists() as playlists: playlists[playlist_name] = playlist_list - return await self._embed_msg(ctx, 'Playlist {} saved: {} tracks added.'.format( - playlist_name, len(tracklist))) + return await self._embed_msg( + ctx, + "Playlist {} saved: {} tracks added.".format(playlist_name, len(tracklist)), + ) - @playlist.command(name='start') + @playlist.command(name="start") async def _playlist_start(self, ctx, playlist_name=None): """Load a playlist into the queue.""" if not await self._playlist_check(ctx): @@ -730,57 +858,69 @@ class Audio: for track in playlists[playlist_name]["tracks"]: player.add(author_obj, lavalink.rest_api.Track(data=track)) track_count = track_count + 1 - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Playlist Enqueued', - description='Added {} tracks to the queue.'.format(track_count)) + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, + title="Playlist Enqueued", + description="Added {} tracks to the queue.".format(track_count), + ) await ctx.send(embed=embed) if not player.current: await player.play() except TypeError: await ctx.invoke(self.play, query=playlists[playlist_name]["playlist_url"]) except KeyError: - await self._embed_msg(ctx, 'That playlist doesn\'t exist.') + await self._embed_msg(ctx, "That playlist doesn't exist.") @checks.is_owner() - @playlist.command(name='upload') + @playlist.command(name="upload") async def _playlist_upload(self, ctx): """Convert a Red v2 playlist file to a playlist.""" if not await self._playlist_check(ctx): return player = lavalink.get_player(ctx.guild.id) - await self._embed_msg(ctx, 'Please upload the playlist file. Any other message will cancel this operation.') + await self._embed_msg( + ctx, "Please upload the playlist file. Any other message will cancel this operation." + ) def check(m): return m.author == ctx.author try: - file_message = await ctx.bot.wait_for('message', timeout=30.0, check=check) + file_message = await ctx.bot.wait_for("message", timeout=30.0, check=check) except asyncio.TimeoutError: - return await self._embed_msg(ctx, 'No file detected, try again later.') + return await self._embed_msg(ctx, "No file detected, try again later.") try: file_url = file_message.attachments[0].url except IndexError: - return await self._embed_msg(ctx, 'Upload canceled.') - v2_playlist_name = (file_url.split('/')[6]).split('.')[0] - file_suffix = file_url.rsplit('.', 1)[1] + return await self._embed_msg(ctx, "Upload canceled.") + v2_playlist_name = (file_url.split("/")[6]).split(".")[0] + file_suffix = file_url.rsplit(".", 1)[1] if file_suffix != "txt": - return await self._embed_msg(ctx, 'Only playlist files can be uploaded.') - async with self.session.request('GET', file_url) as r: - v2_playlist = await r.json(content_type='text/plain') + return await self._embed_msg(ctx, "Only playlist files can be uploaded.") + async with self.session.request("GET", file_url) as r: + v2_playlist = await r.json(content_type="text/plain") try: v2_playlist_url = v2_playlist["link"] except KeyError: v2_playlist_url = None - if (not v2_playlist_url or not self._match_yt_playlist(v2_playlist_url) or not - await player.get_tracks(v2_playlist_url)): + if ( + not v2_playlist_url + or not self._match_yt_playlist(v2_playlist_url) + or not await player.get_tracks(v2_playlist_url) + ): track_list = [] track_count = 0 async with self.config.guild(ctx.guild).playlists() as v3_playlists: try: if v3_playlists[v2_playlist_name]: - return await self._embed_msg(ctx, 'A playlist already exists with this name.') + return await self._embed_msg( + ctx, "A playlist already exists with this name." + ) except KeyError: pass - embed1 = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Please wait, adding tracks...') + embed1 = discord.Embed( + colour=ctx.guild.me.top_role.colour, title="Please wait, adding tracks..." + ) playlist_msg = await ctx.send(embed=embed1) for song_url in v2_playlist["playlist"]: track = await player.get_tracks(song_url) @@ -791,21 +931,29 @@ class Audio: except IndexError: pass if track_count % 5 == 0: - embed2 = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Loading track {}/{}...'.format( - track_count, len(v2_playlist["playlist"]))) + embed2 = discord.Embed( + colour=ctx.guild.me.top_role.colour, + title="Loading track {}/{}...".format( + track_count, len(v2_playlist["playlist"]) + ), + ) await playlist_msg.edit(embed=embed2) if not track_list: - return await self._embed_msg(ctx, 'No tracks found.') + return await self._embed_msg(ctx, "No tracks found.") playlist_list = self._to_json(ctx, v2_playlist_url, track_list) async with self.config.guild(ctx.guild).playlists() as v3_playlists: v3_playlists[v2_playlist_name] = playlist_list if len(v2_playlist["playlist"]) != track_count: bad_tracks = len(v2_playlist["playlist"]) - track_count - msg = ('Added {} tracks from the {} playlist. {} track(s) could not ' - 'be loaded.'.format(track_count, v2_playlist_name, bad_tracks)) + msg = ( + "Added {} tracks from the {} playlist. {} track(s) could not " + "be loaded.".format(track_count, v2_playlist_name, bad_tracks) + ) else: - msg = 'Added {} tracks from the {} playlist.'.format(track_count, v2_playlist_name) - embed3 = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Playlist Saved', description=msg) + msg = "Added {} tracks from the {} playlist.".format(track_count, v2_playlist_name) + embed3 = discord.Embed( + colour=ctx.guild.me.top_role.colour, title="Playlist Saved", description=msg + ) await playlist_msg.edit(embed=embed3) else: await ctx.invoke(self._playlist_save, v2_playlist_name, v2_playlist_url) @@ -815,22 +963,26 @@ class Audio: jukebox_price = await self.config.guild(ctx.guild).jukebox_price() if dj_enabled: if not await self._can_instaskip(ctx, ctx.author): - await self._embed_msg(ctx, 'You need the DJ role to use playlists.') + await self._embed_msg(ctx, "You need the DJ role to use playlists.") return False if not self._player_check(ctx): try: await lavalink.connect(ctx.author.voice.channel) player = lavalink.get_player(ctx.guild.id) - player.store('connect', datetime.datetime.utcnow()) + player.store("connect", datetime.datetime.utcnow()) except AttributeError: - await self._embed_msg(ctx, 'Connect to a voice channel first.') + await self._embed_msg(ctx, "Connect to a voice channel first.") return False player = lavalink.get_player(ctx.guild.id) - player.store('channel', ctx.channel.id) - player.store('guild', ctx.guild.id) - if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not - await self._can_instaskip(ctx, ctx.author)): - await self._embed_msg(ctx, 'You must be in the voice channel to use the playlist command.') + player.store("channel", ctx.channel.id) + player.store("guild", ctx.guild.id) + if ( + (not ctx.author.voice or ctx.author.voice.channel != player.channel) + and not await self._can_instaskip(ctx, ctx.author) + ): + await self._embed_msg( + ctx, "You must be in the voice channel to use the playlist command." + ) return False if not await self._currency_check(ctx, jukebox_price): return False @@ -841,13 +993,13 @@ class Audio: search = False if type(query) is tuple: query = " ".join(query) - if not query.startswith('http'): + if not query.startswith("http"): query = " ".join(query) - query = 'ytsearch:{}'.format(query) + query = "ytsearch:{}".format(query) search = True tracks = await player.get_tracks(query) if not tracks: - return await self._embed_msg(ctx, 'Nothing found.') + return await self._embed_msg(ctx, "Nothing found.") tracklist = [] if not search: for track in tracks: @@ -862,23 +1014,30 @@ class Audio: async def prev(self, ctx): """Skips to the start of the previously played track.""" if not self._player_check(ctx): - return await self._embed_msg(ctx, 'Nothing playing.') + return await self._embed_msg(ctx, "Nothing playing.") dj_enabled = await self.config.guild(ctx.guild).dj_enabled() player = lavalink.get_player(ctx.guild.id) shuffle = await self.config.guild(ctx.guild).shuffle() if dj_enabled: - if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx, ctx.author): - return await self._embed_msg(ctx, 'You need the DJ role to skip songs.') - if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not - await self._can_instaskip(ctx, ctx.author)): - return await self._embed_msg(ctx, 'You must be in the voice channel to skip the music.') + if ( + not await self._can_instaskip(ctx, ctx.author) + and not await self._is_alone(ctx, ctx.author) + ): + return await self._embed_msg(ctx, "You need the DJ role to skip songs.") + if ( + (not ctx.author.voice or ctx.author.voice.channel != player.channel) + and not await self._can_instaskip(ctx, ctx.author) + ): + return await self._embed_msg( + ctx, "You must be in the voice channel to skip the music." + ) if shuffle: - return await self._embed_msg(ctx, 'Turn shuffle off to use this command.') - if player.fetch('prev_song') is None: - return await self._embed_msg(ctx, 'No previous track.') + return await self._embed_msg(ctx, "Turn shuffle off to use this command.") + if player.fetch("prev_song") is None: + return await self._embed_msg(ctx, "No previous track.") else: - last_track = await player.get_tracks(player.fetch('prev_song')) - player.add(player.fetch('prev_requester'), last_track[0]) + last_track = await player.get_tracks(player.fetch("prev_song")) + player.add(player.fetch("prev_requester"), last_track[0]) queue_len = len(player.queue) bump_song = player.queue[-1] player.queue.insert(0, bump_song) @@ -886,20 +1045,19 @@ class Audio: await player.skip() embed = discord.Embed( colour=ctx.guild.me.top_role.colour, - title='Replaying Track', description='**[{}]({})**'.format( - player.current.title, player.current.uri - ) + title="Replaying Track", + description="**[{}]({})**".format(player.current.title, player.current.uri), ) await ctx.send(embed=embed) - @commands.command(aliases=['q']) - async def queue(self, ctx, page: int=1): + @commands.command(aliases=["q"]) + async def queue(self, ctx, page: int = 1): """Lists the queue.""" if not self._player_check(ctx): - return await self._embed_msg(ctx, 'There\'s nothing in the queue.') + return await self._embed_msg(ctx, "There's nothing in the queue.") player = lavalink.get_player(ctx.guild.id) if not player.queue: - return await self._embed_msg(ctx, 'There\'s nothing in the queue.') + return await self._embed_msg(ctx, "There's nothing in the queue.") len_queue_pages = math.ceil(len(player.queue) / 10) queue_page_list = [] for page_num in range(1, len_queue_pages + 1): @@ -915,52 +1073,55 @@ class Audio: queue_num_pages = math.ceil(len(player.queue) / 10) queue_idx_start = (page_num - 1) * 10 queue_idx_end = queue_idx_start + 10 - queue_list = '' + queue_list = "" try: arrow = await self._draw_time(ctx) except AttributeError: - return await self._embed_msg(ctx, 'There\'s nothing in the queue.') + return await self._embed_msg(ctx, "There's nothing in the queue.") pos = lavalink.utils.format_time(player.position) if player.current.is_stream: - dur = 'LIVE' + dur = "LIVE" else: dur = lavalink.utils.format_time(player.current.length) if player.current.is_stream: - queue_list += '**Currently livestreaming:** **[{}]({})**\nRequested by: **{}**\n\n{}`{}`/`{}`\n\n'.format( - player.current.title, - player.current.uri, - player.current.requester, - arrow, pos, dur + queue_list += "**Currently livestreaming:** **[{}]({})**\nRequested by: **{}**\n\n{}`{}`/`{}`\n\n".format( + player.current.title, player.current.uri, player.current.requester, arrow, pos, dur ) else: - queue_list += 'Playing: **[{}]({})**\nRequested by: **{}**\n\n{}`{}`/`{}`\n\n'.format( - player.current.title, - player.current.uri, - player.current.requester, - arrow, pos, dur + queue_list += "Playing: **[{}]({})**\nRequested by: **{}**\n\n{}`{}`/`{}`\n\n".format( + player.current.title, player.current.uri, player.current.requester, arrow, pos, dur ) - for i, track in enumerate(player.queue[queue_idx_start:queue_idx_end], start=queue_idx_start): + for i, track in enumerate( + player.queue[queue_idx_start:queue_idx_end], start=queue_idx_start + ): if len(track.title) > 40: - track_title = str(track.title).replace('[', '') - track_title = '{}...'.format((track_title[:40]).rstrip(' ')) + track_title = str(track.title).replace("[", "") + track_title = "{}...".format((track_title[:40]).rstrip(" ")) else: track_title = track.title req_user = track.requester track_idx = i + 1 - queue_list += '`{}.` **[{}]({})**, requested by **{}**\n'.format(track_idx, track_title, track.uri, req_user) + queue_list += "`{}.` **[{}]({})**, requested by **{}**\n".format( + track_idx, track_title, track.uri, req_user + ) - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Queue for ' + ctx.guild.name, - description=queue_list) + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, + title="Queue for " + ctx.guild.name, + description=queue_list, + ) queue_duration = await self._queue_duration(ctx) queue_total_duration = lavalink.utils.format_time(queue_duration) - text = 'Page {}/{} | {} tracks, {} remaining'.format(page_num, queue_num_pages, len(player.queue) + 1, queue_total_duration) + text = "Page {}/{} | {} tracks, {} remaining".format( + page_num, queue_num_pages, len(player.queue) + 1, queue_total_duration + ) if repeat: - text += ' | Repeat: \N{WHITE HEAVY CHECK MARK}' + text += " | Repeat: \N{WHITE HEAVY CHECK MARK}" if shuffle: - text += ' | Shuffle: \N{WHITE HEAVY CHECK MARK}' + text += " | Shuffle: \N{WHITE HEAVY CHECK MARK}" embed.set_footer(text=text) return embed @@ -969,39 +1130,52 @@ class Audio: """Toggles repeat.""" dj_enabled = await self.config.guild(ctx.guild).dj_enabled() if dj_enabled: - if not await self._can_instaskip(ctx, ctx.author) and not await self._has_dj_role(ctx, ctx.author): - return await self._embed_msg(ctx, 'You need the DJ role to toggle repeat.') + if ( + not await self._can_instaskip(ctx, ctx.author) + and not await self._has_dj_role(ctx, ctx.author) + ): + return await self._embed_msg(ctx, "You need the DJ role to toggle repeat.") repeat = await self.config.guild(ctx.guild).repeat() await self.config.guild(ctx.guild).repeat.set(not repeat) repeat = await self.config.guild(ctx.guild).repeat() if self._player_check(ctx): await self._data_check(ctx) player = lavalink.get_player(ctx.guild.id) - if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not - await self._can_instaskip(ctx, ctx.author)): - return await self._embed_msg(ctx, 'You must be in the voice channel to toggle repeat.') - await self._embed_msg(ctx, 'Repeat songs: {}.'.format(repeat)) + if ( + (not ctx.author.voice or ctx.author.voice.channel != player.channel) + and not await self._can_instaskip(ctx, ctx.author) + ): + return await self._embed_msg( + ctx, "You must be in the voice channel to toggle repeat." + ) + await self._embed_msg(ctx, "Repeat songs: {}.".format(repeat)) @commands.command() async def remove(self, ctx, index: int): """Remove a specific song number from the queue.""" dj_enabled = await self.config.guild(ctx.guild).dj_enabled() if not self._player_check(ctx): - return await self._embed_msg(ctx, 'Nothing playing.') + return await self._embed_msg(ctx, "Nothing playing.") player = lavalink.get_player(ctx.guild.id) if not player.queue: - return await self._embed_msg(ctx, 'Nothing queued.') + return await self._embed_msg(ctx, "Nothing queued.") if dj_enabled: if not await self._can_instaskip(ctx, ctx.author): - return await self._embed_msg(ctx, 'You need the DJ role to remove songs.') - if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not - await self._can_instaskip(ctx, ctx.author)): - return await self._embed_msg(ctx, 'You must be in the voice channel to manage the queue.') + return await self._embed_msg(ctx, "You need the DJ role to remove songs.") + if ( + (not ctx.author.voice or ctx.author.voice.channel != player.channel) + and not await self._can_instaskip(ctx, ctx.author) + ): + return await self._embed_msg( + ctx, "You must be in the voice channel to manage the queue." + ) if index > len(player.queue) or index < 1: - return await self._embed_msg(ctx, 'Song number must be greater than 1 and within the queue limit.') + return await self._embed_msg( + ctx, "Song number must be greater than 1 and within the queue limit." + ) index -= 1 removed = player.queue.pop(index) - await self._embed_msg(ctx, 'Removed {} from the queue.'.format(removed.title)) + await self._embed_msg(ctx, "Removed {} from the queue.".format(removed.title)) @commands.command() async def search(self, ctx, *, query): @@ -1013,43 +1187,50 @@ class Audio: try: await lavalink.connect(ctx.author.voice.channel) player = lavalink.get_player(ctx.guild.id) - player.store('connect', datetime.datetime.utcnow()) + player.store("connect", datetime.datetime.utcnow()) except AttributeError: - return await self._embed_msg(ctx, 'Connect to a voice channel first.') + return await self._embed_msg(ctx, "Connect to a voice channel first.") player = lavalink.get_player(ctx.guild.id) shuffle = await self.config.guild(ctx.guild).shuffle() - player.store('channel', ctx.channel.id) - player.store('guild', ctx.guild.id) - if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not - await self._can_instaskip(ctx, ctx.author)): - return await self._embed_msg(ctx, 'You must be in the voice channel to enqueue songs.') + player.store("channel", ctx.channel.id) + player.store("guild", ctx.guild.id) + if ( + (not ctx.author.voice or ctx.author.voice.channel != player.channel) + and not await self._can_instaskip(ctx, ctx.author) + ): + return await self._embed_msg(ctx, "You must be in the voice channel to enqueue songs.") await self._data_check(ctx) - query = query.strip('<>') - if query.startswith('list '): - query = 'ytsearch:{}'.format(query.lstrip('list ')) + query = query.strip("<>") + if query.startswith("list "): + query = "ytsearch:{}".format(query.lstrip("list ")) tracks = await player.get_tracks(query) if not tracks: - return await self._embed_msg(ctx, 'Nothing found 👀') - songembed = discord.Embed(colour=ctx.guild.me.top_role.colour, - title='Queued {} track(s).'.format(len(tracks))) + return await self._embed_msg(ctx, "Nothing found 👀") + songembed = discord.Embed( + colour=ctx.guild.me.top_role.colour, + title="Queued {} track(s).".format(len(tracks)), + ) queue_duration = await self._queue_duration(ctx) queue_total_duration = lavalink.utils.format_time(queue_duration) if not shuffle and queue_duration > 0: - songembed.set_footer(text='{} until start of search playback: starts at #{} in queue'.format( - queue_total_duration, (len(player.queue) + 1))) + songembed.set_footer( + text="{} until start of search playback: starts at #{} in queue".format( + queue_total_duration, (len(player.queue) + 1) + ) + ) for track in tracks: player.add(ctx.author, track) if not player.current: await player.play() return await ctx.send(embed=songembed) - if query.startswith('sc '): - query = 'scsearch:{}'.format(query.lstrip('sc ')) - elif not query.startswith('http'): - query = 'ytsearch:{}'.format(query) + if query.startswith("sc "): + query = "scsearch:{}".format(query.lstrip("sc ")) + elif not query.startswith("http"): + query = "ytsearch:{}".format(query) tracks = await player.get_tracks(query) if not tracks: - return await self._embed_msg(ctx, 'Nothing found 👀') + return await self._embed_msg(ctx, "Nothing found 👀") len_search_pages = math.ceil(len(tracks) / 5) search_page_list = [] @@ -1062,9 +1243,15 @@ class Audio: if not await self._can_instaskip(ctx, ctx.author): return await menu(ctx, search_page_list, DEFAULT_CONTROLS) - async def _search_menu(ctx: commands.Context, pages: list, - controls: dict, message: discord.Message, page: int, - timeout: float, emoji: str): + async def _search_menu( + ctx: commands.Context, + pages: list, + controls: dict, + message: discord.Message, + page: int, + timeout: float, + emoji: str, + ): if message: await _search_button_action(ctx, tracks, emoji, page) await message.delete() @@ -1078,8 +1265,8 @@ class Audio: "5⃣": _search_menu, "⬅": prev_page, "❌": close_menu, - "➡": next_page - } + "➡": next_page, + } async def _search_button_action(ctx, tracks, emoji, page): player = lavalink.get_player(ctx.guild.id) @@ -1101,15 +1288,21 @@ class Audio: except IndexError: search_choice = tracks[-1] - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Track Enqueued', - description='**[{}]({})**'.format(search_choice.title, search_choice.uri)) + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, + title="Track Enqueued", + description="**[{}]({})**".format(search_choice.title, search_choice.uri), + ) queue_duration = await self._queue_duration(ctx) queue_total_duration = lavalink.utils.format_time(queue_duration) if not shuffle and queue_duration > 0: - embed.set_footer(text='{} until track playback: #{} in queue'.format(queue_total_duration, ( - len(player.queue) + 1))) + embed.set_footer( + text="{} until track playback: #{} in queue".format( + queue_total_duration, (len(player.queue) + 1) + ) + ) elif queue_duration > 0: - embed.set_footer(text='#{} in queue'.format(len(player.queue) + 1)) + embed.set_footer(text="#{} in queue".format(len(player.queue) + 1)) player.add(ctx.author, search_choice) if not player.current: @@ -1122,44 +1315,57 @@ class Audio: search_num_pages = math.ceil(len(tracks) / 5) search_idx_start = (page_num - 1) * 5 search_idx_end = search_idx_start + 5 - search_list = '' + search_list = "" for i, track in enumerate(tracks[search_idx_start:search_idx_end], start=search_idx_start): search_track_num = i + 1 if search_track_num > 5: search_track_num = search_track_num % 5 if search_track_num == 0: search_track_num = 5 - search_list += '`{0}.` **[{1}]({2})**\n'.format(search_track_num, track.title, track.uri) - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Tracks Found:', description=search_list) - embed.set_footer(text='Page {}/{} | {} search results'.format(page_num, search_num_pages, len(tracks))) + search_list += "`{0}.` **[{1}]({2})**\n".format( + search_track_num, track.title, track.uri + ) + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, title="Tracks Found:", description=search_list + ) + embed.set_footer( + text="Page {}/{} | {} search results".format(page_num, search_num_pages, len(tracks)) + ) return embed @commands.command() - async def seek(self, ctx, seconds: int=30): + async def seek(self, ctx, seconds: int = 30): """Seeks ahead or behind on a track by seconds.""" dj_enabled = await self.config.guild(ctx.guild).dj_enabled() if not self._player_check(ctx): - return await self._embed_msg(ctx, 'Nothing playing.') + return await self._embed_msg(ctx, "Nothing playing.") player = lavalink.get_player(ctx.guild.id) - if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not - await self._can_instaskip(ctx, ctx.author)): - return await self._embed_msg(ctx, 'You must be in the voice channel to use seek.') + if ( + (not ctx.author.voice or ctx.author.voice.channel != player.channel) + and not await self._can_instaskip(ctx, ctx.author) + ): + return await self._embed_msg(ctx, "You must be in the voice channel to use seek.") if dj_enabled: - if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx, ctx.author): - return await self._embed_msg(ctx, 'You need the DJ role to use seek.') + if ( + not await self._can_instaskip(ctx, ctx.author) + and not await self._is_alone(ctx, ctx.author) + ): + return await self._embed_msg(ctx, "You need the DJ role to use seek.") if player.current: if player.current.is_stream: - return await self._embed_msg(ctx, 'Can\'t seek on a stream.') + return await self._embed_msg(ctx, "Can't seek on a stream.") else: time_sec = seconds * 1000 seek = player.position + time_sec if seek <= 0: - await self._embed_msg(ctx, 'Moved {}s to 00:00:00'.format(seconds)) + await self._embed_msg(ctx, "Moved {}s to 00:00:00".format(seconds)) else: - await self._embed_msg(ctx, 'Moved {}s to {}'.format(seconds, lavalink.utils.format_time(seek))) + await self._embed_msg( + ctx, "Moved {}s to {}".format(seconds, lavalink.utils.format_time(seek)) + ) return await player.seek(seek) else: - await self._embed_msg(ctx, 'Nothing playing.') + await self._embed_msg(ctx, "Nothing playing.") @commands.command() async def shuffle(self, ctx): @@ -1167,32 +1373,40 @@ class Audio: dj_enabled = await self.config.guild(ctx.guild).dj_enabled() if dj_enabled: if not await self._can_instaskip(ctx, ctx.author): - return await self._embed_msg(ctx, 'You need the DJ role to toggle shuffle.') + return await self._embed_msg(ctx, "You need the DJ role to toggle shuffle.") shuffle = await self.config.guild(ctx.guild).shuffle() await self.config.guild(ctx.guild).shuffle.set(not shuffle) shuffle = await self.config.guild(ctx.guild).shuffle() if self._player_check(ctx): await self._data_check(ctx) player = lavalink.get_player(ctx.guild.id) - if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not - await self._can_instaskip(ctx, ctx.author)): - return await self._embed_msg(ctx, 'You must be in the voice channel to toggle shuffle.') - await self._embed_msg(ctx, 'Shuffle songs: {}.'.format(shuffle)) + if ( + (not ctx.author.voice or ctx.author.voice.channel != player.channel) + and not await self._can_instaskip(ctx, ctx.author) + ): + return await self._embed_msg( + ctx, "You must be in the voice channel to toggle shuffle." + ) + await self._embed_msg(ctx, "Shuffle songs: {}.".format(shuffle)) - @commands.command(aliases=['forceskip', 'fs']) + @commands.command(aliases=["forceskip", "fs"]) async def skip(self, ctx): """Skips to the next track.""" if not self._player_check(ctx): - return await self._embed_msg(ctx, 'Nothing playing.') + return await self._embed_msg(ctx, "Nothing playing.") player = lavalink.get_player(ctx.guild.id) - if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not - await self._can_instaskip(ctx, ctx.author)): - return await self._embed_msg(ctx, 'You must be in the voice channel to skip the music.') + if ( + (not ctx.author.voice or ctx.author.voice.channel != player.channel) + and not await self._can_instaskip(ctx, ctx.author) + ): + return await self._embed_msg( + ctx, "You must be in the voice channel to skip the music." + ) dj_enabled = await self.config.guild(ctx.guild).dj_enabled() vote_enabled = await self.config.guild(ctx.guild).vote_enabled() if dj_enabled and not vote_enabled and not await self._can_instaskip(ctx, ctx.author): if not await self._is_alone(ctx, ctx.author): - return await self._embed_msg(ctx, 'You need the DJ role to skip songs.') + return await self._embed_msg(ctx, "You need the DJ role to skip songs.") if vote_enabled: if not await self._can_instaskip(ctx, ctx.author): if ctx.author.id in self.skip_votes[ctx.message.guild]: @@ -1236,7 +1450,9 @@ class Audio: is_owner = member.id == self.bot.owner_id is_server_owner = member.id == ctx.guild.owner_id is_coowner = any(x == member.id for x in self.bot._co_owners) - is_admin = discord.utils.get(ctx.guild.get_member(member.id).roles, id=admin_role) is not None + is_admin = discord.utils.get( + ctx.guild.get_member(member.id).roles, id=admin_role + ) is not None is_mod = discord.utils.get(ctx.guild.get_member(member.id).roles, id=mod_role) is not None is_bot = member.bot is True @@ -1251,7 +1467,9 @@ class Audio: nonbots = nonbots + 1 except AttributeError: if ctx.guild.get_member(self.bot.user.id).voice is not None: - nonbots = sum(not m.bot for m in ctx.guild.get_member(self.bot.user.id).voice.channel.members) + nonbots = sum( + not m.bot for m in ctx.guild.get_member(self.bot.user.id).voice.channel.members + ) if nonbots == 1: nonbots = 2 elif ctx.guild.get_member(member.id).voice.channel.members == 1: @@ -1275,66 +1493,88 @@ class Audio: pos, dur = player.position, player.current.length time_remain = lavalink.utils.format_time(dur - pos) if player.current.is_stream: - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='There\'s nothing in the queue.') - embed.set_footer(text='Currently livestreaming {}'.format(player.current.title)) + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, title="There's nothing in the queue." + ) + embed.set_footer(text="Currently livestreaming {}".format(player.current.title)) else: - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='There\'s nothing in the queue.') - embed.set_footer(text='{} left on {}'.format(time_remain, player.current.title)) + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, title="There's nothing in the queue." + ) + embed.set_footer(text="{} left on {}".format(time_remain, player.current.title)) return await ctx.send(embed=embed) embed = discord.Embed( - colour=ctx.guild.me.top_role.colour, title='Track Skipped', - description='**[{}]({})**'.format( - player.current.title, player.current.uri - ) + colour=ctx.guild.me.top_role.colour, + title="Track Skipped", + description="**[{}]({})**".format(player.current.title, player.current.uri), ) await ctx.send(embed=embed) await player.skip() - @commands.command(aliases=['s']) + @commands.command(aliases=["s"]) async def stop(self, ctx): """Stops playback and clears the queue.""" dj_enabled = await self.config.guild(ctx.guild).dj_enabled() vote_enabled = await self.config.guild(ctx.guild).vote_enabled() if not self._player_check(ctx): - return await self._embed_msg(ctx, 'Nothing playing.') + return await self._embed_msg(ctx, "Nothing playing.") player = lavalink.get_player(ctx.guild.id) - if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not - await self._can_instaskip(ctx, ctx.author)): - return await self._embed_msg(ctx, 'You must be in the voice channel to stop the music.') + if ( + (not ctx.author.voice or ctx.author.voice.channel != player.channel) + and not await self._can_instaskip(ctx, ctx.author) + ): + return await self._embed_msg( + ctx, "You must be in the voice channel to stop the music." + ) if vote_enabled or vote_enabled and dj_enabled: - if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx, ctx.author): - return await self._embed_msg(ctx, 'There are other people listening - vote to skip instead.') + if ( + not await self._can_instaskip(ctx, ctx.author) + and not await self._is_alone(ctx, ctx.author) + ): + return await self._embed_msg( + ctx, "There are other people listening - vote to skip instead." + ) if dj_enabled and not vote_enabled: if not await self._can_instaskip(ctx, ctx.author): - return await self._embed_msg(ctx, 'You need the DJ role to stop the music.') + return await self._embed_msg(ctx, "You need the DJ role to stop the music.") if player.is_playing: - await self._embed_msg(ctx, 'Stopping...') + await self._embed_msg(ctx, "Stopping...") await player.stop() - player.store('prev_requester', None) - player.store('prev_song', None) - player.store('playing_song', None) - player.store('requester', None) + player.store("prev_requester", None) + player.store("prev_song", None) + player.store("playing_song", None) + player.store("requester", None) @commands.command() - async def volume(self, ctx, vol: int=None): + async def volume(self, ctx, vol: int = None): """Sets the volume, 1% - 150%.""" dj_enabled = await self.config.guild(ctx.guild).dj_enabled() if not vol: vol = await self.config.guild(ctx.guild).volume() - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Current Volume:', - description=str(vol) + '%') + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, + title="Current Volume:", + description=str(vol) + "%", + ) if not self._player_check(ctx): - embed.set_footer(text='Nothing playing.') + embed.set_footer(text="Nothing playing.") return await ctx.send(embed=embed) if self._player_check(ctx): player = lavalink.get_player(ctx.guild.id) - if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not - await self._can_instaskip(ctx, ctx.author)): - return await self._embed_msg(ctx, 'You must be in the voice channel to change the volume.') + if ( + (not ctx.author.voice or ctx.author.voice.channel != player.channel) + and not await self._can_instaskip(ctx, ctx.author) + ): + return await self._embed_msg( + ctx, "You must be in the voice channel to change the volume." + ) if dj_enabled: - if not await self._can_instaskip(ctx, ctx.author) and not await self._has_dj_role(ctx, ctx.author): - return await self._embed_msg(ctx, 'You need the DJ role to change the volume.') + if ( + not await self._can_instaskip(ctx, ctx.author) + and not await self._has_dj_role(ctx, ctx.author) + ): + return await self._embed_msg(ctx, "You need the DJ role to change the volume.") if vol > 150: vol = 150 await self.config.guild(ctx.guild).volume.set(vol) @@ -1344,13 +1584,14 @@ class Audio: await self.config.guild(ctx.guild).volume.set(vol) if self._player_check(ctx): await lavalink.get_player(ctx.guild.id).set_volume(vol) - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Volume:', - description=str(vol) + '%') + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, title="Volume:", description=str(vol) + "%" + ) if not self._player_check(ctx): - embed.set_footer(text='Nothing playing.') + embed.set_footer(text="Nothing playing.") await ctx.send(embed=embed) - @commands.group(aliases=['llset']) + @commands.group(aliases=["llset"]) @checks.is_owner() async def llsetup(self, ctx): """Lavalink server configuration options.""" @@ -1363,62 +1604,72 @@ class Audio: external = await self.config.use_external_lavalink() await self.config.use_external_lavalink.set(not external) if external: - await self.config.host.set('localhost') - await self.config.password.set('youshallnotpass') + await self.config.host.set("localhost") + await self.config.password.set("youshallnotpass") await self.config.rest_port.set(2333) await self.config.ws_port.set(2332) - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='External lavalink server: {}.'.format( - not external)) - embed.set_footer(text='Defaults reset.') + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, + title="External lavalink server: {}.".format(not external), + ) + embed.set_footer(text="Defaults reset.") return await ctx.send(embed=embed) else: - await self._embed_msg(ctx, 'External lavalink server: {}.'.format(not external)) + await self._embed_msg(ctx, "External lavalink server: {}.".format(not external)) @llsetup.command() async def host(self, ctx, host): """Set the lavalink server host.""" await self.config.host.set(host) if await self._check_external(): - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Host set to {}.'.format(host)) - embed.set_footer(text='External lavalink server set to True.') + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, title="Host set to {}.".format(host) + ) + embed.set_footer(text="External lavalink server set to True.") await ctx.send(embed=embed) else: - await self._embed_msg(ctx, 'Host set to {}.'.format(host)) + await self._embed_msg(ctx, "Host set to {}.".format(host)) @llsetup.command() async def password(self, ctx, password): """Set the lavalink server password.""" await self.config.password.set(str(password)) if await self._check_external(): - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, - title='Server password set to {}.'.format(password)) - embed.set_footer(text='External lavalink server set to True.') + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, + title="Server password set to {}.".format(password), + ) + embed.set_footer(text="External lavalink server set to True.") await ctx.send(embed=embed) else: - await self._embed_msg(ctx, 'Server password set to {}.'.format(password)) + await self._embed_msg(ctx, "Server password set to {}.".format(password)) @llsetup.command() async def restport(self, ctx, rest_port): """Set the lavalink REST server port.""" await self.config.rest_port.set(rest_port) if await self._check_external(): - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='REST port set to {}.'.format(rest_port)) - embed.set_footer(text='External lavalink server set to True.') + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, title="REST port set to {}.".format(rest_port) + ) + embed.set_footer(text="External lavalink server set to True.") await ctx.send(embed=embed) else: - await self._embed_msg(ctx, 'REST port set to {}.'.format(rest_port)) + await self._embed_msg(ctx, "REST port set to {}.".format(rest_port)) @llsetup.command() async def wsport(self, ctx, ws_port): """Set the lavalink websocket server port.""" await self.config.rest_port.set(ws_port) if await self._check_external(): - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, - title='Websocket port set to {}.'.format(ws_port)) - embed.set_footer(text='External lavalink server set to True.') + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, + title="Websocket port set to {}.".format(ws_port), + ) + embed.set_footer(text="External lavalink server set to True.") await ctx.send(embed=embed) else: - await self._embed_msg(ctx, 'Websocket port set to {}.'.format(ws_port)) + await self._embed_msg(ctx, "Websocket port set to {}.".format(ws_port)) async def _check_external(self): external = await self.config.use_external_lavalink() @@ -1443,7 +1694,9 @@ class Audio: return True except ValueError: credits_name = await bank.get_currency_name(ctx.guild) - await self._embed_msg(ctx, 'Not enough {} ({} required).'.format(credits_name, jukebox_price)) + await self._embed_msg( + ctx, "Not enough {} ({} required).".format(credits_name, jukebox_price) + ) return False else: return True @@ -1468,12 +1721,12 @@ class Audio: dur = player.current.length sections = 12 loc_time = round((pos / dur) * sections) - bar = '\N{BOX DRAWINGS HEAVY HORIZONTAL}' - seek = '\N{RADIO BUTTON}' + bar = "\N{BOX DRAWINGS HEAVY HORIZONTAL}" + seek = "\N{RADIO BUTTON}" if paused: - msg = '\N{DOUBLE VERTICAL BAR}' + msg = "\N{DOUBLE VERTICAL BAR}" else: - msg = '\N{BLACK RIGHT-POINTING TRIANGLE}' + msg = "\N{BLACK RIGHT-POINTING TRIANGLE}" for i in range(sections): if i == loc_time: msg += seek @@ -1514,8 +1767,9 @@ class Audio: @staticmethod def _match_yt_playlist(url): yt_list_playlist = re.compile( - r'^(https?\:\/\/)?(www\.)?(youtube\.com|youtu\.?be)' - r'(\/playlist\?).*(list=)(.*)(&|$)') + r"^(https?\:\/\/)?(www\.)?(youtube\.com|youtu\.?be)" + r"(\/playlist\?).*(list=)(.*)(&|$)" + ) if yt_list_playlist.match(url): return True return False @@ -1555,7 +1809,7 @@ class Audio: @staticmethod def _track_creator(player, position=None, other_track=None): - if position == 'np': + if position == "np": queued_track = player.current elif position is None: queued_track = other_track @@ -1567,7 +1821,7 @@ class Audio: track_info = {} for k, v in zip(track_keys, track_values): track_info[k] = v - keys = ['track', 'info'] + keys = ["track", "info"] values = [track_id, track_info] track_obj = {} for key, value in zip(keys, values): diff --git a/redbot/cogs/audio/locales/regen_messages.py b/redbot/cogs/audio/locales/regen_messages.py index 9a59ac7ac..d5209318e 100644 --- a/redbot/cogs/audio/locales/regen_messages.py +++ b/redbot/cogs/audio/locales/regen_messages.py @@ -1,15 +1,11 @@ import subprocess -TO_TRANSLATE = [ - '../audio.py' -] +TO_TRANSLATE = ["../audio.py"] def regen_messages(): - subprocess.run( - ['pygettext', '-n'] + TO_TRANSLATE - ) + subprocess.run(["pygettext", "-n"] + TO_TRANSLATE) if __name__ == "__main__": - regen_messages() \ No newline at end of file + regen_messages() diff --git a/redbot/cogs/audio/manager.py b/redbot/cogs/audio/manager.py index f8b3c7c08..85fbb2e62 100644 --- a/redbot/cogs/audio/manager.py +++ b/redbot/cogs/audio/manager.py @@ -5,7 +5,7 @@ from subprocess import Popen, DEVNULL, PIPE import os import logging -log = logging.getLogger('red.audio.manager') +log = logging.getLogger("red.audio.manager") proc = None SHUTDOWN = asyncio.Event() @@ -13,7 +13,8 @@ SHUTDOWN = asyncio.Event() def has_java_error(pid): from . import LAVALINK_DOWNLOAD_DIR - poss_error_file = LAVALINK_DOWNLOAD_DIR / 'hs_err_pid{}.log'.format(pid) + + poss_error_file = LAVALINK_DOWNLOAD_DIR / "hs_err_pid{}.log".format(pid) return poss_error_file.exists() @@ -29,14 +30,14 @@ async def monitor_lavalink_server(loop): log.info("Restarting Lavalink jar.") await start_lavalink_server(loop) else: - log.error("Your Java is borked. Please find the hs_err_pid{}.log file" - " in the Audio data folder and report this issue.".format( - proc.pid - )) + log.error( + "Your Java is borked. Please find the hs_err_pid{}.log file" + " in the Audio data folder and report this issue.".format(proc.pid) + ) async def has_java(loop): - java_available = shutil.which('java') is not None + java_available = shutil.which("java") is not None if not java_available: return False @@ -48,20 +49,18 @@ async def get_java_version(loop): """ This assumes we've already checked that java exists. """ - proc = Popen( - shlex.split("java -version", posix=os.name == 'posix'), - stdout=PIPE, stderr=PIPE - ) + proc = Popen(shlex.split("java -version", posix=os.name == "posix"), stdout=PIPE, stderr=PIPE) _, err = proc.communicate() - version_info = str(err, encoding='utf-8') + version_info = str(err, encoding="utf-8") - version_line = version_info.split('\n')[0] + version_line = version_info.split("\n")[0] version_start = version_line.find('"') version_string = version_line[version_start + 1:-1] - major, minor = version_string.split('.')[:2] + major, minor = version_string.split(".")[:2] return int(major), int(minor) + async def start_lavalink_server(loop): java_available, java_version = await has_java(loop) if not java_available: @@ -72,13 +71,15 @@ async def start_lavalink_server(loop): extra_flags = "-Dsun.zip.disableMemoryMapping=true" from . import LAVALINK_DOWNLOAD_DIR, LAVALINK_JAR_FILE + start_cmd = "java {} -jar {}".format(extra_flags, LAVALINK_JAR_FILE.resolve()) global proc proc = Popen( - shlex.split(start_cmd, posix=os.name == 'posix'), + shlex.split(start_cmd, posix=os.name == "posix"), cwd=str(LAVALINK_DOWNLOAD_DIR), - stdout=DEVNULL, stderr=DEVNULL + stdout=DEVNULL, + stderr=DEVNULL, ) log.info("Lavalink jar started. PID: {}".format(proc.pid)) diff --git a/redbot/cogs/bank/bank.py b/redbot/cogs/bank/bank.py index 83d7a94dd..d73106b35 100644 --- a/redbot/cogs/bank/bank.py +++ b/redbot/cogs/bank/bank.py @@ -6,7 +6,7 @@ from redbot.core.i18n import Translator, cog_i18n from redbot.core.bot import Red # Only used for type hints -_ = Translator('Bank', __file__) +_ = Translator("Bank", __file__) def check_global_setting_guildowner(): @@ -14,6 +14,7 @@ def check_global_setting_guildowner(): Command decorator. If the bank is not global, it checks if the author is either the guildowner or has the administrator permission. """ + async def pred(ctx: commands.Context): author = ctx.author if await ctx.bot.is_owner(author): @@ -32,6 +33,7 @@ def check_global_setting_admin(): Command decorator. If the bank is not global, it checks if the author is either a bot admin or has the manage_guild permission. """ + async def pred(ctx: commands.Context): author = ctx.author if await ctx.bot.is_owner(author): @@ -73,19 +75,23 @@ class Bank: currency_name = await bank._conf.guild(ctx.guild).currency() default_balance = await bank._conf.guild(ctx.guild).default_balance() - settings = (_( - "Bank settings:\n\n" - "Bank name: {}\n" - "Currency: {}\n" - "Default balance: {}" - "").format(bank_name, currency_name, default_balance) + settings = ( + _( + "Bank settings:\n\n" + "Bank name: {}\n" + "Currency: {}\n" + "Default balance: {}" + "" + ).format( + bank_name, currency_name, default_balance + ) ) await ctx.send(box(settings)) await ctx.send_help() @bankset.command(name="toggleglobal") @checks.is_owner() - async def bankset_toggleglobal(self, ctx: commands.Context, confirm: bool=False): + async def bankset_toggleglobal(self, ctx: commands.Context, confirm: bool = False): """Toggles whether the bank is global or not If the bank is global, it will become per-server If the bank is per-server, it will become global""" @@ -94,8 +100,10 @@ class Bank: word = _("per-server") if cur_setting else _("global") if confirm is False: await ctx.send( - _("This will toggle the bank to be {}, deleting all accounts " - "in the process! If you're sure, type `{}`").format( + _( + "This will toggle the bank to be {}, deleting all accounts " + "in the process! If you're sure, type `{}`" + ).format( word, "{}bankset toggleglobal yes".format(ctx.prefix) ) ) diff --git a/redbot/cogs/bank/errors.py b/redbot/cogs/bank/errors.py index 8cc962363..78ec24062 100644 --- a/redbot/cogs/bank/errors.py +++ b/redbot/cogs/bank/errors.py @@ -1,6 +1,7 @@ class BankError(Exception): pass + class BankNotGlobal(BankError): pass @@ -34,4 +35,4 @@ class NegativeValue(BankError): class SameSenderAndReceiver(BankError): - pass \ No newline at end of file + pass diff --git a/redbot/cogs/bank/locales/regen_messages.py b/redbot/cogs/bank/locales/regen_messages.py index 7fe666947..e9e4575b5 100644 --- a/redbot/cogs/bank/locales/regen_messages.py +++ b/redbot/cogs/bank/locales/regen_messages.py @@ -1,15 +1,11 @@ import subprocess -TO_TRANSLATE = [ - '../bank.py' -] +TO_TRANSLATE = ["../bank.py"] def regen_messages(): - subprocess.run( - ['pygettext', '-n'] + TO_TRANSLATE - ) + subprocess.run(["pygettext", "-n"] + TO_TRANSLATE) if __name__ == "__main__": - regen_messages() \ No newline at end of file + regen_messages() diff --git a/redbot/cogs/cleanup/cleanup.py b/redbot/cogs/cleanup/cleanup.py index e8c021c87..4dd0b1e4e 100644 --- a/redbot/cogs/cleanup/cleanup.py +++ b/redbot/cogs/cleanup/cleanup.py @@ -27,13 +27,16 @@ class Cleanup: Tries its best to cleanup after itself if the response is positive. """ + def author_check(message): return message.author == ctx.author - prompt = await ctx.send(_('Are you sure you want to delete {} messages? (y/n)').format(number)) - response = await ctx.bot.wait_for('message', check=author_check) + prompt = await ctx.send( + _("Are you sure you want to delete {} messages? (y/n)").format(number) + ) + response = await ctx.bot.wait_for("message", check=author_check) - if response.content.lower().startswith('y'): + if response.content.lower().startswith("y"): await prompt.delete() try: await response.delete() @@ -41,14 +44,19 @@ class Cleanup: pass return True else: - await ctx.send(_('Cancelled.')) + await ctx.send(_("Cancelled.")) return False @staticmethod async def get_messages_for_deletion( - ctx: commands.Context, channel: discord.TextChannel, number, - check=lambda x: True, limit=100, before=None, after=None, - delete_pinned=False + ctx: commands.Context, + channel: discord.TextChannel, + number, + check=lambda x: True, + limit=100, + before=None, + after=None, + delete_pinned=False, ) -> list: """ Gets a list of messages meeting the requirements to be deleted. @@ -65,9 +73,7 @@ class Cleanup: while not too_old and len(to_delete) - 1 < number: message = None - async for message in channel.history(limit=limit, - before=before, - after=after): + async for message in channel.history(limit=limit, before=before, after=after): if ( (not number or len(to_delete) - 1 < number) and check(message) @@ -96,7 +102,9 @@ class Cleanup: @cleanup.command() @commands.guild_only() @commands.bot_has_permissions(manage_messages=True) - async def text(self, ctx: commands.Context, text: str, number: int, delete_pinned: bool=False): + async def text( + self, ctx: commands.Context, text: str, number: int, delete_pinned: bool = False + ): """Deletes last X messages matching the specified text. Example: @@ -122,12 +130,18 @@ class Cleanup: return False to_delete = await self.get_messages_for_deletion( - ctx, channel, number, check=check, limit=1000, before=ctx.message, - delete_pinned=delete_pinned) + ctx, + channel, + number, + check=check, + limit=1000, + before=ctx.message, + delete_pinned=delete_pinned, + ) - reason = "{}({}) deleted {} messages "\ - " containing '{}' in channel {}.".format(author.name, - author.id, len(to_delete), text, channel.id) + reason = "{}({}) deleted {} messages " " containing '{}' in channel {}.".format( + author.name, author.id, len(to_delete), text, channel.id + ) log.info(reason) if is_bot: @@ -138,7 +152,9 @@ class Cleanup: @cleanup.command() @commands.guild_only() @commands.bot_has_permissions(manage_messages=True) - async def user(self, ctx: commands.Context, user: str, number: int, delete_pinned: bool=False): + async def user( + self, ctx: commands.Context, user: str, number: int, delete_pinned: bool = False + ): """Deletes last X messages from specified user. Examples: @@ -174,13 +190,17 @@ class Cleanup: return False to_delete = await self.get_messages_for_deletion( - ctx, channel, number, check=check, limit=1000, before=ctx.message, - delete_pinned=delete_pinned + ctx, + channel, + number, + check=check, + limit=1000, + before=ctx.message, + delete_pinned=delete_pinned, + ) + reason = "{}({}) deleted {} messages " " made by {}({}) in channel {}." "".format( + author.name, author.id, len(to_delete), member or "???", _id, channel.name ) - reason = "{}({}) deleted {} messages "\ - " made by {}({}) in channel {}."\ - "".format(author.name, author.id, len(to_delete), - member or '???', _id, channel.name) log.info(reason) if is_bot: @@ -192,7 +212,7 @@ class Cleanup: @cleanup.command() @commands.guild_only() @commands.bot_has_permissions(manage_messages=True) - async def after(self, ctx: commands.Context, message_id: int, delete_pinned: bool=False): + async def after(self, ctx: commands.Context, message_id: int, delete_pinned: bool = False): """Deletes all messages after specified message. To get a message id, enable developer mode in Discord's @@ -207,8 +227,7 @@ class Cleanup: is_bot = self.bot.user.bot if not is_bot: - await ctx.send(_("This command can only be used on bots with " - "bot accounts.")) + await ctx.send(_("This command can only be used on bots with " "bot accounts.")) return after = await channel.get_message(message_id) @@ -221,9 +240,9 @@ class Cleanup: ctx, channel, 0, limit=None, after=after, delete_pinned=delete_pinned ) - reason = "{}({}) deleted {} messages in channel {}."\ - "".format(author.name, author.id, - len(to_delete), channel.name) + reason = "{}({}) deleted {} messages in channel {}." "".format( + author.name, author.id, len(to_delete), channel.name + ) log.info(reason) await mass_purge(to_delete, channel) @@ -231,7 +250,7 @@ class Cleanup: @cleanup.command() @commands.guild_only() @commands.bot_has_permissions(manage_messages=True) - async def messages(self, ctx: commands.Context, number: int, delete_pinned: bool=False): + async def messages(self, ctx: commands.Context, number: int, delete_pinned: bool = False): """Deletes last X messages. Example: @@ -248,14 +267,13 @@ class Cleanup: return to_delete = await self.get_messages_for_deletion( - ctx, channel, number, limit=1000, before=ctx.message, - delete_pinned=delete_pinned + ctx, channel, number, limit=1000, before=ctx.message, delete_pinned=delete_pinned ) to_delete.append(ctx.message) - reason = "{}({}) deleted {} messages in channel {}."\ - "".format(author.name, author.id, - number, channel.name) + reason = "{}({}) deleted {} messages in channel {}." "".format( + author.name, author.id, number, channel.name + ) log.info(reason) if is_bot: @@ -263,10 +281,10 @@ class Cleanup: else: await slow_deletion(to_delete) - @cleanup.command(name='bot') + @cleanup.command(name="bot") @commands.guild_only() @commands.bot_has_permissions(manage_messages=True) - async def cleanup_bot(self, ctx: commands.Context, number: int, delete_pinned: bool=False): + async def cleanup_bot(self, ctx: commands.Context, number: int, delete_pinned: bool = False): """Cleans up command messages and messages from the bot.""" channel = ctx.message.channel @@ -278,13 +296,13 @@ class Cleanup: if not cont: return - prefixes = await self.bot.get_prefix(ctx.message) # This returns all server prefixes + prefixes = await self.bot.get_prefix(ctx.message) # This returns all server prefixes if isinstance(prefixes, str): prefixes = [prefixes] # In case some idiot sets a null prefix - if '' in prefixes: - prefixes.remove('') + if "" in prefixes: + prefixes.remove("") def check(m): if m.author.id == self.bot.user.id: @@ -293,20 +311,24 @@ class Cleanup: return True p = discord.utils.find(m.content.startswith, prefixes) if p and len(p) > 0: - cmd_name = m.content[len(p):].split(' ')[0] + cmd_name = m.content[len(p):].split(" ")[0] return bool(self.bot.get_command(cmd_name)) return False to_delete = await self.get_messages_for_deletion( - ctx, channel, number, check=check, limit=1000, before=ctx.message, - delete_pinned=delete_pinned + ctx, + channel, + number, + check=check, + limit=1000, + before=ctx.message, + delete_pinned=delete_pinned, ) to_delete.append(ctx.message) - reason = "{}({}) deleted {} "\ - " command messages in channel {}."\ - "".format(author.name, author.id, len(to_delete), - channel.name) + reason = "{}({}) deleted {} " " command messages in channel {}." "".format( + author.name, author.id, len(to_delete), channel.name + ) log.info(reason) if is_bot: @@ -314,10 +336,14 @@ class Cleanup: else: await slow_deletion(to_delete) - @cleanup.command(name='self') + @cleanup.command(name="self") async def cleanup_self( - self, ctx: commands.Context, number: int, - match_pattern: str = None, delete_pinned: bool=False): + self, + ctx: commands.Context, + number: int, + match_pattern: str = None, + delete_pinned: bool = False, + ): """Cleans up messages owned by the bot. By default, all messages are cleaned. If a third argument is specified, @@ -343,8 +369,7 @@ class Cleanup: me = ctx.guild.me can_mass_purge = channel.permissions_for(me).manage_messages - use_re = (match_pattern and match_pattern.startswith('r(') and - match_pattern.endswith(')')) + use_re = (match_pattern and match_pattern.startswith("r(") and match_pattern.endswith(")")) if use_re: match_pattern = match_pattern[1:] # strip 'r' @@ -352,10 +377,14 @@ class Cleanup: def content_match(c): return bool(match_re.match(c)) + elif match_pattern: + def content_match(c): return match_pattern in c + else: + def content_match(_): return True @@ -367,8 +396,13 @@ class Cleanup: return False to_delete = await self.get_messages_for_deletion( - ctx, channel, number, check=check, limit=1000, before=ctx.message, - delete_pinned=delete_pinned + ctx, + channel, + number, + check=check, + limit=1000, + before=ctx.message, + delete_pinned=delete_pinned, ) # Selfbot convenience, delete trigger message @@ -376,14 +410,13 @@ class Cleanup: to_delete.append(ctx.message) if channel.name: - channel_name = 'channel ' + channel.name + channel_name = "channel " + channel.name else: channel_name = str(channel) - reason = "{}({}) deleted {} messages "\ - "sent by the bot in {}."\ - "".format(author.name, author.id, len(to_delete), - channel_name) + reason = "{}({}) deleted {} messages " "sent by the bot in {}." "".format( + author.name, author.id, len(to_delete), channel_name + ) log.info(reason) if is_bot and can_mass_purge: diff --git a/redbot/cogs/cleanup/locales/regen_messages.py b/redbot/cogs/cleanup/locales/regen_messages.py index 364686b7d..60b020564 100644 --- a/redbot/cogs/cleanup/locales/regen_messages.py +++ b/redbot/cogs/cleanup/locales/regen_messages.py @@ -1,15 +1,11 @@ import subprocess -TO_TRANSLATE = [ - '../cleanup.py' -] +TO_TRANSLATE = ["../cleanup.py"] def regen_messages(): - subprocess.run( - ['pygettext', '-n'] + TO_TRANSLATE - ) + subprocess.run(["pygettext", "-n"] + TO_TRANSLATE) if __name__ == "__main__": - regen_messages() \ No newline at end of file + regen_messages() diff --git a/redbot/cogs/customcom/customcom.py b/redbot/cogs/customcom/customcom.py index 73adac26d..7284d2c28 100644 --- a/redbot/cogs/customcom/customcom.py +++ b/redbot/cogs/customcom/customcom.py @@ -27,8 +27,8 @@ class AlreadyExists(CCError): class CommandObj: def __init__(self, **kwargs): - config = kwargs.get('config') - self.bot = kwargs.get('bot') + config = kwargs.get("config") + self.bot = kwargs.get("bot") self.db = config.guild @staticmethod @@ -40,22 +40,27 @@ class CommandObj: return customcommands async def get_responses(self, ctx): - intro = (_("Welcome to the interactive random {} maker!\n" - "Every message you send will be added as one of the random " - "response to choose from once this {} is " - "triggered. To exit this interactive menu, type `{}`").format( - "customcommand", "customcommand", "exit()" - )) + intro = ( + _( + "Welcome to the interactive random {} maker!\n" + "Every message you send will be added as one of the random " + "response to choose from once this {} is " + "triggered. To exit this interactive menu, type `{}`" + ).format( + "customcommand", "customcommand", "exit()" + ) + ) await ctx.send(intro) def check(m): return m.channel == ctx.channel and m.author == ctx.message.author + responses = [] while True: await ctx.send(_("Add a random response:")) - msg = await self.bot.wait_for('message', check=check) + msg = await self.bot.wait_for("message", check=check) - if msg.content.lower() == 'exit()': + if msg.content.lower() == "exit()": break else: responses.append(msg.content) @@ -64,44 +69,31 @@ class CommandObj: def get_now(self) -> str: # Get current time as a string, for 'created_at' and 'edited_at' fields # in the ccinfo dict - return '{:%d/%m/%Y %H:%M:%S}'.format(datetime.utcnow()) + return "{:%d/%m/%Y %H:%M:%S}".format(datetime.utcnow()) - async def get(self, - message: discord.Message, - command: str) -> str: + async def get(self, message: discord.Message, command: str) -> str: ccinfo = await self.db(message.guild).commands.get_raw(command, default=None) if not ccinfo: raise NotFound else: - return ccinfo['response'] + return ccinfo["response"] - async def create(self, - ctx: commands.Context, - command: str, - response): + async def create(self, ctx: commands.Context, command: str, response): """Create a customcommand""" # Check if this command is already registered as a customcommand if await self.db(ctx.guild).commands.get_raw(command, default=None): raise AlreadyExists() author = ctx.message.author ccinfo = { - 'author': { - 'id': author.id, - 'name': author.name - }, - 'command': command, - 'created_at': self.get_now(), - 'editors': [], - 'response': response - + "author": {"id": author.id, "name": author.name}, + "command": command, + "created_at": self.get_now(), + "editors": [], + "response": response, } - await self.db(ctx.guild).commands.set_raw( - command, value=ccinfo) + await self.db(ctx.guild).commands.set_raw(command, value=ccinfo) - async def edit(self, - ctx: commands.Context, - command: str, - response: None): + async def edit(self, ctx: commands.Context, command: str, response: None): """Edit an already existing custom command""" # Check if this command is registered if not await self.db(ctx.guild).commands.get_raw(command, default=None): @@ -114,41 +106,31 @@ class CommandObj: return m.channel == ctx.channel and m.author == ctx.message.author if not response: - await ctx.send( - _("Do you want to create a 'randomized' cc? {}").format("y/n") - ) + await ctx.send(_("Do you want to create a 'randomized' cc? {}").format("y/n")) - msg = await self.bot.wait_for('message', check=check) - if msg.content.lower() == 'y': + msg = await self.bot.wait_for("message", check=check) + if msg.content.lower() == "y": response = await self.get_responses(ctx=ctx) else: await ctx.send(_("What response do you want?")) - response = (await self.bot.wait_for( - 'message', check=check) - ).content + response = (await self.bot.wait_for("message", check=check)).content - ccinfo['response'] = response - ccinfo['edited_at'] = self.get_now() + ccinfo["response"] = response + ccinfo["edited_at"] = self.get_now() - if author.id not in ccinfo['editors']: + if author.id not in ccinfo["editors"]: # Add the person who invoked the `edit` coroutine to the list of # editors, if the person is not yet in there - ccinfo['editors'].append( - author.id - ) + ccinfo["editors"].append(author.id) - await self.db(ctx.guild).commands.set_raw( - command, value=ccinfo) + await self.db(ctx.guild).commands.set_raw(command, value=ccinfo) - async def delete(self, - ctx: commands.Context, - command: str): + async def delete(self, ctx: commands.Context, command: str): """Delete an already exisiting custom command""" # Check if this command is registered if not await self.db(ctx.guild).commands.get_raw(command, default=None): raise NotFound() - await self.db(ctx.guild).commands.set_raw( - command, value=None) + await self.db(ctx.guild).commands.set_raw(command, value=None) @cog_i18n(_) @@ -159,24 +141,20 @@ class CustomCommands: def __init__(self, bot): self.bot = bot self.key = 414589031223512 - self.config = Config.get_conf(self, - self.key) + self.config = Config.get_conf(self, self.key) self.config.register_guild(commands={}) - self.commandobj = CommandObj(config=self.config, - bot=self.bot) + self.commandobj = CommandObj(config=self.config, bot=self.bot) @commands.group(aliases=["cc"], no_pm=True) @commands.guild_only() - async def customcom(self, - ctx: commands.Context): + async def customcom(self, ctx: commands.Context): """Custom commands management""" if not ctx.invoked_subcommand: await ctx.send_help() @customcom.group(name="add") @checks.mod_or_permissions(administrator=True) - async def cc_add(self, - ctx: commands.Context): + async def cc_add(self, ctx: commands.Context): """ CCs can be enhanced with arguments: @@ -192,15 +170,12 @@ class CustomCommands: {server} message.guild """ - if not ctx.invoked_subcommand or isinstance(ctx.invoked_subcommand, - commands.Group): + if not ctx.invoked_subcommand or isinstance(ctx.invoked_subcommand, commands.Group): await ctx.send_help() - @cc_add.command(name='random') + @cc_add.command(name="random") @checks.mod_or_permissions(administrator=True) - async def cc_add_random(self, - ctx: commands.Context, - command: str): + async def cc_add_random(self, ctx: commands.Context, command: str): """ Create a CC where it will randomly choose a response! Note: This is interactive @@ -210,26 +185,20 @@ class CustomCommands: responses = await self.commandobj.get_responses(ctx=ctx) try: - await self.commandobj.create(ctx=ctx, - command=command, - response=responses) + await self.commandobj.create(ctx=ctx, command=command, response=responses) await ctx.send(_("Custom command successfully added.")) except AlreadyExists: - await ctx.send(_( - "This command already exists. Use " - "`{}` to edit it.").format( + await ctx.send( + _("This command already exists. Use " "`{}` to edit it.").format( "{}customcom edit".format(ctx.prefix) - )) + ) + ) # await ctx.send(str(responses)) @cc_add.command(name="simple") @checks.mod_or_permissions(administrator=True) - async def cc_add_simple(self, - ctx, - command: str, - *, - text): + async def cc_add_simple(self, ctx, command: str, *, text): """Adds a simple custom command Example: [p]customcom add simple yourcommand Text you want @@ -240,24 +209,18 @@ class CustomCommands: await ctx.send(_("That command is already a standard command.")) return try: - await self.commandobj.create(ctx=ctx, - command=command, - response=text) + await self.commandobj.create(ctx=ctx, command=command, response=text) await ctx.send(_("Custom command successfully added.")) except AlreadyExists: - await ctx.send(_( - "This command already exists. Use " - "`{}` to edit it.").format( + await ctx.send( + _("This command already exists. Use " "`{}` to edit it.").format( "{}customcom edit".format(ctx.prefix) - )) + ) + ) @customcom.command(name="edit") @checks.mod_or_permissions(administrator=True) - async def cc_edit(self, - ctx, - command: str, - *, - text=None): + async def cc_edit(self, ctx, command: str, *, text=None): """Edits a custom command Example: [p]customcom edit yourcommand Text you want @@ -266,61 +229,57 @@ class CustomCommands: command = command.lower() try: - await self.commandobj.edit(ctx=ctx, - command=command, - response=text) + await self.commandobj.edit(ctx=ctx, command=command, response=text) await ctx.send(_("Custom command successfully edited.")) except NotFound: - await ctx.send(_( - "That command doesn't exist. Use " - "`{}` to add it.").format( + await ctx.send( + _("That command doesn't exist. Use " "`{}` to add it.").format( "{}customcom add".format(ctx.prefix) - )) + ) + ) @customcom.command(name="delete") @checks.mod_or_permissions(administrator=True) - async def cc_delete(self, - ctx, - command: str): + async def cc_delete(self, ctx, command: str): """Deletes a custom command Example: [p]customcom delete yourcommand""" guild = ctx.message.guild command = command.lower() try: - await self.commandobj.delete(ctx=ctx, - command=command) + await self.commandobj.delete(ctx=ctx, command=command) await ctx.send(_("Custom command successfully deleted.")) except NotFound: await ctx.send(_("That command doesn't exist.")) @customcom.command(name="list") - async def cc_list(self, - ctx): + async def cc_list(self, ctx): """Shows custom commands list""" response = await CommandObj.get_commands(self.config.guild(ctx.guild)) if not response: - await ctx.send(_( - "There are no custom commands in this server." - " Use `{}` to start adding some.").format( + await ctx.send( + _( + "There are no custom commands in this server." + " Use `{}` to start adding some." + ).format( "{}customcom add".format(ctx.prefix) - )) + ) + ) return results = [] for command, body in response.items(): - responses = body['response'] + responses = body["response"] if isinstance(responses, list): result = ", ".join(responses) elif isinstance(responses, str): result = responses else: continue - results.append("{command:<15} : {result}".format(command=command, - result=result)) + results.append("{command:<15} : {result}".format(command=command, result=result)) commands = "\n".join(results) @@ -330,14 +289,13 @@ class CustomCommands: for page in pagify(commands, delims=[" ", "\n"]): await ctx.author.send(box(page)) - async def on_message(self, - message): + async def on_message(self, message): is_private = isinstance(message.channel, discord.abc.PrivateChannel) if len(message.content) < 2 or is_private: return guild = message.guild - prefixes = await self.bot.db.guild(guild).get_raw('prefix', default=[]) + prefixes = await self.bot.db.guild(guild).get_raw("prefix", default=[]) if len(prefixes) < 1: def_prefixes = await self.bot.get_prefix(message) @@ -358,8 +316,7 @@ class CustomCommands: if user_allowed: cmd = message.content[len(prefix):] try: - c = await self.commandobj.get(message=message, - command=cmd) + c = await self.commandobj.get(message=message, command=cmd) if isinstance(c, list): command = random.choice(c) elif isinstance(c, str): @@ -371,18 +328,14 @@ class CustomCommands: response = self.format_cc(command, message) await message.channel.send(response) - def format_cc(self, - command, - message) -> str: + def format_cc(self, command, message) -> str: results = re.findall("\{([^}]+)\}", command) for result in results: param = self.transform_parameter(result, message) command = command.replace("{" + result + "}", param) return command - def transform_parameter(self, - result, - message) -> str: + def transform_parameter(self, result, message) -> str: """ For security reasons only specific objects are allowed Internals are ignored @@ -393,7 +346,7 @@ class CustomCommands: "author": message.author, "channel": message.channel, "guild": message.guild, - "server": message.guild + "server": message.guild, } if result in objects: return str(objects[result]) diff --git a/redbot/cogs/customcom/locales/regen_messages.py b/redbot/cogs/customcom/locales/regen_messages.py index f00c7a916..de0498e0a 100644 --- a/redbot/cogs/customcom/locales/regen_messages.py +++ b/redbot/cogs/customcom/locales/regen_messages.py @@ -1,15 +1,11 @@ import subprocess -TO_TRANSLATE = [ - '../customcom.py' -] +TO_TRANSLATE = ["../customcom.py"] def regen_messages(): - subprocess.run( - ['pygettext', '-n'] + TO_TRANSLATE - ) + subprocess.run(["pygettext", "-n"] + TO_TRANSLATE) if __name__ == "__main__": - regen_messages() \ No newline at end of file + regen_messages() diff --git a/redbot/cogs/dataconverter/core_specs.py b/redbot/cogs/dataconverter/core_specs.py index 47c2301fa..307466505 100644 --- a/redbot/cogs/dataconverter/core_specs.py +++ b/redbot/cogs/dataconverter/core_specs.py @@ -16,49 +16,49 @@ class SpecResolver(object): self.v2path = path self.resolved = set() self.available_core_conversions = { - 'Bank Accounts': { - 'cfg': ('Bank', None, 384734293238749), - 'file': self.v2path / 'data' / 'economy' / 'bank.json', - 'converter': self.bank_accounts_conv_spec + "Bank Accounts": { + "cfg": ("Bank", None, 384734293238749), + "file": self.v2path / "data" / "economy" / "bank.json", + "converter": self.bank_accounts_conv_spec, }, - 'Economy Settings': { - 'cfg': ('Economy', 'config', 1256844281), - 'file': self.v2path / 'data' / 'economy' / 'settings.json', - 'converter': self.economy_conv_spec + "Economy Settings": { + "cfg": ("Economy", "config", 1256844281), + "file": self.v2path / "data" / "economy" / "settings.json", + "converter": self.economy_conv_spec, }, - 'Mod Log Cases': { - 'cfg': ('ModLog', None, 1354799444), - 'file': self.v2path / 'data' / 'mod' / 'modlog.json', - 'converter': None # prevents from showing as available + "Mod Log Cases": { + "cfg": ("ModLog", None, 1354799444), + "file": self.v2path / "data" / "mod" / "modlog.json", + "converter": None, # prevents from showing as available }, - 'Filter': { - 'cfg': ('Filter', 'settings', 4766951341), - 'file': self.v2path / 'data' / 'mod' / 'filter.json', - 'converter': self.filter_conv_spec + "Filter": { + "cfg": ("Filter", "settings", 4766951341), + "file": self.v2path / "data" / "mod" / "filter.json", + "converter": self.filter_conv_spec, }, - 'Past Names': { - 'cfg': ('Mod', 'settings', 4961522000), - 'file': self.v2path / 'data' / 'mod' / 'past_names.json', - 'converter': self.past_names_conv_spec + "Past Names": { + "cfg": ("Mod", "settings", 4961522000), + "file": self.v2path / "data" / "mod" / "past_names.json", + "converter": self.past_names_conv_spec, }, - 'Past Nicknames': { - 'cfg': ('Mod', 'settings', 4961522000), - 'file': self.v2path / 'data' / 'mod' / 'past_nicknames.json', - 'converter': self.past_nicknames_conv_spec + "Past Nicknames": { + "cfg": ("Mod", "settings", 4961522000), + "file": self.v2path / "data" / "mod" / "past_nicknames.json", + "converter": self.past_nicknames_conv_spec, + }, + "Custom Commands": { + "cfg": ("CustomCommands", "config", 414589031223512), + "file": self.v2path / "data" / "customcom" / "commands.json", + "converter": self.customcom_conv_spec, }, - 'Custom Commands': { - 'cfg': ('CustomCommands', 'config', 414589031223512), - 'file': self.v2path / 'data' / 'customcom' / 'commands.json', - 'converter': self.customcom_conv_spec - } } @property def available(self): return sorted( - k for k, v in self.available_core_conversions.items() - if v['file'].is_file() and v['converter'] is not None - and k not in self.resolved + k + for k, v in self.available_core_conversions.items() + if v["file"].is_file() and v["converter"] is not None and k not in self.resolved ) def unpack(self, parent_key, parent_value): @@ -75,15 +75,8 @@ class SpecResolver(object): """Flatten a nested dictionary structure""" dictionary = {(key,): value for key, value in dictionary.items()} while True: - dictionary = dict( - chain.from_iterable( - starmap(self.unpack, dictionary.items()) - ) - ) - if not any( - isinstance(value, dict) - for value in dictionary.values() - ): + dictionary = dict(chain.from_iterable(starmap(self.unpack, dictionary.items()))) + if not any(isinstance(value, dict) for value in dictionary.values()): break return dictionary @@ -97,11 +90,8 @@ class SpecResolver(object): outerkey, innerkey = tuple(k[:-1]), (k[-1],) if outerkey not in ret: ret[outerkey] = {} - if innerkey[0] == 'created_at': - x = int( - datetime.strptime( - v, "%Y-%m-%d %H:%M:%S").timestamp() - ) + if innerkey[0] == "created_at": + x = int(datetime.strptime(v, "%Y-%m-%d %H:%M:%S").timestamp()) ret[outerkey].update({innerkey: x}) else: ret[outerkey].update({innerkey: v}) @@ -121,16 +111,10 @@ class SpecResolver(object): raise NotImplementedError("This one isn't ready yet") def filter_conv_spec(self, data: dict): - return { - (Config.GUILD, k): {('filter',): v} - for k, v in data.items() - } + return {(Config.GUILD, k): {("filter",): v} for k, v in data.items()} def past_names_conv_spec(self, data: dict): - return { - (Config.USER, k): {('past_names',): v} - for k, v in data.items() - } + return {(Config.USER, k): {("past_names",): v} for k, v in data.items()} def past_nicknames_conv_spec(self, data: dict): flatscoped = self.apply_scope(Config.MEMBER, self.flatten_dict(data)) @@ -146,19 +130,16 @@ class SpecResolver(object): flatscoped = self.apply_scope(Config.GUILD, self.flatten_dict(data)) ret = {} for k, v in flatscoped.items(): - outerkey, innerkey = (*k[:-1],), ('commands', k[-1]) + outerkey, innerkey = (*k[:-1],), ("commands", k[-1]) if outerkey not in ret: ret[outerkey] = {} ccinfo = { - 'author': { - 'id': 42, - 'name': 'Converted from a v2 instance' - }, - 'command': k[-1], - 'created_at': '{:%d/%m/%Y %H:%M:%S}'.format(datetime.utcnow()), - 'editors': [], - 'response': v + "author": {"id": 42, "name": "Converted from a v2 instance"}, + "command": k[-1], + "created_at": "{:%d/%m/%Y %H:%M:%S}".format(datetime.utcnow()), + "editors": [], + "response": v, } ret[outerkey].update({innerkey: ccinfo}) return ret @@ -168,8 +149,8 @@ class SpecResolver(object): raise NotImplementedError("No Conversion Specs for this") info = self.available_core_conversions[prettyname] - filepath, converter = info['file'], info['converter'] - (cogname, attr, _id) = info['cfg'] + filepath, converter = info["file"], info["converter"] + (cogname, attr, _id) = info["cfg"] try: config = getattr(bot.get_cog(cogname), attr) except (TypeError, AttributeError): diff --git a/redbot/cogs/dataconverter/dataconverter.py b/redbot/cogs/dataconverter/dataconverter.py index 322302df7..17e6ae35d 100644 --- a/redbot/cogs/dataconverter/dataconverter.py +++ b/redbot/cogs/dataconverter/dataconverter.py @@ -7,7 +7,7 @@ from redbot.core.i18n import Translator, cog_i18n from redbot.cogs.dataconverter.core_specs import SpecResolver from redbot.core.utils.chat_formatting import box -_ = Translator('DataConverter', __file__) +_ = Translator("DataConverter", __file__) @cog_i18n(_) @@ -34,13 +34,14 @@ class DataConverter: if not resolver.available: return await ctx.send( - _("There don't seem to be any data files I know how to " - "handle here. Are you sure you gave me the base " - "installation path?") + _( + "There don't seem to be any data files I know how to " + "handle here. Are you sure you gave me the base " + "installation path?" + ) ) while resolver.available: - menu = _("Please select a set of data to import by number" - ", or 'exit' to exit") + menu = _("Please select a set of data to import by number" ", or 'exit' to exit") for index, entry in enumerate(resolver.available, 1): menu += "\n{}. {}".format(index, entry) @@ -50,24 +51,17 @@ class DataConverter: return m.channel == ctx.channel and m.author == ctx.author try: - message = await self.bot.wait_for( - 'message', check=pred, timeout=60 - ) + message = await self.bot.wait_for("message", check=pred, timeout=60) except asyncio.TimeoutError: - return await ctx.send( - _('Try this again when you are more ready')) + return await ctx.send(_("Try this again when you are more ready")) else: - if message.content.strip().lower() in [ - 'quit', 'exit', '-1', 'q', 'cancel' - ]: + if message.content.strip().lower() in ["quit", "exit", "-1", "q", "cancel"]: return await ctx.tick() try: message = int(message.content.strip()) to_conv = resolver.available[message - 1] except (ValueError, IndexError): - await ctx.send( - _("That wasn't a valid choice.") - ) + await ctx.send(_("That wasn't a valid choice.")) continue else: async with ctx.typing(): @@ -76,6 +70,8 @@ class DataConverter: await menu_message.delete() else: return await ctx.send( - _("There isn't anything else I know how to convert here." - "\nThere might be more things I can convert in the future.") + _( + "There isn't anything else I know how to convert here." + "\nThere might be more things I can convert in the future." + ) ) diff --git a/redbot/cogs/dataconverter/locales/regen_messages.py b/redbot/cogs/dataconverter/locales/regen_messages.py index ecca5345b..5f099e873 100644 --- a/redbot/cogs/dataconverter/locales/regen_messages.py +++ b/redbot/cogs/dataconverter/locales/regen_messages.py @@ -1,14 +1,10 @@ import subprocess -TO_TRANSLATE = [ - '../dataconverter.py' -] +TO_TRANSLATE = ["../dataconverter.py"] def regen_messages(): - subprocess.run( - ['pygettext', '-n'] + TO_TRANSLATE - ) + subprocess.run(["pygettext", "-n"] + TO_TRANSLATE) if __name__ == "__main__": diff --git a/redbot/cogs/downloader/checks.py b/redbot/cogs/downloader/checks.py index 0a87cced5..626fcb856 100644 --- a/redbot/cogs/downloader/checks.py +++ b/redbot/cogs/downloader/checks.py @@ -3,7 +3,7 @@ import asyncio import discord from discord.ext import commands -__all__ = ["install_agreement", ] +__all__ = ["install_agreement"] REPO_INSTALL_MSG = ( "You're about to add a 3rd party repository. The creator of Red" @@ -17,29 +17,28 @@ REPO_INSTALL_MSG = ( def install_agreement(): + async def pred(ctx: commands.Context): downloader = ctx.command.instance if downloader is None: return True elif downloader.already_agreed: return True - elif ctx.invoked_subcommand is None or \ - isinstance(ctx.invoked_subcommand, commands.Group): + elif ctx.invoked_subcommand is None or isinstance(ctx.invoked_subcommand, commands.Group): return True def does_agree(msg: discord.Message): - return ctx.author == msg.author and \ - ctx.channel == msg.channel and \ - msg.content == "I agree" + return ctx.author == msg.author and ctx.channel == msg.channel and msg.content == "I agree" await ctx.send(REPO_INSTALL_MSG) try: - await ctx.bot.wait_for('message', check=does_agree, timeout=30) + await ctx.bot.wait_for("message", check=does_agree, timeout=30) except asyncio.TimeoutError: await ctx.send("Your response has timed out, please try again.") return False downloader.already_agreed = True return True + return commands.check(pred) diff --git a/redbot/cogs/downloader/converters.py b/redbot/cogs/downloader/converters.py index 0a740c417..20ab09a07 100644 --- a/redbot/cogs/downloader/converters.py +++ b/redbot/cogs/downloader/converters.py @@ -5,6 +5,7 @@ from .installable import Installable class InstalledCog(commands.Converter): + async def convert(self, ctx: commands.Context, arg: str) -> Installable: downloader = ctx.bot.get_cog("Downloader") if downloader is None: @@ -12,8 +13,6 @@ class InstalledCog(commands.Converter): cog = discord.utils.get(await downloader.installed_cogs(), name=arg) if cog is None: - raise commands.BadArgument( - "That cog is not installed" - ) + raise commands.BadArgument("That cog is not installed") return cog diff --git a/redbot/cogs/downloader/downloader.py b/redbot/cogs/downloader/downloader.py index dc84a63a8..7fb8be7e3 100644 --- a/redbot/cogs/downloader/downloader.py +++ b/redbot/cogs/downloader/downloader.py @@ -22,20 +22,18 @@ from .installable import Installable from .log import log from .repo_manager import RepoManager, Repo -_ = Translator('Downloader', __file__) +_ = Translator("Downloader", __file__) @cog_i18n(_) class Downloader: + def __init__(self, bot: Red): self.bot = bot - self.conf = Config.get_conf(self, identifier=998240343, - force_registration=True) + self.conf = Config.get_conf(self, identifier=998240343, force_registration=True) - self.conf.register_global( - installed=[] - ) + self.conf.register_global(installed=[]) self.already_agreed = False @@ -46,7 +44,7 @@ class Downloader: self.LIB_PATH.mkdir(parents=True, exist_ok=True) self.SHAREDLIB_PATH.mkdir(parents=True, exist_ok=True) if not self.SHAREDLIB_INIT.exists(): - with self.SHAREDLIB_INIT.open(mode='w', encoding='utf-8') as _: + with self.SHAREDLIB_INIT.open(mode="w", encoding="utf-8") as _: pass if str(self.LIB_PATH) not in syspath: @@ -170,7 +168,7 @@ class Downloader: for repo, reqs in has_reqs: for req in reqs: # noinspection PyTypeChecker - ret = ret and await repo.install_raw_requirements([req, ], self.LIB_PATH) + ret = ret and await repo.install_raw_requirements([req], self.LIB_PATH) return ret @staticmethod @@ -200,8 +198,12 @@ class Downloader: if success: await ctx.send(_("Libraries installed.")) else: - await ctx.send(_("Some libraries failed to install. Please check" - " your logs for a complete list.")) + await ctx.send( + _( + "Some libraries failed to install. Please check" + " your logs for a complete list." + ) + ) @commands.group() @checks.is_owner() @@ -214,7 +216,7 @@ class Downloader: @repo.command(name="add") @install_agreement() - async def _repo_add(self, ctx, name: str, repo_url: str, branch: str=None): + async def _repo_add(self, ctx, name: str, repo_url: str, branch: str = None): """ Add a new repo to Downloader. @@ -223,11 +225,7 @@ class Downloader: """ try: # noinspection PyTypeChecker - repo = await self._repo_manager.add_repo( - name=name, - url=repo_url, - branch=branch - ) + repo = await self._repo_manager.add_repo(name=name, url=repo_url, branch=branch) except ExistingGitRepo: await ctx.send(_("That git repo has already been added under another name.")) except CloningError: @@ -275,20 +273,28 @@ class Downloader: """ cog = discord.utils.get(repo_name.available_cogs, name=cog_name) # type: Installable if cog is None: - await ctx.send(_("Error, there is no cog by the name of" - " `{}` in the `{}` repo.").format(cog_name, repo_name.name)) + await ctx.send( + _("Error, there is no cog by the name of" " `{}` in the `{}` repo.").format( + cog_name, repo_name.name + ) + ) return elif cog.min_python_version > sys.version_info: - await ctx.send(_( - "This cog requires at least python version {}, aborting install.".format( - '.'.join([str(n) for n in cog.min_python_version]) + await ctx.send( + _( + "This cog requires at least python version {}, aborting install.".format( + ".".join([str(n) for n in cog.min_python_version]) + ) ) - )) + ) return if not await repo_name.install_requirements(cog, self.LIB_PATH): - await ctx.send(_("Failed to install the required libraries for" - " `{}`: `{}`").format(cog.name, cog.requirements)) + await ctx.send( + _("Failed to install the required libraries for" " `{}`: `{}`").format( + cog.name, cog.requirements + ) + ) return await repo_name.install_cog(cog, await self.cog_install_path()) @@ -317,12 +323,16 @@ class Downloader: await self._remove_from_installed(cog_name) await ctx.send(_("`{}` was successfully removed.").format(real_name)) else: - await ctx.send(_("That cog was installed but can no longer" - " be located. You may need to remove it's" - " files manually if it is still usable.")) + await ctx.send( + _( + "That cog was installed but can no longer" + " be located. You may need to remove it's" + " files manually if it is still usable." + ) + ) @cog.command(name="update") - async def _cog_update(self, ctx, cog_name: InstalledCog=None): + async def _cog_update(self, ctx, cog_name: InstalledCog = None): """ Updates all cogs or one of your choosing. """ @@ -358,7 +368,8 @@ class Downloader: """ cogs = repo_name.available_cogs cogs = _("Available Cogs:\n") + "\n".join( - ["+ {}: {}".format(c.name, c.short or "") for c in cogs]) + ["+ {}: {}".format(c.name, c.short or "") for c in cogs] + ) await ctx.send(box(cogs, lang="diff")) @@ -369,9 +380,9 @@ class Downloader: """ cog = discord.utils.get(repo_name.available_cogs, name=cog_name) if cog is None: - await ctx.send(_("There is no cog `{}` in the repo `{}`").format( - cog_name, repo_name.name - )) + await ctx.send( + _("There is no cog `{}` in the repo `{}`").format(cog_name, repo_name.name) + ) return msg = _("Information on {}:\n{}").format(cog.name, cog.description or "") @@ -397,8 +408,9 @@ class Downloader: return True, installable return False, None - def format_findcog_info(self, command_name: str, - cog_installable: Union[Installable, object]=None) -> str: + def format_findcog_info( + self, command_name: str, cog_installable: Union[Installable, object] = None + ) -> str: """Format a cog's info for output to discord. Parameters @@ -444,7 +456,7 @@ class Downloader: The name of the cog according to Downloader.. """ - splitted = instance.__module__.split('.') + splitted = instance.__module__.split(".") return splitted[-2] @commands.command() diff --git a/redbot/cogs/downloader/errors.py b/redbot/cogs/downloader/errors.py index 630bd2650..af1892dea 100644 --- a/redbot/cogs/downloader/errors.py +++ b/redbot/cogs/downloader/errors.py @@ -1,6 +1,16 @@ -__all__ = ["DownloaderException", "GitException", "InvalidRepoName", "ExistingGitRepo", - "MissingGitRepo", "CloningError", "CurrentHashError", "HardResetError", - "UpdateError", "GitDiffError", "PipError"] +__all__ = [ + "DownloaderException", + "GitException", + "InvalidRepoName", + "ExistingGitRepo", + "MissingGitRepo", + "CloningError", + "CurrentHashError", + "HardResetError", + "UpdateError", + "GitDiffError", + "PipError", +] class DownloaderException(Exception): diff --git a/redbot/cogs/downloader/installable.py b/redbot/cogs/downloader/installable.py index edaacf7cb..3247fb2a4 100644 --- a/redbot/cogs/downloader/installable.py +++ b/redbot/cogs/downloader/installable.py @@ -56,6 +56,7 @@ class Installable(RepoJSONMixin): :class:`InstallationType`. """ + def __init__(self, location: Path): """Base installable initializer. @@ -114,13 +115,9 @@ class Installable(RepoJSONMixin): # noinspection PyBroadException try: - copy_func( - src=str(self._location), - dst=str(target_dir / self._location.stem) - ) + copy_func(src=str(self._location), dst=str(target_dir / self._location.stem)) except: - log.exception("Error occurred when copying path:" - " {}".format(self._location)) + log.exception("Error occurred when copying path:" " {}".format(self._location)) return False return True @@ -130,7 +127,7 @@ class Installable(RepoJSONMixin): if self._info_file.exists(): self._process_info_file() - def _process_info_file(self, info_file_path: Path=None) -> MutableMapping[str, Any]: + def _process_info_file(self, info_file_path: Path = None) -> MutableMapping[str, Any]: """ Processes an information file. Loads dependencies among other information into this object. @@ -144,13 +141,14 @@ class Installable(RepoJSONMixin): raise ValueError("No valid information file path was found.") info = {} - with info_file_path.open(encoding='utf-8') as f: + with info_file_path.open(encoding="utf-8") as f: try: info = json.load(f) except json.JSONDecodeError: info = {} - log.exception("Invalid JSON information file at path:" - " {}".format(info_file_path)) + log.exception( + "Invalid JSON information file at path:" " {}".format(info_file_path) + ) else: self._info = info @@ -167,7 +165,7 @@ class Installable(RepoJSONMixin): self.bot_version = bot_version try: - min_python_version = tuple(info.get('min_python_version', [3, 5, 1])) + min_python_version = tuple(info.get("min_python_version", [3, 5, 1])) except ValueError: min_python_version = self.min_python_version self.min_python_version = min_python_version @@ -200,15 +198,12 @@ class Installable(RepoJSONMixin): return info def to_json(self): - return { - "repo_name": self.repo_name, - "cog_name": self.name - } + return {"repo_name": self.repo_name, "cog_name": self.name} @classmethod def from_json(cls, data: dict, repo_mgr: "RepoManager"): - repo_name = data['repo_name'] - cog_name = data['cog_name'] + repo_name = data["repo_name"] + cog_name = data["cog_name"] repo = repo_mgr.get_repo(repo_name) if repo is not None: diff --git a/redbot/cogs/downloader/json_mixins.py b/redbot/cogs/downloader/json_mixins.py index 62fd8508b..b989e91ef 100644 --- a/redbot/cogs/downloader/json_mixins.py +++ b/redbot/cogs/downloader/json_mixins.py @@ -24,7 +24,7 @@ class RepoJSONMixin: return try: - with self._info_file.open(encoding='utf-8') as f: + with self._info_file.open(encoding="utf-8") as f: info = json.load(f) except json.JSONDecodeError: return @@ -34,4 +34,4 @@ class RepoJSONMixin: self.author = info.get("author") self.install_msg = info.get("install_msg") self.short = info.get("short") - self.description = info.get("description") \ No newline at end of file + self.description = info.get("description") diff --git a/redbot/cogs/downloader/locales/regen_messages.py b/redbot/cogs/downloader/locales/regen_messages.py index 0a3a2ea68..76cc534c5 100644 --- a/redbot/cogs/downloader/locales/regen_messages.py +++ b/redbot/cogs/downloader/locales/regen_messages.py @@ -1,15 +1,11 @@ import subprocess -TO_TRANSLATE = [ - '../downloader.py' -] +TO_TRANSLATE = ["../downloader.py"] def regen_messages(): - subprocess.run( - ['pygettext', '-n'] + TO_TRANSLATE - ) + subprocess.run(["pygettext", "-n"] + TO_TRANSLATE) if __name__ == "__main__": - regen_messages() \ No newline at end of file + regen_messages() diff --git a/redbot/cogs/downloader/log.py b/redbot/cogs/downloader/log.py index 9095e9c04..068eae932 100644 --- a/redbot/cogs/downloader/log.py +++ b/redbot/cogs/downloader/log.py @@ -1,3 +1,3 @@ import logging -log = logging.getLogger("red.downloader") \ No newline at end of file +log = logging.getLogger("red.downloader") diff --git a/redbot/cogs/downloader/repo_manager.py b/redbot/cogs/downloader/repo_manager.py index 788a34923..92305601c 100644 --- a/redbot/cogs/downloader/repo_manager.py +++ b/redbot/cogs/downloader/repo_manager.py @@ -27,16 +27,23 @@ class Repo(RepoJSONMixin): GIT_LATEST_COMMIT = "git -C {path} rev-parse {branch}" GIT_HARD_RESET = "git -C {path} reset --hard origin/{branch} -q" GIT_PULL = "git -C {path} pull -q --ff-only" - GIT_DIFF_FILE_STATUS = ("git -C {path} diff --no-commit-id --name-status" - " {old_hash} {new_hash}") - GIT_LOG = ("git -C {path} log --relative-date --reverse {old_hash}.." - " {relative_file_path}") + GIT_DIFF_FILE_STATUS = ( + "git -C {path} diff --no-commit-id --name-status" " {old_hash} {new_hash}" + ) + GIT_LOG = ("git -C {path} log --relative-date --reverse {old_hash}.." " {relative_file_path}") GIT_DISCOVER_REMOTE_URL = "git -C {path} config --get remote.origin.url" PIP_INSTALL = "{python} -m pip install -U -t {target_dir} {reqs}" - def __init__(self, name: str, url: str, branch: str, folder_path: Path, - available_modules: Tuple[Installable]=(), loop: asyncio.AbstractEventLoop=None): + def __init__( + self, + name: str, + url: str, + branch: str, + folder_path: Path, + available_modules: Tuple[Installable] = (), + loop: asyncio.AbstractEventLoop = None, + ): self.url = url self.branch = branch @@ -71,11 +78,12 @@ class Repo(RepoJSONMixin): return poss_repo def _existing_git_repo(self) -> (bool, Path): - git_path = self.folder_path / '.git' + git_path = self.folder_path / ".git" return git_path.exists(), git_path async def _get_file_update_statuses( - self, old_hash: str, new_hash: str) -> MutableMapping[str, str]: + self, old_hash: str, new_hash: str + ) -> MutableMapping[str, str]: """ Gets the file update status letters for each changed file between the two hashes. @@ -85,29 +93,25 @@ class Repo(RepoJSONMixin): """ p = await self._run( self.GIT_DIFF_FILE_STATUS.format( - path=self.folder_path, - old_hash=old_hash, - new_hash=new_hash + path=self.folder_path, old_hash=old_hash, new_hash=new_hash ) ) if p.returncode != 0: - raise GitDiffError("Git diff failed for repo at path:" - " {}".format(self.folder_path)) + raise GitDiffError("Git diff failed for repo at path:" " {}".format(self.folder_path)) - stdout = p.stdout.strip().decode().split('\n') + stdout = p.stdout.strip().decode().split("\n") ret = {} for filename in stdout: # TODO: filter these filenames by ones in self.available_modules - status, _, filepath = filename.partition('\t') + status, _, filepath = filename.partition("\t") ret[filepath] = status return ret - async def _get_commit_notes(self, old_commit_hash: str, - relative_file_path: str) -> str: + async def _get_commit_notes(self, old_commit_hash: str, relative_file_path: str) -> str: """ Gets the commit notes from git log. :param old_commit_hash: Point in time to start getting messages @@ -119,13 +123,15 @@ class Repo(RepoJSONMixin): self.GIT_LOG.format( path=self.folder_path, old_hash=old_commit_hash, - relative_file_path=relative_file_path + relative_file_path=relative_file_path, ) ) if p.returncode != 0: - raise GitException("An exception occurred while executing git log on" - " this repo: {}".format(self.folder_path)) + raise GitException( + "An exception occurred while executing git log on" + " this repo: {}".format(self.folder_path) + ) return p.stdout.decode().strip() @@ -146,10 +152,8 @@ class Repo(RepoJSONMixin): Installable(location=name) ) """ - for file_finder, name, is_pkg in pkgutil.walk_packages(path=[str(self.folder_path), ]): - curr_modules.append( - Installable(location=self.folder_path / name) - ) + for file_finder, name, is_pkg in pkgutil.walk_packages(path=[str(self.folder_path)]): + curr_modules.append(Installable(location=self.folder_path / name)) self.available_modules = curr_modules # noinspection PyTypeChecker @@ -157,12 +161,11 @@ class Repo(RepoJSONMixin): async def _run(self, *args, **kwargs): env = os.environ.copy() - env['GIT_TERMINAL_PROMPT'] = '0' - kwargs['env'] = env + env["GIT_TERMINAL_PROMPT"] = "0" + kwargs["env"] = env async with self._repo_lock: return await self._loop.run_in_executor( - self._executor, - functools.partial(sp_run, *args, stdout=PIPE, **kwargs) + self._executor, functools.partial(sp_run, *args, stdout=PIPE, **kwargs) ) async def clone(self) -> Tuple[str]: @@ -176,24 +179,17 @@ class Repo(RepoJSONMixin): """ exists, path = self._existing_git_repo() if exists: - raise ExistingGitRepo( - "A git repo already exists at path: {}".format(path) - ) + raise ExistingGitRepo("A git repo already exists at path: {}".format(path)) if self.branch is not None: p = await self._run( self.GIT_CLONE.format( - branch=self.branch, - url=self.url, - folder=self.folder_path + branch=self.branch, url=self.url, folder=self.folder_path ).split() ) else: p = await self._run( - self.GIT_CLONE_NO_BRANCH.format( - url=self.url, - folder=self.folder_path - ).split() + self.GIT_CLONE_NO_BRANCH.format(url=self.url, folder=self.folder_path).split() ) if p.returncode != 0: @@ -217,23 +213,18 @@ class Repo(RepoJSONMixin): """ exists, _ = self._existing_git_repo() if not exists: - raise MissingGitRepo( - "A git repo does not exist at path: {}".format(self.folder_path) - ) + raise MissingGitRepo("A git repo does not exist at path: {}".format(self.folder_path)) - p = await self._run( - self.GIT_CURRENT_BRANCH.format( - path=self.folder_path - ).split() - ) + p = await self._run(self.GIT_CURRENT_BRANCH.format(path=self.folder_path).split()) if p.returncode != 0: - raise GitException("Could not determine current branch" - " at path: {}".format(self.folder_path)) + raise GitException( + "Could not determine current branch" " at path: {}".format(self.folder_path) + ) return p.stdout.decode().strip() - async def current_commit(self, branch: str=None) -> str: + async def current_commit(self, branch: str = None) -> str: """Determine the current commit hash of the repo. Parameters @@ -252,15 +243,10 @@ class Repo(RepoJSONMixin): exists, _ = self._existing_git_repo() if not exists: - raise MissingGitRepo( - "A git repo does not exist at path: {}".format(self.folder_path) - ) + raise MissingGitRepo("A git repo does not exist at path: {}".format(self.folder_path)) p = await self._run( - self.GIT_LATEST_COMMIT.format( - path=self.folder_path, - branch=branch - ).split() + self.GIT_LATEST_COMMIT.format(path=self.folder_path, branch=branch).split() ) if p.returncode != 0: @@ -268,7 +254,7 @@ class Repo(RepoJSONMixin): return p.stdout.decode().strip() - async def current_url(self, folder: Path=None) -> str: + async def current_url(self, folder: Path = None) -> str: """ Discovers the FETCH URL for a Git repo. @@ -290,18 +276,14 @@ class Repo(RepoJSONMixin): if folder is None: folder = self.folder_path - p = await self._run( - Repo.GIT_DISCOVER_REMOTE_URL.format( - path=folder - ).split() - ) + p = await self._run(Repo.GIT_DISCOVER_REMOTE_URL.format(path=folder).split()) if p.returncode != 0: raise RuntimeError("Unable to discover a repo URL.") return p.stdout.decode().strip() - async def hard_reset(self, branch: str=None) -> None: + async def hard_reset(self, branch: str = None) -> None: """Perform a hard reset on the current repo. Parameters @@ -315,21 +297,18 @@ class Repo(RepoJSONMixin): exists, _ = self._existing_git_repo() if not exists: - raise MissingGitRepo( - "A git repo does not exist at path: {}".format(self.folder_path) - ) + raise MissingGitRepo("A git repo does not exist at path: {}".format(self.folder_path)) p = await self._run( - self.GIT_HARD_RESET.format( - path=self.folder_path, - branch=branch - ).split() + self.GIT_HARD_RESET.format(path=self.folder_path, branch=branch).split() ) if p.returncode != 0: - raise HardResetError("Some error occurred when trying to" - " execute a hard reset on the repo at" - " the following path: {}".format(self.folder_path)) + raise HardResetError( + "Some error occurred when trying to" + " execute a hard reset on the repo at" + " the following path: {}".format(self.folder_path) + ) async def update(self) -> (str, str): """Update the current branch of this repo. @@ -345,15 +324,13 @@ class Repo(RepoJSONMixin): await self.hard_reset(branch=curr_branch) - p = await self._run( - self.GIT_PULL.format( - path=self.folder_path - ).split() - ) + p = await self._run(self.GIT_PULL.format(path=self.folder_path).split()) if p.returncode != 0: - raise UpdateError("Git pull returned a non zero exit code" - " for the repo located at path: {}".format(self.folder_path)) + raise UpdateError( + "Git pull returned a non zero exit code" + " for the repo located at path: {}".format(self.folder_path) + ) new_commit = await self.current_commit(branch=curr_branch) @@ -389,7 +366,9 @@ class Repo(RepoJSONMixin): return await cog.copy_to(target_dir=target_dir) - async def install_libraries(self, target_dir: Path, libraries: Tuple[Installable]=()) -> bool: + async def install_libraries( + self, target_dir: Path, libraries: Tuple[Installable] = () + ) -> bool: """Install shared libraries to the target directory. If :code:`libraries` is not specified, all shared libraries in the repo @@ -469,16 +448,16 @@ class Repo(RepoJSONMixin): p = await self._run( self.PIP_INSTALL.format( - python=executable, - target_dir=target_dir, - reqs=" ".join(requirements) + python=executable, target_dir=target_dir, reqs=" ".join(requirements) ).split() ) if p.returncode != 0: - log.error("Something went wrong when installing" - " the following requirements:" - " {}".format(", ".join(requirements))) + log.error( + "Something went wrong when installing" + " the following requirements:" + " {}".format(", ".join(requirements)) + ) return False return True @@ -490,8 +469,7 @@ class Repo(RepoJSONMixin): """ # noinspection PyTypeChecker return tuple( - [m for m in self.available_modules - if m.type == InstallableType.COG and not m.hidden] + [m for m in self.available_modules if m.type == InstallableType.COG and not m.hidden] ) @property @@ -501,8 +479,7 @@ class Repo(RepoJSONMixin): """ # noinspection PyTypeChecker return tuple( - [m for m in self.available_modules - if m.type == InstallableType.SHARED_LIBRARY] + [m for m in self.available_modules if m.type == InstallableType.SHARED_LIBRARY] ) @classmethod @@ -515,6 +492,7 @@ class Repo(RepoJSONMixin): class RepoManager: + def __init__(self, downloader_config: Config): self.downloader_config = downloader_config @@ -526,7 +504,7 @@ class RepoManager: @property def repos_folder(self) -> Path: data_folder = data_manager.cog_data_path(self) - return data_folder / 'repos' + return data_folder / "repos" def does_repo_exist(self, name: str) -> bool: return name in self._repos @@ -537,7 +515,7 @@ class RepoManager: raise InvalidRepoName("Not a valid Python variable name.") return name.lower() - async def add_repo(self, url: str, name: str, branch: str="master") -> Repo: + async def add_repo(self, url: str, name: str, branch: str = "master") -> Repo: """Add and clone a git repository. Parameters @@ -557,13 +535,11 @@ class RepoManager: """ if self.does_repo_exist(name): raise InvalidRepoName( - "That repo name you provided already exists." - " Please choose another." + "That repo name you provided already exists." " Please choose another." ) # noinspection PyTypeChecker - r = Repo(url=url, name=name, branch=branch, - folder_path=self.repos_folder / name) + r = Repo(url=url, name=name, branch=branch, folder_path=self.repos_folder / name) await r.clone() self._repos[name] = r diff --git a/redbot/cogs/economy/economy.py b/redbot/cogs/economy/economy.py index 5a9e1f5e1..1a23c962d 100644 --- a/redbot/cogs/economy/economy.py +++ b/redbot/cogs/economy/economy.py @@ -36,45 +36,44 @@ class SMReel(Enum): PAYOUTS = { (SMReel.two, SMReel.two, SMReel.six): { "payout": lambda x: x * 2500 + x, - "phrase": _("JACKPOT! 226! Your bid has been multiplied * 2500!") + "phrase": _("JACKPOT! 226! Your bid has been multiplied * 2500!"), }, (SMReel.flc, SMReel.flc, SMReel.flc): { - "payout": lambda x: x + 1000, - "phrase": _("4LC! +1000!") + "payout": lambda x: x + 1000, "phrase": _("4LC! +1000!") }, (SMReel.cherries, SMReel.cherries, SMReel.cherries): { - "payout": lambda x: x + 800, - "phrase": _("Three cherries! +800!") + "payout": lambda x: x + 800, "phrase": _("Three cherries! +800!") }, (SMReel.two, SMReel.six): { - "payout": lambda x: x * 4 + x, - "phrase": _("2 6! Your bid has been multiplied * 4!") + "payout": lambda x: x * 4 + x, "phrase": _("2 6! Your bid has been multiplied * 4!") }, (SMReel.cherries, SMReel.cherries): { "payout": lambda x: x * 3 + x, - "phrase": _("Two cherries! Your bid has been multiplied * 3!") - }, - "3 symbols": { - "payout": lambda x: x + 500, - "phrase": _("Three symbols! +500!") + "phrase": _("Two cherries! Your bid has been multiplied * 3!"), }, + "3 symbols": {"payout": lambda x: x + 500, "phrase": _("Three symbols! +500!")}, "2 symbols": { "payout": lambda x: x * 2 + x, - "phrase": _("Two consecutive symbols! Your bid has been multiplied * 2!") + "phrase": _("Two consecutive symbols! Your bid has been multiplied * 2!"), }, } -SLOT_PAYOUTS_MSG = _("Slot machine payouts:\n" - "{two.value} {two.value} {six.value} Bet * 2500\n" - "{flc.value} {flc.value} {flc.value} +1000\n" - "{cherries.value} {cherries.value} {cherries.value} +800\n" - "{two.value} {six.value} Bet * 4\n" - "{cherries.value} {cherries.value} Bet * 3\n\n" - "Three symbols: +500\n" - "Two symbols: Bet * 2").format(**SMReel.__dict__) +SLOT_PAYOUTS_MSG = _( + "Slot machine payouts:\n" + "{two.value} {two.value} {six.value} Bet * 2500\n" + "{flc.value} {flc.value} {flc.value} +1000\n" + "{cherries.value} {cherries.value} {cherries.value} +800\n" + "{two.value} {six.value} Bet * 4\n" + "{cherries.value} {cherries.value} Bet * 3\n\n" + "Three symbols: +500\n" + "Two symbols: Bet * 2" +).format( + **SMReel.__dict__ +) def guild_only_check(): + async def pred(ctx: commands.Context): if await bank.is_global(): return True @@ -82,10 +81,12 @@ def guild_only_check(): return True else: return False + return commands.check(pred) class SetParser: + def __init__(self, argument): allowed = ("+", "-") self.sum = int(argument) @@ -115,19 +116,14 @@ class Economy: "SLOT_MIN": 5, "SLOT_MAX": 100, "SLOT_TIME": 0, - "REGISTER_CREDITS": 0 + "REGISTER_CREDITS": 0, } default_global_settings = default_guild_settings - default_member_settings = { - "next_payday": 0, - "last_slot": 0 - } + default_member_settings = {"next_payday": 0, "last_slot": 0} - default_role_settings = { - "PAYDAY_CREDITS": 0 - } + default_role_settings = {"PAYDAY_CREDITS": 0} default_user_settings = default_member_settings @@ -159,8 +155,7 @@ class Economy: bal = await bank.get_balance(user) currency = await bank.get_currency_name(ctx.guild) - await ctx.send(_("{}'s balance is {} {}").format( - user.display_name, bal, currency)) + await ctx.send(_("{}'s balance is {} {}").format(user.display_name, bal, currency)) @_bank.command() async def transfer(self, ctx: commands.Context, to: discord.Member, amount: int): @@ -173,9 +168,11 @@ class Economy: except ValueError as e: await ctx.send(str(e)) - await ctx.send(_("{} transferred {} {} to {}").format( - from_.display_name, amount, currency, to.display_name - )) + await ctx.send( + _("{} transferred {} {} to {}").format( + from_.display_name, amount, currency, to.display_name + ) + ) @_bank.command(name="set") @check_global_setting_admin() @@ -193,19 +190,25 @@ class Economy: if creds.operation == "deposit": await bank.deposit_credits(to, creds.sum) - await ctx.send(_("{} added {} {} to {}'s account.").format( - author.display_name, creds.sum, currency, to.display_name - )) + await ctx.send( + _("{} added {} {} to {}'s account.").format( + author.display_name, creds.sum, currency, to.display_name + ) + ) elif creds.operation == "withdraw": await bank.withdraw_credits(to, creds.sum) - await ctx.send(_("{} removed {} {} from {}'s account.").format( - author.display_name, creds.sum, currency, to.display_name - )) + await ctx.send( + _("{} removed {} {} from {}'s account.").format( + author.display_name, creds.sum, currency, to.display_name + ) + ) else: await bank.set_balance(to, creds.sum) - await ctx.send(_("{} set {}'s account to {} {}.").format( - author.display_name, to.display_name, creds.sum, currency - )) + await ctx.send( + _("{} set {}'s account to {} {}.").format( + author.display_name, to.display_name, creds.sum, currency + ) + ) @_bank.command() @guild_only_check() @@ -214,19 +217,20 @@ class Economy: """Deletes bank accounts""" if confirmation is False: await ctx.send( - _("This will delete all bank accounts for {}.\nIf you're sure, type " - "`{}bank reset yes`").format( - self.bot.user.name if await bank.is_global() else "this server", - ctx.prefix + _( + "This will delete all bank accounts for {}.\nIf you're sure, type " + "`{}bank reset yes`" + ).format( + self.bot.user.name if await bank.is_global() else "this server", ctx.prefix ) ) else: await bank.wipe_bank() - await ctx.send(_("All bank accounts for {} have been " - "deleted.").format( - self.bot.user.name if await bank.is_global() else "this server" - ) - ) + await ctx.send( + _("All bank accounts for {} have been " "deleted.").format( + self.bot.user.name if await bank.is_global() else "this server" + ) + ) @commands.command() @guild_only_check() @@ -245,50 +249,65 @@ class Economy: await self.config.user(author).next_payday.set(next_payday) pos = await bank.get_leaderboard_position(author) - await ctx.send(_( - "{0.mention} Here, take some {1}. Enjoy! (+{2} {1}!)\n\n" - "You currently have {3} {1}.\n\n" - "You are currently #{4} on the leaderboard!" - ).format( - author, credits_name, str(await self.config.PAYDAY_CREDITS()), - str(await bank.get_balance(author)), pos - )) + await ctx.send( + _( + "{0.mention} Here, take some {1}. Enjoy! (+{2} {1}!)\n\n" + "You currently have {3} {1}.\n\n" + "You are currently #{4} on the leaderboard!" + ).format( + author, + credits_name, + str(await self.config.PAYDAY_CREDITS()), + str(await bank.get_balance(author)), + pos, + ) + ) else: dtime = self.display_time(next_payday - cur_time) await ctx.send( - _("{} Too soon. For your next payday you have to" - " wait {}.").format(author.mention, dtime) + _("{} Too soon. For your next payday you have to" " wait {}.").format( + author.mention, dtime + ) ) else: next_payday = await self.config.member(author).next_payday() if cur_time >= next_payday: credit_amount = await self.config.guild(guild).PAYDAY_CREDITS() for role in author.roles: - role_credits = await self.config.role(role).PAYDAY_CREDITS() # Nice variable name + role_credits = await self.config.role( + role + ).PAYDAY_CREDITS() # Nice variable name if role_credits > credit_amount: credit_amount = role_credits await bank.deposit_credits(author, credit_amount) next_payday = cur_time + await self.config.guild(guild).PAYDAY_TIME() await self.config.member(author).next_payday.set(next_payday) pos = await bank.get_leaderboard_position(author) - await ctx.send(_( - "{0.mention} Here, take some {1}. Enjoy! (+{2} {1}!)\n\n" - "You currently have {3} {1}.\n\n" - "You are currently #{4} on the leaderboard!" - ).format( - author, credits_name, credit_amount, - str(await bank.get_balance(author)), pos - )) + await ctx.send( + _( + "{0.mention} Here, take some {1}. Enjoy! (+{2} {1}!)\n\n" + "You currently have {3} {1}.\n\n" + "You are currently #{4} on the leaderboard!" + ).format( + author, + credits_name, + credit_amount, + str(await bank.get_balance(author)), + pos, + ) + ) else: dtime = self.display_time(next_payday - cur_time) await ctx.send( - _("{} Too soon. For your next payday you have to" - " wait {}.").format(author.mention, dtime)) + _("{} Too soon. For your next payday you have to" " wait {}.").format( + author.mention, dtime + ) + ) @commands.command() @guild_only_check() - async def leaderboard(self, ctx: commands.Context, top: int = 10, show_global: bool=False): + async def leaderboard(self, ctx: commands.Context, top: int = 10, show_global: bool = False): """Prints out the leaderboard Defaults to top 10""" @@ -296,7 +315,9 @@ class Economy: guild = ctx.guild if top < 1: top = 10 - if await bank.is_global() and show_global: # show_global is only applicable if bank is global + if ( + await bank.is_global() and show_global + ): # show_global is only applicable if bank is global guild = None bank_sorted = await bank.get_leaderboard(positions=top, guild=guild) if len(bank_sorted) < top: @@ -310,8 +331,12 @@ class Economy: balance = acc[1]["balance"] balwidth = 2 highscore += "{pos: <{poswidth}} {name: <{namewidth}s} {balance: >{balwidth}}\n".format( - pos=pos, poswidth=poswidth, name=name, namewidth=namewidth, - balance=balance, balwidth=balwidth + pos=pos, + poswidth=poswidth, + name=name, + namewidth=namewidth, + balance=balance, + balwidth=balwidth, ) if highscore != "": for page in pagify(highscore, shorten_by=12): @@ -337,7 +362,11 @@ class Economy: slot_time = await self.config.SLOT_TIME() last_slot = await self.config.user(author).last_slot() else: - valid_bid = await self.config.guild(guild).SLOT_MIN() <= bid <= await self.config.guild(guild).SLOT_MAX() + valid_bid = await self.config.guild( + guild + ).SLOT_MIN() <= bid <= await self.config.guild( + guild + ).SLOT_MAX() slot_time = await self.config.guild(guild).SLOT_TIME() last_slot = await self.config.member(author).last_slot() now = calendar.timegm(ctx.message.created_at.utctimetuple()) @@ -364,9 +393,11 @@ class Economy: default_reel.rotate(random.randint(-999, 999)) # weeeeee new_reel = deque(default_reel, maxlen=3) # we need only 3 symbols reels.append(new_reel) # for each reel - rows = ((reels[0][0], reels[1][0], reels[2][0]), - (reels[0][1], reels[1][1], reels[2][1]), - (reels[0][2], reels[1][2], reels[2][2])) + rows = ( + (reels[0][0], reels[1][0], reels[2][0]), + (reels[0][1], reels[1][1], reels[2][1]), + (reels[0][2], reels[1][2], reels[2][2]), + ) slot = "~~\n~~" # Mobile friendly for i, row in enumerate(rows): # Let's build the slot to show @@ -378,8 +409,7 @@ class Economy: payout = PAYOUTS.get(rows[1]) if not payout: # Checks for two-consecutive-symbols special rewards - payout = PAYOUTS.get((rows[1][0], rows[1][1]), - PAYOUTS.get((rows[1][1], rows[1][2]))) + payout = PAYOUTS.get((rows[1][0], rows[1][1]), PAYOUTS.get((rows[1][1], rows[1][2]))) if not payout: # Still nothing. Let's check for 3 generic same symbols # or 2 consecutive symbols @@ -395,15 +425,20 @@ class Economy: pay = payout["payout"](bid) now = then - bid + pay await bank.set_balance(author, now) - await channel.send(_("{}\n{} {}\n\nYour bid: {}\n{} → {}!" - "").format(slot, author.mention, - payout["phrase"], bid, then, now)) + await channel.send( + _("{}\n{} {}\n\nYour bid: {}\n{} → {}!" "").format( + slot, author.mention, payout["phrase"], bid, then, now + ) + ) else: then = await bank.get_balance(author) await bank.withdraw_credits(author, bid) now = then - bid - await channel.send(_("{}\n{} Nothing!\nYour bid: {}\n{} → {}!" - "").format(slot, author.mention, bid, then, now)) + await channel.send( + _("{}\n{} Nothing!\nYour bid: {}\n{} → {}!" "").format( + slot, author.mention, bid, then, now + ) + ) @commands.group() @guild_only_check() @@ -427,17 +462,18 @@ class Economy: payday_amount = await self.config.guild(guild).PAYDAY_CREDITS() register_amount = await bank.get_default_balance(guild) msg = box( - _("Minimum slot bid: {}\n" - "Maximum slot bid: {}\n" - "Slot cooldown: {}\n" - "Payday amount: {}\n" - "Payday cooldown: {}\n" - "Amount given at account registration: {}" - "").format( - slot_min, slot_max, slot_time, - payday_amount, payday_time, register_amount + _( + "Minimum slot bid: {}\n" + "Maximum slot bid: {}\n" + "Slot cooldown: {}\n" + "Payday amount: {}\n" + "Payday cooldown: {}\n" + "Amount given at account registration: {}" + "" + ).format( + slot_min, slot_max, slot_time, payday_amount, payday_time, register_amount ), - _("Current Economy settings:") + _("Current Economy settings:"), ) await ctx.send(msg) @@ -445,7 +481,7 @@ class Economy: async def slotmin(self, ctx: commands.Context, bid: int): """Minimum slot machine bid""" if bid < 1: - await ctx.send(_('Invalid min bid amount.')) + await ctx.send(_("Invalid min bid amount.")) return guild = ctx.guild if await bank.is_global(): @@ -460,8 +496,7 @@ class Economy: """Maximum slot machine bid""" slot_min = await self.config.SLOT_MIN() if bid < 1 or bid < slot_min: - await ctx.send(_('Invalid slotmax bid amount. Must be greater' - ' than slotmin.')) + await ctx.send(_("Invalid slotmax bid amount. Must be greater" " than slotmin.")) return guild = ctx.guild credits_name = await bank.get_currency_name(guild) @@ -489,8 +524,11 @@ class Economy: await self.config.PAYDAY_TIME.set(seconds) else: await self.config.guild(guild).PAYDAY_TIME.set(seconds) - await ctx.send(_("Value modified. At least {} seconds must pass " - "between each payday.").format(seconds)) + await ctx.send( + _("Value modified. At least {} seconds must pass " "between each payday.").format( + seconds + ) + ) @economyset.command() async def paydayamount(self, ctx: commands.Context, creds: int): @@ -504,8 +542,7 @@ class Economy: await self.config.PAYDAY_CREDITS.set(creds) else: await self.config.guild(guild).PAYDAY_CREDITS.set(creds) - await ctx.send(_("Every payday will now give {} {}." - "").format(creds, credits_name)) + await ctx.send(_("Every payday will now give {} {}." "").format(creds, credits_name)) @economyset.command() async def rolepaydayamount(self, ctx: commands.Context, role: discord.Role, creds: int): @@ -516,8 +553,11 @@ class Economy: await ctx.send("The bank must be per-server for per-role paydays to work.") else: await self.config.role(role).PAYDAY_CREDITS.set(creds) - await ctx.send(_("Every payday will now give {} {} to people with the role {}." - "").format(creds, credits_name, role.name)) + await ctx.send( + _("Every payday will now give {} {} to people with the role {}." "").format( + creds, credits_name, role.name + ) + ) @economyset.command() async def registeramount(self, ctx: commands.Context, creds: int): @@ -527,17 +567,18 @@ class Economy: creds = 0 credits_name = await bank.get_currency_name(guild) await bank.set_default_balance(creds, guild) - await ctx.send(_("Registering an account will now give {} {}." - "").format(creds, credits_name)) + await ctx.send( + _("Registering an account will now give {} {}." "").format(creds, credits_name) + ) # What would I ever do without stackoverflow? def display_time(self, seconds, granularity=2): intervals = ( # Source: http://stackoverflow.com/a/24542445 - (_('weeks'), 604800), # 60 * 60 * 24 * 7 - (_('days'), 86400), # 60 * 60 * 24 - (_('hours'), 3600), # 60 * 60 - (_('minutes'), 60), - (_('seconds'), 1), + (_("weeks"), 604800), # 60 * 60 * 24 * 7 + (_("days"), 86400), # 60 * 60 * 24 + (_("hours"), 3600), # 60 * 60 + (_("minutes"), 60), + (_("seconds"), 1), ) result = [] @@ -547,6 +588,6 @@ class Economy: if value: seconds -= value * count if value == 1: - name = name.rstrip('s') + name = name.rstrip("s") result.append("{} {}".format(value, name)) - return ', '.join(result[:granularity]) + return ", ".join(result[:granularity]) diff --git a/redbot/cogs/economy/locales/regen_messages.py b/redbot/cogs/economy/locales/regen_messages.py index cecea4c80..28fb42862 100644 --- a/redbot/cogs/economy/locales/regen_messages.py +++ b/redbot/cogs/economy/locales/regen_messages.py @@ -1,15 +1,11 @@ import subprocess -TO_TRANSLATE = [ - '../economy.py' -] +TO_TRANSLATE = ["../economy.py"] def regen_messages(): - subprocess.run( - ['pygettext', '-n'] + TO_TRANSLATE - ) + subprocess.run(["pygettext", "-n"] + TO_TRANSLATE) if __name__ == "__main__": - regen_messages() \ No newline at end of file + regen_messages() diff --git a/redbot/cogs/filter/filter.py b/redbot/cogs/filter/filter.py index 2b03e6d8a..8b9ecb8df 100644 --- a/redbot/cogs/filter/filter.py +++ b/redbot/cogs/filter/filter.py @@ -21,12 +21,9 @@ class Filter: "filterban_count": 0, "filterban_time": 0, "filter_names": False, - "filter_default_name": "John Doe" - } - default_member_settings = { - "filter_count": 0, - "next_reset_time": 0 + "filter_default_name": "John Doe", } + default_member_settings = {"filter_count": 0, "next_reset_time": 0} self.settings.register_guild(**default_guild_settings) self.settings.register_member(**default_member_settings) self.register_task = self.bot.loop.create_task(self.register_filterban()) @@ -37,8 +34,7 @@ class Filter: async def register_filterban(self): try: await modlog.register_casetype( - "filterban", False, ":filing_cabinet: :hammer:", - "Filter ban", "ban" + "filterban", False, ":filing_cabinet: :hammer:", "Filter ban", "ban" ) except RuntimeError: pass @@ -79,13 +75,12 @@ class Filter: word_list = [] tmp = "" for word in split_words: - if not word.startswith("\"")\ - and not word.endswith("\"") and not tmp: + if not word.startswith('"') and not word.endswith('"') and not tmp: word_list.append(word) else: - if word.startswith("\""): + if word.startswith('"'): tmp += word[1:] - elif word.endswith("\""): + elif word.endswith('"'): tmp += word[:-1] word_list.append(tmp) tmp = "" @@ -110,13 +105,12 @@ class Filter: word_list = [] tmp = "" for word in split_words: - if not word.startswith("\"")\ - and not word.endswith("\"") and not tmp: + if not word.startswith('"') and not word.endswith('"') and not tmp: word_list.append(word) else: - if word.startswith("\""): + if word.startswith('"'): tmp += word[1:] - elif word.endswith("\""): + elif word.endswith('"'): tmp += word[:-1] word_list.append(tmp) tmp = "" @@ -139,14 +133,10 @@ class Filter: await self.settings.guild(guild).filter_names.set(not current_setting) if current_setting: await ctx.send( - _("Names and nicknames will no longer be " - "checked against the filter") + _("Names and nicknames will no longer be " "checked against the filter") ) else: - await ctx.send( - _("Names and nicknames will now be checked against " - "the filter") - ) + await ctx.send(_("Names and nicknames will now be checked against " "the filter")) @_filter.command(name="defaultname") async def filter_default_name(self, ctx: commands.Context, name: str): @@ -160,17 +150,17 @@ class Filter: await ctx.send(_("The name to use on filtered names has been set")) @_filter.command(name="ban") - async def filter_ban( - self, ctx: commands.Context, count: int, timeframe: int): + async def filter_ban(self, ctx: commands.Context, count: int, timeframe: int): """ Sets up an autoban if the specified number of messages are filtered in the specified amount of time (in seconds) """ if (count <= 0) != (timeframe <= 0): await ctx.send( - _("Count and timeframe either both need to be 0 " - "or both need to be greater than 0!" - ) + _( + "Count and timeframe either both need to be 0 " + "or both need to be greater than 0!" + ) ) return elif count == 0 and timeframe == 0: @@ -213,9 +203,7 @@ class Filter: if filter_count > 0 and filter_time > 0: if message.created_at.timestamp() >= next_reset_time: next_reset_time = message.created_at.timestamp() + filter_time - await self.settings.member(author).next_reset_time.set( - next_reset_time - ) + await self.settings.member(author).next_reset_time.set(next_reset_time) if user_count > 0: user_count = 0 await self.settings.member(author).filter_count.set(user_count) @@ -231,8 +219,10 @@ class Filter: if filter_count > 0 and filter_time > 0: user_count += 1 await self.settings.member(author).filter_count.set(user_count) - if user_count >= filter_count and \ - message.created_at.timestamp() < next_reset_time: + if ( + user_count >= filter_count + and message.created_at.timestamp() < next_reset_time + ): reason = "Autoban (too many filtered messages)" try: await server.ban(author, reason=reason) @@ -240,8 +230,13 @@ class Filter: pass else: await modlog.create_case( - self.bot, server, message.created_at, - "filterban", author, server.me, reason + self.bot, + server, + message.created_at, + "filterban", + author, + server.me, + reason, ) async def on_message(self, message: discord.Message): @@ -251,7 +246,7 @@ class Filter: valid_user = isinstance(author, discord.Member) and not author.bot if not valid_user: return - + # Bots and mods or superior are ignored from the filter mod_or_superior = await is_mod_or_superior(self.bot, obj=author) if mod_or_superior: @@ -266,7 +261,7 @@ class Filter: valid_user = isinstance(author, discord.Member) and not author.bot if not valid_user: return - + # Bots and mods or superior are ignored from the filter mod_or_superior = await is_mod_or_superior(self.bot, obj=author) if mod_or_superior: @@ -323,4 +318,3 @@ class Filter: except: pass break - diff --git a/redbot/cogs/filter/locales/regen_messages.py b/redbot/cogs/filter/locales/regen_messages.py index f6aecb74e..c545a0d4b 100644 --- a/redbot/cogs/filter/locales/regen_messages.py +++ b/redbot/cogs/filter/locales/regen_messages.py @@ -1,15 +1,11 @@ import subprocess -TO_TRANSLATE = [ - '../filter.py' -] +TO_TRANSLATE = ["../filter.py"] def regen_messages(): - subprocess.run( - ['pygettext', '-n'] + TO_TRANSLATE - ) + subprocess.run(["pygettext", "-n"] + TO_TRANSLATE) if __name__ == "__main__": - regen_messages() \ No newline at end of file + regen_messages() diff --git a/redbot/cogs/general/general.py b/redbot/cogs/general/general.py index b1b262bce..3c3798543 100644 --- a/redbot/cogs/general/general.py +++ b/redbot/cogs/general/general.py @@ -15,12 +15,13 @@ _ = Translator("General", __file__) class RPS(Enum): - rock = "\N{MOYAI}" - paper = "\N{PAGE FACING UP}" + rock = "\N{MOYAI}" + paper = "\N{PAGE FACING UP}" scissors = "\N{BLACK SCISSORS}" class RPSParser: + def __init__(self, argument): argument = argument.lower() if argument == "rock": @@ -40,13 +41,26 @@ class General: def __init__(self): self.stopwatches = {} self.ball = [ - _("As I see it, yes"), _("It is certain"), _("It is decidedly so"), - _("Most likely"), _("Outlook good"), _("Signs point to yes"), - _("Without a doubt"), _("Yes"), _("Yes – definitely"), _("You may rely on it"), - _("Reply hazy, try again"), _("Ask again later"), - _("Better not tell you now"), _("Cannot predict now"), - _("Concentrate and ask again"), _("Don't count on it"), _("My reply is no"), - _("My sources say no"), _("Outlook not so good"), _("Very doubtful") + _("As I see it, yes"), + _("It is certain"), + _("It is decidedly so"), + _("Most likely"), + _("Outlook good"), + _("Signs point to yes"), + _("Without a doubt"), + _("Yes"), + _("Yes – definitely"), + _("You may rely on it"), + _("Reply hazy, try again"), + _("Ask again later"), + _("Better not tell you now"), + _("Cannot predict now"), + _("Concentrate and ask again"), + _("Don't count on it"), + _("My reply is no"), + _("My sources say no"), + _("Outlook not so good"), + _("Very doubtful"), ] @commands.command() @@ -57,12 +71,12 @@ class General: """ choices = [escape(c, mass_mentions=True) for c in choices] if len(choices) < 2: - await ctx.send(_('Not enough choices to pick from.')) + await ctx.send(_("Not enough choices to pick from.")) else: await ctx.send(choice(choices)) @commands.command() - async def roll(self, ctx, number : int = 100): + async def roll(self, ctx, number: int = 100): """Rolls random number (between 1 and user choice) Defaults to 100. @@ -70,14 +84,12 @@ class General: author = ctx.author if number > 1: n = randint(1, number) - await ctx.send( - _("{} :game_die: {} :game_die:").format(author.mention, n) - ) + await ctx.send(_("{} :game_die: {} :game_die:").format(author.mention, n)) else: await ctx.send(_("{} Maybe higher than 1? ;P").format(author.mention)) @commands.command() - async def flip(self, ctx, user: discord.Member=None): + async def flip(self, ctx, user: discord.Member = None): """Flips a coin... or a user. Defaults to coin. @@ -86,8 +98,7 @@ class General: msg = "" if user.id == ctx.bot.user.id: user = ctx.author - msg = _("Nice try. You think this is funny?\n" - "How about *this* instead:\n\n") + msg = _("Nice try. You think this is funny?\n" "How about *this* instead:\n\n") char = "abcdefghijklmnopqrstuvwxyz" tran = "ɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz" table = str.maketrans(char, tran) @@ -98,45 +109,37 @@ class General: name = name.translate(table) await ctx.send(msg + "(╯°□°)╯︵ " + name[::-1]) else: - await ctx.send( - _("*flips a coin and... ") + choice([_("HEADS!*"), _("TAILS!*")]) - ) + await ctx.send(_("*flips a coin and... ") + choice([_("HEADS!*"), _("TAILS!*")])) @commands.command() - async def rps(self, ctx, your_choice : RPSParser): + async def rps(self, ctx, your_choice: RPSParser): """Play rock paper scissors""" author = ctx.author player_choice = your_choice.choice red_choice = choice((RPS.rock, RPS.paper, RPS.scissors)) cond = { - (RPS.rock, RPS.paper) : False, - (RPS.rock, RPS.scissors) : True, - (RPS.paper, RPS.rock) : True, - (RPS.paper, RPS.scissors) : False, - (RPS.scissors, RPS.rock) : False, - (RPS.scissors, RPS.paper) : True - } + (RPS.rock, RPS.paper): False, + (RPS.rock, RPS.scissors): True, + (RPS.paper, RPS.rock): True, + (RPS.paper, RPS.scissors): False, + (RPS.scissors, RPS.rock): False, + (RPS.scissors, RPS.paper): True, + } if red_choice == player_choice: - outcome = None # Tie + outcome = None # Tie else: outcome = cond[(player_choice, red_choice)] if outcome is True: - await ctx.send(_("{} You win {}!").format( - red_choice.value, author.mention - )) + await ctx.send(_("{} You win {}!").format(red_choice.value, author.mention)) elif outcome is False: - await ctx.send(_("{} You lose {}!").format( - red_choice.value, author.mention - )) + await ctx.send(_("{} You lose {}!").format(red_choice.value, author.mention)) else: - await ctx.send(_("{} We're square {}!").format( - red_choice.value, author.mention - )) + await ctx.send(_("{} We're square {}!").format(red_choice.value, author.mention)) @commands.command(name="8", aliases=["8ball"]) - async def _8ball(self, ctx, *, question : str): + async def _8ball(self, ctx, *, question: str): """Ask 8 ball a question Question must end with a question mark. @@ -160,14 +163,14 @@ class General: self.stopwatches.pop(author.id, None) @commands.command() - async def lmgtfy(self, ctx, *, search_terms : str): + async def lmgtfy(self, ctx, *, search_terms: str): """Creates a lmgtfy link""" search_terms = escape(search_terms.replace(" ", "+"), mass_mentions=True) await ctx.send("https://lmgtfy.com/?q={}".format(search_terms)) @commands.command(hidden=True) @commands.guild_only() - async def hug(self, ctx, user : discord.Member, intensity : int=1): + async def hug(self, ctx, user: discord.Member, intensity: int = 1): """Because everyone likes hugs Up to 10 intensity levels.""" @@ -186,7 +189,7 @@ class General: @commands.command() @commands.guild_only() - async def userinfo(self, ctx, *, user: discord.Member=None): + async def userinfo(self, ctx, *, user: discord.Member = None): """Shows users's informations""" author = ctx.author guild = ctx.guild @@ -196,8 +199,7 @@ class General: # A special case for a special someone :^) special_date = datetime.datetime(2016, 1, 10, 6, 8, 4, 443000) - is_special = (user.id == 96130341705637888 and - guild.id == 133049272517001216) + is_special = (user.id == 96130341705637888 and guild.id == 133049272517001216) roles = sorted(user.roles)[1:] @@ -206,12 +208,11 @@ class General: since_joined = (ctx.message.created_at - joined_at).days user_joined = joined_at.strftime("%d %b %Y %H:%M") user_created = user.created_at.strftime("%d %b %Y %H:%M") - member_number = sorted(guild.members, - key=lambda m: m.joined_at).index(user) + 1 + member_number = sorted(guild.members, key=lambda m: m.joined_at).index(user) + 1 created_on = _("{}\n({} days ago)").format(user_created, since_created) joined_on = _("{}\n({} days ago)").format(user_joined, since_joined) - + activity = _("Chilling in {} status").format(user.status) if user.activity is None: # Default status pass @@ -233,15 +234,14 @@ class General: data.add_field(name=_("Joined Discord on"), value=created_on) data.add_field(name=_("Joined this server on"), value=joined_on) data.add_field(name=_("Roles"), value=roles, inline=False) - data.set_footer(text=_("Member #{} | User ID: {}" - "").format(member_number, user.id)) + data.set_footer(text=_("Member #{} | User ID: {}" "").format(member_number, user.id)) name = str(user) name = " ~ ".join((name, user.nick)) if user.nick else name if user.avatar: avatar = user.avatar_url - avatar = avatar.replace('webp', 'png') + avatar = avatar.replace("webp", "png") data.set_author(name=name, url=avatar) data.set_thumbnail(url=avatar) else: @@ -250,31 +250,34 @@ class General: try: await ctx.send(embed=data) except discord.HTTPException: - await ctx.send(_("I need the `Embed links` permission " - "to send this.")) + await ctx.send(_("I need the `Embed links` permission " "to send this.")) @commands.command() @commands.guild_only() async def serverinfo(self, ctx): """Shows server's informations""" guild = ctx.guild - online = len([m.status for m in guild.members - if m.status == discord.Status.online or - m.status == discord.Status.idle]) + online = len( + [ + m.status + for m in guild.members + if m.status == discord.Status.online or m.status == discord.Status.idle + ] + ) total_users = len(guild.members) text_channels = len(guild.text_channels) voice_channels = len(guild.voice_channels) passed = (ctx.message.created_at - guild.created_at).days - created_at = (_("Since {}. That's over {} days ago!" - "").format(guild.created_at.strftime("%d %b %Y %H:%M"), - passed)) + created_at = ( + _("Since {}. That's over {} days ago!" "").format( + guild.created_at.strftime("%d %b %Y %H:%M"), passed + ) + ) - colour = ''.join([choice('0123456789ABCDEF') for x in range(6)]) + colour = "".join([choice("0123456789ABCDEF") for x in range(6)]) colour = randint(0, 0xFFFFFF) - data = discord.Embed( - description=created_at, - colour=discord.Colour(value=colour)) + data = discord.Embed(description=created_at, colour=discord.Colour(value=colour)) data.add_field(name=_("Region"), value=str(guild.region)) data.add_field(name=_("Users"), value="{}/{}".format(online, total_users)) data.add_field(name=_("Text Channels"), value=text_channels) @@ -292,16 +295,16 @@ class General: try: await ctx.send(embed=data) except discord.HTTPException: - await ctx.send(_("I need the `Embed links` permission " - "to send this.")) + await ctx.send(_("I need the `Embed links` permission " "to send this.")) @commands.command() - async def urban(self, ctx, *, search_terms: str, definition_number: int=1): + async def urban(self, ctx, *, search_terms: str, definition_number: int = 1): """Urban Dictionary search Definition number must be between 1 and 10""" + def encode(s): - return quote_plus(s, encoding='utf-8', errors='replace') + return quote_plus(s, encoding="utf-8", errors="replace") # definition_number is just there to show up in the help # all this mess is to avoid forcing double quotes on the user @@ -313,8 +316,8 @@ class General: search_terms = search_terms[:-1] else: pos = 0 - if pos not in range(0, 11): # API only provides the - pos = 0 # top 10 definitions + if pos not in range(0, 11): # API only provides the + pos = 0 # top 10 definitions except ValueError: pos = 0 @@ -326,18 +329,19 @@ class General: result = await r.json() item_list = result["list"] if item_list: - definition = item_list[pos]['definition'] - example = item_list[pos]['example'] + definition = item_list[pos]["definition"] + example = item_list[pos]["example"] defs = len(item_list) - msg = ("**Definition #{} out of {}:\n**{}\n\n" - "**Example:\n**{}".format(pos+1, defs, definition, - example)) + msg = ( + "**Definition #{} out of {}:\n**{}\n\n" + "**Example:\n**{}".format(pos + 1, defs, definition, example) + ) msg = pagify(msg, ["\n"]) for page in msg: await ctx.send(page) else: await ctx.send(_("Your search terms gave no results.")) except IndexError: - await ctx.send(_("There is no definition #{}").format(pos+1)) + await ctx.send(_("There is no definition #{}").format(pos + 1)) except: await ctx.send(_("Error.")) diff --git a/redbot/cogs/general/locales/regen_messages.py b/redbot/cogs/general/locales/regen_messages.py index 441baebe0..67e8e180b 100644 --- a/redbot/cogs/general/locales/regen_messages.py +++ b/redbot/cogs/general/locales/regen_messages.py @@ -1,15 +1,11 @@ import subprocess -TO_TRANSLATE = [ - '../general.py' -] +TO_TRANSLATE = ["../general.py"] def regen_messages(): - subprocess.run( - ['pygettext', '-n'] + TO_TRANSLATE - ) + subprocess.run(["pygettext", "-n"] + TO_TRANSLATE) if __name__ == "__main__": - regen_messages() \ No newline at end of file + regen_messages() diff --git a/redbot/cogs/image/image.py b/redbot/cogs/image/image.py index d52d9d29a..5f181356f 100644 --- a/redbot/cogs/image/image.py +++ b/redbot/cogs/image/image.py @@ -13,9 +13,7 @@ GIPHY_API_KEY = "dc6zaTOxFJmzC" @cog_i18n(_) class Image: """Image related commands.""" - default_global = { - "imgur_client_id": None - } + default_global = {"imgur_client_id": None} def __init__(self, bot): self.bot = bot @@ -45,7 +43,9 @@ class Image: if not imgur_client_id: await ctx.send( _("A client ID has not been set! Please set one with {}").format( - "`{}imgurcreds`".format(ctx.prefix))) + "`{}imgurcreds`".format(ctx.prefix) + ) + ) return headers = {"Authorization": "Client-ID {}".format(imgur_client_id)} async with self.session.get(url, headers=headers, params=params) as search_get: @@ -66,7 +66,9 @@ class Image: await ctx.send(_("Something went wrong. Error code is {}").format(data["status"])) @_imgur.command(name="subreddit") - async def imgur_subreddit(self, ctx, subreddit: str, sort_type: str="top", window: str="day"): + async def imgur_subreddit( + self, ctx, subreddit: str, sort_type: str = "top", window: str = "day" + ): """Gets images from the specified subreddit section Sort types: new, top @@ -90,7 +92,9 @@ class Image: if not imgur_client_id: await ctx.send( _("A client ID has not been set! Please set one with {}").format( - "`{}imgurcreds`".format(ctx.prefix))) + "`{}imgurcreds`".format(ctx.prefix) + ) + ) return links = [] @@ -139,8 +143,10 @@ class Image: await ctx.send_help() return - url = ("http://api.giphy.com/v1/gifs/search?&api_key={}&q={}" - "".format(GIPHY_API_KEY, keywords)) + url = ( + "http://api.giphy.com/v1/gifs/search?&api_key={}&q={}" + "".format(GIPHY_API_KEY, keywords) + ) async with self.session.get(url) as r: result = await r.json() @@ -161,8 +167,10 @@ class Image: await ctx.send_help() return - url = ("http://api.giphy.com/v1/gifs/random?&api_key={}&tag={}" - "".format(GIPHY_API_KEY, keywords)) + url = ( + "http://api.giphy.com/v1/gifs/random?&api_key={}&tag={}" + "".format(GIPHY_API_KEY, keywords) + ) async with self.session.get(url) as r: result = await r.json() diff --git a/redbot/cogs/image/locales/regen_messages.py b/redbot/cogs/image/locales/regen_messages.py index bc8632837..bf7660d4f 100644 --- a/redbot/cogs/image/locales/regen_messages.py +++ b/redbot/cogs/image/locales/regen_messages.py @@ -1,15 +1,11 @@ import subprocess -TO_TRANSLATE = [ - '../image.py' -] +TO_TRANSLATE = ["../image.py"] def regen_messages(): - subprocess.run( - ['pygettext', '-n'] + TO_TRANSLATE - ) + subprocess.run(["pygettext", "-n"] + TO_TRANSLATE) if __name__ == "__main__": - regen_messages() \ No newline at end of file + regen_messages() diff --git a/redbot/cogs/mod/checks.py b/redbot/cogs/mod/checks.py index 54e657c8b..56e1b1463 100644 --- a/redbot/cogs/mod/checks.py +++ b/redbot/cogs/mod/checks.py @@ -3,6 +3,7 @@ import discord def mod_or_voice_permissions(**perms): + async def pred(ctx: commands.Context): author = ctx.author guild = ctx.guild @@ -23,10 +24,12 @@ def mod_or_voice_permissions(**perms): return False else: return True + return commands.check(pred) def admin_or_voice_permissions(**perms): + async def pred(ctx: commands.Context): author = ctx.author guild = ctx.guild @@ -42,10 +45,12 @@ def admin_or_voice_permissions(**perms): return False else: return True + return commands.check(pred) def bot_has_voice_permissions(**perms): + async def pred(ctx: commands.Context): guild = ctx.guild for vc in guild.voice_channels: @@ -55,4 +60,5 @@ def bot_has_voice_permissions(**perms): return False else: return True + return commands.check(pred) diff --git a/redbot/cogs/mod/locales/regen_messages.py b/redbot/cogs/mod/locales/regen_messages.py index e04dd4843..cd12e1867 100644 --- a/redbot/cogs/mod/locales/regen_messages.py +++ b/redbot/cogs/mod/locales/regen_messages.py @@ -1,15 +1,11 @@ import subprocess -TO_TRANSLATE = [ - '../mod.py' -] +TO_TRANSLATE = ["../mod.py"] def regen_messages(): - subprocess.run( - ['pygettext', '-n'] + TO_TRANSLATE - ) + subprocess.run(["pygettext", "-n"] + TO_TRANSLATE) if __name__ == "__main__": - regen_messages() \ No newline at end of file + regen_messages() diff --git a/redbot/cogs/mod/mod.py b/redbot/cogs/mod/mod.py index 8e2155274..a0e94c692 100644 --- a/redbot/cogs/mod/mod.py +++ b/redbot/cogs/mod/mod.py @@ -9,8 +9,7 @@ from redbot.core.bot import Red from redbot.core.i18n import Translator, cog_i18n from redbot.core.utils.chat_formatting import box, escape from .checks import mod_or_voice_permissions, admin_or_voice_permissions, bot_has_voice_permissions -from redbot.core.utils.mod import is_mod_or_superior, is_allowed_by_hierarchy, \ - get_audit_reason +from redbot.core.utils.mod import is_mod_or_superior, is_allowed_by_hierarchy, get_audit_reason from .log import log _ = Translator("Mod", __file__) @@ -27,22 +26,14 @@ class Mod: "respect_hierarchy": True, "delete_delay": -1, "reinvite_on_unban": False, - "current_tempbans": [] + "current_tempbans": [], } - default_channel_settings = { - "ignored": False - } + default_channel_settings = {"ignored": False} - default_member_settings = { - "past_nicks": [], - "perms_cache": {}, - "banned_until": False - } + default_member_settings = {"past_nicks": [], "perms_cache": {}, "banned_until": False} - default_user_settings = { - "past_names": [] - } + default_user_settings = {"past_names": []} def __init__(self, bot: Red): self.bot = bot @@ -71,99 +62,99 @@ class Mod: "default_setting": True, "image": "\N{HAMMER}", "case_str": "Ban", - "audit_type": "ban" + "audit_type": "ban", }, { "name": "kick", "default_setting": True, "image": "\N{WOMANS BOOTS}", "case_str": "Kick", - "audit_type": "kick" + "audit_type": "kick", }, { "name": "hackban", "default_setting": True, "image": "\N{BUST IN SILHOUETTE}\N{HAMMER}", "case_str": "Hackban", - "audit_type": "ban" + "audit_type": "ban", }, { "name": "tempban", "default_setting": True, "image": "\N{ALARM CLOCK}\N{HAMMER}", "case_str": "Tempban", - "audit_type": "ban" + "audit_type": "ban", }, { "name": "softban", "default_setting": True, "image": "\N{DASH SYMBOL}\N{HAMMER}", "case_str": "Softban", - "audit_type": "ban" + "audit_type": "ban", }, { "name": "unban", "default_setting": True, "image": "\N{DOVE OF PEACE}", "case_str": "Unban", - "audit_type": "unban" + "audit_type": "unban", }, { "name": "voiceban", "default_setting": True, "image": "\N{SPEAKER WITH CANCELLATION STROKE}", "case_str": "Voice Ban", - "audit_type": "member_update" + "audit_type": "member_update", }, { "name": "voiceunban", "default_setting": True, "image": "\N{SPEAKER}", "case_str": "Voice Unban", - "audit_type": "member_update" + "audit_type": "member_update", }, { "name": "vmute", "default_setting": False, "image": "\N{SPEAKER WITH CANCELLATION STROKE}", "case_str": "Voice Mute", - "audit_type": "overwrite_update" + "audit_type": "overwrite_update", }, { "name": "cmute", "default_setting": False, "image": "\N{SPEAKER WITH CANCELLATION STROKE}", "case_str": "Channel Mute", - "audit_type": "overwrite_update" + "audit_type": "overwrite_update", }, { "name": "smute", "default_setting": True, "image": "\N{SPEAKER WITH CANCELLATION STROKE}", "case_str": "Server Mute", - "audit_type": "overwrite_update" + "audit_type": "overwrite_update", }, { "name": "vunmute", "default_setting": False, "image": "\N{SPEAKER}", "case_str": "Voice Unmute", - "audit_type": "overwrite_update" + "audit_type": "overwrite_update", }, { "name": "cunmute", "default_setting": False, "image": "\N{SPEAKER}", "case_str": "Channel Unmute", - "audit_type": "overwrite_update" + "audit_type": "overwrite_update", }, { "name": "sunmute", "default_setting": True, "image": "\N{SPEAKER}", "case_str": "Server Unmute", - "audit_type": "overwrite_update" - } + "audit_type": "overwrite_update", + }, ] try: await modlog.register_casetypes(casetypes_to_register) @@ -188,7 +179,9 @@ class Mod: msg = "" msg += "Delete repeats: {}\n".format("Yes" if delete_repeats else "No") msg += "Ban mention spam: {}\n".format( - "{} mentions".format(ban_mention_spam) if isinstance(ban_mention_spam, int) else "No" + "{} mentions".format(ban_mention_spam) + if isinstance(ban_mention_spam, int) + else "No" ) msg += "Respects hierarchy: {}\n".format("Yes" if respect_hierarchy else "No") msg += "Delete delay: {}\n".format( @@ -205,16 +198,18 @@ class Mod: toggled = await self.settings.guild(guild).respect_hierarchy() if not toggled: await self.settings.guild(guild).respect_hierarchy.set(True) - await ctx.send(_("Role hierarchy will be checked when " - "moderation commands are issued.")) + await ctx.send( + _("Role hierarchy will be checked when " "moderation commands are issued.") + ) else: await self.settings.guild(guild).respect_hierarchy.set(False) - await ctx.send(_("Role hierarchy will be ignored when " - "moderation commands are issued.")) + await ctx.send( + _("Role hierarchy will be ignored when " "moderation commands are issued.") + ) @modset.command() @commands.guild_only() - async def banmentionspam(self, ctx: commands.Context, max_mentions: int=False): + async def banmentionspam(self, ctx: commands.Context, max_mentions: int = False): """Enables auto ban for messages mentioning X different people Accepted values: 5 or superior""" @@ -224,10 +219,13 @@ class Mod: max_mentions = 5 await self.settings.guild(guild).ban_mention_spam.set(max_mentions) await ctx.send( - _("Autoban for mention spam enabled. " - "Anyone mentioning {} or more different people " - "in a single message will be autobanned.").format( - max_mentions) + _( + "Autoban for mention spam enabled. " + "Anyone mentioning {} or more different people " + "in a single message will be autobanned." + ).format( + max_mentions + ) ) else: cur_setting = await self.settings.guild(guild).ban_mention_spam() @@ -245,15 +243,14 @@ class Mod: cur_setting = await self.settings.guild(guild).delete_repeats() if not cur_setting: await self.settings.guild(guild).delete_repeats.set(True) - await ctx.send(_("Messages repeated up to 3 times will " - "be deleted.")) + await ctx.send(_("Messages repeated up to 3 times will " "be deleted.")) else: await self.settings.guild(guild).delete_repeats.set(False) await ctx.send(_("Repeated messages will be ignored.")) @modset.command() @commands.guild_only() - async def deletedelay(self, ctx: commands.Context, time: int=None): + async def deletedelay(self, ctx: commands.Context, time: int = None): """Sets the delay until the bot removes the command message. Must be between -1 and 60. @@ -266,15 +263,19 @@ class Mod: if time == -1: await ctx.send(_("Command deleting disabled.")) else: - await ctx.send( - _("Delete delay set to {} seconds.").format(time) - ) + await ctx.send(_("Delete delay set to {} seconds.").format(time)) else: delay = await self.settings.guild(guild).delete_delay() if delay != -1: - await ctx.send(_("Bot will delete command messages after" - " {} seconds. Set this value to -1 to" - " stop deleting messages").format(delay)) + await ctx.send( + _( + "Bot will delete command messages after" + " {} seconds. Set this value to -1 to" + " stop deleting messages" + ).format( + delay + ) + ) else: await ctx.send(_("I will not delete command messages.")) @@ -306,19 +307,23 @@ class Mod: guild = ctx.guild if author == user: - await ctx.send(_("I cannot let you do that. Self-harm is " - "bad {}").format("\N{PENSIVE FACE}")) + await ctx.send( + _("I cannot let you do that. Self-harm is " "bad {}").format("\N{PENSIVE FACE}") + ) return elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user): - await ctx.send(_("I cannot let you do that. You are " - "not higher than the user in the role " - "hierarchy.")) + await ctx.send( + _( + "I cannot let you do that. You are " + "not higher than the user in the role " + "hierarchy." + ) + ) return audit_reason = get_audit_reason(author, reason) try: await guild.kick(user, reason=audit_reason) - log.info("{}({}) kicked {}({})".format( - author.name, author.id, user.name, user.id)) + log.info("{}({}) kicked {}({})".format(author.name, author.id, user.name, user.id)) except discord.errors.Forbidden: await ctx.send(_("I'm not allowed to do that.")) except Exception as e: @@ -328,8 +333,15 @@ class Mod: try: await modlog.create_case( - self.bot, guild, ctx.message.created_at, "kick", - user, author, reason, until=None, channel=None + self.bot, + guild, + ctx.message.created_at, + "kick", + user, + author, + reason, + until=None, + channel=None, ) except RuntimeError as e: await ctx.send(e) @@ -337,7 +349,9 @@ class Mod: @commands.command() @commands.guild_only() @checks.admin_or_permissions(ban_members=True) - async def ban(self, ctx: commands.Context, user: discord.Member, days: str = None, *, reason: str = None): + async def ban( + self, ctx: commands.Context, user: discord.Member, days: str = None, *, reason: str = None + ): """Bans user and deletes last X days worth of messages. If days is not a number, it's treated as the first word of the reason. @@ -346,13 +360,18 @@ class Mod: guild = ctx.guild if author == user: - await ctx.send(_("I cannot let you do that. Self-harm is " - "bad {}").format("\N{PENSIVE FACE}")) + await ctx.send( + _("I cannot let you do that. Self-harm is " "bad {}").format("\N{PENSIVE FACE}") + ) return elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user): - await ctx.send(_("I cannot let you do that. You are " - "not higher than the user in the role " - "hierarchy.")) + await ctx.send( + _( + "I cannot let you do that. You are " + "not higher than the user in the role " + "hierarchy." + ) + ) return if days: @@ -376,8 +395,11 @@ class Mod: self.ban_queue.append(queue_entry) try: await guild.ban(user, reason=audit_reason, delete_message_days=days) - log.info("{}({}) banned {}({}), deleting {} days worth of messages".format( - author.name, author.id, user.name, user.id, str(days))) + log.info( + "{}({}) banned {}({}), deleting {} days worth of messages".format( + author.name, author.id, user.name, user.id, str(days) + ) + ) except discord.Forbidden: self.ban_queue.remove(queue_entry) await ctx.send(_("I'm not allowed to do that.")) @@ -389,8 +411,15 @@ class Mod: try: await modlog.create_case( - self.bot, guild, ctx.message.created_at, "ban", - user, author, reason, until=None, channel=None + self.bot, + guild, + ctx.message.created_at, + "ban", + user, + author, + reason, + until=None, + channel=None, ) except RuntimeError as e: await ctx.send(e) @@ -426,24 +455,28 @@ class Mod: self.ban_queue.append(queue_entry) try: await guild.ban(user, reason=audit_reason) - log.info("{}({}) hackbanned {}" - "".format(author.name, author.id, user_id)) + log.info("{}({}) hackbanned {}" "".format(author.name, author.id, user_id)) except discord.NotFound: self.ban_queue.remove(queue_entry) - await ctx.send(_("User not found. Have you provided the " - "correct user ID?")) + await ctx.send(_("User not found. Have you provided the " "correct user ID?")) except discord.Forbidden: self.ban_queue.remove(queue_entry) await ctx.send(_("I lack the permissions to do this.")) else: - await ctx.send(_("Done. The user will not be able to join this " - "server.")) + await ctx.send(_("Done. The user will not be able to join this " "server.")) user_info = await self.bot.get_user_info(user_id) try: await modlog.create_case( - self.bot, guild, ctx.message.created_at, "hackban", - user_info, author, reason, until=None, channel=None + self.bot, + guild, + ctx.message.created_at, + "hackban", + user_info, + author, + reason, + until=None, + channel=None, ) except RuntimeError as e: await ctx.send(e) @@ -451,7 +484,9 @@ class Mod: @commands.command() @commands.guild_only() @checks.admin_or_permissions(ban_members=True) - async def tempban(self, ctx: commands.Context, user: discord.Member, days: int=1, *, reason: str=None): + async def tempban( + self, ctx: commands.Context, user: discord.Member, days: int = 1, *, reason: str = None + ): """Tempbans the user for the specified number of days""" guild = ctx.guild author = ctx.author @@ -473,9 +508,13 @@ class Mod: try: # We don't want blocked DMs preventing us from banning msg = await user.send( - _("You have been temporarily banned from {} until {}. " - "Here is an invite for when your ban expires: {}").format( - guild.name, unban_time.strftime("%m-%d-%Y %H:%M:%S"), invite)) + _( + "You have been temporarily banned from {} until {}. " + "Here is an invite for when your ban expires: {}" + ).format( + guild.name, unban_time.strftime("%m-%d-%Y %H:%M:%S"), invite + ) + ) except discord.HTTPException: msg = None self.ban_queue.append(queue_entry) @@ -490,8 +529,14 @@ class Mod: try: await modlog.create_case( - self.bot, guild, ctx.message.created_at, "tempban", - user, author, reason, unban_time + self.bot, + guild, + ctx.message.created_at, + "tempban", + user, + author, + reason, + unban_time, ) except RuntimeError as e: await ctx.send(e) @@ -507,13 +552,18 @@ class Mod: author = ctx.author if author == user: - await ctx.send(_("I cannot let you do that. Self-harm is " - "bad {}").format("\N{PENSIVE FACE}")) + await ctx.send( + _("I cannot let you do that. Self-harm is " "bad {}").format("\N{PENSIVE FACE}") + ) return elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user): - await ctx.send(_("I cannot let you do that. You are " - "not higher than the user in the role " - "hierarchy.")) + await ctx.send( + _( + "I cannot let you do that. You are " + "not higher than the user in the role " + "hierarchy." + ) + ) return audit_reason = get_audit_reason(author, reason) @@ -526,19 +576,22 @@ class Mod: queue_entry = (guild.id, user.id) try: # We don't want blocked DMs preventing us from banning msg = await user.send( - _("You have been banned and " - "then unbanned as a quick way to delete your messages.\n" - "You can now join the server again. {}").format(invite)) + _( + "You have been banned and " + "then unbanned as a quick way to delete your messages.\n" + "You can now join the server again. {}" + ).format( + invite + ) + ) except discord.HTTPException: msg = None self.ban_queue.append(queue_entry) try: - await guild.ban( - user, reason=audit_reason, delete_message_days=1) + await guild.ban(user, reason=audit_reason, delete_message_days=1) except discord.errors.Forbidden: self.ban_queue.remove(queue_entry) - await ctx.send( - _("My role is not high enough to softban that user.")) + await ctx.send(_("My role is not high enough to softban that user.")) if msg is not None: await msg.delete() return @@ -555,9 +608,10 @@ class Mod: return else: await ctx.send(_("Done. Enough chaos.")) - log.info("{}({}) softbanned {}({}), deleting 1 day worth " - "of messages".format(author.name, author.id, - user.name, user.id)) + log.info( + "{}({}) softbanned {}({}), deleting 1 day worth " + "of messages".format(author.name, author.id, user.name, user.id) + ) try: await modlog.create_case( self.bot, @@ -568,7 +622,8 @@ class Mod: author, reason, until=None, - channel=None) + channel=None, + ) except RuntimeError as e: await ctx.send(e) else: @@ -610,8 +665,15 @@ class Mod: try: await modlog.create_case( - self.bot, guild, ctx.message.created_at, "unban", - user, author, reason, until=None, channel=None + self.bot, + guild, + ctx.message.created_at, + "unban", + user, + author, + reason, + until=None, + channel=None, ) except RuntimeError as e: await ctx.send(e) @@ -621,22 +683,35 @@ class Mod: if invite: try: user.send( - _("You've been unbanned from {}.\n" - "Here is an invite for that server: {}").format(guild.name, invite.url)) + _( + "You've been unbanned from {}.\n" + "Here is an invite for that server: {}" + ).format( + guild.name, invite.url + ) + ) except discord.Forbidden: await ctx.send( - _("I failed to send an invite to that user. " - "Perhaps you may be able to send it for me?\n" - "Here's the invite link: {}").format(invite.url) + _( + "I failed to send an invite to that user. " + "Perhaps you may be able to send it for me?\n" + "Here's the invite link: {}" + ).format( + invite.url + ) ) except discord.HTTPException: await ctx.send( - _("Something went wrong when attempting to send that user" - "an invite. Here's the link so you can try: {}") - .format(invite.url)) + _( + "Something went wrong when attempting to send that user" + "an invite. Here's the link so you can try: {}" + ).format( + invite.url + ) + ) @staticmethod - async def get_invite_for_reinvite(ctx: commands.Context, max_age: int=86400): + async def get_invite_for_reinvite(ctx: commands.Context, max_age: int = 86400): """Handles the reinvite logic for getting an invite to send the newly unbanned user :returns: :class:`Invite`""" @@ -654,11 +729,12 @@ class Mod: return inv else: # No existing invite found that is valid channels_and_perms = zip( - guild.text_channels, - map(guild.me.permissions_in, guild.text_channels)) - channel = next(( - channel for channel, perms in channels_and_perms - if perms.create_instant_invite), None) + guild.text_channels, map(guild.me.permissions_in, guild.text_channels) + ) + channel = next( + (channel for channel, perms in channels_and_perms if perms.create_instant_invite), + None, + ) if channel is None: return try: @@ -671,7 +747,7 @@ class Mod: @commands.guild_only() @admin_or_voice_permissions(mute_members=True, deafen_members=True) @bot_has_voice_permissions(mute_members=True, deafen_members=True) - async def voiceban(self, ctx: commands.Context, user: discord.Member, *, reason: str=None): + async def voiceban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None): """Bans the target user from speaking and listening in voice channels in the server""" user_voice_state = user.voice if user_voice_state is None: @@ -691,15 +767,19 @@ class Mod: else: await ctx.send(_("That user is already muted and deafened server-wide!")) return - await ctx.send( - _("User has been banned from speaking or " - "listening in voice channels") - ) + await ctx.send(_("User has been banned from speaking or " "listening in voice channels")) try: await modlog.create_case( - self.bot, guild, ctx.message.created_at, "voiceban", - user, author, reason, until=None, channel=None + self.bot, + guild, + ctx.message.created_at, + "voiceban", + user, + author, + reason, + until=None, + channel=None, ) except RuntimeError as e: await ctx.send(e) @@ -708,7 +788,7 @@ class Mod: @commands.guild_only() @admin_or_voice_permissions(mute_members=True, deafen_members=True) @bot_has_voice_permissions(mute_members=True, deafen_members=True) - async def voiceunban(self, ctx: commands.Context, user: discord.Member, *, reason: str=None): + async def voiceunban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None): """Unbans the user from speaking/listening in the server's voice channels""" user_voice_state = user.voice if user_voice_state is None: @@ -726,15 +806,20 @@ class Mod: else: await ctx.send(_("That user isn't muted or deafened by the server!")) return - await ctx.send( - _("User is now allowed to speak and listen in voice channels") - ) + await ctx.send(_("User is now allowed to speak and listen in voice channels")) guild = ctx.guild author = ctx.author try: await modlog.create_case( - self.bot, guild, ctx.message.created_at, "voiceunban", - user, author, reason, until=None, channel=None + self.bot, + guild, + ctx.message.created_at, + "voiceunban", + user, + author, + reason, + until=None, + channel=None, ) except RuntimeError as e: await ctx.send(e) @@ -750,14 +835,12 @@ class Mod: if nickname == "": nickname = None try: - await user.edit( - reason=get_audit_reason(ctx.author, None), - nick=nickname - ) + await user.edit(reason=get_audit_reason(ctx.author, None), nick=nickname) await ctx.send("Done.") except discord.Forbidden: - await ctx.send(_("I cannot do that, I lack the " - "'{}' permission.").format("Manage Nicknames")) + await ctx.send( + _("I cannot do that, I lack the " "'{}' permission.").format("Manage Nicknames") + ) @commands.group() @commands.guild_only() @@ -771,8 +854,7 @@ class Mod: @commands.guild_only() @mod_or_voice_permissions(mute_members=True) @bot_has_voice_permissions(mute_members=True) - async def voice_mute(self, ctx: commands.Context, user: discord.Member, - *, reason: str = None): + async def voice_mute(self, ctx: commands.Context, user: discord.Member, *, reason: str = None): """Mutes the user in a voice channel""" user_voice_state = user.voice guild = ctx.guild @@ -786,14 +868,20 @@ class Mod: await channel.set_permissions(user, overwrite=overwrites, reason=audit_reason) await ctx.send( _("Muted {}#{} in channel {}").format( - user.name, user.discriminator, - channel.name + user.name, user.discriminator, channel.name ) ) try: await modlog.create_case( - self.bot, guild, ctx.message.created_at, "boicemute", - user, author, reason, until=None, channel=channel + self.bot, + guild, + ctx.message.created_at, + "boicemute", + user, + author, + reason, + until=None, + channel=channel, ) except RuntimeError as e: await ctx.send(e) @@ -810,7 +898,9 @@ class Mod: @checks.mod_or_permissions(administrator=True) @mute.command(name="channel") @commands.guild_only() - async def channel_mute(self, ctx: commands.Context, user: discord.Member, *, reason: str = None): + async def channel_mute( + self, ctx: commands.Context, user: discord.Member, *, reason: str = None + ): """Mutes user in the current channel""" author = ctx.message.author channel = ctx.message.channel @@ -819,7 +909,9 @@ class Mod: if reason is None: audit_reason = "Channel mute requested by {} (ID {})".format(author, author.id) else: - audit_reason = "Channel mute requested by {} (ID {}). Reason: {}".format(author, author.id, reason) + audit_reason = "Channel mute requested by {} (ID {}). Reason: {}".format( + author, author.id, reason + ) success, issue = await self.mute_user(guild, channel, author, user, audit_reason) @@ -827,8 +919,15 @@ class Mod: await channel.send(_("User has been muted in this channel.")) try: await modlog.create_case( - self.bot, guild, ctx.message.created_at, "cmute", - user, author, reason, until=None, channel=channel + self.bot, + guild, + ctx.message.created_at, + "cmute", + user, + author, + reason, + until=None, + channel=channel, ) except RuntimeError as e: await ctx.send(e) @@ -846,7 +945,9 @@ class Mod: if reason is None: audit_reason = "server mute requested by {} (ID {})".format(author, author.id) else: - audit_reason = "server mute requested by {} (ID {}). Reason: {}".format(author, author.id, reason) + audit_reason = "server mute requested by {} (ID {}). Reason: {}".format( + author, author.id, reason + ) mute_success = [] for channel in guild.channels: @@ -863,16 +964,27 @@ class Mod: await ctx.send(_("User has been muted in this server.")) try: await modlog.create_case( - self.bot, guild, ctx.message.created_at, "smute", - user, author, reason, until=None, channel=None + self.bot, + guild, + ctx.message.created_at, + "smute", + user, + author, + reason, + until=None, + channel=None, ) except RuntimeError as e: await ctx.send(e) - async def mute_user(self, guild: discord.Guild, - channel: discord.TextChannel, - author: discord.Member, - user: discord.Member, reason: str) -> (bool, str): + async def mute_user( + self, + guild: discord.Guild, + channel: discord.TextChannel, + author: discord.Member, + user: discord.Member, + reason: str, + ) -> (bool, str): """Mutes the specified user in the specified channel""" overwrites = channel.overwrites_for(user) permissions = channel.permissions_for(user) @@ -885,11 +997,9 @@ class Mod: return False, mute_unmute_issues["hierarchy_problem"] perms_cache[str(channel.id)] = { - "send_messages": overwrites.send_messages, - "add_reactions": overwrites.add_reactions + "send_messages": overwrites.send_messages, "add_reactions": overwrites.add_reactions } - overwrites.update(send_messages=False, - add_reactions=False) + overwrites.update(send_messages=False, add_reactions=False) try: await channel.set_permissions(user, overwrite=overwrites, reason=reason) except discord.Forbidden: @@ -912,7 +1022,9 @@ class Mod: @commands.guild_only() @mod_or_voice_permissions(mute_members=True) @bot_has_voice_permissions(mute_members=True) - async def voice_unmute(self, ctx: commands.Context, user: discord.Member, *, reason: str = None): + async def voice_unmute( + self, ctx: commands.Context, user: discord.Member, *, reason: str = None + ): """Unmutes the user in a voice channel""" user_voice_state = user.voice if user_voice_state: @@ -926,11 +1038,20 @@ class Mod: guild = ctx.guild await ctx.send( _("Unmuted {}#{} in channel {}").format( - user.name, user.discriminator, channel.name)) + user.name, user.discriminator, channel.name + ) + ) try: await modlog.create_case( - self.bot, guild, ctx.message.created_at, "voiceunmute", - user, author, reason, until=None, channel=channel + self.bot, + guild, + ctx.message.created_at, + "voiceunmute", + user, + author, + reason, + until=None, + channel=channel, ) except RuntimeError as e: await ctx.send(e) @@ -946,7 +1067,9 @@ class Mod: @checks.mod_or_permissions(administrator=True) @unmute.command(name="channel") @commands.guild_only() - async def channel_unmute(self, ctx: commands.Context, user: discord.Member, *, reason: str=None): + async def channel_unmute( + self, ctx: commands.Context, user: discord.Member, *, reason: str = None + ): """Unmutes user in the current channel""" channel = ctx.channel author = ctx.author @@ -958,8 +1081,15 @@ class Mod: await ctx.send(_("User unmuted in this channel.")) try: await modlog.create_case( - self.bot, guild, ctx.message.created_at, "cunmute", - user, author, reason, until=None, channel=channel + self.bot, + guild, + ctx.message.created_at, + "cunmute", + user, + author, + reason, + until=None, + channel=channel, ) except RuntimeError as e: await ctx.send(e) @@ -969,7 +1099,9 @@ class Mod: @checks.mod_or_permissions(administrator=True) @unmute.command(name="server", aliases=["guild"]) @commands.guild_only() - async def guild_unmute(self, ctx: commands.Context, user: discord.Member, *, reason: str=None): + async def guild_unmute( + self, ctx: commands.Context, user: discord.Member, *, reason: str = None + ): """Unmutes user in the server""" guild = ctx.guild author = ctx.author @@ -982,24 +1114,33 @@ class Mod: overwrites = channel.overwrites_for(user) overwrites.speak = None audit_reason = get_audit_reason(author, reason) - await channel.set_permissions( - user, overwrite=overwrites, reason=audit_reason) + await channel.set_permissions(user, overwrite=overwrites, reason=audit_reason) success, message = await self.unmute_user(guild, channel, author, user) unmute_success.append((success, message)) await asyncio.sleep(0.1) await ctx.send(_("User has been unmuted in this server.")) try: await modlog.create_case( - self.bot, guild, ctx.message.created_at, "sunmute", - user, author, reason, until=None, channel=channel + self.bot, + guild, + ctx.message.created_at, + "sunmute", + user, + author, + reason, + until=None, + channel=channel, ) except RuntimeError as e: await ctx.send(e) - async def unmute_user(self, guild: discord.Guild, - channel: discord.TextChannel, - author: discord.Member, - user: discord.Member) -> (bool, str): + async def unmute_user( + self, + guild: discord.Guild, + channel: discord.TextChannel, + author: discord.Member, + user: discord.Member, + ) -> (bool, str): overwrites = channel.overwrites_for(user) permissions = channel.permissions_for(user) perms_cache = await self.settings.member(user).perms_cache() @@ -1014,8 +1155,9 @@ class Mod: old_values = perms_cache[channel.id] else: old_values = {"send_messages": None, "add_reactions": None} - overwrites.update(send_messages=old_values["send_messages"], - add_reactions=old_values["add_reactions"]) + overwrites.update( + send_messages=old_values["send_messages"], add_reactions=old_values["add_reactions"] + ) is_empty = self.are_overwrites_empty(overwrites) try: @@ -1044,7 +1186,7 @@ class Mod: await ctx.send(await self.count_ignored()) @ignore.command(name="channel") - async def ignore_channel(self, ctx: commands.Context, channel: discord.TextChannel=None): + async def ignore_channel(self, ctx: commands.Context, channel: discord.TextChannel = None): """Ignores channel Defaults to current one""" @@ -1077,7 +1219,7 @@ class Mod: await ctx.send(await self.count_ignored()) @unignore.command(name="channel") - async def unignore_channel(self, ctx: commands.Context, channel: discord.TextChannel=None): + async def unignore_channel(self, ctx: commands.Context, channel: discord.TextChannel = None): """Removes channel from ignore list Defaults to current one""" @@ -1120,16 +1262,17 @@ class Mod: Any users who have permission to use the `ignore` or `unignore` commands surpass the check.""" perms = ctx.channel.permissions_for(ctx.author) - surpass_ignore = (isinstance(ctx.channel, discord.abc.PrivateChannel) or - perms.manage_guild or - await ctx.bot.is_owner(ctx.author) or - await ctx.bot.is_admin(ctx.author)) + surpass_ignore = ( + isinstance(ctx.channel, discord.abc.PrivateChannel) + or perms.manage_guild + or await ctx.bot.is_owner(ctx.author) + or await ctx.bot.is_admin(ctx.author) + ) if surpass_ignore: return True guild_ignored = await self.settings.guild(ctx.guild).ignored() chann_ignored = await self.settings.channel(ctx.channel).ignored() - return not (guild_ignored or - chann_ignored and not perms.manage_channels) + return not (guild_ignored or chann_ignored and not perms.manage_channels) @commands.command() async def names(self, ctx: commands.Context, user: discord.Member): @@ -1152,8 +1295,7 @@ class Mod: if msg: await ctx.send(msg) else: - await ctx.send(_("That user doesn't have any recorded name or " - "nickname change.")) + await ctx.send(_("That user doesn't have any recorded name or " "nickname change.")) async def check_tempban_expirations(self): member = namedtuple("Member", "id guild") @@ -1162,9 +1304,7 @@ class Mod: guild_tempbans = await self.settings.guild(guild).current_tempbans() for uid in guild_tempbans: unban_time = datetime.utcfromtimestamp( - await self.settings.member( - member(uid, guild) - ).banned_until() + await self.settings.member(member(uid, guild)).banned_until() ) now = datetime.utcnow() if now > unban_time: # Time to unban the user @@ -1189,8 +1329,7 @@ class Mod: return False self.cache[author].append(message) msgs = self.cache[author] - if len(msgs) == 3 and \ - msgs[0].content == msgs[1].content == msgs[2].content: + if len(msgs) == 3 and msgs[0].content == msgs[1].content == msgs[2].content: try: await message.delete() return True @@ -1209,13 +1348,21 @@ class Mod: try: await guild.ban(author, reason="Mention spam (Autoban)") except discord.HTTPException: - log.info("Failed to ban member for mention spam in " - "server {}.".format(guild.id)) + log.info( + "Failed to ban member for mention spam in " "server {}.".format(guild.id) + ) else: try: case = await modlog.create_case( - self.bot, guild, message.created_at, "ban", author, - guild.me, "Mention spam (Autoban)", until=None, channel=None + self.bot, + guild, + message.created_at, + "ban", + author, + guild.me, + "Mention spam (Autoban)", + until=None, + channel=None, ) except RuntimeError as e: print(e) @@ -1270,13 +1417,14 @@ class Mod: except RuntimeError: return # No modlog channel so no point in continuing mod, reason, date = await self.get_audit_entry_info( - guild, discord.AuditLogAction.ban, member) + guild, discord.AuditLogAction.ban, member + ) if date is None: date = datetime.now() try: - await modlog.create_case(self.bot, guild, date, - "ban", member, mod, - reason if reason else None) + await modlog.create_case( + self.bot, guild, date, "ban", member, mod, reason if reason else None + ) except RuntimeError as e: print(e) @@ -1289,19 +1437,16 @@ class Mod: except RuntimeError: return # No modlog channel so no point in continuing mod, reason, date = await self.get_audit_entry_info( - guild, discord.AuditLogAction.unban, user) + guild, discord.AuditLogAction.unban, user + ) if date is None: date = datetime.now() try: - await modlog.create_case(self.bot, guild, date, "unban", - user, mod, reason) + await modlog.create_case(self.bot, guild, date, "unban", user, mod, reason) except RuntimeError as e: print(e) - async def get_audit_entry_info(self, - guild: discord.Guild, - action: int, - target): + async def get_audit_entry_info(self, guild: discord.Guild, action: int, target): """Get info about an audit log entry. Parameters @@ -1321,18 +1466,14 @@ class Mod: if the audit log entry could not be found. """ try: - entry = await self.get_audit_log_entry( - guild, action=action, target=target) + entry = await self.get_audit_log_entry(guild, action=action, target=target) except discord.HTTPException: entry = None if entry is None: return None, None, None return entry.user, entry.reason, entry.created_at - async def get_audit_log_entry(self, - guild: discord.Guild, - action: int, - target): + async def get_audit_log_entry(self, guild: discord.Guild, action: int, target): """Get an audit log entry. Any exceptions encountered when looking through the audit log will be @@ -1379,16 +1520,15 @@ class Mod: def are_overwrites_empty(overwrites): """There is currently no cleaner way to check if a PermissionOverwrite object is empty""" - return [p for p in iter(overwrites)] ==\ - [p for p in iter(discord.PermissionOverwrite())] + return [p for p in iter(overwrites)] == [p for p in iter(discord.PermissionOverwrite())] mute_unmute_issues = { "already_muted": "That user can't send messages in this channel.", "already_unmuted": "That user isn't muted in this channel!", "hierarchy_problem": "I cannot let you do that. You are not higher than " - "the user in the role hierarchy.", + "the user in the role hierarchy.", "permissions_issue": "Failed to mute user. I need the manage roles " - "permission and the user I'm muting must be " - "lower than myself in the role hierarchy." + "permission and the user I'm muting must be " + "lower than myself in the role hierarchy.", } diff --git a/redbot/cogs/modlog/locales/regen_messages.py b/redbot/cogs/modlog/locales/regen_messages.py index e6f07bb39..278749d63 100644 --- a/redbot/cogs/modlog/locales/regen_messages.py +++ b/redbot/cogs/modlog/locales/regen_messages.py @@ -1,15 +1,11 @@ import subprocess -TO_TRANSLATE = [ - '../modlog.py' -] +TO_TRANSLATE = ["../modlog.py"] def regen_messages(): - subprocess.run( - ['pygettext', '-n'] + TO_TRANSLATE - ) + subprocess.run(["pygettext", "-n"] + TO_TRANSLATE) if __name__ == "__main__": - regen_messages() \ No newline at end of file + regen_messages() diff --git a/redbot/cogs/modlog/modlog.py b/redbot/cogs/modlog/modlog.py index 970f187ff..200647ac8 100644 --- a/redbot/cogs/modlog/modlog.py +++ b/redbot/cogs/modlog/modlog.py @@ -5,7 +5,7 @@ from redbot.core.bot import Red from redbot.core.i18n import Translator, cog_i18n from redbot.core.utils.chat_formatting import box -_ = Translator('ModLog', __file__) +_ = Translator("ModLog", __file__) @cog_i18n(_) @@ -32,15 +32,12 @@ class ModLog: if channel: if channel.permissions_for(guild.me).send_messages: await modlog.set_modlog_channel(guild, channel) - await ctx.send( - _("Mod events will be sent to {}").format( - channel.mention - ) - ) + await ctx.send(_("Mod events will be sent to {}").format(channel.mention)) else: await ctx.send( - _("I do not have permissions to " - "send messages in {}!").format(channel.mention) + _("I do not have permissions to " "send messages in {}!").format( + channel.mention + ) ) else: try: @@ -51,7 +48,7 @@ class ModLog: await modlog.set_modlog_channel(guild, None) await ctx.send(_("Mod log deactivated.")) - @modlogset.command(name='cases') + @modlogset.command(name="cases") @commands.guild_only() async def set_cases(self, ctx: commands.Context, action: str = None): """Enables or disables case creation for each type of mod action""" @@ -64,8 +61,8 @@ class ModLog: msg = "" for ct in casetypes: enabled = await ct.is_enabled() - value = 'enabled' if enabled else 'disabled' - msg += '%s : %s\n' % (ct.name, value) + value = "enabled" if enabled else "disabled" + msg += "%s : %s\n" % (ct.name, value) msg = title + "\n" + box(msg) await ctx.send(msg) @@ -79,8 +76,8 @@ class ModLog: await casetype.set_enabled(True if not enabled else False) msg = ( - _('Case creation for {} actions is now {}.').format( - action, 'enabled' if not enabled else 'disabled' + _("Case creation for {} actions is now {}.").format( + action, "enabled" if not enabled else "disabled" ) ) await ctx.send(msg) @@ -133,8 +130,10 @@ class ModLog: if audit_type: audit_case = None async for entry in guild.audit_logs(action=audit_type): - if entry.target.id == case_before.user.id and \ - entry.action == audit_type: + if ( + entry.target.id == case_before.user.id + and entry.action == audit_type + ): audit_case = entry break if audit_case: @@ -145,9 +144,7 @@ class ModLog: if not (is_guild_owner or is_case_author or author_is_mod): await ctx.send(_("You are not authorized to modify that case!")) return - to_modify = { - "reason": reason, - } + to_modify = {"reason": reason} if case_before.moderator != author: to_modify["amended_by"] = author to_modify["modified_at"] = ctx.message.created_at.timestamp() diff --git a/redbot/cogs/reports/reports.py b/redbot/cogs/reports/reports.py index d9f4fd290..6efd4039a 100644 --- a/redbot/cogs/reports/reports.py +++ b/redbot/cogs/reports/reports.py @@ -22,15 +22,9 @@ log = logging.getLogger("red.reports") @cog_i18n(_) class Reports: - default_guild_settings = { - "output_channel": None, - "active": False, - "next_ticket": 1 - } + default_guild_settings = {"output_channel": None, "active": False, "next_ticket": 1} - default_report = { - 'report': {} - } + default_report = {"report": {}} # This can be made configureable later if it # becomes an issue. @@ -42,15 +36,14 @@ class Reports: (timedelta(seconds=5), 1), (timedelta(minutes=5), 3), (timedelta(hours=1), 10), - (timedelta(days=1), 24) + (timedelta(days=1), 24), ] def __init__(self, bot: Red): self.bot = bot - self.config = Config.get_conf( - self, 78631113035100160, force_registration=True) + self.config = Config.get_conf(self, 78631113035100160, force_registration=True) self.config.register_guild(**self.default_guild_settings) - self.config.register_custom('REPORT', **self.default_report) + self.config.register_custom("REPORT", **self.default_report) self.antispam = {} self.user_cache = [] self.tunnel_store = {} @@ -59,9 +52,7 @@ class Reports: @property def tunnels(self): - return [ - x['tun'] for x in self.tunnel_store.values() - ] + return [x["tun"] for x in self.tunnel_store.values()] @checks.admin_or_permissions(manage_guild=True) @commands.guild_only() @@ -99,9 +90,7 @@ class Reports: admin_role = discord.utils.get( guild.roles, id=await self.bot.db.guild(guild).admin_role() ) - mod_role = discord.utils.get( - guild.roles, id=await self.bot.db.guild(guild).mod_role() - ) + mod_role = discord.utils.get(guild.roles, id=await self.bot.db.guild(guild).mod_role()) ret |= any(r in m.roles for r in (mod_role, admin_role)) if perms: ret |= m.guild_permissions >= perms @@ -111,10 +100,13 @@ class Reports: return ret async def discover_guild( - self, author: discord.User, *, - mod: bool=False, - permissions: Union[discord.Permissions, dict]=None, - prompt: str=""): + self, + author: discord.User, + *, + mod: bool = False, + permissions: Union[discord.Permissions, dict] = None, + prompt: str = "" + ): """ discovers which of shared guilds between the bot and provided user based on conditions (mod or permissions is an or) @@ -151,13 +143,9 @@ class Reports: return m.author == author and m.channel == dm.channel try: - message = await self.bot.wait_for( - 'message', check=pred, timeout=45 - ) + message = await self.bot.wait_for("message", check=pred, timeout=45) except asyncio.TimeoutError: - await author.send( - _("You took too long to select. Try again later.") - ) + await author.send(_("You took too long to select. Try again later.")) return None try: @@ -187,35 +175,31 @@ class Reports: if await self.bot.embed_requested(channel, author): em = discord.Embed(description=report) em.set_author( - name=_('Report from {0.display_name}').format(author), - icon_url=author.avatar_url + name=_("Report from {0.display_name}").format(author), icon_url=author.avatar_url ) em.set_footer(text=_("Report #{}").format(ticket_number)) send_content = None else: em = None - send_content = _( - 'Report from {author.mention} (Ticket #{number})' - ).format(author=author, number=ticket_number) + send_content = _("Report from {author.mention} (Ticket #{number})").format( + author=author, number=ticket_number + ) send_content += "\n" + report try: await Tunnel.message_forwarder( - destination=channel, - content=send_content, - embed=em, - files=files + destination=channel, content=send_content, embed=em, files=files ) except (discord.Forbidden, discord.HTTPException): return None - await self.config.custom('REPORT', guild.id, ticket_number).report.set( - {'user_id': author.id, 'report': report} + await self.config.custom("REPORT", guild.id, ticket_number).report.set( + {"user_id": author.id, "report": report} ) return ticket_number @commands.group(name="report", invoke_without_command=True) - async def report(self, ctx: commands.Context, *, _report: str=""): + async def report(self, ctx: commands.Context, *, _report: str = ""): """ Follow the prompts to make a report @@ -226,8 +210,7 @@ class Reports: guild = ctx.guild if guild is None: guild = await self.discover_guild( - author, - prompt=_("Select a server to make a report in by number.") + author, prompt=_("Select a server to make a report in by number.") ) else: try: @@ -238,24 +221,23 @@ class Reports: return g_active = await self.config.guild(guild).active() if not g_active: - return await author.send( - _("Reporting has not been enabled for this server") - ) + return await author.send(_("Reporting has not been enabled for this server")) if guild.id not in self.antispam: self.antispam[guild.id] = {} if author.id not in self.antispam[guild.id]: self.antispam[guild.id][author.id] = AntiSpam(self.intervals) if self.antispam[guild.id][author.id].spammy: return await author.send( - _("You've sent a few too many of these recently. " - "Contact a server admin to resolve this, or try again " - "later.") + _( + "You've sent a few too many of these recently. " + "Contact a server admin to resolve this, or try again " + "later." + ) ) if author.id in self.user_cache: return await author.send( - _("Finish making your prior report " - "before making an additional one") + _("Finish making your prior report " "before making an additional one") ) if ctx.guild: @@ -273,13 +255,13 @@ class Reports: else: try: dm = await author.send( - _("Please respond to this message with your Report." - "\nYour report should be a single message") + _( + "Please respond to this message with your Report." + "\nYour report should be a single message" + ) ) except discord.Forbidden: - await ctx.send( - _("This requires DMs enabled.") - ) + await ctx.send(_("This requires DMs enabled.")) self.user_cache.remove(author.id) return @@ -287,25 +269,17 @@ class Reports: return m.author == author and m.channel == dm.channel try: - message = await self.bot.wait_for( - 'message', check=pred, timeout=180 - ) + message = await self.bot.wait_for("message", check=pred, timeout=180) except asyncio.TimeoutError: - await author.send( - _("You took too long. Try again later.") - ) + await author.send(_("You took too long. Try again later.")) else: val = await self.send_report(message, guild) with contextlib.suppress(discord.Forbidden, discord.HTTPException): if val is None: - await author.send( - _("There was an error sending your report.") - ) + await author.send(_("There was an error sending your report.")) else: - await author.send( - _("Your report was submitted. (Ticket #{})").format(val) - ) + await author.send(_("Your report was submitted. (Ticket #{})").format(val)) self.antispam[guild.id][author.id].stamp() self.user_cache.remove(author.id) @@ -318,18 +292,14 @@ class Reports: return _id = payload.message_id - t = next(filter( - lambda x: _id in x[1]['msgs'], - self.tunnel_store.items() - ), None) + t = next(filter(lambda x: _id in x[1]["msgs"], self.tunnel_store.items()), None) if t is None: return - tun = t[1]['tun'] + tun = t[1]["tun"] if payload.user_id in [x.id for x in tun.members]: await tun.react_close( - uid=payload.user_id, - message=_("{closer} has closed the correspondence") + uid=payload.user_id, message=_("{closer} has closed the correspondence") ) self.tunnel_store.pop(t[0], None) @@ -337,12 +307,12 @@ class Reports: for k, v in self.tunnel_store.items(): topic = _("Re: ticket# {1} in {0.name}").format(*k) # Tunnels won't forward unintended messages, this is safe - msgs = await v['tun'].communicate(message=message, topic=topic) + msgs = await v["tun"].communicate(message=message, topic=topic) if msgs: - self.tunnel_store[k]['msgs'] = msgs + self.tunnel_store[k]["msgs"] = msgs @checks.mod_or_permissions(manage_members=True) - @report.command(name='interact') + @report.command(name="interact") async def response(self, ctx, ticket_number: int): """ opens a message tunnel between things you say in this channel @@ -353,27 +323,24 @@ class Reports: # note, mod_or_permissions is an implicit guild_only guild = ctx.guild - rec = await self.config.custom( - 'REPORT', guild.id, ticket_number).report() + rec = await self.config.custom("REPORT", guild.id, ticket_number).report() try: - user = guild.get_member(rec.get('user_id')) + user = guild.get_member(rec.get("user_id")) except KeyError: - return await ctx.send( - _("That ticket doesn't seem to exist") - ) + return await ctx.send(_("That ticket doesn't seem to exist")) if user is None: - return await ctx.send( - _("That user isn't here anymore.") - ) + return await ctx.send(_("That user isn't here anymore.")) tun = Tunnel(recipient=user, origin=ctx.channel, sender=ctx.author) if tun is None: return await ctx.send( - _("Either you or the user you are trying to reach already " - "has an open communication.") + _( + "Either you or the user you are trying to reach already " + "has an open communication." + ) ) big_topic = _( @@ -387,18 +354,13 @@ class Reports: "\nTunnels are not persistent across bot restarts." ) topic = big_topic.format( - ticketnum=ticket_number, - who=_("A moderator in `{guild.name}` has").format(guild=guild) + ticketnum=ticket_number, who=_("A moderator in `{guild.name}` has").format(guild=guild) ) try: - m = await tun.communicate( - message=ctx.message, topic=topic, skip_message_content=True - ) + m = await tun.communicate(message=ctx.message, topic=topic, skip_message_content=True) except discord.Forbidden: await ctx.send(_("User has disabled DMs.")) tun.close() else: - self.tunnel_store[(guild, ticket_number)] = {'tun': tun, 'msgs': m} - await ctx.send( - big_topic.format(who=_("You have"), ticketnum=ticket_number) - ) + self.tunnel_store[(guild, ticket_number)] = {"tun": tun, "msgs": m} + await ctx.send(big_topic.format(who=_("You have"), ticketnum=ticket_number)) diff --git a/redbot/cogs/streams/errors.py b/redbot/cogs/streams/errors.py index 6159a7ed0..81d66bf97 100644 --- a/redbot/cogs/streams/errors.py +++ b/redbot/cogs/streams/errors.py @@ -27,4 +27,4 @@ class OfflineStream(StreamsError): class OfflineCommunity(StreamsError): - pass \ No newline at end of file + pass diff --git a/redbot/cogs/streams/locales/regen_messages.py b/redbot/cogs/streams/locales/regen_messages.py index e04dd4843..cd12e1867 100644 --- a/redbot/cogs/streams/locales/regen_messages.py +++ b/redbot/cogs/streams/locales/regen_messages.py @@ -1,15 +1,11 @@ import subprocess -TO_TRANSLATE = [ - '../mod.py' -] +TO_TRANSLATE = ["../mod.py"] def regen_messages(): - subprocess.run( - ['pygettext', '-n'] + TO_TRANSLATE - ) + subprocess.run(["pygettext", "-n"] + TO_TRANSLATE) if __name__ == "__main__": - regen_messages() \ No newline at end of file + regen_messages() diff --git a/redbot/cogs/streams/streams.py b/redbot/cogs/streams/streams.py index 3ff66b603..0e2b48906 100644 --- a/redbot/cogs/streams/streams.py +++ b/redbot/cogs/streams/streams.py @@ -3,9 +3,24 @@ from redbot.core import Config, checks, commands from redbot.core.utils.chat_formatting import pagify from redbot.core.bot import Red from redbot.core.i18n import Translator, cog_i18n -from .streamtypes import TwitchStream, HitboxStream, MixerStream, PicartoStream, TwitchCommunity, YoutubeStream -from .errors import (OfflineStream, StreamNotFound, APIError, InvalidYoutubeCredentials, - CommunityNotFound, OfflineCommunity, StreamsError, InvalidTwitchCredentials) +from .streamtypes import ( + TwitchStream, + HitboxStream, + MixerStream, + PicartoStream, + TwitchCommunity, + YoutubeStream, +) +from .errors import ( + OfflineStream, + StreamNotFound, + APIError, + InvalidYoutubeCredentials, + CommunityNotFound, + OfflineCommunity, + StreamsError, + InvalidTwitchCredentials, +) from . import streamtypes as StreamClasses from collections import defaultdict import asyncio @@ -20,21 +35,11 @@ _ = Translator("Streams", __file__) @cog_i18n(_) class Streams: - global_defaults = { - "tokens": {}, - "streams": [], - "communities": [] - } + global_defaults = {"tokens": {}, "streams": [], "communities": []} - guild_defaults = { - "autodelete": False, - "mention_everyone": False, - "mention_here": False - } + guild_defaults = {"autodelete": False, "mention_everyone": False, "mention_here": False} - role_defaults = { - "mention": False - } + role_defaults = {"mention": False} def __init__(self, bot: Red): self.db = Config.get_conf(self, 26262626) @@ -67,8 +72,7 @@ class Streams: async def twitch(self, ctx: commands.Context, channel_name: str): """Checks if a Twitch channel is streaming""" token = await self.db.tokens.get_raw(TwitchStream.__name__, default=None) - stream = TwitchStream(name=channel_name, - token=token) + stream = TwitchStream(name=channel_name, token=token) await self.check_online(ctx, stream) @commands.command() @@ -110,14 +114,21 @@ class Streams: except StreamNotFound: await ctx.send(_("The channel doesn't seem to exist.")) except InvalidTwitchCredentials: - await ctx.send(_("The twitch token is either invalid or has not been set. " - "See `{}`.").format("{}streamset twitchtoken".format(ctx.prefix))) + await ctx.send( + _("The twitch token is either invalid or has not been set. " "See `{}`.").format( + "{}streamset twitchtoken".format(ctx.prefix) + ) + ) except InvalidYoutubeCredentials: - await ctx.send(_("The Youtube API key is either invalid or has not been set. " - "See {}.").format("`{}streamset youtubekey`".format(ctx.prefix))) + await ctx.send( + _("The Youtube API key is either invalid or has not been set. " "See {}.").format( + "`{}streamset youtubekey`".format(ctx.prefix) + ) + ) except APIError: - await ctx.send(_("Something went wrong whilst trying to contact the " - "stream service's API.")) + await ctx.send( + _("Something went wrong whilst trying to contact the " "stream service's API.") + ) else: await ctx.send(embed=embed) @@ -166,7 +177,7 @@ class Streams: await self.stream_alert(ctx, PicartoStream, channel_name) @streamalert.command(name="stop") - async def streamalert_stop(self, ctx: commands.Context, _all: bool=False): + async def streamalert_stop(self, ctx: commands.Context, _all: bool = False): """Stops all stream notifications in the channel Adding 'yes' will disable all notifications in the server""" @@ -191,8 +202,9 @@ class Streams: self.streams = streams await self.save_streams() - msg = _("All {}'s stream alerts have been disabled." - "").format("server" if _all else "channel") + msg = _("All {}'s stream alerts have been disabled." "").format( + "server" if _all else "channel" + ) await ctx.send(msg) @@ -226,23 +238,29 @@ class Streams: if is_yt and not self.check_name_or_id(channel_name): stream = _class(id=channel_name, token=token) else: - stream = _class(name=channel_name, - token=token) + stream = _class(name=channel_name, token=token) try: exists = await self.check_exists(stream) except InvalidTwitchCredentials: await ctx.send( - _("The twitch token is either invalid or has not been set. " - "See {}.").format("`{}streamset twitchtoken`".format(ctx.prefix))) + _("The twitch token is either invalid or has not been set. " "See {}.").format( + "`{}streamset twitchtoken`".format(ctx.prefix) + ) + ) return except InvalidYoutubeCredentials: - await ctx.send(_("The Youtube API key is either invalid or has not been set. " - "See {}.").format("`{}streamset youtubekey`".format(ctx.prefix))) + await ctx.send( + _( + "The Youtube API key is either invalid or has not been set. " "See {}." + ).format( + "`{}streamset youtubekey`".format(ctx.prefix) + ) + ) return except APIError: await ctx.send( - _("Something went wrong whilst trying to contact the " - "stream service's API.")) + _("Something went wrong whilst trying to contact the " "stream service's API.") + ) return else: if not exists: @@ -260,16 +278,18 @@ class Streams: await community.get_community_streams() except InvalidTwitchCredentials: await ctx.send( - _("The twitch token is either invalid or has not been set. " - "See {}.").format("`{}streamset twitchtoken`".format(ctx.prefix))) + _("The twitch token is either invalid or has not been set. " "See {}.").format( + "`{}streamset twitchtoken`".format(ctx.prefix) + ) + ) return except CommunityNotFound: await ctx.send(_("That community doesn't seem to exist.")) return except APIError: await ctx.send( - _("Something went wrong whilst trying to contact the " - "stream service's API.")) + _("Something went wrong whilst trying to contact the " "stream service's API.") + ) return except OfflineCommunity: pass @@ -331,12 +351,21 @@ class Streams: current_setting = await self.db.guild(guild).mention_everyone() if current_setting: await self.db.guild(guild).mention_everyone.set(False) - await ctx.send(_("{} will no longer be mentioned " - "for a stream alert.").format("@\u200beveryone")) + await ctx.send( + _("{} will no longer be mentioned " "for a stream alert.").format( + "@\u200beveryone" + ) + ) else: await self.db.guild(guild).mention_everyone.set(True) - await ctx.send(_("When a stream configured for stream alerts " - "comes online, {} will be mentioned").format("@\u200beveryone")) + await ctx.send( + _( + "When a stream configured for stream alerts " + "comes online, {} will be mentioned" + ).format( + "@\u200beveryone" + ) + ) @mention.command(aliases=["here"]) @commands.guild_only() @@ -346,12 +375,19 @@ class Streams: current_setting = await self.db.guild(guild).mention_here() if current_setting: await self.db.guild(guild).mention_here.set(False) - await ctx.send(_("{} will no longer be mentioned " - "for a stream alert.").format("@\u200bhere")) + await ctx.send( + _("{} will no longer be mentioned " "for a stream alert.").format("@\u200bhere") + ) else: await self.db.guild(guild).mention_here.set(True) - await ctx.send(_("When a stream configured for stream alerts " - "comes online, {} will be mentioned").format("@\u200bhere")) + await ctx.send( + _( + "When a stream configured for stream alerts " + "comes online, {} will be mentioned" + ).format( + "@\u200bhere" + ) + ) @mention.command() @commands.guild_only() @@ -363,13 +399,22 @@ class Streams: return if current_setting: await self.db.role(role).mention.set(False) - await ctx.send(_("{} will no longer be mentioned " - "for a stream alert").format("@\u200b{}".format(role.name))) + await ctx.send( + _("{} will no longer be mentioned " "for a stream alert").format( + "@\u200b{}".format(role.name) + ) + ) else: await self.db.role(role).mention.set(True) - await ctx.send(_("When a stream configured for stream alerts " - "comes online, {} will be mentioned" - "").format("@\u200b{}".format(role.name))) + await ctx.send( + _( + "When a stream configured for stream alerts " + "comes online, {} will be mentioned" + "" + ).format( + "@\u200b{}".format(role.name) + ) + ) @streamset.command() @commands.guild_only() @@ -377,8 +422,7 @@ class Streams: """Toggles automatic deletion of notifications for streams that go offline""" await self.db.guild(ctx.guild).autodelete.set(on_off) if on_off: - await ctx.send("The notifications will be deleted once " - "streams go offline.") + await ctx.send("The notifications will be deleted once " "streams go offline.") else: await ctx.send("Notifications will never be deleted.") @@ -387,14 +431,20 @@ class Streams: stream.channels.append(ctx.channel.id) if stream not in self.streams: self.streams.append(stream) - await ctx.send(_("I'll send a notification in this channel when {} " - "is online.").format(stream.name)) + await ctx.send( + _("I'll send a notification in this channel when {} " "is online.").format( + stream.name + ) + ) else: stream.channels.remove(ctx.channel.id) if not stream.channels: self.streams.remove(stream) - await ctx.send(_("I won't send notifications about {} in this " - "channel anymore.").format(stream.name)) + await ctx.send( + _("I won't send notifications about {} in this " "channel anymore.").format( + stream.name + ) + ) await self.save_streams() @@ -403,16 +453,28 @@ class Streams: community.channels.append(ctx.channel.id) if community not in self.communities: self.communities.append(community) - await ctx.send(_("I'll send a notification in this channel when a " - "channel is streaming to the {} community" - "").format(community.name)) + await ctx.send( + _( + "I'll send a notification in this channel when a " + "channel is streaming to the {} community" + "" + ).format( + community.name + ) + ) else: community.channels.remove(ctx.channel.id) if not community.channels: self.communities.remove(community) - await ctx.send(_("I won't send notifications about channels streaming " - "to the {} community in this channel anymore" - "").format(community.name)) + await ctx.send( + _( + "I won't send notifications about channels streaming " + "to the {} community in this channel anymore" + "" + ).format( + community.name + ) + ) await self.save_communities() def get_stream(self, _class, name): @@ -499,13 +561,13 @@ class Streams: settings = self.db.guild(guild) mentions = [] if await settings.mention_everyone(): - mentions.append('@everyone') + mentions.append("@everyone") if await settings.mention_here(): - mentions.append('@here') + mentions.append("@here") for role in guild.roles: if await self.db.role(role).mention(): mentions.append(role.mention) - return ' '.join(mentions) + return " ".join(mentions) async def check_communities(self): for community in self.communities: @@ -579,8 +641,7 @@ class Streams: # Fast dedupe below seen = set() seen_add = seen.add - return [x for x in streams - if not (x.name.lower() in seen or seen_add(x.name.lower()))] + return [x for x in streams if not (x.name.lower() in seen or seen_add(x.name.lower()))] # return streams @@ -604,8 +665,7 @@ class Streams: # Fast dedupe below seen = set() seen_add = seen.add - return [x for x in communities - if not (x.name.lower() in seen or seen_add(x.name.lower()))] + return [x for x in communities if not (x.name.lower() in seen or seen_add(x.name.lower()))] # return communities async def save_streams(self): diff --git a/redbot/cogs/streams/streamtypes.py b/redbot/cogs/streams/streamtypes.py index f4016c390..fb17f3283 100644 --- a/redbot/cogs/streams/streamtypes.py +++ b/redbot/cogs/streams/streamtypes.py @@ -1,5 +1,12 @@ -from .errors import StreamNotFound, APIError, OfflineStream, CommunityNotFound, OfflineCommunity, \ - InvalidYoutubeCredentials, InvalidTwitchCredentials +from .errors import ( + StreamNotFound, + APIError, + OfflineStream, + CommunityNotFound, + OfflineCommunity, + InvalidYoutubeCredentials, + InvalidTwitchCredentials, +) from random import choice, sample from string import ascii_letters import discord @@ -23,6 +30,7 @@ def rnd(url): class TwitchCommunity: + def __init__(self, **kwargs): self.name = kwargs.pop("name") self.id = kwargs.pop("id", None) @@ -32,15 +40,12 @@ class TwitchCommunity: self.type = self.__class__.__name__ async def get_community_id(self): - headers = { - "Accept": "application/vnd.twitchtv.v5+json", - "Client-ID": str(self._token) - } - params = { - "name": self.name - } + headers = {"Accept": "application/vnd.twitchtv.v5+json", "Client-ID": str(self._token)} + params = {"name": self.name} async with aiohttp.ClientSession() as session: - async with session.get(TWITCH_COMMUNITIES_ENDPOINT, headers=headers, params=params) as r: + async with session.get( + TWITCH_COMMUNITIES_ENDPOINT, headers=headers, params=params + ) as r: data = await r.json() if r.status == 200: return data["_id"] @@ -57,14 +62,8 @@ class TwitchCommunity: self.id = await self.get_community_id() except CommunityNotFound: raise - headers = { - "Accept": "application/vnd.twitchtv.v5+json", - "Client-ID": str(self._token) - } - params = { - "community_id": self.id, - "limit": 100 - } + headers = {"Accept": "application/vnd.twitchtv.v5+json", "Client-ID": str(self._token)} + params = {"community_id": self.id, "limit": 100} url = TWITCH_BASE_URL + "/kraken/streams" async with aiohttp.ClientSession() as session: async with session.get(url, headers=headers, params=params) as r: @@ -82,14 +81,11 @@ class TwitchCommunity: raise APIError() async def make_embed(self, streams: list) -> discord.Embed: - headers = { - "Accept": "application/vnd.twitchtv.v5+json", - "Client-ID": str(self._token) - } + headers = {"Accept": "application/vnd.twitchtv.v5+json", "Client-ID": str(self._token)} async with aiohttp.ClientSession() as session: async with session.get( - "{}/{}".format(TWITCH_COMMUNITIES_ENDPOINT, self.id), - headers=headers) as r: + "{}/{}".format(TWITCH_COMMUNITIES_ENDPOINT, self.id), headers=headers + ) as r: data = await r.json() avatar = data["avatar_image_url"] @@ -102,9 +98,7 @@ class TwitchCommunity: else: stream_list = streams for stream in stream_list: - name = "[{}]({})".format( - stream["channel"]["display_name"], stream["channel"]["url"] - ) + name = "[{}]({})".format(stream["channel"]["display_name"], stream["channel"]["url"]) embed.add_field(name=stream["channel"]["status"], value=name, inline=False) embed.color = 0x6441A4 @@ -125,10 +119,11 @@ class TwitchCommunity: class Stream: + def __init__(self, **kwargs): self.name = kwargs.pop("name", None) self.channels = kwargs.pop("channels", []) - #self.already_online = kwargs.pop("already_online", False) + # self.already_online = kwargs.pop("already_online", False) self._messages_cache = kwargs.pop("_messages_cache", []) self.type = self.__class__.__name__ @@ -153,6 +148,7 @@ class Stream: class YoutubeStream(Stream): + def __init__(self, **kwargs): self.id = kwargs.pop("id", None) self._token = kwargs.pop("token", None) @@ -167,7 +163,7 @@ class YoutubeStream(Stream): "part": "snippet", "channelId": self.id, "type": "video", - "eventType": "live" + "eventType": "live", } async with aiohttp.ClientSession() as session: async with session.get(url, params=params) as r: @@ -176,11 +172,7 @@ class YoutubeStream(Stream): raise OfflineStream() elif "items" in data: vid_id = data["items"][0]["id"]["videoId"] - params = { - "key": self._token, - "id": vid_id, - "part": "snippet" - } + params = {"key": self._token, "id": vid_id, "part": "snippet"} async with aiohttp.ClientSession() as session: async with session.get(YOUTUBE_VIDEOS_ENDPOINT, params=params) as r: data = await r.json() @@ -199,17 +191,16 @@ class YoutubeStream(Stream): return embed async def fetch_id(self): - params = { - "key": self._token, - "forUsername": self.name, - "part": "id" - } + params = {"key": self._token, "forUsername": self.name, "part": "id"} async with aiohttp.ClientSession() as session: async with session.get(YOUTUBE_CHANNELS_ENDPOINT, params=params) as r: data = await r.json() - if "error" in data and data["error"]["code"] == 400 and\ - data["error"]["errors"][0]["reason"] == "keyInvalid": + if ( + "error" in data + and data["error"]["code"] == 400 + and data["error"]["errors"][0]["reason"] == "keyInvalid" + ): raise InvalidYoutubeCredentials() elif "items" in data and len(data["items"]) == 0: raise StreamNotFound() @@ -222,6 +213,7 @@ class YoutubeStream(Stream): class TwitchStream(Stream): + def __init__(self, **kwargs): self.id = kwargs.pop("id", None) self._token = kwargs.pop("token", None) @@ -232,19 +224,16 @@ class TwitchStream(Stream): self.id = await self.fetch_id() url = TWITCH_STREAMS_ENDPOINT + self.id - header = { - 'Client-ID': str(self._token), - 'Accept': 'application/vnd.twitchtv.v5+json' - } + header = {"Client-ID": str(self._token), "Accept": "application/vnd.twitchtv.v5+json"} async with aiohttp.ClientSession() as session: async with session.get(url, headers=header) as r: - data = await r.json(encoding='utf-8') + data = await r.json(encoding="utf-8") if r.status == 200: if data["stream"] is None: - #self.already_online = False + # self.already_online = False raise OfflineStream() - #self.already_online = True + # self.already_online = True # In case of rename self.name = data["stream"]["channel"]["name"] return self.make_embed(data) @@ -256,10 +245,7 @@ class TwitchStream(Stream): raise APIError() async def fetch_id(self): - header = { - 'Client-ID': str(self._token), - 'Accept': 'application/vnd.twitchtv.v5+json' - } + header = {"Client-ID": str(self._token), "Accept": "application/vnd.twitchtv.v5+json"} url = TWITCH_ID_ENDPOINT + self.name async with aiohttp.ClientSession() as session: @@ -280,8 +266,7 @@ class TwitchStream(Stream): url = channel["url"] logo = channel["logo"] if logo is None: - logo = ("https://static-cdn.jtvnw.net/" - "jtv_user_pictures/xarth/404_user_70x70.png") + logo = ("https://static-cdn.jtvnw.net/" "jtv_user_pictures/xarth/404_user_70x70.png") status = channel["status"] if not status: status = "Untitled broadcast" @@ -303,21 +288,22 @@ class TwitchStream(Stream): class HitboxStream(Stream): + async def is_online(self): url = "https://api.hitbox.tv/media/live/" + self.name async with aiohttp.ClientSession() as session: async with session.get(url) as r: - #data = await r.json(encoding='utf-8') + # data = await r.json(encoding='utf-8') data = await r.text() data = json.loads(data, strict=False) if "livestream" not in data: raise StreamNotFound() elif data["livestream"][0]["media_is_live"] == "0": - #self.already_online = False + # self.already_online = False raise OfflineStream() elif data["livestream"][0]["media_is_live"] == "1": - #self.already_online = True + # self.already_online = True return self.make_embed(data) raise APIError() @@ -340,20 +326,21 @@ class HitboxStream(Stream): class MixerStream(Stream): + async def is_online(self): url = "https://mixer.com/api/v1/channels/" + self.name async with aiohttp.ClientSession() as session: async with session.get(url) as r: - #data = await r.json(encoding='utf-8') - data = await r.text(encoding='utf-8') + # data = await r.json(encoding='utf-8') + data = await r.text(encoding="utf-8") if r.status == 200: data = json.loads(data, strict=False) if data["online"] is True: - #self.already_online = True + # self.already_online = True return self.make_embed(data) else: - #self.already_online = False + # self.already_online = False raise OfflineStream() elif r.status == 404: raise StreamNotFound() @@ -361,8 +348,7 @@ class MixerStream(Stream): raise APIError() def make_embed(self, data): - default_avatar = ("https://mixer.com/_latest/assets/images/main/" - "avatars/default.jpg") + default_avatar = ("https://mixer.com/_latest/assets/images/main/" "avatars/default.jpg") user = data["user"] url = "https://mixer.com/" + data["token"] embed = discord.Embed(title=data["name"], url=url) @@ -382,19 +368,20 @@ class MixerStream(Stream): class PicartoStream(Stream): + async def is_online(self): url = "https://api.picarto.tv/v1/channel/name/" + self.name async with aiohttp.ClientSession() as session: async with session.get(url) as r: - data = await r.text(encoding='utf-8') + data = await r.text(encoding="utf-8") if r.status == 200: data = json.loads(data) if data["online"] is True: - #self.already_online = True + # self.already_online = True return self.make_embed(data) else: - #self.already_online = False + # self.already_online = False raise OfflineStream() elif r.status == 404: raise StreamNotFound() @@ -402,8 +389,9 @@ class PicartoStream(Stream): raise APIError() def make_embed(self, data): - avatar = rnd("https://picarto.tv/user_data/usrimg/{}/dsdefault.jpg" - "".format(data["name"].lower())) + avatar = rnd( + "https://picarto.tv/user_data/usrimg/{}/dsdefault.jpg" "".format(data["name"].lower()) + ) url = "https://picarto.tv/" + data["name"] thumbnail = data["thumbnails"]["web"] embed = discord.Embed(title=data["title"], url=url) @@ -424,6 +412,5 @@ class PicartoStream(Stream): data["adult"] = "" embed.color = 0x4C90F3 - embed.set_footer(text="{adult}Category: {category} | Tags: {tags}" - "".format(**data)) + embed.set_footer(text="{adult}Category: {category} | Tags: {tags}" "".format(**data)) return embed diff --git a/redbot/cogs/trivia/locales/regen_messages.py b/redbot/cogs/trivia/locales/regen_messages.py index e04dd4843..cd12e1867 100644 --- a/redbot/cogs/trivia/locales/regen_messages.py +++ b/redbot/cogs/trivia/locales/regen_messages.py @@ -1,15 +1,11 @@ import subprocess -TO_TRANSLATE = [ - '../mod.py' -] +TO_TRANSLATE = ["../mod.py"] def regen_messages(): - subprocess.run( - ['pygettext', '-n'] + TO_TRANSLATE - ) + subprocess.run(["pygettext", "-n"] + TO_TRANSLATE) if __name__ == "__main__": - regen_messages() \ No newline at end of file + regen_messages() diff --git a/redbot/cogs/trivia/session.py b/redbot/cogs/trivia/session.py index 28fa92786..78993497d 100644 --- a/redbot/cogs/trivia/session.py +++ b/redbot/cogs/trivia/session.py @@ -10,11 +10,13 @@ from .log import LOG __all__ = ["TriviaSession"] -_REVEAL_MESSAGES = ("I know this one! {}!", "Easy: {}.", - "Oh really? It's {} of course.") -_FAIL_MESSAGES = ("To the next one I guess...", "Moving on...", - "I'm sure you'll know the answer of the next one.", - "\N{PENSIVE FACE} Next one.") +_REVEAL_MESSAGES = ("I know this one! {}!", "Easy: {}.", "Oh really? It's {} of course.") +_FAIL_MESSAGES = ( + "To the next one I guess...", + "Moving on...", + "I'm sure you'll know the answer of the next one.", + "\N{PENSIVE FACE} Next one.", +) class TriviaSession(): @@ -49,10 +51,7 @@ class TriviaSession(): """ - def __init__(self, - ctx, - question_list: dict, - settings: dict): + def __init__(self, ctx, question_list: dict, settings: dict): self.ctx = ctx list_ = list(question_list.items()) random.shuffle(list_) @@ -128,9 +127,9 @@ class TriviaSession(): num_lists = len(list_names) if num_lists > 2: # at least 3 lists, join all but last with comma - msg = ", ".join(list_names[:num_lists-1]) + msg = ", ".join(list_names[:num_lists - 1]) # join onto last with "and" - msg = " and ".join((msg, list_names[num_lists-1])) + msg = " and ".join((msg, list_names[num_lists - 1])) else: # either 1 or 2 lists, join together with "and" msg = " and ".join(list_names) @@ -150,10 +149,7 @@ class TriviaSession(): answers = _parse_answers(answers) yield question, answers - async def wait_for_answer(self, - answers, - delay: float, - timeout: float): + async def wait_for_answer(self, answers, delay: float, timeout: float): """Wait for a correct answer, and then respond. Scores are also updated in this method. @@ -178,7 +174,8 @@ class TriviaSession(): """ try: message = await self.ctx.bot.wait_for( - "message", check=self.check_answer(answers), timeout=delay) + "message", check=self.check_answer(answers), timeout=delay + ) except asyncio.TimeoutError: if time.time() - self._last_response >= timeout: await self.ctx.send("Guys...? Well, I guess I'll stop then.") @@ -194,8 +191,7 @@ class TriviaSession(): await self.ctx.send(reply) else: self.scores[message.author] += 1 - reply = "You got it {}! **+1** to you!".format( - message.author.display_name) + reply = "You got it {}! **+1** to you!".format(message.author.display_name) await self.ctx.send(reply) return True @@ -218,9 +214,11 @@ class TriviaSession(): """ answers = tuple(s.lower() for s in answers) + def _pred(message: discord.Message): - early_exit = (message.channel != self.ctx.channel - or message.author == self.ctx.guild.me) + early_exit = ( + message.channel != self.ctx.channel or message.author == self.ctx.guild.me + ) if early_exit: return False @@ -260,8 +258,7 @@ class TriviaSession(): """Cancel whichever tasks this session is running.""" self._task.cancel() channel = self.ctx.channel - LOG.debug("Force stopping trivia session; #%s in %s", channel, - channel.guild.id) + LOG.debug("Force stopping trivia session; #%s in %s", channel, channel.guild.id) async def pay_winner(self, multiplier: float): """Pay the winner of this trivia session. @@ -275,8 +272,7 @@ class TriviaSession(): paid. """ - (winner, score) = next((tup for tup in self.scores.most_common(1)), - (None, None)) + (winner, score) = next((tup for tup in self.scores.most_common(1)), (None, None)) me_ = self.ctx.guild.me if winner is not None and winner != me_ and score > 0: contestants = list(self.scores.keys()) @@ -285,13 +281,12 @@ class TriviaSession(): if len(contestants) >= 3: amount = int(multiplier * score) if amount > 0: - LOG.debug("Paying trivia winner: %d credits --> %s", - amount, str(winner)) + LOG.debug("Paying trivia winner: %d credits --> %s", amount, str(winner)) await deposit_credits(winner, int(multiplier * score)) await self.ctx.send( "Congratulations, {0}, you have received {1} credits" - " for coming first.".format(winner.display_name, - amount)) + " for coming first.".format(winner.display_name, amount) + ) def _parse_answers(answers): diff --git a/redbot/cogs/trivia/trivia.py b/redbot/cogs/trivia/trivia.py index a0721c4ca..eec6b0e06 100644 --- a/redbot/cogs/trivia/trivia.py +++ b/redbot/cogs/trivia/trivia.py @@ -26,8 +26,7 @@ class Trivia: def __init__(self): self.trivia_sessions = [] - self.conf = Config.get_conf( - self, identifier=UNIQUE_ID, force_registration=True) + self.conf = Config.get_conf(self, identifier=UNIQUE_ID, force_registration=True) self.conf.register_guild( max_score=10, @@ -36,10 +35,10 @@ class Trivia: bot_plays=False, reveal_answer=True, payout_multiplier=0.0, - allow_override=True) + allow_override=True, + ) - self.conf.register_member( - wins=0, games=0, total_score=0) + self.conf.register_member(wins=0, games=0, total_score=0) @commands.group() @commands.guild_only() @@ -60,7 +59,8 @@ class Trivia: "Payout multiplier: {payout_multiplier}\n" "Allow lists to override settings: {allow_override}" "".format(**settings_dict), - lang="py") + lang="py", + ) await ctx.send(msg) @triviaset.command(name="maxscore") @@ -81,8 +81,7 @@ class Trivia: return settings = self.conf.guild(ctx.guild) await settings.delay.set(seconds) - await ctx.send("Done. Maximum seconds to answer set to {}." - "".format(seconds)) + await ctx.send("Done. Maximum seconds to answer set to {}." "".format(seconds)) @triviaset.command(name="stopafter") async def triviaset_stopafter(self, ctx: commands.Context, seconds: float): @@ -92,38 +91,41 @@ class Trivia: await ctx.send("Must be larger than the answer time limit.") return await settings.timeout.set(seconds) - await ctx.send("Done. Trivia sessions will now time out after {}" - " seconds of no responses.".format(seconds)) + await ctx.send( + "Done. Trivia sessions will now time out after {}" + " seconds of no responses.".format(seconds) + ) @triviaset.command(name="override") - async def triviaset_allowoverride(self, - ctx: commands.Context, - enabled: bool): + async def triviaset_allowoverride(self, ctx: commands.Context, enabled: bool): """Allow/disallow trivia lists to override settings.""" settings = self.conf.guild(ctx.guild) await settings.allow_override.set(enabled) enabled = "now" if enabled else "no longer" - await ctx.send("Done. Trivia lists can {} override the trivia settings" - " for this server.".format(enabled)) + await ctx.send( + "Done. Trivia lists can {} override the trivia settings" + " for this server.".format(enabled) + ) @triviaset.command(name="botplays") - async def trivaset_bot_plays(self, - ctx: commands.Context, - true_or_false: bool): + async def trivaset_bot_plays(self, ctx: commands.Context, true_or_false: bool): """Set whether or not the bot gains points. If enabled, the bot will gain a point if no one guesses correctly. """ settings = self.conf.guild(ctx.guild) await settings.bot_plays.set(true_or_false) - await ctx.send("Done. " + ( - "I'll gain a point if users don't answer in time." if true_or_false - else "Alright, I won't embarass you at trivia anymore.")) + await ctx.send( + "Done. " + + ( + "I'll gain a point if users don't answer in time." + if true_or_false + else "Alright, I won't embarass you at trivia anymore." + ) + ) @triviaset.command(name="revealanswer") - async def trivaset_reveal_answer(self, - ctx: commands.Context, - true_or_false: bool): + async def trivaset_reveal_answer(self, ctx: commands.Context, true_or_false: bool): """Set whether or not the answer is revealed. If enabled, the bot will reveal the answer if no one guesses correctly @@ -131,15 +133,18 @@ class Trivia: """ settings = self.conf.guild(ctx.guild) await settings.reveal_answer.set(true_or_false) - await ctx.send("Done. " + ( - "I'll reveal the answer if no one knows it." if true_or_false else - "I won't reveal the answer to the questions anymore.")) + await ctx.send( + "Done. " + + ( + "I'll reveal the answer if no one knows it." + if true_or_false + else "I won't reveal the answer to the questions anymore." + ) + ) @triviaset.command(name="payout") @check_global_setting_admin() - async def triviaset_payout_multiplier(self, - ctx: commands.Context, - multiplier: float): + async def triviaset_payout_multiplier(self, ctx: commands.Context, multiplier: float): """Set the payout multiplier. This can be any positive decimal number. If a user wins trivia when at @@ -155,8 +160,7 @@ class Trivia: return await settings.payout_multiplier.set(multiplier) if not multiplier: - await ctx.send("Done. I will no longer reward the winner with a" - " payout.") + await ctx.send("Done. I will no longer reward the winner with a" " payout.") return await ctx.send("Done. Payout multiplier set to {}.".format(multiplier)) @@ -174,8 +178,7 @@ class Trivia: categories = [c.lower() for c in categories] session = self._get_trivia_session(ctx.channel) if session is not None: - await ctx.send( - "There is already an ongoing trivia session in this channel.") + await ctx.send("There is already an ongoing trivia session in this channel.") return trivia_dict = {} authors = [] @@ -185,21 +188,26 @@ class Trivia: try: dict_ = self.get_trivia_list(category) except FileNotFoundError: - await ctx.send("Invalid category `{0}`. See `{1}trivia list`" - " for a list of trivia categories." - "".format(category, ctx.prefix)) + await ctx.send( + "Invalid category `{0}`. See `{1}trivia list`" + " for a list of trivia categories." + "".format(category, ctx.prefix) + ) except InvalidListError: - await ctx.send("There was an error parsing the trivia list for" - " the `{}` category. It may be formatted" - " incorrectly.".format(category)) + await ctx.send( + "There was an error parsing the trivia list for" + " the `{}` category. It may be formatted" + " incorrectly.".format(category) + ) else: trivia_dict.update(dict_) authors.append(trivia_dict.pop("AUTHOR", None)) continue return if not trivia_dict: - await ctx.send("The trivia list was parsed successfully, however" - " it appears to be empty!") + await ctx.send( + "The trivia list was parsed successfully, however" " it appears to be empty!" + ) return settings = await self.conf.guild(ctx.guild).all() config = trivia_dict.pop("CONFIG", None) @@ -215,13 +223,16 @@ class Trivia: """Stop an ongoing trivia session.""" session = self._get_trivia_session(ctx.channel) if session is None: - await ctx.send( - "There is no ongoing trivia session in this channel.") + await ctx.send("There is no ongoing trivia session in this channel.") return author = ctx.author - auth_checks = (await ctx.bot.is_owner(author), await - ctx.bot.is_mod(author), await ctx.bot.is_admin(author), - author == ctx.guild.owner, author == session.ctx.author) + auth_checks = ( + await ctx.bot.is_owner(author), + await ctx.bot.is_mod(author), + await ctx.bot.is_admin(author), + author == ctx.guild.owner, + author == session.ctx.author, + ) if any(auth_checks): await session.end_game() session.force_stop() @@ -234,8 +245,7 @@ class Trivia: """List available trivia categories.""" lists = set(p.stem for p in self._all_lists()) - msg = box("**Available trivia lists**\n\n{}" - "".format(", ".join(sorted(lists)))) + msg = box("**Available trivia lists**\n\n{}" "".format(", ".join(sorted(lists)))) if len(msg) > 1000: await ctx.author.send(msg) return @@ -256,10 +266,9 @@ class Trivia: @trivia_leaderboard.command(name="server") @commands.guild_only() - async def trivia_leaderboard_server(self, - ctx: commands.Context, - sort_by: str="wins", - top: int=10): + async def trivia_leaderboard_server( + self, ctx: commands.Context, sort_by: str = "wins", top: int = 10 + ): """Leaderboard for this server. can be any of the following fields: @@ -271,9 +280,11 @@ class Trivia: """ key = self._get_sort_key(sort_by) if key is None: - await ctx.send("Unknown field `{}`, see `{}help trivia " - "leaderboard server` for valid fields to sort by." - "".format(sort_by, ctx.prefix)) + await ctx.send( + "Unknown field `{}`, see `{}help trivia " + "leaderboard server` for valid fields to sort by." + "".format(sort_by, ctx.prefix) + ) return guild = ctx.guild data = await self.conf.all_members(guild) @@ -282,10 +293,9 @@ class Trivia: await self.send_leaderboard(ctx, data, key, top) @trivia_leaderboard.command(name="global") - async def trivia_leaderboard_global(self, - ctx: commands.Context, - sort_by: str="wins", - top: int=10): + async def trivia_leaderboard_global( + self, ctx: commands.Context, sort_by: str = "wins", top: int = 10 + ): """Global trivia leaderboard. can be any of the following fields: @@ -298,9 +308,11 @@ class Trivia: """ key = self._get_sort_key(sort_by) if key is None: - await ctx.send("Unknown field `{}`, see `{}help trivia " - "leaderboard global` for valid fields to sort by." - "".format(sort_by, ctx.prefix)) + await ctx.send( + "Unknown field `{}`, see `{}help trivia " + "leaderboard global` for valid fields to sort by." + "".format(sort_by, ctx.prefix) + ) return data = await self.conf.all_members() collated_data = {} @@ -327,11 +339,7 @@ class Trivia: elif key in ("total", "score", "answers", "correct"): return "total_score" - async def send_leaderboard(self, - ctx: commands.Context, - data: dict, - key: str, - top: int): + async def send_leaderboard(self, ctx: commands.Context, data: dict, key: str, top: int): """Send the leaderboard from the given data. Parameters @@ -382,23 +390,34 @@ class Trivia: items = sorted(items, key=lambda t: t[1][key], reverse=True) max_name_len = max(map(lambda m: len(str(m)), data.keys())) # Headers - headers = ("Rank", "Member{}".format(" " * (max_name_len - 6)), "Wins", - "Games Played", "Total Score", "Average Score") + headers = ( + "Rank", + "Member{}".format(" " * (max_name_len - 6)), + "Wins", + "Games Played", + "Total Score", + "Average Score", + ) lines = [" | ".join(headers)] # Header underlines lines.append(" | ".join(("-" * len(h) for h in headers))) for rank, tup in enumerate(items, 1): member, m_data = tup # Align fields to header width - fields = tuple(map(str, (rank, - member, - m_data["wins"], - m_data["games"], - m_data["total_score"], - round(m_data["average_score"], 2)))) - padding = [ - " " * (len(h) - len(f)) for h, f in zip(headers, fields) - ] + fields = tuple( + map( + str, + ( + rank, + member, + m_data["wins"], + m_data["games"], + m_data["total_score"], + round(m_data["average_score"], 2), + ), + ) + ) + padding = [" " * (len(h) - len(f)) for h, f in zip(headers, fields)] fields = tuple(f + padding[i] for i, f in enumerate(fields)) lines.append(" | ".join(fields).format(member=member, **m_data)) if rank == top: @@ -418,8 +437,7 @@ class Trivia: """ channel = session.ctx.channel - LOG.debug("Ending trivia session; #%s in %s", channel, - channel.guild.id) + LOG.debug("Ending trivia session; #%s in %s", channel, channel.guild.id) if session in self.trivia_sessions: self.trivia_sessions.remove(session) if session.scores: @@ -462,10 +480,9 @@ class Trivia: try: path = next(p for p in self._all_lists() if p.stem == category) except StopIteration: - raise FileNotFoundError("Could not find the `{}` category" - "".format(category)) + raise FileNotFoundError("Could not find the `{}` category" "".format(category)) - with path.open(encoding='utf-8') as file: + with path.open(encoding="utf-8") as file: try: dict_ = yaml.load(file) except yaml.error.YAMLError as exc: @@ -473,14 +490,13 @@ class Trivia: else: return dict_ - def _get_trivia_session(self, - channel: discord.TextChannel) -> TriviaSession: - return next((session for session in self.trivia_sessions - if session.ctx.channel == channel), None) + def _get_trivia_session(self, channel: discord.TextChannel) -> TriviaSession: + return next( + (session for session in self.trivia_sessions if session.ctx.channel == channel), None + ) def _all_lists(self): - personal_lists = tuple(p.resolve() - for p in cog_data_path(self).glob("*.yaml")) + personal_lists = tuple(p.resolve() for p in cog_data_path(self).glob("*.yaml")) return personal_lists + tuple(ext_trivia.lists()) diff --git a/redbot/cogs/warnings/helpers.py b/redbot/cogs/warnings/helpers.py index 6bc6664b8..405a92e09 100644 --- a/redbot/cogs/warnings/helpers.py +++ b/redbot/cogs/warnings/helpers.py @@ -9,7 +9,9 @@ from redbot.core.i18n import Translator _ = Translator("Warnings", __file__) -async def warning_points_add_check(config: Config, ctx: commands.Context, user: discord.Member, points: int): +async def warning_points_add_check( + config: Config, ctx: commands.Context, user: discord.Member, points: int +): """Handles any action that needs to be taken or not based on the points""" guild = ctx.guild guild_settings = config.guild(guild) @@ -24,7 +26,9 @@ async def warning_points_add_check(config: Config, ctx: commands.Context, user: await create_and_invoke_context(ctx, act["exceed_command"], user) -async def warning_points_remove_check(config: Config, ctx: commands.Context, user: discord.Member, points: int): +async def warning_points_remove_check( + config: Config, ctx: commands.Context, user: discord.Member, points: int +): guild = ctx.guild guild_settings = config.guild(guild) act = {} @@ -38,7 +42,9 @@ async def warning_points_remove_check(config: Config, ctx: commands.Context, use await create_and_invoke_context(ctx, act["drop_command"], user) -async def create_and_invoke_context(realctx: commands.Context, command_str: str, user: discord.Member): +async def create_and_invoke_context( + realctx: commands.Context, command_str: str, user: discord.Member +): m = copy(realctx.message) m.content = command_str.format(user=user.mention, prefix=realctx.prefix) fctx = await realctx.bot.get_context(m, cls=commands.Context) @@ -54,7 +60,7 @@ def get_command_from_input(bot, userinput: str): while com is None: com = bot.get_command(userinput) if com is None: - userinput = ' '.join(userinput.split(' ')[:-1]) + userinput = " ".join(userinput.split(" ")[:-1]) if len(userinput) == 0: break if com is None: @@ -63,8 +69,9 @@ def get_command_from_input(bot, userinput: str): check_str = inspect.getsource(checks.is_owner) if any(inspect.getsource(x) in check_str for x in com.checks): # command the user specified has the is_owner check - return None, _("That command requires bot owner. I can't " - "allow you to use that for an action") + return None, _( + "That command requires bot owner. I can't " "allow you to use that for an action" + ) return "{prefix}" + orig, None @@ -72,13 +79,15 @@ async def get_command_for_exceeded_points(ctx: commands.Context): """Gets the command to be executed when the user is at or exceeding the points threshold for the action""" await ctx.send( - _("Enter the command to be run when the user exceeds the points for " - "this action to occur.\nEnter it exactly as you would if you were " - "actually trying to run the command, except don't put a prefix and " - "use {user} in place of any user/member arguments\n\n" - "WARNING: The command entered will be run without regard to checks or cooldowns. " - "Commands requiring bot owner are not allowed for security reasons.\n\n" - "Please wait 15 seconds before entering your response.") + _( + "Enter the command to be run when the user exceeds the points for " + "this action to occur.\nEnter it exactly as you would if you were " + "actually trying to run the command, except don't put a prefix and " + "use {user} in place of any user/member arguments\n\n" + "WARNING: The command entered will be run without regard to checks or cooldowns. " + "Commands requiring bot owner are not allowed for security reasons.\n\n" + "Please wait 15 seconds before entering your response." + ) ) await asyncio.sleep(15) @@ -110,15 +119,17 @@ async def get_command_for_dropping_points(ctx: commands.Context): when the user exceeded the threshold """ await ctx.send( - _("Enter the command to be run when the user returns to a value below " - "the points for this action to occur. Please note that this is " - "intended to be used for reversal of the action taken when the user " - "exceeded the action's point value\nEnter it exactly as you would " - "if you were actually trying to run the command, except don't put a prefix " - "and use {user} in place of any user/member arguments\n\n" - "WARNING: The command entered will be run without regard to checks or cooldowns. " - "Commands requiring bot owner are not allowed for security reasons.\n\n" - "Please wait 15 seconds before entering your response.") + _( + "Enter the command to be run when the user returns to a value below " + "the points for this action to occur. Please note that this is " + "intended to be used for reversal of the action taken when the user " + "exceeded the action's point value\nEnter it exactly as you would " + "if you were actually trying to run the command, except don't put a prefix " + "and use {user} in place of any user/member arguments\n\n" + "WARNING: The command entered will be run without regard to checks or cooldowns. " + "Commands requiring bot owner are not allowed for security reasons.\n\n" + "Please wait 15 seconds before entering your response." + ) ) await asyncio.sleep(15) diff --git a/redbot/cogs/warnings/locales/regen_messages.py b/redbot/cogs/warnings/locales/regen_messages.py index 024c00482..6b46d4338 100644 --- a/redbot/cogs/warnings/locales/regen_messages.py +++ b/redbot/cogs/warnings/locales/regen_messages.py @@ -1,15 +1,10 @@ import subprocess -TO_TRANSLATE = [ - '../warnings.py', - '../helpers.py' -] +TO_TRANSLATE = ["../warnings.py", "../helpers.py"] def regen_messages(): - subprocess.run( - ['pygettext', '-n'] + TO_TRANSLATE - ) + subprocess.run(["pygettext", "-n"] + TO_TRANSLATE) if __name__ == "__main__": diff --git a/redbot/cogs/warnings/warnings.py b/redbot/cogs/warnings/warnings.py index bf3a1f5f9..5d4a9c25e 100644 --- a/redbot/cogs/warnings/warnings.py +++ b/redbot/cogs/warnings/warnings.py @@ -3,8 +3,12 @@ from collections import namedtuple import discord import asyncio -from redbot.cogs.warnings.helpers import warning_points_add_check, get_command_for_exceeded_points, \ - get_command_for_dropping_points, warning_points_remove_check +from redbot.cogs.warnings.helpers import ( + warning_points_add_check, + get_command_for_exceeded_points, + get_command_for_dropping_points, + warning_points_remove_check, +) from redbot.core import Config, modlog, checks, commands from redbot.core.bot import Red from redbot.core.i18n import Translator, cog_i18n @@ -18,17 +22,9 @@ _ = Translator("Warnings", __file__) class Warnings: """A warning system for Red""" - default_guild = { - "actions": [], - "reasons": {}, - "allow_custom_reasons": False - } + default_guild = {"actions": [], "reasons": {}, "allow_custom_reasons": False} - default_member = { - "total_points": 0, - "status": "", - "warnings": {} - } + default_member = {"total_points": 0, "status": "", "warnings": {}} def __init__(self, bot: Red): self.config = Config.get_conf(self, identifier=5757575755) @@ -41,9 +37,7 @@ class Warnings: @staticmethod async def register_warningtype(): try: - await modlog.register_casetype( - "warning", True, "\N{WARNING SIGN}", "Warning", None - ) + await modlog.register_casetype("warning", True, "\N{WARNING SIGN}", "Warning", None) except RuntimeError: pass @@ -105,7 +99,7 @@ class Warnings: "action_name": name, "points": points, "exceed_command": exceed_command, - "drop_command": drop_command + "drop_command": drop_command, } # Have all details for the action, now save the action @@ -138,9 +132,7 @@ class Warnings: registered_actions.remove(to_remove) await ctx.tick() else: - await ctx.send( - _("No action named {} exists!").format(action_name) - ) + await ctx.send(_("No action named {} exists!").format(action_name)) @commands.group() @commands.guild_only() @@ -159,13 +151,8 @@ class Warnings: if name.lower() == "custom": await ctx.send("That cannot be used as a reason name!") return - to_add = { - "points": points, - "description": description - } - completed = { - name.lower(): to_add - } + to_add = {"points": points, "description": description} + completed = {name.lower(): to_add} guild_settings = self.config.guild(guild) @@ -219,8 +206,7 @@ class Warnings: msg_list.append( "Name: {}\nPoints: {}\nExceed command: {}\n" "Drop command: {}".format( - r["action_name"], r["points"], r["exceed_command"], - r["drop_command"] + r["action_name"], r["points"], r["exceed_command"], r["drop_command"] ) ) if msg_list: @@ -262,7 +248,7 @@ class Warnings: str(ctx.message.id): { "points": reason_type["points"], "description": reason_type["description"], - "mod": ctx.author.id + "mod": ctx.author.id, } } async with member_settings.warnings() as user_warnings: @@ -275,7 +261,7 @@ class Warnings: @commands.command() @commands.guild_only() - async def warnings(self, ctx: commands.Context, userid: int=None): + async def warnings(self, ctx: commands.Context, userid: int = None): """Show warnings for the specified user. If userid is None, show warnings for the person running the command Note that showing warnings for users other than yourself requires @@ -285,10 +271,7 @@ class Warnings: else: if not await is_admin_or_superior(self.bot, ctx.author): await ctx.send( - warning( - _("You are not allowed to check " - "warnings for other users!") - ) + warning(_("You are not allowed to check " "warnings for other users!")) ) return else: @@ -305,22 +288,14 @@ class Warnings: mod = ctx.guild.get_member(user_warnings[key]["mod"]) if mod is None: mod = discord.utils.get( - self.bot.get_all_members(), - id=user_warnings[key]["mod"] + self.bot.get_all_members(), id=user_warnings[key]["mod"] ) if mod is None: - mod = await self.bot.get_user_info( - user_warnings[key]["mod"] - ) + mod = await self.bot.get_user_info(user_warnings[key]["mod"]) msg += "{} point warning {} issued by {} for {}\n".format( - user_warnings[key]["points"], - key, - mod, - user_warnings[key]["description"] + user_warnings[key]["points"], key, mod, user_warnings[key]["description"] ) - await ctx.send_interactive( - pagify(msg), box_lang="Warnings for {}".format(user) - ) + await ctx.send_interactive(pagify(msg), box_lang="Warnings for {}".format(user)) @commands.command() @commands.guild_only() @@ -348,10 +323,7 @@ class Warnings: @staticmethod async def custom_warning_reason(ctx: commands.Context): """Handles getting description and points for custom reasons""" - to_add = { - "points": 0, - "description": "" - } + to_add = {"points": 0, "description": ""} def same_author_check(m): return m.author == ctx.author diff --git a/redbot/core/__init__.py b/redbot/core/__init__.py index 798ce4cfa..bb6bee637 100644 --- a/redbot/core/__init__.py +++ b/redbot/core/__init__.py @@ -4,16 +4,15 @@ __all__ = ["Config", "__version__"] class VersionInfo: + def __init__(self, major, minor, micro, releaselevel, serial): - self._levels = ['alpha', 'beta', 'final'] + self._levels = ["alpha", "beta", "final"] self.major = major self.minor = minor self.micro = micro if releaselevel not in self._levels: - raise TypeError("'releaselevel' must be one of: {}".format( - ', '.join(self._levels) - )) + raise TypeError("'releaselevel' must be one of: {}".format(", ".join(self._levels))) self.releaselevel = releaselevel self.serial = serial @@ -21,8 +20,9 @@ class VersionInfo: def __lt__(self, other): my_index = self._levels.index(self.releaselevel) other_index = self._levels.index(other.releaselevel) - return (self.major, self.minor, self.micro, my_index, self.serial) < \ - (other.major, other.minor, other.micro, other_index, other.serial) + return (self.major, self.minor, self.micro, my_index, self.serial) < ( + other.major, other.minor, other.micro, other_index, other.serial + ) def __repr__(self): return "VersionInfo(major={}, minor={}, micro={}, releaselevel={}, serial={})".format( @@ -32,5 +32,6 @@ class VersionInfo: def to_json(self): return [self.major, self.minor, self.micro, self.releaselevel, self.serial] + __version__ = "3.0.0b14" -version_info = VersionInfo(3, 0, 0, 'beta', 14) +version_info = VersionInfo(3, 0, 0, "beta", 14) diff --git a/redbot/core/bank.py b/redbot/core/bank.py index 0ba634610..0bb85e196 100644 --- a/redbot/core/bank.py +++ b/redbot/core/bank.py @@ -6,29 +6,36 @@ import discord from redbot.core import Config -__all__ = ["Account", "get_balance", "set_balance", "withdraw_credits", "deposit_credits", - "can_spend", "transfer_credits", "wipe_bank", "get_account", "is_global", - "set_global", "get_bank_name", "set_bank_name", "get_currency_name", - "set_currency_name", "get_default_balance", "set_default_balance"] +__all__ = [ + "Account", + "get_balance", + "set_balance", + "withdraw_credits", + "deposit_credits", + "can_spend", + "transfer_credits", + "wipe_bank", + "get_account", + "is_global", + "set_global", + "get_bank_name", + "set_bank_name", + "get_currency_name", + "set_currency_name", + "get_default_balance", + "set_default_balance", +] _DEFAULT_GLOBAL = { "is_global": False, "bank_name": "Twentysix bank", "currency": "credits", - "default_balance": 100 + "default_balance": 100, } -_DEFAULT_GUILD = { - "bank_name": "Twentysix bank", - "currency": "credits", - "default_balance": 100 -} +_DEFAULT_GUILD = {"bank_name": "Twentysix bank", "currency": "credits", "default_balance": 100} -_DEFAULT_MEMBER = { - "name": "", - "balance": 0, - "created_at": 0 -} +_DEFAULT_MEMBER = {"name": "", "balance": 0, "created_at": 0} _DEFAULT_USER = _DEFAULT_MEMBER @@ -50,9 +57,9 @@ def _register_defaults(): _conf.register_member(**_DEFAULT_MEMBER) _conf.register_user(**_DEFAULT_USER) -if not os.environ.get('BUILDING_DOCS'): - _conf = Config.get_conf( - None, 384734293238749, cog_name="Bank", force_registration=True) + +if not os.environ.get("BUILDING_DOCS"): + _conf = Config.get_conf(None, 384734293238749, cog_name="Bank", force_registration=True) _register_defaults() @@ -285,7 +292,7 @@ async def wipe_bank(): await _conf.clear_all_members() -async def get_leaderboard(positions: int=None, guild: discord.Guild=None) -> List[tuple]: +async def get_leaderboard(positions: int = None, guild: discord.Guild = None) -> List[tuple]: """ Gets the bank's leaderboard @@ -319,14 +326,16 @@ async def get_leaderboard(positions: int=None, guild: discord.Guild=None) -> Lis if guild is None: raise TypeError("Expected a guild, got NoneType object instead!") raw_accounts = await _conf.all_members(guild) - sorted_acc = sorted(raw_accounts.items(), key=lambda x: x[1]['balance'], reverse=True) + sorted_acc = sorted(raw_accounts.items(), key=lambda x: x[1]["balance"], reverse=True) if positions is None: return sorted_acc else: return sorted_acc[:positions] -async def get_leaderboard_position(member: Union[discord.User, discord.Member]) -> Union[int, None]: +async def get_leaderboard_position( + member: Union[discord.User, discord.Member] +) -> Union[int, None]: """ Get the leaderboard position for the specified user @@ -387,13 +396,13 @@ async def get_account(member: Union[discord.Member, discord.User]) -> Account: if acc_data == {}: acc_data = default - acc_data['name'] = member.display_name + acc_data["name"] = member.display_name try: - acc_data['balance'] = await get_default_balance(member.guild) + acc_data["balance"] = await get_default_balance(member.guild) except AttributeError: - acc_data['balance'] = await get_default_balance() + acc_data["balance"] = await get_default_balance() - acc_data['created_at'] = _decode_time(acc_data['created_at']) + acc_data["created_at"] = _decode_time(acc_data["created_at"]) return Account(**acc_data) @@ -444,7 +453,7 @@ async def set_global(global_: bool) -> bool: return global_ -async def get_bank_name(guild: discord.Guild=None) -> str: +async def get_bank_name(guild: discord.Guild = None) -> str: """Get the current bank name. Parameters @@ -472,7 +481,7 @@ async def get_bank_name(guild: discord.Guild=None) -> str: raise RuntimeError("Guild parameter is required and missing.") -async def set_bank_name(name: str, guild: discord.Guild=None) -> str: +async def set_bank_name(name: str, guild: discord.Guild = None) -> str: """Set the bank name. Parameters @@ -499,12 +508,13 @@ async def set_bank_name(name: str, guild: discord.Guild=None) -> str: elif guild is not None: await _conf.guild(guild).bank_name.set(name) else: - raise RuntimeError("Guild must be provided if setting the name of a guild" - "-specific bank.") + raise RuntimeError( + "Guild must be provided if setting the name of a guild" "-specific bank." + ) return name -async def get_currency_name(guild: discord.Guild=None) -> str: +async def get_currency_name(guild: discord.Guild = None) -> str: """Get the currency name of the bank. Parameters @@ -532,7 +542,7 @@ async def get_currency_name(guild: discord.Guild=None) -> str: raise RuntimeError("Guild must be provided.") -async def set_currency_name(name: str, guild: discord.Guild=None) -> str: +async def set_currency_name(name: str, guild: discord.Guild = None) -> str: """Set the currency name for the bank. Parameters @@ -559,12 +569,13 @@ async def set_currency_name(name: str, guild: discord.Guild=None) -> str: elif guild is not None: await _conf.guild(guild).currency.set(name) else: - raise RuntimeError("Guild must be provided if setting the currency" - " name of a guild-specific bank.") + raise RuntimeError( + "Guild must be provided if setting the currency" " name of a guild-specific bank." + ) return name -async def get_default_balance(guild: discord.Guild=None) -> int: +async def get_default_balance(guild: discord.Guild = None) -> int: """Get the current default balance amount. Parameters @@ -592,7 +603,7 @@ async def get_default_balance(guild: discord.Guild=None) -> int: raise RuntimeError("Guild is missing and required!") -async def set_default_balance(amount: int, guild: discord.Guild=None) -> int: +async def set_default_balance(amount: int, guild: discord.Guild = None) -> int: """Set the default balance amount. Parameters diff --git a/redbot/core/bot.py b/redbot/core/bot.py index d4eaf62c6..51fafd93d 100644 --- a/redbot/core/bot.py +++ b/redbot/core/bot.py @@ -14,15 +14,11 @@ from discord.ext.commands import when_mentioned_or # This supresses the PyNaCl warning that isn't relevant here from discord.voice_client import VoiceClient + VoiceClient.warn_nacl = False from .cog_manager import CogManager -from . import ( - Config, - i18n, - commands, - rpc -) +from . import Config, i18n, commands, rpc from .help_formatter import Help, help as help_ from .sentry import SentryManager from .utils import TYPE_CHECKING @@ -32,6 +28,7 @@ if TYPE_CHECKING: # noinspection PyUnresolvedReferences class RpcMethodMixin: + async def rpc__cogs(self, request): return list(self.cogs.keys()) @@ -48,7 +45,8 @@ class RedBase(BotBase, RpcMethodMixin): Selfbots should inherit from this mixin along with `discord.Client`. """ - def __init__(self, cli_flags, bot_dir: Path=Path.cwd(), **kwargs): + + def __init__(self, cli_flags, bot_dir: Path = Path.cwd(), **kwargs): self._shutdown_mode = ExitCodes.CRITICAL self.db = Config.get_core_conf(force_registration=True) self._co_owners = cli_flags.co_owner @@ -62,22 +60,15 @@ class RedBase(BotBase, RpcMethodMixin): whitelist=[], blacklist=[], enable_sentry=None, - locale='en', - embeds=True + locale="en", + embeds=True, ) self.db.register_guild( - prefix=[], - whitelist=[], - blacklist=[], - admin_role=None, - mod_role=None, - embeds=None + prefix=[], whitelist=[], blacklist=[], admin_role=None, mod_role=None, embeds=None ) - self.db.register_user( - embeds=None - ) + self.db.register_user(embeds=None) async def prefix_manager(bot, message): if not cli_flags.prefix: @@ -88,9 +79,13 @@ class RedBase(BotBase, RpcMethodMixin): return global_prefix server_prefix = await bot.db.guild(message.guild).prefix() if cli_flags.mentionable: - return when_mentioned_or(*server_prefix)(bot, message) \ - if server_prefix else \ - when_mentioned_or(*global_prefix)(bot, message) + return when_mentioned_or(*server_prefix)( + bot, message + ) if server_prefix else when_mentioned_or( + *global_prefix + )( + bot, message + ) else: return server_prefix if server_prefix else global_prefix @@ -109,13 +104,13 @@ class RedBase(BotBase, RpcMethodMixin): self.main_dir = bot_dir - self.cog_mgr = CogManager(paths=(str(self.main_dir / 'cogs'),)) + self.cog_mgr = CogManager(paths=(str(self.main_dir / "cogs"),)) self.register_rpc_methods() super().__init__(formatter=Help(), **kwargs) - self.remove_command('help') + self.remove_command("help") self.add_command(help_) @@ -124,7 +119,7 @@ class RedBase(BotBase, RpcMethodMixin): def enable_sentry(self): """Enable Sentry logging for Red.""" if self._sentry_mgr is None: - sentry_log = logging.getLogger('red.sentry') + sentry_log = logging.getLogger("red.sentry") sentry_log.setLevel(logging.WARNING) self._sentry_mgr = SentryManager(sentry_log) self._sentry_mgr.enable() @@ -143,7 +138,7 @@ class RedBase(BotBase, RpcMethodMixin): :return: """ - indict['owner_id'] = await self.db.owner() + indict["owner_id"] = await self.db.owner() i18n.set_locale(await self.db.locale()) async def embed_requested(self, channel, user, command=None) -> bool: @@ -164,8 +159,9 @@ class RedBase(BotBase, RpcMethodMixin): bool :code:`True` if an embed is requested """ - if isinstance(channel, discord.abc.PrivateChannel) or ( - command and command == self.get_command("help") + if ( + isinstance(channel, discord.abc.PrivateChannel) + or (command and command == self.get_command("help")) ): user_setting = await self.db.user(user).embeds() if user_setting is not None: @@ -214,14 +210,14 @@ class RedBase(BotBase, RpcMethodMixin): curr_pkgs.remove(pkg_name) async def load_extension(self, spec: ModuleSpec): - name = spec.name.split('.')[-1] + name = spec.name.split(".")[-1] if name in self.extensions: return lib = spec.loader.load_module() - if not hasattr(lib, 'setup'): + if not hasattr(lib, "setup"): del lib - raise discord.ClientException('extension does not have a setup function') + raise discord.ClientException("extension does not have a setup function") if asyncio.iscoroutinefunction(lib.setup): await lib.setup(self) @@ -262,7 +258,7 @@ class RedBase(BotBase, RpcMethodMixin): del event_list[index] try: - func = getattr(lib, 'teardown') + func = getattr(lib, "teardown") except AttributeError: pass else: @@ -279,19 +275,20 @@ class RedBase(BotBase, RpcMethodMixin): if m.startswith(pkg_name): del sys.modules[m] - if pkg_name.startswith('redbot.cogs'): - del sys.modules['redbot.cogs'].__dict__[name] + if pkg_name.startswith("redbot.cogs"): + del sys.modules["redbot.cogs"].__dict__[name] def register_rpc_methods(self): - rpc.add_method('bot', self.rpc__cogs) - rpc.add_method('bot', self.rpc__extensions) + rpc.add_method("bot", self.rpc__cogs) + rpc.add_method("bot", self.rpc__extensions) class Red(RedBase, discord.AutoShardedClient): """ You're welcome Caleb. """ - async def shutdown(self, *, restart: bool=False): + + async def shutdown(self, *, restart: bool = False): """Gracefully quit Red. The program will exit with code :code:`0` by default. @@ -314,4 +311,4 @@ class Red(RedBase, discord.AutoShardedClient): class ExitCodes(Enum): CRITICAL = 1 SHUTDOWN = 0 - RESTART = 26 + RESTART = 26 diff --git a/redbot/core/checks.py b/redbot/core/checks.py index 7dd1a3255..4eb54aa50 100644 --- a/redbot/core/checks.py +++ b/redbot/core/checks.py @@ -5,23 +5,22 @@ from discord.ext import commands async def check_overrides(ctx, *, level): if await ctx.bot.is_owner(ctx.author): return True - perm_cog = ctx.bot.get_cog('Permissions') + perm_cog = ctx.bot.get_cog("Permissions") if not perm_cog or ctx.cog == perm_cog: return None # don't break if someone loaded a cog named # permissions that doesn't implement this - func = getattr(perm_cog, 'check_overrides', None) + func = getattr(perm_cog, "check_overrides", None) val = None if func is None else await func(ctx, level) return val def is_owner(**kwargs): + async def check(ctx): - override = await check_overrides(ctx, level='owner') - return ( - override if override is not None - else await ctx.bot.is_owner(ctx.author, **kwargs) - ) + override = await check_overrides(ctx, level="owner") + return (override if override is not None else await ctx.bot.is_owner(ctx.author, **kwargs)) + return commands.check(check) @@ -32,10 +31,7 @@ async def check_permissions(ctx, perms): return False resolved = ctx.channel.permissions_for(ctx.author) - return all( - getattr(resolved, name, None) == value - for name, value in perms.items() - ) + return all(getattr(resolved, name, None) == value for name, value in perms.items()) async def is_mod_or_superior(ctx): @@ -75,47 +71,49 @@ async def is_admin_or_superior(ctx): def mod_or_permissions(**perms): + async def predicate(ctx): - override = await check_overrides(ctx, level='mod') + override = await check_overrides(ctx, level="mod") return ( - override if override is not None - else await check_permissions(ctx, perms) - or await is_mod_or_superior(ctx) + override + if override is not None + else await check_permissions(ctx, perms) or await is_mod_or_superior(ctx) ) return commands.check(predicate) def admin_or_permissions(**perms): + async def predicate(ctx): - override = await check_overrides(ctx, level='admin') + override = await check_overrides(ctx, level="admin") return ( - override if override is not None - else await check_permissions(ctx, perms) - or await is_admin_or_superior(ctx) + override + if override is not None + else await check_permissions(ctx, perms) or await is_admin_or_superior(ctx) ) return commands.check(predicate) def bot_in_a_guild(**kwargs): + async def predicate(ctx): return len(ctx.bot.guilds) > 0 + return commands.check(predicate) def guildowner_or_permissions(**perms): + async def predicate(ctx): has_perms_or_is_owner = await check_permissions(ctx, perms) if ctx.guild is None: return has_perms_or_is_owner is_guild_owner = ctx.author == ctx.guild.owner - override = await check_overrides(ctx, level='guildowner') - return ( - override if override is not None - else is_guild_owner or has_perms_or_is_owner - ) + override = await check_overrides(ctx, level="guildowner") + return (override if override is not None else is_guild_owner or has_perms_or_is_owner) return commands.check(predicate) diff --git a/redbot/core/cli.py b/redbot/core/cli.py index ed4143fcf..ad289e7be 100644 --- a/redbot/core/cli.py +++ b/redbot/core/cli.py @@ -26,16 +26,17 @@ def interactive_config(red, token_set, prefix_set): if not prefix_set: prefix = "" - print("\nPick a prefix. A prefix is what you type before a " - "command. Example:\n" - "!help\n^ The exclamation mark is the prefix in this case.\n" - "Can be multiple characters. You will be able to change it " - "later and add more of them.\nChoose your prefix:\n") + print( + "\nPick a prefix. A prefix is what you type before a " + "command. Example:\n" + "!help\n^ The exclamation mark is the prefix in this case.\n" + "Can be multiple characters. You will be able to change it " + "later and add more of them.\nChoose your prefix:\n" + ) while not prefix: prefix = input("Prefix> ") if len(prefix) > 10: - print("Your prefix seems overly long. Are you sure it " - "is correct? (y/n)") + print("Your prefix seems overly long. Are you sure it " "is correct? (y/n)") if not confirm("> "): prefix = "" if prefix: @@ -48,12 +49,14 @@ def interactive_config(red, token_set, prefix_set): def ask_sentry(red: Red): loop = asyncio.get_event_loop() - print("\nThank you for installing Red V3 beta! The current version\n" - " is not suited for production use and is aimed at testing\n" - " the current and upcoming featureset, that's why we will\n" - " also collect the fatal error logs to help us fix any new\n" - " found issues in a timely manner. If you wish to opt in\n" - " the process please type \"yes\":\n") + print( + "\nThank you for installing Red V3 beta! The current version\n" + " is not suited for production use and is aimed at testing\n" + " the current and upcoming featureset, that's why we will\n" + " also collect the fatal error logs to help us fix any new\n" + " found issues in a timely manner. If you wish to opt in\n" + ' the process please type "yes":\n' + ) if not confirm("> "): loop.run_until_complete(red.db.enable_sentry.set(False)) else: @@ -62,64 +65,82 @@ def ask_sentry(red: Red): def parse_cli_flags(args): - parser = argparse.ArgumentParser(description="Red - Discord Bot", - usage="redbot [arguments]") - parser.add_argument("--version", "-V", action="store_true", - help="Show Red's current version") - parser.add_argument("--list-instances", action="store_true", - help="List all instance names setup " - "with 'redbot-setup'") - parser.add_argument("--owner", type=int, - help="ID of the owner. Only who hosts " - "Red should be owner, this has " - "serious security implications if misused.") - parser.add_argument("--co-owner", type=int, default=[], nargs="*", - help="ID of a co-owner. Only people who have access " - "to the system that is hosting Red should be " - "co-owners, as this gives them complete access " - "to the system's data. This has serious " - "security implications if misused. Can be " - "multiple.") - parser.add_argument("--prefix", "-p", action="append", - help="Global prefix. Can be multiple") - parser.add_argument("--no-prompt", action="store_true", - help="Disables console inputs. Features requiring " - "console interaction could be disabled as a " - "result") - parser.add_argument("--no-cogs", - action="store_true", - help="Starts Red with no cogs loaded, only core") - parser.add_argument("--load-cogs", type=str, nargs="*", - help="Force loading specified cogs from the installed packages. " - "Can be used with the --no-cogs flag to load these cogs exclusively.") - parser.add_argument("--self-bot", - action='store_true', - help="Specifies if Red should log in as selfbot") - parser.add_argument("--not-bot", - action='store_true', - help="Specifies if the token used belongs to a bot " - "account.") - parser.add_argument("--dry-run", - action="store_true", - help="Makes Red quit with code 0 just before the " - "login. This is useful for testing the boot " - "process.") - parser.add_argument("--debug", - action="store_true", - help="Sets the loggers level as debug") - parser.add_argument("--dev", - action="store_true", - help="Enables developer mode") - parser.add_argument("--mentionable", - action="store_true", - help="Allows mentioning the bot as an alternative " - "to using the bot prefix") - parser.add_argument("--rpc", - action="store_true", - help="Enables the built-in RPC server. Please read the docs" - "prior to enabling this!") - parser.add_argument("instance_name", nargs="?", - help="Name of the bot instance created during `redbot-setup`.") + parser = argparse.ArgumentParser( + description="Red - Discord Bot", usage="redbot [arguments]" + ) + parser.add_argument("--version", "-V", action="store_true", help="Show Red's current version") + parser.add_argument( + "--list-instances", + action="store_true", + help="List all instance names setup " "with 'redbot-setup'", + ) + parser.add_argument( + "--owner", + type=int, + help="ID of the owner. Only who hosts " + "Red should be owner, this has " + "serious security implications if misused.", + ) + parser.add_argument( + "--co-owner", + type=int, + default=[], + nargs="*", + help="ID of a co-owner. Only people who have access " + "to the system that is hosting Red should be " + "co-owners, as this gives them complete access " + "to the system's data. This has serious " + "security implications if misused. Can be " + "multiple.", + ) + parser.add_argument("--prefix", "-p", action="append", help="Global prefix. Can be multiple") + parser.add_argument( + "--no-prompt", + action="store_true", + help="Disables console inputs. Features requiring " + "console interaction could be disabled as a " + "result", + ) + parser.add_argument( + "--no-cogs", action="store_true", help="Starts Red with no cogs loaded, only core" + ) + parser.add_argument( + "--load-cogs", + type=str, + nargs="*", + help="Force loading specified cogs from the installed packages. " + "Can be used with the --no-cogs flag to load these cogs exclusively.", + ) + parser.add_argument( + "--self-bot", action="store_true", help="Specifies if Red should log in as selfbot" + ) + parser.add_argument( + "--not-bot", + action="store_true", + help="Specifies if the token used belongs to a bot " "account.", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Makes Red quit with code 0 just before the " + "login. This is useful for testing the boot " + "process.", + ) + parser.add_argument("--debug", action="store_true", help="Sets the loggers level as debug") + parser.add_argument("--dev", action="store_true", help="Enables developer mode") + parser.add_argument( + "--mentionable", + action="store_true", + help="Allows mentioning the bot as an alternative " "to using the bot prefix", + ) + parser.add_argument( + "--rpc", + action="store_true", + help="Enables the built-in RPC server. Please read the docs" "prior to enabling this!", + ) + parser.add_argument( + "instance_name", nargs="?", help="Name of the bot instance created during `redbot-setup`." + ) args = parser.parse_args(args) @@ -129,4 +150,3 @@ def parse_cli_flags(args): args.prefix = [] return args - diff --git a/redbot/core/cog_manager.py b/redbot/core/cog_manager.py index f504ac5de..9266238e2 100644 --- a/redbot/core/cog_manager.py +++ b/redbot/core/cog_manager.py @@ -34,14 +34,12 @@ class CogManager: install new cogs to, the default being the :code:`cogs/` folder in the root bot directory. """ - def __init__(self, paths: Tuple[str]=()): + + def __init__(self, paths: Tuple[str] = ()): self.conf = Config.get_conf(self, 2938473984732, True) tmp_cog_install_path = cog_data_path(self) / "cogs" tmp_cog_install_path.mkdir(parents=True, exist_ok=True) - self.conf.register_global( - paths=(), - install_path=str(tmp_cog_install_path) - ) + self.conf.register_global(paths=(), install_path=str(tmp_cog_install_path)) self._paths = [Path(p) for p in paths] @@ -158,7 +156,7 @@ class CogManager: if path == await self.install_path(): raise ValueError("Cannot add the install path as an additional path.") - all_paths = _deduplicate(await self.paths() + (path, )) + all_paths = _deduplicate(await self.paths() + (path,)) # noinspection PyTypeChecker await self.set_paths(all_paths) @@ -225,8 +223,10 @@ class CogManager: if spec: return spec - raise RuntimeError("No 3rd party module by the name of '{}' was found" - " in any available path.".format(name)) + raise RuntimeError( + "No 3rd party module by the name of '{}' was found" + " in any available path.".format(name) + ) async def _find_core_cog(self, name: str) -> ModuleSpec: """ @@ -247,10 +247,11 @@ class CogManager: """ real_name = ".{}".format(name) try: - mod = import_module(real_name, package='redbot.cogs') + mod = import_module(real_name, package="redbot.cogs") except ImportError as e: - raise RuntimeError("No core cog by the name of '{}' could" - "be found.".format(name)) from e + raise RuntimeError( + "No core cog by the name of '{}' could" "be found.".format(name) + ) from e return mod.__spec__ # noinspection PyUnreachableCode @@ -284,7 +285,7 @@ class CogManager: async def available_modules(self) -> List[str]: """Finds the names of all available modules to load. """ - paths = (await self.install_path(), ) + await self.paths() + paths = (await self.install_path(),) + await self.paths() paths = [str(p) for p in paths] ret = [] @@ -341,8 +342,9 @@ class CogManagerUI: Add a path to the list of available cog paths. """ if not path.is_dir(): - await ctx.send(_("That path does not exist or does not" - " point to a valid directory.")) + await ctx.send( + _("That path does not exist or does not" " point to a valid directory.") + ) return try: @@ -398,7 +400,7 @@ class CogManagerUI: @commands.command() @checks.is_owner() - async def installpath(self, ctx: commands.Context, path: Path=None): + async def installpath(self, ctx: commands.Context, path: Path = None): """ Returns the current install path or sets it if one is provided. The provided path must be absolute or relative to the bot's @@ -416,8 +418,9 @@ class CogManagerUI: return install_path = await ctx.bot.cog_mgr.install_path() - await ctx.send(_("The bot will install new cogs to the `{}`" - " directory.").format(install_path)) + await ctx.send( + _("The bot will install new cogs to the `{}`" " directory.").format(install_path) + ) @commands.command() @checks.is_owner() @@ -435,22 +438,20 @@ class CogManagerUI: unloaded = sorted(list(unloaded), key=str.lower) if await ctx.embed_requested(): - loaded = ('**{} loaded:**\n').format(len(loaded)) + ", ".join(loaded) - unloaded = ('**{} unloaded:**\n').format(len(unloaded)) + ", ".join(unloaded) + loaded = ("**{} loaded:**\n").format(len(loaded)) + ", ".join(loaded) + unloaded = ("**{} unloaded:**\n").format(len(unloaded)) + ", ".join(unloaded) - for page in pagify(loaded, delims=[', ', '\n'], page_length=1800): - e = discord.Embed(description=page, - colour=discord.Colour.dark_green()) + for page in pagify(loaded, delims=[", ", "\n"], page_length=1800): + e = discord.Embed(description=page, colour=discord.Colour.dark_green()) await ctx.send(embed=e) - for page in pagify(unloaded, delims=[', ', '\n'], page_length=1800): - e = discord.Embed(description=page, - colour=discord.Colour.dark_red()) + for page in pagify(unloaded, delims=[", ", "\n"], page_length=1800): + e = discord.Embed(description=page, colour=discord.Colour.dark_red()) await ctx.send(embed=e) else: - loaded_count = '**{} loaded:**\n'.format(len(loaded)) + loaded_count = "**{} loaded:**\n".format(len(loaded)) loaded = ", ".join(loaded) - unloaded_count = '**{} unloaded:**\n'.format(len(unloaded)) + unloaded_count = "**{} unloaded:**\n".format(len(unloaded)) unloaded = ", ".join(unloaded) loaded_count_sent = False unloaded_count_sent = False diff --git a/redbot/core/commands/commands.py b/redbot/core/commands/commands.py index 332441aa0..03a39a1c4 100644 --- a/redbot/core/commands/commands.py +++ b/redbot/core/commands/commands.py @@ -20,7 +20,7 @@ class Command(commands.Command): """ def __init__(self, *args, **kwargs): - self._help_override = kwargs.pop('help_override', None) + self._help_override = kwargs.pop("help_override", None) super().__init__(*args, **kwargs) self.translator = kwargs.pop("i18n", None) @@ -40,7 +40,7 @@ class Command(commands.Command): translator = self.translator command_doc = self.callback.__doc__ if command_doc is None: - return '' + return "" return inspect.cleandoc(translator(command_doc)) @help.setter @@ -60,6 +60,7 @@ class Group(Command, commands.Group): # decorators + def command(name=None, cls=Command, **attrs): """A decorator which transforms an async function into a `Command`. diff --git a/redbot/core/commands/context.py b/redbot/core/commands/context.py index c9674c363..533b43118 100644 --- a/redbot/core/commands/context.py +++ b/redbot/core/commands/context.py @@ -59,10 +59,9 @@ class Context(commands.Context): else: return True - async def send_interactive(self, - messages: Iterable[str], - box_lang: str=None, - timeout: int=15) -> List[discord.Message]: + async def send_interactive( + self, messages: Iterable[str], box_lang: str = None, timeout: int = 15 + ) -> List[discord.Message]: """Send multiple messages interactively. The user will be prompted for whether or not they would like to view @@ -84,9 +83,9 @@ class Context(commands.Context): messages = tuple(messages) ret = [] - more_check = lambda m: (m.author == self.author and - m.channel == self.channel and - m.content.lower() == "more") + more_check = lambda m: ( + m.author == self.author and m.channel == self.channel and m.content.lower() == "more" + ) for idx, page in enumerate(messages, 1): if box_lang is None: @@ -105,10 +104,10 @@ class Context(commands.Context): query = await self.send( "There {} still {} message{} remaining. " "Type `more` to continue." - "".format(is_are, n_remaining, plural)) + "".format(is_are, n_remaining, plural) + ) try: - resp = await self.bot.wait_for( - 'message', check=more_check, timeout=timeout) + resp = await self.bot.wait_for("message", check=more_check, timeout=timeout) except asyncio.TimeoutError: await query.delete() break @@ -134,9 +133,7 @@ class Context(commands.Context): """ if self.guild and not self.channel.permissions_for(self.guild.me).embed_links: return False - return await self.bot.embed_requested( - self.channel, self.author, command=self.command - ) + return await self.bot.embed_requested(self.channel, self.author, command=self.command) async def maybe_send_embed(self, message: str) -> discord.Message: """ diff --git a/redbot/core/config.py b/redbot/core/config.py index e7f9f82ed..b4ec00131 100644 --- a/redbot/core/config.py +++ b/redbot/core/config.py @@ -38,9 +38,11 @@ class _ValueCtxManager: async def __aenter__(self): self.raw_value = await self if not isinstance(self.raw_value, (list, dict)): - raise TypeError("Type of retrieved value must be mutable (i.e. " - "list or dict) in order to use a config value as " - "a context manager.") + raise TypeError( + "Type of retrieved value must be mutable (i.e. " + "list or dict) in order to use a config value as " + "a context manager." + ) return self.raw_value async def __aexit__(self, *exc_info): @@ -61,6 +63,7 @@ class Value: A reference to `Config.driver`. """ + def __init__(self, identifiers: Tuple[str], default_value, driver): self._identifiers = identifiers self.default = default_value @@ -168,10 +171,10 @@ class Group(Value): A reference to `Config.driver`. """ - def __init__(self, identifiers: Tuple[str], - defaults: dict, - driver, - force_registration: bool=False): + + def __init__( + self, identifiers: Tuple[str], defaults: dict, driver, force_registration: bool = False + ): self._defaults = defaults self.force_registration = force_registration self.driver = driver @@ -209,31 +212,22 @@ class Group(Value): """ is_group = self.is_group(item) is_value = not is_group and self.is_value(item) - new_identifiers = self.identifiers + (item, ) + new_identifiers = self.identifiers + (item,) if is_group: return Group( identifiers=new_identifiers, defaults=self._defaults[item], driver=self.driver, - force_registration=self.force_registration + force_registration=self.force_registration, ) elif is_value: return Value( - identifiers=new_identifiers, - default_value=self._defaults[item], - driver=self.driver + identifiers=new_identifiers, default_value=self._defaults[item], driver=self.driver ) elif self.force_registration: - raise AttributeError( - "'{}' is not a valid registered Group " - "or value.".format(item) - ) + raise AttributeError("'{}' is not a valid registered Group " "or value.".format(item)) else: - return Value( - identifiers=new_identifiers, - default_value=None, - driver=self.driver - ) + return Value(identifiers=new_identifiers, default_value=None, driver=self.driver) def is_group(self, item: str) -> bool: """A helper method for `__getattr__`. Most developers will have no need @@ -385,9 +379,7 @@ class Group(Value): async def set(self, value): if not isinstance(value, dict): - raise ValueError( - "You may only set the value of a group to be a dict." - ) + raise ValueError("You may only set the value of a group to be a dict.") await super().set(value) async def set_raw(self, *nested_path: str, value): @@ -456,10 +448,14 @@ class Config: USER = "USER" MEMBER = "MEMBER" - def __init__(self, cog_name: str, unique_identifier: str, - driver: "BaseDriver", - force_registration: bool=False, - defaults: dict=None): + def __init__( + self, + cog_name: str, + unique_identifier: str, + driver: "BaseDriver", + force_registration: bool = False, + defaults: dict = None, + ): self.cog_name = cog_name self.unique_identifier = unique_identifier @@ -472,8 +468,7 @@ class Config: return deepcopy(self._defaults) @classmethod - def get_conf(cls, cog_instance, identifier: int, - force_registration=False, cog_name=None): + def get_conf(cls, cog_instance, identifier: int, force_registration=False, cog_name=None): """Get a Config instance for your cog. .. warning:: @@ -519,20 +514,24 @@ class Config: log.debug("Basic config: \n\n{}".format(basic_config)) - driver_name = basic_config.get('STORAGE_TYPE', 'JSON') - driver_details = basic_config.get('STORAGE_DETAILS', {}) + driver_name = basic_config.get("STORAGE_TYPE", "JSON") + driver_details = basic_config.get("STORAGE_DETAILS", {}) log.debug("Using driver: '{}'".format(driver_name)) - driver = get_driver(driver_name, cog_name, uuid, data_path_override=cog_path_override, - **driver_details) - conf = cls(cog_name=cog_name, unique_identifier=uuid, - force_registration=force_registration, - driver=driver) + driver = get_driver( + driver_name, cog_name, uuid, data_path_override=cog_path_override, **driver_details + ) + conf = cls( + cog_name=cog_name, + unique_identifier=uuid, + force_registration=force_registration, + driver=driver, + ) return conf @classmethod - def get_core_conf(cls, force_registration: bool=False): + def get_core_conf(cls, force_registration: bool = False): """Get a Config instance for a core module. All core modules that require a config instance should use this @@ -549,14 +548,18 @@ class Config: # We have to import this here otherwise we have a circular dependency from .data_manager import basic_config - driver_name = basic_config.get('STORAGE_TYPE', 'JSON') - driver_details = basic_config.get('STORAGE_DETAILS', {}) + driver_name = basic_config.get("STORAGE_TYPE", "JSON") + driver_details = basic_config.get("STORAGE_DETAILS", {}) - driver = get_driver(driver_name, "Core", '0', data_path_override=core_path, - **driver_details) - conf = cls(cog_name="Core", driver=driver, - unique_identifier='0', - force_registration=force_registration) + driver = get_driver( + driver_name, "Core", "0", data_path_override=core_path, **driver_details + ) + conf = cls( + cog_name="Core", + driver=driver, + unique_identifier="0", + force_registration=force_registration, + ) return conf def __getattr__(self, item: str) -> Union[Group, Value]: @@ -593,7 +596,7 @@ class Config: """ ret = {} partial = ret - splitted = key.split('__') + splitted = key.split("__") for i, k in enumerate(splitted, start=1): if not k.isidentifier(): raise RuntimeError("'{}' is an invalid config key.".format(k)) @@ -621,8 +624,9 @@ class Config: existing_is_dict = isinstance(_partial[k], dict) if val_is_dict != existing_is_dict: # != is XOR - raise KeyError("You cannot register a Group and a Value under" - " the same name.") + raise KeyError( + "You cannot register a Group and a Value under" " the same name." + ) if val_is_dict: Config._update_defaults(v, _partial=_partial[k]) else: @@ -736,7 +740,7 @@ class Config: identifiers=(key, *identifiers), defaults=self.defaults.get(key, {}), driver=self.driver, - force_registration=self.force_registration + force_registration=self.force_registration, ) def guild(self, guild: discord.Guild) -> Group: @@ -935,7 +939,7 @@ class Config: ret[int(member_id)] = new_member_data return ret - async def all_members(self, guild: discord.Guild=None) -> dict: + async def all_members(self, guild: discord.Guild = None) -> dict: """Get data for all members. If :code:`guild` is specified, only the data for the members of that @@ -965,8 +969,7 @@ class Config: group = self._get_base_group(self.MEMBER) dict_ = await group() for guild_id, guild_data in dict_.items(): - ret[int(guild_id)] = self._all_members_from_guild( - group, guild_data) + ret[int(guild_id)] = self._all_members_from_guild(group, guild_data) else: group = self._get_base_group(self.MEMBER, guild.id) guild_data = await group() @@ -992,9 +995,7 @@ class Config: """ if not scopes: - group = Group(identifiers=[], - defaults={}, - driver=self.driver) + group = Group(identifiers=[], defaults={}, driver=self.driver) else: group = self._get_base_group(*scopes) await group.clear() @@ -1046,7 +1047,7 @@ class Config: """ await self._clear_scope(self.USER) - async def clear_all_members(self, guild: discord.Guild=None): + async def clear_all_members(self, guild: discord.Guild = None): """Clear all member data. This resets all specified member data to its registered defaults. diff --git a/redbot/core/core_commands.py b/redbot/core/core_commands.py index 2fb2a0fac..3fda444ec 100644 --- a/redbot/core/core_commands.py +++ b/redbot/core/core_commands.py @@ -32,10 +32,12 @@ __all__ = ["Core"] log = logging.getLogger("red") -OWNER_DISCLAIMER = ("⚠ **Only** the person who is hosting Red should be " - "owner. **This has SERIOUS security implications. The " - "owner can access any data that is present on the host " - "system.** ⚠") +OWNER_DISCLAIMER = ( + "⚠ **Only** the person who is hosting Red should be " + "owner. **This has SERIOUS security implications. The " + "owner can access any data that is present on the host " + "system.** ⚠" +) _ = i18n.Translator("Core", __file__) @@ -44,12 +46,13 @@ _ = i18n.Translator("Core", __file__) @i18n.cog_i18n(_) class Core: """Commands related to core functions""" + def __init__(self, bot): self.bot = bot # type: Red - rpc.add_method('core', self.rpc_load) - rpc.add_method('core', self.rpc_unload) - rpc.add_method('core', self.rpc_reload) + rpc.add_method("core", self.rpc_load) + rpc.add_method("core", self.rpc_unload) + rpc.add_method("core", self.rpc_reload) @commands.command(hidden=True) async def ping(self, ctx): @@ -72,15 +75,13 @@ class Core: since = datetime.datetime(2016, 1, 2, 0, 0) days_since = (datetime.datetime.utcnow() - since).days dpy_version = "[{}]({})".format(discord.__version__, dpy_repo) - python_version = "[{}.{}.{}]({})".format( - *sys.version_info[:3], python_url - ) + python_version = "[{}.{}.{}]({})".format(*sys.version_info[:3], python_url) red_version = "[{}]({})".format(__version__, red_pypi) app_info = await self.bot.application_info() owner = app_info.owner async with aiohttp.ClientSession() as session: - async with session.get('{}/json'.format(red_pypi)) as r: + async with session.get("{}/json".format(red_pypi)) as r: data = await r.json() outdated = StrictVersion(data["info"]["version"]) > StrictVersion(__version__) about = ( @@ -89,7 +90,8 @@ class Core: "Red is backed by a passionate community who contributes and " "creates content for everyone to enjoy. [Join us today]({}) " "and help us improve!\n\n" - "".format(red_repo, author_repo, org_repo, support_server_url)) + "".format(red_repo, author_repo, org_repo, support_server_url) + ) embed = discord.Embed(color=discord.Color.red()) embed.add_field(name="Instance owned by", value=str(owner)) @@ -97,14 +99,14 @@ class Core: embed.add_field(name="discord.py", value=dpy_version) embed.add_field(name="Red version", value=red_version) if outdated: - embed.add_field(name="Outdated", value="Yes, {} is available".format( - data["info"]["version"] - ) + embed.add_field( + name="Outdated", value="Yes, {} is available".format(data["info"]["version"]) ) embed.add_field(name="About Red", value=about, inline=False) - embed.set_footer(text="Bringing joy since 02 Jan 2016 (over " - "{} days ago!)".format(days_since)) + embed.set_footer( + text="Bringing joy since 02 Jan 2016 (over " "{} days ago!)".format(days_since) + ) try: await ctx.send(embed=embed) except discord.HTTPException: @@ -115,11 +117,7 @@ class Core: """Shows Red's uptime""" since = ctx.bot.uptime.strftime("%Y-%m-%d %H:%M:%S") passed = self.get_bot_uptime() - await ctx.send( - "Been up for: **{}** (since {} UTC)".format( - passed, since - ) - ) + await ctx.send("Been up for: **{}** (since {} UTC)".format(passed, since)) def get_bot_uptime(self, *, brief=False): # Courtesy of Danny @@ -131,13 +129,13 @@ class Core: if not brief: if days: - fmt = '{d} days, {h} hours, {m} minutes, and {s} seconds' + fmt = "{d} days, {h} hours, {m} minutes, and {s} seconds" else: - fmt = '{h} hours, {m} minutes, and {s} seconds' + fmt = "{h} hours, {m} minutes, and {s} seconds" else: - fmt = '{h}h {m}m {s}s' + fmt = "{h}h {m}m {s}s" if days: - fmt = '{d}d ' + fmt + fmt = "{d}d " + fmt return fmt.format(d=days, h=hours, m=minutes, s=seconds) @@ -176,14 +174,12 @@ class Core: current = await self.bot.db.embeds() await self.bot.db.embeds.set(not current) await ctx.send( - _("Embeds are now {} by default.").format( - "disabled" if current else "enabled" - ) + _("Embeds are now {} by default.").format("disabled" if current else "enabled") ) @embedset.command(name="guild") @checks.guildowner_or_permissions(administrator=True) - async def embedset_guild(self, ctx: commands.Context, enabled: bool=None): + async def embedset_guild(self, ctx: commands.Context, enabled: bool = None): """ Toggle the guild's embed setting. @@ -197,18 +193,14 @@ class Core: """ await self.bot.db.guild(ctx.guild).embeds.set(enabled) if enabled is None: - await ctx.send( - _("Embeds will now fall back to the global setting.") - ) + await ctx.send(_("Embeds will now fall back to the global setting.")) else: await ctx.send( - _("Embeds are now {} for this guild.").format( - "enabled" if enabled else "disabled" - ) + _("Embeds are now {} for this guild.").format("enabled" if enabled else "disabled") ) @embedset.command(name="user") - async def embedset_user(self, ctx: commands.Context, enabled: bool=None): + async def embedset_user(self, ctx: commands.Context, enabled: bool = None): """ Toggle the user's embed setting. @@ -222,19 +214,15 @@ class Core: """ await self.bot.db.user(ctx.author).embeds.set(enabled) if enabled is None: - await ctx.send( - _("Embeds will now fall back to the global setting.") - ) + await ctx.send(_("Embeds will now fall back to the global setting.")) else: await ctx.send( - _("Embeds are now {} for you.").format( - "enabled" if enabled else "disabled" - ) + _("Embeds are now {} for you.").format("enabled" if enabled else "disabled") ) @commands.command() @checks.is_owner() - async def traceback(self, ctx, public: bool=False): + async def traceback(self, ctx, public: bool = False): """Sends to the owner the last command exception that has occurred If public (yes is specified), it will be sent to the chat instead""" @@ -267,8 +255,7 @@ class Core: author = ctx.author guild = ctx.guild - await ctx.send("Are you sure you want me to leave this server?" - " Type yes to confirm.") + await ctx.send("Are you sure you want me to leave this server?" " Type yes to confirm.") def conf_check(m): return m.author == author @@ -285,15 +272,14 @@ class Core: async def servers(self, ctx): """Lists and allows to leave servers""" owner = ctx.author - guilds = sorted(list(self.bot.guilds), - key=lambda s: s.name.lower()) + guilds = sorted(list(self.bot.guilds), key=lambda s: s.name.lower()) msg = "" for i, server in enumerate(guilds, 1): msg += "{}: {}\n".format(i, server.name) msg += "\nTo leave a server, just type its number." - for page in pagify(msg, ['\n']): + for page in pagify(msg, ["\n"]): await ctx.send(page) def msg_check(m): @@ -343,7 +329,7 @@ class Core: loaded_packages = [] notfound_packages = [] - cognames = [c.strip() for c in cog_name.split(' ')] + cognames = [c.strip() for c in cog_name.split(" ")] cogspecs = [] for c in cognames: @@ -352,20 +338,22 @@ class Core: cogspecs.append((spec, c)) except RuntimeError: notfound_packages.append(inline(c)) - #await ctx.send(_("No module named '{}' was found in any" + # await ctx.send(_("No module named '{}' was found in any" # " cog path.").format(c)) if len(cogspecs) > 0: - for spec, name in cogspecs: + for spec, name in cogspecs: try: await ctx.bot.load_extension(spec) except Exception as e: log.exception("Package loading failed", exc_info=e) - exception_log = ("Exception in command '{}'\n" - "".format(ctx.command.qualified_name)) - exception_log += "".join(traceback.format_exception(type(e), - e, e.__traceback__)) + exception_log = ( + "Exception in command '{}'\n" "".format(ctx.command.qualified_name) + ) + exception_log += "".join( + traceback.format_exception(type(e), e, e.__traceback__) + ) self.bot._last_exception = exception_log failed_packages.append(inline(name)) else: @@ -378,21 +366,23 @@ class Core: await ctx.send(_(formed)) if failed_packages: - fmt = ("Failed to load package{plural} {packs}. Check your console or " - "logs for details.") + fmt = ( + "Failed to load package{plural} {packs}. Check your console or " + "logs for details." + ) formed = self.get_package_strings(failed_packages, fmt) await ctx.send(_(formed)) if notfound_packages: - fmt = 'The package{plural} {packs} {other} not found in any cog path.' - formed = self.get_package_strings(notfound_packages, fmt, ('was', 'were')) + fmt = "The package{plural} {packs} {other} not found in any cog path." + formed = self.get_package_strings(notfound_packages, fmt, ("was", "were")) await ctx.send(_(formed)) @commands.group() @checks.is_owner() async def unload(self, ctx, *, cog_name: str): """Unloads packages""" - cognames = [c.strip() for c in cog_name.split(' ')] + cognames = [c.strip() for c in cog_name.split(" ")] failed_packages = [] unloaded_packages = [] @@ -406,12 +396,12 @@ class Core: if unloaded_packages: fmt = "Package{plural} {packs} {other} unloaded." - formed = self.get_package_strings(unloaded_packages, fmt, ('was', 'were')) + formed = self.get_package_strings(unloaded_packages, fmt, ("was", "were")) await ctx.send(_(formed)) if failed_packages: fmt = "The package{plural} {packs} {other} not loaded." - formed = self.get_package_strings(failed_packages, fmt, ('is', 'are')) + formed = self.get_package_strings(failed_packages, fmt, ("is", "are")) await ctx.send(_(formed)) @commands.command(name="reload") @@ -419,7 +409,7 @@ class Core: async def _reload(self, ctx, *, cog_name: str): """Reloads packages""" - cognames = [c.strip() for c in cog_name.split(' ')] + cognames = [c.strip() for c in cog_name.split(" ")] for c in cognames: ctx.bot.unload_extension(c) @@ -444,50 +434,46 @@ class Core: except Exception as e: log.exception("Package reloading failed", exc_info=e) - exception_log = ("Exception in command '{}'\n" - "".format(ctx.command.qualified_name)) - exception_log += "".join(traceback.format_exception(type(e), - e, e.__traceback__)) + exception_log = ( + "Exception in command '{}'\n" "".format(ctx.command.qualified_name) + ) + exception_log += "".join(traceback.format_exception(type(e), e, e.__traceback__)) self.bot._last_exception = exception_log failed_packages.append(inline(name)) if loaded_packages: fmt = "Package{plural} {packs} {other} reloaded." - formed = self.get_package_strings(loaded_packages, fmt, ('was', 'were')) + formed = self.get_package_strings(loaded_packages, fmt, ("was", "were")) await ctx.send(_(formed)) if failed_packages: - fmt = ("Failed to reload package{plural} {packs}. Check your " - "logs for details") + fmt = ("Failed to reload package{plural} {packs}. Check your " "logs for details") formed = self.get_package_strings(failed_packages, fmt) await ctx.send(_(formed)) if notfound_packages: - fmt = 'The package{plural} {packs} {other} not found in any cog path.' - formed = self.get_package_strings(notfound_packages, fmt, ('was', 'were')) + fmt = "The package{plural} {packs} {other} not found in any cog path." + formed = self.get_package_strings(notfound_packages, fmt, ("was", "were")) await ctx.send(_(formed)) - def get_package_strings(self, packages: list, fmt: str, other: tuple=None): + def get_package_strings(self, packages: list, fmt: str, other: tuple = None): """ Gets the strings needed for the load, unload and reload commands """ if other is None: - other = ('', '') - plural = 's' if len(packages) > 1 else '' - use_and, other = ('', other[0]) if len(packages) == 1 else (' and ', other[1]) - packages_string = ', '.join(packages[:-1]) + use_and + packages[-1] + other = ("", "") + plural = "s" if len(packages) > 1 else "" + use_and, other = ("", other[0]) if len(packages) == 1 else (" and ", other[1]) + packages_string = ", ".join(packages[:-1]) + use_and + packages[-1] - form = {'plural': plural, - 'packs' : packages_string, - 'other' : other - } + form = {"plural": plural, "packs": packages_string, "other": other} final_string = fmt.format(**form) return final_string @commands.command(name="shutdown") @checks.is_owner() - async def _shutdown(self, ctx, silently: bool=False): + async def _shutdown(self, ctx, silently: bool = False): """Shuts down the bot""" wave = "\N{WAVING HAND SIGN}" skin = "\N{EMOJI MODIFIER FITZPATRICK TYPE-3}" @@ -500,7 +486,7 @@ class Core: @commands.command(name="restart") @checks.is_owner() - async def _restart(self, ctx, silently: bool=False): + async def _restart(self, ctx, silently: bool = False): """Attempts to restart Red Makes Red quit with exit code 26 @@ -515,7 +501,7 @@ class Core: def cleanup_and_refresh_modules(self, module_name: str): """Interally reloads modules so that changes are detected""" - splitted = module_name.split('.') + splitted = module_name.split(".") def maybe_reload(new_name): try: @@ -553,9 +539,11 @@ class Core: "Mod role: {}\n" "Locale: {}" "".format( - ctx.bot.user.name, " ".join(prefixes), + ctx.bot.user.name, + " ".join(prefixes), admin_role.name if admin_role else "Not set", - mod_role.name if mod_role else "Not set", locale + mod_role.name if mod_role else "Not set", + locale, ) ) await ctx.send(box(settings)) @@ -588,9 +576,13 @@ class Core: try: await ctx.bot.user.edit(avatar=data) except discord.HTTPException: - await ctx.send(_("Failed. Remember that you can edit my avatar " - "up to two times a hour. The URL must be a " - "direct link to a JPG / PNG.")) + await ctx.send( + _( + "Failed. Remember that you can edit my avatar " + "up to two times a hour. The URL must be a " + "direct link to a JPG / PNG." + ) + ) except discord.InvalidArgument: await ctx.send(_("JPG / PNG format only.")) else: @@ -599,26 +591,24 @@ class Core: @_set.command(name="game") @checks.bot_in_a_guild() @checks.is_owner() - async def _game(self, ctx, *, game: str=None): + async def _game(self, ctx, *, game: str = None): """Sets Red's playing status""" if game: game = discord.Game(name=game) else: game = None - status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 \ - else discord.Status.online + status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online await ctx.bot.change_presence(status=status, activity=game) await ctx.send(_("Game set.")) @_set.command(name="listening") @checks.bot_in_a_guild() @checks.is_owner() - async def _listening(self, ctx, *, listening: str=None): + async def _listening(self, ctx, *, listening: str = None): """Sets Red's listening status""" - status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 \ - else discord.Status.online + status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online if listening: activity = discord.Activity(name=listening, type=discord.ActivityType.listening) else: @@ -629,11 +619,10 @@ class Core: @_set.command(name="watching") @checks.bot_in_a_guild() @checks.is_owner() - async def _watching(self, ctx, *, watching: str=None): + async def _watching(self, ctx, *, watching: str = None): """Sets Red's watching status""" - status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 \ - else discord.Status.online + status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online if watching: activity = discord.Activity(name=watching, type=discord.ActivityType.watching) else: @@ -658,7 +647,7 @@ class Core: "online": discord.Status.online, "idle": discord.Status.idle, "dnd": discord.Status.dnd, - "invisible": discord.Status.invisible + "invisible": discord.Status.invisible, } game = ctx.bot.guilds[0].me.activity if len(ctx.bot.guilds) > 0 else None @@ -677,8 +666,7 @@ class Core: """Sets Red's streaming status Leaving both streamer and stream_title empty will clear it.""" - status = ctx.bot.guilds[0].me.status \ - if len(ctx.bot.guilds) > 0 else None + status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else None if stream_title: stream_title = stream_title.strip() @@ -700,23 +688,28 @@ class Core: try: await ctx.bot.user.edit(username=username) except discord.HTTPException: - await ctx.send(_("Failed to change name. Remember that you can " - "only do it up to 2 times an hour. Use " - "nicknames if you need frequent changes. " - "`{}set nickname`").format(ctx.prefix)) + await ctx.send( + _( + "Failed to change name. Remember that you can " + "only do it up to 2 times an hour. Use " + "nicknames if you need frequent changes. " + "`{}set nickname`" + ).format( + ctx.prefix + ) + ) else: await ctx.send(_("Done.")) @_set.command(name="nickname") @checks.admin() @commands.guild_only() - async def _nickname(self, ctx, *, nickname: str=None): + async def _nickname(self, ctx, *, nickname: str = None): """Sets Red's nickname""" try: await ctx.guild.me.edit(nick=nickname) except discord.Forbidden: - await ctx.send(_("I lack the permissions to change my own " - "nickname.")) + await ctx.send(_("I lack the permissions to change my own " "nickname.")) else: await ctx.send("Done.") @@ -748,6 +741,7 @@ class Core: @commands.cooldown(1, 60 * 10, commands.BucketType.default) async def owner(self, ctx): """Sets Red's main owner""" + def check(m): return m.author == ctx.author and m.channel == ctx.channel @@ -759,20 +753,22 @@ class Core: for i in range(length): token += random.choice(chars) - log.info("{0} ({0.id}) requested to be set as owner." - "".format(ctx.author)) + log.info("{0} ({0.id}) requested to be set as owner." "".format(ctx.author)) print(_("\nVerification token:")) print(token) await ctx.send(_("Remember:\n") + OWNER_DISCLAIMER) await asyncio.sleep(5) - await ctx.send(_("I have printed a one-time token in the console. " - "Copy and paste it here to confirm you are the owner.")) + await ctx.send( + _( + "I have printed a one-time token in the console. " + "Copy and paste it here to confirm you are the owner." + ) + ) try: - message = await ctx.bot.wait_for("message", check=check, - timeout=60) + message = await ctx.bot.wait_for("message", check=check, timeout=60) except asyncio.TimeoutError: self.owner.reset_cooldown(ctx) await ctx.send(_("The set owner request has timed out.")) @@ -798,10 +794,15 @@ class Core: pass await ctx.send( - _("Please use that command in DM. Since users probably saw your token," - " it is recommended to reset it right now. Go to the following link and" - " select `Reveal Token` and `Generate a new token?`." - "\n\nhttps://discordapp.com/developers/applications/me/{}").format(self.bot.user.id)) + _( + "Please use that command in DM. Since users probably saw your token," + " it is recommended to reset it right now. Go to the following link and" + " select `Reveal Token` and `Generate a new token?`." + "\n\nhttps://discordapp.com/developers/applications/me/{}" + ).format( + self.bot.user.id + ) + ) return await ctx.bot.db.token.set(token) @@ -854,9 +855,7 @@ class Core: locale_list = sorted(set([loc.stem for loc in list(red_path.glob("**/*.po"))])) pages = pagify("\n".join(locale_list)) - await ctx.send_interactive( - pages, box_lang="Available Locales:" - ) + await ctx.send_interactive(pages, box_lang="Available Locales:") @commands.command() @checks.is_owner() @@ -864,9 +863,11 @@ class Core: """Creates a backup of all data for the instance.""" from redbot.core.data_manager import basic_config, instance_name from redbot.core.drivers.red_json import JSON + data_dir = Path(basic_config["DATA_PATH"]) if basic_config["STORAGE_TYPE"] == "MongoDB": from redbot.core.drivers.red_mongo import Mongo + m = Mongo("Core", **basic_config["STORAGE_DETAILS"]) db = m.db collection_names = await db.collection_names(include_system_collections=False) @@ -891,9 +892,9 @@ class Core: os.chdir(str(data_dir.parent)) with tarfile.open(str(backup_file), "w:gz") as tar: tar.add(data_dir.stem) - await ctx.send(_("A backup has been made of this instance. It is at {}.").format( - backup_file - )) + await ctx.send( + _("A backup has been made of this instance. It is at {}.").format(backup_file) + ) else: await ctx.send(_("That directory doesn't seem to exist...")) @@ -902,8 +903,7 @@ class Core: async def contact(self, ctx, *, message: str): """Sends a message to the owner""" guild = ctx.message.guild - owner = discord.utils.get(ctx.bot.get_all_members(), - id=ctx.bot.owner_id) + owner = discord.utils.get(ctx.bot.get_all_members(), id=ctx.bot.owner_id) author = ctx.message.author footer = _("User ID: {}").format(author.id) @@ -916,12 +916,11 @@ class Core: # We need to grab the DM command prefix (global) # Since it can also be set through cli flags, bot.db is not a reliable # source. So we'll just mock a DM message instead. - fake_message = namedtuple('Message', 'guild') + fake_message = namedtuple("Message", "guild") prefixes = await ctx.bot.command_prefix(ctx.bot, fake_message(guild=None)) prefix = prefixes[0] - content = _("Use `{}dm {} ` to reply to this user" - "").format(prefix, author.id) + content = _("Use `{}dm {} ` to reply to this user" "").format(prefix, author.id) description = _("Sent by {} {}").format(author, source) @@ -941,21 +940,21 @@ class Core: try: await owner.send(content, embed=e) except discord.InvalidArgument: - await ctx.send(_("I cannot send your message, I'm unable to find " - "my owner... *sigh*")) + await ctx.send( + _("I cannot send your message, I'm unable to find " "my owner... *sigh*") + ) except: await ctx.send(_("I'm unable to deliver your message. Sorry.")) else: await ctx.send(_("Your message has been sent.")) else: - msg_text = ( - "{}\nMessage:\n\n{}\n{}".format(description, message, footer) - ) + msg_text = ("{}\nMessage:\n\n{}\n{}".format(description, message, footer)) try: await owner.send("{}\n{}".format(content, box(msg_text))) except discord.InvalidArgument: - await ctx.send(_("I cannot send your message, I'm unable to find " - "my owner... *sigh*")) + await ctx.send( + _("I cannot send your message, I'm unable to find " "my owner... *sigh*") + ) except: await ctx.send(_("I'm unable to deliver your message. Sorry.")) else: @@ -970,15 +969,18 @@ class Core: To get a user id enable 'developer mode' in Discord's settings, 'appearance' tab. Then right click a user and copy their id""" - destination = discord.utils.get(ctx.bot.get_all_members(), - id=user_id) + destination = discord.utils.get(ctx.bot.get_all_members(), id=user_id) if destination is None: - await ctx.send(_("Invalid ID or user not found. You can only " - "send messages to people I share a server " - "with.")) + await ctx.send( + _( + "Invalid ID or user not found. You can only " + "send messages to people I share a server " + "with." + ) + ) return - fake_message = namedtuple('Message', 'guild') + fake_message = namedtuple("Message", "guild") prefixes = await ctx.bot.command_prefix(ctx.bot, fake_message(guild=None)) prefix = prefixes[0] description = _("Owner of {}").format(ctx.bot.user) @@ -995,8 +997,9 @@ class Core: try: await destination.send(embed=e) except: - await ctx.send(_("Sorry, I couldn't deliver your message " - "to {}").format(destination)) + await ctx.send( + _("Sorry, I couldn't deliver your message " "to {}").format(destination) + ) else: await ctx.send(_("Message delivered to {}").format(destination)) else: @@ -1004,8 +1007,9 @@ class Core: try: await destination.send("{}\n{}".format(box(response), content)) except: - await ctx.send(_("Sorry, I couldn't deliver your message " - "to {}").format(destination)) + await ctx.send( + _("Sorry, I couldn't deliver your message " "to {}").format(destination) + ) else: await ctx.send(_("Message delivered to {}").format(destination)) @@ -1018,7 +1022,7 @@ class Core: if ctx.invoked_subcommand is None: await ctx.send_help() - @whitelist.command(name='add') + @whitelist.command(name="add") async def whitelist_add(self, ctx, user: discord.User): """ Adds a user to the whitelist. @@ -1029,7 +1033,7 @@ class Core: await ctx.send(_("User added to whitelist.")) - @whitelist.command(name='list') + @whitelist.command(name="list") async def whitelist_list(self, ctx): """ Lists whitelisted users. @@ -1043,7 +1047,7 @@ class Core: for page in pagify(msg): await ctx.send(box(page)) - @whitelist.command(name='remove') + @whitelist.command(name="remove") async def whitelist_remove(self, ctx, user: discord.User): """ Removes user from whitelist. @@ -1060,7 +1064,7 @@ class Core: else: await ctx.send(_("User was not in the whitelist.")) - @whitelist.command(name='clear') + @whitelist.command(name="clear") async def whitelist_clear(self, ctx): """ Clears the whitelist. @@ -1077,7 +1081,7 @@ class Core: if ctx.invoked_subcommand is None: await ctx.send_help() - @blacklist.command(name='add') + @blacklist.command(name="add") async def blacklist_add(self, ctx, user: discord.User): """ Adds a user to the blacklist. @@ -1092,7 +1096,7 @@ class Core: await ctx.send(_("User added to blacklist.")) - @blacklist.command(name='list') + @blacklist.command(name="list") async def blacklist_list(self, ctx): """ Lists blacklisted users. @@ -1106,7 +1110,7 @@ class Core: for page in pagify(msg): await ctx.send(box(page)) - @blacklist.command(name='remove') + @blacklist.command(name="remove") async def blacklist_remove(self, ctx, user: discord.User): """ Removes user from blacklist. @@ -1123,7 +1127,7 @@ class Core: else: await ctx.send(_("User was not in the blacklist.")) - @blacklist.command(name='clear') + @blacklist.command(name="clear") async def blacklist_clear(self, ctx): """ Clears the blacklist. diff --git a/redbot/core/data_manager.py b/redbot/core/data_manager.py index 8a56b5c53..6c39d9017 100644 --- a/redbot/core/data_manager.py +++ b/redbot/core/data_manager.py @@ -14,9 +14,15 @@ from .utils import TYPE_CHECKING if TYPE_CHECKING: from . import Config -__all__ = ['load_basic_configuration', 'cog_data_path', 'core_data_path', - 'load_bundled_data', 'bundled_data_path', 'storage_details', - 'storage_type'] +__all__ = [ + "load_basic_configuration", + "cog_data_path", + "core_data_path", + "load_bundled_data", + "bundled_data_path", + "storage_details", + "storage_type", +] log = logging.getLogger("red.data_manager") @@ -25,20 +31,16 @@ basic_config = None instance_name = None -basic_config_default = { - "DATA_PATH": None, - "COG_PATH_APPEND": "cogs", - "CORE_PATH_APPEND": "core" -} +basic_config_default = {"DATA_PATH": None, "COG_PATH_APPEND": "cogs", "CORE_PATH_APPEND": "core"} config_dir = None appdir = appdirs.AppDirs("Red-DiscordBot") -if sys.platform == 'linux': +if sys.platform == "linux": if 0 < os.getuid() < 1000: config_dir = Path(appdir.site_data_dir) if not config_dir: config_dir = Path(appdir.user_config_dir) -config_file = config_dir / 'config.json' +config_file = config_dir / "config.json" def load_basic_configuration(instance_name_: str): @@ -67,20 +69,23 @@ def load_basic_configuration(instance_name_: str): config = jsonio._load_json() basic_config = config[instance_name] except (FileNotFoundError, KeyError): - print("You need to configure the bot instance using `redbot-setup`" - " prior to running the bot.") + print( + "You need to configure the bot instance using `redbot-setup`" + " prior to running the bot." + ) sys.exit(1) def _base_data_path() -> Path: if basic_config is None: - raise RuntimeError("You must load the basic config before you" - " can get the base data path.") - path = basic_config['DATA_PATH'] + raise RuntimeError( + "You must load the basic config before you" " can get the base data path." + ) + path = basic_config["DATA_PATH"] return Path(path).resolve() -def cog_data_path(cog_instance=None, raw_name: str=None) -> Path: +def cog_data_path(cog_instance=None, raw_name: str = None) -> Path: """Gets the base cog data path. If you want to get the folder with which to store your own cog's data please pass in an instance of your cog class. @@ -104,9 +109,10 @@ def cog_data_path(cog_instance=None, raw_name: str=None) -> Path: try: base_data_path = Path(_base_data_path()) except RuntimeError as e: - raise RuntimeError("You must load the basic config before you" - " can get the cog data path.") from e - cog_path = base_data_path / basic_config['COG_PATH_APPEND'] + raise RuntimeError( + "You must load the basic config before you" " can get the cog data path." + ) from e + cog_path = base_data_path / basic_config["COG_PATH_APPEND"] if raw_name is not None: cog_path = cog_path / raw_name @@ -121,9 +127,10 @@ def core_data_path() -> Path: try: base_data_path = Path(_base_data_path()) except RuntimeError as e: - raise RuntimeError("You must load the basic config before you" - " can get the core data path.") from e - core_path = base_data_path / basic_config['CORE_PATH_APPEND'] + raise RuntimeError( + "You must load the basic config before you" " can get the core data path." + ) from e + core_path = base_data_path / basic_config["CORE_PATH_APPEND"] core_path.mkdir(exist_ok=True, parents=True) return core_path.resolve() @@ -145,15 +152,13 @@ def _find_data_files(init_location: str) -> (Path, List[Path]): if not init_file.is_file(): return [] - package_folder = init_file.parent.resolve() / 'data' + package_folder = init_file.parent.resolve() / "data" if not package_folder.is_dir(): return [] all_files = list(package_folder.rglob("*")) - return package_folder, [p.resolve() - for p in all_files - if p.is_file()] + return package_folder, [p.resolve() for p in all_files if p.is_file()] def _compare_and_copy(to_copy: List[Path], bundled_data_dir: Path, cog_data_dir: Path): @@ -181,27 +186,24 @@ def _compare_and_copy(to_copy: List[Path], bundled_data_dir: Path, cog_data_dir: yield block block = afile.read(blocksize) - lookup = {p: cog_data_dir.joinpath(p.relative_to(bundled_data_dir)) - for p in to_copy} + lookup = {p: cog_data_dir.joinpath(p.relative_to(bundled_data_dir)) for p in to_copy} for orig, poss_existing in lookup.items(): if not poss_existing.is_file(): poss_existing.parent.mkdir(exist_ok=True, parents=True) exists_checksum = None else: - exists_checksum = hash_bytestr_iter(file_as_blockiter( - poss_existing.open('rb')), hashlib.sha256()) + exists_checksum = hash_bytestr_iter( + file_as_blockiter(poss_existing.open("rb")), hashlib.sha256() + ) orig_checksum = ... if exists_checksum is not None: - orig_checksum = hash_bytestr_iter(file_as_blockiter( - orig.open('rb')), hashlib.sha256()) + orig_checksum = hash_bytestr_iter(file_as_blockiter(orig.open("rb")), hashlib.sha256()) if exists_checksum != orig_checksum: shutil.copy(str(orig), str(poss_existing)) - log.debug("Copying {} to {}".format( - orig, poss_existing - )) + log.debug("Copying {} to {}".format(orig, poss_existing)) def load_bundled_data(cog_instance, init_location: str): @@ -233,7 +235,7 @@ def load_bundled_data(cog_instance, init_location: str): """ bundled_data_folder, to_copy = _find_data_files(init_location) - cog_data_folder = cog_data_path(cog_instance) / 'bundled_data' + cog_data_folder = cog_data_path(cog_instance) / "bundled_data" _compare_and_copy(to_copy, bundled_data_folder, cog_data_folder) @@ -264,12 +266,10 @@ def bundled_data_path(cog_instance) -> Path: If no bundled data folder exists or if it hasn't been loaded yet. """ - bundled_path = cog_data_path(cog_instance) / 'bundled_data' + bundled_path = cog_data_path(cog_instance) / "bundled_data" if not bundled_path.is_dir(): - raise FileNotFoundError("No such directory {}".format( - bundled_path - )) + raise FileNotFoundError("No such directory {}".format(bundled_path)) return bundled_path @@ -282,9 +282,9 @@ def storage_type() -> str: str """ try: - return basic_config['STORAGE_TYPE'] + return basic_config["STORAGE_TYPE"] except KeyError as e: - raise RuntimeError('Bot basic config has not been loaded yet.') from e + raise RuntimeError("Bot basic config has not been loaded yet.") from e def storage_details() -> dict: @@ -297,6 +297,6 @@ def storage_details() -> dict: dict """ try: - return basic_config['STORAGE_DETAILS'] + return basic_config["STORAGE_DETAILS"] except KeyError as e: - raise RuntimeError('Bot basic config has not been loaded yet.') from e + raise RuntimeError("Bot basic config has not been loaded yet.") from e diff --git a/redbot/core/dev_commands.py b/redbot/core/dev_commands.py index 5c40522e1..1f67f2710 100644 --- a/redbot/core/dev_commands.py +++ b/redbot/core/dev_commands.py @@ -10,6 +10,7 @@ import discord from . import checks, commands from .i18n import Translator from .utils.chat_formatting import box, pagify + """ Notice: @@ -32,11 +33,11 @@ class Dev: def cleanup_code(content): """Automatically removes code blocks from the code.""" # remove ```py\n``` - if content.startswith('```') and content.endswith('```'): - return '\n'.join(content.split('\n')[1:-1]) + if content.startswith("```") and content.endswith("```"): + return "\n".join(content.split("\n")[1:-1]) # remove `foo` - return content.strip('` \n') + return content.strip("` \n") @staticmethod def get_syntax_error(e): @@ -45,11 +46,10 @@ class Dev: Returns a string representation of the error formatted as a codeblock. """ if e.text is None: - return box('{0.__class__.__name__}: {0}'.format(e), lang="py") + return box("{0.__class__.__name__}: {0}".format(e), lang="py") return box( - '{0.text}{1:>{0.offset}}\n{2}: {0}' - ''.format(e, '^', type(e).__name__), - lang="py") + "{0.text}{1:>{0.offset}}\n{2}: {0}" "".format(e, "^", type(e).__name__), lang="py" + ) @staticmethod def get_pages(msg: str): @@ -90,15 +90,15 @@ class Dev: _ - The result of the last dev command. """ env = { - 'bot': ctx.bot, - 'ctx': ctx, - 'channel': ctx.channel, - 'author': ctx.author, - 'guild': ctx.guild, - 'message': ctx.message, - 'discord': discord, - 'commands': commands, - '_': self._last_result + "bot": ctx.bot, + "ctx": ctx, + "channel": ctx.channel, + "author": ctx.author, + "guild": ctx.guild, + "message": ctx.message, + "discord": discord, + "commands": commands, + "_": self._last_result, } code = self.cleanup_code(code) @@ -109,8 +109,7 @@ class Dev: await ctx.send(self.get_syntax_error(e)) return except Exception as e: - await ctx.send( - box('{}: {!s}'.format(type(e).__name__, e), lang='py')) + await ctx.send(box("{}: {!s}".format(type(e).__name__, e), lang="py")) return if asyncio.iscoroutine(result): @@ -122,7 +121,7 @@ class Dev: await ctx.send_interactive(self.get_pages(result), box_lang="py") - @commands.command(name='eval') + @commands.command(name="eval") @checks.is_owner() async def _eval(self, ctx, *, body: str): """Execute asynchronous code. @@ -145,28 +144,28 @@ class Dev: _ - The result of the last dev command. """ env = { - 'bot': ctx.bot, - 'ctx': ctx, - 'channel': ctx.channel, - 'author': ctx.author, - 'guild': ctx.guild, - 'message': ctx.message, - 'discord': discord, - 'commands': commands, - '_': self._last_result + "bot": ctx.bot, + "ctx": ctx, + "channel": ctx.channel, + "author": ctx.author, + "guild": ctx.guild, + "message": ctx.message, + "discord": discord, + "commands": commands, + "_": self._last_result, } body = self.cleanup_code(body) stdout = io.StringIO() - to_compile = 'async def func():\n%s' % textwrap.indent(body, ' ') + to_compile = "async def func():\n%s" % textwrap.indent(body, " ") try: exec(to_compile, env) except SyntaxError as e: return await ctx.send(self.get_syntax_error(e)) - func = env['func'] + func = env["func"] result = None try: with redirect_stdout(stdout): @@ -199,43 +198,43 @@ class Dev: async function. """ variables = { - 'ctx': ctx, - 'bot': ctx.bot, - 'message': ctx.message, - 'guild': ctx.guild, - 'channel': ctx.channel, - 'author': ctx.author, - '_': None, + "ctx": ctx, + "bot": ctx.bot, + "message": ctx.message, + "guild": ctx.guild, + "channel": ctx.channel, + "author": ctx.author, + "_": None, } if ctx.channel.id in self.sessions: - await ctx.send(_('Already running a REPL session in this channel. ' - 'Exit it with `quit`.')) + await ctx.send( + _("Already running a REPL session in this channel. " "Exit it with `quit`.") + ) return self.sessions.add(ctx.channel.id) - await ctx.send(_('Enter code to execute or evaluate.' - ' `exit()` or `quit` to exit.')) + await ctx.send(_("Enter code to execute or evaluate." " `exit()` or `quit` to exit.")) - msg_check = lambda m: (m.author == ctx.author and - m.channel == ctx.channel and - m.content.startswith('`')) + msg_check = lambda m: ( + m.author == ctx.author and m.channel == ctx.channel and m.content.startswith("`") + ) while True: response = await ctx.bot.wait_for("message", check=msg_check) cleaned = self.cleanup_code(response.content) - if cleaned in ('quit', 'exit', 'exit()'): - await ctx.send('Exiting.') + if cleaned in ("quit", "exit", "exit()"): + await ctx.send("Exiting.") self.sessions.remove(ctx.channel.id) return executor = exec - if cleaned.count('\n') == 0: + if cleaned.count("\n") == 0: # single statement, potentially 'eval' try: - code = compile(cleaned, '', 'eval') + code = compile(cleaned, "", "eval") except SyntaxError: pass else: @@ -243,12 +242,12 @@ class Dev: if executor is exec: try: - code = compile(cleaned, '', 'exec') + code = compile(cleaned, "", "exec") except SyntaxError as e: await ctx.send(self.get_syntax_error(e)) continue - variables['message'] = response + variables["message"] = response stdout = io.StringIO() @@ -266,7 +265,7 @@ class Dev: value = stdout.getvalue() if result is not None: msg = "{}{}".format(value, result) - variables['_'] = result + variables["_"] = result elif value: msg = "{}".format(value) @@ -277,7 +276,7 @@ class Dev: except discord.Forbidden: pass except discord.HTTPException as e: - await ctx.send(_('Unexpected error: `{}`').format(e)) + await ctx.send(_("Unexpected error: `{}`").format(e)) @commands.command() @checks.is_owner() @@ -290,7 +289,7 @@ class Dev: msg.author = user msg.content = ctx.prefix + command - ctx.bot.dispatch('message', msg) + ctx.bot.dispatch("message", msg) @commands.command(name="mockmsg") @checks.is_owner() diff --git a/redbot/core/drivers/__init__.py b/redbot/core/drivers/__init__.py index b93ea0bd5..6cf6ca2ca 100644 --- a/redbot/core/drivers/__init__.py +++ b/redbot/core/drivers/__init__.py @@ -24,8 +24,10 @@ def get_driver(type, *args, **kwargs): """ if type == "JSON": from .red_json import JSON + return JSON(*args, **kwargs) elif type == "MongoDB": from .red_mongo import Mongo + return Mongo(*args, **kwargs) raise RuntimeError("Invalid driver type: '{}'".format(type)) diff --git a/redbot/core/drivers/red_base.py b/redbot/core/drivers/red_base.py index 38b8bb14f..7766a44e9 100644 --- a/redbot/core/drivers/red_base.py +++ b/redbot/core/drivers/red_base.py @@ -2,6 +2,7 @@ __all__ = ["BaseDriver"] class BaseDriver: + def __init__(self, cog_name, identifier): self.cog_name = cog_name self.unique_cog_identifier = identifier diff --git a/redbot/core/drivers/red_json.py b/redbot/core/drivers/red_json.py index 348cb44bc..7ca1be3aa 100644 --- a/redbot/core/drivers/red_json.py +++ b/redbot/core/drivers/red_json.py @@ -44,14 +44,21 @@ class JSON(BaseDriver): The path in which to store the file indicated by :py:attr:`file_name`. """ - def __init__(self, cog_name, identifier, *, data_path_override: Path=None, - file_name_override: str="settings.json"): + + def __init__( + self, + cog_name, + identifier, + *, + data_path_override: Path = None, + file_name_override: str = "settings.json" + ): super().__init__(cog_name, identifier) self.file_name = file_name_override if data_path_override: self.data_path = data_path_override else: - self.data_path = Path.cwd() / 'cogs' / '.data' / self.cog_name + self.data_path = Path.cwd() / "cogs" / ".data" / self.cog_name self.data_path.mkdir(parents=True, exist_ok=True) diff --git a/redbot/core/drivers/red_mongo.py b/redbot/core/drivers/red_mongo.py index bba59fe71..da6f8e388 100644 --- a/redbot/core/drivers/red_mongo.py +++ b/redbot/core/drivers/red_mongo.py @@ -8,21 +8,16 @@ _conn = None def _initialize(**kwargs): - host = kwargs['HOST'] - port = kwargs['PORT'] - admin_user = kwargs['USERNAME'] - admin_pass = kwargs['PASSWORD'] - db_name = kwargs.get('DB_NAME', 'default_db') + host = kwargs["HOST"] + port = kwargs["PORT"] + admin_user = kwargs["USERNAME"] + admin_pass = kwargs["PASSWORD"] + db_name = kwargs.get("DB_NAME", "default_db") if admin_user is not None and admin_pass is not None: - url = "mongodb://{}:{}@{}:{}/{}".format( - admin_user, admin_pass, host, port, - db_name - ) + url = "mongodb://{}:{}@{}:{}/{}".format(admin_user, admin_pass, host, port, db_name) else: - url = "mongodb://{}:{}/{}".format( - host, port, db_name - ) + url = "mongodb://{}:{}/{}".format(host, port, db_name) global _conn _conn = motor.motor_asyncio.AsyncIOMotorClient(url) @@ -32,6 +27,7 @@ class Mongo(BaseDriver): """ Subclass of :py:class:`.red_base.BaseDriver`. """ + def __init__(self, cog_name, identifier, **kwargs): super().__init__(cog_name, identifier) @@ -75,45 +71,40 @@ class Mongo(BaseDriver): async def get(self, *identifiers: str): mongo_collection = self.get_collection() - dot_identifiers = '.'.join(identifiers) + dot_identifiers = ".".join(identifiers) partial = await mongo_collection.find_one( - filter={'_id': self.unique_cog_identifier}, - projection={dot_identifiers: True} + filter={"_id": self.unique_cog_identifier}, projection={dot_identifiers: True} ) if partial is None: - raise KeyError("No matching document was found and Config expects" - " a KeyError.") + raise KeyError("No matching document was found and Config expects" " a KeyError.") for i in identifiers: partial = partial[i] return partial async def set(self, *identifiers: str, value=None): - dot_identifiers = '.'.join(identifiers) + dot_identifiers = ".".join(identifiers) mongo_collection = self.get_collection() await mongo_collection.update_one( - {'_id': self.unique_cog_identifier}, + {"_id": self.unique_cog_identifier}, update={"$set": {dot_identifiers: value}}, - upsert=True + upsert=True, ) async def clear(self, *identifiers: str): - dot_identifiers = '.'.join(identifiers) + dot_identifiers = ".".join(identifiers) mongo_collection = self.get_collection() if len(identifiers) > 0: await mongo_collection.update_one( - {'_id': self.unique_cog_identifier}, - update={"$unset": {dot_identifiers: 1}} + {"_id": self.unique_cog_identifier}, update={"$unset": {dot_identifiers: 1}} ) else: - await mongo_collection.delete_one( - {'_id': self.unique_cog_identifier} - ) + await mongo_collection.delete_one({"_id": self.unique_cog_identifier}) def get_config_details(): @@ -129,10 +120,10 @@ def get_config_details(): admin_uname = admin_password = None ret = { - 'HOST': host, - 'PORT': port, - 'USERNAME': admin_uname, - 'PASSWORD': admin_password, - 'DB_NAME': db_name + "HOST": host, + "PORT": port, + "USERNAME": admin_uname, + "PASSWORD": admin_password, + "DB_NAME": db_name, } return ret diff --git a/redbot/core/events.py b/redbot/core/events.py index 9b41eea7a..ec36c6844 100644 --- a/redbot/core/events.py +++ b/redbot/core/events.py @@ -44,8 +44,8 @@ def should_log_sentry(exception) -> bool: tb_frame = tb.tb_frame tb = tb.tb_next - module = tb_frame.f_globals.get('__name__') - return module.startswith('redbot') + module = tb_frame.f_globals.get("__name__") + return module.startswith("redbot") def init_events(bot, cli_flags): @@ -77,8 +77,7 @@ def init_events(bot, cli_flags): 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) + log.exception("Failed to load package {}".format(package), exc_info=e) await bot.remove_loaded_package(package) to_remove.append(package) for package in to_remove: @@ -104,18 +103,21 @@ def init_events(bot, cli_flags): 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)] + 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))) + INFO.append("{} cogs with {} commands".format(len(bot.cogs), len(bot.commands))) async with aiohttp.ClientSession() as session: async with session.get("https://pypi.python.org/pypi/red-discordbot/json") as r: @@ -139,11 +141,7 @@ def init_events(bot, cli_flags): sentry = await bot.db.enable_sentry() mongo_enabled = storage_type() != "JSON" - reqs_installed = { - "voice": None, - "docs": None, - "test": None - } + 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: @@ -158,7 +156,7 @@ def init_events(bot, cli_flags): ("MongoDB", mongo_enabled), ("Voice", reqs_installed["voice"]), ("Docs", reqs_installed["docs"]), - ("Tests", reqs_installed["test"]) + ("Tests", reqs_installed["test"]), ) on_symbol, off_symbol, ascii_border = _get_startup_screen_specs() @@ -201,21 +199,25 @@ def init_events(bot, cli_flags): await ctx.send(msg) return """ - log.exception("Exception in command '{}'" - "".format(ctx.command.qualified_name), - exc_info=error.original) + log.exception( + "Exception in command '{}'" "".format(ctx.command.qualified_name), + exc_info=error.original, + ) if should_log_sentry(error): - sentry_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__)) + 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)) @@ -226,9 +228,9 @@ def init_events(bot, cli_flags): 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)) + 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: @@ -237,8 +239,7 @@ def init_events(bot, cli_flags): sentry_error = error if should_log_sentry(sentry_error): - sentry_log.exception("Unhandled command error.", - exc_info=sentry_error) + sentry_log.exception("Unhandled command error.", exc_info=sentry_error) @bot.event async def on_message(message): @@ -253,6 +254,7 @@ def init_events(bot, cli_flags): async def on_command(command): bot.counter["processed_commands"] += 1 + def _get_startup_screen_specs(): """Get specs for displaying the startup screen on stdout. @@ -278,11 +280,10 @@ def _get_startup_screen_specs(): off_symbol = "X" try: - encoder('┌┐└┘─│') # border symbols + encoder("┌┐└┘─│") # border symbols except UnicodeEncodeError: ascii_border = True else: ascii_border = False return on_symbol, off_symbol, ascii_border - diff --git a/redbot/core/help_formatter.py b/redbot/core/help_formatter.py index 9bcf3c2dc..424426e4d 100644 --- a/redbot/core/help_formatter.py +++ b/redbot/core/help_formatter.py @@ -38,16 +38,13 @@ import traceback from . import commands -EMPTY_STRING = u'\u200b' +EMPTY_STRING = u"\u200b" -_mentions_transforms = { - '@everyone': '@\u200beveryone', - '@here': '@\u200bhere' -} +_mentions_transforms = {"@everyone": "@\u200beveryone", "@here": "@\u200bhere"} -_mention_pattern = re.compile('|'.join(_mentions_transforms.keys())) +_mention_pattern = re.compile("|".join(_mentions_transforms.keys())) -EmbedField = namedtuple('EmbedField', 'name value inline') +EmbedField = namedtuple("EmbedField", "name value inline") class Help(formatter.HelpFormatter): @@ -71,7 +68,7 @@ class Help(formatter.HelpFormatter): @property def avatar(self): - return self.context.bot.user.avatar_url_as(format='png') + return self.context.bot.user.avatar_url_as(format="png") @property def color(self): @@ -94,48 +91,41 @@ class Help(formatter.HelpFormatter): if self.pm_check(self.context): name = self.context.bot.user.name else: - name = self.me.display_name if not '' else self.context.bot.user.name - author = { - 'name': '{0} Help Manual'.format(name), - 'icon_url': self.avatar - } + name = self.me.display_name if not "" else self.context.bot.user.name + author = {"name": "{0} Help Manual".format(name), "icon_url": self.avatar} return author def _add_subcommands(self, cmds): - entries = '' + entries = "" for name, command in cmds: if name in command.aliases: # skip aliases continue if self.is_cog() or self.is_bot(): - name = '{0}{1}'.format(self.clean_prefix, name) + name = "{0}{1}".format(self.clean_prefix, name) - entries += '**{0}** {1}\n'.format(name, command.short_doc) + entries += "**{0}** {1}\n".format(name, command.short_doc) return entries def get_ending_note(self): # command_name = self.context.invoked_with - return "Type {0}help for more info on a command.\n" \ - "You can also type {0}help for more info on a category.".format(self.clean_prefix) + return "Type {0}help for more info on a command.\n" "You can also type {0}help for more info on a category.".format( + self.clean_prefix + ) async def format(self) -> dict: """Formats command for output. Returns a dict used to build embed""" emb = { - 'embed': { - 'title': '', - 'description': '', - }, - 'footer': { - 'text': self.get_ending_note() - }, - 'fields': [] + "embed": {"title": "", "description": ""}, + "footer": {"text": self.get_ending_note()}, + "fields": [], } if self.is_cog(): - translator = getattr(self.command, '__translator__', lambda s: s) + translator = getattr(self.command, "__translator__", lambda s: s) description = ( inspect.cleandoc(translator(self.command.__doc__)) if self.command.__doc__ @@ -144,27 +134,27 @@ class Help(formatter.HelpFormatter): else: description = self.command.description - if not description == '' and description is not None: - description = '*{0}*'.format(description) + if not description == "" and description is not None: + description = "*{0}*".format(description) if description: # portion - emb['embed']['description'] = description[:2046] + emb["embed"]["description"] = description[:2046] if isinstance(self.command, discord.ext.commands.core.Command): # - emb['embed']['title'] = emb['embed']['description'] - emb['embed']['description'] = '`Syntax: {0}`'.format(self.get_command_signature()) + emb["embed"]["title"] = emb["embed"]["description"] + emb["embed"]["description"] = "`Syntax: {0}`".format(self.get_command_signature()) # section if self.command.help: - splitted = self.command.help.split('\n\n') - name = '__{0}__'.format(splitted[0]) - value = '\n\n'.join(splitted[1:]).replace('[p]', self.clean_prefix) - if value == '': + splitted = self.command.help.split("\n\n") + name = "__{0}__".format(splitted[0]) + value = "\n\n".join(splitted[1:]).replace("[p]", self.clean_prefix) + if value == "": value = EMPTY_STRING field = EmbedField(name[:252], value[:1024], False) - emb['fields'].append(field) + emb["fields"].append(field) # end it here if it's just a regular command if not self.has_subcommands(): @@ -173,7 +163,7 @@ class Help(formatter.HelpFormatter): def category(tup): # Turn get cog (Category) name from cog/list tuples cog = tup[1].cog_name - return '**__{0}:__**'.format(cog) if cog is not None else '**__\u200bNo Category:__**' + return "**__{0}:__**".format(cog) if cog is not None else "**__\u200bNo Category:__**" # Get subcommands for bot or category filtered = await self.filter_command_list() @@ -185,18 +175,21 @@ class Help(formatter.HelpFormatter): commands_ = sorted(commands_) if len(commands_) > 0: field = EmbedField(category, self._add_subcommands(commands_), False) - emb['fields'].append(field) + emb["fields"].append(field) else: # Get list of commands for category filtered = sorted(filtered) if filtered: field = EmbedField( - '**__Commands:__**' if not self.is_bot() and self.is_cog() else '**__Subcommands:__**', + "**__Commands:__**" + if not self.is_bot() and self.is_cog() + else "**__Subcommands:__**", self._add_subcommands(filtered), # May need paginated - False) + False, + ) - emb['fields'].append(field) + emb["fields"].append(field) return emb @@ -214,7 +207,7 @@ class Help(formatter.HelpFormatter): return ret - async def format_help_for(self, ctx, command_or_bot, reason: str=None): + async def format_help_for(self, ctx, command_or_bot, reason: str = None): """Formats the help page and handles the actual heavy lifting of how ### WTF HAPPENED? the help command looks like. To change the behaviour, override the :meth:`~.HelpFormatter.format` method. @@ -237,16 +230,18 @@ class Help(formatter.HelpFormatter): emb = await self.format() if reason: - emb['embed']['title'] = "{0}".format(reason) + emb["embed"]["title"] = "{0}".format(reason) ret = [] - field_groups = self.group_fields(emb['fields']) + field_groups = self.group_fields(emb["fields"]) for i, group in enumerate(field_groups, 1): - embed = discord.Embed(color=self.color, **emb['embed']) + embed = discord.Embed(color=self.color, **emb["embed"]) if len(field_groups) > 1: - description = "{} *- Page {} of {}*".format(embed.description, i, len(field_groups)) + description = "{} *- Page {} of {}*".format( + embed.description, i, len(field_groups) + ) embed.description = description embed.set_author(**self.author) @@ -254,7 +249,7 @@ class Help(formatter.HelpFormatter): for field in group: embed.add_field(**field._asdict()) - embed.set_footer(**emb['footer']) + embed.set_footer(**emb["footer"]) ret.append(embed) @@ -275,18 +270,18 @@ class Help(formatter.HelpFormatter): embed = self.simple_embed( ctx, title=ctx.bot.command_not_found.format(cmd), - description='Commands are case sensitive. Please check your spelling and try again', - color=color) + description="Commands are case sensitive. Please check your spelling and try again", + color=color, + ) return embed def cmd_has_no_subcommands(self, ctx, cmd, color=None): embed = self.simple_embed( - ctx, - title=ctx.bot.command_has_no_subcommands.format(cmd), - color=color + ctx, title=ctx.bot.command_has_no_subcommands.format(cmd), color=color ) return embed + @commands.command() async def help(ctx, *cmds: str): """Shows help documentation. @@ -297,7 +292,8 @@ async def help(ctx, *cmds: str): destination = ctx.author if ctx.bot.pm_help else ctx def repl(obj): - return _mentions_transforms.get(obj.group(0), '') + return _mentions_transforms.get(obj.group(0), "") + use_embeds = await ctx.embed_requested() f = formatter.HelpFormatter() # help by itself just lists our own commands. @@ -316,12 +312,9 @@ async def help(ctx, *cmds: str): command = ctx.bot.all_commands.get(name) if command is None: if use_embeds: - await destination.send( - embed=ctx.bot.formatter.cmd_not_found(ctx, name)) + await destination.send(embed=ctx.bot.formatter.cmd_not_found(ctx, name)) else: - await destination.send( - ctx.bot.command_not_found.format(name) - ) + await destination.send(ctx.bot.command_not_found.format(name)) return if use_embeds: embeds = await ctx.bot.formatter.format_help_for(ctx, command) @@ -332,12 +325,9 @@ async def help(ctx, *cmds: str): command = ctx.bot.all_commands.get(name) if command is None: if use_embeds: - await destination.send( - embed=ctx.bot.formatter.cmd_not_found(ctx, name)) + await destination.send(embed=ctx.bot.formatter.cmd_not_found(ctx, name)) else: - await destination.send( - ctx.bot.command_not_found.format(name) - ) + await destination.send(ctx.bot.command_not_found.format(name)) return for key in cmds[1:]: @@ -346,12 +336,9 @@ async def help(ctx, *cmds: str): command = command.all_commands.get(key) if command is None: if use_embeds: - await destination.send( - embed=ctx.bot.formatter.cmd_not_found(ctx, key)) + await destination.send(embed=ctx.bot.formatter.cmd_not_found(ctx, key)) else: - await destination.send( - ctx.bot.command_not_found.format(key) - ) + await destination.send(ctx.bot.command_not_found.format(key)) return except AttributeError: if use_embeds: @@ -359,11 +346,11 @@ async def help(ctx, *cmds: str): embed=ctx.bot.formatter.simple_embed( ctx, title='Command "{0.name}" has no subcommands.'.format(command), - color=ctx.bot.formatter.color)) - else: - await destination.send( - ctx.bot.command_has_no_subcommands.format(command) + color=ctx.bot.formatter.color, + ) ) + else: + await destination.send(ctx.bot.command_has_no_subcommands.format(command)) return if use_embeds: embeds = await ctx.bot.formatter.format_help_for(ctx, command) @@ -391,5 +378,5 @@ async def help(ctx, *cmds: str): @help.error async def help_error(ctx, error): destination = ctx.author if ctx.bot.pm_help else ctx - await destination.send('{0.__name__}: {1}'.format(type(error), error)) + await destination.send("{0.__name__}: {1}".format(type(error), error)) traceback.print_tb(error.original.__traceback__, file=sys.stderr) diff --git a/redbot/core/i18n.py b/redbot/core/i18n.py index 844869973..04d7f43c5 100644 --- a/redbot/core/i18n.py +++ b/redbot/core/i18n.py @@ -3,10 +3,9 @@ from pathlib import Path from . import commands -__all__ = ['get_locale', 'set_locale', 'reload_locales', 'cog_i18n', - 'Translator'] +__all__ = ["get_locale", "set_locale", "reload_locales", "cog_i18n", "Translator"] -_current_locale = 'en_us' +_current_locale = "en_us" WAITING_FOR_MSGID = 1 IN_MSGID = 2 @@ -54,8 +53,8 @@ def _parse(translation_file): if line.startswith(MSGID): # Don't check if step is WAITING_FOR_MSGID - untranslated = '' - translated = '' + untranslated = "" + translated = "" data = line[len(MSGID):-1] if len(data) == 0: # Multiline mode step = IN_MSGID @@ -63,10 +62,9 @@ def _parse(translation_file): untranslated += data step = WAITING_FOR_MSGSTR - elif step is IN_MSGID and line.startswith('"') and \ - line.endswith('"'): + elif step is IN_MSGID and line.startswith('"') and line.endswith('"'): untranslated += line[1:-1] - elif step is IN_MSGID and untranslated == '': # Empty MSGID + elif step is IN_MSGID and untranslated == "": # Empty MSGID step = WAITING_FOR_MSGID elif step is IN_MSGID: # the MSGID is finished step = WAITING_FOR_MSGSTR @@ -79,16 +77,15 @@ def _parse(translation_file): translations |= {(untranslated, data)} step = WAITING_FOR_MSGID - elif step is IN_MSGSTR and line.startswith('"') and \ - line.endswith('"'): + elif step is IN_MSGSTR and line.startswith('"') and line.endswith('"'): translated += line[1:-1] elif step is IN_MSGSTR: # the MSGSTR is finished step = WAITING_FOR_MSGID - if translated == '': + if translated == "": translated = untranslated translations |= {(untranslated, translated)} if step is IN_MSGSTR: - if translated == '': + if translated == "": translated = untranslated translations |= {(untranslated, translated)} return translations @@ -107,33 +104,34 @@ def _normalize(string, remove_newline=False): :param remove_newline: :return: """ + def normalize_whitespace(s): """Normalizes the whitespace in a string; \s+ becomes one space.""" if not s: return str(s) # not the same reference - starts_with_space = (s[0] in ' \n\t\r') - ends_with_space = (s[-1] in ' \n\t\r') + starts_with_space = (s[0] in " \n\t\r") + ends_with_space = (s[-1] in " \n\t\r") if remove_newline: - newline_re = re.compile('[\r\n]+') - s = ' '.join(filter(bool, newline_re.split(s))) - s = ' '.join(filter(bool, s.split('\t'))) - s = ' '.join(filter(bool, s.split(' '))) + newline_re = re.compile("[\r\n]+") + s = " ".join(filter(bool, newline_re.split(s))) + s = " ".join(filter(bool, s.split("\t"))) + s = " ".join(filter(bool, s.split(" "))) if starts_with_space: - s = ' ' + s + s = " " + s if ends_with_space: - s += ' ' + s += " " return s if string is None: return "" - - string = string.replace('\\n\\n', '\n\n') - string = string.replace('\\n', ' ') + + string = string.replace("\\n\\n", "\n\n") + string = string.replace("\\n", " ") string = string.replace('\\"', '"') - string = string.replace("\'", "'") + string = string.replace("'", "'") string = normalize_whitespace(string) - string = string.strip('\n') - string = string.strip('\t') + string = string.strip("\n") + string = string.strip("\t") return string @@ -148,7 +146,7 @@ def get_locale_path(cog_folder: Path, extension: str) -> Path: :return: Path of possible localization file, it may not exist. """ - return cog_folder / 'locales' / "{}.{}".format(get_locale(), extension) + return cog_folder / "locales" / "{}.{}".format(get_locale(), extension) class Translator: @@ -193,13 +191,13 @@ class Translator: """ self.translations = {} translation_file = None - locale_path = get_locale_path(self.cog_folder, 'po') + locale_path = get_locale_path(self.cog_folder, "po") try: try: - translation_file = locale_path.open('ru', encoding='utf-8') + translation_file = locale_path.open("ru", encoding="utf-8") except ValueError: # We are using Windows - translation_file = locale_path.open('r', encoding='utf-8') + translation_file = locale_path.open("r", encoding="utf-8") self._parse(translation_file) except (IOError, FileNotFoundError): # The translation is unavailable pass @@ -221,6 +219,7 @@ class Translator: def cog_i18n(translator: Translator): """Get a class decorator to link the translator to this cog.""" + def decorator(cog_class: type): cog_class.__translator__ = translator for name, attr in cog_class.__dict__.items(): @@ -228,4 +227,5 @@ def cog_i18n(translator: Translator): attr.translator = translator setattr(cog_class, name, attr) return cog_class + return decorator diff --git a/redbot/core/json_io.py b/redbot/core/json_io.py index 060cf09c8..c2be5e939 100644 --- a/redbot/core/json_io.py +++ b/redbot/core/json_io.py @@ -11,13 +11,14 @@ from pathlib import Path log = logging.getLogger("red") -PRETTY = {"indent": 4, "sort_keys": True, "separators": (',', ' : ')} -MINIFIED = {"sort_keys": True, "separators": (',', ':')} +PRETTY = {"indent": 4, "sort_keys": True, "separators": (",", " : ")} +MINIFIED = {"sort_keys": True, "separators": (",", ":")} class JsonIO: """Basic functions for atomic saving / loading of json files""" - def __init__(self, path: Path=Path.cwd()): + + def __init__(self, path: Path = Path.cwd()): """ :param path: Full path to file. """ @@ -43,7 +44,7 @@ class JsonIO: # noinspection PyUnresolvedReferences def _load_json(self): log.debug("Reading file {}".format(self.path)) - with self.path.open(encoding='utf-8', mode="r") as f: + with self.path.open(encoding="utf-8", mode="r") as f: data = json.load(f) return data diff --git a/redbot/core/locales/regen_messages.py b/redbot/core/locales/regen_messages.py index 5b24765af..c84ef7ad0 100644 --- a/redbot/core/locales/regen_messages.py +++ b/redbot/core/locales/regen_messages.py @@ -1,17 +1,11 @@ import subprocess -TO_TRANSLATE = [ - '../cog_manager.py', - '../core_commands.py', - '../dev_commands.py' -] +TO_TRANSLATE = ["../cog_manager.py", "../core_commands.py", "../dev_commands.py"] def regen_messages(): - subprocess.run( - ['pygettext', '-n'] + TO_TRANSLATE - ) + subprocess.run(["pygettext", "-n"] + TO_TRANSLATE) if __name__ == "__main__": - regen_messages() \ No newline at end of file + regen_messages() diff --git a/redbot/core/modlog.py b/redbot/core/modlog.py index 76334003d..8887b5260 100644 --- a/redbot/core/modlog.py +++ b/redbot/core/modlog.py @@ -8,21 +8,24 @@ from redbot.core import Config from redbot.core.bot import Red __all__ = [ - "Case", "CaseType", "get_next_case_number", "get_case", "get_all_cases", - "create_case", "get_casetype", "get_all_casetypes", "register_casetype", - "register_casetypes", "get_modlog_channel", "set_modlog_channel", - "reset_cases" + "Case", + "CaseType", + "get_next_case_number", + "get_case", + "get_all_cases", + "create_case", + "get_casetype", + "get_all_casetypes", + "register_casetype", + "register_casetypes", + "get_modlog_channel", + "set_modlog_channel", + "reset_cases", ] -_DEFAULT_GLOBAL = { - "casetypes": {} -} +_DEFAULT_GLOBAL = {"casetypes": {}} -_DEFAULT_GUILD = { - "mod_log": None, - "cases": {}, - "casetypes": {} -} +_DEFAULT_GUILD = {"mod_log": None, "cases": {}, "casetypes": {}} def _register_defaults(): @@ -30,8 +33,8 @@ def _register_defaults(): _conf.register_guild(**_DEFAULT_GUILD) -if not os.environ.get('BUILDING_DOCS'): - _conf = Config.get_conf(None, 1354799444, cog_name='ModLog') +if not os.environ.get("BUILDING_DOCS"): + _conf = Config.get_conf(None, 1354799444, cog_name="ModLog") _register_defaults() @@ -39,11 +42,20 @@ class Case: """A single mod log case""" def __init__( - self, guild: discord.Guild, created_at: int, action_type: str, - user: discord.User, moderator: discord.Member, case_number: int, - reason: str=None, until: int=None, - channel: discord.TextChannel=None, amended_by: discord.Member=None, - modified_at: int=None, message: discord.Message=None): + self, + guild: discord.Guild, + created_at: int, + action_type: str, + user: discord.User, + moderator: discord.Member, + case_number: int, + reason: str = None, + until: int = None, + channel: discord.TextChannel = None, + amended_by: discord.Member = None, + modified_at: int = None, + message: discord.Message = None, + ): self.guild = guild self.created_at = created_at self.action_type = action_type @@ -82,11 +94,9 @@ class Case: else: await self.message.edit(case_content) - await _conf.guild(self.guild).cases.set_raw( - str(self.case_number), value=self.to_json() - ) + await _conf.guild(self.guild).cases.set_raw(str(self.case_number), value=self.to_json()) - async def message_content(self, embed: bool=True): + async def message_content(self, embed: bool = True): """ Format a case message @@ -102,22 +112,18 @@ class Case: """ casetype = await get_casetype(self.action_type) - title = "{}".format("Case #{} | {} {}".format( - self.case_number, casetype.case_str, casetype.image)) + title = "{}".format( + "Case #{} | {} {}".format(self.case_number, casetype.case_str, casetype.image) + ) if self.reason: reason = "**Reason:** {}".format(self.reason) else: - reason = \ - "**Reason:** Use `[p]reason {} ` to add it".format( - self.case_number - ) + reason = "**Reason:** Use `[p]reason {} ` to add it".format(self.case_number) if self.moderator is not None: moderator = "{}#{} ({})\n".format( - self.moderator.name, - self.moderator.discriminator, - self.moderator.id + self.moderator.name, self.moderator.discriminator, self.moderator.id ) else: moderator = "Unknown" @@ -126,7 +132,7 @@ class Case: if self.until: start = datetime.fromtimestamp(self.created_at) end = datetime.fromtimestamp(self.until) - end_fmt = end.strftime('%Y-%m-%d %H:%M:%S') + end_fmt = end.strftime("%Y-%m-%d %H:%M:%S") duration = end - start dur_fmt = _strfdelta(duration) until = end_fmt @@ -135,21 +141,16 @@ class Case: amended_by = None if self.amended_by: amended_by = "{}#{} ({})".format( - self.amended_by.name, - self.amended_by.discriminator, - self.amended_by.id + self.amended_by.name, self.amended_by.discriminator, self.amended_by.id ) last_modified = None if self.modified_at: last_modified = "{}".format( - datetime.fromtimestamp( - self.modified_at - ).strftime('%Y-%m-%d %H:%M:%S') + datetime.fromtimestamp(self.modified_at).strftime("%Y-%m-%d %H:%M:%S") ) - user = "{}#{} ({})\n".format( - self.user.name, self.user.discriminator, self.user.id) + user = "{}#{} ({})\n".format(self.user.name, self.user.discriminator, self.user.id) if embed: emb = discord.Embed(title=title, description=reason) @@ -208,7 +209,7 @@ class Case: "channel": self.channel.id if hasattr(self.channel, "id") else None, "amended_by": self.amended_by.id if hasattr(self.amended_by, "id") else None, "modified_at": self.modified_at, - "message": self.message.id if hasattr(self.message, "id") else None + "message": self.message.id if hasattr(self.message, "id") else None, } return data @@ -239,11 +240,18 @@ class Case: amended_by = guild.get_member(data["amended_by"]) case_guild = bot.get_guild(data["guild"]) return cls( - guild=case_guild, created_at=data["created_at"], - action_type=data["action_type"], user=user, moderator=moderator, - case_number=data["case_number"], reason=data["reason"], - until=data["until"], channel=channel, amended_by=amended_by, - modified_at=data["modified_at"], message=message + guild=case_guild, + created_at=data["created_at"], + action_type=data["action_type"], + user=user, + moderator=moderator, + case_number=data["case_number"], + reason=data["reason"], + until=data["until"], + channel=channel, + amended_by=amended_by, + modified_at=data["modified_at"], + message=message, ) @@ -266,9 +274,16 @@ class CaseType: The action type of the action as it would appear in the audit log """ + def __init__( - self, name: str, default_setting: bool, image: str, - case_str: str, audit_type: str=None, guild: discord.Guild=None): + self, + name: str, + default_setting: bool, + image: str, + case_str: str, + audit_type: str = None, + guild: discord.Guild = None, + ): self.name = name self.default_setting = default_setting self.image = image @@ -282,7 +297,7 @@ class CaseType: "default_setting": self.default_setting, "image": self.image, "case_str": self.case_str, - "audit_type": self.audit_type + "audit_type": self.audit_type, } await _conf.casetypes.set_raw(self.name, value=data) @@ -302,7 +317,8 @@ class CaseType: if not self.guild: return False return await _conf.guild(self.guild).casetypes.get_raw( - self.name, default=self.default_setting) + self.name, default=self.default_setting + ) async def set_enabled(self, enabled: bool): """ @@ -348,16 +364,11 @@ async def get_next_case_number(guild: discord.Guild) -> str: The next case number """ - cases = sorted( - (await _conf.guild(guild).get_raw("cases")), - key=lambda x: int(x), - reverse=True - ) + cases = sorted((await _conf.guild(guild).get_raw("cases")), key=lambda x: int(x), reverse=True) return str(int(cases[0]) + 1) if cases else "1" -async def get_case(case_number: int, guild: discord.Guild, - bot: Red) -> Case: +async def get_case(case_number: int, guild: discord.Guild, bot: Red) -> Case: """ Gets the case with the associated case number @@ -384,9 +395,7 @@ async def get_case(case_number: int, guild: discord.Guild, try: case = await _conf.guild(guild).cases.get_raw(str(case_number)) except KeyError as e: - raise RuntimeError( - "That case does not exist for guild {}".format(guild.name) - ) from e + raise RuntimeError("That case does not exist for guild {}".format(guild.name)) from e mod_channel = await get_modlog_channel(guild) return await Case.from_json(mod_channel, bot, case) @@ -416,11 +425,17 @@ async def get_all_cases(guild: discord.Guild, bot: Red) -> List[Case]: return case_list -async def create_case(bot: Red, guild: discord.Guild, created_at: datetime, action_type: str, - user: Union[discord.User, discord.Member], - moderator: discord.Member=None, reason: str=None, - until: datetime=None, channel: discord.TextChannel=None - ) -> Union[Case, None]: +async def create_case( + bot: Red, + guild: discord.Guild, + created_at: datetime, + action_type: str, + user: Union[discord.User, discord.Member], + moderator: discord.Member = None, + reason: str = None, + until: datetime = None, + channel: discord.TextChannel = None, +) -> Union[Case, None]: """ Creates a new case @@ -463,9 +478,7 @@ async def create_case(bot: Red, guild: discord.Guild, created_at: datetime, acti try: mod_channel = await get_modlog_channel(guild) except RuntimeError: - raise RuntimeError( - "No mod log channel set for guild {}".format(guild.name) - ) + raise RuntimeError("No mod log channel set for guild {}".format(guild.name)) case_type = await get_casetype(action_type, guild) if case_type is None: return None @@ -475,9 +488,20 @@ async def create_case(bot: Red, guild: discord.Guild, created_at: datetime, acti next_case_number = int(await get_next_case_number(guild)) - case = Case(guild, int(created_at.timestamp()), action_type, user, moderator, - next_case_number, reason, int(until.timestamp()) if until else None, - channel, amended_by=None, modified_at=None, message=None) + case = Case( + guild, + int(created_at.timestamp()), + action_type, + user, + moderator, + next_case_number, + reason, + int(until.timestamp()) if until else None, + channel, + amended_by=None, + modified_at=None, + message=None, + ) if hasattr(mod_channel, "send"): # Not going to be the case for tests use_embeds = await bot.embed_requested(mod_channel, guild.me) case_content = await case.message_content(use_embeds) @@ -490,7 +514,7 @@ async def create_case(bot: Red, guild: discord.Guild, created_at: datetime, acti return case -async def get_casetype(name: str, guild: discord.Guild=None) -> Union[CaseType, None]: +async def get_casetype(name: str, guild: discord.Guild = None) -> Union[CaseType, None]: """ Gets the case type @@ -516,7 +540,7 @@ async def get_casetype(name: str, guild: discord.Guild=None) -> Union[CaseType, return None -async def get_all_casetypes(guild: discord.Guild=None) -> List[CaseType]: +async def get_all_casetypes(guild: discord.Guild = None) -> List[CaseType]: """ Get all currently registered case types @@ -538,8 +562,8 @@ async def get_all_casetypes(guild: discord.Guild=None) -> List[CaseType]: async def register_casetype( - name: str, default_setting: bool, - image: str, case_str: str, audit_type: str=None) -> CaseType: + name: str, default_setting: bool, image: str, case_str: str, audit_type: str = None +) -> CaseType: """ Registers a case type. If the case type exists and there are differences between the values passed and @@ -586,7 +610,7 @@ async def register_casetype( raise ValueError("The 'image' is not a string!") if not isinstance(case_str, str): raise ValueError("The 'case_str' is not a string!") - if audit_type is not None: + if audit_type is not None: if not isinstance(audit_type, str): raise ValueError("The 'audit_type' is not a string!") try: @@ -665,8 +689,7 @@ async def register_casetypes(new_types: List[dict]) -> List[CaseType]: return type_list -async def get_modlog_channel(guild: discord.Guild - ) -> Union[discord.TextChannel, None]: +async def get_modlog_channel(guild: discord.Guild) -> Union[discord.TextChannel, None]: """ Get the current modlog channel @@ -695,8 +718,9 @@ async def get_modlog_channel(guild: discord.Guild return channel -async def set_modlog_channel(guild: discord.Guild, - channel: Union[discord.TextChannel, None]) -> bool: +async def set_modlog_channel( + guild: discord.Guild, channel: Union[discord.TextChannel, None] +) -> bool: """ Changes the modlog channel @@ -713,9 +737,7 @@ async def set_modlog_channel(guild: discord.Guild, `True` if successful """ - await _conf.guild(guild).mod_log.set( - channel.id if hasattr(channel, "id") else None - ) + await _conf.guild(guild).mod_log.set(channel.id if hasattr(channel, "id") else None) return True @@ -741,19 +763,19 @@ async def reset_cases(guild: discord.Guild) -> bool: def _strfdelta(delta): s = [] if delta.days: - ds = '%i day' % delta.days + ds = "%i day" % delta.days if delta.days > 1: - ds += 's' + ds += "s" s.append(ds) - hrs, rem = divmod(delta.seconds, 60*60) + hrs, rem = divmod(delta.seconds, 60 * 60) if hrs: - hs = '%i hr' % hrs + hs = "%i hr" % hrs if hrs > 1: - hs += 's' + hs += "s" s.append(hs) mins, secs = divmod(rem, 60) if mins: - s.append('%i min' % mins) + s.append("%i min" % mins) if secs: - s.append('%i sec' % secs) - return ' '.join(s) + s.append("%i sec" % secs) + return " ".join(s) diff --git a/redbot/core/rpc.py b/redbot/core/rpc.py index 323477406..5828c5e3e 100644 --- a/redbot/core/rpc.py +++ b/redbot/core/rpc.py @@ -10,8 +10,8 @@ from .utils import TYPE_CHECKING, NewType if TYPE_CHECKING: from .bot import Red -log = logging.getLogger('red.rpc') -JsonSerializable = NewType('JsonSerializable', dict) +log = logging.getLogger("red.rpc") +JsonSerializable = NewType("JsonSerializable", dict) _rpc = JsonRpc(logger=log) @@ -22,13 +22,13 @@ async def initialize(bot: "Red"): global _rpc_server app = Application(loop=bot.loop) - app.router.add_route('*', '/rpc', _rpc) + app.router.add_route("*", "/rpc", _rpc) handler = app.make_handler() - _rpc_server = await bot.loop.create_server(handler, '127.0.0.1', 6133) + _rpc_server = await bot.loop.create_server(handler, "127.0.0.1", 6133) - log.debug('Created RPC _rpc_server listener.') + log.debug("Created RPC _rpc_server listener.") def add_topic(topic_name: str): @@ -77,10 +77,7 @@ def add_method(prefix, method): method MUST BE A COROUTINE OR OBJECT. """ - _rpc.add_methods( - ('', method), - prefix=prefix - ) + _rpc.add_methods(("", method), prefix=prefix) def clean_up(): diff --git a/redbot/core/sentry.py b/redbot/core/sentry.py index e1808f04b..f0d6e20d6 100644 --- a/redbot/core/sentry.py +++ b/redbot/core/sentry.py @@ -12,11 +12,13 @@ class SentryManager: def __init__(self, logger: logging.Logger): self.client = Client( - dsn=("https://62402161d4cd4ef18f83b16f3e22a020:9310ef55a502442598203205a84da2bb@" - "sentry.io/253983"), + dsn=( + "https://62402161d4cd4ef18f83b16f3e22a020:9310ef55a502442598203205a84da2bb@" + "sentry.io/253983" + ), release=__version__, - include_paths=['redbot'], - enable_breadcrumbs=False + include_paths=["redbot"], + enable_breadcrumbs=False, ) self.handler = SentryHandler(self.client) self.logger = logger diff --git a/redbot/core/utils/__init__.py b/redbot/core/utils/__init__.py index 13011d008..59dffe0cb 100644 --- a/redbot/core/utils/__init__.py +++ b/redbot/core/utils/__init__.py @@ -1,4 +1,4 @@ -__all__ = ['TYPE_CHECKING', 'NewType', 'safe_delete'] +__all__ = ["TYPE_CHECKING", "NewType", "safe_delete"] from pathlib import Path import os @@ -12,6 +12,7 @@ except ImportError: try: from typing import NewType except ImportError: + def NewType(name, tp): return type(name, (tp,), {}) diff --git a/redbot/core/utils/antispam.py b/redbot/core/utils/antispam.py index 5352ece30..d80c7072c 100644 --- a/redbot/core/utils/antispam.py +++ b/redbot/core/utils/antispam.py @@ -3,7 +3,7 @@ from typing import Tuple, List from collections import namedtuple Interval = Tuple[timedelta, int] -AntiSpamInterval = namedtuple('AntiSpamInterval', ['period', 'frequency']) +AntiSpamInterval = namedtuple("AntiSpamInterval", ["period", "frequency"]) class AntiSpam: @@ -26,21 +26,18 @@ class AntiSpam: (timedelta(seconds=5), 3), (timedelta(minutes=1), 5), (timedelta(hours=1), 10), - (timedelta(days=1), 24) + (timedelta(days=1), 24), ] def __init__(self, intervals: List[Interval]): self.__event_timestamps = [] _itvs = intervals if intervals else self.default_intervals - self.__intervals = [ - AntiSpamInterval(*x) for x in _itvs - ] + self.__intervals = [AntiSpamInterval(*x) for x in _itvs] self.__discard_after = max([x.period for x in self.__intervals]) def __interval_check(self, interval: AntiSpamInterval): return len( - [t for t in self.__event_timestamps - if (t + interval.period) > datetime.utcnow()] + [t for t in self.__event_timestamps if (t + interval.period) > datetime.utcnow()] ) >= interval.frequency @property @@ -57,6 +54,5 @@ class AntiSpam: """ self.__event_timestamps.append(datetime.utcnow()) self.__event_timestamps = [ - t for t in self.__event_timestamps - if t + self.__discard_after > datetime.utcnow() + t for t in self.__event_timestamps if t + self.__discard_after > datetime.utcnow() ] diff --git a/redbot/core/utils/chat_formatting.py b/redbot/core/utils/chat_formatting.py index f6620a332..14c43412a 100644 --- a/redbot/core/utils/chat_formatting.py +++ b/redbot/core/utils/chat_formatting.py @@ -1,6 +1,7 @@ import itertools from typing import Sequence, Iterator + def error(text: str) -> str: """Get text prefixed with an error emoji. @@ -66,7 +67,7 @@ def bold(text: str) -> str: return "**{}**".format(text) -def box(text: str, lang: str="") -> str: +def box(text: str, lang: str = "") -> str: """Get the given text in a code block. Parameters @@ -120,7 +121,7 @@ def italics(text: str) -> str: return "*{}*".format(text) -def bordered(*columns: Sequence[str], ascii_border: bool=False) -> str: +def bordered(*columns: Sequence[str], ascii_border: bool = False) -> str: """Get two blocks of text in a borders. Note @@ -141,18 +142,18 @@ def bordered(*columns: Sequence[str], ascii_border: bool=False) -> str: """ borders = { - 'TL': '-' if ascii_border else '┌', # Top-left - 'TR': '-' if ascii_border else '┐', # Top-right - 'BL': '-' if ascii_border else '└', # Bottom-left - 'BR': '-' if ascii_border else '┘', # Bottom-right - 'HZ': '-' if ascii_border else '─', # Horizontal - 'VT': '|' if ascii_border else '│', # Vertical + "TL": "-" if ascii_border else "┌", # Top-left + "TR": "-" if ascii_border else "┐", # Top-right + "BL": "-" if ascii_border else "└", # Bottom-left + "BR": "-" if ascii_border else "┘", # Bottom-right + "HZ": "-" if ascii_border else "─", # Horizontal + "VT": "|" if ascii_border else "│", # Vertical } - sep = ' ' * 4 # Separator between boxes + sep = " " * 4 # Separator between boxes widths = tuple(max(len(row) for row in column) + 9 for column in columns) # width of each col colsdone = [False] * len(columns) # whether or not each column is done - lines = [sep.join('{TL}' + '{HZ}'*width + '{TR}' for width in widths)] + lines = [sep.join("{TL}" + "{HZ}" * width + "{TR}" for width in widths)] for line in itertools.zip_longest(*columns): row = [] @@ -162,36 +163,38 @@ def bordered(*columns: Sequence[str], ascii_border: bool=False) -> str: if column is None: if not done: # bottom border of column - column = '{HZ}' * width - row.append('{BL}' + column + '{BR}') + column = "{HZ}" * width + row.append("{BL}" + column + "{BR}") colsdone[colidx] = True # mark column as done else: # leave empty - row.append(' ' * (width + 2)) + row.append(" " * (width + 2)) else: - column += ' ' * (width - len(column)) # append padded spaces - row.append('{VT}' + column + '{VT}') + column += " " * (width - len(column)) # append padded spaces + row.append("{VT}" + column + "{VT}") lines.append(sep.join(row)) final_row = [] for width, done in zip(widths, colsdone): if not done: - final_row.append('{BL}' + '{HZ}' * width + '{BR}') + final_row.append("{BL}" + "{HZ}" * width + "{BR}") else: - final_row.append(' ' * (width + 2)) + final_row.append(" " * (width + 2)) lines.append(sep.join(final_row)) return "\n".join(lines).format(**borders) -def pagify(text: str, - delims: Sequence[str]=["\n"], - *, - priority: bool=False, - escape_mass_mentions: bool=True, - shorten_by: int=8, - page_length: int=2000) -> Iterator[str]: +def pagify( + text: str, + delims: Sequence[str] = ["\n"], + *, + priority: bool = False, + escape_mass_mentions: bool = True, + shorten_by: int = 8, + page_length: int = 2000 +) -> Iterator[str]: """Generate multiple pages from the given text. Note @@ -232,10 +235,10 @@ def pagify(text: str, while len(in_text) > page_length: this_page_len = page_length if escape_mass_mentions: - this_page_len -= (in_text.count("@here", 0, page_length) + - in_text.count("@everyone", 0, page_length)) - closest_delim = (in_text.rfind(d, 1, this_page_len) - for d in delims) + this_page_len -= ( + in_text.count("@here", 0, page_length) + in_text.count("@everyone", 0, page_length) + ) + closest_delim = (in_text.rfind(d, 1, this_page_len) for d in delims) if priority: closest_delim = next((x for x in closest_delim if x > 0), -1) else: @@ -290,8 +293,7 @@ def underline(text: str) -> str: return "__{}__".format(text) -def escape(text: str, *, mass_mentions: bool=False, - formatting: bool=False) -> str: +def escape(text: str, *, mass_mentions: bool = False, formatting: bool = False) -> str: """Get text with all mass mentions or markdown escaped. Parameters @@ -313,8 +315,7 @@ def escape(text: str, *, mass_mentions: bool=False, text = text.replace("@everyone", "@\u200beveryone") text = text.replace("@here", "@\u200bhere") if formatting: - text = (text.replace("`", "\\`") - .replace("*", "\\*") - .replace("_", "\\_") - .replace("~", "\\~")) + text = ( + text.replace("`", "\\`").replace("*", "\\*").replace("_", "\\_").replace("~", "\\~") + ) return text diff --git a/redbot/core/utils/data_converter.py b/redbot/core/utils/data_converter.py index 39986483a..d01aaaa11 100644 --- a/redbot/core/utils/data_converter.py +++ b/redbot/core/utils/data_converter.py @@ -28,7 +28,7 @@ class DataConverter: The file isn't valid JSON """ try: - with file_path.open(mode='r', encoding='utf-8') as f: + with file_path.open(mode="r", encoding="utf-8") as f: data = json.load(f) except (FileNotFoundError, json.JSONDecodeError): raise diff --git a/redbot/core/utils/menus.py b/redbot/core/utils/menus.py index 50df3b649..deb0ee9eb 100644 --- a/redbot/core/utils/menus.py +++ b/redbot/core/utils/menus.py @@ -10,10 +10,14 @@ import discord from redbot.core import commands -async def menu(ctx: commands.Context, pages: list, - controls: dict, - message: discord.Message=None, page: int=0, - timeout: float=30.0): +async def menu( + ctx: commands.Context, + pages: list, + controls: dict, + message: discord.Message = None, + page: int = 0, + timeout: float = 30.0, +): """ An emoji-based menu @@ -48,8 +52,10 @@ async def menu(ctx: commands.Context, pages: list, RuntimeError If either of the notes above are violated """ - if not all(isinstance(x, discord.Embed) for x in pages) and\ - not all(isinstance(x, str) for x in pages): + if ( + not all(isinstance(x, discord.Embed) for x in pages) + and not all(isinstance(x, str) for x in pages) + ): raise RuntimeError("All pages must be of the same type") for key, value in controls.items(): if not asyncio.iscoroutinefunction(value): @@ -70,15 +76,10 @@ async def menu(ctx: commands.Context, pages: list, await message.edit(content=current_page) def react_check(r, u): - return u == ctx.author and r.message.id == message.id and \ - str(r.emoji) in controls.keys() + return u == ctx.author and r.message.id == message.id and str(r.emoji) in controls.keys() try: - react, user = await ctx.bot.wait_for( - "reaction_add", - check=react_check, - timeout=timeout - ) + react, user = await ctx.bot.wait_for("reaction_add", check=react_check, timeout=timeout) except asyncio.TimeoutError: try: await message.clear_reactions() @@ -87,14 +88,18 @@ async def menu(ctx: commands.Context, pages: list, await message.remove_reaction(key, ctx.bot.user) return None - return await controls[react.emoji](ctx, pages, controls, - message, page, - timeout, react.emoji) + return await controls[react.emoji](ctx, pages, controls, message, page, timeout, react.emoji) -async def next_page(ctx: commands.Context, pages: list, - controls: dict, message: discord.Message, page: int, - timeout: float, emoji: str): +async def next_page( + ctx: commands.Context, + pages: list, + controls: dict, + message: discord.Message, + page: int, + timeout: float, + emoji: str, +): perms = message.channel.permissions_for(ctx.guild.me) if perms.manage_messages: # Can manage messages, so remove react try: @@ -105,13 +110,18 @@ async def next_page(ctx: commands.Context, pages: list, page = 0 # Loop around to the first item else: page = page + 1 - return await menu(ctx, pages, controls, message=message, - page=page, timeout=timeout) + return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout) -async def prev_page(ctx: commands.Context, pages: list, - controls: dict, message: discord.Message, page: int, - timeout: float, emoji: str): +async def prev_page( + ctx: commands.Context, + pages: list, + controls: dict, + message: discord.Message, + page: int, + timeout: float, + emoji: str, +): perms = message.channel.permissions_for(ctx.guild.me) if perms.manage_messages: # Can manage messages, so remove react try: @@ -122,20 +132,21 @@ async def prev_page(ctx: commands.Context, pages: list, next_page = len(pages) - 1 # Loop around to the last item else: next_page = page - 1 - return await menu(ctx, pages, controls, message=message, - page=next_page, timeout=timeout) + return await menu(ctx, pages, controls, message=message, page=next_page, timeout=timeout) -async def close_menu(ctx: commands.Context, pages: list, - controls: dict, message: discord.Message, page: int, - timeout: float, emoji: str): +async def close_menu( + ctx: commands.Context, + pages: list, + controls: dict, + message: discord.Message, + page: int, + timeout: float, + emoji: str, +): if message: await message.delete() return None -DEFAULT_CONTROLS = { - "⬅": prev_page, - "❌": close_menu, - "➡": next_page -} \ No newline at end of file +DEFAULT_CONTROLS = {"⬅": prev_page, "❌": close_menu, "➡": next_page} diff --git a/redbot/core/utils/mod.py b/redbot/core/utils/mod.py index a19a6807b..26f36ae71 100644 --- a/redbot/core/utils/mod.py +++ b/redbot/core/utils/mod.py @@ -8,8 +8,7 @@ from redbot.core import Config from redbot.core.bot import Red -async def mass_purge(messages: List[discord.Message], - channel: discord.TextChannel): +async def mass_purge(messages: List[discord.Message], channel: discord.TextChannel): """Bulk delete messages from a channel. If more than 100 messages are supplied, the bot will delete 100 messages at @@ -80,24 +79,23 @@ def get_audit_reason(author: discord.Member, reason: str = None): The formatted audit log reason. """ - return \ - "Action requested by {} (ID {}). Reason: {}".format(author, author.id, reason) if reason else \ - "Action requested by {} (ID {}).".format(author, author.id) + return "Action requested by {} (ID {}). Reason: {}".format( + author, author.id, reason + ) if reason else "Action requested by {} (ID {}).".format( + author, author.id + ) -async def is_allowed_by_hierarchy(bot: Red, - settings: Config, - guild: discord.Guild, - mod: discord.Member, - user: discord.Member): +async def is_allowed_by_hierarchy( + bot: Red, settings: Config, guild: discord.Guild, mod: discord.Member, user: discord.Member +): if not await settings.guild(guild).respect_hierarchy(): return True is_special = mod == guild.owner or await bot.is_owner(mod) return mod.top_role.position > user.top_role.position or is_special -async def is_mod_or_superior( - bot: Red, obj: Union[discord.Message, discord.Member, discord.Role]): +async def is_mod_or_superior(bot: Red, obj: Union[discord.Message, discord.Member, discord.Role]): """Check if an object has mod or superior permissions. If a message is passed, its author's permissions are checked. If a role is @@ -129,7 +127,7 @@ async def is_mod_or_superior( elif isinstance(obj, discord.Role): pass else: - raise TypeError('Only messages, members or roles may be passed') + raise TypeError("Only messages, members or roles may be passed") server = obj.guild admin_role_id = await bot.db.guild(server).admin_role() @@ -168,26 +166,27 @@ def strfdelta(delta: timedelta): """ s = [] if delta.days: - ds = '%i day' % delta.days + ds = "%i day" % delta.days if delta.days > 1: - ds += 's' + ds += "s" s.append(ds) - hrs, rem = divmod(delta.seconds, 60*60) + hrs, rem = divmod(delta.seconds, 60 * 60) if hrs: - hs = '%i hr' % hrs + hs = "%i hr" % hrs if hrs > 1: - hs += 's' + hs += "s" s.append(hs) mins, secs = divmod(rem, 60) if mins: - s.append('%i min' % mins) + s.append("%i min" % mins) if secs: - s.append('%i sec' % secs) - return ' '.join(s) + s.append("%i sec" % secs) + return " ".join(s) async def is_admin_or_superior( - bot: Red, obj: Union[discord.Message, discord.Member, discord.Role]): + bot: Red, obj: Union[discord.Message, discord.Member, discord.Role] +): """Same as `is_mod_or_superior` except for admin permissions. If a message is passed, its author's permissions are checked. If a role is @@ -219,7 +218,7 @@ async def is_admin_or_superior( elif isinstance(obj, discord.Role): pass else: - raise TypeError('Only messages, members or roles may be passed') + raise TypeError("Only messages, members or roles may be passed") server = obj.guild admin_role_id = await bot.db.guild(server).admin_role() diff --git a/redbot/core/utils/tunnel.py b/redbot/core/utils/tunnel.py index df3c17a54..72328c2e9 100644 --- a/redbot/core/utils/tunnel.py +++ b/redbot/core/utils/tunnel.py @@ -16,10 +16,7 @@ class TunnelMeta(type): """ def __call__(cls, *args, **kwargs): - lockout_tuple = ( - (kwargs.get('sender'), kwargs.get('origin')), - kwargs.get('recipient') - ) + lockout_tuple = ((kwargs.get("sender"), kwargs.get("origin")), kwargs.get("recipient")) if lockout_tuple in _instances: return _instances[lockout_tuple] @@ -30,13 +27,8 @@ class TunnelMeta(type): while True: try: if not ( - any( - lockout_tuple[0] == x[0] - for x in _instances.keys() - ) or any( - lockout_tuple[1] == x[1] - for x in _instances.keys() - ) + any(lockout_tuple[0] == x[0] for x in _instances.keys()) + or any(lockout_tuple[1] == x[1] for x in _instances.keys()) ): # if this isn't temporarily stored, the weakref dict # will discard this before the return statement, @@ -70,10 +62,9 @@ class Tunnel(metaclass=TunnelMeta): The user on the other end of the tunnel """ - def __init__(self, *, - sender: discord.Member, - origin: discord.TextChannel, - recipient: discord.User): + def __init__( + self, *, sender: discord.Member, origin: discord.TextChannel, recipient: discord.User + ): self.sender = sender self.origin = origin self.recipient = recipient @@ -81,11 +72,8 @@ class Tunnel(metaclass=TunnelMeta): async def react_close(self, *, uid: int, message: str): send_to = self.origin if uid == self.sender.id else self.sender - closer = next(filter( - lambda x: x.id == uid, (self.sender, self.recipient)), None) - await send_to.send( - message.format(closer=closer) - ) + closer = next(filter(lambda x: x.id == uid, (self.sender, self.recipient)), None) + await send_to.send(message.format(closer=closer)) @property def members(self): @@ -97,8 +85,8 @@ class Tunnel(metaclass=TunnelMeta): @staticmethod async def message_forwarder( - *, destination: discord.abc.Messageable, - content: str=None, embed=None, files=[]) -> List[discord.Message]: + *, destination: discord.abc.Messageable, content: str = None, embed=None, files=[] + ) -> List[discord.Message]: """ This does the actual sending, use this instead of a full tunnel if you are using command initiated reactions instead of persistent @@ -131,18 +119,13 @@ class Tunnel(metaclass=TunnelMeta): files = files if files else None if content: for page in pagify(content): - rets.append( - await destination.send( - page, files=files, embed=embed) - ) + rets.append(await destination.send(page, files=files, embed=embed)) if files: del files if embed: del embed elif embed or files: - rets.append( - await destination.send(files=files, embed=embed) - ) + rets.append(await destination.send(files=files, embed=embed)) return rets @staticmethod @@ -172,15 +155,12 @@ class Tunnel(metaclass=TunnelMeta): size += sys.getsizeof(_fp) if size > max_size: return [] - files.append( - discord.File(_fp, filename=a.filename) - ) + files.append(discord.File(_fp, filename=a.filename)) return files - async def communicate(self, *, - message: discord.Message, - topic: str=None, - skip_message_content: bool=False): + async def communicate( + self, *, message: discord.Message, topic: str = None, skip_message_content: bool = False + ): """ Forwards a message. @@ -208,18 +188,15 @@ class Tunnel(metaclass=TunnelMeta): the bot can't upload at the origin channel or can't add reactions there. """ - if message.channel == self.origin \ - and message.author == self.sender: + if message.channel == self.origin and message.author == self.sender: send_to = self.recipient - elif message.author == self.recipient \ - and isinstance(message.channel, discord.DMChannel): + elif message.author == self.recipient and isinstance(message.channel, discord.DMChannel): send_to = self.origin else: return None if not skip_message_content: - content = "\n".join((topic, message.content)) if topic \ - else message.content + content = "\n".join((topic, message.content)) if topic else message.content else: content = topic @@ -234,11 +211,7 @@ class Tunnel(metaclass=TunnelMeta): else: attach = [] - rets = await self.message_forwarder( - destination=send_to, - content=content, - files=attach - ) + rets = await self.message_forwarder(destination=send_to, content=content, files=attach) await message.add_reaction("\N{WHITE HEAVY CHECK MARK}") await message.add_reaction("\N{NEGATIVE SQUARED CROSS MARK}") diff --git a/redbot/launcher.py b/redbot/launcher.py index b61dc0706..aa7e2abac 100644 --- a/redbot/launcher.py +++ b/redbot/launcher.py @@ -8,7 +8,14 @@ import asyncio import pkg_resources from pathlib import Path -from redbot.setup import basic_setup, load_existing_config, remove_instance, remove_instance_interaction, create_backup, save_config +from redbot.setup import ( + basic_setup, + load_existing_config, + remove_instance, + remove_instance_interaction, + create_backup, + save_config, +) from redbot.core.utils import safe_delete from redbot.core.cli import confirm @@ -18,9 +25,9 @@ if sys.platform == "linux": PYTHON_OK = sys.version_info >= (3, 5) INTERACTIVE_MODE = not len(sys.argv) > 1 # CLI flags = non-interactive -INTRO = ("==========================\n" - "Red Discord Bot - Launcher\n" - "==========================\n") +INTRO = ( + "==========================\n" "Red Discord Bot - Launcher\n" "==========================\n" +) IS_WINDOWS = os.name == "nt" IS_MAC = sys.platform == "darwin" @@ -31,35 +38,35 @@ def parse_cli_args(): description="Red - Discord Bot's launcher (V3)", allow_abbrev=False ) instances = load_existing_config() - parser.add_argument("instancename", metavar="instancename", type=str, - nargs="?", help="The instance to run", choices=list(instances.keys())) - parser.add_argument("--start", "-s", - help="Starts Red", - action="store_true") - parser.add_argument("--auto-restart", - help="Autorestarts Red in case of issues", - action="store_true") - parser.add_argument("--update", - help="Updates Red", - action="store_true") - parser.add_argument("--update-dev", - help="Updates Red from the Github repo", - action="store_true") - parser.add_argument("--voice", - help="Installs extra 'voice' when updating", - action="store_true") - parser.add_argument("--docs", - help="Installs extra 'docs' when updating", - action="store_true") - parser.add_argument("--test", - help="Installs extra 'test' when updating", - action="store_true") - parser.add_argument("--mongo", - help="Installs extra 'mongo' when updating", - action="store_true") - parser.add_argument("--debuginfo", - help="Prints basic debug info that would be useful for support", - action="store_true") + parser.add_argument( + "instancename", + metavar="instancename", + type=str, + nargs="?", + help="The instance to run", + choices=list(instances.keys()), + ) + parser.add_argument("--start", "-s", help="Starts Red", action="store_true") + parser.add_argument( + "--auto-restart", help="Autorestarts Red in case of issues", action="store_true" + ) + parser.add_argument("--update", help="Updates Red", action="store_true") + parser.add_argument( + "--update-dev", help="Updates Red from the Github repo", action="store_true" + ) + parser.add_argument( + "--voice", help="Installs extra 'voice' when updating", action="store_true" + ) + parser.add_argument("--docs", help="Installs extra 'docs' when updating", action="store_true") + parser.add_argument("--test", help="Installs extra 'test' when updating", action="store_true") + parser.add_argument( + "--mongo", help="Installs extra 'mongo' when updating", action="store_true" + ) + parser.add_argument( + "--debuginfo", + help="Prints basic debug info that would be useful for support", + action="store_true", + ) return parser.parse_known_args() @@ -97,20 +104,24 @@ def update_red(dev=False, reinstall=False, voice=False, mongo=False, docs=False, if egg_l: package += "[{}]".format(", ".join(egg_l)) if reinstall: - code = subprocess.call([ - interpreter, "-m", - "pip", "install", "-U", "-I", - "--force-reinstall", "--no-cache-dir", - "--process-dependency-links", - package - ]) + code = subprocess.call( + [ + interpreter, + "-m", + "pip", + "install", + "-U", + "-I", + "--force-reinstall", + "--no-cache-dir", + "--process-dependency-links", + package, + ] + ) else: - code = subprocess.call([ - interpreter, "-m", - "pip", "install", "-U", - "--process-dependency-links", - package - ]) + code = subprocess.call( + [interpreter, "-m", "pip", "install", "-U", "--process-dependency-links", package] + ) if code == 0: print("Red has been updated") else: @@ -123,7 +134,7 @@ def update_red(dev=False, reinstall=False, voice=False, mongo=False, docs=False, os.rename(new_name, old_name) -def run_red(selected_instance, autorestart: bool=False, cliflags=None): +def run_red(selected_instance, autorestart: bool = False, cliflags=None): while True: print("Starting {}...".format(selected_instance)) cmd_list = ["redbot", selected_instance] @@ -153,12 +164,15 @@ def cli_flag_getter(): if choice == "y": print( "Enter the prefixes, separated by a space (please note " - "that prefixes containing a space will need to be added with [p]set prefix)") + "that prefixes containing a space will need to be added with [p]set prefix)" + ) prefixes = user_choice().split() for p in prefixes: flags.append("-p {}".format(p)) - print("Would you like to disable console input? Please note that features " - "requiring console interaction may fail to work (y/n)") + print( + "Would you like to disable console input? Please note that features " + "requiring console interaction may fail to work (y/n)" + ) choice = user_choice() if choice == "y": flags.append("--no-prompt") @@ -169,9 +183,11 @@ def cli_flag_getter(): print("Is this a selfbot? (y/n)") choice = user_choice() if choice == "y": - print("Please note that selfbots are not allowed by Discord. See" - "https://support.discordapp.com/hc/en-us/articles/115002192352-Automated-user-accounts-self-bots-" - "for more information.") + print( + "Please note that selfbots are not allowed by Discord. See" + "https://support.discordapp.com/hc/en-us/articles/115002192352-Automated-user-accounts-self-bots-" + "for more information." + ) flags.append("--self-bot") print("Does this token belong to a user account rather than a bot account? (y/n)") choice = user_choice() @@ -185,7 +201,9 @@ def cli_flag_getter(): choice = user_choice() if choice == "y": flags.append("--debug") - print("Do you want the Dev cog loaded (thus enabling commands such as debug and repl)? (y/n)") + print( + "Do you want the Dev cog loaded (thus enabling commands such as debug and repl)? (y/n)" + ) choice = user_choice() if choice == "y": flags.append("--dev") @@ -218,8 +236,8 @@ def instance_menu(): name_num_map = {} for name in list(instances.keys()): - print("{}. {}\n".format(counter+1, name)) - name_num_map[str(counter+1)] = name + print("{}. {}\n".format(counter + 1, name)) + name_num_map[str(counter + 1)] = name counter += 1 while True: @@ -229,7 +247,7 @@ def instance_menu(): except ValueError: print("Invalid input! Please enter a number corresponding to an instance.") else: - if selection not in list(range(1, counter+1)): + if selection not in list(range(1, counter + 1)): print("Invalid selection! Please try again") else: return name_num_map[str(selection)] @@ -237,13 +255,15 @@ def instance_menu(): async def reset_red(): instances = load_existing_config() - + if not instances: print("No instance to delete.\n") return print("WARNING: You are about to remove ALL Red instances on this computer.") - print("If you want to reset data of only one instance, " - "please select option 5 in the launcher.") + print( + "If you want to reset data of only one instance, " + "please select option 5 in the launcher." + ) await asyncio.sleep(2) print("\nIf you continue you will remove these instanes.\n") for instance in list(instances.keys()): @@ -254,7 +274,7 @@ async def reset_red(): if response != "I agree": print("Cancelling...") return - + if confirm("\nDo you want to create a backup for an instance? (y/n) "): for index, instance in instances.items(): print("\nRemoving {}...".format(index)) @@ -264,7 +284,7 @@ async def reset_red(): for index, instance in instances.items(): await remove_instance(index, instance) print("All instances have been removed.") - + def clear_screen(): if IS_WINDOWS: @@ -290,7 +310,7 @@ def extras_selector(): return selected -def development_choice(reinstall = False): +def development_choice(reinstall=False): while True: print("\n") print("Do you want to install stable or development version?") @@ -301,18 +321,22 @@ def development_choice(reinstall = False): selected = extras_selector() if choice == "1": update_red( - dev=False, reinstall=reinstall, voice=True if "voice" in selected else False, + dev=False, + reinstall=reinstall, + voice=True if "voice" in selected else False, docs=True if "docs" in selected else False, test=True if "test" in selected else False, - mongo=True if "mongo" in selected else False + mongo=True if "mongo" in selected else False, ) break elif choice == "2": update_red( - dev=True, reinstall=reinstall, voice=True if "voice" in selected else False, + dev=True, + reinstall=reinstall, + voice=True if "voice" in selected else False, docs=True if "docs" in selected else False, test=True if "test" in selected else False, - mongo=True if "mongo" in selected else False + mongo=True if "mongo" in selected else False, ) break @@ -332,12 +356,17 @@ def debug_info(): os_info = distro.linux_distribution() osver = "{} {}".format(os_info[0], os_info[1]).strip() user_who_ran = getpass.getuser() - info = "Debug Info for Red\n\n" +\ - "Python version: {}\n".format(pyver) +\ - "Red version: {}\n".format(redver) +\ - "OS version: {}\n".format(osver) +\ - "System arch: {}\n".format(platform.machine()) +\ - "User: {}\n".format(user_who_ran) + info = "Debug Info for Red\n\n" + "Python version: {}\n".format( + pyver + ) + "Red version: {}\n".format( + redver + ) + "OS version: {}\n".format( + osver + ) + "System arch: {}\n".format( + platform.machine() + ) + "User: {}\n".format( + user_who_ran + ) print(info) exit(0) @@ -385,7 +414,9 @@ def main_menu(): loop = asyncio.get_event_loop() clear_screen() print("==== Reinstall Red ====") - print("1. Reinstall Red requirements (discard code changes, keep data and 3rd party cogs)") + print( + "1. Reinstall Red requirements (discard code changes, keep data and 3rd party cogs)" + ) print("2. Reset all data") print("3. Factory reset (discard code changes, reset all data)") print("\n") @@ -411,27 +442,20 @@ def main_menu(): def main(): if not PYTHON_OK: raise RuntimeError( - "Red requires Python 3.5 or greater. " - "Please install the correct version!" + "Red requires Python 3.5 or greater. " "Please install the correct version!" ) if args.debuginfo: # Check first since the function triggers an exit debug_info() - + if args.update and args.update_dev: # Conflicting args, so error out raise RuntimeError( "\nUpdate requested but conflicting arguments provided.\n\n" "Please try again using only one of --update or --update-dev" ) if args.update: - update_red( - voice=args.voice, docs=args.docs, - test=args.test, mongo=args.mongo - ) + update_red(voice=args.voice, docs=args.docs, test=args.test, mongo=args.mongo) elif args.update_dev: - update_red( - dev=True, voice=args.voice, docs=args.docs, - test=args.test, mongo=args.mongo - ) + update_red(dev=True, voice=args.voice, docs=args.docs, test=args.test, mongo=args.mongo) if INTERACTIVE_MODE: main_menu() diff --git a/redbot/setup.py b/redbot/setup.py index bcb88a793..ad7a75230 100644 --- a/redbot/setup.py +++ b/redbot/setup.py @@ -20,7 +20,7 @@ from redbot.core.drivers.red_json import JSON config_dir = None appdir = appdirs.AppDirs("Red-DiscordBot") -if sys.platform == 'linux': +if sys.platform == "linux": if 0 < os.getuid() < 1000: config_dir = Path(appdir.site_data_dir) if not config_dir: @@ -28,27 +28,17 @@ if not config_dir: try: config_dir.mkdir(parents=True, exist_ok=True) except PermissionError: - print( - "You don't have permission to write to " - "'{}'\nExiting...".format(config_dir)) + print("You don't have permission to write to " "'{}'\nExiting...".format(config_dir)) sys.exit(1) -config_file = config_dir / 'config.json' +config_file = config_dir / "config.json" def parse_cli_args(): - parser = argparse.ArgumentParser( - description="Red - Discord Bot's instance manager (V3)" - ) + parser = argparse.ArgumentParser(description="Red - Discord Bot's instance manager (V3)") parser.add_argument( - "--delete", "-d", - help="Interactively delete an instance", - action="store_true" - ) - parser.add_argument( - "--edit", "-e", - help="Interactively edit an instance", - action="store_true" + "--delete", "-d", help="Interactively delete an instance", action="store_true" ) + parser.add_argument("--edit", "-e", help="Interactively edit an instance", action="store_true") return parser.parse_known_args() @@ -79,18 +69,20 @@ def save_config(name, data, remove=False): def get_data_dir(): default_data_dir = Path(appdir.user_data_dir) - print("Hello! Before we begin the full configuration process we need to" - " gather some initial information about where you'd like us" - " to store your bot's data. We've attempted to figure out a" - " sane default data location which is printed below. If you don't" - " want to change this default please press [ENTER], otherwise" - " input your desired data location.") + print( + "Hello! Before we begin the full configuration process we need to" + " gather some initial information about where you'd like us" + " to store your bot's data. We've attempted to figure out a" + " sane default data location which is printed below. If you don't" + " want to change this default please press [ENTER], otherwise" + " input your desired data location." + ) print() print("Default: {}".format(default_data_dir)) - new_path = input('> ') + new_path = input("> ") - if new_path != '': + if new_path != "": new_path = Path(new_path) default_data_dir = new_path @@ -98,13 +90,14 @@ def get_data_dir(): try: default_data_dir.mkdir(parents=True, exist_ok=True) except OSError: - print("We were unable to create your chosen directory." - " You may need to restart this process with admin" - " privileges.") + print( + "We were unable to create your chosen directory." + " You may need to restart this process with admin" + " privileges." + ) sys.exit(1) - print("You have chosen {} to be your data directory." - "".format(default_data_dir)) + print("You have chosen {} to be your data directory." "".format(default_data_dir)) if not confirm("Please confirm (y/n):"): print("Please start the process over.") sys.exit(0) @@ -112,10 +105,7 @@ def get_data_dir(): def get_storage_type(): - storage_dict = { - 1: "JSON", - 2: "MongoDB" - } + storage_dict = {1: "JSON", 2: "MongoDB"} storage = None while storage is None: print() @@ -137,8 +127,10 @@ def get_name(): name = "" while len(name) == 0: print() - print("Please enter a name for your instance, this name cannot include spaces" - " and it will be used to run your bot from here on out.") + print( + "Please enter a name for your instance, this name cannot include spaces" + " and it will be used to run your bot from here on out." + ) name = input("> ") if " " in name: name = "" @@ -154,41 +146,40 @@ def basic_setup(): default_data_dir = get_data_dir() default_dirs = deepcopy(basic_config_default) - default_dirs['DATA_PATH'] = str(default_data_dir.resolve()) + default_dirs["DATA_PATH"] = str(default_data_dir.resolve()) storage = get_storage_type() - storage_dict = { - 1: "JSON", - 2: "MongoDB" - } - default_dirs['STORAGE_TYPE'] = storage_dict.get(storage, 1) + storage_dict = {1: "JSON", 2: "MongoDB"} + default_dirs["STORAGE_TYPE"] = storage_dict.get(storage, 1) if storage_dict.get(storage, 1) == "MongoDB": from redbot.core.drivers.red_mongo import get_config_details - default_dirs['STORAGE_DETAILS'] = get_config_details() + + default_dirs["STORAGE_DETAILS"] = get_config_details() else: - default_dirs['STORAGE_DETAILS'] = {} + default_dirs["STORAGE_DETAILS"] = {} name = get_name() save_config(name, default_dirs) print() - print("Your basic configuration has been saved. Please run `redbot ` to" - " continue your setup process and to run the bot.") + print( + "Your basic configuration has been saved. Please run `redbot ` to" + " continue your setup process and to run the bot." + ) async def json_to_mongo(current_data_dir: Path, storage_details: dict): from redbot.core.drivers.red_mongo import Mongo + core_data_file = list(current_data_dir.glob("core/settings.json"))[0] m = Mongo("Core", "0", **storage_details) with core_data_file.open(mode="r") as f: core_data = json.loads(f.read()) collection = m.get_collection() await collection.update_one( - {'_id': m.unique_cog_identifier}, - update={"$set": core_data["0"]}, - upsert=True + {"_id": m.unique_cog_identifier}, update={"$set": core_data["0"]}, upsert=True ) for p in current_data_dir.glob("cogs/**/settings.json"): with p.open(mode="r") as f: @@ -200,14 +191,13 @@ async def json_to_mongo(current_data_dir: Path, storage_details: dict): cog_c = cog_m.get_collection() for ident in list(cog_data.keys()): await cog_c.update_one( - {"_id": cog_m.unique_cog_identifier}, - update={"$set": cog_data[cog_i]}, - upsert=True + {"_id": cog_m.unique_cog_identifier}, update={"$set": cog_data[cog_i]}, upsert=True ) async def mongo_to_json(current_data_dir: Path, storage_details: dict): from redbot.core.drivers.red_mongo import Mongo + m = Mongo("Core", "0", **storage_details) db = m.db collection_names = await db.collection_names(include_system_collections=False) @@ -250,9 +240,7 @@ async def edit_instance(): default_dirs = deepcopy(basic_config_default) current_data_dir = Path(instance_data["DATA_PATH"]) - print( - "You have selected '{}' as the instance to modify.".format(selected) - ) + print("You have selected '{}' as the instance to modify.".format(selected)) if not confirm("Please confirm (y/n):"): print("Ok, we will not continue then.") return @@ -273,13 +261,11 @@ async def edit_instance(): if confirm("Would you like to change the storage type? (y/n):"): storage = get_storage_type() - storage_dict = { - 1: "JSON", - 2: "MongoDB" - } + storage_dict = {1: "JSON", 2: "MongoDB"} default_dirs["STORAGE_TYPE"] = storage_dict[storage] if storage_dict.get(storage, 1) == "MongoDB": from redbot.core.drivers.red_mongo import get_config_details + storage_details = get_config_details() default_dirs["STORAGE_DETAILS"] = storage_details @@ -297,9 +283,7 @@ async def edit_instance(): save_config(selected, {}, remove=True) save_config(name, default_dirs) - print( - "Your basic configuration has been edited" - ) + print("Your basic configuration has been edited") async def create_backup(selected, instance_data): @@ -317,10 +301,8 @@ async def create_backup(selected, instance_data): os.chdir(str(pth.parent)) with tarfile.open(str(backup_file), "w:gz") as tar: tar.add(pth.stem) - print("A backup of {} has been made. It is at {}".format( - selected, backup_file - )) - + print("A backup of {} has been made. It is at {}".format(selected, backup_file)) + else: print("Backing up the instance's data...") backup_filename = "redv3-{}-{}.tar.gz".format( @@ -333,12 +315,8 @@ async def create_backup(selected, instance_data): os.chdir(str(pth.parent)) # str is used here because 3.5 support with tarfile.open(str(backup_file), "w:gz") as tar: tar.add(pth.stem) # add all files in that directory - print( - "A backup of {} has been made. It is at {}".format( - selected, backup_file - ) - ) - + print("A backup of {} has been made. It is at {}".format(selected, backup_file)) + async def remove_instance(selected, instance_data): instance_list = load_existing_config() @@ -370,12 +348,12 @@ async def remove_instance_interaction(): print("{}\n".format(instance)) print("Please select one of the above by entering its name") selected = input("> ") - + if selected not in instance_list.keys(): print("That isn't a valid instance!") return instance_data = instance_list[selected] - + await create_backup(selected, instance_data) await remove_instance(selected, instance_data) @@ -390,6 +368,7 @@ def main(): else: basic_setup() + args, _ = parse_cli_args() if __name__ == "__main__":