From e1f1df154d74bf136a669c58e49467c74a485cf5 Mon Sep 17 00:00:00 2001 From: Gabe Date: Sat, 23 Nov 2024 15:53:47 -0800 Subject: [PATCH 1/3] Added functionality to get stream destination info (stream url and key) --- kick/client.py | 24 +++++++++++++++++++++++- kick/http.py | 32 ++++++++++++++++++++++++-------- kick/types/user.py | 5 +++++ kick/users.py | 29 +++++++++++++++++++++++++++-- 4 files changed, 79 insertions(+), 11 deletions(-) diff --git a/kick/client.py b/kick/client.py index 874fd75..d6300a6 100644 --- a/kick/client.py +++ b/kick/client.py @@ -10,7 +10,7 @@ from .http import HTTPClient from .livestream import PartialLivestream from .message import Message -from .users import ClientUser, PartialUser, User +from .users import ClientUser, PartialUser, User, DestinationInfo from .utils import MISSING, decorator, setup_logging if TYPE_CHECKING: @@ -213,6 +213,28 @@ async def fetch_user(self, name: str, /) -> User: user = User(data=data, http=self.http) return user + async def fetch_stream_url_and_key(self) -> DestinationInfo: + """ + |coro| + + Fetches your stream URL and stream key from the API. + You must be authenticated to use this endpoint. + + Raises + ----------- + HTTPException + Fetching Failed + Forbidden + You are not authenticated + + Returns + ----------- + str + """ + + data = await self.http.get_stream_destination_url_and_key() + return DestinationInfo(data=data) + def dispatch(self, event_name: str, *args, **kwargs) -> None: event_name = f"on_{event_name}" diff --git a/kick/http.py b/kick/http.py index 57df00c..ed0d6b6 100644 --- a/kick/http.py +++ b/kick/http.py @@ -43,7 +43,12 @@ ReplyOriginalSender, V1MessageSentPayload, ) - from .types.user import ChatterPayload, ClientUserPayload, UserPayload + from .types.user import ( + ChatterPayload, + ClientUserPayload, + UserPayload, + DestinationInfoPayload, + ) from .types.videos import GetVideosPayload T = TypeVar("T") @@ -60,10 +65,9 @@ async def json_or_text(response: ClientResponse, /) -> Union[dict[str, Any], str]: text = await response.text() try: - try: - return json.loads(text) - except json.JSONDecodeError: - pass + return json.loads(text) + except json.JSONDecodeError: + pass except KeyError: pass @@ -238,9 +242,11 @@ async def request(self, route: Route, **kwargs) -> Any: try: res = await self.__session.request( route.method, - url - if self.whitelisted is True - else f"{self.bypass_host}:{self.bypass_port}/request?url={url}", + ( + url + if self.whitelisted is True + else f"{self.bypass_host}:{self.bypass_port}/request?url={url}" + ), headers=headers, cookies=cookies, **kwargs, @@ -488,6 +494,16 @@ def reply_to_message( def get_me(self) -> Response[ClientUserPayload]: return self.request(Route.root("GET", "/api/v1/user")) + def get_stream_destination_url_and_key(self) -> Response[DestinationInfoPayload]: + """Gets the authenticated user's stream URL and key. + + Returns + ------- + StreamURLKeyPayload + The stream URL and key information containing the publish URL and token + """ + return self.request(Route.root("GET", "/stream/publish_token")) + async def get_asset(self, url: str) -> bytes: if self.__session is MISSING: self.__session = ClientSession() diff --git a/kick/types/user.py b/kick/types/user.py index c292199..bd7a1d9 100644 --- a/kick/types/user.py +++ b/kick/types/user.py @@ -140,3 +140,8 @@ class ClientUserPayload(TypedDict): streamer_channel: ClientUserStreamerChannelsPayload roles: list # Unknown profilepic: str | None + + +class DestinationInfoPayload(TypedDict): + rtmp_publish_path: str + rtmp_stream_token: str diff --git a/kick/users.py b/kick/users.py index 9011c0f..790fd50 100644 --- a/kick/users.py +++ b/kick/users.py @@ -16,9 +16,34 @@ if TYPE_CHECKING: from .chatroom import Chatroom from .http import HTTPClient - from .types.user import ClientUserPayload, InnerUser, UserPayload + from .types.user import ClientUserPayload, InnerUser, UserPayload, DestinationInfoPayload, StreamInfoPayload + +__all__ = ("DestinationInfo", "StreamInfo", "Socials", "PartialUser", "User", "ClientUser") + + +class DestinationInfo(BaseDataclass["DestinationInfoPayload"]): + """ + Information about a user's stream destination + + Attributes + ----------- + stream_url: str + The URL for streaming + stream_key: str + The stream key + """ + + @property + def stream_url(self) -> str: + """The URL for streaming""" + return self._data["rtmp_publish_path"] + + @property + def stream_key(self) -> str: + """The stream key""" + return self._data["rtmp_stream_token"] + -__all__ = ("User", "Socials", "PartialUser", "ClientUser") class Socials(BaseDataclass["InnerUser | ClientUserPayload"]): From 361547b253da9134ec209f21fc6b5ebaed89a8cd Mon Sep 17 00:00:00 2001 From: Gabe Date: Sun, 24 Nov 2024 03:10:11 -0800 Subject: [PATCH 2/3] addressed PR comments --- kick/client.py | 2 +- kick/http.py | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/kick/client.py b/kick/client.py index d6300a6..db7b356 100644 --- a/kick/client.py +++ b/kick/client.py @@ -229,7 +229,7 @@ async def fetch_stream_url_and_key(self) -> DestinationInfo: Returns ----------- - str + DesetinationInfo """ data = await self.http.get_stream_destination_url_and_key() diff --git a/kick/http.py b/kick/http.py index ed0d6b6..ac5359f 100644 --- a/kick/http.py +++ b/kick/http.py @@ -495,13 +495,6 @@ def get_me(self) -> Response[ClientUserPayload]: return self.request(Route.root("GET", "/api/v1/user")) def get_stream_destination_url_and_key(self) -> Response[DestinationInfoPayload]: - """Gets the authenticated user's stream URL and key. - - Returns - ------- - StreamURLKeyPayload - The stream URL and key information containing the publish URL and token - """ return self.request(Route.root("GET", "/stream/publish_token")) async def get_asset(self, url: str) -> bytes: From 2672c85bdc9f972d9840b1c416f7c1ab6e80c371 Mon Sep 17 00:00:00 2001 From: Gabe Date: Sun, 24 Nov 2024 03:18:28 -0800 Subject: [PATCH 3/3] typo fix --- kick/client.py | 4 ++-- kick/http.py | 2 +- kick/users.py | 11 +++++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/kick/client.py b/kick/client.py index db7b356..70c24db 100644 --- a/kick/client.py +++ b/kick/client.py @@ -229,10 +229,10 @@ async def fetch_stream_url_and_key(self) -> DestinationInfo: Returns ----------- - DesetinationInfo + DestinationInfo """ - data = await self.http.get_stream_destination_url_and_key() + data = await self.http.fetch_stream_destination_url_and_key() return DestinationInfo(data=data) def dispatch(self, event_name: str, *args, **kwargs) -> None: diff --git a/kick/http.py b/kick/http.py index ac5359f..8343e8b 100644 --- a/kick/http.py +++ b/kick/http.py @@ -494,7 +494,7 @@ def reply_to_message( def get_me(self) -> Response[ClientUserPayload]: return self.request(Route.root("GET", "/api/v1/user")) - def get_stream_destination_url_and_key(self) -> Response[DestinationInfoPayload]: + def fetch_stream_destination_url_and_key(self) -> Response[DestinationInfoPayload]: return self.request(Route.root("GET", "/stream/publish_token")) async def get_asset(self, url: str) -> bytes: diff --git a/kick/users.py b/kick/users.py index 790fd50..6fa4545 100644 --- a/kick/users.py +++ b/kick/users.py @@ -16,9 +16,10 @@ if TYPE_CHECKING: from .chatroom import Chatroom from .http import HTTPClient - from .types.user import ClientUserPayload, InnerUser, UserPayload, DestinationInfoPayload, StreamInfoPayload + from .types.user import (ClientUserPayload, InnerUser, UserPayload, + DestinationInfoPayload) -__all__ = ("DestinationInfo", "StreamInfo", "Socials", "PartialUser", "User", "ClientUser") +__all__ = ("DestinationInfo", "Socials", "PartialUser", "User", "ClientUser") class DestinationInfo(BaseDataclass["DestinationInfoPayload"]): @@ -32,20 +33,18 @@ class DestinationInfo(BaseDataclass["DestinationInfoPayload"]): stream_key: str The stream key """ - + @property def stream_url(self) -> str: """The URL for streaming""" return self._data["rtmp_publish_path"] - + @property def stream_key(self) -> str: """The stream key""" return self._data["rtmp_stream_token"] - - class Socials(BaseDataclass["InnerUser | ClientUserPayload"]): """ The socials a user on kick has added to their profile