Skip to content

Commit

Permalink
feat(livekit): inital livekit support
Browse files Browse the repository at this point in the history
  • Loading branch information
Zomatree committed Oct 29, 2024
1 parent bc8d650 commit 8e3cd77
Show file tree
Hide file tree
Showing 12 changed files with 122 additions and 12 deletions.
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ docs = [
"sphinx-toolbox==3.2.*",
"setuptools==65.4.*"
]
voice = [
"livekit==0.11.1"
]

[project.urls]
Homepage = "https://github.com/revoltchat/revolt.py"
Expand Down
1 change: 1 addition & 0 deletions revolt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@
from .role import *
from .server import *
from .user import *
from .voice import *

__version__ = "0.2.0"
29 changes: 27 additions & 2 deletions revolt/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

from typing import TYPE_CHECKING, Any, Optional, Union


from .asset import Asset
from .enums import ChannelType
from .messageable import Messageable
from .permissions import Permissions, PermissionsOverwrite
from .utils import Missing, Ulid
from .voice import VoiceInformation, VoiceState
from .errors import NotAVoiceChannel

if TYPE_CHECKING:
from .message import Message
Expand Down Expand Up @@ -65,6 +68,24 @@ async def edit(self, **kwargs: Any) -> None:

await self.state.http.edit_channel(self.id, remove, kwargs)

class Joinable:
__slots__ = ()

state: State
id: str

async def join(self) -> VoiceState:
if isinstance(self, TextChannel):
if self.voice is None:
raise NotAVoiceChannel()

token = await self.state.http.join_voice_channel(self.id)

state = VoiceState(self.state, token["token"])
await state.connect()

return state

class Channel(Ulid):
"""Base class for all channels
Expand Down Expand Up @@ -337,7 +358,7 @@ def _update(self, *, name: Optional[str] = None, description: Optional[str] = No
if default_permissions is not None:
self.default_permissions = PermissionsOverwrite._from_overwrite(default_permissions)

class TextChannel(ServerChannel, Messageable, EditableChannel):
class TextChannel(ServerChannel, Messageable, EditableChannel, Joinable):
"""A text channel
Subclasses :class:`ServerChannel` and :class:`Messageable`
Expand Down Expand Up @@ -366,6 +387,10 @@ def __init__(self, data: TextChannelPayload, state: State):
super().__init__(data, state)

self.last_message_id: str | None = data.get("last_message_id")
self.voice: VoiceInformation | None = None

if voice := data.get("voice"):
self.voice = VoiceInformation(voice)

async def _get_channel_id(self) -> str:
return self.id
Expand All @@ -384,7 +409,7 @@ def last_message(self) -> Message:

return self.state.get_message(self.last_message_id)

