mirror of
https://github.com/stoatchat/python-client-sdk.git
synced 2026-07-01 20:44:04 -04:00
228 lines
7.9 KiB
Python
Executable File
228 lines
7.9 KiB
Python
Executable File
from __future__ import annotations
|
|
|
|
import datetime
|
|
from typing import TYPE_CHECKING, Any, Optional
|
|
|
|
|
|
from .utils import _Missing, Missing
|
|
|
|
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")
|
|
joined_at = data["joined_at"]
|
|
|
|
if isinstance(joined_at, int):
|
|
self.joined_at: datetime.datetime = datetime.datetime.fromtimestamp(joined_at / 1000)
|
|
else:
|
|
self.joined_at: datetime.datetime = datetime.datetime.strptime(joined_at, "%Y-%m-%dT%H:%M:%S.%f%z")
|
|
|
|
self.current_timeout: datetime.datetime | None
|
|
|
|
if current_timeout := data.get("timeout"):
|
|
self.current_timeout = datetime.datetime.strptime(current_timeout, "%Y-%m-%dT%H:%M:%S.%f%z")
|
|
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 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):
|
|
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)
|
|
|
|
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:
|
|
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 roles is not Missing:
|
|
data["roles"] = roles
|
|
|
|
if avatar is None:
|
|
remove.append("Avatar")
|
|
elif avatar is not Missing:
|
|
# pyright cant understand custom singletons - it doesnt know this will never be an instance of _Missing here because Missing is the only instance
|
|
assert not isinstance(avatar, _Missing)
|
|
|
|
data["avatar"] = (await self.state.http.upload_file(avatar, "avatars"))["id"]
|
|
|
|
if timeout is None:
|
|
remove.append("Timeout")
|
|
elif timeout is not Missing:
|
|
assert 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.utcnow() + 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()])
|