Source code for discord_http.member

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

from . import utils
from .asset import Asset
from .embeds import Embed
from .file import File
from .flag import Permissions, PublicFlags, GuildMemberFlags
from .guild import 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 .channel import DMChannel, PartialChannel
    from .http import DiscordAPI
    from .message import Message

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


[docs] class VoiceState: def __init__(self, *, state: "DiscordAPI", data: dict): self._state = state self.session_id: str = data["session_id"] self.guild: Optional[PartialGuild] = None self.channel: Optional[PartialChannel] = None self.user: PartialUser = PartialUser(state=state, id=int(data["user_id"])) self.member: Optional[Member] = None self.deaf: bool = data["deaf"] self.mute: bool = data["mute"] self.self_deaf: bool = data["self_deaf"] self.self_mute: bool = data["self_mute"] self.self_stream: bool = data.get("self_stream", False) self.self_video: bool = data["self_video"] self.suppress: bool = data["suppress"] self.request_to_speak_timestamp: Optional[datetime] = None self._from_data(data) def __repr__(self) -> str: return f"<VoiceState id={self.user} session_id='{self.session_id}'>" def _from_data(self, data: dict) -> None: if data.get("guild_id", None): self.guild = PartialGuild( state=self._state, id=int(data["guild_id"]) ) if data.get("channel_id", None): from .channel import PartialChannel self.channel = PartialChannel( state=self._state, id=int(data["channel_id"]) ) if data.get("member", None) and self.guild: self.member = Member( state=self._state, guild=self.guild, data=data["member"] ) if data.get("request_to_speak_timestamp", None): self.request_to_speak_timestamp = utils.parse_time( data["request_to_speak_timestamp"] )
[docs] async def edit( self, *, suppress: bool = MISSING, ) -> None: """ Updates the voice state of the member Parameters ---------- suppress: `bool` Whether to suppress the user """ data: dict[str, Any] = {} if suppress is not MISSING: data["suppress"] = bool(suppress) await self._state.query( "PATCH", f"/guilds/{self.guild.id}/voice-states/{int(self.user)}", json=data, res_method="text" )
[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) def __repr__(self) -> str: return f"<PartialMember id={self.id} guild_id={self.guild_id}>" @property def guild(self) -> PartialGuild: """ `PartialGuild`: The guild of the member """ return PartialGuild(state=self._state, id=self.guild_id)
[docs] async def fetch_voice_state(self) -> VoiceState: """ Fetches the voice state of the member Returns ------- `VoiceState` The voice state of the member Raises ------ `NotFound` - If the member is not in the guild - If the member is not in a voice channel """ r = await self._state.query( "GET", f"/guilds/{self.guild_id}/voice-states/{self.id}" ) return VoiceState(state=self._state, data=r.response)
[docs] async def edit_voice_state( self, channel: Snowflake, *, suppress: bool = MISSING, ) -> None: """ Updates another user's voice state in a stage channel Parameters ---------- channel: `Snowflake` The channel that the member is in (it must be the same channel as the current one) suppress: `bool` Whether to suppress the user """ data: dict[str, Any] = {"channel_id": str(int(channel))} if suppress is not MISSING: data["suppress"] = bool(suppress) await self._state.query( "PATCH", f"/guilds/{self.guild_id}/voice-states/{self.id}", json=data, res_method="text" )
[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, ) -> "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 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 )
[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 """ payload = {} if delete_message_days and delete_message_seconds: raise ValueError("Cannot specify both delete_message_days and delete_message_seconds") if delete_message_days: if delete_message_days not in range(0, 8): raise ValueError("delete_message_days must be between 0 and 7") payload["delete_message_seconds"] = int(timedelta(days=delete_message_days).total_seconds()) if delete_message_seconds: if delete_message_seconds not in range(0, 604801): raise ValueError("delete_message_seconds must be between 0 and 604,800") payload["delete_message_seconds"] = delete_message_seconds await self._state.query( "PUT", f"/guilds/{self.guild_id}/bans/{self.id}", reason=reason, json=payload )
[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._state.query( "DELETE", f"/guilds/{self.guild_id}/bans/{self.id}", reason=reason, res_method="text" )
[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._state.query( "DELETE", f"/guilds/{self.guild_id}/members/{self.id}", reason=reason, res_method="text" )
[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"] = [ role.id if isinstance(role, (PartialRole, Role)) else 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/{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/{role}", reason=reason )
@property def mention(self) -> str: """ `str`: The mention of the member """ return f"<@!{self.id}>"
[docs] class ThreadMember(PartialBase): def __init__(self, *, state: "DiscordAPI", data: dict): super().__init__(id=int(data["user_id"])) self._state = state self.flags: int = data["flags"] self.join_timestamp: datetime = utils.parse_time(data["join_timestamp"]) def __str__(self) -> str: return str(self.id) def __int__(self) -> int: return self.id
[docs] class Member(PartialMember): def __init__( self, *, state: "DiscordAPI", guild: PartialGuild, data: dict ): super().__init__( state=state, id=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.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.guild.id, self.id, has_avatar )
[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)
@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._user.avatar