Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Member): add flags #918

Merged
merged 19 commits into from Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/918.feature.rst
@@ -0,0 +1 @@
Add :attr:`~Member.flags` property to :class:`Member`.
103 changes: 103 additions & 0 deletions disnake/flags.py
Expand Up @@ -39,6 +39,7 @@
"ApplicationFlags",
"ChannelFlags",
"AutoModKeywordPresets",
"MemberFlags",
)

BF = TypeVar("BF", bound="BaseFlags")
Expand Down Expand Up @@ -2134,3 +2135,105 @@ def slurs(self):
(contains insults or words that may be considered hate speech).
"""
return 1 << 3


class MemberFlags(BaseFlags):
"""Wraps up Discord Member flags.

.. container:: operations

.. describe:: x == y

Victorsitou marked this conversation as resolved.
Show resolved Hide resolved
Checks if two MemberFlags instances are equal.
.. describe:: x != y

Checks if two MemberFlags instances are not equal.
.. describe:: x <= y

Checks if an MemberFlags instance is a subset of another MemberFlags instance.
.. describe:: x >= y

Checks if an MemberFlags instance is a superset of another MemberFlags instance.
.. describe:: x < y

Checks if an MemberFlags instance is a strict subset of another MemberFlags instance.
.. describe:: x > y

Checks if an MemberFlags instance is a strict superset of another MemberFlags instance.
.. describe:: x | y, x |= y

Returns a new MemberFlags instance with all enabled flags from both x and y.
(Using ``|=`` will update in place).
.. describe:: x & y, x &= y

Returns a new MemberFlags instance with only flags enabled on both x and y.
(Using ``&=`` will update in place).
.. describe:: x ^ y, x ^= y

Returns a new MemberFlags instance with only flags enabled on one of x or y, but not both.
(Using ``^=`` will update in place).
.. describe:: ~x

Returns a new MemberFlags instance with all flags from x inverted.
.. describe:: hash(x)

Returns 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.

Additionally supported are a few operations on class attributes.

.. describe:: MemberFlags.y | MemberFlags.z, MemberFlags(y=True) | MemberFlags.z

Returns a MemberFlags instance with all provided flags enabled.

.. describe:: ~MemberFlags.y

Returns a MemberFlags instance with all flags except ``y`` inverted from their default value.

.. versionadded:: 2.8

Attributes
----------
value: :class:`int`
The raw value. You should query flags via the properties
rather than using this raw value.
"""

__slots__ = ()

if TYPE_CHECKING:

@_generated
def __init__(
self,
*,
bypasses_verification: bool = ...,
completed_onboarding: bool = ...,
did_rejoin: bool = ...,
started_onboarding: bool = ...,
) -> None:
...

@flag_value
def did_rejoin(self):
""":class:`bool`: Returns ``True`` if the member has left and rejoined the 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 is able to bypass 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
41 changes: 41 additions & 0 deletions disnake/member.py
Expand Up @@ -28,6 +28,7 @@
from .asset import Asset
from .colour import Colour
from .enums import Status, try_enum
from .flags import MemberFlags
from .object import Object
from .permissions import Permissions
from .user import BaseUser, User, _UserTag
Expand Down Expand Up @@ -271,6 +272,7 @@ class Member(disnake.abc.Messageable, _UserTag):
"_state",
"_avatar",
"_communication_disabled_until",
"_flags",
)