class VoiceChannel(ServerChannel, EditableChannel):
class VoiceChannel(ServerChannel, EditableChannel, Joinable):
"""A voice channel
Subclasses :class:`ServerChannel`
Expand Down
7 changes: 5 additions & 2 deletions revolt/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,18 @@ async def get_api_info(self) -> ApiInfo:
except:
raise RevoltError(f"Cant fetch api info:\n{text}")

async def start(self, *, reconnect: bool = True) -> None:
"""Starts the client"""
async def connect(self):
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.bot)
self.state = State(self.http, api_info, self.max_messages)
self.websocket = WebsocketHandler(self.session, self.token, api_info["ws"], self.dispatch, self.state)

async def start(self, *, reconnect: bool = True) -> None:
"""Starts the client"""

await self.connect()
await self.websocket.start(reconnect)

async def stop(self) -> None:
Expand Down
3 changes: 3 additions & 0 deletions revolt/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ class AutumnDisabled(FeatureDisabled):

class Forbidden(HTTPError):
"Missing permissions"

class NotAVoiceChannel(RevoltError):
"Channel is not a voice channel"
10 changes: 8 additions & 2 deletions revolt/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@
from .types import SendableEmbed as SendableEmbedPayload
from .types import User as UserPayload
from .types import (Server, ServerBans, TextChannel, UserProfile, VoiceChannel, Member, Invite, ApiInfo, Channel, SavedMessages,
DMChannel, EmojiParent, GetServerMembers, GroupDMChannel, MessageReplyPayload, MessageWithUserData, PartialInvite, CreateRole)
DMChannel, EmojiParent, GetServerMembers, GroupDMChannel, MessageReplyPayload, MessageWithUserData, PartialInvite,
CreateRole, JoinVoiceChannelPayload)

from aiohttp.client import _RequestOptions

Check failure on line 35 in revolt/http.py

View workflow job for this annotation

GitHub Actions / pyright-type-checking (3.9)

"_RequestOptions" is unknown import symbol (reportAttributeAccessIssue)

Check failure on line 35 in revolt/http.py

View workflow job for this annotation

GitHub Actions / pyright-type-checking (3.9)

Type of "_RequestOptions" is unknown (reportUnknownVariableType)

Check failure on line 35 in revolt/http.py

View workflow job for this annotation

GitHub Actions / pyright-type-checking (3.10)

"_RequestOptions" is unknown import symbol (reportAttributeAccessIssue)

Check failure on line 35 in revolt/http.py

View workflow job for this annotation

GitHub Actions / pyright-type-checking (3.10)

Type of "_RequestOptions" is unknown (reportUnknownVariableType)

__all__ = ("HttpClient",)

Expand All @@ -49,7 +52,7 @@ def __init__(self, session: aiohttp.ClientSession, token: str, api_url: str, api
async def request(self, method: Literal["GET", "POST", "PUT", "DELETE", "PATCH"], route: str, *, json: Optional[dict[str, Any]] = None, nonce: bool = True, params: Optional[dict[str, Any]] = None) -> Any:
url = f"{self.api_url}{route}"

kwargs = {}
kwargs: _RequestOptions = {}

Check failure on line 55 in revolt/http.py

View workflow job for this annotation

GitHub Actions / pyright-type-checking (3.9)

Type of "kwargs" is unknown (reportUnknownVariableType)

Check failure on line 55 in revolt/http.py

View workflow job for this annotation

GitHub Actions / pyright-type-checking (3.10)

Type of "kwargs" is unknown (reportUnknownVariableType)

headers = {
"User-Agent": "Revolt.py (https://github.com/revoltchat/revolt.py)",
Expand Down Expand Up @@ -430,3 +433,6 @@ def edit_member(self, server_id: str, member_id: str, remove: list[str] | None,

def delete_messages(self, channel_id: str, messages: list[str]) -> Request[None]:
return self.request("DELETE", f"/channels/{channel_id}/messages/bulk", json={"ids": messages})

def join_voice_channel(self, channel_id: str) -> Request[JoinVoiceChannelPayload]:
return self.request("POST", f"/channels/{channel_id}/join_call")
1 change: 1 addition & 0 deletions revolt/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
from .role import *
from .server import *
from .user import *
from .voice import *
4 changes: 4 additions & 0 deletions revolt/types/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

from typing_extensions import NotRequired


if TYPE_CHECKING:
from .file import File
from .permissions import Overwrite
from .voice import VoiceInformation

__all__ = (
"SavedMessages",
Expand Down Expand Up @@ -53,6 +55,8 @@ class TextChannel(BaseChannel):
role_permissions: NotRequired[dict[str, Overwrite]]
nsfw: NotRequired[bool]
last_message_id: NotRequired[str]
voice: NotRequired[VoiceInformation]


class VoiceChannel(BaseChannel):
server: str
Expand Down
2 changes: 2 additions & 0 deletions revolt/types/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from .member import Member, MemberID
from .server import Server, SystemMessagesConfig
from .user import Status, User, UserProfile, UserRelation
from .voice import ChannelVoiceState

__all__ = (
"BasePayload",
Expand Down Expand Up @@ -58,6 +59,7 @@ class ReadyEventPayload(BasePayload):
channels: list[Channel]
members: list[Member]
emojis: list[Emoji]
voice_states: list[ChannelVoiceState]

class MessageEventPayload(BasePayload, Message):
pass
Expand Down
13 changes: 7 additions & 6 deletions revolt/types/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,27 @@


__all__ = (
"VosoFeature",
"ApiInfo",
"Autumn",
"GetServerMembers",
"MessageWithUserData",
"CreateRole",
"JoinVoiceChannelPayload"
)


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
livekit: ApiFeature


class ApiInfo(TypedDict):
revolt: str
Expand All @@ -56,4 +54,7 @@ class MessageWithUserData(TypedDict):

class CreateRole(TypedDict):
id: str
role: Role
role: Role

class JoinVoiceChannelPayload(TypedDict):
token: str
24 changes: 24 additions & 0 deletions revolt/types/voice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from __future__ import annotations

from typing import TypedDict

from typing_extensions import NotRequired

__all__ = (
"VoiceInformation",
"ChannelVoiceState"
)

class VoiceInformation(TypedDict):
max_users: NotRequired[int]

class ChannelVoiceState(TypedDict):
id: str
participants: list[UserVoiceState]

class UserVoiceState(TypedDict):
id: str
can_receive: bool
can_publish: bool
screensharing: bool
camera: bool
37 changes: 37 additions & 0 deletions revolt/voice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from __future__ import annotations

from typing import TYPE_CHECKING
import livekit

Check failure on line 4 in revolt/voice.py

View workflow job for this annotation

GitHub Actions / pyright-type-checking (3.9)

Import "livekit" could not be resolved (reportMissingImports)

Check failure on line 4 in revolt/voice.py

View workflow job for this annotation

GitHub Actions / pyright-type-checking (3.10)

Import "livekit" could not be resolved (reportMissingImports)
import livekit.rtc

Check failure on line 5 in revolt/voice.py

View workflow job for this annotation

GitHub Actions / pyright-type-checking (3.9)

Import "livekit.rtc" could not be resolved (reportMissingImports)

Check failure on line 5 in revolt/voice.py

View workflow job for this annotation

GitHub Actions / pyright-type-checking (3.10)

Import "livekit.rtc" could not be resolved (reportMissingImports)


if TYPE_CHECKING:
from .types import VoiceInformation as VoiceInformationPayload
from .state import State


__all__ = (
"VoiceInformation",
"VoiceState"
)


class VoiceInformation:
"""Holds information on the voice configuration of the text channel
Attributes
-----------
max_users: Optional[:class:`int`]
How many users can be in the voice at once
"""
def __init__(self, data: VoiceInformationPayload) -> None:
self.max_users: int | None = data.get("max_users", None)

class VoiceState:
def __init__(self, state: State, token: str) -> None:
self.state = state
self.token = token
self.room = livekit.rtc.Room()

Check failure on line 34 in revolt/voice.py

View workflow job for this annotation

GitHub Actions / pyright-type-checking (3.9)

Type of "room" is unknown (reportUnknownMemberType)

Check failure on line 34 in revolt/voice.py

View workflow job for this annotation

GitHub Actions / pyright-type-checking (3.9)

Type of "rtc" is unknown (reportUnknownMemberType)

Check failure on line 34 in revolt/voice.py

View workflow job for this annotation

GitHub Actions / pyright-type-checking (3.9)

Type of "Room" is unknown (reportUnknownMemberType)

Check failure on line 34 in revolt/voice.py

View workflow job for this annotation

GitHub Actions / pyright-type-checking (3.10)

Type of "room" is unknown (reportUnknownMemberType)

Check failure on line 34 in revolt/voice.py

View workflow job for this annotation

GitHub Actions / pyright-type-checking (3.10)

Type of "rtc" is unknown (reportUnknownMemberType)

Check failure on line 34 in revolt/voice.py

View workflow job for this annotation

GitHub Actions / pyright-type-checking (3.10)

Type of "Room" is unknown (reportUnknownMemberType)

async def connect(self):
await self.room.connect(self.state.api_info["features"]["livekit"]["url"], self.token)

0 comments on commit 8e3cd77

Please sign in to comment.