chzzk-python 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
chzzk/__init__.py ADDED
@@ -0,0 +1,175 @@
1
+ """Chzzk Python SDK - Unofficial Python SDK for Chzzk streaming platform."""
2
+
3
+ from chzzk.api import (
4
+ AsyncCategoryService,
5
+ AsyncChannelService,
6
+ AsyncChatService,
7
+ AsyncLiveService,
8
+ AsyncRestrictionService,
9
+ AsyncSessionService,
10
+ AsyncUserService,
11
+ CategoryService,
12
+ ChannelService,
13
+ ChatService,
14
+ LiveService,
15
+ RestrictionService,
16
+ SessionService,
17
+ UserService,
18
+ )
19
+ from chzzk.auth import (
20
+ AsyncChzzkOAuth,
21
+ CallbackTokenStorage,
22
+ ChzzkOAuth,
23
+ FileTokenStorage,
24
+ InMemoryTokenStorage,
25
+ Token,
26
+ TokenStorage,
27
+ )
28
+ from chzzk.client import AsyncChzzkClient, ChzzkClient
29
+ from chzzk.exceptions import (
30
+ AuthenticationError,
31
+ ChzzkAPIError,
32
+ ChzzkError,
33
+ EventSubscriptionError,
34
+ ForbiddenError,
35
+ InvalidClientError,
36
+ InvalidStateError,
37
+ InvalidTokenError,
38
+ NotFoundError,
39
+ RateLimitError,
40
+ ServerError,
41
+ SessionConnectionError,
42
+ SessionError,
43
+ SessionLimitExceededError,
44
+ TokenExpiredError,
45
+ )
46
+ from chzzk.models import (
47
+ Badge,
48
+ Category,
49
+ CategoryType,
50
+ ChannelInfo,
51
+ ChannelManager,
52
+ ChatAvailableCondition,
53
+ ChatAvailableGroup,
54
+ ChatEvent,
55
+ ChatMessageResponse,
56
+ ChatProfile,
57
+ ChatSettings,
58
+ DonationEvent,
59
+ DonationType,
60
+ EventType,
61
+ Follower,
62
+ LiveInfo,
63
+ LiveListResponse,
64
+ LiveSetting,
65
+ LiveSettingCategory,
66
+ Page,
67
+ RestrictedChannel,
68
+ SessionAuthResponse,
69
+ SessionInfo,
70
+ SessionListResponse,
71
+ StreamKey,
72
+ SubscribedEvent,
73
+ Subscriber,
74
+ SubscriberSortType,
75
+ SubscriptionEvent,
76
+ SystemEvent,
77
+ SystemEventData,
78
+ SystemMessageType,
79
+ UpdateChatSettingsRequest,
80
+ UpdateLiveSettingRequest,
81
+ UserInfo,
82
+ UserRole,
83
+ UserRoleCode,
84
+ )
85
+ from chzzk.realtime import AsyncChzzkEventClient, ChzzkEventClient
86
+
87
+ try:
88
+ from chzzk._version import __version__
89
+ except ImportError:
90
+ __version__ = "0.0.0.dev0" # 개발 환경 fallback
91
+
92
+ __all__ = [
93
+ # Clients
94
+ "AsyncChzzkClient",
95
+ "AsyncChzzkEventClient",
96
+ "ChzzkClient",
97
+ "ChzzkEventClient",
98
+ # Services
99
+ "AsyncCategoryService",
100
+ "AsyncChannelService",
101
+ "AsyncChatService",
102
+ "AsyncLiveService",
103
+ "AsyncRestrictionService",
104
+ "AsyncSessionService",
105
+ "AsyncUserService",
106
+ "CategoryService",
107
+ "ChannelService",
108
+ "ChatService",
109
+ "LiveService",
110
+ "RestrictionService",
111
+ "SessionService",
112
+ "UserService",
113
+ # Auth
114
+ "AsyncChzzkOAuth",
115
+ "CallbackTokenStorage",
116
+ "ChzzkOAuth",
117
+ "FileTokenStorage",
118
+ "InMemoryTokenStorage",
119
+ "Token",
120
+ "TokenStorage",
121
+ # Models
122
+ "Badge",
123
+ "Category",
124
+ "CategoryType",
125
+ "ChannelInfo",
126
+ "ChannelManager",
127
+ "ChatAvailableCondition",
128
+ "ChatAvailableGroup",
129
+ "ChatEvent",
130
+ "ChatMessageResponse",
131
+ "ChatProfile",
132
+ "ChatSettings",
133
+ "DonationEvent",
134
+ "DonationType",
135
+ "EventType",
136
+ "Follower",
137
+ "LiveInfo",
138
+ "LiveListResponse",
139
+ "LiveSetting",
140
+ "LiveSettingCategory",
141
+ "Page",
142
+ "RestrictedChannel",
143
+ "SessionAuthResponse",
144
+ "SessionInfo",
145
+ "SessionListResponse",
146
+ "StreamKey",
147
+ "SubscribedEvent",
148
+ "Subscriber",
149
+ "SubscriberSortType",
150
+ "SubscriptionEvent",
151
+ "SystemEvent",
152
+ "SystemEventData",
153
+ "SystemMessageType",
154
+ "UpdateChatSettingsRequest",
155
+ "UpdateLiveSettingRequest",
156
+ "UserInfo",
157
+ "UserRole",
158
+ "UserRoleCode",
159
+ # Exceptions
160
+ "AuthenticationError",
161
+ "ChzzkAPIError",
162
+ "ChzzkError",
163
+ "EventSubscriptionError",
164
+ "ForbiddenError",
165
+ "InvalidClientError",
166
+ "InvalidStateError",
167
+ "InvalidTokenError",
168
+ "NotFoundError",
169
+ "RateLimitError",
170
+ "ServerError",
171
+ "SessionConnectionError",
172
+ "SessionError",
173
+ "SessionLimitExceededError",
174
+ "TokenExpiredError",
175
+ ]
chzzk/_version.py ADDED
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '0.1.0'
32
+ __version_tuple__ = version_tuple = (0, 1, 0)
33
+
34
+ __commit_id__ = commit_id = None
chzzk/api/__init__.py ADDED
@@ -0,0 +1,29 @@
1
+ """API services for Chzzk SDK."""
2
+
3
+ from chzzk.api.base import AsyncBaseService, BaseService
4
+ from chzzk.api.category import AsyncCategoryService, CategoryService
5
+ from chzzk.api.channel import AsyncChannelService, ChannelService
6
+ from chzzk.api.chat import AsyncChatService, ChatService
7
+ from chzzk.api.live import AsyncLiveService, LiveService
8
+ from chzzk.api.restriction import AsyncRestrictionService, RestrictionService
9
+ from chzzk.api.session import AsyncSessionService, SessionService
10
+ from chzzk.api.user import AsyncUserService, UserService
11
+
12
+ __all__ = [
13
+ "AsyncBaseService",
14
+ "AsyncCategoryService",
15
+ "AsyncChannelService",
16
+ "AsyncChatService",
17
+ "AsyncLiveService",
18
+ "AsyncRestrictionService",
19
+ "AsyncSessionService",
20
+ "AsyncUserService",
21
+ "BaseService",
22
+ "CategoryService",
23
+ "ChannelService",
24
+ "ChatService",
25
+ "LiveService",
26
+ "RestrictionService",
27
+ "SessionService",
28
+ "UserService",
29
+ ]
chzzk/api/base.py ADDED
@@ -0,0 +1,147 @@
1
+ """Base service class for API services."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from chzzk.http.client import AsyncHTTPClient, HTTPClient
10
+
11
+
12
+ class BaseService:
13
+ """Base class for synchronous API services."""
14
+
15
+ def __init__(
16
+ self,
17
+ http_client: HTTPClient,
18
+ *,
19
+ client_id: str | None = None,
20
+ client_secret: str | None = None,
21
+ access_token: str | None = None,
22
+ token_refresher: Callable[[], str | None] | None = None,
23
+ ) -> None:
24
+ """Initialize the service.
25
+
26
+ Args:
27
+ http_client: HTTP client instance for making requests.
28
+ client_id: Client ID for API authentication.
29
+ client_secret: Client secret for API authentication.
30
+ access_token: Access token for user-authenticated requests.
31
+ token_refresher: Optional callback to get/refresh access token.
32
+ """
33
+ self._http = http_client
34
+ self._client_id = client_id
35
+ self._client_secret = client_secret
36
+ self._access_token = access_token
37
+ self._token_refresher = token_refresher
38
+
39
+ def _get_client_headers(self) -> dict[str, str]:
40
+ """Get headers for Client-authenticated requests.
41
+
42
+ Returns:
43
+ Headers dict with Client-Id and Client-Secret.
44
+ """
45
+ headers: dict[str, str] = {}
46
+ if self._client_id:
47
+ headers["Client-Id"] = self._client_id
48
+ if self._client_secret:
49
+ headers["Client-Secret"] = self._client_secret
50
+ return headers
51
+
52
+ def _get_token_headers(self) -> dict[str, str]:
53
+ """Get headers for Access Token-authenticated requests.
54
+
55
+ If a token_refresher callback is configured, it will be called
56
+ to get the current access token (and potentially refresh it).
57
+
58
+ Returns:
59
+ Headers dict with Authorization Bearer token.
60
+ """
61
+ # Use token refresher if available (may refresh expired token)
62
+ if self._token_refresher:
63
+ token = self._token_refresher()
64
+ if token:
65
+ self._access_token = token
66
+
67
+ headers: dict[str, str] = {}
68
+ if self._access_token:
69
+ headers["Authorization"] = f"Bearer {self._access_token}"
70
+ return headers
71
+
72
+ def set_access_token(self, token: str) -> None:
73
+ """Update the access token.
74
+
75
+ Args:
76
+ token: New access token.
77
+ """
78
+ self._access_token = token
79
+
80
+
81
+ class AsyncBaseService:
82
+ """Base class for asynchronous API services."""
83
+
84
+ def __init__(
85
+ self,
86
+ http_client: AsyncHTTPClient,
87
+ *,
88
+ client_id: str | None = None,
89
+ client_secret: str | None = None,
90
+ access_token: str | None = None,
91
+ async_token_refresher: Callable[[], str | None] | None = None,
92
+ ) -> None:
93
+ """Initialize the async service.
94
+
95
+ Args:
96
+ http_client: Async HTTP client instance for making requests.
97
+ client_id: Client ID for API authentication.
98
+ client_secret: Client secret for API authentication.
99
+ access_token: Access token for user-authenticated requests.
100
+ async_token_refresher: Optional async callback to get/refresh access token.
101
+ """
102
+ self._http = http_client
103
+ self._client_id = client_id
104
+ self._client_secret = client_secret
105
+ self._access_token = access_token
106
+ self._async_token_refresher = async_token_refresher
107
+
108
+ def _get_client_headers(self) -> dict[str, str]:
109
+ """Get headers for Client-authenticated requests.
110
+
111
+ Returns:
112
+ Headers dict with Client-Id and Client-Secret.
113
+ """
114
+ headers: dict[str, str] = {}
115
+ if self._client_id:
116
+ headers["Client-Id"] = self._client_id
117
+ if self._client_secret:
118
+ headers["Client-Secret"] = self._client_secret
119
+ return headers
120
+
121
+ async def _get_token_headers(self) -> dict[str, str]:
122
+ """Get headers for Access Token-authenticated requests.
123
+
124
+ If an async_token_refresher callback is configured, it will be called
125
+ to get the current access token (and potentially refresh it).
126
+
127
+ Returns:
128
+ Headers dict with Authorization Bearer token.
129
+ """
130
+ # Use async token refresher if available (may refresh expired token)
131
+ if self._async_token_refresher:
132
+ token = await self._async_token_refresher()
133
+ if token:
134
+ self._access_token = token
135
+
136
+ headers: dict[str, str] = {}
137
+ if self._access_token:
138
+ headers["Authorization"] = f"Bearer {self._access_token}"
139
+ return headers
140
+
141
+ def set_access_token(self, token: str) -> None:
142
+ """Update the access token.
143
+
144
+ Args:
145
+ token: New access token.
146
+ """
147
+ self._access_token = token
chzzk/api/category.py ADDED
@@ -0,0 +1,65 @@
1
+ """Category API service for Chzzk."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from chzzk.api.base import AsyncBaseService, BaseService
6
+ from chzzk.http import CATEGORIES_SEARCH_URL
7
+ from chzzk.models.category import Category
8
+
9
+
10
+ class CategoryService(BaseService):
11
+ """Synchronous Category API service.
12
+
13
+ Provides access to category-related API endpoints.
14
+ """
15
+
16
+ def search(self, query: str, *, size: int = 20) -> list[Category]:
17
+ """Search for categories.
18
+
19
+ Args:
20
+ query: Search query string.
21
+ size: Maximum number of results to return (default: 20).
22
+
23
+ Returns:
24
+ List of Category objects matching the query.
25
+
26
+ Raises:
27
+ ChzzkAPIError: If the API request fails.
28
+ """
29
+ params = {"query": query, "size": size}
30
+ data = self._http.get(
31
+ CATEGORIES_SEARCH_URL,
32
+ params=params,
33
+ headers=self._get_client_headers(),
34
+ )
35
+ categories = data.get("data", [])
36
+ return [Category.model_validate(item) for item in categories]
37
+
38
+
39
+ class AsyncCategoryService(AsyncBaseService):
40
+ """Asynchronous Category API service.
41
+
42
+ Provides access to category-related API endpoints.
43
+ """
44
+
45
+ async def search(self, query: str, *, size: int = 20) -> list[Category]:
46
+ """Search for categories.
47
+
48
+ Args:
49
+ query: Search query string.
50
+ size: Maximum number of results to return (default: 20).
51
+
52
+ Returns:
53
+ List of Category objects matching the query.
54
+
55
+ Raises:
56
+ ChzzkAPIError: If the API request fails.
57
+ """
58
+ params = {"query": query, "size": size}
59
+ data = await self._http.get(
60
+ CATEGORIES_SEARCH_URL,
61
+ params=params,
62
+ headers=self._get_client_headers(),
63
+ )
64
+ categories = data.get("data", [])
65
+ return [Category.model_validate(item) for item in categories]
chzzk/api/channel.py ADDED
@@ -0,0 +1,218 @@
1
+ """Channel API service for Chzzk."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from chzzk.api.base import AsyncBaseService, BaseService
6
+ from chzzk.http import (
7
+ CHANNEL_FOLLOWERS_URL,
8
+ CHANNEL_ROLES_URL,
9
+ CHANNEL_SUBSCRIBERS_URL,
10
+ CHANNELS_URL,
11
+ )
12
+ from chzzk.models.channel import (
13
+ ChannelInfo,
14
+ ChannelManager,
15
+ Follower,
16
+ Subscriber,
17
+ SubscriberSortType,
18
+ )
19
+
20
+
21
+ class ChannelService(BaseService):
22
+ """Synchronous Channel API service.
23
+
24
+ Provides access to channel-related API endpoints.
25
+ """
26
+
27
+ def get_channels(self, channel_ids: list[str]) -> list[ChannelInfo]:
28
+ """Get information for multiple channels.
29
+
30
+ Args:
31
+ channel_ids: List of channel IDs to look up (max 20).
32
+
33
+ Returns:
34
+ List of ChannelInfo objects.
35
+
36
+ Raises:
37
+ ChzzkAPIError: If the API request fails.
38
+ """
39
+ params = {"channelIds": ",".join(channel_ids)}
40
+ data = self._http.get(
41
+ CHANNELS_URL,
42
+ params=params,
43
+ headers=self._get_client_headers(),
44
+ )
45
+ channels = data.get("data", [])
46
+ return [ChannelInfo.model_validate(item) for item in channels]
47
+
48
+ def get_streaming_roles(self) -> list[ChannelManager]:
49
+ """Get channel managers for the authenticated user's channel.
50
+
51
+ Returns:
52
+ List of ChannelManager objects.
53
+
54
+ Raises:
55
+ InvalidTokenError: If the access token is invalid.
56
+ ChzzkAPIError: If the API request fails.
57
+ """
58
+ data = self._http.get(CHANNEL_ROLES_URL, headers=self._get_token_headers())
59
+ managers = data.get("data", [])
60
+ return [ChannelManager.model_validate(item) for item in managers]
61
+
62
+ def get_followers(
63
+ self,
64
+ *,
65
+ page: int = 0,
66
+ size: int = 20,
67
+ ) -> list[Follower]:
68
+ """Get followers for the authenticated user's channel.
69
+
70
+ Args:
71
+ page: Page number (0-indexed).
72
+ size: Number of results per page.
73
+
74
+ Returns:
75
+ List of Follower objects.
76
+
77
+ Raises:
78
+ InvalidTokenError: If the access token is invalid.
79
+ ChzzkAPIError: If the API request fails.
80
+ """
81
+ params = {"page": page, "size": size}
82
+ data = self._http.get(
83
+ CHANNEL_FOLLOWERS_URL,
84
+ params=params,
85
+ headers=self._get_token_headers(),
86
+ )
87
+ followers = data.get("data", [])
88
+ return [Follower.model_validate(item) for item in followers]
89
+
90
+ def get_subscribers(
91
+ self,
92
+ *,
93
+ page: int = 0,
94
+ size: int = 20,
95
+ sort: SubscriberSortType = SubscriberSortType.RECENT,
96
+ ) -> list[Subscriber]:
97
+ """Get subscribers for the authenticated user's channel.
98
+
99
+ Args:
100
+ page: Page number (0-indexed).
101
+ size: Number of results per page.
102
+ sort: Sort order (RECENT or LONGER).
103
+
104
+ Returns:
105
+ List of Subscriber objects.
106
+
107
+ Raises:
108
+ InvalidTokenError: If the access token is invalid.
109
+ ChzzkAPIError: If the API request fails.
110
+ """
111
+ params = {"page": page, "size": size, "sortType": sort.value}
112
+ data = self._http.get(
113
+ CHANNEL_SUBSCRIBERS_URL,
114
+ params=params,
115
+ headers=self._get_token_headers(),
116
+ )
117
+ subscribers = data.get("data", [])
118
+ return [Subscriber.model_validate(item) for item in subscribers]
119
+
120
+
121
+ class AsyncChannelService(AsyncBaseService):
122
+ """Asynchronous Channel API service.
123
+
124
+ Provides access to channel-related API endpoints.
125
+ """
126
+
127
+ async def get_channels(self, channel_ids: list[str]) -> list[ChannelInfo]:
128
+ """Get information for multiple channels.
129
+
130
+ Args:
131
+ channel_ids: List of channel IDs to look up (max 20).
132
+
133
+ Returns:
134
+ List of ChannelInfo objects.
135
+
136
+ Raises:
137
+ ChzzkAPIError: If the API request fails.
138
+ """
139
+ params = {"channelIds": ",".join(channel_ids)}
140
+ data = await self._http.get(
141
+ CHANNELS_URL,
142
+ params=params,
143
+ headers=self._get_client_headers(),
144
+ )
145
+ channels = data.get("data", [])
146
+ return [ChannelInfo.model_validate(item) for item in channels]
147
+
148
+ async def get_streaming_roles(self) -> list[ChannelManager]:
149
+ """Get channel managers for the authenticated user's channel.
150
+
151
+ Returns:
152
+ List of ChannelManager objects.
153
+
154
+ Raises:
155
+ InvalidTokenError: If the access token is invalid.
156
+ ChzzkAPIError: If the API request fails.
157
+ """
158
+ data = await self._http.get(CHANNEL_ROLES_URL, headers=await self._get_token_headers())
159
+ managers = data.get("data", [])
160
+ return [ChannelManager.model_validate(item) for item in managers]
161
+
162
+ async def get_followers(
163
+ self,
164
+ *,
165
+ page: int = 0,
166
+ size: int = 20,
167
+ ) -> list[Follower]:
168
+ """Get followers for the authenticated user's channel.
169
+
170
+ Args:
171
+ page: Page number (0-indexed).
172
+ size: Number of results per page.
173
+
174
+ Returns:
175
+ List of Follower objects.
176
+
177
+ Raises:
178
+ InvalidTokenError: If the access token is invalid.
179
+ ChzzkAPIError: If the API request fails.
180
+ """
181
+ params = {"page": page, "size": size}
182
+ data = await self._http.get(
183
+ CHANNEL_FOLLOWERS_URL,
184
+ params=params,
185
+ headers=await self._get_token_headers(),
186
+ )
187
+ followers = data.get("data", [])
188
+ return [Follower.model_validate(item) for item in followers]
189
+
190
+ async def get_subscribers(
191
+ self,
192
+ *,
193
+ page: int = 0,
194
+ size: int = 20,
195
+ sort: SubscriberSortType = SubscriberSortType.RECENT,
196
+ ) -> list[Subscriber]:
197
+ """Get subscribers for the authenticated user's channel.
198
+
199
+ Args:
200
+ page: Page number (0-indexed).
201
+ size: Number of results per page.
202
+ sort: Sort order (RECENT or LONGER).
203
+
204
+ Returns:
205
+ List of Subscriber objects.
206
+
207
+ Raises:
208
+ InvalidTokenError: If the access token is invalid.
209
+ ChzzkAPIError: If the API request fails.
210
+ """
211
+ params = {"page": page, "size": size, "sortType": sort.value}
212
+ data = await self._http.get(
213
+ CHANNEL_SUBSCRIBERS_URL,
214
+ params=params,
215
+ headers=await self._get_token_headers(),
216
+ )
217
+ subscribers = data.get("data", [])
218
+ return [Subscriber.model_validate(item) for item in subscribers]