diff --git a/discord/flags.py b/discord/flags.py index 7baca22c3..bb8ea9a29 100644 --- a/discord/flags.py +++ b/discord/flags.py @@ -56,6 +56,7 @@ __all__ = ( 'HubProgressFlags', 'OnboardingProgressFlags', 'AutoModPresets', + 'MemberFlags', ) BF = TypeVar('BF', bound='BaseFlags') @@ -2274,3 +2275,77 @@ class AutoModPresets(ArrayFlags): def slurs(self): """:class:`bool`: Whether to use the preset slurs filter.""" return 1 << 2 + + +@fill_with_flags() +class MemberFlags(BaseFlags): + r"""Wraps up the Discord Guild Member flags + + .. versionadded:: 2.0 + + .. container:: operations + + .. describe:: x == y + + Checks if two MemberFlags are equal. + + .. describe:: x != y + + Checks if two MemberFlags are not equal. + + .. describe:: x | y, x |= y + + Returns a MemberFlags instance with all enabled flags from + both x and y. + + .. describe:: x & y, x &= y + + Returns a MemberFlags instance with only flags enabled on + both x and y. + + .. describe:: x ^ y, x ^= y + + Returns a MemberFlags instance with only flags enabled on + only one of x or y, not on both. + + .. describe:: ~x + + Returns a MemberFlags instance with all flags inverted from x. + + .. describe:: hash(x) + + Return the flag's hash. + + .. describe:: iter(x) + + Returns an iterator of ``(name, value)`` pairs. This allows it + to be, for example, constructed as a dict or a list of pairs. + Note that aliases are not shown. + + + Attributes + ----------- + value: :class:`int` + The raw value. You should query flags via the properties + rather than using this raw value. + """ + + @flag_value + def did_rejoin(self): + """:class:`bool`: Returns ``True`` if the member left and rejoined the :attr:`~discord.Member.guild`.""" + return 1 << 0 + + @flag_value + def completed_onboarding(self): + """:class:`bool`: Returns ``True`` if the member has completed onboarding.""" + return 1 << 1 + + @flag_value + def bypasses_verification(self): + """:class:`bool`: Returns ``True`` if the member can bypass the guild verification requirements.""" + return 1 << 2 + + @flag_value + def started_onboarding(self): + """:class:`bool`: Returns ``True`` if the member has started onboarding.""" + return 1 << 3 diff --git a/discord/member.py b/discord/member.py index aa8ef5a8b..3850f1944 100644 --- a/discord/member.py +++ b/discord/member.py @@ -41,6 +41,7 @@ from .enums import RelationshipAction, Status, try_enum from .errors import ClientException from .colour import Colour from .object import Object +from .flags import MemberFlags __all__ = ( 'VoiceState', @@ -275,7 +276,7 @@ class Member(discord.abc.Messageable, discord.abc.Connectable, _UserTag): '_user', '_state', '_avatar', - '_communication_disabled_until', + '_flags', ) if TYPE_CHECKING: @@ -306,6 +307,7 @@ class Member(discord.abc.Messageable, discord.abc.Connectable, _UserTag): self.nick: Optional[str] = data.get('nick', None) self.pending: bool = data.get('pending', False) self._avatar: Optional[str] = data.get('avatar') + self._flags: int = data.get('flags', 0) self.timed_out_until: Optional[datetime.datetime] = utils.parse_time(data.get('communication_disabled_until')) def __str__(self) -> str: @@ -339,6 +341,7 @@ class Member(discord.abc.Messageable, discord.abc.Connectable, _UserTag): self.nick = data.get('nick', None) self.pending = data.get('pending', False) self.timed_out_until = utils.parse_time(data.get('communication_disabled_until')) + self._flags = data.get('flags', 0) @classmethod def _try_upgrade(cls, *, data: UserWithMemberPayload, guild: Guild, state: ConnectionState) -> Union[User, Self]: @@ -363,6 +366,7 @@ class Member(discord.abc.Messageable, discord.abc.Connectable, _UserTag): self.nick = member.nick self.pending = member.pending self.timed_out_until = member.timed_out_until + self._flags = member._flags self._state = member._state self._avatar = member._avatar @@ -390,6 +394,7 @@ class Member(discord.abc.Messageable, discord.abc.Connectable, _UserTag): self.timed_out_until = utils.parse_time(data.get('communication_disabled_until')) self._roles = utils.SnowflakeList(map(int, data['roles'])) self._avatar = data.get('avatar') + self._flags = data.get('flags', 0) attrs = {'joined_at', 'premium_since', '_roles', '_avatar', 'timed_out_until', 'nick', 'pending'} @@ -649,6 +654,14 @@ class Member(discord.abc.Messageable, discord.abc.Connectable, _UserTag): """Optional[:class:`VoiceState`]: Returns the member's current voice state.""" return self.guild._voice_state_for(self._user.id) + @property + def flags(self) -> MemberFlags: + """:class:`MemberFlags`: Returns the member's flags. + + .. versionadded:: 2.0 + """ + return MemberFlags._from_value(self._flags) + async def ban( self, *, @@ -694,6 +707,7 @@ class Member(discord.abc.Messageable, discord.abc.Connectable, _UserTag): avatar: Optional[bytes] = MISSING, banner: Optional[bytes] = MISSING, bio: Optional[str] = MISSING, + bypass_verification: bool = MISSING, reason: Optional[str] = None, ) -> Optional[Member]: """|coro| @@ -702,21 +716,23 @@ class Member(discord.abc.Messageable, discord.abc.Connectable, _UserTag): Depending on the parameter passed, this requires different permissions listed below: - +-----------------+--------------------------------------+ - | Parameter | Permission | - +-----------------+--------------------------------------+ - | nick | :attr:`Permissions.manage_nicknames` | - +-----------------+--------------------------------------+ - | mute | :attr:`Permissions.mute_members` | - +-----------------+--------------------------------------+ - | deafen | :attr:`Permissions.deafen_members` | - +-----------------+--------------------------------------+ - | roles | :attr:`Permissions.manage_roles` | - +-----------------+--------------------------------------+ - | voice_channel | :attr:`Permissions.move_members` | - +-----------------+--------------------------------------+ - | timed_out_until | :attr:`Permissions.moderate_members` | - +-----------------+--------------------------------------+ + +---------------------+--------------------------------------+ + | Parameter | Permission | + +---------------------+--------------------------------------+ + | nick | :attr:`Permissions.manage_nicknames` | + +---------------------+--------------------------------------+ + | mute | :attr:`Permissions.mute_members` | + +---------------------+--------------------------------------+ + | deafen | :attr:`Permissions.deafen_members` | + +---------------------+--------------------------------------+ + | roles | :attr:`Permissions.manage_roles` | + +---------------------+--------------------------------------+ + | voice_channel | :attr:`Permissions.move_members` | + +---------------------+--------------------------------------+ + | timed_out_until | :attr:`Permissions.moderate_members` | + +---------------------+--------------------------------------+ + | bypass_verification | :attr:`Permissions.manage_guild` | + +---------------------+--------------------------------------+ All parameters are optional. @@ -769,6 +785,10 @@ class Member(discord.abc.Messageable, discord.abc.Connectable, _UserTag): The member's new guild "about me". Pass ``None`` to remove the bio. You can only change your own guild bio. + .. versionadded:: 2.0 + bypass_verification: :class:`bool` + Indicates if the member should be allowed to bypass the guild verification requirements. + .. versionadded:: 2.0 reason: Optional[:class:`str`] The reason for editing this member. Shows up on the audit log. @@ -850,6 +870,11 @@ class Member(discord.abc.Messageable, discord.abc.Connectable, _UserTag): ) payload['communication_disabled_until'] = timed_out_until.isoformat() + if bypass_verification is not MISSING: + flags = MemberFlags._from_value(self._flags) + flags.bypasses_verification = bypass_verification + payload['flags'] = flags.value + if payload: data = await http.edit_member(guild_id, self.id, reason=reason, **payload) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 876fdb5cc..b0c0b0138 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -101,7 +101,7 @@ class ScheduledEvent(Hashable): creator_id: Optional[:class:`int`] The ID of the user that created the scheduled event. - .. versionadded:: 2.2 + .. versionadded:: 2.0 location: Optional[:class:`str`] The location of the scheduled event. """ diff --git a/discord/types/gateway.py b/discord/types/gateway.py index 097dc9787..74c2a8b3f 100644 --- a/discord/types/gateway.py +++ b/discord/types/gateway.py @@ -278,6 +278,7 @@ class GuildMemberUpdateEvent(TypedDict): user: PartialUser avatar: Optional[str] joined_at: Optional[str] + flags: int nick: NotRequired[str] premium_since: NotRequired[Optional[str]] deaf: NotRequired[bool] diff --git a/discord/types/member.py b/discord/types/member.py index 027c56187..ad9e49008 100644 --- a/discord/types/member.py +++ b/discord/types/member.py @@ -36,6 +36,7 @@ class PartialMember(TypedDict): joined_at: str deaf: bool mute: bool + flags: int class Member(PartialMember, total=False): diff --git a/discord/utils.py b/discord/utils.py index b4c756563..6ed38c8d1 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -750,7 +750,7 @@ async def maybe_coroutine(f: MaybeAwaitableFunc[P, T], *args: P.args, **kwargs: This is useful for functions that may or may not be coroutines. - .. versionadded:: 2.2 + .. versionadded:: 2.0 Parameters ----------- diff --git a/docs/api.rst b/docs/api.rst index 677d3d9d9..d5680007f 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -916,6 +916,7 @@ Members - pending - timeout - guild avatar + - flags Due to a Discord limitation, this event is not dispatched when a member's timeout expires. @@ -7416,6 +7417,11 @@ Flags .. autoclass:: HubProgressFlags() :members: +.. attributetable:: MemberFlags + +.. autoclass:: MemberFlags() + :members: + .. attributetable:: MessageFlags .. autoclass:: MessageFlags()