From d8a9261941e2cfc027f14f2f56aae0b4f55858dd Mon Sep 17 00:00:00 2001 From: Manuel Ignacio Pérez Alcolea Date: Mon, 16 Dec 2019 01:44:38 -0300 Subject: Add FetchedUser to convert ids of absent users to `discord.User` This `discord.ext.commands.Converter` fetches a user from the Discord API and returns a `discord.User` object. This should replace the `proxy_user` function from the moderation `utils`. --- bot/converters.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/bot/converters.py b/bot/converters.py index 8d2ab7eb8..fbe9ecd90 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -278,3 +278,25 @@ class ISODateTime(Converter): dt = dt.replace(tzinfo=None) return dt + + +class FetchedUser(Converter): + """Fetches from the Discord API and returns a `discord.User` object.""" + + @staticmethod + async def convert(ctx: Context, user_id: str) -> discord.User: + """Converts `user_id` to a `discord.User` object, after fetching from the Discord API.""" + # TODO: add docstring + # TODO: add remaining exceptions + try: + user_id = int(user_id) + user = await ctx.bot.fetch_user(user_id) + except ValueError: + raise BadArgument(f"The provided argument can't be turned into integer: `{user_id}`") + except discord.HTTPException as e: + # If the Discord error isn't Unknown user, save it in the log + if e.code != 10013: + log.warning(f"Failed to fetch user:") + raise BadArgument + + return user -- cgit v1.2.3 From 051565d6778bea9893de21d7df1114526ec812ed Mon Sep 17 00:00:00 2001 From: Manuel Ignacio Pérez Alcolea Date: Mon, 16 Dec 2019 01:57:18 -0300 Subject: Remove pointless comma after last argument --- bot/cogs/moderation/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/moderation/utils.py b/bot/cogs/moderation/utils.py index 325b9567a..044c6f8d1 100644 --- a/bot/cogs/moderation/utils.py +++ b/bot/cogs/moderation/utils.py @@ -58,7 +58,7 @@ async def post_infraction( reason: str, expires_at: datetime = None, hidden: bool = False, - active: bool = True, + active: bool = True ) -> t.Optional[dict]: """Posts an infraction to the API.""" log.trace(f"Posting {infr_type} infraction for {user} to the API.") -- cgit v1.2.3 From 2798e2db4e37f538b94bd84fee473d4b31504f17 Mon Sep 17 00:00:00 2001 From: Manuel Ignacio Pérez Alcolea Date: Mon, 16 Dec 2019 01:59:50 -0300 Subject: Add `post_user` function to POST a new user to the DB As it is now, this function is planned to be used a big-helper in `post_infraction`. Its interface is partially similar: it will return a "JSON" dictionary if everything went well, or `None` if it failed. If it fails, it will send a message to the channel and register the issue in the `log`. --- bot/cogs/moderation/utils.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/bot/cogs/moderation/utils.py b/bot/cogs/moderation/utils.py index 044c6f8d1..81a975ba4 100644 --- a/bot/cogs/moderation/utils.py +++ b/bot/cogs/moderation/utils.py @@ -31,6 +31,39 @@ Infraction = t.Dict[str, t.Union[str, int, bool]] Expiry = t.Union[Duration, ISODateTime] +async def post_user(ctx: Context, user: discord.User) -> t.Optional[dict]: + """ + Create a new user in the database. + + Used when an infraction needs to be applied on a user absent in the guild. + """ + log.warn("Attempting to add user to the database.") + + payload = { + 'avatar_hash': user.avatar, + 'discriminator': int(user.discriminator), + 'id': user.id, + 'in_guild': False, + 'name': user.name, + 'roles': [] + } + + try: + response = await ctx.bot.api_client.post('bot/users', json=payload) + except ResponseCodeError: + # TODO: Add details, and specific information per possible situation. + # Potential race condition if someone joins and the bot syncs before the API replies! + log.info("Couldn't post user.") + # TODO: Rewrite message. + await ctx.send("Tried to post user to the DB, couldn't be done.") + + return + + return response + + +# TODO: maybe delete proxy_user + def proxy_user(user_id: str) -> discord.Object: """ Create a proxy user object from the given id. -- cgit v1.2.3 From 447de16b5c62a6d473abb4bf1c61b34f8d5e0f26 Mon Sep 17 00:00:00 2001 From: Manuel Ignacio Pérez Alcolea Date: Mon, 16 Dec 2019 02:11:55 -0300 Subject: Make post_infraction try to `post_user` if user doesn't exist Try twice to apply the infraction. If the user is not in the database, try to add it, then try to apply the infraction again. This allows any moderation function that uses `FetchedUser` as a converter to apply the infraction even when the user is absent in the local database. --- bot/cogs/moderation/infractions.py | 4 +--- bot/cogs/moderation/utils.py | 33 ++++++++++++++++----------------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/bot/cogs/moderation/infractions.py b/bot/cogs/moderation/infractions.py index 3536a3d38..253a8db5b 100644 --- a/bot/cogs/moderation/infractions.py +++ b/bot/cogs/moderation/infractions.py @@ -13,12 +13,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 +from .utils import MemberObject, UserTypes as MemberConverter log = logging.getLogger(__name__) -MemberConverter = t.Union[utils.UserTypes, utils.proxy_user] - class Infractions(InfractionScheduler, commands.Cog): """Apply and pardon infractions on users for moderation purposes.""" diff --git a/bot/cogs/moderation/utils.py b/bot/cogs/moderation/utils.py index 81a975ba4..994bdeaa3 100644 --- a/bot/cogs/moderation/utils.py +++ b/bot/cogs/moderation/utils.py @@ -9,7 +9,7 @@ from discord.ext.commands import Context from bot.api import ResponseCodeError from bot.constants import Colours, Icons -from bot.converters import Duration, ISODateTime +from bot.converters import Duration, FetchedUser, ISODateTime log = logging.getLogger(__name__) @@ -25,7 +25,7 @@ INFRACTION_ICONS = { RULES_URL = "https://pythondiscord.com/pages/rules" APPEALABLE_INFRACTIONS = ("ban", "mute") -UserTypes = t.Union[discord.Member, discord.User] +UserTypes = t.Union[discord.Member, discord.User, FetchedUser] MemberObject = t.Union[UserTypes, discord.Object] Infraction = t.Dict[str, t.Union[str, int, bool]] Expiry = t.Union[Duration, ISODateTime] @@ -107,22 +107,21 @@ async def post_infraction( if expires_at: payload['expires_at'] = expires_at.isoformat() - try: - response = await ctx.bot.api_client.post('bot/infractions', json=payload) - except ResponseCodeError as exp: - if exp.status == 400 and 'user' in exp.response_json: - log.info( - f"{ctx.author} tried to add a {infr_type} infraction to `{user.id}`, " - "but that user id was not found in the database." - ) - await ctx.send( - f":x: Cannot add infraction, the specified user is not known to the database." - ) - return + # Try to apply the infraction. If it fails because the user doesn't exist, try to add it. + for attempt in range(2): + try: + response = await ctx.bot.api_client.post('bot/infractions', json=payload) + except ResponseCodeError as exp: + if exp.status == 400 and 'user'in exp.response_json: + # Only once attempt to try to add the user to the database, not two: + if attempt > 0 or await post_user(ctx, user) is None: + return + else: + log.exception("An unexpected ResponseCodeError occurred while adding an infraction:") + await ctx.send(":x: There was an error adding the infraction.") + return else: - log.exception("An unexpected ResponseCodeError occurred while adding an infraction:") - await ctx.send(":x: There was an error adding the infraction.") - return + break return response -- cgit v1.2.3 From e9ed6442e01f78f27ec7404759bb45999d8b8104 Mon Sep 17 00:00:00 2001 From: Manuel Ignacio Pérez Alcolea Date: Fri, 20 Dec 2019 02:15:32 -0300 Subject: Refactor minor details in `post_infraction` --- bot/cogs/moderation/utils.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bot/cogs/moderation/utils.py b/bot/cogs/moderation/utils.py index 994bdeaa3..1b683b4a3 100644 --- a/bot/cogs/moderation/utils.py +++ b/bot/cogs/moderation/utils.py @@ -108,13 +108,14 @@ async def post_infraction( payload['expires_at'] = expires_at.isoformat() # Try to apply the infraction. If it fails because the user doesn't exist, try to add it. - for attempt in range(2): + for should_post_user in (True, False): try: response = await ctx.bot.api_client.post('bot/infractions', json=payload) + return response except ResponseCodeError as exp: if exp.status == 400 and 'user'in exp.response_json: - # Only once attempt to try to add the user to the database, not two: - if attempt > 0 or await post_user(ctx, user) is None: + # Only one attempt to add the user to the database, not two: + if not should_post_user or await post_user(ctx, user) is None: return else: log.exception("An unexpected ResponseCodeError occurred while adding an infraction:") @@ -123,8 +124,6 @@ async def post_infraction( else: break - return response - async def has_active_infraction(ctx: Context, user: MemberObject, infr_type: str) -> bool: """Checks if a user already has an active infraction of the given type.""" -- cgit v1.2.3 From 98096a4ada2d4d27edac24a606c04e74fb4dc45b Mon Sep 17 00:00:00 2001 From: Manuel Ignacio Pérez Alcolea Date: Fri, 20 Dec 2019 02:28:34 -0300 Subject: Give `post_user` default values for `payload` if absent in `user` Now `post_user(...)` expects either a `discord.User` or a `discord.Object` object as `user`. Either way, it will try to take the relevant attributes from `user` to fill the DB columns. If it can't be done, `.avatar_hash`, `.discriminator`, and `name` will take default values. --- bot/cogs/moderation/utils.py | 45 +++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/bot/cogs/moderation/utils.py b/bot/cogs/moderation/utils.py index 1b683b4a3..0674e8d45 100644 --- a/bot/cogs/moderation/utils.py +++ b/bot/cogs/moderation/utils.py @@ -31,38 +31,45 @@ Infraction = t.Dict[str, t.Union[str, int, bool]] Expiry = t.Union[Duration, ISODateTime] -async def post_user(ctx: Context, user: discord.User) -> t.Optional[dict]: +async def post_user(ctx: Context, user: t.Union[discord.User, discord.Object]) -> t.Optional[dict]: """ Create a new user in the database. Used when an infraction needs to be applied on a user absent in the guild. """ - log.warn("Attempting to add user to the database.") + log.trace("Attempting to add user to the database.") - payload = { - 'avatar_hash': user.avatar, - 'discriminator': int(user.discriminator), - 'id': user.id, - 'in_guild': False, - 'name': user.name, - 'roles': [] - } + try: + payload = { + 'avatar_hash': user.avatar, + 'discriminator': int(user.discriminator), + 'id': user.id, + 'in_guild': False, + 'name': user.name, + 'roles': [] + } + except AttributeError: + log.trace("Couldn't take all the attributes for the user payload, taking just its ID.") + # XXX: Not sure if these default values are ideal. + payload = { + 'avatar_hash': 0, + 'discriminator': 0, + 'id': user.id, + 'in_guild': False, + 'name': 'Some name', + 'roles': [] + } try: response = await ctx.bot.api_client.post('bot/users', json=payload) - except ResponseCodeError: + return response + except ResponseCodeError as e: # TODO: Add details, and specific information per possible situation. # Potential race condition if someone joins and the bot syncs before the API replies! log.info("Couldn't post user.") - # TODO: Rewrite message. - await ctx.send("Tried to post user to the DB, couldn't be done.") - - return - - return response - + # NOTE: `e.status` is probably not enough for a good message + await ctx.send(f"The attempt to add the user to the DB failed: {e.status}") -# TODO: maybe delete proxy_user def proxy_user(user_id: str) -> discord.Object: """ -- cgit v1.2.3 From 64f2c23f862f78095069f44cc7762ebd1408b31c Mon Sep 17 00:00:00 2001 From: Manuel Ignacio Pérez Alcolea Date: Fri, 20 Dec 2019 02:30:25 -0300 Subject: Make `FetchedUser` return a `discord.Object` if user *may* exist The FetchedUser Converter now counts with a `proxy_user` helper function (which SHOULD NOT be there) to return a user as a last resource, in case there was an issue fetching from the Discord API, as long as the error isn't that there's no user with the given ID. --- bot/converters.py | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/bot/converters.py b/bot/converters.py index fbe9ecd90..b33229cc7 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -281,22 +281,50 @@ class ISODateTime(Converter): class FetchedUser(Converter): - """Fetches from the Discord API and returns a `discord.User` object.""" + """ + Fetches from the Discord API and returns a `discord.User` or `discord.Object` object, given an ID. + + 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. + """ + + # XXX: `proxy_user` shouldn't be here as a helper. + # Should wait for PR #651 to import from bot.utils.whatever, maybe? + @staticmethod + def proxy_user(user_id: str) -> discord.Object: + """ + Create a proxy user object from the given id. + + Used when a Member or User object cannot be resolved. + """ + log.trace(f"Attempting to create a proxy user for the user id {user_id}.") + + try: + user_id = int(user_id) + except ValueError: + raise BadArgument + + user = discord.Object(user_id) + user.mention = user.id + user.avatar_url_as = lambda static_format: None + + return user @staticmethod - async def convert(ctx: Context, user_id: str) -> discord.User: - """Converts `user_id` to a `discord.User` object, after fetching from the Discord API.""" - # TODO: add docstring - # TODO: add remaining exceptions + 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.""" try: user_id = int(user_id) user = await ctx.bot.fetch_user(user_id) except ValueError: raise BadArgument(f"The provided argument can't be turned into integer: `{user_id}`") except discord.HTTPException as e: - # If the Discord error isn't Unknown user, save it in the log + # If the Discord error isn't `Unknown user`, save it in the log and return a proxy if e.code != 10013: - log.warning(f"Failed to fetch user:") + log.warning("Failed to fetch user, returning a proxy instead.") + # XXX: + return FetchedUser.proxy_user(user_id) raise BadArgument return user -- cgit v1.2.3 From 8e9d19c7b73f44dac9482302bc34bc737a2fe3ed Mon Sep 17 00:00:00 2001 From: Manuel Ignacio Pérez Alcolea Date: Fri, 20 Dec 2019 03:09:25 -0300 Subject: Make post_user take default values for payload from `getattr` --- bot/cogs/moderation/utils.py | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/bot/cogs/moderation/utils.py b/bot/cogs/moderation/utils.py index 0674e8d45..604ce5ce4 100644 --- a/bot/cogs/moderation/utils.py +++ b/bot/cogs/moderation/utils.py @@ -39,26 +39,18 @@ async def post_user(ctx: Context, user: t.Union[discord.User, discord.Object]) - """ log.trace("Attempting to add user to the database.") - try: - payload = { - 'avatar_hash': user.avatar, - 'discriminator': int(user.discriminator), - 'id': user.id, - 'in_guild': False, - 'name': user.name, - 'roles': [] - } - except AttributeError: - log.trace("Couldn't take all the attributes for the user payload, taking just its ID.") - # XXX: Not sure if these default values are ideal. - payload = { - 'avatar_hash': 0, - 'discriminator': 0, - 'id': user.id, - 'in_guild': False, - 'name': 'Some name', - 'roles': [] - } + if not isinstance(user, discord.User): + log.warn("The given user is not a discord.User object.") + + # XXX: Not sure if these default values are ideal. + payload = { + 'avatar_hash': getattr(user, 'avatar', 0), + 'discriminator': int(getattr(user, 'discriminator', 0)), + 'id': user.id, + 'in_guild': False, + 'name': getattr(user, 'name', 'No name'), + 'roles': [] + } try: response = await ctx.bot.api_client.post('bot/users', json=payload) -- cgit v1.2.3 From 3d6113e0eef031f0788a64de46395f3a4af4f921 Mon Sep 17 00:00:00 2001 From: Manuel Ignacio Pérez Alcolea Date: Fri, 20 Dec 2019 23:04:07 -0300 Subject: Move `utils.proxy_user` to Converters and do minor refactoring The `proxy_user` function now belongs to the `Converters` module, since its use is directly related to it. `FetchedUser` uses this function if there's an error trying to fetch and it doesn't indicate a non existing user. Technically finished and working. --- bot/cogs/moderation/management.py | 4 ++-- bot/cogs/moderation/utils.py | 33 +++++---------------------- bot/converters.py | 47 ++++++++++++++++++--------------------- 3 files changed, 29 insertions(+), 55 deletions(-) diff --git a/bot/cogs/moderation/management.py b/bot/cogs/moderation/management.py index 9605d47b2..fff86e9ea 100644 --- a/bot/cogs/moderation/management.py +++ b/bot/cogs/moderation/management.py @@ -10,7 +10,7 @@ from discord.ext.commands import Context from bot import constants from bot.bot import Bot -from bot.converters import InfractionSearchQuery, allowed_strings +from bot.converters import InfractionSearchQuery, allowed_strings, proxy_user from bot.pagination import LinePaginator from bot.utils import time from bot.utils.checks import in_channel_check, with_role_check @@ -20,7 +20,7 @@ from .modlog import ModLog log = logging.getLogger(__name__) -UserConverter = t.Union[discord.User, utils.proxy_user] +UserConverter = t.Union[discord.User, proxy_user] class ModManagement(commands.Cog): diff --git a/bot/cogs/moderation/utils.py b/bot/cogs/moderation/utils.py index 604ce5ce4..5b5aacb16 100644 --- a/bot/cogs/moderation/utils.py +++ b/bot/cogs/moderation/utils.py @@ -4,7 +4,6 @@ import typing as t from datetime import datetime import discord -from discord.ext import commands from discord.ext.commands import Context from bot.api import ResponseCodeError @@ -42,45 +41,23 @@ async def post_user(ctx: Context, user: t.Union[discord.User, discord.Object]) - if not isinstance(user, discord.User): log.warn("The given user is not a discord.User object.") - # XXX: Not sure if these default values are ideal. payload = { 'avatar_hash': getattr(user, 'avatar', 0), 'discriminator': int(getattr(user, 'discriminator', 0)), 'id': user.id, 'in_guild': False, - 'name': getattr(user, 'name', 'No name'), + 'name': getattr(user, 'name', 'Name unknown'), 'roles': [] } try: response = await ctx.bot.api_client.post('bot/users', json=payload) + log.trace(f"User {user.id} added to the DB.") return response except ResponseCodeError as e: - # TODO: Add details, and specific information per possible situation. - # Potential race condition if someone joins and the bot syncs before the API replies! - log.info("Couldn't post user.") - # NOTE: `e.status` is probably not enough for a good message - await ctx.send(f"The attempt to add the user to the DB failed: {e.status}") - - -def proxy_user(user_id: str) -> discord.Object: - """ - Create a proxy user object from the given id. - - Used when a Member or User object cannot be resolved. - """ - log.trace(f"Attempting to create a proxy user for the user id {user_id}.") - - try: - user_id = int(user_id) - except ValueError: - raise commands.BadArgument - - user = discord.Object(user_id) - user.mention = user.id - user.avatar_url_as = lambda static_format: None - - return user + log.warn("Couldn't post user.") + await ctx.send("The attempt to add the user to the DB failed: " + f"{e.status}, {e.response_text if e.response_text else 'no message received'}.") async def post_infraction( diff --git a/bot/converters.py b/bot/converters.py index b33229cc7..28bf58cf4 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -280,36 +280,34 @@ class ISODateTime(Converter): return dt -class FetchedUser(Converter): +def proxy_user(user_id: str) -> discord.Object: """ - Fetches from the Discord API and returns a `discord.User` or `discord.Object` object, given an ID. + Create a proxy user object from the given id. - 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. + Used when a Member or User object cannot be resolved. """ + log.trace(f"Attempting to create a proxy user for the user id {user_id}.") - # XXX: `proxy_user` shouldn't be here as a helper. - # Should wait for PR #651 to import from bot.utils.whatever, maybe? - @staticmethod - def proxy_user(user_id: str) -> discord.Object: - """ - Create a proxy user object from the given id. + try: + user_id = int(user_id) + except ValueError: + raise BadArgument - Used when a Member or User object cannot be resolved. - """ - log.trace(f"Attempting to create a proxy user for the user id {user_id}.") + user = discord.Object(user_id) + user.mention = user.id + user.avatar_url_as = lambda static_format: None - try: - user_id = int(user_id) - except ValueError: - raise BadArgument + return user - user = discord.Object(user_id) - user.mention = user.id - user.avatar_url_as = lambda static_format: None - return user +class FetchedUser(Converter): + """ + Fetches from the Discord API and returns a `discord.User` or `discord.Object` object, given an ID. + + 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. + """ @staticmethod async def convert(ctx: Context, user_id: str) -> t.Union[discord.User, discord.Object]: @@ -320,11 +318,10 @@ class FetchedUser(Converter): except ValueError: raise BadArgument(f"The provided argument can't be turned into integer: `{user_id}`") except discord.HTTPException as e: - # If the Discord error isn't `Unknown user`, save it in the log and return a proxy + # If the Discord error isn't `Unknown user`, save it in the log and return a proxy instead if e.code != 10013: log.warning("Failed to fetch user, returning a proxy instead.") - # XXX: - return FetchedUser.proxy_user(user_id) + return proxy_user(user_id) raise BadArgument return user -- cgit v1.2.3 From f1689cc8a193747ba0e048db3fa5d8b2c52a1ac7 Mon Sep 17 00:00:00 2001 From: Manuel Ignacio Pérez Alcolea Date: Fri, 20 Dec 2019 23:14:49 -0300 Subject: "Correct indentation style" --- bot/cogs/moderation/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bot/cogs/moderation/utils.py b/bot/cogs/moderation/utils.py index 5b5aacb16..3b39b52ac 100644 --- a/bot/cogs/moderation/utils.py +++ b/bot/cogs/moderation/utils.py @@ -56,8 +56,10 @@ async def post_user(ctx: Context, user: t.Union[discord.User, discord.Object]) - return response except ResponseCodeError as e: log.warn("Couldn't post user.") - await ctx.send("The attempt to add the user to the DB failed: " - f"{e.status}, {e.response_text if e.response_text else 'no message received'}.") + await ctx.send( + "The attempt to add the user to the DB failed: " + f"{e.status}, {e.response_text if e.response_text else 'no message received'}." + ) async def post_infraction( -- cgit v1.2.3 From 32a3fdff0adce1ae76ad6915f5cf6119dde04621 Mon Sep 17 00:00:00 2001 From: Manuel Ignacio Pérez Alcolea Date: Sun, 22 Dec 2019 03:36:40 -0300 Subject: Correct log messages levels, add log messages and ctx.send message --- bot/cogs/moderation/utils.py | 10 +++++----- bot/converters.py | 9 +++++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/bot/cogs/moderation/utils.py b/bot/cogs/moderation/utils.py index 3b39b52ac..8328e4c85 100644 --- a/bot/cogs/moderation/utils.py +++ b/bot/cogs/moderation/utils.py @@ -36,10 +36,10 @@ async def post_user(ctx: Context, user: t.Union[discord.User, discord.Object]) - Used when an infraction needs to be applied on a user absent in the guild. """ - log.trace("Attempting to add user to the database.") + log.trace(f"Attempting to add user {user.id} to the database.") if not isinstance(user, discord.User): - log.warn("The given user is not a discord.User object.") + log.warn("The user being added to the DB is not a Member or User object.") payload = { 'avatar_hash': getattr(user, 'avatar', 0), @@ -52,12 +52,12 @@ async def post_user(ctx: Context, user: t.Union[discord.User, discord.Object]) - try: response = await ctx.bot.api_client.post('bot/users', json=payload) - log.trace(f"User {user.id} added to the DB.") + log.info(f"User {user.id} added to the DB.") return response except ResponseCodeError as e: - log.warn("Couldn't post user.") + log.error(f"Failed to add user {user.id} to the DB. {e}") await ctx.send( - "The attempt to add the user to the DB failed: " + ":x: The attempt to add the user to the DB failed: " f"{e.status}, {e.response_text if e.response_text else 'no message received'}." ) diff --git a/bot/converters.py b/bot/converters.py index 28bf58cf4..5b33f6818 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -291,7 +291,8 @@ def proxy_user(user_id: str) -> discord.Object: try: user_id = int(user_id) except ValueError: - raise BadArgument + log.debug(f"Failed to create proxy user {user_id}: could not convert to int.") + raise BadArgument(f"User ID `{user_id}` is invalid - could not convert to an integer.") user = discord.Object(user_id) user.mention = user.id @@ -314,14 +315,18 @@ class FetchedUser(Converter): """Convert `user_id` to a `discord.User` object, after fetching from the Discord API.""" try: user_id = int(user_id) + log.trace(f"Fetching user {user_id}...") user = 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}`") except discord.HTTPException as e: # If the Discord error isn't `Unknown user`, save it in the log and return a proxy instead if e.code != 10013: - log.warning("Failed to fetch user, returning a proxy instead.") + log.warning(f"Failed to fetch user, returning a proxy instead: status {e.status}") return proxy_user(user_id) + + log.debug(f"Failed to fetch user {user_id}: user does not exist.") raise BadArgument return user -- cgit v1.2.3 From 0fa6c443b4554b526fff5f1e80307b615cea900f Mon Sep 17 00:00:00 2001 From: manusaurio Date: Sun, 22 Dec 2019 03:45:15 -0300 Subject: Add space in condition Co-Authored-By: Mark --- bot/cogs/moderation/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/moderation/utils.py b/bot/cogs/moderation/utils.py index 8328e4c85..d0eaba7c8 100644 --- a/bot/cogs/moderation/utils.py +++ b/bot/cogs/moderation/utils.py @@ -91,7 +91,7 @@ async def post_infraction( response = await ctx.bot.api_client.post('bot/infractions', json=payload) return response except ResponseCodeError as exp: - if exp.status == 400 and 'user'in exp.response_json: + if exp.status == 400 and 'user' in exp.response_json: # Only one attempt to add the user to the database, not two: if not should_post_user or await post_user(ctx, user) is None: return -- cgit v1.2.3 From 3747b8b56dc754ad9f7434e9a5d437133d70b4f3 Mon Sep 17 00:00:00 2001 From: Manuel Ignacio Pérez Alcolea Date: Sun, 22 Dec 2019 03:51:48 -0300 Subject: Add descriptive message to exception if user from API doesn't exist --- bot/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/converters.py b/bot/converters.py index 5b33f6818..028e7cb02 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -327,6 +327,6 @@ class FetchedUser(Converter): return proxy_user(user_id) log.debug(f"Failed to fetch user {user_id}: user does not exist.") - raise BadArgument + raise BadArgument(f"User `{user_id}` does not exist") return user -- cgit v1.2.3 From 0ec0ca1607d81eccd81c458827637eeedc31e9f7 Mon Sep 17 00:00:00 2001 From: Manuel Ignacio Pérez Alcolea Date: Sun, 22 Dec 2019 13:51:15 -0300 Subject: Minor refactor moving `return ...` --- bot/converters.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bot/converters.py b/bot/converters.py index 028e7cb02..aa2fa3cc2 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -316,7 +316,7 @@ class FetchedUser(Converter): try: user_id = int(user_id) log.trace(f"Fetching user {user_id}...") - user = await ctx.bot.fetch_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}`") @@ -328,5 +328,3 @@ class FetchedUser(Converter): log.debug(f"Failed to fetch user {user_id}: user does not exist.") raise BadArgument(f"User `{user_id}` does not exist") - - return user -- cgit v1.2.3 From fcce6b5a7d56fddd0d015408257980b1bc56e4de Mon Sep 17 00:00:00 2001 From: Manuel Ignacio Pérez Alcolea Date: Sun, 22 Dec 2019 14:08:35 -0300 Subject: Move type declarations for annotations and converters It turns out how it was originally was the best idea. Now the `infractions` module imports `FetchedUser` and makes a `typing.Union` between it and `utils.UserTypes`. The usage of `FetchedUser` isn't needed in `utils` at all, and it shouldn't be used for/as type hinting there. --- bot/cogs/moderation/infractions.py | 5 ++++- bot/cogs/moderation/utils.py | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/bot/cogs/moderation/infractions.py b/bot/cogs/moderation/infractions.py index 253a8db5b..5b6a63dbb 100644 --- a/bot/cogs/moderation/infractions.py +++ b/bot/cogs/moderation/infractions.py @@ -9,14 +9,17 @@ from discord.ext.commands import Context, command from bot import constants from bot.bot import Bot from bot.constants import Event +from bot.converters import FetchedUser 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 as MemberConverter +from .utils import MemberObject, UserTypes log = logging.getLogger(__name__) +MemberConverter = t.Union[UserTypes, FetchedUser] + class Infractions(InfractionScheduler, commands.Cog): """Apply and pardon infractions on users for moderation purposes.""" diff --git a/bot/cogs/moderation/utils.py b/bot/cogs/moderation/utils.py index d0eaba7c8..d564d0699 100644 --- a/bot/cogs/moderation/utils.py +++ b/bot/cogs/moderation/utils.py @@ -8,7 +8,7 @@ from discord.ext.commands import Context from bot.api import ResponseCodeError from bot.constants import Colours, Icons -from bot.converters import Duration, FetchedUser, ISODateTime +from bot.converters import Duration, ISODateTime log = logging.getLogger(__name__) @@ -24,13 +24,13 @@ INFRACTION_ICONS = { RULES_URL = "https://pythondiscord.com/pages/rules" APPEALABLE_INFRACTIONS = ("ban", "mute") -UserTypes = t.Union[discord.Member, discord.User, FetchedUser] +UserTypes = t.Union[discord.Member, discord.User] MemberObject = t.Union[UserTypes, discord.Object] Infraction = t.Dict[str, t.Union[str, int, bool]] Expiry = t.Union[Duration, ISODateTime] -async def post_user(ctx: Context, user: t.Union[discord.User, discord.Object]) -> t.Optional[dict]: +async def post_user(ctx: Context, user: MemberObject) -> t.Optional[dict]: """ Create a new user in the database. -- cgit v1.2.3 From 388cf752c0e41695644f532d5c4903bd821f0c29 Mon Sep 17 00:00:00 2001 From: Manuel Ignacio Pérez Alcolea Date: Sun, 22 Dec 2019 14:14:00 -0300 Subject: Fix bug to log if `user` is either `Member` or `User` --- bot/cogs/moderation/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/moderation/utils.py b/bot/cogs/moderation/utils.py index d564d0699..6c28d8db5 100644 --- a/bot/cogs/moderation/utils.py +++ b/bot/cogs/moderation/utils.py @@ -38,7 +38,7 @@ async def post_user(ctx: Context, user: MemberObject) -> t.Optional[dict]: """ log.trace(f"Attempting to add user {user.id} to the database.") - if not isinstance(user, discord.User): + if not isinstance(user, (discord.Member, discord.User)): log.warn("The user being added to the DB is not a Member or User object.") payload = { -- cgit v1.2.3 From 6d42e1e3f33f595b264b17b4ab1d260f956051aa Mon Sep 17 00:00:00 2001 From: Manuel Ignacio Pérez Alcolea Date: Sun, 22 Dec 2019 15:30:08 -0300 Subject: Make `watchchannels` use `FetchedUser` instead of `proxy_user` This changes also removes the original `proxy_user` used by `watchchannels` the attributes in its `discord.Object` object to the one returned by FetchedUser. --- bot/cogs/alias.py | 17 +++++++++++------ bot/cogs/watchchannels/bigbrother.py | 7 ++++--- bot/cogs/watchchannels/talentpool.py | 9 +++++---- bot/cogs/watchchannels/watchchannel.py | 20 ++------------------ bot/converters.py | 2 ++ 5 files changed, 24 insertions(+), 31 deletions(-) diff --git a/bot/cogs/alias.py b/bot/cogs/alias.py index c1db38462..03c49c2f4 100644 --- a/bot/cogs/alias.py +++ b/bot/cogs/alias.py @@ -7,8 +7,7 @@ from discord.ext.commands import Cog, Command, Context, clean_content, command, from bot.bot import Bot from bot.cogs.extensions import Extension -from bot.cogs.watchchannels.watchchannel import proxy_user -from bot.converters import TagNameConverter +from bot.converters import FetchedUser, TagNameConverter from bot.pagination import LinePaginator log = logging.getLogger(__name__) @@ -61,12 +60,18 @@ 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, proxy_user], *, reason: str) -> None: + async def bigbrother_watch_alias( + self, + ctx: Context, + user: Union[Member, User, FetchedUser], + *, + reason: str + ) -> None: """Alias for invoking 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, proxy_user], *, reason: str) -> None: + async def bigbrother_unwatch_alias(self, ctx: Context, user: Union[User, FetchedUser], *, reason: str) -> None: """Alias for invoking bigbrother unwatch [user] [reason].""" await self.invoke(ctx, "bigbrother unwatch", user, reason=reason) @@ -132,12 +137,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, proxy_user], *, reason: str) -> None: + async def nomination_add_alias(self, ctx: Context, user: Union[Member, User, FetchedUser], *, reason: str) -> None: """Alias for invoking 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, proxy_user], *, reason: str) -> None: + async def nomination_end_alias(self, ctx: Context, user: Union[User, FetchedUser], *, reason: str) -> None: """Alias for invoking nomination end [user] [reason].""" await self.invoke(ctx, "nomination end", user, reason=reason) diff --git a/bot/cogs/watchchannels/bigbrother.py b/bot/cogs/watchchannels/bigbrother.py index 306ed4c64..7a30d5033 100644 --- a/bot/cogs/watchchannels/bigbrother.py +++ b/bot/cogs/watchchannels/bigbrother.py @@ -8,8 +8,9 @@ from discord.ext.commands import Cog, Context, group from bot.bot import Bot from bot.cogs.moderation.utils import post_infraction from bot.constants import Channels, MODERATION_ROLES, Webhooks +from bot.converters import FetchedUser from bot.decorators import with_role -from .watchchannel import WatchChannel, proxy_user +from .watchchannel import WatchChannel log = logging.getLogger(__name__) @@ -46,7 +47,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, proxy_user], *, reason: str) -> None: + async def watch_command(self, ctx: Context, user: Union[User, FetchedUser], *, reason: str) -> None: """ Relay messages sent by the given `user` to the `#big-brother` channel. @@ -93,7 +94,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, proxy_user], *, reason: str) -> None: + async def unwatch_command(self, ctx: Context, user: Union[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 cc8feeeee..62be3bc3b 100644 --- a/bot/cogs/watchchannels/talentpool.py +++ b/bot/cogs/watchchannels/talentpool.py @@ -9,10 +9,11 @@ from discord.ext.commands import Cog, Context, group from bot.api import ResponseCodeError from bot.bot import Bot from bot.constants import Channels, Guild, MODERATION_ROLES, STAFF_ROLES, Webhooks +from bot.converters import FetchedUser from bot.decorators import with_role from bot.pagination import LinePaginator from bot.utils import time -from .watchchannel import WatchChannel, proxy_user +from .watchchannel import WatchChannel log = logging.getLogger(__name__) @@ -49,7 +50,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, proxy_user], *, reason: str) -> None: + async def watch_command(self, ctx: Context, user: Union[Member, User, FetchedUser], *, reason: str) -> None: """ Relay messages sent by the given `user` to the `#talent-pool` channel. @@ -114,7 +115,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, proxy_user]) -> None: + async def history_command(self, ctx: Context, user: Union[User, FetchedUser]) -> None: """Shows the specified user's nomination history.""" result = await self.bot.api_client.get( self.api_endpoint, @@ -143,7 +144,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, proxy_user], *, reason: str) -> None: + async def unwatch_command(self, ctx: Context, user: Union[User, FetchedUser], *, reason: str) -> None: """ Ends the active nomination of the specified user with the given reason. diff --git a/bot/cogs/watchchannels/watchchannel.py b/bot/cogs/watchchannels/watchchannel.py index bd0622554..eb787b083 100644 --- a/bot/cogs/watchchannels/watchchannel.py +++ b/bot/cogs/watchchannels/watchchannel.py @@ -9,8 +9,8 @@ from typing import Optional import dateutil.parser import discord -from discord import Color, Embed, HTTPException, Message, Object, errors -from discord.ext.commands import BadArgument, Cog, Context +from discord import Color, Embed, HTTPException, Message, errors +from discord.ext.commands import Cog, Context from bot.api import ResponseCodeError from bot.bot import Bot @@ -25,22 +25,6 @@ log = logging.getLogger(__name__) URL_RE = re.compile(r"(https?://[^\s]+)") -def proxy_user(user_id: str) -> Object: - """A proxy user object that mocks a real User instance for when the later is not available.""" - try: - user_id = int(user_id) - except ValueError: - raise BadArgument - - user = Object(user_id) - user.mention = user.id - user.display_name = f"<@{user.id}>" - user.avatar_url_as = lambda static_format: None - user.bot = False - - return user - - @dataclass class MessageHistory: """Represents a watch channel's message history.""" diff --git a/bot/converters.py b/bot/converters.py index aa2fa3cc2..a2e445d74 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -296,7 +296,9 @@ def proxy_user(user_id: str) -> discord.Object: user = discord.Object(user_id) user.mention = user.id + user.display_name = f"<@{user.id}>" user.avatar_url_as = lambda static_format: None + user.bot = False return user -- cgit v1.2.3 From e30cedc63e06ea136947934e579824f9e3b744bf Mon Sep 17 00:00:00 2001 From: Manuel Ignacio Pérez Alcolea Date: Sun, 22 Dec 2019 17:27:42 -0300 Subject: Catch HTTPException in fetching, only fetch if isn't User or Member There's now a check to see if the `user` argument (possibly a `discord.Object`) needs to be made a `User`, instead of doing so directly, to avoid unnecessary requests to the Discord API. Besides that, a possible HTTPException is catched if it the fetch fails, cancelling the message to be send to the user (which would make the following calls fail later on for not being of the proper type.) --- bot/cogs/moderation/scheduler.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/bot/cogs/moderation/scheduler.py b/bot/cogs/moderation/scheduler.py index 01e4b1fe7..4b3d553c2 100644 --- a/bot/cogs/moderation/scheduler.py +++ b/bot/cogs/moderation/scheduler.py @@ -106,16 +106,20 @@ class InfractionScheduler(Scheduler): # DM the user about the infraction if it's not a shadow/hidden infraction. if not infraction["hidden"]: - # Sometimes user is a discord.Object; make it a proper user. - user = await self.bot.fetch_user(user.id) + dm_result = f"{constants.Emojis.failmail} " + dm_log_text = "\nDM: **Failed**" - # Accordingly display whether the user was successfully notified via DM. - if await utils.notify_infraction(user, infr_type, expiry, reason, icon): - dm_result = ":incoming_envelope: " - dm_log_text = "\nDM: Sent" + # Sometimes user is a discord.Object; make it a proper user. + try: + if not isinstance(user, (discord.Member, discord.User)): + user = await self.bot.fetch_user(user.id) + except discord.HTTPException as e: + log.error(f"Failed to fetch user `{user.id}`: status {e.status}") else: - dm_result = f"{constants.Emojis.failmail} " - dm_log_text = "\nDM: **Failed**" + # Accordingly display whether the user was successfully notified via DM. + if await utils.notify_infraction(user, infr_type, expiry, reason, icon): + dm_result = ":incoming_envelope: " + dm_log_text = "\nDM: Sent" if infraction["actor"] == self.bot.user.id: log.trace( -- cgit v1.2.3 From 9f3efb023c4bed8987f2b90454462ed37cdbb597 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 22 Dec 2019 14:25:36 -0800 Subject: Remove unreachable break in post_infraction loop * Show the user in the post_infraction error log message --- bot/cogs/moderation/utils.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bot/cogs/moderation/utils.py b/bot/cogs/moderation/utils.py index 6c28d8db5..160ced501 100644 --- a/bot/cogs/moderation/utils.py +++ b/bot/cogs/moderation/utils.py @@ -96,11 +96,9 @@ async def post_infraction( if not should_post_user or await post_user(ctx, user) is None: return else: - log.exception("An unexpected ResponseCodeError occurred while adding an infraction:") + log.exception(f"Unexpected error while adding an infraction for {user}:") await ctx.send(":x: There was an error adding the infraction.") return - else: - break async def has_active_infraction(ctx: Context, user: MemberObject, infr_type: str) -> bool: -- cgit v1.2.3 From cf7d4f9c334bb96b3dd95a083bdff12be9e7defe Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 22 Dec 2019 14:29:42 -0800 Subject: Use more specific error message for infraction DM user fetch --- bot/cogs/moderation/scheduler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/moderation/scheduler.py b/bot/cogs/moderation/scheduler.py index 4b3d553c2..732091c17 100644 --- a/bot/cogs/moderation/scheduler.py +++ b/bot/cogs/moderation/scheduler.py @@ -114,7 +114,7 @@ class InfractionScheduler(Scheduler): if not isinstance(user, (discord.Member, discord.User)): user = await self.bot.fetch_user(user.id) except discord.HTTPException as e: - log.error(f"Failed to fetch user `{user.id}`: status {e.status}") + log.error(f"Failed to DM {user.id}: could not fetch user (status {e.status})") else: # Accordingly display whether the user was successfully notified via DM. if await utils.notify_infraction(user, infr_type, expiry, reason, icon): -- cgit v1.2.3 From ae6eefbdcad5dbbd02edc33adba8e481be14fa2b Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 22 Dec 2019 14:43:24 -0800 Subject: Show only status code in Discord msg when infraction post fails When debugging, the response_text exceeds the character limit since it's basically an entire HTML document. --- bot/cogs/moderation/utils.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/bot/cogs/moderation/utils.py b/bot/cogs/moderation/utils.py index 160ced501..73335ca30 100644 --- a/bot/cogs/moderation/utils.py +++ b/bot/cogs/moderation/utils.py @@ -56,10 +56,7 @@ async def post_user(ctx: Context, user: MemberObject) -> t.Optional[dict]: return response except ResponseCodeError as e: log.error(f"Failed to add user {user.id} to the DB. {e}") - await ctx.send( - ":x: The attempt to add the user to the DB failed: " - f"{e.status}, {e.response_text if e.response_text else 'no message received'}." - ) + await ctx.send(f":x: The attempt to add the user to the DB failed: status {e.status}") async def post_infraction( @@ -90,14 +87,14 @@ async def post_infraction( try: response = await ctx.bot.api_client.post('bot/infractions', json=payload) return response - except ResponseCodeError as exp: - if exp.status == 400 and 'user' in exp.response_json: + except ResponseCodeError as e: + if e.status == 400 and 'user' in e.response_json: # Only one attempt to add the user to the database, not two: if not should_post_user or await post_user(ctx, user) is None: return else: log.exception(f"Unexpected error while adding an infraction for {user}:") - await ctx.send(":x: There was an error adding the infraction.") + await ctx.send(f":x: There was an error adding the infraction: status {e.status}.") return -- cgit v1.2.3 From 8946f7d05272c26141faaae5f9b4b5f278b269cf Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 22 Dec 2019 19:12:01 -0800 Subject: Use log.warning instead of the deprecated log.warn --- bot/cogs/moderation/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/moderation/utils.py b/bot/cogs/moderation/utils.py index 73335ca30..6f7ea9771 100644 --- a/bot/cogs/moderation/utils.py +++ b/bot/cogs/moderation/utils.py @@ -39,7 +39,7 @@ async def post_user(ctx: Context, user: MemberObject) -> t.Optional[dict]: log.trace(f"Attempting to add user {user.id} to the database.") if not isinstance(user, (discord.Member, discord.User)): - log.warn("The user being added to the DB is not a Member or User object.") + log.warning("The user being added to the DB is not a Member or User object.") payload = { 'avatar_hash': getattr(user, 'avatar', 0), -- cgit v1.2.3 From 562af4fbef7d2b093eb76f563c5f1c635124e299 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 22 Dec 2019 19:15:32 -0800 Subject: Make FetchedUser a subclass of UserConverter --- bot/cogs/alias.py | 17 ++++--------- bot/cogs/moderation/infractions.py | 18 ++++++-------- bot/cogs/moderation/management.py | 4 +-- bot/cogs/watchchannels/bigbrother.py | 6 ++--- bot/cogs/watchchannels/talentpool.py | 9 +++---- 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 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 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 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 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") -- cgit v1.2.3 From c0293abbea7a6a6bed66221b40c827d71ca5ee22 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Mon, 23 Dec 2019 11:16:05 -0800 Subject: Create an alias for a Member + FetchedUser converter --- bot/cogs/alias.py | 10 +++++----- bot/cogs/moderation/infractions.py | 16 ++++++++-------- bot/cogs/watchchannels/bigbrother.py | 6 +++--- bot/cogs/watchchannels/talentpool.py | 8 ++++---- bot/converters.py | 3 +++ 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/bot/cogs/alias.py b/bot/cogs/alias.py index e709be85d..197155456 100644 --- a/bot/cogs/alias.py +++ b/bot/cogs/alias.py @@ -6,7 +6,7 @@ from discord.ext.commands import Cog, Command, Context, clean_content, command, from bot.bot import Bot from bot.cogs.extensions import Extension -from bot.converters import FetchedUser, TagNameConverter +from bot.converters import FetchedMember, TagNameConverter from bot.pagination import LinePaginator log = logging.getLogger(__name__) @@ -59,12 +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: FetchedUser, *, reason: str) -> None: + async def bigbrother_watch_alias(self, ctx: Context, user: FetchedMember, *, reason: str) -> None: """Alias for invoking 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: FetchedUser, *, reason: str) -> None: + async def bigbrother_unwatch_alias(self, ctx: Context, user: FetchedMember, *, reason: str) -> None: """Alias for invoking bigbrother unwatch [user] [reason].""" await self.invoke(ctx, "bigbrother unwatch", user, reason=reason) @@ -130,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: FetchedUser, *, reason: str) -> None: + async def nomination_add_alias(self, ctx: Context, user: FetchedMember, *, reason: str) -> None: """Alias for invoking 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: FetchedUser, *, reason: str) -> None: + async def nomination_end_alias(self, ctx: Context, user: FetchedMember, *, reason: str) -> None: """Alias for invoking 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 264d8bcf5..87407f26b 100644 --- a/bot/cogs/moderation/infractions.py +++ b/bot/cogs/moderation/infractions.py @@ -9,7 +9,7 @@ from discord.ext.commands import Context, command from bot import constants from bot.bot import Bot from bot.constants import Event -from bot.converters import FetchedUser +from bot.converters import FetchedMember from bot.decorators import respect_role_hierarchy from bot.utils.checks import with_role_check from . import utils @@ -66,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: FetchedUser, *, reason: str = None) -> None: + async def ban(self, ctx: Context, user: FetchedMember, *, reason: str = None) -> None: """Permanently ban a user for the given reason.""" await self.apply_ban(ctx, user, reason) @@ -93,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: FetchedUser, duration: utils.Expiry, *, reason: str = None) -> None: + async def tempban(self, ctx: Context, user: FetchedMember, duration: utils.Expiry, *, reason: str = None) -> None: """ Temporarily ban a user for the given reason and duration. @@ -115,7 +115,7 @@ class Infractions(InfractionScheduler, commands.Cog): # region: Permanent shadow infractions @command(hidden=True) - async def note(self, ctx: Context, user: FetchedUser, *, reason: str = None) -> None: + async def note(self, ctx: Context, user: FetchedMember, *, 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: @@ -129,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: FetchedUser, *, reason: str = None) -> None: + async def shadow_ban(self, ctx: Context, user: FetchedMember, *, 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) @@ -159,7 +159,7 @@ class Infractions(InfractionScheduler, commands.Cog): async def shadow_tempban( self, ctx: Context, - user: FetchedUser, + user: FetchedMember, duration: utils.Expiry, *, reason: str = None @@ -185,12 +185,12 @@ class Infractions(InfractionScheduler, commands.Cog): # region: Remove infractions (un- commands) @command() - async def unmute(self, ctx: Context, user: FetchedUser) -> None: + async def unmute(self, ctx: Context, user: FetchedMember) -> 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: FetchedUser) -> None: + async def unban(self, ctx: Context, user: FetchedMember) -> None: """Prematurely end the active ban infraction for the user.""" await self.pardon_infraction(ctx, "ban", user) diff --git a/bot/cogs/watchchannels/bigbrother.py b/bot/cogs/watchchannels/bigbrother.py index b2cb99368..41f933247 100644 --- a/bot/cogs/watchchannels/bigbrother.py +++ b/bot/cogs/watchchannels/bigbrother.py @@ -6,7 +6,7 @@ from discord.ext.commands import Cog, Context, group from bot.bot import Bot from bot.cogs.moderation.utils import post_infraction from bot.constants import Channels, MODERATION_ROLES, Webhooks -from bot.converters import FetchedUser +from bot.converters import FetchedMember from bot.decorators import with_role from .watchchannel import WatchChannel @@ -45,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: FetchedUser, *, reason: str) -> None: + async def watch_command(self, ctx: Context, user: FetchedMember, *, reason: str) -> None: """ Relay messages sent by the given `user` to the `#big-brother` channel. @@ -92,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: FetchedUser, *, reason: str) -> None: + async def unwatch_command(self, ctx: Context, user: FetchedMember, *, 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 776f0ea87..329a78af4 100644 --- a/bot/cogs/watchchannels/talentpool.py +++ b/bot/cogs/watchchannels/talentpool.py @@ -8,7 +8,7 @@ from discord.ext.commands import Cog, Context, group from bot.api import ResponseCodeError from bot.bot import Bot from bot.constants import Channels, Guild, MODERATION_ROLES, STAFF_ROLES, Webhooks -from bot.converters import FetchedUser +from bot.converters import FetchedMember from bot.decorators import with_role from bot.pagination import LinePaginator from bot.utils import time @@ -49,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: FetchedUser, *, reason: str) -> None: + async def watch_command(self, ctx: Context, user: FetchedMember, *, reason: str) -> None: """ Relay messages sent by the given `user` to the `#talent-pool` channel. @@ -114,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: FetchedUser) -> None: + async def history_command(self, ctx: Context, user: FetchedMember) -> None: """Shows the specified user's nomination history.""" result = await self.bot.api_client.get( self.api_endpoint, @@ -143,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: FetchedUser, *, reason: str) -> None: + async def unwatch_command(self, ctx: Context, user: FetchedMember, *, 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 4fb800a01..be972086d 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -346,3 +346,6 @@ class FetchedUser(UserConverter): log.debug(f"Failed to fetch user {arg}: user does not exist.") raise BadArgument(f"User `{arg}` does not exist") + + +FetchedMember = t.Union[discord.Member, FetchedUser] -- cgit v1.2.3 From 1b52d13ac313eda420a54de829750882cf2bb2ab Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Mon, 23 Dec 2019 11:16:51 -0800 Subject: Refactor user type aliases --- bot/cogs/moderation/infractions.py | 4 ++-- bot/cogs/moderation/modlog.py | 3 +-- bot/cogs/moderation/scheduler.py | 6 +++--- bot/cogs/moderation/utils.py | 17 +++++++++-------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/bot/cogs/moderation/infractions.py b/bot/cogs/moderation/infractions.py index 87407f26b..d7201bdb1 100644 --- a/bot/cogs/moderation/infractions.py +++ b/bot/cogs/moderation/infractions.py @@ -14,7 +14,7 @@ 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 +from .utils import UserSnowflake log = logging.getLogger(__name__) @@ -229,7 +229,7 @@ class Infractions(InfractionScheduler, commands.Cog): await self.apply_infraction(ctx, infraction, user, action) @respect_role_hierarchy() - async def apply_ban(self, ctx: Context, user: MemberObject, reason: str, **kwargs) -> None: + async def apply_ban(self, ctx: Context, user: UserSnowflake, reason: str, **kwargs) -> None: """Apply a ban infraction with kwargs passed to `post_infraction`.""" if await utils.has_active_infraction(ctx, user, "ban"): return diff --git a/bot/cogs/moderation/modlog.py b/bot/cogs/moderation/modlog.py index 35ef6cbcc..1e031443c 100644 --- a/bot/cogs/moderation/modlog.py +++ b/bot/cogs/moderation/modlog.py @@ -15,7 +15,6 @@ from discord.ext.commands import Cog, Context from bot.bot import Bot from bot.constants import Channels, Colours, Emojis, Event, Guild as GuildConstant, Icons, URLs from bot.utils.time import humanize_delta -from .utils import UserTypes log = logging.getLogger(__name__) @@ -355,7 +354,7 @@ class ModLog(Cog, name="ModLog"): ) @Cog.listener() - async def on_member_ban(self, guild: discord.Guild, member: UserTypes) -> None: + async def on_member_ban(self, guild: discord.Guild, member: discord.Member) -> None: """Log ban event to user log.""" if guild.id != GuildConstant.id: return diff --git a/bot/cogs/moderation/scheduler.py b/bot/cogs/moderation/scheduler.py index 732091c17..e14c302cb 100644 --- a/bot/cogs/moderation/scheduler.py +++ b/bot/cogs/moderation/scheduler.py @@ -17,7 +17,7 @@ from bot.utils import time from bot.utils.scheduling import Scheduler from . import utils from .modlog import ModLog -from .utils import MemberObject +from .utils import UserSnowflake log = logging.getLogger(__name__) @@ -77,7 +77,7 @@ class InfractionScheduler(Scheduler): self, ctx: Context, infraction: utils.Infraction, - user: MemberObject, + user: UserSnowflake, action_coro: t.Optional[t.Awaitable] = None ) -> None: """Apply an infraction to the user, log the infraction, and optionally notify the user.""" @@ -189,7 +189,7 @@ class InfractionScheduler(Scheduler): log.info(f"Applied {infr_type} infraction #{id_} to {user}.") - async def pardon_infraction(self, ctx: Context, infr_type: str, user: MemberObject) -> None: + async def pardon_infraction(self, ctx: Context, infr_type: str, user: UserSnowflake) -> None: """Prematurely end an infraction for a user and log the action in the mod log.""" log.trace(f"Pardoning {infr_type} infraction for {user}.") diff --git a/bot/cogs/moderation/utils.py b/bot/cogs/moderation/utils.py index 6f7ea9771..79555369c 100644 --- a/bot/cogs/moderation/utils.py +++ b/bot/cogs/moderation/utils.py @@ -24,13 +24,14 @@ INFRACTION_ICONS = { RULES_URL = "https://pythondiscord.com/pages/rules" APPEALABLE_INFRACTIONS = ("ban", "mute") -UserTypes = t.Union[discord.Member, discord.User] -MemberObject = t.Union[UserTypes, discord.Object] +# Type aliases +UserObject = t.Union[discord.Member, discord.User] +UserSnowflake = t.Union[UserObject, discord.Object] Infraction = t.Dict[str, t.Union[str, int, bool]] Expiry = t.Union[Duration, ISODateTime] -async def post_user(ctx: Context, user: MemberObject) -> t.Optional[dict]: +async def post_user(ctx: Context, user: UserSnowflake) -> t.Optional[dict]: """ Create a new user in the database. @@ -61,7 +62,7 @@ async def post_user(ctx: Context, user: MemberObject) -> t.Optional[dict]: async def post_infraction( ctx: Context, - user: MemberObject, + user: UserSnowflake, infr_type: str, reason: str, expires_at: datetime = None, @@ -98,7 +99,7 @@ async def post_infraction( return -async def has_active_infraction(ctx: Context, user: MemberObject, infr_type: str) -> bool: +async def has_active_infraction(ctx: Context, user: UserSnowflake, infr_type: str) -> bool: """Checks if a user already has an active infraction of the given type.""" log.trace(f"Checking if {user} has active infractions of type {infr_type}.") @@ -123,7 +124,7 @@ async def has_active_infraction(ctx: Context, user: MemberObject, infr_type: str async def notify_infraction( - user: UserTypes, + user: UserObject, infr_type: str, expires_at: t.Optional[str] = None, reason: t.Optional[str] = None, @@ -154,7 +155,7 @@ async def notify_infraction( async def notify_pardon( - user: UserTypes, + user: UserObject, title: str, content: str, icon_url: str = Icons.user_verified @@ -172,7 +173,7 @@ async def notify_pardon( return await send_private_embed(user, embed) -async def send_private_embed(user: UserTypes, embed: discord.Embed) -> bool: +async def send_private_embed(user: UserObject, embed: discord.Embed) -> bool: """ A helper method for sending an embed to a user's DMs. -- cgit v1.2.3 From 35207f5b05e244b88c1c3cf02cd8385152821104 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Mon, 23 Dec 2019 11:37:50 -0800 Subject: Move Expiry converter alias to converters.py --- bot/cogs/moderation/infractions.py | 10 +++++----- bot/cogs/moderation/management.py | 4 ++-- bot/cogs/moderation/superstarify.py | 3 ++- bot/cogs/moderation/utils.py | 2 -- bot/converters.py | 1 + 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bot/cogs/moderation/infractions.py b/bot/cogs/moderation/infractions.py index d7201bdb1..f4159adb9 100644 --- a/bot/cogs/moderation/infractions.py +++ b/bot/cogs/moderation/infractions.py @@ -9,7 +9,7 @@ from discord.ext.commands import Context, command from bot import constants from bot.bot import Bot from bot.constants import Event -from bot.converters import FetchedMember +from bot.converters import Expiry, FetchedMember from bot.decorators import respect_role_hierarchy from bot.utils.checks import with_role_check from . import utils @@ -74,7 +74,7 @@ class Infractions(InfractionScheduler, commands.Cog): # region: Temporary infractions @command(aliases=["mute"]) - async def tempmute(self, ctx: Context, user: Member, duration: utils.Expiry, *, reason: str = None) -> None: + async def tempmute(self, ctx: Context, user: Member, duration: Expiry, *, reason: str = None) -> None: """ Temporarily mute a user for the given reason and duration. @@ -93,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: FetchedMember, duration: utils.Expiry, *, reason: str = None) -> None: + async def tempban(self, ctx: Context, user: FetchedMember, duration: Expiry, *, reason: str = None) -> None: """ Temporarily ban a user for the given reason and duration. @@ -137,7 +137,7 @@ class Infractions(InfractionScheduler, commands.Cog): # region: Temporary shadow infractions @command(hidden=True, aliases=["shadowtempmute, stempmute", "shadowmute", "smute"]) - async def shadow_tempmute(self, ctx: Context, user: Member, duration: utils.Expiry, *, reason: str = None) -> None: + async def shadow_tempmute(self, ctx: Context, user: Member, duration: Expiry, *, reason: str = None) -> None: """ Temporarily mute a user for the given reason and duration without notifying the user. @@ -160,7 +160,7 @@ class Infractions(InfractionScheduler, commands.Cog): self, ctx: Context, user: FetchedMember, - duration: utils.Expiry, + duration: Expiry, *, reason: str = None ) -> None: diff --git a/bot/cogs/moderation/management.py b/bot/cogs/moderation/management.py index 2ec69960e..0636422d3 100644 --- a/bot/cogs/moderation/management.py +++ b/bot/cogs/moderation/management.py @@ -10,7 +10,7 @@ from discord.ext.commands import Context from bot import constants from bot.bot import Bot -from bot.converters import InfractionSearchQuery, allowed_strings, proxy_user +from bot.converters import Expiry, InfractionSearchQuery, allowed_strings, proxy_user from bot.pagination import LinePaginator from bot.utils import time from bot.utils.checks import in_channel_check, with_role_check @@ -51,7 +51,7 @@ class ModManagement(commands.Cog): self, ctx: Context, infraction_id: t.Union[int, allowed_strings("l", "last", "recent")], - duration: t.Union[utils.Expiry, allowed_strings("p", "permanent"), None], + duration: t.Union[Expiry, allowed_strings("p", "permanent"), None], *, reason: str = None ) -> None: diff --git a/bot/cogs/moderation/superstarify.py b/bot/cogs/moderation/superstarify.py index 7631d9bbe..be1e7b2d9 100644 --- a/bot/cogs/moderation/superstarify.py +++ b/bot/cogs/moderation/superstarify.py @@ -10,6 +10,7 @@ from discord.ext.commands import Cog, Context, command from bot import constants from bot.bot import Bot +from bot.converters import Expiry from bot.utils.checks import with_role_check from bot.utils.time import format_infraction from . import utils @@ -107,7 +108,7 @@ class Superstarify(InfractionScheduler, Cog): self, ctx: Context, member: Member, - duration: utils.Expiry, + duration: Expiry, reason: str = None ) -> None: """ diff --git a/bot/cogs/moderation/utils.py b/bot/cogs/moderation/utils.py index 79555369c..5052b9048 100644 --- a/bot/cogs/moderation/utils.py +++ b/bot/cogs/moderation/utils.py @@ -8,7 +8,6 @@ from discord.ext.commands import Context from bot.api import ResponseCodeError from bot.constants import Colours, Icons -from bot.converters import Duration, ISODateTime log = logging.getLogger(__name__) @@ -28,7 +27,6 @@ APPEALABLE_INFRACTIONS = ("ban", "mute") UserObject = t.Union[discord.Member, discord.User] UserSnowflake = t.Union[UserObject, discord.Object] Infraction = t.Dict[str, t.Union[str, int, bool]] -Expiry = t.Union[Duration, ISODateTime] async def post_user(ctx: Context, user: UserSnowflake) -> t.Optional[dict]: diff --git a/bot/converters.py b/bot/converters.py index be972086d..cca57a02d 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -348,4 +348,5 @@ class FetchedUser(UserConverter): raise BadArgument(f"User `{arg}` does not exist") +Expiry = t.Union[Duration, ISODateTime] FetchedMember = t.Union[discord.Member, FetchedUser] -- cgit v1.2.3