diff --git a/discord/client.py b/discord/client.py index b6198d10..1faa5706 100644 --- a/discord/client.py +++ b/discord/client.py @@ -827,6 +827,38 @@ class Client: """ return self._connection.get_user(id) + async def try_user(self, id: int, /) -> Optional[User]: + """|coro| + Returns a user with the given ID. If not from cache, the user will be requested from the API. + + You do not have to share any guilds with the user to get this information from the API, + however many operations do require that you do. + + .. note:: + This method is an API call. If you have :attr:`discord.Intents.members` and member cache enabled, consider :meth:`get_user` instead. + + .. versionadded:: 2.0 + + Parameters + ----------- + id: :class:`int` + The ID to search for. + + Returns + -------- + Optional[:class:`~discord.User`] + The user or ``None`` if not found. + """ + maybe_user = self.get_user(id) + + if maybe_user is not None: + return maybe_user + + try: + return await self.fetch_user(id) + except NotFound: + return None + def get_emoji(self, id: int, /) -> Optional[Emoji]: """Returns an emoji with the given ID. diff --git a/discord/ext/commands/bot.py b/discord/ext/commands/bot.py index b4da6100..27fb9c91 100644 --- a/discord/ext/commands/bot.py +++ b/discord/ext/commands/bot.py @@ -344,14 +344,59 @@ class BotBase(GroupMixin): elif self.owner_ids: return user.id in self.owner_ids else: + # Populate the used fields, then retry the check. This is only done at-most once in the bot lifetime. + await self.populate_owners() + return await self.is_owner(user) - app = await self.application_info() # type: ignore - if app.team: - self.owner_ids = ids = {m.id for m in app.team.members} - return user.id in ids + async def try_owners(self) -> List[discord.User]: + """|coro| + + Returns a list of :class:`~discord.User` representing the owners of the bot. + It uses the :attr:`owner_id` and :attr:`owner_ids`, if set. + + .. versionadded:: 2.0 + The function also checks if the application is team-owned if + :attr:`owner_ids` is not set. + + Returns + -------- + List[:class:`~discord.User`] + List of owners of the bot. + """ + if self.owner_id: + owner = await self.try_user(self.owner_id) + + if owner: + return [owner] else: - self.owner_id = owner_id = app.owner.id - return user.id == owner_id + return [] + + elif self.owner_ids: + owners = [] + + for owner_id in self.owner_ids: + owner = await self.try_user(owner_id) + if owner: + owners.append(owner) + + return owners + else: + # We didn't have owners cached yet, cache them and retry. + await self.populate_owners() + return await self.try_owners() + + async def populate_owners(self): + """|coro| + + Populate the :attr:`owner_id` and :attr:`owner_ids` through the use of :meth:`~.Bot.application_info`. + + .. versionadded:: 2.0 + """ + app = await self.application_info() # type: ignore + if app.team: + self.owner_ids = {m.id for m in app.team.members} + else: + self.owner_id = app.owner.id def before_invoke(self, coro: CFT) -> CFT: """A decorator that registers a coroutine as a pre-invoke hook.