diff --git a/revolt/asset.py b/revolt/asset.py index 4f9bd14..d229c53 100644 --- a/revolt/asset.py +++ b/revolt/asset.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import TYPE_CHECKING, Union if TYPE_CHECKING: - from .payloads import File as FilePayload + from .types import File as FilePayload from .state import State from io import IOBase class Asset: diff --git a/revolt/channel.py b/revolt/channel.py index d27cdee..4937f59 100644 --- a/revolt/channel.py +++ b/revolt/channel.py @@ -5,7 +5,7 @@ from .messageable import Messageable from typing import TYPE_CHECKING if TYPE_CHECKING: - from .payloads import Channel as ChannelPayload + from .types import Channel as ChannelPayload from .state import State class Channel: diff --git a/revolt/client.py b/revolt/client.py index a2c5d01..2016260 100644 --- a/revolt/client.py +++ b/revolt/client.py @@ -15,7 +15,7 @@ except ImportError: import json if TYPE_CHECKING: - from .payloads import ApiInfo + from .types import ApiInfo from .user import User from .server import Server from .channel import Channel diff --git a/revolt/embed.py b/revolt/embed.py index 2d83f64..f86d14f 100644 --- a/revolt/embed.py +++ b/revolt/embed.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import TYPE_CHECKING if TYPE_CHECKING: - from .payloads import Embed as EmbedPayload + from .types import Embed as EmbedPayload class Embed: @classmethod diff --git a/revolt/http.py b/revolt/http.py index 173d4c7..1753bbf 100644 --- a/revolt/http.py +++ b/revolt/http.py @@ -14,7 +14,7 @@ except ImportError: if TYPE_CHECKING: import aiohttp - from .payloads import ApiInfo, Autumn as AutumnPayload, Message as MessagePayload, Embed as EmbedPayload + from .types import ApiInfo, Autumn as AutumnPayload, Message as MessagePayload, Embed as EmbedPayload, GetServerMembers from .file import File class HttpClient: diff --git a/revolt/member.py b/revolt/member.py index f12efe8..dc1f656 100644 --- a/revolt/member.py +++ b/revolt/member.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: from .state import State - from .payloads import Member as MemberPayload + from .types import Member as MemberPayload from .server import Server class Member: diff --git a/revolt/message.py b/revolt/message.py index c8f6656..a4349e0 100644 --- a/revolt/message.py +++ b/revolt/message.py @@ -9,7 +9,7 @@ from .channel import TextChannel if TYPE_CHECKING: from .state import State - from .payloads import Message as MessagePayload + from .types import Message as MessagePayload class Message: def __init__(self, data: MessagePayload, state: State): diff --git a/revolt/payloads.py b/revolt/payloads.py deleted file mode 100644 index bb376f1..0000000 --- a/revolt/payloads.py +++ /dev/null @@ -1,204 +0,0 @@ -from typing import Literal, Type, TypedDict, Union - -class ApiFeature(TypedDict): - enabled: bool - url: str - -class VosoFeature(ApiFeature): - ws: str - -class Features(TypedDict): - email: bool - invite_only: bool - captcha: ApiFeature - autumn: ApiFeature - january: ApiFeature - voso: VosoFeature - -class ApiInfo(TypedDict): - revolt: str - features: Features - ws: str - app: str - vapid: str - -class BasePayload(TypedDict): - type: str - -class AuthenticatePayload(BasePayload): - token: str - -class UserBot(TypedDict): - owner: str - -class SizedMetadata(TypedDict): - type: Literal["Image", "Video"] - height: int - width: int - -class SimpleMetadata(TypedDict): - type: Literal["File", "Text", "Audio"] - -FileMetadata = Union[SizedMetadata, SimpleMetadata] - -class File(TypedDict): - _id: str - tag: str - size: int - filename: str - metadata: FileMetadata - content_type: str - -class Status(TypedDict, total=False): - text: str - presence: Literal["Busy", "Idle", "Invisible", "Online"] - -class _OptionalUser(TypedDict, total=False): - avatar: File - relations: list - badges: int - status: Status - relationship: Literal["Blocked", "BlockedOther", "Friend", "Incoming", "None", "Outgoing", "User"] - online: bool - flags: int - bot: UserBot - -class User(_OptionalUser): - _id: str - username: str - -class Server(TypedDict): - _id: str - -class UserAddContent(TypedDict): - id: str - by: str - -class UserRemoveContent(TypedDict): - id: str - by: str - -class UserJoinedContent(TypedDict): - id: str - by: str - -class UserLeftContent(TypedDict): - id: str - -class UserKickedContent(TypedDict): - id: str - -class UserBanned(TypedDict): - id: str - -class ChannelRenameContent(TypedDict): - name: str - by: str - -class ChannelDescriptionChangeContent(TypedDict): - by: str - -class ChannelIconChanged(TypedDict): - by: str - -class Embed(TypedDict): - pass # TODO -class _OptionalMessage(TypedDict): - attachments: list[File] - embeds: list[Embed] - -class Message(_OptionalMessage): - _id: str - channel: str - author: str - content: Union[str, UserAddContent, UserRemoveContent, UserJoinedContent, UserLeftContent, UserKickedContent, UserBanned, ChannelRenameContent, ChannelDescriptionChangeContent, ChannelIconChanged] - -class _NonceChannel(TypedDict, total=False): - nonce: str - -class BaseChannel(TypedDict): - _id: str - -class SavedMessages(_NonceChannel, BaseChannel): - user: str - channel_type: Literal["SavedMessage"] - -class DirectMessage(_NonceChannel, BaseChannel): - active: bool - recipients: list[str] - last_message: Message - channel_type: Literal["DirectMessage"] - -class _GroupOptional(TypedDict): - icon: File - permissions: int - description: str - -class Group(_NonceChannel, _GroupOptional, BaseChannel): - recipients: list[str] - name: str - owner: str - channel_type: Literal["Group"] - -class _TextChannelOptional(TypedDict, total=False): - icon: File - default_permissions: int - role_permissions: int - -class TextChannel(_NonceChannel, _TextChannelOptional, BaseChannel): - server: str - name: str - description: str - last_message: Message - channel_type: Literal["TextChannel"] - -class _VoiceChannelOptional(TypedDict, total=False): - icon: File - default_permissions: int - role_permissions: int - -class VoiceChannel(_NonceChannel, _TextChannelOptional, BaseChannel): - server: str - name: str - description: str - channel_type: Literal["VoiceChannel"] - -Channel = Union[SavedMessages, DirectMessage, Group, TextChannel, VoiceChannel] - -class _MemberOptional(TypedDict, total=False): - nickname: str - avatar: File - roles: list[str] - -class Member(_MemberOptional): - _id: str - -class ReadyMemberUser(TypedDict): - server: str - user: str - -class ReadyMember(_MemberOptional): - _id: ReadyMemberUser - -class ReadyEventPayload(BasePayload): - users: list[User] - servers: list[Server] - channels: list[Channel] - members: list[ReadyMember] - -class MessageEventPayload(BasePayload, Message): - pass - -class Autumn(TypedDict): - id: str - -Permission = tuple[int, int] - -class _RoleOptional(TypedDict, total=False): - colour: str - hoist: bool - rank: int - -class Role(_RoleOptional): - name: str - permissions: Permission diff --git a/revolt/permissions.py b/revolt/permissions.py index 81fd481..8dbe053 100644 --- a/revolt/permissions.py +++ b/revolt/permissions.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from .payloads import Permission as PermissionTuple + from .types import Permission as PermissionTuple class Permissions: def __init__(self, permission_tuple: PermissionTuple): diff --git a/revolt/role.py b/revolt/role.py index 9cd5abe..d67f3b0 100644 --- a/revolt/role.py +++ b/revolt/role.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: from .state import State - from .payloads import Role as RolePayload + from .types import Role as RolePayload class Role: def __init__(self, data: RolePayload, role_id: str, state: State): diff --git a/revolt/server.py b/revolt/server.py index f5f987f..bc88b82 100644 --- a/revolt/server.py +++ b/revolt/server.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import TYPE_CHECKING, Optional if TYPE_CHECKING: - from .payloads import Server as ServerPayload + from .types import Server as ServerPayload from .state import State from .member import Member from .role import Role diff --git a/revolt/state.py b/revolt/state.py index 0d0138e..6353d66 100644 --- a/revolt/state.py +++ b/revolt/state.py @@ -11,7 +11,7 @@ from .member import Member if TYPE_CHECKING: from .http import HttpClient - from .payloads import ApiInfo, User as UserPayload, Channel as ChannelPayload, Server as ServerPayload, Message as MessagePayload, Member as MemberPayload + from .types import ApiInfo, User as UserPayload, Channel as ChannelPayload, Server as ServerPayload, Message as MessagePayload, Member as MemberPayload class State: def __init__(self, http: HttpClient, api_info: ApiInfo, max_messages: int): diff --git a/revolt/types/__init__.py b/revolt/types/__init__.py new file mode 100644 index 0000000..b1c34fe --- /dev/null +++ b/revolt/types/__init__.py @@ -0,0 +1,10 @@ +from .channel import * +from .embed import * +from .file import * +from .gateway import * +from .http import * +from .member import * +from .message import * +from .role import * +from .server import * +from .user import * diff --git a/revolt/types/channel.py b/revolt/types/channel.py new file mode 100644 index 0000000..18e7380 --- /dev/null +++ b/revolt/types/channel.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, TypedDict, Literal, Union + +if TYPE_CHECKING: + from .message import Message + from .file import File + +class _NonceChannel(TypedDict, total=False): + nonce: str + +class BaseChannel(TypedDict): + _id: str + +class SavedMessages(_NonceChannel, BaseChannel): + user: str + channel_type: Literal["SavedMessage"] + +class DirectMessage(_NonceChannel, BaseChannel): + active: bool + recipients: list[str] + last_message: Message + channel_type: Literal["DirectMessage"] + +class _GroupOptional(TypedDict): + icon: File + permissions: int + description: str + +class Group(_NonceChannel, _GroupOptional, BaseChannel): + recipients: list[str] + name: str + owner: str + channel_type: Literal["Group"] + +class _TextChannelOptional(TypedDict, total=False): + icon: File + default_permissions: int + role_permissions: int + +class TextChannel(_NonceChannel, _TextChannelOptional, BaseChannel): + server: str + name: str + description: str + last_message: Message + channel_type: Literal["TextChannel"] + +class _VoiceChannelOptional(TypedDict, total=False): + icon: File + default_permissions: int + role_permissions: int + +class VoiceChannel(_NonceChannel, _TextChannelOptional, BaseChannel): + server: str + name: str + description: str + channel_type: Literal["VoiceChannel"] + +Channel = Union[SavedMessages, DirectMessage, Group, TextChannel, VoiceChannel] diff --git a/revolt/types/embed.py b/revolt/types/embed.py new file mode 100644 index 0000000..9f9b0af --- /dev/null +++ b/revolt/types/embed.py @@ -0,0 +1,6 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, TypedDict + +class Embed(TypedDict): + pass # TODO diff --git a/revolt/types/file.py b/revolt/types/file.py new file mode 100644 index 0000000..038a183 --- /dev/null +++ b/revolt/types/file.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, TypedDict, Literal, Union + +class SizedMetadata(TypedDict): + type: Literal["Image", "Video"] + height: int + width: int + +class SimpleMetadata(TypedDict): + type: Literal["File", "Text", "Audio"] + +FileMetadata = Union[SizedMetadata, SimpleMetadata] + +class File(TypedDict): + _id: str + tag: str + size: int + filename: str + metadata: FileMetadata + content_type: str diff --git a/revolt/types/gateway.py b/revolt/types/gateway.py new file mode 100644 index 0000000..8adbaac --- /dev/null +++ b/revolt/types/gateway.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, TypedDict + +from .message import Message + +if TYPE_CHECKING: + from .user import User + from .server import Server + from .channel import Channel + from .member import Member + +class BasePayload(TypedDict): + type: str + +class AuthenticatePayload(BasePayload): + token: str + +class ReadyEventPayload(BasePayload): + users: list[User] + servers: list[Server] + channels: list[Channel] + members: list[Member] + +class MessageEventPayload(BasePayload, Message): + pass diff --git a/revolt/types/http.py b/revolt/types/http.py new file mode 100644 index 0000000..7ad2d48 --- /dev/null +++ b/revolt/types/http.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, TypedDict + +if TYPE_CHECKING: + from .user import User + from .member import Member + +class ApiFeature(TypedDict): + enabled: bool + url: str + +class VosoFeature(ApiFeature): + ws: str + +class Features(TypedDict): + email: bool + invite_only: bool + captcha: ApiFeature + autumn: ApiFeature + january: ApiFeature + voso: VosoFeature + +class ApiInfo(TypedDict): + revolt: str + features: Features + ws: str + app: str + vapid: str + +class Autumn(TypedDict): + id: str + +class GetServerMembers(TypedDict): + members: list[Member] + users: list[User] diff --git a/revolt/types/member.py b/revolt/types/member.py new file mode 100644 index 0000000..6d3fc2c --- /dev/null +++ b/revolt/types/member.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, TypedDict + +if TYPE_CHECKING: + from .file import File + +class _MemberOptional(TypedDict, total=False): + nickname: str + avatar: File + roles: list[str] + +class MemberID(TypedDict): + server: str + user: str + +class Member(_MemberOptional): + _id: MemberID diff --git a/revolt/types/message.py b/revolt/types/message.py new file mode 100644 index 0000000..3b1a7e5 --- /dev/null +++ b/revolt/types/message.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, TypedDict, Union + +if TYPE_CHECKING: + from .file import File + from .embed import Embed + +class UserAddContent(TypedDict): + id: str + by: str + +class UserRemoveContent(TypedDict): + id: str + by: str + +class UserJoinedContent(TypedDict): + id: str + by: str + +class UserLeftContent(TypedDict): + id: str + +class UserKickedContent(TypedDict): + id: str + +class UserBanned(TypedDict): + id: str + +class ChannelRenameContent(TypedDict): + name: str + by: str + +class ChannelDescriptionChangeContent(TypedDict): + by: str + +class ChannelIconChanged(TypedDict): + by: str + +class _OptionalMessage(TypedDict): + attachments: list[File] + embeds: list[Embed] + +class Message(_OptionalMessage): + _id: str + channel: str + author: str + content: Union[str, UserAddContent, UserRemoveContent, UserJoinedContent, UserLeftContent, UserKickedContent, UserBanned, ChannelRenameContent, ChannelDescriptionChangeContent, ChannelIconChanged] diff --git a/revolt/types/role.py b/revolt/types/role.py new file mode 100644 index 0000000..4720c3a --- /dev/null +++ b/revolt/types/role.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from typing import TypedDict + +Permission = tuple[int, int] + +class _RoleOptional(TypedDict, total=False): + colour: str + hoist: bool + rank: int + +class Role(_RoleOptional): + name: str + permissions: Permission diff --git a/revolt/types/server.py b/revolt/types/server.py new file mode 100644 index 0000000..416a833 --- /dev/null +++ b/revolt/types/server.py @@ -0,0 +1,9 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, TypedDict + +if TYPE_CHECKING: + pass + +class Server(TypedDict): + _id: str diff --git a/revolt/types/user.py b/revolt/types/user.py new file mode 100644 index 0000000..12076f9 --- /dev/null +++ b/revolt/types/user.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, TypedDict, Literal + +if TYPE_CHECKING: + from .file import File + +class UserBot(TypedDict): + owner: str + +class Status(TypedDict, total=False): + text: str + presence: Literal["Busy", "Idle", "Invisible", "Online"] + +class _OptionalUser(TypedDict, total=False): + avatar: File + relations: list + badges: int + status: Status + relationship: Literal["Blocked", "BlockedOther", "Friend", "Incoming", "None", "Outgoing", "User"] + online: bool + flags: int + bot: UserBot + +class User(_OptionalUser): + _id: str + username: str diff --git a/revolt/user.py b/revolt/user.py index ea19255..04514e6 100644 --- a/revolt/user.py +++ b/revolt/user.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: from .state import State - from .payloads import User as UserPayload + from .types import User as UserPayload class User: def __init__(self, data: UserPayload, state: State): diff --git a/revolt/websocket.py b/revolt/websocket.py index 2ea8469..b9a9908 100644 --- a/revolt/websocket.py +++ b/revolt/websocket.py @@ -4,16 +4,22 @@ from typing import Callable, TYPE_CHECKING, cast import logging import asyncio -from .payloads import Message as MessagePayload +from .types import Message as MessagePayload try: import ujson as json except ImportError: import json +try: + import msgpack + use_msgpack = True +except ImportError: + use_msgpack = False + if TYPE_CHECKING: import aiohttp - from .payloads import BasePayload, AuthenticatePayload, ReadyEventPayload, MessageEventPayload, Member as MemberPayload + from .types import BasePayload, AuthenticatePayload, ReadyEventPayload, MessageEventPayload, Member as MemberPayload from .state import State logger = logging.getLogger("revolt") @@ -28,10 +34,14 @@ class WebsocketHandler: self.websocket: aiohttp.ClientWebSocketResponse async def send_payload(self, payload: BasePayload): - await self.websocket.send_str(json.dumps(payload)) + if use_msgpack: + await self.websocket.send_bytes(msgpack.packb(payload)) # type: ignore + else: + await self.websocket.send_str(json.dumps(payload)) async def heartbeat(self): while not self.websocket.closed: + logger.info("Sending hearbeat") await self.send_payload({"type": "Ping"}) await asyncio.sleep(15) @@ -45,7 +55,7 @@ class WebsocketHandler: async def handle_event(self, payload: BasePayload): event_type = payload["type"].lower() - + logger.debug("Recieved event %s %s", event_type, payload) try: func = getattr(self, f"handle_{event_type}") except: @@ -68,9 +78,9 @@ class WebsocketHandler: self.state.add_channel(channel) for member in payload["members"]: - server_id = member["_id"]["server"] - member_payload: MemberPayload = member | {"_id": member["_id"]["user"]} # type: ignore - self.state.add_member(server_id, member_payload) + self.state.add_member(member["_id"]["server"], member) + + await self.state.fetch_all_server_members() self.dispatch("ready") @@ -79,10 +89,19 @@ class WebsocketHandler: self.dispatch("message", message) async def start(self): - self.websocket = await self.session.ws_connect(self.ws_url) + if use_msgpack: + url = f"{self.ws_url}?format=msgpack" + else: + url = f"{self.ws_url}?format=json" + self.websocket = await self.session.ws_connect(url) await self.send_authenticate() asyncio.create_task(self.heartbeat()) async for msg in self.websocket: - await self.handle_event(json.loads(msg.data)) + if use_msgpack: + payload = msgpack.unpackb(msg.data) + else: + payload = json.loads(msg.data) + + await self.handle_event(payload)