diff --git a/rolemanagement/abc.py b/rolemanagement/abc.py index 2bf2d96..3acd00f 100644 --- a/rolemanagement/abc.py +++ b/rolemanagement/abc.py @@ -28,9 +28,7 @@ class MixinMeta(ABC): raise NotImplementedError() @abstractmethod - async def is_self_assign_eligible( - self, who: discord.Member, role: discord.Role - ) -> List[discord.Role]: + async def is_self_assign_eligible(self, who: discord.Member, role: discord.Role) -> List[discord.Role]: raise NotImplementedError() @abstractmethod diff --git a/rolemanagement/converters.py b/rolemanagement/converters.py index 56235cd..c5a1da0 100644 --- a/rolemanagement/converters.py +++ b/rolemanagement/converters.py @@ -19,9 +19,7 @@ class RoleSyntaxConverter(NamedTuple): @classmethod async def convert(cls, ctx: Context, argument: str): - parser = NoExitParser( - description="Role management syntax help", add_help=False, allow_abbrev=True - ) + parser = NoExitParser(description="Role management syntax help", add_help=False, allow_abbrev=True) parser.add_argument("--add", nargs="*", dest="add", default=[]) parser.add_argument("--remove", nargs="*", dest="remove", default=[]) try: @@ -68,9 +66,7 @@ class ComplexActionConverter(NamedTuple): parser.add_argument("--has-any", nargs="*", dest="any", default=[]) parser.add_argument("--has-all", nargs="*", dest="all", default=[]) parser.add_argument("--has-none", nargs="*", dest="none", default=[]) - parser.add_argument( - "--has-no-roles", action="store_true", default=False, dest="noroles" - ) + parser.add_argument("--has-no-roles", action="store_true", default=False, dest="noroles") parser.add_argument("--has-perms", nargs="*", dest="hasperm", default=[]) parser.add_argument("--any-perm", nargs="*", dest="anyperm", default=[]) parser.add_argument("--not-perm", nargs="*", dest="notperm", default=[]) @@ -82,15 +78,9 @@ class ComplexActionConverter(NamedTuple): parser.add_argument("--above", dest="above", type=str, default=None) parser.add_argument("--below", dest="below", type=str, default=None) hum_or_bot = parser.add_mutually_exclusive_group() - hum_or_bot.add_argument( - "--only-humans", action="store_true", default=False, dest="humans" - ) - hum_or_bot.add_argument( - "--only-bots", action="store_true", default=False, dest="bots" - ) - hum_or_bot.add_argument( - "--everyone", action="store_true", default=False, dest="everyone" - ) + hum_or_bot.add_argument("--only-humans", action="store_true", default=False, dest="humans") + hum_or_bot.add_argument("--only-bots", action="store_true", default=False, dest="bots") + hum_or_bot.add_argument("--everyone", action="store_true", default=False, dest="everyone") try: vals = vars(parser.parse_args(shlex.split(argument))) @@ -131,10 +121,7 @@ class ComplexActionConverter(NamedTuple): for attr in ("hasperm", "anyperm", "notperm"): - vals[attr] = [ - i.replace("_", " ").lower().replace(" ", "_").replace("server", "guild") - for i in vals[attr] - ] + vals[attr] = [i.replace("_", " ").lower().replace(" ", "_").replace("server", "guild") for i in vals[attr]] if any(perm not in dir(discord.Permissions) for perm in vals[attr]): raise BadArgument("You gave an invalid permission") @@ -169,30 +156,20 @@ class ComplexSearchConverter(NamedTuple): parser.add_argument("--has-any", nargs="*", dest="any", default=[]) parser.add_argument("--has-all", nargs="*", dest="all", default=[]) parser.add_argument("--has-none", nargs="*", dest="none", default=[]) - parser.add_argument( - "--has-no-roles", action="store_true", default=False, dest="noroles" - ) + parser.add_argument("--has-no-roles", action="store_true", default=False, dest="noroles") parser.add_argument("--has-perms", nargs="*", dest="hasperm", default=[]) parser.add_argument("--any-perm", nargs="*", dest="anyperm", default=[]) parser.add_argument("--not-perm", nargs="*", dest="notperm", default=[]) parser.add_argument("--csv", action="store_true", default=False) - parser.add_argument( - "--has-exactly-nroles", dest="quantity", type=int, default=None - ) + parser.add_argument("--has-exactly-nroles", dest="quantity", type=int, default=None) parser.add_argument("--has-more-than-nroles", dest="gt", type=int, default=None) parser.add_argument("--has-less-than-nroles", dest="lt", type=int, default=None) parser.add_argument("--above", dest="above", type=str, default=None) parser.add_argument("--below", dest="below", type=str, default=None) hum_or_bot = parser.add_mutually_exclusive_group() - hum_or_bot.add_argument( - "--only-humans", action="store_true", default=False, dest="humans" - ) - hum_or_bot.add_argument( - "--only-bots", action="store_true", default=False, dest="bots" - ) - hum_or_bot.add_argument( - "--everyone", action="store_true", default=False, dest="everyone" - ) + hum_or_bot.add_argument("--only-humans", action="store_true", default=False, dest="humans") + hum_or_bot.add_argument("--only-bots", action="store_true", default=False, dest="bots") + hum_or_bot.add_argument("--everyone", action="store_true", default=False, dest="everyone") try: vals = vars(parser.parse_args(shlex.split(argument))) except Exception: @@ -229,10 +206,7 @@ class ComplexSearchConverter(NamedTuple): for attr in ("hasperm", "anyperm", "notperm"): - vals[attr] = [ - i.replace("_", " ").lower().replace(" ", "_").replace("server", "guild") - for i in vals[attr] - ] + vals[attr] = [i.replace("_", " ").lower().replace(" ", "_").replace("server", "guild") for i in vals[attr]] if any(perm not in dir(discord.Permissions) for perm in vals[attr]): raise BadArgument("You gave an invalid permission") diff --git a/rolemanagement/core.py b/rolemanagement/core.py index 5711961..630ed6f 100644 --- a/rolemanagement/core.py +++ b/rolemanagement/core.py @@ -38,15 +38,13 @@ class CompositeMetaClass(DPYCogMeta, ABCMeta): pass # MRO is fine on __new__ with super() use # no need to manually ensure both get handled here. + MIN_SUB_TIME = 3600 SLEEP_TIME = 300 + class RoleManagement( - UtilMixin, - MassManagementMixin, - EventMixin, - commands.Cog, - metaclass=CompositeMetaClass, + UtilMixin, MassManagementMixin, EventMixin, commands.Cog, metaclass=CompositeMetaClass, ): """ Cog for role management @@ -61,12 +59,8 @@ class RoleManagement( def __init__(self, bot): self.bot = bot - self.config = Config.get_conf( - self, identifier=78631113035100160, force_registration=True - ) - self.config.register_global( - handled_variation=False, handled_full_str_emoji=False - ) + self.config = Config.get_conf(self, identifier=78631113035100160, force_registration=True) + self.config.register_global(handled_variation=False, handled_full_str_emoji=False) self.config.register_role( exclusive_to=[], requires_any=[], @@ -77,8 +71,8 @@ class RoleManagement( protected=False, cost=0, subscription=0, - subscribed_users={} - )#subscribed_users maps str(user.id)-> end time in unix timestamp + subscribed_users={}, + ) # subscribed_users maps str(user.id)-> end time in unix timestamp self.config.register_member(roles=[], forbidden=[]) self.config.init_custom("REACTROLE", 2) self.config.register_custom( @@ -155,7 +149,7 @@ class RoleManagement( now_time = time.time() if end_time <= now_time: member = guild.get_member(int(user_id)) - if not member: # clean absent members + if not member: # clean absent members del role_data["subscribed_users"][user_id] continue # charge user @@ -172,7 +166,7 @@ class RoleManagement( await bank.withdraw_credits(member, cost) msg += f"\n\nNo further action is required! You'll be charged again in {parse_seconds(curr_sub)}." role_data["subscribed_users"][user_id] = now_time + curr_sub - except ValueError: # user is poor + except ValueError: # user is poor msg += f"\n\nHowever, you do not have enough {currency_name} to cover the subscription. The role will be removed." await self.update_roles_atomically(who=member, remove=[role]) del role_data["subscribed_users"][user_id] @@ -202,7 +196,7 @@ class RoleManagement( async with self.config.guild(guild).s_roles() as s_roles: for role_id in reversed(s_roles): role = guild.get_role(role_id) - if not role: # clean stale subs if role is deleted + if not role: # clean stale subs if role is deleted s_roles.remove(role_id) continue @@ -210,9 +204,7 @@ class RoleManagement( role_data = await self.sub_helper(guild, role, role_data) - await self.config.role(role).subscribed_users.set( - role_data["subscribed_users"] - ) + await self.config.role(role).subscribed_users.set(role_data["subscribed_users"]) if len(role_data["subscribed_users"]) == 0: s_roles.remove(role_id) @@ -226,9 +218,7 @@ class RoleManagement( """ if not await self.all_are_valid_roles(ctx, role): - return await ctx.maybe_send_embed( - "Can't do that. Discord role heirarchy applies here." - ) + return await ctx.maybe_send_embed("Can't do that. Discord role heirarchy applies here.") if not await self.config.role(role).sticky(): return await ctx.send("This only works on sticky roles.") @@ -244,9 +234,7 @@ class RoleManagement( await ctx.maybe_send_embed("They are in the guild...assigned anyway.") else: - async with self.config.member_from_ids( - ctx.guild.id, user_id - ).roles() as sticky: + async with self.config.member_from_ids(ctx.guild.id, user_id).roles() as sticky: if role.id not in sticky: sticky.append(role.id) @@ -292,12 +280,7 @@ class RoleManagement( @checks.admin_or_permissions(manage_guild=True) @commands.command(name="rolebind") async def bind_role_to_reactions( - self, - ctx: GuildContext, - role: discord.Role, - channel: discord.TextChannel, - msgid: int, - emoji: str, + self, ctx: GuildContext, role: discord.Role, channel: discord.TextChannel, msgid: int, emoji: str, ): """ Binds a role to a reaction on a message... @@ -307,9 +290,7 @@ class RoleManagement( """ if not await self.all_are_valid_roles(ctx, role): - return await ctx.maybe_send_embed( - "Can't do that. Discord role heirarchy applies here." - ) + return await ctx.maybe_send_embed("Can't do that. Discord role heirarchy applies here.") try: message = await channel.fetch_message(msgid) @@ -334,17 +315,11 @@ class RoleManagement( try: await message.add_reaction(_emoji) except discord.HTTPException: - return await ctx.maybe_send_embed( - "Hmm, that message couldn't be reacted to" - ) + return await ctx.maybe_send_embed("Hmm, that message couldn't be reacted to") cfg = self.config.custom("REACTROLE", str(message.id), eid) await cfg.set( - { - "roleid": role.id, - "channelid": message.channel.id, - "guildid": role.guild.id, - } + {"roleid": role.id, "channelid": message.channel.id, "guildid": role.guild.id,} ) await ctx.send( f"Remember, the reactions only function according to " @@ -356,21 +331,15 @@ class RoleManagement( @commands.bot_has_permissions(manage_roles=True) @checks.admin_or_permissions(manage_guild=True) @commands.command(name="roleunbind") - async def unbind_role_from_reactions( - self, ctx: commands.Context, role: discord.Role, msgid: int, emoji: str - ): + async def unbind_role_from_reactions(self, ctx: commands.Context, role: discord.Role, msgid: int, emoji: str): """ unbinds a role from a reaction on a message """ if not await self.all_are_valid_roles(ctx, role): - return await ctx.maybe_send_embed( - "Can't do that. Discord role heirarchy applies here." - ) + return await ctx.maybe_send_embed("Can't do that. Discord role heirarchy applies here.") - await self.config.custom( - "REACTROLE", f"{msgid}", self.strip_variations(emoji) - ).clear() + await self.config.custom("REACTROLE", f"{msgid}", self.strip_variations(emoji)).clear() await ctx.tick() @commands.guild_only() @@ -392,12 +361,7 @@ class RoleManagement( use_embeds = await ctx.embed_requested() react_roles = "\n".join( - [ - msg - async for msg in self.build_messages_for_react_roles( - *ctx.guild.roles, use_embeds=use_embeds - ) - ] + [msg async for msg in self.build_messages_for_react_roles(*ctx.guild.roles, use_embeds=use_embeds)] ) if not react_roles: @@ -408,9 +372,7 @@ class RoleManagement( color = await ctx.embed_colour() if use_embeds else None - for page in pagify( - react_roles, escape_mass_mentions=False, page_length=1800, shorten_by=0 - ): + for page in pagify(react_roles, escape_mass_mentions=False, page_length=1800, shorten_by=0): # unrolling iterative calling of ctx.maybe_send_embed if use_embeds: await ctx.send(embed=discord.Embed(description=page, color=color)) @@ -431,22 +393,14 @@ class RoleManagement( f"\n{'is' if rsets['sticky'] else 'is not'} sticky." ) if rsets["requires_any"]: - rstring = ", ".join( - r.name for r in ctx.guild.roles if r.id in rsets["requires_any"] - ) + rstring = ", ".join(r.name for r in ctx.guild.roles if r.id in rsets["requires_any"]) output += f"\nThis role requires any of the following roles: {rstring}" if rsets["requires_all"]: - rstring = ", ".join( - r.name for r in ctx.guild.roles if r.id in rsets["requires_all"] - ) + rstring = ", ".join(r.name for r in ctx.guild.roles if r.id in rsets["requires_all"]) output += f"\nThis role requires all of the following roles: {rstring}" if rsets["exclusive_to"]: - rstring = ", ".join( - r.name for r in ctx.guild.roles if r.id in rsets["exclusive_to"] - ) - output += ( - f"\nThis role is mutually exclusive to the following roles: {rstring}" - ) + rstring = ", ".join(r.name for r in ctx.guild.roles if r.id in rsets["exclusive_to"]) + output += f"\nThis role is mutually exclusive to the following roles: {rstring}" if rsets["cost"]: curr = await bank.get_currency_name(ctx.guild) cost = rsets["cost"] @@ -462,9 +416,7 @@ class RoleManagement( await ctx.send(page) @rgroup.command(name="cost") - async def make_purchasable( - self, ctx: GuildContext, cost: int, *, role: discord.Role - ): + async def make_purchasable(self, ctx: GuildContext, cost: int, *, role: discord.Role): """ Makes a role purchasable for a specified cost. Cost must be a number greater than 0. @@ -477,9 +429,7 @@ class RoleManagement( """ if not await self.all_are_valid_roles(ctx, role): - return await ctx.maybe_send_embed( - "Can't do that. Discord role heirarchy applies here." - ) + return await ctx.maybe_send_embed("Can't do that. Discord role heirarchy applies here.") if cost < 0: return await ctx.send_help() @@ -507,9 +457,7 @@ class RoleManagement( (etc) """ if not await self.all_are_valid_roles(ctx, role): - return await ctx.maybe_send_embed( - "Can't do that. Discord role heirarchy applies here." - ) + return await ctx.maybe_send_embed("Can't do that. Discord role heirarchy applies here.") role_cost = await self.config.role(role).cost() if role_cost == 0: @@ -532,9 +480,7 @@ class RoleManagement( await ctx.send(f"Subscription set to {parse_seconds(time.total_seconds())}.") @rgroup.command(name="forbid") - async def forbid_role( - self, ctx: GuildContext, role: discord.Role, *, user: discord.Member - ): + async def forbid_role(self, ctx: GuildContext, role: discord.Role, *, user: discord.Member): """ Forbids a user from gaining a specific role. """ @@ -546,9 +492,7 @@ class RoleManagement( await ctx.tick() @rgroup.command(name="unforbid") - async def unforbid_role( - self, ctx: GuildContext, role: discord.Role, *, user: discord.Member - ): + async def unforbid_role(self, ctx: GuildContext, role: discord.Role, *, user: discord.Member): """ Unforbids a user from gaining a specific role. """ @@ -572,9 +516,7 @@ class RoleManagement( for role in _roles: async with self.config.role(role).exclusive_to() as ex_list: - ex_list.extend( - [r.id for r in _roles if r != role and r.id not in ex_list] - ) + ex_list.extend([r.id for r in _roles if r != role and r.id not in ex_list]) await ctx.tick() @rgroup.command(name="unexclusive") @@ -595,20 +537,14 @@ class RoleManagement( await ctx.tick() @rgroup.command(name="sticky") - async def setsticky( - self, ctx: GuildContext, role: discord.Role, sticky: bool = None - ): + async def setsticky(self, ctx: GuildContext, role: discord.Role, sticky: bool = None): """ sets a role as sticky if used without a settings, gets the current ones """ if sticky is None: is_sticky = await self.config.role(role).sticky() - return await ctx.send( - "{role} {verb} sticky".format( - role=role.name, verb=("is" if is_sticky else "is not") - ) - ) + return await ctx.send("{role} {verb} sticky".format(role=role.name, verb=("is" if is_sticky else "is not"))) await self.config.role(role).sticky.set(sticky) if sticky: @@ -619,7 +555,7 @@ class RoleManagement( await ctx.tick() - #TODO set roles who don't need to pay for roles + # TODO set roles who don't need to pay for roles @rgroup.command(name="requireall") async def reqall(self, ctx: GuildContext, role: discord.Role, *roles: discord.Role): """ @@ -645,9 +581,7 @@ class RoleManagement( await ctx.tick() @rgroup.command(name="selfrem") - async def selfrem( - self, ctx: GuildContext, role: discord.Role, removable: bool = None - ): + async def selfrem(self, ctx: GuildContext, role: discord.Role, removable: bool = None): """ Sets if a role is self-removable (default False) @@ -657,18 +591,14 @@ class RoleManagement( if removable is None: is_removable = await self.config.role(role).self_removable() return await ctx.send( - "{role} {verb} self-removable".format( - role=role.name, verb=("is" if is_removable else "is not") - ) + "{role} {verb} self-removable".format(role=role.name, verb=("is" if is_removable else "is not")) ) await self.config.role(role).self_removable.set(removable) await ctx.tick() @rgroup.command(name="selfadd") - async def selfadd( - self, ctx: GuildContext, role: discord.Role, assignable: bool = None - ): + async def selfadd(self, ctx: GuildContext, role: discord.Role, assignable: bool = None): """ Sets if a role is self-assignable via command @@ -680,9 +610,7 @@ class RoleManagement( if assignable is None: is_assignable = await self.config.role(role).self_role() return await ctx.send( - "{role} {verb} self-assignable".format( - role=role.name, verb=("is" if is_assignable else "is not") - ) + "{role} {verb} self-assignable".format(role=role.name, verb=("is" if is_assignable else "is not")) ) await self.config.role(role).self_role.set(assignable) @@ -778,7 +706,8 @@ class RoleManagement( for role, (cost, sub) in sorted(data.items(), key=lambda kv: kv[1]): embed.add_field( name=f"__**{i+1}. {role.name}**__", - value="%s%s" % ((f"Cost: {cost}" if cost else "Free"), (f", every {parse_seconds(sub)}" if sub else "")) + value="%s%s" + % ((f"Cost: {cost}" if cost else "Free"), (f", every {parse_seconds(sub)}" if sub else "")), ) i += 1 if i % 25 == 0: @@ -802,37 +731,27 @@ class RoleManagement( except RoleManagementException: return except PermissionOrHierarchyException: - await ctx.send( - "I cannot assign roles which I can not manage. (Discord Hierarchy)" - ) + await ctx.send("I cannot assign roles which I can not manage. (Discord Hierarchy)") else: if not eligible: - return await ctx.send( - f"You aren't allowed to add `{role}` to yourself {ctx.author.mention}!" - ) + return await ctx.send(f"You aren't allowed to add `{role}` to yourself {ctx.author.mention}!") if not cost: - return await ctx.send( - "This role doesn't have a cost. Please try again using `[p]srole add`." - ) + return await ctx.send("This role doesn't have a cost. Please try again using `[p]srole add`.") free_roles = await self.config.guild(ctx.guild).free_roles() currency_name = await bank.get_currency_name(ctx.guild) for m_role in ctx.author.roles: if m_role.id in free_roles: await ctx.send(f"You're special, no {currency_name} will be deducted from your account.") - await self.update_roles_atomically( - who=ctx.author, give=[role], remove=remove - ) + await self.update_roles_atomically(who=ctx.author, give=[role], remove=remove) await ctx.tick() return try: await bank.withdraw_credits(ctx.author, cost) except ValueError: - return await ctx.send( - f"You don't have enough {currency_name} (Cost: {cost} {currency_name})" - ) + return await ctx.send(f"You don't have enough {currency_name} (Cost: {cost} {currency_name})") else: if subscription > 0: await ctx.send(f"{role.name} will be renewed every {parse_seconds(subscription)}") @@ -842,9 +761,7 @@ class RoleManagement( if role.id not in s: s.append(role.id) - await self.update_roles_atomically( - who=ctx.author, give=[role], remove=remove - ) + await self.update_roles_atomically(who=ctx.author, give=[role], remove=remove) await ctx.tick() @srole.command(name="add") @@ -862,24 +779,15 @@ class RoleManagement( except RoleManagementException: return except PermissionOrHierarchyException: - await ctx.send( - "I cannot assign roles which I can not manage. (Discord Hierarchy)" - ) + await ctx.send("I cannot assign roles which I can not manage. (Discord Hierarchy)") else: if not eligible: - await ctx.send( - f"You aren't allowed to add `{role}` to yourself {ctx.author.mention}!" - ) + await ctx.send(f"You aren't allowed to add `{role}` to yourself {ctx.author.mention}!") elif cost: - await ctx.send( - "This role is not free. " - "Please use `[p]srole buy` if you would like to purchase it." - ) + await ctx.send("This role is not free. " "Please use `[p]srole buy` if you would like to purchase it.") else: - await self.update_roles_atomically( - who=ctx.author, give=[role], remove=remove - ) + await self.update_roles_atomically(who=ctx.author, give=[role], remove=remove) await ctx.tick() @srole.command(name="remove") @@ -892,22 +800,18 @@ class RoleManagement( return if await self.config.role(role).self_removable(): await self.update_roles_atomically(who=ctx.author, remove=[role]) - try: # remove subscription, if any + try: # remove subscription, if any async with self.config.role(role).subscribed_users() as s: del s[str(ctx.author.id)] except: pass await ctx.tick() else: - await ctx.send( - f"You aren't allowed to remove `{role}` from yourself {ctx.author.mention}!`" - ) + await ctx.send(f"You aren't allowed to remove `{role}` from yourself {ctx.author.mention}!`") # Stuff for clean interaction with react role entries - async def build_messages_for_react_roles( - self, *roles: discord.Role, use_embeds=True - ) -> AsyncIterator[str]: + async def build_messages_for_react_roles(self, *roles: discord.Role, use_embeds=True) -> AsyncIterator[str]: """ Builds info. @@ -926,22 +830,16 @@ class RoleManagement( channel_id = data.get("channelid", None) if channel_id: - link = linkfmt.format( - guild_id=role.guild.id, - channel_id=channel_id, - message_id=message_id, - ) + link = linkfmt.format(guild_id=role.guild.id, channel_id=channel_id, message_id=message_id,) else: link = ( - f"unknown message with id {message_id}" - f" (use `roleset fixup` to find missing data for this)" + f"unknown message with id {message_id}" f" (use `roleset fixup` to find missing data for this)" ) emoji: Union[discord.Emoji, str] if emoji_info.isdigit(): emoji = ( - discord.utils.get(self.bot.emojis, id=int(emoji_info)) - or f"A custom enoji with id {emoji_info}" + discord.utils.get(self.bot.emojis, id=int(emoji_info)) or f"A custom enoji with id {emoji_info}" ) else: emoji = emoji_info @@ -949,9 +847,7 @@ class RoleManagement( react_m = f"{role.name} is bound to {emoji} on {link}" yield react_m - async def get_react_role_entries( - self, role: discord.Role - ) -> AsyncIterator[Tuple[str, str, dict]]: + async def get_react_role_entries(self, role: discord.Role) -> AsyncIterator[Tuple[str, str, dict]]: """ yields: str, str, dict diff --git a/rolemanagement/events.py b/rolemanagement/events.py index 0d14eab..d5ae54c 100644 --- a/rolemanagement/events.py +++ b/rolemanagement/events.py @@ -84,9 +84,7 @@ class EventMixin(MixinMeta): await member.add_roles(*to_add) @commands.Cog.listener() - async def on_raw_reaction_add( - self, payload: discord.raw_models.RawReactionActionEvent - ): + async def on_raw_reaction_add(self, payload: discord.raw_models.RawReactionActionEvent): await self.wait_for_ready() if not payload.guild_id: return @@ -127,9 +125,7 @@ class EventMixin(MixinMeta): await self.update_roles_atomically(who=member, give=[role], remove=remove) @commands.Cog.listener() - async def on_raw_reaction_remove( - self, payload: discord.raw_models.RawReactionActionEvent - ): + async def on_raw_reaction_remove(self, payload: discord.raw_models.RawReactionActionEvent): await self.wait_for_ready() if not payload.guild_id: return diff --git a/rolemanagement/exceptions.py b/rolemanagement/exceptions.py index e371d8a..6b62aea 100644 --- a/rolemanagement/exceptions.py +++ b/rolemanagement/exceptions.py @@ -4,9 +4,11 @@ from __future__ import annotations class RoleManagementException(Exception): pass + class PermissionOrHierarchyException(Exception): pass + class MissingRequirementsException(RoleManagementException): def __init__(self, *, miss_any=None, miss_all=None): self.miss_all = miss_all or [] diff --git a/rolemanagement/massmanager.py b/rolemanagement/massmanager.py index bc25db3..3ea1c27 100644 --- a/rolemanagement/massmanager.py +++ b/rolemanagement/massmanager.py @@ -88,14 +88,12 @@ class MassManagementMixin(MixinMeta): return False if query["anyperm"] and not any( - bool(value and perm in query["anyperm"]) - for perm, value in iter(m.guild_permissions) + bool(value and perm in query["anyperm"]) for perm, value in iter(m.guild_permissions) ): return False if query["notperm"] and any( - bool(value and perm in query["notperm"]) - for perm, value in iter(m.guild_permissions) + bool(value and perm in query["notperm"]) for perm, value in iter(m.guild_permissions) ): return False @@ -126,11 +124,7 @@ class MassManagementMixin(MixinMeta): @mrole.command(name="user") async def mrole_user( - self, - ctx: GuildContext, - users: commands.Greedy[discord.Member], - *, - _query: RoleSyntaxConverter, + self, ctx: GuildContext, users: commands.Greedy[discord.Member], *, _query: RoleSyntaxConverter, ) -> None: """ adds/removes roles to one or more users @@ -150,16 +144,11 @@ class MassManagementMixin(MixinMeta): query = _query.parsed apply = query["add"] + query["remove"] if not await self.all_are_valid_roles(ctx, *apply): - await ctx.send( - "Either you or I don't have the required permissions " - "or position in the hierarchy." - ) + await ctx.send("Either you or I don't have the required permissions " "or position in the hierarchy.") return for user in users: - await self.update_roles_atomically( - who=user, give=query["add"], remove=query["remove"] - ) + await self.update_roles_atomically(who=user, give=query["add"], remove=query["remove"]) await ctx.tick() @@ -213,9 +202,7 @@ class MassManagementMixin(MixinMeta): embed = discord.Embed(description=description) if ctx.guild: embed.color = ctx.guild.me.color - await ctx.send( - embed=embed, content=f"Search results for {ctx.author.mention}" - ) + await ctx.send(embed=embed, content=f"Search results for {ctx.author.mention}") else: await self.send_maybe_chunked_csv(ctx, list(members)) @@ -223,9 +210,7 @@ class MassManagementMixin(MixinMeta): @staticmethod async def send_maybe_chunked_csv(ctx: GuildContext, members): chunk_size = 75000 - chunks = [ - members[i : (i + chunk_size)] for i in range(0, len(members), chunk_size) - ] + chunks = [members[i : (i + chunk_size)] for i in range(0, len(members), chunk_size)] for part, chunk in enumerate(chunks, 1): @@ -246,9 +231,7 @@ class MassManagementMixin(MixinMeta): "ID": member.id, "Display Name": member.display_name, "Username#Discrim": str(member), - "Joined Server": member.joined_at.strftime(fmt) - if member.joined_at - else None, + "Joined Server": member.joined_at.strftime(fmt) if member.joined_at else None, "Joined Discord": member.created_at.strftime(fmt), } ) @@ -262,8 +245,7 @@ class MassManagementMixin(MixinMeta): filename += f"-part{part}" filename += ".csv" await ctx.send( - content=f"Data for {ctx.author.mention}", - files=[discord.File(data, filename=filename)], + content=f"Data for {ctx.author.mention}", files=[discord.File(data, filename=filename)], ) csvf.close() data.close() @@ -302,37 +284,26 @@ class MassManagementMixin(MixinMeta): apply = query["add"] + query["remove"] if not await self.all_are_valid_roles(ctx, *apply): return await ctx.send( - "Either you or I don't have the required permissions " - "or position in the hierarchy." + "Either you or I don't have the required permissions " "or position in the hierarchy." ) members = set(ctx.guild.members) members = self.search_filter(members, query) if len(members) > 100: - await ctx.send( - "This may take a while given the number of members to update." - ) + await ctx.send("This may take a while given the number of members to update.") async with ctx.typing(): for member in members: try: - await self.update_roles_atomically( - who=member, give=query["add"], remove=query["remove"] - ) + await self.update_roles_atomically(who=member, give=query["add"], remove=query["remove"]) except RoleManagementException: log.debug( - "Internal filter failure on member id %d guild id %d query %s", - member.id, - ctx.guild.id, - query, + "Internal filter failure on member id %d guild id %d query %s", member.id, ctx.guild.id, query, ) except discord.HTTPException: log.debug( - "Unpredicted failure for member id %d in guild id %d query %s", - member.id, - ctx.guild.id, - query, + "Unpredicted failure for member id %d in guild id %d query %s", member.id, ctx.guild.id, query, ) await ctx.tick() diff --git a/rolemanagement/utils.py b/rolemanagement/utils.py index de26bba..6752533 100644 --- a/rolemanagement/utils.py +++ b/rolemanagement/utils.py @@ -26,6 +26,7 @@ TIME_RE_STRING = r"\s?".join( TIME_RE = re.compile(TIME_RE_STRING, re.I) + def parse_timedelta(argument: str) -> timedelta: """ Parses a string that contains a time interval and converts it to a timedelta object. @@ -37,6 +38,7 @@ def parse_timedelta(argument: str) -> timedelta: return timedelta(**params) return None + def parse_seconds(seconds) -> str: """ Take seconds and converts it to larger units @@ -77,11 +79,7 @@ class UtilMixin(MixinMeta): return variation_stripper_re.sub("", s) async def update_roles_atomically( - self, - *, - who: discord.Member, - give: List[discord.Role] = None, - remove: List[discord.Role] = None, + self, *, who: discord.Member, give: List[discord.Role] = None, remove: List[discord.Role] = None, ): """ Give and remove roles as a single op with some slight sanity @@ -95,10 +93,7 @@ class UtilMixin(MixinMeta): roles.extend([r for r in give if r not in roles]) if sorted(roles) == sorted(who.roles): return - if ( - any(r >= me.top_role for r in heirarchy_testing) - or not me.guild_permissions.manage_roles - ): + if any(r >= me.top_role for r in heirarchy_testing) or not me.guild_permissions.manage_roles: raise PermissionOrHierarchyException("Can't do that.") await who.edit(roles=roles) @@ -120,10 +115,7 @@ class UtilMixin(MixinMeta): # Bot allowed if not ( guild.me.guild_permissions.manage_roles - and ( - guild.me == guild.owner - or all(guild.me.top_role > role for role in roles) - ) + and (guild.me == guild.owner or all(guild.me.top_role > role for role in roles)) ): return False @@ -133,9 +125,7 @@ class UtilMixin(MixinMeta): return True - async def is_self_assign_eligible( - self, who: discord.Member, role: discord.Role - ) -> List[discord.Role]: + async def is_self_assign_eligible(self, who: discord.Member, role: discord.Role) -> List[discord.Role]: """ Returns a list of roles to be removed if this one is added, or raises an exception @@ -167,22 +157,14 @@ class UtilMixin(MixinMeta): req_any_fail = [] break - req_all_fail = [ - idx - for idx in await self.config.role(role).requires_all() - if not who._roles.has(idx) - ] + req_all_fail = [idx for idx in await self.config.role(role).requires_all() if not who._roles.has(idx)] if req_any_fail or req_all_fail: - raise MissingRequirementsException( - miss_all=req_all_fail, miss_any=req_any_fail - ) + raise MissingRequirementsException(miss_all=req_all_fail, miss_any=req_any_fail) return None - async def check_exclusivity( - self, who: discord.Member, role: discord.Role - ) -> List[discord.Role]: + async def check_exclusivity(self, who: discord.Member, role: discord.Role) -> List[discord.Role]: """ Returns a list of roles to remove, or raises an error """