Source code for discord_http.member

from datetime import datetime, timedelta
from typing import Union, TYPE_CHECKING, Optional

from . import utils
from .asset import Asset
from .embeds import Embed
from .file import File
from .flags import Permissions, PublicFlags, GuildMemberFlags
from .guild import Guild, PartialGuild
from .mentions import AllowedMentions
from .object import PartialBase, Snowflake
from .response import ResponseType
from .role import PartialRole, Role
from .user import User, PartialUser
from .view import View


MISSING = utils.MISSING

if TYPE_CHECKING:
    from .gateway.object import Presence
    from .types.guilds import (
        ThreadMember as ThreadMemberPayload
    )
    from .http import DiscordAPI
    from .message import Message
    from .channel import DMChannel, PartialChannel, Thread

__all__ = (
    "PartialMember",
    "Member",
    "ThreadMember",
)


[docs] class PartialMember(PartialBase): def __init__( self, *, state: "DiscordAPI", id: int, guild_id: int, ): super().__init__(id=int(id)) self._state = state self._user = PartialUser(state=state, id=self.id) self.guild_id: int = int(guild_id) self.presence: "Presence | None" = None def __repr__(self) -> str: return f"<PartialMember id={self.id} guild_id={self.guild_id}>" def _update_presence(self, obj: "Presence | None") -> None: self.presence = obj @property def guild(self) -> Guild | PartialGuild: """ `PartialGuild | Guild`: The guild of the member If you are using gateway cache, it can return full object too """ cache = self._state.cache.get_guild(self.guild_id) if cache: return cache return PartialGuild(state=self._state, id=self.guild_id) @property def default_avatar(self) -> Asset: """ `Asset`: Alias for `User.default_avatar` """ return self._user.default_avatar
[docs] async def fetch(self) -> "Member": """ `Fetch`: Fetches the member from the API """ r = await self._state.query( "GET", f"/guilds/{self.guild_id}/members/{self.id}" ) return Member( state=self._state, guild=self.guild, data=r.response )
[docs] async def send( self, content: Optional[str] = MISSING, *, channel_id: Optional[int] = MISSING, embed: Optional[Embed] = MISSING, embeds: Optional[list[Embed]] = MISSING, file: Optional[File] = MISSING, files: Optional[list[File]] = MISSING, view: Optional[View] = MISSING, tts: Optional[bool] = False, type: Union[ResponseType, int] = 4, allowed_mentions: Optional[AllowedMentions] = MISSING, delete_after: Optional[float] = None ) -> "Message": """ Send a message to the user Parameters ---------- content: `Optional[str]` Content of the message channel_id: `Optional[int]` Channel ID of the user, leave empty to create a DM embed: `Optional[Embed]` Embed of the message embeds: `Optional[list[Embed]]` Embeds of the message file: `Optional[File]` File of the message files: `Optional[Union[list[File], File]]` Files of the message view: `Optional[View]` Components to add to the message tts: `Optional[bool]` Whether the message should be sent as TTS type: `Optional[ResponseType]` Type of the message allowed_mentions: `Optional[AllowedMentions]` Allowed mentions of the message delete_after: `Optional[float]` How long to wait before deleting the message Returns ------- `Message` The message sent """ return await self._user.send( content, channel_id=channel_id, embed=embed, embeds=embeds, file=file, files=files, view=view, tts=tts, type=type, allowed_mentions=allowed_mentions, delete_after=delete_after )
[docs] async def create_dm(self) -> "DMChannel": """ `DMChannel`: Create a DM channel with the user """ return await self._user.create_dm()
[docs] async def ban( self, *, reason: Optional[str] = None, delete_message_days: Optional[int] = 0, delete_message_seconds: Optional[int] = 0, ) -> None: """ Ban the user Parameters ---------- reason: `Optional[str]` The reason for banning the user delete_message_days: `Optional[int]` How many days of messages to delete delete_message_seconds: `Optional[int]` How many seconds of messages to delete Raises ------ `ValueError` - If delete_message_days and delete_message_seconds are both specified - If delete_message_days is not between 0 and 7 - If delete_message_seconds is not between 0 and 604,800 """ await self.guild.ban( self.id, reason=reason, delete_message_days=delete_message_days, delete_message_seconds=delete_message_seconds )
[docs] async def unban( self, *, reason: Optional[str] = None ) -> None: """ Unban the user Parameters ---------- reason: `Optional[str]` The reason for unbanning the user """ await self.guild.unban(self.id, reason=reason)
[docs] async def kick( self, *, reason: Optional[str] = None ) -> None: """ Kick the user Parameters ---------- reason: `Optional[str]` The reason for kicking the user """ await self.guild.kick(self.id, reason=reason)
[docs] async def edit( self, *, nick: Optional[str] = MISSING, roles: Union[list[Union[PartialRole, int]], None] = MISSING, mute: Optional[bool] = MISSING, deaf: Optional[bool] = MISSING, communication_disabled_until: Union[timedelta, datetime, int, None] = MISSING, channel_id: Optional[int] = MISSING, reason: Optional[str] = None ) -> "Member": """ Edit the member Parameters ---------- nick: `Optional[str]` The new nickname of the member roles: `Optional[list[Union[PartialRole, int]]]` Roles to make the member have mute: `Optional[bool]` Whether to mute the member deaf: `Optional[bool]` Whether to deafen the member communication_disabled_until: `Optional[Union[timedelta, datetime, int]]` How long to disable communication for (timeout) channel_id: `Optional[int]` The channel ID to move the member to reason: `Optional[str]` The reason for editing the member Returns ------- `Member` The edited member Raises ------ `TypeError` - If communication_disabled_until is not timedelta, datetime, or int """ payload = {} if nick is not MISSING: payload["nick"] = nick if isinstance(roles, list) and roles is not MISSING: payload["roles"] = [str(int(role)) for role in roles] if mute is not MISSING: payload["mute"] = mute if deaf is not MISSING: payload["deaf"] = deaf if channel_id is not MISSING: payload["channel_id"] = channel_id if communication_disabled_until is not MISSING: if communication_disabled_until is None: payload["communication_disabled_until"] = None else: _parse_ts = utils.add_to_datetime( communication_disabled_until ) payload["communication_disabled_until"] = _parse_ts.isoformat() r = await self._state.query( "PATCH", f"/guilds/{self.guild_id}/members/{self.id}", json=payload, reason=reason ) return Member( state=self._state, guild=self.guild, data=r.response )
[docs] async def add_roles( self, *roles: Union[PartialRole, int], reason: Optional[str] = None ) -> None: """ Add roles to someone Parameters ---------- *roles: `Union[PartialRole, int]` Roles to add to the member reason: `Optional[str]` The reason for adding the roles Parameters ---------- reason: `Optional[str]` The reason for adding the roles """ for role in roles: if isinstance(role, PartialRole): role = role.id await self._state.query( "PUT", f"/guilds/{self.guild_id}/members/{self.id}/roles/{int(role)}", reason=reason )
[docs] async def remove_roles( self, *roles: Union[PartialRole, int], reason: Optional[str] = None ) -> None: """ Remove roles from someone Parameters ---------- reason: `Optional[str]` The reason for removing the roles """ for role in roles: if isinstance(role, PartialRole): role = role.id await self._state.query( "DELETE", f"/guilds/{self.guild_id}/members/{self.id}/roles/{int(role)}", reason=reason )
@property def mention(self) -> str: """ `str`: The mention of the member """ return f"<@!{self.id}>"
[docs] class Member(PartialMember): def __init__( self, *, state: "DiscordAPI", guild: Guild | PartialGuild, data: dict ): super().__init__( state=state, id=int(data["user"]["id"]), guild_id=guild.id, ) self._user = User(state=state, data=data["user"]) self.avatar: Optional[Asset] = None self.flags: GuildMemberFlags = GuildMemberFlags(data["flags"]) self.pending: bool = data.get("pending", False) self._raw_permissions: Optional[int] = utils.get_int(data, "permissions") self.nick: Optional[str] = data.get("nick", None) self.joined_at: datetime = utils.parse_time(data["joined_at"]) self.communication_disabled_until: datetime | None = None self.premium_since: datetime | None = None self._roles: list[PartialRole] = [ PartialRole(state=state, id=int(r), guild_id=self.guild.id) for r in data["roles"] ] self._from_data(data) def __repr__(self) -> str: return ( f"<Member id={self.id} name='{self.name}' " f"global_name='{self._user.global_name}'>" ) def __str__(self) -> str: return str(self._user) def _from_data(self, data: dict) -> None: has_avatar = data.get("avatar", None) if has_avatar: self.avatar = Asset._from_guild_avatar( self._state, self.guild.id, self.id, has_avatar ) if data.get("communication_disabled_until", None): self.communication_disabled_until = utils.parse_time( data["communication_disabled_until"] ) if data.get("premium_since", None): self.premium_since = utils.parse_time( data["premium_since"] ) @property def roles(self) -> list[Role | PartialRole]: """ `list[Role | PartialRole]`: Returns the roles of the member """ if self.guild.roles: # If there is a guild cache, we could potentially return full Role object g_roles = [r.id for r in self._roles] return [ g for g in self.guild.roles if g.id in g_roles ] return self._roles
[docs] def get_role( self, role: Union[Snowflake, int] ) -> Optional[PartialRole]: """ Get a role from the member Parameters ---------- role: `Union[Snowflake, int]` The role to get. Can either be a role object or the Role ID Returns ------- `Optional[PartialRole]` The role if found, else None """ return next(( r for r in self.roles if r.id == int(role) ), None)
[docs] def is_timed_out(self) -> bool: """ `bool`: Returns whether the member is timed out or not """ if self.communication_disabled_until is None: return False return utils.utcnow() < self.communication_disabled_until
@property def guild_permissions(self) -> Permissions: """ `Permissions`: Returns the guild permissions of the member. This is only available if you are using gateway with guild cache. """ if getattr(self.guild, "owner_id", None) == self.id: return Permissions.all() base = Permissions.none() for r in self.roles: g_role = self.guild.get_role(r.id) if isinstance(g_role, Role): base |= g_role.permissions if Permissions.administrator in base: return Permissions.all() if self.is_timed_out(): _timeout_perm = ( Permissions.view_channel | Permissions.read_message_history ) if Permissions.view_channel not in base: _timeout_perm &= ~Permissions.view_channel if Permissions.read_message_history not in base: _timeout_perm &= ~Permissions.read_message_history base = _timeout_perm return base @property def resolved_permissions(self) -> Permissions: """ `Permissions` Returns permissions from an interaction. Will always be `Permissions.none()` if used in `Member.fetch()` """ if self._raw_permissions is None: return Permissions(0) return Permissions(self._raw_permissions)
[docs] def has_permissions(self, *args: str) -> bool: """ Check if a member has a permission Will be False if used in `Member.fetch()` every time Parameters ---------- *args: `str` Permissions to check Returns ------- `bool` Whether the member has the permission(s) """ if ( Permissions.from_names("administrator") in self.resolved_permissions ): return True return ( Permissions.from_names(*args) in self.resolved_permissions )
@property def name(self) -> str: """ `str`: Returns the username of the member """ return self._user.name @property def bot(self) -> bool: """ `bool`: Returns whether the member is a bot """ return self._user.bot @property def system(self) -> bool: """ `bool`: Returns whether the member is a system user """ return self._user.system @property def discriminator(self) -> Optional[str]: """ Gives the discriminator of the member if available Returns ------- `Optional[str]` Discriminator of a user who has yet to convert or a bot account. If the user has converted to the new username, this will return None """ return self._user.discriminator @property def public_flags(self) -> PublicFlags: """ `int`: Returns the public flags of the member """ return self._user.public_flags or PublicFlags(0) @property def banner(self) -> Optional[Asset]: """ `Optional[Asset]`: Returns the banner of the member if available """ return self._user.banner @property def avatar_decoration(self) -> Optional[Asset]: """ `Optional[Asset]`: Returns the avatar decoration of the member """ return self._user.avatar_decoration @property def global_name(self) -> Optional[str]: """ `Optional[str]`: Gives the global display name of a member if available """ return self._user.global_name @property def global_avatar(self) -> Optional[Asset]: """ `Optional[Asset]`: Shortcut for `User.avatar` """ return self._user.avatar @property def global_banner(self) -> Optional[Asset]: """ `Optional[Asset]`: Shortcut for `User.banner` """ return self._user.banner @property def display_name(self) -> str: """ `str`: Returns the display name of the member """ return self.nick or self.global_name or self.name @property def display_avatar(self) -> Optional[Asset]: """ `Optional[Asset]`: Returns the display avatar of the member """ return ( self.avatar or self.global_avatar or self.default_avatar ) @property def top_role(self) -> PartialRole | Role | None: """ `Optional[PartialRole | Role]`: Returns the top role of the member. Only usable if you are using gateway and caching """ if not isinstance(self.guild, Guild): return None return self.guild.get_member_top_role(self)
class PartialThreadMember(PartialMember): def __init__( self, *, state: "DiscordAPI", data: "ThreadMemberPayload", guild_id: int, ) -> None: super().__init__( state=state, id=int(data["user_id"]), guild_id=guild_id, ) self.thread_id: int = int(data["id"]) self.join_timestamp: datetime = utils.parse_time(data["join_timestamp"]) self.flags: int = data["flags"] @property def thread(self) -> "PartialChannel | Thread": """ `PartialChannel | Thread"`: The thread the member is in """ return ( self.guild.get_channel(self.thread_id) or self.guild.get_partial_channel(self.thread_id) )
[docs] class ThreadMember(Member): def __init__( self, *, state: "DiscordAPI", guild: "PartialGuild", data: dict ) -> None: super().__init__( state=state, guild=guild, data=data["member"] ) self.thread_id: int = int(data["id"]) self.join_timestamp: datetime = utils.parse_time(data["join_timestamp"]) self.flags: int = data["flags"] @property def thread(self) -> "PartialChannel | Thread": """ `PartialChannel | Thread"`: The thread the member is in """ return ( self.guild.get_channel(self.thread_id) or self.guild.get_partial_channel(self.thread_id) )