mirror of
https://github.com/stoatchat/python-client-sdk.git
synced 2026-07-21 01:55:23 -04:00
add enums, add more stuff
This commit is contained in:
@@ -34,6 +34,25 @@ DMChannel
|
||||
.. autoclass:: DMChannel
|
||||
:members:
|
||||
|
||||
GroupDMChannel
|
||||
~~~~~~~~~~
|
||||
|
||||
.. autoclass:: GroupDMChannel
|
||||
:members:
|
||||
|
||||
TextChannel
|
||||
~~~~~~~~~~
|
||||
|
||||
.. autoclass:: TextChannel
|
||||
:members:
|
||||
|
||||
VoiceChannel
|
||||
~~~~~~~~~~
|
||||
|
||||
.. autoclass:: VoiceChannel
|
||||
:members:
|
||||
|
||||
|
||||
Embed
|
||||
~~~~~~~~~~
|
||||
|
||||
@@ -88,3 +107,78 @@ User
|
||||
|
||||
.. autoclass:: User
|
||||
:members:
|
||||
|
||||
Enums
|
||||
------
|
||||
|
||||
The api uses enums to say what variant of something is, these represent those enums
|
||||
|
||||
All enums subclass `aenum.Enum`.
|
||||
|
||||
.. class:: ChannelType
|
||||
|
||||
Specifies the type of channel.
|
||||
|
||||
.. attribute:: saved_message
|
||||
|
||||
A private channel only you can access.
|
||||
.. attribute:: direct_message
|
||||
|
||||
A private direct message channel between you and another user
|
||||
.. attribute:: group
|
||||
|
||||
A private group channel for messages between a group of users
|
||||
.. attribute:: text_channel
|
||||
|
||||
A text channel in a server
|
||||
.. attribute:: voice_channel
|
||||
|
||||
A voice only channel
|
||||
|
||||
.. class:: PresenceType
|
||||
|
||||
Specifies what a users presence is
|
||||
|
||||
.. attribute:: busy
|
||||
|
||||
The user is busy and wont receive notification
|
||||
.. attribute:: idle
|
||||
|
||||
The user is idle
|
||||
.. attribute:: invisible
|
||||
|
||||
The user is invisible, you will never receive this, instead they will appear offline
|
||||
.. attribute:: online
|
||||
|
||||
The user is online
|
||||
|
||||
.. attribute:: offline
|
||||
|
||||
The user is offline or invisible
|
||||
|
||||
.. attribute:: RelationshipType
|
||||
|
||||
Specifies the relationship between two users
|
||||
|
||||
.. attribute:: blocked
|
||||
|
||||
You have blocked them
|
||||
.. attribute:: blocked_other
|
||||
|
||||
They have blocked you
|
||||
.. attribute:: friend
|
||||
|
||||
You are friends with them
|
||||
.. attribute:: incoming_friend_request
|
||||
|
||||
They are sending you a friend request
|
||||
.. attribute:: none
|
||||
|
||||
You have no relationship with them
|
||||
.. attribute:: outgoing_friend_request
|
||||
|
||||
You are sending them a friend request
|
||||
|
||||
.. attribute:: user
|
||||
|
||||
That user is yourself
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
aiohttp==3.7.4.post0
|
||||
ulid-py==1.1.0
|
||||
aenum==3.1.0
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
from .asset import Asset
|
||||
from .channel import DMChannel, Channel, GroupDMChannel, PartialTextChannel, SavedMessageChannel, VoiceChannel
|
||||
from .channel import DMChannel, Channel, GroupDMChannel, SavedMessageChannel, VoiceChannel
|
||||
from .client import Client
|
||||
from .embed import Embed
|
||||
from .errors import HTTPError, RevoltError, ServerError
|
||||
|
||||
+43
-8
@@ -2,6 +2,9 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .errors import AutumnDisabled
|
||||
from .enums import AssetType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .types import File as FilePayload
|
||||
from .state import State
|
||||
@@ -12,22 +15,26 @@ class Asset:
|
||||
|
||||
Attributes
|
||||
-----------
|
||||
id: str
|
||||
id: :class:`str`
|
||||
The id of the asset
|
||||
tag: str
|
||||
tag: :class:`str`
|
||||
The tag of the asset, this corrasponds to where the asset is used
|
||||
size: int
|
||||
size: :class:`int`
|
||||
Amount of bytes in the file
|
||||
filename: str
|
||||
filename: :class:`str`
|
||||
The name of the file
|
||||
height: Optional[int]
|
||||
height: Optional[:class:`int`]
|
||||
The height of the file if it is an image or video
|
||||
width: Optional[int]
|
||||
width: Optional[:class:`int`]
|
||||
The width of the file if it is an image or video
|
||||
content_type: str
|
||||
The content type of the file
|
||||
content_type: :class:`str`
|
||||
The content type of the file
|
||||
type: :class:`AssetType`
|
||||
The type of asset it is
|
||||
"""
|
||||
def __init__(self, data: FilePayload, state: State):
|
||||
self.state = state
|
||||
|
||||
self.id = data['_id']
|
||||
self.tag = data['tag']
|
||||
self.size = data['size']
|
||||
@@ -44,6 +51,8 @@ class Asset:
|
||||
|
||||
self.content_type = data["content_type"]
|
||||
|
||||
self.type = AssetType(metadata["type"])
|
||||
|
||||
async def read(self) -> bytes:
|
||||
"""Reads the files content into bytes"""
|
||||
...
|
||||
@@ -57,3 +66,29 @@ class Asset:
|
||||
The file to write too.
|
||||
"""
|
||||
fp.write(await self.read())
|
||||
|
||||
@property
|
||||
def url(self) -> str:
|
||||
"""Returns the url for the asset
|
||||
|
||||
.. note:: This can error if autumn is disabled on the instance of revolt
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`str`
|
||||
The url
|
||||
|
||||
Raises
|
||||
-------
|
||||
:class:`AutumnDisabled`
|
||||
Raises if autumn is disabled
|
||||
|
||||
"""
|
||||
enabled = self.state.api_info["features"]["autumn"]["enabled"]
|
||||
|
||||
if not enabled:
|
||||
raise AutumnDisabled
|
||||
|
||||
base_url = self.state.api_info["features"]["autumn"]["url"]
|
||||
|
||||
return f"{base_url}/{self.tag}/{self.id}"
|
||||
|
||||
+22
-19
@@ -1,12 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from .messageable import Messageable
|
||||
from typing import TYPE_CHECKING, cast
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from .messageable import Messageable
|
||||
from .enums import ChannelType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .types import Channel as ChannelPayload
|
||||
from .types import Channel as ChannelPayload, SavedMessages as SavedMessagesPayload, DMChannel as DMChannelPayload, Group as GroupDMChannelPayload, TextChannel as TextChannelPayload
|
||||
from .state import State
|
||||
from .user import User
|
||||
|
||||
class Channel:
|
||||
"""Base class for all channels
|
||||
@@ -15,7 +17,7 @@ class Channel:
|
||||
-----------
|
||||
id: :class:`str`
|
||||
The id of the channel
|
||||
channel_type: Literal['SavedMessage', 'DirectMessage', 'Group', 'TextChannel', 'VoiceChannel']
|
||||
channel_type: ChannelType
|
||||
The type of the channel
|
||||
server: Optional[:class:`Server`]
|
||||
The server the channel is part of
|
||||
@@ -23,39 +25,40 @@ class Channel:
|
||||
def __init__(self, data: ChannelPayload, state: State):
|
||||
self.state = state
|
||||
self.id = data["_id"]
|
||||
self.channel_type = data["channel_type"]
|
||||
self.channel_type = ChannelType(data["channel_type"])
|
||||
self.server = None
|
||||
|
||||
class SavedMessageChannel(Channel, Messageable):
|
||||
"""The Saved Message Channel"""
|
||||
def __init__(self, data: ChannelPayload, state: State):
|
||||
def __init__(self, data: SavedMessagesPayload, state: State):
|
||||
super().__init__(data, state)
|
||||
|
||||
class DMChannel(Channel, Messageable):
|
||||
"""A DM channel"""
|
||||
def __init__(self, data: ChannelPayload, state: State):
|
||||
def __init__(self, data: DMChannelPayload, state: State):
|
||||
super().__init__(data, state)
|
||||
|
||||
class GroupDMChannel(Channel, Messageable):
|
||||
"""A group DM channel"""
|
||||
def __init__(self, data: ChannelPayload, state: State):
|
||||
def __init__(self, data: GroupDMChannelPayload, state: State):
|
||||
super().__init__(data, state)
|
||||
self.recipients = cast(list[User], list(filter(bool, [state.get_user(user_id) for user_id in data["recipients"]])))
|
||||
self.name = data["name"]
|
||||
self.owner = state.get_user(data["owner"])
|
||||
|
||||
class TextChannel(Channel, Messageable):
|
||||
"""A text channel"""
|
||||
def __init__(self, data: ChannelPayload, state: State):
|
||||
def __init__(self, data: TextChannelPayload, state: State):
|
||||
super().__init__(data, state)
|
||||
Messageable.__init__(self, state)
|
||||
|
||||
class PartialTextChannel(Messageable):
|
||||
"""A partial text channel, this will appear when the channel isnt cached"""
|
||||
def __init__(self, channel_id: str, state: State):
|
||||
super().__init__(state)
|
||||
|
||||
self.id = channel_id
|
||||
self.name = "Unknown Channel"
|
||||
self.channel_type = "TextChannel"
|
||||
self.server = None
|
||||
|
||||
self.server = state.get_server(data["server"])
|
||||
self.name = data["name"]
|
||||
self.description = data.get("description")
|
||||
|
||||
last_message_id = data.get("last_message")
|
||||
self.last_message = state.get_message(last_message_id)
|
||||
self.last_message_id = last_message_id
|
||||
|
||||
class VoiceChannel(Channel):
|
||||
"""A voice channel"""
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
# typing doesnt understand aenum so im pretending its stdlib enum while type checking
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import enum
|
||||
else:
|
||||
import aenum as enum
|
||||
|
||||
class ChannelType(enum.Enum):
|
||||
saved_message = "SavedMessage"
|
||||
direct_message = "DirectMessage"
|
||||
group = "Group"
|
||||
text_channel = "TextChannel"
|
||||
voice_channel = "VoiceChannel"
|
||||
|
||||
class PresenceType(enum.Enum):
|
||||
busy = "Busy"
|
||||
idle = "Idle"
|
||||
invisible = "Invisible"
|
||||
online = "Online"
|
||||
|
||||
class RelationshipType(enum.Enum):
|
||||
blocked = "Blocked"
|
||||
blocked_other = "BlockedOther"
|
||||
friend = "Friend"
|
||||
incoming_friend_request = "Incoming"
|
||||
none = "None"
|
||||
outgoing_friend_request = "Outgoing"
|
||||
user = "User"
|
||||
|
||||
class AssetType(enum.Enum):
|
||||
image = "Image"
|
||||
video = "Video"
|
||||
text = "Text"
|
||||
audio = "Audio"
|
||||
file = "File"
|
||||
@@ -6,3 +6,9 @@ class HTTPError(RevoltError):
|
||||
|
||||
class ServerError(RevoltError):
|
||||
"Internal server error"
|
||||
|
||||
class FeatureDisabled(RevoltError):
|
||||
"""Base class for any any feature disabled"""
|
||||
|
||||
class AutumnDisabled(FeatureDisabled):
|
||||
"""The autumn feature is disabled"""
|
||||
|
||||
+8
-5
@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from .asset import Asset
|
||||
from .embed import Embed
|
||||
from .channel import TextChannel, PartialTextChannel, Messageable
|
||||
from .channel import Messageable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .state import State
|
||||
@@ -38,13 +38,16 @@ class Message:
|
||||
self.attachments = [Asset(attachment, state) for attachment in data.get("attachments", [])]
|
||||
self.embeds = [Embed.from_dict(embed) for embed in data.get("embeds", [])]
|
||||
|
||||
channel = state.get_channel(data["channel"]) or PartialTextChannel(data["channel"], state)
|
||||
channel = state.get_channel(data["channel"])
|
||||
assert isinstance(channel, Messageable)
|
||||
self.channel = channel
|
||||
|
||||
self.server = self.channel and self.channel.server
|
||||
|
||||
if isinstance(self.channel, TextChannel) and self.server:
|
||||
self.author = state.get_member(self.server.id, data["author"])
|
||||
if self.server:
|
||||
author = state.get_member(self.server.id, data["author"])
|
||||
else:
|
||||
self.author = state.get_user(data["author"])
|
||||
author = state.get_user(data["author"])
|
||||
|
||||
assert author
|
||||
self.author = author
|
||||
|
||||
+4
-5
@@ -1,16 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from typing import TYPE_CHECKING, Optional, cast
|
||||
|
||||
from .permissions import Permissions
|
||||
from .channel import channel_factory
|
||||
from .role import Role
|
||||
from .channel import Channel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .types import Server as ServerPayload
|
||||
from .state import State
|
||||
from .member import Member
|
||||
from .role import Role
|
||||
from .channel import Channel
|
||||
|
||||
class Server:
|
||||
"""Represents a server
|
||||
@@ -33,7 +32,7 @@ class Server:
|
||||
|
||||
self._members: dict[str, Member] = {}
|
||||
self._roles: dict[str, Role] = {role_id: Role(role, role_id, state) for role_id, role in data.get("roles", {}).items()}
|
||||
channels = [channel_factory(channel, state) for channel in data.get("channels", [])]
|
||||
channels = cast(list[Channel], list(filter(bool, [state.get_channel(channel_id) for channel_id in data["channels"]])))
|
||||
self._channels: dict[str, Channel] = {channel.id: channel for channel in channels}
|
||||
|
||||
@property
|
||||
|
||||
@@ -75,6 +75,11 @@ class State:
|
||||
self.messages.appendleft(message)
|
||||
return message
|
||||
|
||||
def get_message(self, message_id: str) -> Optional[Message]:
|
||||
for msg in self.messages:
|
||||
if msg.id == message_id:
|
||||
return msg
|
||||
|
||||
async def fetch_all_server_members(self):
|
||||
for server_id in self.servers.keys():
|
||||
data = await self.http.get_server_members(server_id)
|
||||
|
||||
@@ -16,7 +16,7 @@ class SavedMessages(_NonceChannel, BaseChannel):
|
||||
user: str
|
||||
channel_type: Literal["SavedMessage"]
|
||||
|
||||
class DirectMessage(_NonceChannel, BaseChannel):
|
||||
class DMChannel(_NonceChannel, BaseChannel):
|
||||
active: bool
|
||||
recipients: list[str]
|
||||
last_message: Message
|
||||
@@ -42,7 +42,7 @@ class TextChannel(_NonceChannel, _TextChannelOptional, BaseChannel):
|
||||
server: str
|
||||
name: str
|
||||
description: str
|
||||
last_message: Message
|
||||
last_message: str
|
||||
channel_type: Literal["TextChannel"]
|
||||
|
||||
class _VoiceChannelOptional(TypedDict, total=False):
|
||||
@@ -56,4 +56,4 @@ class VoiceChannel(_NonceChannel, _TextChannelOptional, BaseChannel):
|
||||
description: str
|
||||
channel_type: Literal["VoiceChannel"]
|
||||
|
||||
Channel = Union[SavedMessages, DirectMessage, Group, TextChannel, VoiceChannel]
|
||||
Channel = Union[SavedMessages, DMChannel, Group, TextChannel, VoiceChannel]
|
||||
|
||||
@@ -28,5 +28,5 @@ class Server(TypedDict):
|
||||
_id: str
|
||||
owner: str
|
||||
name: str
|
||||
channels: list[Channel]
|
||||
channels: list[str]
|
||||
default_permissions: Permission
|
||||
|
||||
+11
-3
@@ -5,19 +5,27 @@ from typing import TYPE_CHECKING, TypedDict, Literal
|
||||
if TYPE_CHECKING:
|
||||
from .file import File
|
||||
|
||||
Relation = Literal["Blocked", "BlockedOther", "Friend", "Incoming", "None", "Outgoing", "User"]
|
||||
|
||||
class UserBot(TypedDict):
|
||||
owner: str
|
||||
|
||||
class Status(TypedDict, total=False):
|
||||
class _OptionalStatus(TypedDict, total=False):
|
||||
text: str
|
||||
|
||||
class Status(_OptionalStatus):
|
||||
presence: Literal["Busy", "Idle", "Invisible", "Online"]
|
||||
|
||||
class UserRelation(TypedDict):
|
||||
status: Relation
|
||||
_id: str
|
||||
|
||||
class _OptionalUser(TypedDict, total=False):
|
||||
avatar: File
|
||||
relations: list
|
||||
relations: list[UserRelation]
|
||||
badges: int
|
||||
status: Status
|
||||
relationship: Literal["Blocked", "BlockedOther", "Friend", "Incoming", "None", "Outgoing", "User"]
|
||||
relationship: Relation
|
||||
online: bool
|
||||
flags: int
|
||||
bot: UserBot
|
||||
|
||||
+24
-5
@@ -1,10 +1,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, NamedTuple, Optional
|
||||
|
||||
from .asset import Asset
|
||||
from .enums import RelationshipType, PresenceType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .state import State
|
||||
from .types import User as UserPayload
|
||||
from .types import User as UserPayload, UserRelation
|
||||
|
||||
class Relation(NamedTuple):
|
||||
type: RelationshipType
|
||||
user: Optional["User"]
|
||||
|
||||
class Status(NamedTuple):
|
||||
text: Optional[str]
|
||||
presence: PresenceType
|
||||
|
||||
class User:
|
||||
"""Represents a user
|
||||
@@ -25,6 +36,8 @@ class User:
|
||||
Whether or not the user is online
|
||||
flags: :class:`int`
|
||||
The user flags
|
||||
avatar: :class:`Asset`
|
||||
The users avatar if they have one
|
||||
"""
|
||||
__flattern_attributes__ = ("id", "name", "bot", "owner", "badges", "online", "flags")
|
||||
|
||||
@@ -45,6 +58,12 @@ class User:
|
||||
self.online = data.get("online", False)
|
||||
self.flags = data.get("flags", 0)
|
||||
|
||||
# avatar
|
||||
# relations
|
||||
# relationship
|
||||
avatar = data.get("avatar")
|
||||
self.avatar = Asset(avatar, state) if avatar else None
|
||||
|
||||
self.relations = [Relation(RelationshipType(relation["status"]), state.get_user(relation["_id"])) for relation in data.get("relations", [])]
|
||||
relationship = data.get("relationship")
|
||||
self.relationship = RelationshipType(relationship) if relationship else None
|
||||
|
||||
status = data.get("status")
|
||||
self.status = Status(status.get("text"), PresenceType(status["presence"])) if status else None
|
||||
|
||||
+3
-2
@@ -32,6 +32,7 @@ class WebsocketHandler:
|
||||
self.dispatch = dispatch
|
||||
self.state = state
|
||||
self.websocket: aiohttp.ClientWebSocketResponse
|
||||
self.loop = asyncio.get_running_loop()
|
||||
|
||||
async def send_payload(self, payload: BasePayload):
|
||||
if use_msgpack:
|
||||
@@ -42,7 +43,7 @@ class WebsocketHandler:
|
||||
async def heartbeat(self):
|
||||
while not self.websocket.closed:
|
||||
logger.info("Sending hearbeat")
|
||||
await self.send_payload({"type": "Ping"})
|
||||
await self.websocket.ping()
|
||||
await asyncio.sleep(15)
|
||||
|
||||
async def send_authenticate(self):
|
||||
@@ -104,4 +105,4 @@ class WebsocketHandler:
|
||||
else:
|
||||
payload = json.loads(msg.data)
|
||||
|
||||
await self.handle_event(payload)
|
||||
self.loop.create_task(self.handle_event(payload))
|
||||
|
||||
Reference in New Issue
Block a user