mirror of
https://github.com/stoatchat/python-client-sdk.git
synced 2026-07-20 15:38:19 -04:00
add most basic types
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
.venv
|
||||
**/__pycache__
|
||||
test.py
|
||||
dist
|
||||
*.egg-info
|
||||
|
||||
@@ -2,3 +2,4 @@ from .channel import TextChannel, VoiceChannel
|
||||
from .client import Client
|
||||
from .server import Server
|
||||
from .user import User
|
||||
from .message import Message
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .payloads import File as FilePayload
|
||||
from .state import State
|
||||
from io import IOBase
|
||||
class Asset:
|
||||
def __init__(self, data: FilePayload, state: State):
|
||||
self.id = data['_id']
|
||||
self.tag = data['tag']
|
||||
self.size = data['size']
|
||||
self.filename = data['filename']
|
||||
|
||||
metadata = data['metadata']
|
||||
|
||||
if metadata["type"] == "Image" or metadata["type"] == "Video": # cant use `in` because type narrowing wont happen
|
||||
self.height = metadata["height"]
|
||||
self.width = metadata["width"]
|
||||
else:
|
||||
self.height = None
|
||||
self.width = None
|
||||
|
||||
self.content_type = data["content_type"]
|
||||
|
||||
async def read(self) -> bytes:
|
||||
...
|
||||
|
||||
async def save(self, fp: IOBase):
|
||||
fp.write(await self.read())
|
||||
+10
-6
@@ -1,6 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Text, Union
|
||||
from .messageable import Messageable
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .payloads import Channel as ChannelPayload
|
||||
@@ -11,30 +13,32 @@ class Channel:
|
||||
self.state = state
|
||||
self.id = data["_id"]
|
||||
self.channel_type = data["channel_type"]
|
||||
self.server = None
|
||||
|
||||
class SavedMessageChannel(Channel):
|
||||
class SavedMessageChannel(Channel, Messageable):
|
||||
def __init__(self, data: ChannelPayload, state: State):
|
||||
super().__init__(data, state)
|
||||
|
||||
class DMChannel(Channel):
|
||||
class DMChannel(Channel, Messageable):
|
||||
def __init__(self, data: ChannelPayload, state: State):
|
||||
super().__init__(data, state)
|
||||
|
||||
class GroupDMChannel(Channel):
|
||||
class GroupDMChannel(Channel, Messageable):
|
||||
def __init__(self, data: ChannelPayload, state: State):
|
||||
super().__init__(data, state)
|
||||
|
||||
class TextChannel(Channel):
|
||||
class TextChannel(Channel, Messageable):
|
||||
def __init__(self, data: ChannelPayload, state: State):
|
||||
super().__init__(data, state)
|
||||
Messageable.__init__(self, state)
|
||||
|
||||
class VoiceChannel(Channel):
|
||||
def __init__(self, data: ChannelPayload, state: State):
|
||||
super().__init__(data, state)
|
||||
|
||||
def channel_factory(data: ChannelPayload) -> type[Channel]:
|
||||
# Literal["SavedMessage", "DirectMessage", "Group", "TextChannel", "VoiceChannel"]
|
||||
channel_type = data["channel_type"]
|
||||
|
||||
if channel_type == "SavedMessage":
|
||||
return SavedMessageChannel
|
||||
elif channel_type == "DirectMessage":
|
||||
|
||||
+13
-8
@@ -26,11 +26,11 @@ R = TypeVar("R")
|
||||
Coro = Coroutine[Any, Any, R]
|
||||
|
||||
class Client:
|
||||
def __init__(self, session: aiohttp.ClientSession, token: str, api_url: str = "https://api.revolt.chat"):
|
||||
def __init__(self, session: aiohttp.ClientSession, token: str, api_url: str = "https://api.revolt.chat", max_messages: int = 5000):
|
||||
self.session = session
|
||||
self.token = token
|
||||
self.api_url = api_url
|
||||
self.events: dict[str, list[Callable[..., Coro[None]]]] = {}
|
||||
self.max_messages = max_messages
|
||||
|
||||
self.api_info: ApiInfo
|
||||
self.http: HttpClient
|
||||
@@ -39,22 +39,27 @@ class Client:
|
||||
|
||||
def event(self, name: str = None):
|
||||
def inner(func: Callable[..., Coro[None]]):
|
||||
event_name = name or func.__name__
|
||||
self.events.setdefault(event_name, []).append(func)
|
||||
event_name = "on_{name}" if name else func.__name__
|
||||
setattr(self, event_name, func)
|
||||
|
||||
return func
|
||||
return inner
|
||||
|
||||
def dispatch(self, event: str, *args: Any):
|
||||
...
|
||||
func = getattr(self, "on_{event}", None)
|
||||
if func:
|
||||
asyncio.create_task(func(*args))
|
||||
|
||||
async def get_api_info(self) -> ApiInfo:
|
||||
async with self.session.get(self.api_url) as resp:
|
||||
return json.loads(await resp.text())
|
||||
|
||||
async def start(self):
|
||||
async with self.session.get(self.api_url) as resp:
|
||||
api_info: ApiInfo = json.loads(await resp.text())
|
||||
api_info = await self.get_api_info()
|
||||
|
||||
self.api_info = api_info
|
||||
self.http = HttpClient(self.session, self.token, self.api_url, self.api_info)
|
||||
self.state = State(self.http, api_info)
|
||||
self.state = State(self.http, api_info, self.max_messages)
|
||||
self.websocket = WebsocketHandler(self.session, self.token, api_info["ws"], self.dispatch, self.state)
|
||||
|
||||
await self.websocket.start()
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .payloads import Embed as EmbedPayload
|
||||
|
||||
class Embed:
|
||||
@classmethod
|
||||
def from_dict(cls, data: EmbedPayload):
|
||||
...
|
||||
|
||||
def to_dict(self) -> EmbedPayload:
|
||||
...
|
||||
@@ -0,0 +1,8 @@
|
||||
class RevoltError(Exception):
|
||||
"Base exception for revolt"
|
||||
|
||||
class HTTPError(RevoltError):
|
||||
"Base exception for http errors"
|
||||
|
||||
class ServerError(RevoltError):
|
||||
"Internal server error"
|
||||
@@ -0,0 +1,24 @@
|
||||
from typing import Optional, Union
|
||||
|
||||
import io
|
||||
import os
|
||||
|
||||
class File:
|
||||
def __init__(self, file: Union[str, bytes], *, filename: Optional[str] = None, spoiler: bool = False):
|
||||
if isinstance(file, str):
|
||||
self.f = open(file, "rb")
|
||||
elif isinstance(file, bytes):
|
||||
self.f = io.BytesIO(file)
|
||||
|
||||
if filename is None and isinstance(file, str):
|
||||
filename = self.f.name
|
||||
|
||||
if spoiler or (filename and filename.startswith("SPOILER_")):
|
||||
self.spoiler = True
|
||||
else:
|
||||
self.spoiler = False
|
||||
|
||||
if self.spoiler and (filename and not filename.startswith("SPOILER_")):
|
||||
filename = f"SPOILER_{filename}"
|
||||
|
||||
self.filename = filename
|
||||
+80
-2
@@ -1,14 +1,92 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Any, Optional, TYPE_CHECKING, Literal
|
||||
import aiohttp
|
||||
|
||||
from .errors import HTTPError, ServerError
|
||||
from .file import File
|
||||
|
||||
try:
|
||||
import ujson as _json
|
||||
except ImportError:
|
||||
import json as _json
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import aiohttp
|
||||
from .payloads import ApiInfo
|
||||
from .payloads import ApiInfo, Autumn as AutumnPayload, Message as MessagePayload, Embed as EmbedPayload
|
||||
from .file import File
|
||||
|
||||
class HttpClient:
|
||||
def __init__(self, session: aiohttp.ClientSession, token: str, api_url: str, api_info: ApiInfo):
|
||||
self.session = session
|
||||
self.token = token
|
||||
self.api_url = api_url
|
||||
self.api_info = api_info
|
||||
|
||||
async def request(self, method: Literal["GET", "POST", "PUT", "DELETE", "PATCH"], route: str, *, files: Optional[list[File]] = None, json: Optional[dict] = None) -> Any:
|
||||
url = f"{self.api_url}{route}"
|
||||
|
||||
kwargs = {}
|
||||
|
||||
headers = {
|
||||
"User-Agent": "Revolt.py https://github.com/Zomatree/revolt.py",
|
||||
"x-bot-token": self.token
|
||||
}
|
||||
|
||||
if json:
|
||||
headers["Content-Type"] = "application/json"
|
||||
kwargs["data"] = _json.dumps(json)
|
||||
|
||||
kwargs["headers"] = headers
|
||||
|
||||
async with self.session.request(method, url, **kwargs) as resp:
|
||||
response = _json.loads(await resp.text())
|
||||
|
||||
resp_code = resp.status
|
||||
|
||||
if 200 <= resp_code <= 300:
|
||||
return response
|
||||
elif resp_code == 400:
|
||||
raise HTTPError
|
||||
|
||||
async def upload_file(self, file: File, tag: str) -> AutumnPayload:
|
||||
url = f"{self.api_info['features']['autumn']['url']}/{tag}"
|
||||
|
||||
headers = {
|
||||
"User-Agent": "Revolt.py https://github.com/Zomatree/revolt.py"
|
||||
}
|
||||
|
||||
form = aiohttp.FormData()
|
||||
form.add_field("file", file.f.read(), filename=file.filename)
|
||||
|
||||
async with self.session.post(url, data=form, headers=headers) as resp:
|
||||
response: AutumnPayload = _json.loads(await resp.text())
|
||||
|
||||
resp_code = resp.status
|
||||
|
||||
if resp_code == 400:
|
||||
raise HTTPError(response)
|
||||
elif 500 <= resp_code <= 600:
|
||||
raise ServerError
|
||||
else:
|
||||
return response
|
||||
|
||||
async def send_message(self, channel: str, content: Optional[str], embeds: Optional[list[EmbedPayload]], attachments: Optional[list[File]]) -> MessagePayload:
|
||||
json = {}
|
||||
|
||||
if content:
|
||||
json["content"] = content
|
||||
|
||||
if embeds:
|
||||
json["embeds"] = embeds
|
||||
|
||||
if attachments:
|
||||
attachment_ids = []
|
||||
|
||||
for attachment in attachments:
|
||||
data = await self.upload_file(attachment, "attachments")
|
||||
attachment_ids.append(data["id"])
|
||||
|
||||
json["attachments"] = attachment_ids
|
||||
|
||||
return await self.request("POST", "/channels/{channel}/messages", json=json)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .state import State
|
||||
from .payloads import Member as MemberPayload
|
||||
from .server import Server
|
||||
|
||||
class Member:
|
||||
def __init__(self, data: MemberPayload, server: Server, state: State):
|
||||
self._state = state
|
||||
self.id = data["_id"]
|
||||
self.nickname = data.get("nickname")
|
||||
self.roles = [server.get_role(role_id) for role_id in data.get("roles", [])]
|
||||
|
||||
self.server = server
|
||||
@@ -0,0 +1,29 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .asset import Asset
|
||||
from .embed import Embed
|
||||
from .channel import TextChannel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .state import State
|
||||
from .payloads import Message as MessagePayload
|
||||
|
||||
class Message:
|
||||
def __init__(self, data: MessagePayload, state: State):
|
||||
self.state = state
|
||||
|
||||
self.id = data["_id"]
|
||||
self.content = data['content']
|
||||
self.attachments = [Asset(attachment, state) for attachment in data.get('attachments', [])]
|
||||
self.embeds = [Embed.from_dict(embed) for embed in data["embeds"]]
|
||||
|
||||
self.channel = state.get_channel(data['channel'])
|
||||
self.server = self.channel and self.channel.server
|
||||
|
||||
if isinstance(self.channel, TextChannel):
|
||||
self.author = state.get_member(self.id, data['author'])
|
||||
else:
|
||||
self.author = state.get_user(data['author'])
|
||||
@@ -0,0 +1,25 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .state import State
|
||||
from .message import Message
|
||||
from .embed import Embed
|
||||
from .file import File
|
||||
|
||||
class Messageable:
|
||||
id: str
|
||||
|
||||
def __init__(self, state: State):
|
||||
self.state = state
|
||||
|
||||
async def send(self, content: Optional[str] = None, embeds: Optional[list[Embed]] = None, embed: Optional[Embed] = None, attachments: Optional[list[File]] = None) -> Message:
|
||||
if embed:
|
||||
embeds = [embed]
|
||||
|
||||
embed_payload = [embed.to_dict() for embed in embeds] if embeds else None
|
||||
|
||||
message = await self.state.http.send_message(self.id, content, embed_payload, attachments)
|
||||
return self.state.add_message(message)
|
||||
+101
-16
@@ -1,8 +1,23 @@
|
||||
from typing import Literal, TypedDict, Union
|
||||
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: dict
|
||||
features: Features
|
||||
ws: str
|
||||
app: str
|
||||
vapid: str
|
||||
@@ -16,12 +31,15 @@ class AuthenticatePayload(BasePayload):
|
||||
class UserBot(TypedDict):
|
||||
owner: str
|
||||
|
||||
class _FileMetadataOptional(TypedDict, total=False):
|
||||
width: int
|
||||
class SizedMetadata(TypedDict):
|
||||
type: Literal["Image", "Video"]
|
||||
height: int
|
||||
width: int
|
||||
|
||||
class FileMetadata(_FileMetadataOptional):
|
||||
type: Literal["File", "Text", "Audio", "Image", "Video"]
|
||||
class SimpleMetadata(TypedDict):
|
||||
type: Literal["File", "Text", "Audio"]
|
||||
|
||||
FileMetadata = Union[SizedMetadata, SimpleMetadata]
|
||||
|
||||
class File(TypedDict):
|
||||
_id: str
|
||||
@@ -31,11 +49,15 @@ class File(TypedDict):
|
||||
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: Status
|
||||
relationship: Literal["Blocked", "BlockedOther", "Friend", "Incoming", "None", "Outgoing", "User"]
|
||||
online: bool
|
||||
flags: int
|
||||
@@ -82,31 +104,94 @@ class ChannelIconChanged(TypedDict):
|
||||
class _OptionalMessage(TypedDict):
|
||||
attachments: list[File]
|
||||
|
||||
class Embed(TypedDict):
|
||||
pass # TODO
|
||||
|
||||
class Message(_OptionalMessage):
|
||||
_id: str
|
||||
channel: str
|
||||
author: str
|
||||
content: Union[str, UserAddContent, UserRemoveContent, UserJoinedContent, UserLeftContent, UserKickedContent, UserBanned, ChannelRenameContent, ChannelDescriptionChangeContent, ChannelIconChanged]
|
||||
embeds: list[Embed]
|
||||
|
||||
class _OptionalChannel(TypedDict, total=False):
|
||||
user: str
|
||||
class _NonceChannel(TypedDict, total=False):
|
||||
nonce: str
|
||||
active: 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
|
||||
description: str
|
||||
channel_type: Literal["Group"]
|
||||
|
||||
class _TextChannelOptional(TypedDict, total=False):
|
||||
icon: File
|
||||
default_permissions: int
|
||||
role_permissions: int
|
||||
|
||||
class Channel(_OptionalChannel):
|
||||
_id: str
|
||||
channel_type: Literal["SavedMessage", "DirectMessage", "Group", "TextChannel", "VoiceChannel"]
|
||||
class TextChannel(_NonceChannel, _TextChannelOptional, BaseChannel):
|
||||
server: str
|
||||
name: str
|
||||
description: str
|
||||
last_message: Message
|
||||
channel_type: Literal["TextChannel"]
|
||||
|
||||
class ReadyPayload(BasePayload):
|
||||
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 ReadyEventPayload(BasePayload):
|
||||
users: list[User]
|
||||
servers: list[Server]
|
||||
channels: list[Channel]
|
||||
|
||||
class MessageEventPayload(BasePayload):
|
||||
class MessageEventPayload(BasePayload, Message):
|
||||
pass
|
||||
|
||||
class Autumn(TypedDict):
|
||||
id: str
|
||||
|
||||
class _MemberOptional(TypedDict, total=False):
|
||||
nickname: str
|
||||
avatar: File
|
||||
roles: list[str]
|
||||
|
||||
class Member(_MemberOptional):
|
||||
_id: str
|
||||
|
||||
Permission = tuple[int, int]
|
||||
|
||||
class _RoleOptional(TypedDict, total=False):
|
||||
colour: str
|
||||
hoist: bool
|
||||
rank: int
|
||||
|
||||
class Role(_RoleOptional):
|
||||
name: str
|
||||
permissions: Permission
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .payloads import Permission as PermissionTuple
|
||||
|
||||
class Permissions:
|
||||
def __init__(self, permission_tuple: PermissionTuple):
|
||||
...
|
||||
@@ -0,0 +1,14 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .state import State
|
||||
from .payloads import Role as RolePayload
|
||||
|
||||
class Role:
|
||||
def __init__(self, data: RolePayload, role_id: str, state: State):
|
||||
self.id = role_id
|
||||
self.name = data["name"]
|
||||
self.colour = data.get("colour")
|
||||
self.hoist = data.get("hoist", False)
|
||||
self.rank = data.get("rank", 0)
|
||||
+14
-2
@@ -1,13 +1,25 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .payloads import Server as ServerPayload
|
||||
from .state import State
|
||||
|
||||
from .member import Member
|
||||
from .role import Role
|
||||
from .channel import Channel
|
||||
|
||||
class Server:
|
||||
def __init__(self, data: ServerPayload, state: State):
|
||||
self.state = state
|
||||
self.id = data["_id"]
|
||||
|
||||
self._members: dict[str, Member] = {}
|
||||
self._roles: dict[str, Role] = {}
|
||||
self._channels: dict[str, Channel] = {}
|
||||
|
||||
def get_role(self, role_id: str, /) -> Optional[Role]:
|
||||
return self._roles.get(role_id)
|
||||
|
||||
def get_member(self, member_id: str, /) -> Optional[Member]:
|
||||
return self._members.get(member_id)
|
||||
|
||||
+36
-5
@@ -1,38 +1,61 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from collections import deque
|
||||
|
||||
from .user import User
|
||||
from .channel import Channel, channel_factory
|
||||
from .server import Server
|
||||
from .message import Message
|
||||
from .member import Member
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .http import HttpClient
|
||||
from .payloads import ApiInfo, User as UserPayload, Channel as ChannelPayload, Server as ServerPayload
|
||||
from .payloads 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):
|
||||
def __init__(self, http: HttpClient, api_info: ApiInfo, max_messages: int):
|
||||
self.http = http
|
||||
self.api_info = api_info
|
||||
self.max_messages = max_messages
|
||||
|
||||
self.users: dict[str, User] = {}
|
||||
self.channels: dict[str, Channel] = {}
|
||||
self.servers: dict[str, Server] = {}
|
||||
self.messages: deque[Message] = deque()
|
||||
|
||||
def get_user(self, id: str) -> Optional[User]:
|
||||
self.users.get(id)
|
||||
return self.users.get(id)
|
||||
|
||||
def get_member(self, server_id: str, member_id: str) -> Optional[Member]:
|
||||
server = self.servers.get(server_id)
|
||||
|
||||
if not server:
|
||||
return
|
||||
|
||||
return server.get_member(member_id)
|
||||
|
||||
def get_channel(self, id: str) -> Optional[Channel]:
|
||||
self.channels.get(id)
|
||||
return self.channels.get(id)
|
||||
|
||||
def get_server(self, id: str) -> Optional[Server]:
|
||||
self.channels.get(id)
|
||||
return self.servers.get(id)
|
||||
|
||||
def add_user(self, payload: UserPayload) -> User:
|
||||
user = User(payload, self)
|
||||
self.users[user.id] = user
|
||||
return user
|
||||
|
||||
def add_member(self, server_id: str, payload: MemberPayload) -> Optional[Member]:
|
||||
server = self.get_server(server_id)
|
||||
if not server:
|
||||
return
|
||||
|
||||
member = Member(payload, server, self)
|
||||
|
||||
server._members[member.id] = member
|
||||
return member
|
||||
|
||||
def add_channel(self, payload: ChannelPayload) -> Channel:
|
||||
cls = channel_factory(payload)
|
||||
channel = cls(payload, self)
|
||||
@@ -43,3 +66,11 @@ class State:
|
||||
server = Server(payload, self)
|
||||
self.servers[server.id] = server
|
||||
return server
|
||||
|
||||
def add_message(self, payload: MessagePayload) -> Message:
|
||||
message = Message(payload, self)
|
||||
if len(self.messages) >= self.max_messages:
|
||||
self.messages.pop()
|
||||
|
||||
self.messages.appendleft(message)
|
||||
return message
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class _Missing:
|
||||
def __repr__(self):
|
||||
return "<Missing>"
|
||||
|
||||
Missing = _Missing()
|
||||
+15
-5
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
from revolt.payloads import MessageEventPayload
|
||||
|
||||
from typing import Callable, TYPE_CHECKING
|
||||
from typing import Callable, TYPE_CHECKING, Coroutine, cast
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
@@ -11,13 +12,13 @@ except ImportError:
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import aiohttp
|
||||
from .payloads import BasePayload, AuthenticatePayload, ReadyPayload
|
||||
from .payloads import BasePayload, AuthenticatePayload, ReadyEventPayload, Message as MessagePayload
|
||||
from .state import State
|
||||
|
||||
logger = logging.getLogger("revolt")
|
||||
|
||||
class WebsocketHandler:
|
||||
def __init__(self, session: aiohttp.ClientSession, token: str, ws_url: str, dispatch: Callable, state: State):
|
||||
def __init__(self, session: aiohttp.ClientSession, token: str, ws_url: str, dispatch: Callable[..., None], state: State):
|
||||
self.session = session
|
||||
self.token = token
|
||||
self.ws_url = ws_url
|
||||
@@ -52,16 +53,25 @@ class WebsocketHandler:
|
||||
|
||||
await func(payload)
|
||||
|
||||
async def handle_ready(self, payload: ReadyPayload):
|
||||
async def handle_authenticated(self, _):
|
||||
logger.info("Successfully authenticated")
|
||||
|
||||
async def handle_ready(self, payload: ReadyEventPayload):
|
||||
for user in payload["users"]:
|
||||
self.state.add_user(user)
|
||||
|
||||
|
||||
for server in payload["servers"]:
|
||||
self.state.add_server(server)
|
||||
|
||||
for channel in payload["channels"]:
|
||||
self.state.add_channel(channel)
|
||||
|
||||
self.dispatch("ready")
|
||||
|
||||
async def handle_message(self, payload: MessageEventPayload):
|
||||
message = self.state.add_message(cast(MessagePayload, payload))
|
||||
self.dispatch("message", message)
|
||||
|
||||
async def start(self):
|
||||
self.websocket = await self.session.ws_connect(self.ws_url)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user