Support applying infractions to users not in the DB via Converter FetchedUser#701
Conversation
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`.
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`.
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.
FetchedUser
FetchedUserFetchedUser
|
I know adding to the DB could have been done in the converter itself, but I felt it was a bit odd to make a converter have side effects like that. I think it should limit its job to just... convert. Not sure if I'm wrong on this. |
MarkKoz
left a comment
There was a problem hiding this comment.
This looks promising so far. It may be worth keeping proxy_user in case a fetch or a POST to the database fails (but probably not if the fetch error indicates a Discord user with that ID does not exist). Note that the watch channels have their own proxy user definition for whatever reason.
|
Also, there is a |
|
I'll pick this up in a few hours, to refactor it and finish it. Question: how should On the other hand, there's a PR by @Numerlor that removes both |
|
The fetch may fail even if the user is already in the database since our
API is, of course, separate from Discord's. In this situation the
infractions will behave as they currently do - they work as long as the
user is in the database. Better than not having anything at all to fall
back on.
If we want it to really be robust, then fake information could be posted to
the API if it's fallen back to a proxy user. It's not a big deal since the
ID is the only important field and that is being supplied by the person
invoking the command (if it's a username instead then just give up).
|
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.
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.
|
A couple of issues came up, so now it looks worse. As you said, @MarkKoz, it would be a good idea to have that |
Why not? If it's a
There isn't a need to import it. Make the object inside the class FetchedUser:
async def convert():
...
try:
# If the fetch succeeds just return the user
return await fetch_user(user_id)
except discord.HTTPException as e:
if e.status != 404:
# There was some weird API issue.
# Create a mock User object
user = discord.Object(user_id)
user.mention = user.id
...
return user
else:
# It's a 404 so the user simply doesn't exist.
raise BadArgument("User does not exist.")The original |
My sentence should be read as "if there is an error and it doesn't imply the user doesn't exist, then return a proxy," sorry if I worded it poorly, the comma shouldn't be there (
That makes it much easier then. But |
Oh that is a good case for it. I'd say leave it. You should just move Regarding #651, it's stale so I wouldn't wait for it. I don't think the resulting conflicts will be too bad. If you really want it merged first, you could try poking the author or even ask to take it over yourself if you fancy that. |
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.
MarkKoz
left a comment
There was a problem hiding this comment.
For the most part, this seems to work.
This fetch_user needs to be addressed somehow (e.g. skip DM if user is a proxy) because otherwise it will raise an error
bot/bot/cogs/moderation/scheduler.py
Line 110 in 3b6c7d8
Watch channels have their own proxy_user which should be removed. The commands there should also use FetchedUser
Co-Authored-By: Mark <kozlovmark@gmail.com>
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.
This changes also removes the original `proxy_user` used by `watchchannels` the attributes in its `discord.Object` object to the one returned by FetchedUser.
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.)
* Show the user in the post_infraction error log message
When debugging, the response_text exceeds the character limit since it's basically an entire HTML document.
MarkKoz
left a comment
There was a problem hiding this comment.
Nice work! I took some liberties to make the converter subclass UserConverter and also clean up some of the aliases.
Closes #627
Trying to ban a user uses 3 sources as converter:
discord.Member,discord.Userand theproxy_userfunction, in that order. If a user never joined the server, the first two fails, andproxy_userdoesn't, but the latter is essentially useless anyway in this case, since the user needs to have its own row in the database for the infraction to take place.How this change works
Ban-functions now try to convert the
userargument with sources in the following order:discord.Memberdiscord.Userbot.converters.FetchedUser(returnsdiscord.Userordiscord.Object)There are several rules for this in the previous flow:
If the
FetchedUserconverter is executed, it will fetch from the Discord API. If there's no error during the fetch, thediscord.Userobject will be used to try to post the infraction. If there's an error because the user doesn't exist, thediscord.Userobject attributes will be used to add a new user to the database, then infraction application will be posted again.If there's any error during the fetch, and said error isn't explicitly that the user doesn't exist, a made-up
discord.Objectcontaining the user ID will be used, and if the user needs to be added to the DB, default values will be used to fill the remaining columns:avatar_hash:0discriminator:0in_guild:Falsename:Name unknownroles:[]Main changes and additions
post_user(...): adds a new user to the database, uses default values if the passed object doesn't have all the required attributes.post_infraction: tries topost_user(...)if the first try posting the infraction fails because the user doesn't exist.proxy_usertobot.convertersFetchedUser(Converter)class, which fetches from the Discord API and returns adiscord.Userordiscord.Objectif the fetching fails (as long as the error doesn't indicate the user doesn't exist.)Observations
The flow after
discord.Member/discord.Usercould be different (since we seem to aim to minimize API calls), but longer, and (presumably) more convoluted:discord.Objectfirst.discord.Objectanyway and use default values for the rows to add the user to the database.Examples