diff options
| -rw-r--r-- | bot/cogs/alias.py | 17 | ||||
| -rw-r--r-- | bot/cogs/moderation/infractions.py | 18 | ||||
| -rw-r--r-- | bot/cogs/moderation/management.py | 4 | ||||
| -rw-r--r-- | bot/cogs/watchchannels/bigbrother.py | 6 | ||||
| -rw-r--r-- | bot/cogs/watchchannels/talentpool.py | 9 | ||||
| -rw-r--r-- | bot/converters.py | 48 | 
6 files changed, 52 insertions, 50 deletions
| diff --git a/bot/cogs/alias.py b/bot/cogs/alias.py index 03c49c2f4..e709be85d 100644 --- a/bot/cogs/alias.py +++ b/bot/cogs/alias.py @@ -1,8 +1,7 @@  import inspect  import logging -from typing import Union -from discord import Colour, Embed, Member, User +from discord import Colour, Embed  from discord.ext.commands import Cog, Command, Context, clean_content, command, group  from bot.bot import Bot @@ -60,18 +59,12 @@ class Alias (Cog):          await self.invoke(ctx, "site tools")      @command(name="watch", hidden=True) -    async def bigbrother_watch_alias( -            self, -            ctx: Context, -            user: Union[Member, User, FetchedUser], -            *, -            reason: str -    ) -> None: +    async def bigbrother_watch_alias(self, ctx: Context, user: FetchedUser, *, reason: str) -> None:          """Alias for invoking <prefix>bigbrother watch [user] [reason]."""          await self.invoke(ctx, "bigbrother watch", user, reason=reason)      @command(name="unwatch", hidden=True) -    async def bigbrother_unwatch_alias(self, ctx: Context, user: Union[User, FetchedUser], *, reason: str) -> None: +    async def bigbrother_unwatch_alias(self, ctx: Context, user: FetchedUser, *, reason: str) -> None:          """Alias for invoking <prefix>bigbrother unwatch [user] [reason]."""          await self.invoke(ctx, "bigbrother unwatch", user, reason=reason) @@ -137,12 +130,12 @@ class Alias (Cog):          await self.invoke(ctx, "docs get", symbol)      @command(name="nominate", hidden=True) -    async def nomination_add_alias(self, ctx: Context, user: Union[Member, User, FetchedUser], *, reason: str) -> None: +    async def nomination_add_alias(self, ctx: Context, user: FetchedUser, *, reason: str) -> None:          """Alias for invoking <prefix>talentpool add [user] [reason]."""          await self.invoke(ctx, "talentpool add", user, reason=reason)      @command(name="unnominate", hidden=True) -    async def nomination_end_alias(self, ctx: Context, user: Union[User, FetchedUser], *, reason: str) -> None: +    async def nomination_end_alias(self, ctx: Context, user: FetchedUser, *, reason: str) -> None:          """Alias for invoking <prefix>nomination end [user] [reason]."""          await self.invoke(ctx, "nomination end", user, reason=reason) diff --git a/bot/cogs/moderation/infractions.py b/bot/cogs/moderation/infractions.py index 5b6a63dbb..264d8bcf5 100644 --- a/bot/cogs/moderation/infractions.py +++ b/bot/cogs/moderation/infractions.py @@ -14,12 +14,10 @@ from bot.decorators import respect_role_hierarchy  from bot.utils.checks import with_role_check  from . import utils  from .scheduler import InfractionScheduler -from .utils import MemberObject, UserTypes +from .utils import MemberObject  log = logging.getLogger(__name__) -MemberConverter = t.Union[UserTypes, FetchedUser] -  class Infractions(InfractionScheduler, commands.Cog):      """Apply and pardon infractions on users for moderation purposes.""" @@ -68,7 +66,7 @@ class Infractions(InfractionScheduler, commands.Cog):          await self.apply_kick(ctx, user, reason, active=False)      @command() -    async def ban(self, ctx: Context, user: MemberConverter, *, reason: str = None) -> None: +    async def ban(self, ctx: Context, user: FetchedUser, *, reason: str = None) -> None:          """Permanently ban a user for the given reason."""          await self.apply_ban(ctx, user, reason) @@ -95,7 +93,7 @@ class Infractions(InfractionScheduler, commands.Cog):          await self.apply_mute(ctx, user, reason, expires_at=duration)      @command() -    async def tempban(self, ctx: Context, user: MemberConverter, duration: utils.Expiry, *, reason: str = None) -> None: +    async def tempban(self, ctx: Context, user: FetchedUser, duration: utils.Expiry, *, reason: str = None) -> None:          """          Temporarily ban a user for the given reason and duration. @@ -117,7 +115,7 @@ class Infractions(InfractionScheduler, commands.Cog):      # region: Permanent shadow infractions      @command(hidden=True) -    async def note(self, ctx: Context, user: MemberConverter, *, reason: str = None) -> None: +    async def note(self, ctx: Context, user: FetchedUser, *, reason: str = None) -> None:          """Create a private note for a user with the given reason without notifying the user."""          infraction = await utils.post_infraction(ctx, user, "note", reason, hidden=True, active=False)          if infraction is None: @@ -131,7 +129,7 @@ class Infractions(InfractionScheduler, commands.Cog):          await self.apply_kick(ctx, user, reason, hidden=True, active=False)      @command(hidden=True, aliases=['shadowban', 'sban']) -    async def shadow_ban(self, ctx: Context, user: MemberConverter, *, reason: str = None) -> None: +    async def shadow_ban(self, ctx: Context, user: FetchedUser, *, reason: str = None) -> None:          """Permanently ban a user for the given reason without notifying the user."""          await self.apply_ban(ctx, user, reason, hidden=True) @@ -161,7 +159,7 @@ class Infractions(InfractionScheduler, commands.Cog):      async def shadow_tempban(          self,          ctx: Context, -        user: MemberConverter, +        user: FetchedUser,          duration: utils.Expiry,          *,          reason: str = None @@ -187,12 +185,12 @@ class Infractions(InfractionScheduler, commands.Cog):      # region: Remove infractions (un- commands)      @command() -    async def unmute(self, ctx: Context, user: MemberConverter) -> None: +    async def unmute(self, ctx: Context, user: FetchedUser) -> None:          """Prematurely end the active mute infraction for the user."""          await self.pardon_infraction(ctx, "mute", user)      @command() -    async def unban(self, ctx: Context, user: MemberConverter) -> None: +    async def unban(self, ctx: Context, user: FetchedUser) -> None:          """Prematurely end the active ban infraction for the user."""          await self.pardon_infraction(ctx, "ban", user) diff --git a/bot/cogs/moderation/management.py b/bot/cogs/moderation/management.py index fff86e9ea..2ec69960e 100644 --- a/bot/cogs/moderation/management.py +++ b/bot/cogs/moderation/management.py @@ -20,8 +20,6 @@ from .modlog import ModLog  log = logging.getLogger(__name__) -UserConverter = t.Union[discord.User, proxy_user] -  class ModManagement(commands.Cog):      """Management of infractions.""" @@ -182,7 +180,7 @@ class ModManagement(commands.Cog):              await ctx.invoke(self.search_reason, query)      @infraction_search_group.command(name="user", aliases=("member", "id")) -    async def search_user(self, ctx: Context, user: UserConverter) -> None: +    async def search_user(self, ctx: Context, user: t.Union[discord.User, proxy_user]) -> None:          """Search for infractions by member."""          infraction_list = await self.bot.api_client.get(              'bot/infractions', diff --git a/bot/cogs/watchchannels/bigbrother.py b/bot/cogs/watchchannels/bigbrother.py index 7a30d5033..b2cb99368 100644 --- a/bot/cogs/watchchannels/bigbrother.py +++ b/bot/cogs/watchchannels/bigbrother.py @@ -1,8 +1,6 @@  import logging  from collections import ChainMap -from typing import Union -from discord import User  from discord.ext.commands import Cog, Context, group  from bot.bot import Bot @@ -47,7 +45,7 @@ class BigBrother(WatchChannel, Cog, name="Big Brother"):      @bigbrother_group.command(name='watch', aliases=('w',))      @with_role(*MODERATION_ROLES) -    async def watch_command(self, ctx: Context, user: Union[User, FetchedUser], *, reason: str) -> None: +    async def watch_command(self, ctx: Context, user: FetchedUser, *, reason: str) -> None:          """          Relay messages sent by the given `user` to the `#big-brother` channel. @@ -94,7 +92,7 @@ class BigBrother(WatchChannel, Cog, name="Big Brother"):      @bigbrother_group.command(name='unwatch', aliases=('uw',))      @with_role(*MODERATION_ROLES) -    async def unwatch_command(self, ctx: Context, user: Union[User, FetchedUser], *, reason: str) -> None: +    async def unwatch_command(self, ctx: Context, user: FetchedUser, *, reason: str) -> None:          """Stop relaying messages by the given `user`."""          active_watches = await self.bot.api_client.get(              self.api_endpoint, diff --git a/bot/cogs/watchchannels/talentpool.py b/bot/cogs/watchchannels/talentpool.py index 62be3bc3b..776f0ea87 100644 --- a/bot/cogs/watchchannels/talentpool.py +++ b/bot/cogs/watchchannels/talentpool.py @@ -1,9 +1,8 @@  import logging  import textwrap  from collections import ChainMap -from typing import Union -from discord import Color, Embed, Member, User +from discord import Color, Embed, Member  from discord.ext.commands import Cog, Context, group  from bot.api import ResponseCodeError @@ -50,7 +49,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):      @nomination_group.command(name='watch', aliases=('w', 'add', 'a'))      @with_role(*STAFF_ROLES) -    async def watch_command(self, ctx: Context, user: Union[Member, User, FetchedUser], *, reason: str) -> None: +    async def watch_command(self, ctx: Context, user: FetchedUser, *, reason: str) -> None:          """          Relay messages sent by the given `user` to the `#talent-pool` channel. @@ -115,7 +114,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):      @nomination_group.command(name='history', aliases=('info', 'search'))      @with_role(*MODERATION_ROLES) -    async def history_command(self, ctx: Context, user: Union[User, FetchedUser]) -> None: +    async def history_command(self, ctx: Context, user: FetchedUser) -> None:          """Shows the specified user's nomination history."""          result = await self.bot.api_client.get(              self.api_endpoint, @@ -144,7 +143,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):      @nomination_group.command(name='unwatch', aliases=('end', ))      @with_role(*MODERATION_ROLES) -    async def unwatch_command(self, ctx: Context, user: Union[User, FetchedUser], *, reason: str) -> None: +    async def unwatch_command(self, ctx: Context, user: FetchedUser, *, reason: str) -> None:          """          Ends the active nomination of the specified user with the given reason. diff --git a/bot/converters.py b/bot/converters.py index a2e445d74..4fb800a01 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -9,7 +9,7 @@ import dateutil.tz  import discord  from aiohttp import ClientConnectorError  from dateutil.relativedelta import relativedelta -from discord.ext.commands import BadArgument, Context, Converter +from discord.ext.commands import BadArgument, Context, Converter, UserConverter  log = logging.getLogger(__name__) @@ -303,30 +303,46 @@ def proxy_user(user_id: str) -> discord.Object:      return user -class FetchedUser(Converter): +class FetchedUser(UserConverter):      """ -    Fetches from the Discord API and returns a `discord.User` or `discord.Object` object, given an ID. +    Converts to a `discord.User` or, if it fails, a `discord.Object`. -    If the fetching is successful, a `discord.User` object is returned. If it fails and -    the error doesn't imply the user doesn't exist, then a `discord.Object` is returned -    via the `user_proxy` function. +    Unlike the default `UserConverter`, which only does lookups via the global user cache, this +    converter attempts to fetch the user via an API call to Discord when the using the cache is +    unsuccessful. + +    If the fetch also fails and the error doesn't imply the user doesn't exist, then a +    `discord.Object` is returned via the `user_proxy` converter. + +    The lookup strategy is as follows (in order): + +    1. Lookup by ID. +    2. Lookup by mention. +    3. Lookup by name#discrim +    4. Lookup by name +    5. Lookup via API +    6. Create a proxy user with discord.Object      """ -    @staticmethod -    async def convert(ctx: Context, user_id: str) -> t.Union[discord.User, discord.Object]: -        """Convert `user_id` to a `discord.User` object, after fetching from the Discord API.""" +    async def convert(self, ctx: Context, arg: str) -> t.Union[discord.User, discord.Object]: +        """Convert the `arg` to a `discord.User` or `discord.Object`.""" +        try: +            return await super().convert(ctx, arg) +        except BadArgument: +            pass +          try: -            user_id = int(user_id) +            user_id = int(arg)              log.trace(f"Fetching user {user_id}...")              return await ctx.bot.fetch_user(user_id)          except ValueError: -            log.debug(f"Failed to fetch user {user_id}: could not convert to int.") -            raise BadArgument(f"The provided argument can't be turned into integer: `{user_id}`") +            log.debug(f"Failed to fetch user {arg}: could not convert to int.") +            raise BadArgument(f"The provided argument can't be turned into integer: `{arg}`")          except discord.HTTPException as e: -            # If the Discord error isn't `Unknown user`, save it in the log and return a proxy instead +            # If the Discord error isn't `Unknown user`, return a proxy instead              if e.code != 10013:                  log.warning(f"Failed to fetch user, returning a proxy instead: status {e.status}") -                return proxy_user(user_id) +                return proxy_user(arg) -            log.debug(f"Failed to fetch user {user_id}: user does not exist.") -            raise BadArgument(f"User `{user_id}` does not exist") +            log.debug(f"Failed to fetch user {arg}: user does not exist.") +            raise BadArgument(f"User `{arg}` does not exist") | 
