From cd48c9259d7286cec6163e50364d99a14e75a757 Mon Sep 17 00:00:00 2001 From: shiftinv <8530778+shiftinv@users.noreply.github.com> Date: Fri, 17 Nov 2023 20:39:17 +0100 Subject: [PATCH] feat: add `TeamMember.role` (#1094) --- changelog/1094.doc.rst | 1 + changelog/1094.feature.0.rst | 1 + changelog/1094.feature.1.rst | 1 + disnake/enums.py | 10 ++++++++++ disnake/ext/commands/bot.py | 4 ++-- disnake/ext/commands/common_bot_base.py | 13 +++++++++++-- disnake/team.py | 23 +++++++++++------------ disnake/types/team.py | 5 +++-- docs/api/app_info.rst | 22 ++++++++++++++++++++++ 9 files changed, 62 insertions(+), 18 deletions(-) create mode 100644 changelog/1094.doc.rst create mode 100644 changelog/1094.feature.0.rst create mode 100644 changelog/1094.feature.1.rst diff --git a/changelog/1094.doc.rst b/changelog/1094.doc.rst new file mode 100644 index 0000000000..13fe750a4b --- /dev/null +++ b/changelog/1094.doc.rst @@ -0,0 +1 @@ +Add inherited attributes to :class:`TeamMember`, and fix :attr:`TeamMember.avatar` documentation. diff --git a/changelog/1094.feature.0.rst b/changelog/1094.feature.0.rst new file mode 100644 index 0000000000..dcb2dcf367 --- /dev/null +++ b/changelog/1094.feature.0.rst @@ -0,0 +1 @@ +Add :attr:`TeamMember.role`. diff --git a/changelog/1094.feature.1.rst b/changelog/1094.feature.1.rst new file mode 100644 index 0000000000..1b7eb2cd33 --- /dev/null +++ b/changelog/1094.feature.1.rst @@ -0,0 +1 @@ +|commands| Update :meth:`Bot.is_owner ` to take team member roles into account. diff --git a/disnake/enums.py b/disnake/enums.py index cb603c5425..912cb36183 100644 --- a/disnake/enums.py +++ b/disnake/enums.py @@ -35,6 +35,7 @@ "ActivityType", "NotificationLevel", "TeamMembershipState", + "TeamMemberRole", "WebhookType", "ExpireBehaviour", "ExpireBehavior", @@ -551,6 +552,15 @@ class TeamMembershipState(Enum): accepted = 2 +class TeamMemberRole(Enum): + admin = "admin" + developer = "developer" + read_only = "read_only" + + def __str__(self) -> str: + return self.name + + class WebhookType(Enum): incoming = 1 channel_follower = 2 diff --git a/disnake/ext/commands/bot.py b/disnake/ext/commands/bot.py index 5c3ba59eac..825f96e6ae 100644 --- a/disnake/ext/commands/bot.py +++ b/disnake/ext/commands/bot.py @@ -184,7 +184,7 @@ class Bot(BotBase, InteractionBotBase, disnake.Client): owner_ids: Optional[Collection[:class:`int`]] The IDs of the users that own the bot. This is similar to :attr:`owner_id`. If this is not set and the application is team based, then it is - fetched automatically using :meth:`~.Bot.application_info`. + fetched automatically using :meth:`~.Bot.application_info` (taking team roles into account). For performance reasons it is recommended to use a :class:`set` for the collection. You cannot set both ``owner_id`` and ``owner_ids``. @@ -403,7 +403,7 @@ class InteractionBot(InteractionBotBase, disnake.Client): owner_ids: Optional[Collection[:class:`int`]] The IDs of the users that own the bot. This is similar to :attr:`owner_id`. If this is not set and the application is team based, then it is - fetched automatically using :meth:`~.Bot.application_info`. + fetched automatically using :meth:`~.Bot.application_info` (taking team roles into account). For performance reasons it is recommended to use a :class:`set` for the collection. You cannot set both ``owner_id`` and ``owner_ids``. diff --git a/disnake/ext/commands/common_bot_base.py b/disnake/ext/commands/common_bot_base.py index f0d8fd5566..737736c170 100644 --- a/disnake/ext/commands/common_bot_base.py +++ b/disnake/ext/commands/common_bot_base.py @@ -81,8 +81,13 @@ async def _fill_owners(self) -> None: app: disnake.AppInfo = await self.application_info() # type: ignore if app.team: - self.owners = set(app.team.members) - self.owner_ids = {m.id for m in app.team.members} + self.owners = owners = { + member + for member in app.team.members + # these roles can access the bot token, consider them bot owners + if member.role in (disnake.TeamMemberRole.admin, disnake.TeamMemberRole.developer) + } + self.owner_ids = {m.id for m in owners} else: self.owner = app.owner self.owner_id = app.owner.id @@ -130,6 +135,10 @@ async def is_owner(self, user: Union[disnake.User, disnake.Member]) -> bool: The function also checks if the application is team-owned if :attr:`owner_ids` is not set. + .. versionchanged:: 2.10 + Also takes team roles into account; only team members with the :attr:`~disnake.TeamMemberRole.admin` + or :attr:`~disnake.TeamMemberRole.developer` roles are considered bot owners. + Parameters ---------- user: :class:`.abc.User` diff --git a/disnake/team.py b/disnake/team.py index dd0ee48d76..a1f126304e 100644 --- a/disnake/team.py +++ b/disnake/team.py @@ -7,7 +7,7 @@ from . import utils from .asset import Asset -from .enums import TeamMembershipState, try_enum +from .enums import TeamMemberRole, TeamMembershipState, try_enum from .user import BaseUser if TYPE_CHECKING: @@ -21,7 +21,8 @@ class Team: - """Represents an application team for a bot provided by Discord. + """Represents an application team. + Teams are groups of users who share access to an application's configuration. Attributes ---------- @@ -30,7 +31,7 @@ class Team: name: :class:`str` The team name. owner_id: :class:`int` - The team's owner ID. + The team owner's ID. members: List[:class:`TeamMember`] A list of the members in the team. @@ -44,7 +45,7 @@ def __init__(self, state: ConnectionState, data: TeamPayload) -> None: self.id: int = int(data["id"]) self.name: str = data["name"] - self._icon: Optional[str] = data["icon"] + self._icon: Optional[str] = data.get("icon") self.owner_id: Optional[int] = utils._get_as_snowflake(data, "owner_user_id") self.members: List[TeamMember] = [ TeamMember(self, self._state, member) for member in data["members"] @@ -113,29 +114,27 @@ class TeamMember(BaseUser): See the `help article `__ for details. global_name: Optional[:class:`str`] - The team members's global display name, if set. + The team member's global display name, if set. This takes precedence over :attr:`.name` when shown. .. versionadded:: 2.9 - avatar: Optional[:class:`str`] - The avatar hash the team member has. Could be None. - bot: :class:`bool` - Specifies if the user is a bot account. team: :class:`Team` The team that the member is from. membership_state: :class:`TeamMembershipState` - The membership state of the member (e.g. invited or accepted) + The membership state of the member (e.g. invited or accepted). + role: :class:`TeamMemberRole` + The role of the team member in the team. """ - __slots__ = ("team", "membership_state", "permissions") + __slots__ = ("team", "membership_state", "role") def __init__(self, team: Team, state: ConnectionState, data: TeamMemberPayload) -> None: self.team: Team = team self.membership_state: TeamMembershipState = try_enum( TeamMembershipState, data["membership_state"] ) - self.permissions: List[str] = data["permissions"] + self.role: TeamMemberRole = try_enum(TeamMemberRole, data.get("role")) super().__init__(state=state, data=data["user"]) def __repr__(self) -> str: diff --git a/disnake/types/team.py b/disnake/types/team.py index 5662365e03..0829c18b5c 100644 --- a/disnake/types/team.py +++ b/disnake/types/team.py @@ -8,13 +8,14 @@ from .user import PartialUser TeamMembershipState = Literal[1, 2] +TeamMemberRole = Literal["admin", "developer", "read_only"] class TeamMember(TypedDict): - user: PartialUser membership_state: TeamMembershipState - permissions: List[str] team_id: Snowflake + user: PartialUser + role: TeamMemberRole class Team(TypedDict): diff --git a/docs/api/app_info.rst b/docs/api/app_info.rst index eed2a74143..7aa6c4148f 100644 --- a/docs/api/app_info.rst +++ b/docs/api/app_info.rst @@ -49,6 +49,7 @@ TeamMember .. autoclass:: TeamMember() :members: + :inherited-members: Data Classes ------------ @@ -89,6 +90,27 @@ TeamMembershipState Represents a member currently in the team. +TeamMemberRole +~~~~~~~~~~~~~~ + +.. class:: TeamMemberRole + + Represents the role of a team member retrieved through :func:`Client.application_info`. + + .. versionadded:: 2.10 + + .. attribute:: admin + + Admins have the most permissions. An admin can only take destructive actions on the team or team-owned apps if they are the team owner. + + .. attribute:: developer + + Developers can access information about a team and team-owned applications, and take limited actions on them, like configuring interaction endpoints or resetting the bot token. + + .. attribute:: read_only + + Read-only members can access information about a team and team-owned applications. + ApplicationRoleConnectionMetadataType ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~