from __future__ import annotations from typing import TYPE_CHECKING, Optional, cast from .asset import Asset from .category import Category from .channel import Channel, VoiceChannel from .invite import Invite from .permissions import Permissions from .role import Role from .utils import Ulid if TYPE_CHECKING: from .channel import TextChannel from .member import Member from .state import State from .types import Ban from .types import Category as CategoryPayload from .types import File as FilePayload from .types import Server as ServerPayload from .types import SystemMessagesConfig from .emoji import Emoji from .file import File __all__ = ("Server", "SystemMessages", "ServerBan") class SystemMessages: def __init__(self, data: SystemMessagesConfig, state: State): self.state = state self.user_joined_id = data.get("user_joined") self.user_left_id = data.get("user_left") self.user_kicked_id = data.get("user_kicked") self.user_banned_id = data.get("user_banned") @property def user_joined(self) -> Optional[TextChannel]: if not self.user_joined_id: return channel = self.state.get_channel(self.user_joined_id) assert isinstance(channel, TextChannel) return channel @property def user_left(self) -> Optional[TextChannel]: if not self.user_left_id: return channel = self.state.get_channel(self.user_left_id) assert isinstance(channel, TextChannel) return channel @property def user_kicked(self) -> Optional[TextChannel]: if not self.user_kicked_id: return channel = self.state.get_channel(self.user_kicked_id) assert isinstance(channel, TextChannel) return channel @property def user_banned(self) -> Optional[TextChannel]: if not self.user_banned_id: return channel = self.state.get_channel(self.user_banned_id) assert isinstance(channel, TextChannel) return channel class Server(Ulid): """Represents a server Attributes ----------- id: :class:`str` The id of the server name: :class:`str` The name of the server owner_id: :class:`str` The owner's id of the server description: Optional[:class:`str`] The servers description nsfw: :class:`bool` Whether the server is nsfw or not system_messages: :class:`SystemMessages` The system message config for the server icon: Optional[:class:`Asset`] The servers icon banner: Optional[:class:`Asset`] The servers banner default_permissions: :class:`Permissions` The permissions for the default role """ __slots__ = ("state", "id", "name", "owner_id", "default_permissions", "_members", "_roles", "_channels", "description", "icon", "banner", "nsfw", "system_messages", "_categories", "_emojis") def __init__(self, data: ServerPayload, state: State): self.state = state self.id = data["_id"] self.name = data["name"] self.owner_id = data["owner"] self.description = data.get("description") or None self.nsfw = data.get("nsfw", False) self.system_messages = SystemMessages(data.get("system_messages", cast("SystemMessagesConfig", {})), state) self._categories = {data["id"]: Category(data, state) for data in data.get("categories", [])} self.default_permissions = Permissions(data["default_permissions"]) if icon := data.get("icon"): self.icon = Asset(icon, state) else: self.icon = None if banner := data.get("banner"): self.banner = Asset(banner, state) else: self.banner = None self._members: dict[str, Member] = {} self._roles: dict[str, Role] = {role_id: Role(role, role_id, self, state) for role_id, role in data.get("roles", {}).items()} self._channels: dict[str, Channel] = {} # The api doesnt send us all the channels but sends us all the ids, this is because channels we dont have permissions to see are not sent # this causes get_channel to error so we have to first check ourself if its in the cache. for channel_id in data["channels"]: if channel := state.channels.get(channel_id): self._channels[channel_id] = channel self._emojis: dict[str, Emoji] = {} def _update(self, *, owner: Optional[str] = None, name: Optional[str] = None, description: Optional[str] = None, icon: Optional[FilePayload] = None, banner: Optional[FilePayload] = None, default_permissions: Optional[int] = None, nsfw: Optional[bool] = None, system_messages: Optional[SystemMessagesConfig] = None, categories: Optional[list[CategoryPayload]] = None, channels: Optional[list[str]] = None): if owner is not None: self.owner_id = owner if name is not None: self.name = name if description is not None: self.description = description or None if icon is not None: self.icon = Asset(icon, self.state) if banner is not None: self.banner = Asset(banner, self.state) if default_permissions is not None: self.default_permissions = Permissions(default_permissions) if nsfw is not None: self.nsfw = nsfw if system_messages is not None: self.system_messages = SystemMessages(system_messages, self.state) if categories is not None: self._categories = {data["id"]: Category(data, self.state) for data in categories} if channels is not None: self._channels = {channel_id: self.state.get_channel(channel_id) for channel_id in channels} @property def roles(self) -> list[Role]: """list[:class:`Role`] Gets all roles in the server in decending order""" return list(self._roles.values()) @property def members(self) -> list[Member]: """list[:class:`Member`] Gets all members in the server""" return list(self._members.values()) @property def channels(self) -> list[Channel]: """list[:class:`Member`] Gets all channels in the server""" return list(self._channels.values()) @property def categories(self) -> list[Category]: """list[:class:`Category`] Gets all categories in the server""" return list(self._categories.values()) @property def emojis(self) -> list[Emoji]: """list[:class:`Emoji`] Gets all emojis in the server""" return list(self._emojis.values()) def get_role(self, role_id: str) -> Role: """Gets a role from the cache Parameters ----------- id: :class:`str` The id of the role Returns -------- :class:`Role` The role """ return self._roles[role_id] def get_member(self, member_id: str) -> Member: """Gets a member from the cache Parameters ----------- id: :class:`str` The id of the member Returns -------- :class:`Member` The member """ try: return self._members[member_id] except KeyError: raise LookupError from None def get_channel(self, channel_id: str) -> Channel: """Gets a channel from the cache Parameters ----------- id: :class:`str` The id of the channel Returns -------- :class:`Channel` The channel """ try: return self._channels[channel_id] except KeyError: raise LookupError from None def get_category(self, category_id: str) -> Category: """Gets a category from the cache Parameters ----------- id: :class:`str` The id of the category Returns -------- :class:`Category` The category """ try: return self._categories[category_id] except KeyError: raise LookupError from None def get_emoji(self, emoji_id: str) -> Emoji: """Gets a emoji from the cache Parameters ----------- id: :class:`str` The id of the emoji Returns -------- :class:`Emoji` The emoji """ try: return self._emojis[emoji_id] except KeyError as e: raise LookupError from e @property def owner(self) -> Member: """:class:`Member` The owner of the server""" return self.get_member(self.owner_id) async def set_default_permissions(self, permissions: Permissions) -> None: """Sets the default server permissions. Parameters ----------- server_permissions: Optional[:class:`ServerPermissions`] The new default server permissions channel_permissions: Optional[:class:`ChannelPermissions`] the new default channel permissions """ await self.state.http.set_server_default_permissions(self.id, permissions.value) async def leave_server(self): """Leaves or deletes the server""" await self.state.http.delete_leave_server(self.id) async def delete_server(self): """Leaves or deletes a server, alias to :meth`Server.leave_server`""" await self.leave_server() async def create_text_channel(self, *, name: str, description: Optional[str] = None) -> TextChannel: """Creates a text channel in the server Parameters ----------- name: :class:`str` The name of the channel description: Optional[:class:`str`] The channel's description Returns -------- :class:`TextChannel` The text channel that was just created """ payload = await self.state.http.create_channel(self.id, "Text", name, description) channel = TextChannel(payload, self.state) self._channels[channel.id] = channel return channel async def create_voice_channel(self, *, name: str, description: Optional[str] = None) -> VoiceChannel: """Creates a voice channel in the server Parameters ----------- name: :class:`str` The name of the channel description: Optional[:class:`str`] The channel's description Returns -------- :class:`VoiceChannel` The voice channel that was just created """ payload = await self.state.http.create_channel(self.id, "Voice", name, description) channel = VoiceChannel(payload, self.state) self._channels[channel.id] = channel return channel async def fetch_invites(self) -> list[Invite]: """Fetches all invites in the server Returns -------- list[:class:`Invite`] """ invite_payloads = await self.state.http.fetch_server_invites(self.id) return [Invite._from_partial(payload["_id"], payload["server"], payload["creator"], payload["channel"], self.state) for payload in invite_payloads] async def fetch_member(self, member_id: str) -> Member: """Fetches a member from this server Parameters ----------- member_id: :class:`str` The id of the member you are fetching Returns -------- :class:`Member` The member with the matching id """ payload = await self.state.http.fetch_member(self.id, member_id) return Member(payload, self, self.state) async def fetch_bans(self) -> list[ServerBan]: """Fetches all invites in the server Returns -------- list[:class:`Invite`] """ payload = await self.state.http.fetch_bans(self.id) return [ServerBan(ban, self.state) for ban in payload["bans"]] async def create_role(self, name: str) -> Role: """Creates a role in the server Parameters ----------- name: :class:`str` The name of the role Returns -------- :class:`Role` The role that was just created """ payload = await self.state.http.create_role(self.id, name) return Role(payload, name, self, self.state) async def create_emoji(self, name: str, file: File, *, nsfw: bool = False): """Creates an emoji Parameters ----------- name: :class:`str` The name for the emoji file: :class:`File` The image for the emoji nsfw: :class:`bool` Whether or not the emoji is nsfw """ payload = await self.state.http.create_emoji(name, file, nsfw, {"type": "Server", "id": self.id}) return self.state.add_emoji(payload) class ServerBan: """Represents a server ban Attributes ----------- reason: Optional[:class:str`] The reason the user was banned server: :class:`Server` The server the user was banned in user_id: :class:`str` The id of the user who was banned """ __slots__ = ("reason", "server", "user_id", "state") def __init__(self, ban: Ban, state: State): self.reason = ban.get("reason") self.server = state.get_server(ban["_id"]["server"]) self.user_id = ban["_id"]["user"] self.state = state async def unban(self): """Unbans the user""" await self.state.http.unban_member(self.server.id, self.user_id)