Files
2023-09-11 15:53:54 +01:00

247 lines
8.5 KiB
Python
Executable File

from __future__ import annotations
import datetime
from typing import TYPE_CHECKING, Any, Optional
from .utils import _Missing, Missing, parse_timestamp
from .asset import Asset
from .permissions import Permissions
from .permissions_calculator import calculate_permissions
from .user import User
from .file import File
if TYPE_CHECKING:
from .channel import Channel
from .server import Server
from .state import State
from .types import File as FilePayload
from .types import Member as MemberPayload
from .role import Role
__all__ = ("Member",)
def flattern_user(member: Member, user: User) -> None:
for attr in user.__flattern_attributes__:
setattr(member, attr, getattr(user, attr))
class Member(User):
"""Represents a member of a server, subclasses :class:`User`
Attributes
-----------
nickname: Optional[:class:`str`]
The nickname of the member if any
roles: list[:class:`Role`]
The roles of the member, ordered by the role's rank in decending order
server: :class:`Server`
The server the member belongs to
guild_avatar: Optional[:class:`Asset`]
The member's guild avatar if any
"""
__slots__ = ("state", "nickname", "roles", "server", "guild_avatar", "joined_at", "current_timeout")
def __init__(self, data: MemberPayload, server: Server, state: State):
user = state.get_user(data["_id"]["user"])
# due to not having a user payload and only a user object we have to manually add all the attributes instead of calling User.__init__
flattern_user(self, user)
user._members[server.id] = self
self.state: State = state
self.guild_avatar: Asset | None
if avatar := data.get("avatar"):
self.guild_avatar = Asset(avatar, state)
else:
self.guild_avatar = None
roles = [server.get_role(role_id) for role_id in data.get("roles", [])]
self.roles: list[Role] = sorted(roles, key=lambda role: role.rank, reverse=True)
self.server: Server = server
self.nickname: str | None = data.get("nickname")
self.joined_at: datetime.datetime = parse_timestamp(data["joined_at"])
self.current_timeout: datetime.datetime | None
if current_timeout := data.get("timeout"):
self.current_timeout = parse_timestamp(current_timeout)
else:
self.current_timeout = None
@property
def avatar(self) -> Optional[Asset]:
"""Optional[:class:`Asset`] The avatar the member is displaying, this includes guild avatars and masqueraded avatar"""
return self.masquerade_avatar or self.guild_avatar or self.original_avatar
@property
def name(self) -> str:
""":class:`str` The name the user is displaying, this includes (in order) their masqueraded name, display name and orginal name"""
return self.nickname or self.display_name or self.masquerade_name or self.original_name
@property
def mention(self) -> str:
""":class:`str`: Returns a string that allows you to mention the given member."""
return f"<@{self.id}>"
def _update(
self,
*,
nickname: Optional[str] = None,
avatar: Optional[FilePayload] = None,
roles: Optional[list[str]] = None,
timeout: Optional[str | int] = None
) -> None:
if nickname is not None:
self.nickname = nickname
if avatar is not None:
self.guild_avatar = Asset(avatar, self.state)
if roles is not None:
member_roles = [self.server.get_role(role_id) for role_id in roles]
self.roles = sorted(member_roles, key=lambda role: role.rank, reverse=True)
if timeout is not None:
self.current_timeout = parse_timestamp(timeout)
async def kick(self) -> None:
"""Kicks the member from the server"""
await self.state.http.kick_member(self.server.id, self.id)
async def ban(self, *, reason: Optional[str] = None) -> None:
"""Bans the member from the server
Parameters
-----------
reason: Optional[:class:`str`]
The reason for the ban
"""
await self.state.http.ban_member(self.server.id, self.id, reason)
async def unban(self) -> None:
"""Unbans the member from the server"""
await self.state.http.unban_member(self.server.id, self.id)
async def edit(
self,
*,
nickname: str | None | _Missing = Missing,
roles: list[Role] | None | _Missing = Missing,
avatar: File | None | _Missing = Missing,
timeout: datetime.timedelta | None | _Missing = Missing
) -> None:
"""Edits the member
Parameters
-----------
nickname: Union[:class:`str`, :class:`None`]
The new nickname, or :class:`None` to reset it
roles: Union[list[:class:`Role`], :class:`None`]
The new roles for the member, or :class:`None` to clear it
avatar: Union[:class:`File`, :class:`None`]
The new server avatar, or :class:`None` to reset it
timeout: Union[:class:`datetime.timedelta`, :class:`None`]
The new timeout length for the member, or :class:`None` to reset it
"""
remove: list[str] = []
data: dict[str, Any] = {}
if nickname is None:
remove.append("Nickname")
elif nickname is not Missing:
data["nickname"] = nickname
if roles is None:
remove.append("Roles")
elif not isinstance(roles, _Missing):
data["roles"] = [role.id for role in roles]
if avatar is None:
remove.append("Avatar")
elif not isinstance(avatar, _Missing):
data["avatar"] = (await self.state.http.upload_file(avatar, "avatars"))["id"]
if timeout is None:
remove.append("Timeout")
elif not isinstance(timeout, _Missing):
data["timeout"] = (datetime.datetime.now(datetime.timezone.utc) + timeout).isoformat()
await self.state.http.edit_member(self.server.id, self.id, remove, data)
async def timeout(self, length: datetime.timedelta) -> None:
"""Timeouts the member
Parameters
-----------
length: :class:`datetime.timedelta`
The length of the timeout
"""
ends_at = datetime.datetime.now(tz=datetime.timezone.utc) + length
await self.state.http.edit_member(self.server.id, self.id, None, {"timeout": ends_at.isoformat()})
def get_permissions(self) -> Permissions:
"""Gets the permissions for the member in the server
Returns
--------
:class:`Permissions`
The members permissions
"""
return calculate_permissions(self, self.server)
def get_channel_permissions(self, channel: Channel) -> Permissions:
"""Gets the permissions for the member in the server taking into account the channel as well
Parameters
-----------
channel: :class:`Channel`
The channel to calculate permissions with
Returns
--------
:class:`Permissions`
The members permissions
"""
return calculate_permissions(self, channel)
def has_permissions(self, **permissions: bool) -> bool:
"""Computes if the member has the specified permissions
Parameters
-----------
permissions: :class:`bool`
The permissions to check, this also accepted `False` if you need to check if the member does not have the permission
Returns
--------
:class:`bool`
Whether or not they have the permissions
"""
calculated_perms = self.get_permissions()
return all([getattr(calculated_perms, key, False) == value for key, value in permissions.items()])
def has_channel_permissions(self, channel: Channel, **permissions: bool) -> bool:
"""Computes if the member has the specified permissions, taking into account the channel as well
Parameters
-----------
channel: :class:`Channel`
The channel to calculate permissions with
permissions: :class:`bool`
The permissions to check, this also accepted `False` if you need to check if the member does not have the permission
Returns
--------
:class:`bool`
Whether or not they have the permissions
"""
calculated_perms = self.get_channel_permissions(channel)
return all([getattr(calculated_perms, key, False) == value for key, value in permissions.items()])