diff --git a/fishjam/_openapi_client/api/moq/__init__.py b/fishjam/_openapi_client/api/moq/__init__.py new file mode 100644 index 0000000..2d7c0b2 --- /dev/null +++ b/fishjam/_openapi_client/api/moq/__init__.py @@ -0,0 +1 @@ +"""Contains endpoint functions for accessing the API""" diff --git a/fishjam/_openapi_client/api/moq/create_moq_publisher_token.py b/fishjam/_openapi_client/api/moq/create_moq_publisher_token.py new file mode 100644 index 0000000..33c7677 --- /dev/null +++ b/fishjam/_openapi_client/api/moq/create_moq_publisher_token.py @@ -0,0 +1,165 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.error import Error +from ...models.moq_token import MoqToken +from ...types import Response + + +def _get_kwargs( + stream_id: str, +) -> dict[str, Any]: + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/moq/{stream_id}/publisher".format( + stream_id=quote(str(stream_id), safe=""), + ), + } + + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Error | MoqToken | None: + if response.status_code == 200: + response_200 = MoqToken.from_dict(response.json()) + + return response_200 + + if response.status_code == 401: + response_401 = Error.from_dict(response.json()) + + return response_401 + + if response.status_code == 503: + response_503 = Error.from_dict(response.json()) + + return response_503 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[Error | MoqToken]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + stream_id: str, + *, + client: AuthenticatedClient, +) -> Response[Error | MoqToken]: + """Creates a MoQ publisher token for the given stream + + Args: + stream_id (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Error | MoqToken] + """ + + kwargs = _get_kwargs( + stream_id=stream_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + stream_id: str, + *, + client: AuthenticatedClient, +) -> Error | MoqToken | None: + """Creates a MoQ publisher token for the given stream + + Args: + stream_id (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Error | MoqToken + """ + + return sync_detailed( + stream_id=stream_id, + client=client, + ).parsed + + +async def asyncio_detailed( + stream_id: str, + *, + client: AuthenticatedClient, +) -> Response[Error | MoqToken]: + """Creates a MoQ publisher token for the given stream + + Args: + stream_id (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Error | MoqToken] + """ + + kwargs = _get_kwargs( + stream_id=stream_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + stream_id: str, + *, + client: AuthenticatedClient, +) -> Error | MoqToken | None: + """Creates a MoQ publisher token for the given stream + + Args: + stream_id (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Error | MoqToken + """ + + return ( + await asyncio_detailed( + stream_id=stream_id, + client=client, + ) + ).parsed diff --git a/fishjam/_openapi_client/api/moq/create_moq_subscriber_token.py b/fishjam/_openapi_client/api/moq/create_moq_subscriber_token.py new file mode 100644 index 0000000..c7089d8 --- /dev/null +++ b/fishjam/_openapi_client/api/moq/create_moq_subscriber_token.py @@ -0,0 +1,165 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.error import Error +from ...models.moq_token import MoqToken +from ...types import Response + + +def _get_kwargs( + stream_id: str, +) -> dict[str, Any]: + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/moq/{stream_id}/subscriber".format( + stream_id=quote(str(stream_id), safe=""), + ), + } + + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Error | MoqToken | None: + if response.status_code == 200: + response_200 = MoqToken.from_dict(response.json()) + + return response_200 + + if response.status_code == 401: + response_401 = Error.from_dict(response.json()) + + return response_401 + + if response.status_code == 503: + response_503 = Error.from_dict(response.json()) + + return response_503 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[Error | MoqToken]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + stream_id: str, + *, + client: AuthenticatedClient, +) -> Response[Error | MoqToken]: + """Creates a MoQ subscriber token for the given stream + + Args: + stream_id (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Error | MoqToken] + """ + + kwargs = _get_kwargs( + stream_id=stream_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + stream_id: str, + *, + client: AuthenticatedClient, +) -> Error | MoqToken | None: + """Creates a MoQ subscriber token for the given stream + + Args: + stream_id (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Error | MoqToken + """ + + return sync_detailed( + stream_id=stream_id, + client=client, + ).parsed + + +async def asyncio_detailed( + stream_id: str, + *, + client: AuthenticatedClient, +) -> Response[Error | MoqToken]: + """Creates a MoQ subscriber token for the given stream + + Args: + stream_id (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Error | MoqToken] + """ + + kwargs = _get_kwargs( + stream_id=stream_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + stream_id: str, + *, + client: AuthenticatedClient, +) -> Error | MoqToken | None: + """Creates a MoQ subscriber token for the given stream + + Args: + stream_id (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Error | MoqToken + """ + + return ( + await asyncio_detailed( + stream_id=stream_id, + client=client, + ) + ).parsed diff --git a/fishjam/_openapi_client/models/__init__.py b/fishjam/_openapi_client/models/__init__.py index b4636bf..a7ed34e 100644 --- a/fishjam/_openapi_client/models/__init__.py +++ b/fishjam/_openapi_client/models/__init__.py @@ -5,6 +5,7 @@ from .audio_sample_rate import AudioSampleRate from .composition_info import CompositionInfo from .error import Error +from .moq_token import MoqToken from .peer import Peer from .peer_config import PeerConfig from .peer_details_response import PeerDetailsResponse @@ -52,6 +53,7 @@ "AudioSampleRate", "CompositionInfo", "Error", + "MoqToken", "Peer", "PeerConfig", "PeerDetailsResponse", diff --git a/fishjam/_openapi_client/models/moq_token.py b/fishjam/_openapi_client/models/moq_token.py new file mode 100644 index 0000000..d1f04d9 --- /dev/null +++ b/fishjam/_openapi_client/models/moq_token.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="MoqToken") + + +@_attrs_define +class MoqToken: + """Token for authorizing a MoQ relay connection + + Attributes: + token (str): JWT token for MoQ relay Example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb290IjoiZmlzaGphbSIsInB1d + CI6WyJteS1zdHJlYW0iXSwiZ2V0IjpbXSwiaWF0IjoxNzEzMzYwMDAwLCJleHAiOjE3MTMzNjM2MDB9.abc123. + """ + + token: str + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + token = self.token + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({ + "token": token, + }) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + token = d.pop("token") + + moq_token = cls( + token=token, + ) + + moq_token.additional_properties = d + return moq_token + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/fishjam/api/_fishjam_client.py b/fishjam/api/_fishjam_client.py index fa61f66..79b8a72 100644 --- a/fishjam/api/_fishjam_client.py +++ b/fishjam/api/_fishjam_client.py @@ -3,6 +3,12 @@ from dataclasses import dataclass, field from typing import Any, Literal, cast +from fishjam._openapi_client.api.moq import ( + create_moq_publisher_token as moq_create_publisher_token, +) +from fishjam._openapi_client.api.moq import ( + create_moq_subscriber_token as moq_create_subscriber_token, +) from fishjam._openapi_client.api.room import add_peer as room_add_peer from fishjam._openapi_client.api.room import create_room as room_create_room from fishjam._openapi_client.api.room import delete_peer as room_delete_peer @@ -22,6 +28,7 @@ AgentOutput, AudioFormat, AudioSampleRate, + MoqToken, Peer, PeerConfig, PeerDetailsResponse, @@ -362,6 +369,38 @@ def create_livestream_streamer_token(self, room_id: str) -> str: return response.token + def create_moq_publisher_token(self, stream_id: str) -> str: + """Generates a MoQ publisher token for the given stream. + + Args: + stream_id: The name of the MoQ stream. + + Returns: + str: The generated publisher token. + """ + response = cast( + MoqToken, + self._request(moq_create_publisher_token, stream_id=stream_id), + ) + + return response.token + + def create_moq_subscriber_token(self, stream_id: str) -> str: + """Generates a MoQ subscriber token for the given stream. + + Args: + stream_id: The name of the MoQ stream. + + Returns: + str: The generated subscriber token. + """ + response = cast( + MoqToken, + self._request(moq_create_subscriber_token, stream_id=stream_id), + ) + + return response.token + def subscribe_peer(self, room_id: str, peer_id: str, target_peer_id: str): """Subscribes a peer to all tracks of another peer. diff --git a/tests/test_room_api.py b/tests/test_room_api.py index 36e9e77..6c159fe 100644 --- a/tests/test_room_api.py +++ b/tests/test_room_api.py @@ -318,3 +318,29 @@ def test_invalid(self, room_api: FishjamClient): with pytest.raises(NotFoundError): room_api.create_livestream_streamer_token(room.id) + + +class TestCreateMoqPublisherToken: + def test_valid(self, room_api: FishjamClient): + token = room_api.create_moq_publisher_token("test-stream") + + assert isinstance(token, str) + + def test_unauthorized(self): + room_api = FishjamClient(FISHJAM_ID, "invalid") + + with pytest.raises(UnauthorizedError): + room_api.create_moq_publisher_token("test-stream") + + +class TestCreateMoqSubscriberToken: + def test_valid(self, room_api: FishjamClient): + token = room_api.create_moq_subscriber_token("test-stream") + + assert isinstance(token, str) + + def test_unauthorized(self): + room_api = FishjamClient(FISHJAM_ID, "invalid") + + with pytest.raises(UnauthorizedError): + room_api.create_moq_subscriber_token("test-stream")