if TYPE_CHECKING:
Expand Down Expand Up @@ -336,6 +338,7 @@ def __init__(
self._avatar: Optional[str] = data.get("avatar")
timeout_datetime = utils.parse_time(data.get("communication_disabled_until"))
self._communication_disabled_until: Optional[datetime.datetime] = timeout_datetime
self._flags: int = data.get("flags", 0)

def __str__(self) -> str:
return str(self._user)
Expand Down Expand Up @@ -371,6 +374,7 @@ def _update_from_message(self, data: MemberPayload) -> None:
self._roles = utils.SnowflakeList(map(int, data["roles"]))
self.nick = data.get("nick", None)
self.pending = data.get("pending", False)
self._flags = data.get("flags", 0)

@classmethod
def _try_upgrade(
Expand Down Expand Up @@ -399,6 +403,7 @@ def _copy(cls, member: Member) -> Self:
self._state = member._state
self._avatar = member._avatar
self._communication_disabled_until = member.current_timeout
self._flags = member._flags

# Reference will not be copied unless necessary by PRESENCE_UPDATE
# See below
Expand Down Expand Up @@ -427,6 +432,7 @@ def _update(self, data: GuildMemberUpdateEvent) -> None:
self._avatar = data.get("avatar")
timeout_datetime = utils.parse_time(data.get("communication_disabled_until"))
self._communication_disabled_until = timeout_datetime
self._flags = data.get("flags", 0)

def _presence_update(
self, data: PresenceData, user: UserPayload
Expand Down Expand Up @@ -699,6 +705,14 @@ def current_timeout(self) -> Optional[datetime.datetime]:

return self._communication_disabled_until

@property
def flags(self) -> MemberFlags:
""":class:`MemberFlags`: Returns the member's flags.

.. versionadded:: 2.8
"""
return MemberFlags._from_value(self._flags)

@overload
async def ban(
self,
Expand Down Expand Up @@ -759,6 +773,8 @@ async def edit(
roles: Sequence[disnake.abc.Snowflake] = MISSING,
voice_channel: Optional[VocalGuildChannel] = MISSING,
timeout: Optional[Union[float, datetime.timedelta, datetime.datetime]] = MISSING,
flags: MemberFlags = MISSING,
bypasses_verification: bool = MISSING,
reason: Optional[str] = None,
) -> Optional[Member]:
"""|coro|
Expand All @@ -782,6 +798,10 @@ async def edit(
+------------------------------+-------------------------------------+
| timeout | :attr:`Permissions.moderate_members`|
+------------------------------+-------------------------------------+
| flags | :attr:`Permissions.moderate_members`|
+------------------------------+-------------------------------------+
| bypasses_verification | :attr:`Permissions.moderate_members`|
+------------------------------+-------------------------------------+

All parameters are optional.

Expand Down Expand Up @@ -816,6 +836,19 @@ async def edit(

.. versionadded:: 2.3

flags: :class:`MemberFlags`
The member's new flags. To know what flags are editable,
see :ddocs:`the documentation <resources/guild#guild-member-object-guild-member-flags>`.

If parameter ``verified`` is provided, that will override the setting of :attr:`MemberFlags.bypasses_verification`.
Victorsitou marked this conversation as resolved.
Show resolved Hide resolved

.. versionadded:: 2.8

bypasses_verification: :class:`bool`
Whether the member bypasses guild verification requirements.

.. versionadded:: 2.8

reason: Optional[:class:`str`]
The reason for editing this member. Shows up on the audit log.

Expand Down Expand Up @@ -889,6 +922,14 @@ async def edit(
else:
payload["communication_disabled_until"] = None

if bypasses_verification is not MISSING:
# create base flags if flags are provided, otherwise use the internal flags.
flags = MemberFlags._from_value(self._flags if flags is MISSING else flags.value)
flags.bypasses_verification = bypasses_verification

if flags is not MISSING:
payload["flags"] = flags.value

if payload:
data = await http.edit_member(guild_id, self.id, reason=reason, **payload)
return Member(data=data, guild=self.guild, state=self._state)
Expand Down
3 changes: 2 additions & 1 deletion disnake/permissions.py
Expand Up @@ -916,7 +916,8 @@ def start_embedded_activities(self) -> int:

@flag_value
def moderate_members(self) -> int:
""":class:`bool`: Returns ``True`` if a user can perform limited moderation actions (timeout).
""":class:`bool`: Returns ``True`` if a user can perform limited moderation actions,
such as timeout or edit members' flags.
Victorsitou marked this conversation as resolved.
Show resolved Hide resolved

.. versionadded:: 2.3
"""
Expand Down
1 change: 1 addition & 0 deletions disnake/types/gateway.py
Expand Up @@ -436,6 +436,7 @@ class GuildMemberUpdateEvent(TypedDict):
mute: NotRequired[bool]
pending: NotRequired[bool]
communication_disabled_until: NotRequired[Optional[str]]
flags: int


# https://discord.com/developers/docs/topics/gateway-events#guild-emojis-update
Expand Down
1 change: 1 addition & 0 deletions disnake/types/member.py
Expand Up @@ -19,6 +19,7 @@ class BaseMember(TypedDict):
pending: NotRequired[bool]
permissions: NotRequired[str]
communication_disabled_until: NotRequired[Optional[str]]
flags: int


class Member(BaseMember, total=False):
Expand Down
8 changes: 8 additions & 0 deletions docs/api.rst
Expand Up @@ -6092,6 +6092,14 @@ PublicUserFlags
.. autoclass:: PublicUserFlags()
:members:

MemberFlags
Victorsitou marked this conversation as resolved.
Show resolved Hide resolved
~~~~~~~~~~~

.. attributetable:: MemberFlags

.. autoclass:: MemberFlags()
:members:

.. _discord_ui_kit:

Bot UI Kit
Expand Down
1 change: 1 addition & 0 deletions tests/interactions/test_base.py
Expand Up @@ -141,6 +141,7 @@ def test_init_member(self, state):
"joined_at": "2022-09-02T22:00:55.069000+00:00",
"deaf": False,
"mute": False,
"flags": 0,
}

user_payload: UserPayload = {
Expand Down