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 +175 -0
- chzzk/_version.py +34 -0
- chzzk/api/__init__.py +29 -0
- chzzk/api/base.py +147 -0
- chzzk/api/category.py +65 -0
- chzzk/api/channel.py +218 -0
- chzzk/api/chat.py +239 -0
- chzzk/api/live.py +212 -0
- chzzk/api/restriction.py +147 -0
- chzzk/api/session.py +389 -0
- chzzk/api/user.py +47 -0
- chzzk/auth/__init__.py +34 -0
- chzzk/auth/models.py +115 -0
- chzzk/auth/oauth.py +452 -0
- chzzk/auth/token.py +119 -0
- chzzk/client.py +515 -0
- chzzk/exceptions/__init__.py +37 -0
- chzzk/exceptions/errors.py +130 -0
- chzzk/http/__init__.py +68 -0
- chzzk/http/client.py +310 -0
- chzzk/http/endpoints.py +52 -0
- chzzk/models/__init__.py +86 -0
- chzzk/models/category.py +18 -0
- chzzk/models/channel.py +69 -0
- chzzk/models/chat.py +63 -0
- chzzk/models/common.py +23 -0
- chzzk/models/live.py +78 -0
- chzzk/models/restriction.py +18 -0
- chzzk/models/session.py +161 -0
- chzzk/models/user.py +14 -0
- chzzk/py.typed +0 -0
- chzzk/realtime/__init__.py +8 -0
- chzzk/realtime/client.py +635 -0
- chzzk_python-0.1.0.dist-info/METADATA +314 -0
- chzzk_python-0.1.0.dist-info/RECORD +37 -0
- chzzk_python-0.1.0.dist-info/WHEEL +4 -0
- chzzk_python-0.1.0.dist-info/licenses/LICENSE +21 -0
chzzk/api/session.py
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
"""Session 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
|
+
SESSIONS_AUTH_CLIENT_URL,
|
|
8
|
+
SESSIONS_AUTH_URL,
|
|
9
|
+
SESSIONS_CLIENT_URL,
|
|
10
|
+
SESSIONS_SUBSCRIBE_CHAT_URL,
|
|
11
|
+
SESSIONS_SUBSCRIBE_DONATION_URL,
|
|
12
|
+
SESSIONS_SUBSCRIBE_SUBSCRIPTION_URL,
|
|
13
|
+
SESSIONS_UNSUBSCRIBE_CHAT_URL,
|
|
14
|
+
SESSIONS_UNSUBSCRIBE_DONATION_URL,
|
|
15
|
+
SESSIONS_UNSUBSCRIBE_SUBSCRIPTION_URL,
|
|
16
|
+
SESSIONS_URL,
|
|
17
|
+
)
|
|
18
|
+
from chzzk.models.session import SessionAuthResponse, SessionInfo, SessionListResponse
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SessionService(BaseService):
|
|
22
|
+
"""Synchronous Session API service.
|
|
23
|
+
|
|
24
|
+
Provides access to session-related API endpoints for WebSocket event subscriptions.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def create_session(self) -> SessionAuthResponse:
|
|
28
|
+
"""Create a session for WebSocket connection (user authentication).
|
|
29
|
+
|
|
30
|
+
Creates a session URL using access token authentication.
|
|
31
|
+
Maximum 3 connections per user.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
SessionAuthResponse containing the WebSocket connection URL.
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
InvalidTokenError: If the access token is invalid.
|
|
38
|
+
ChzzkAPIError: If the API request fails.
|
|
39
|
+
"""
|
|
40
|
+
data = self._http.get(SESSIONS_AUTH_URL, headers=self._get_token_headers())
|
|
41
|
+
return SessionAuthResponse.model_validate(data)
|
|
42
|
+
|
|
43
|
+
def create_client_session(self) -> SessionAuthResponse:
|
|
44
|
+
"""Create a session for WebSocket connection (client authentication).
|
|
45
|
+
|
|
46
|
+
Creates a session URL using client credentials authentication.
|
|
47
|
+
Maximum 10 connections per client.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
SessionAuthResponse containing the WebSocket connection URL.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
InvalidClientError: If the client credentials are invalid.
|
|
54
|
+
ChzzkAPIError: If the API request fails.
|
|
55
|
+
"""
|
|
56
|
+
data = self._http.get(SESSIONS_AUTH_CLIENT_URL, headers=self._get_client_headers())
|
|
57
|
+
return SessionAuthResponse.model_validate(data)
|
|
58
|
+
|
|
59
|
+
def get_sessions(self, *, size: int = 20, page: int = 0) -> list[SessionInfo]:
|
|
60
|
+
"""Get session list (user authentication).
|
|
61
|
+
|
|
62
|
+
Retrieves sessions created with access token authentication.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
size: Number of sessions to retrieve (1-50). Default is 20.
|
|
66
|
+
page: Page number to retrieve (0-indexed). Default is 0.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
List of SessionInfo objects.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
InvalidTokenError: If the access token is invalid.
|
|
73
|
+
ChzzkAPIError: If the API request fails.
|
|
74
|
+
"""
|
|
75
|
+
params = {"size": size, "page": page}
|
|
76
|
+
data = self._http.get(SESSIONS_URL, params=params, headers=self._get_token_headers())
|
|
77
|
+
response = SessionListResponse.model_validate(data)
|
|
78
|
+
return response.data
|
|
79
|
+
|
|
80
|
+
def get_client_sessions(self, *, size: int = 20, page: int = 0) -> list[SessionInfo]:
|
|
81
|
+
"""Get session list (client authentication).
|
|
82
|
+
|
|
83
|
+
Retrieves sessions created with client credentials authentication.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
size: Number of sessions to retrieve (1-50). Default is 20.
|
|
87
|
+
page: Page number to retrieve (0-indexed). Default is 0.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
List of SessionInfo objects.
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
InvalidClientError: If the client credentials are invalid.
|
|
94
|
+
ChzzkAPIError: If the API request fails.
|
|
95
|
+
"""
|
|
96
|
+
params = {"size": size, "page": page}
|
|
97
|
+
headers = self._get_client_headers()
|
|
98
|
+
data = self._http.get(SESSIONS_CLIENT_URL, params=params, headers=headers)
|
|
99
|
+
response = SessionListResponse.model_validate(data)
|
|
100
|
+
return response.data
|
|
101
|
+
|
|
102
|
+
def subscribe_chat(self, session_key: str) -> None:
|
|
103
|
+
"""Subscribe to chat events for a session.
|
|
104
|
+
|
|
105
|
+
Maximum 30 events (chat, donation, subscription) per session.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
session_key: The session key to subscribe events to.
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
InvalidTokenError: If the access token is invalid.
|
|
112
|
+
ChzzkAPIError: If the API request fails.
|
|
113
|
+
"""
|
|
114
|
+
self._http.post(
|
|
115
|
+
SESSIONS_SUBSCRIBE_CHAT_URL,
|
|
116
|
+
params={"sessionKey": session_key},
|
|
117
|
+
headers=self._get_token_headers(),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def subscribe_donation(self, session_key: str) -> None:
|
|
121
|
+
"""Subscribe to donation events for a session.
|
|
122
|
+
|
|
123
|
+
Maximum 30 events (chat, donation, subscription) per session.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
session_key: The session key to subscribe events to.
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
InvalidTokenError: If the access token is invalid.
|
|
130
|
+
ChzzkAPIError: If the API request fails.
|
|
131
|
+
"""
|
|
132
|
+
self._http.post(
|
|
133
|
+
SESSIONS_SUBSCRIBE_DONATION_URL,
|
|
134
|
+
params={"sessionKey": session_key},
|
|
135
|
+
headers=self._get_token_headers(),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def subscribe_subscription(self, session_key: str) -> None:
|
|
139
|
+
"""Subscribe to subscription events for a session.
|
|
140
|
+
|
|
141
|
+
Maximum 30 events (chat, donation, subscription) per session.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
session_key: The session key to subscribe events to.
|
|
145
|
+
|
|
146
|
+
Raises:
|
|
147
|
+
InvalidTokenError: If the access token is invalid.
|
|
148
|
+
ChzzkAPIError: If the API request fails.
|
|
149
|
+
"""
|
|
150
|
+
self._http.post(
|
|
151
|
+
SESSIONS_SUBSCRIBE_SUBSCRIPTION_URL,
|
|
152
|
+
params={"sessionKey": session_key},
|
|
153
|
+
headers=self._get_token_headers(),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def unsubscribe_chat(self, session_key: str) -> None:
|
|
157
|
+
"""Unsubscribe from chat events for a session.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
session_key: The session key to unsubscribe events from.
|
|
161
|
+
|
|
162
|
+
Raises:
|
|
163
|
+
InvalidTokenError: If the access token is invalid.
|
|
164
|
+
ChzzkAPIError: If the API request fails.
|
|
165
|
+
"""
|
|
166
|
+
self._http.post(
|
|
167
|
+
SESSIONS_UNSUBSCRIBE_CHAT_URL,
|
|
168
|
+
params={"sessionKey": session_key},
|
|
169
|
+
headers=self._get_token_headers(),
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def unsubscribe_donation(self, session_key: str) -> None:
|
|
173
|
+
"""Unsubscribe from donation events for a session.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
session_key: The session key to unsubscribe events from.
|
|
177
|
+
|
|
178
|
+
Raises:
|
|
179
|
+
InvalidTokenError: If the access token is invalid.
|
|
180
|
+
ChzzkAPIError: If the API request fails.
|
|
181
|
+
"""
|
|
182
|
+
self._http.post(
|
|
183
|
+
SESSIONS_UNSUBSCRIBE_DONATION_URL,
|
|
184
|
+
params={"sessionKey": session_key},
|
|
185
|
+
headers=self._get_token_headers(),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def unsubscribe_subscription(self, session_key: str) -> None:
|
|
189
|
+
"""Unsubscribe from subscription events for a session.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
session_key: The session key to unsubscribe events from.
|
|
193
|
+
|
|
194
|
+
Raises:
|
|
195
|
+
InvalidTokenError: If the access token is invalid.
|
|
196
|
+
ChzzkAPIError: If the API request fails.
|
|
197
|
+
"""
|
|
198
|
+
self._http.post(
|
|
199
|
+
SESSIONS_UNSUBSCRIBE_SUBSCRIPTION_URL,
|
|
200
|
+
params={"sessionKey": session_key},
|
|
201
|
+
headers=self._get_token_headers(),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class AsyncSessionService(AsyncBaseService):
|
|
206
|
+
"""Asynchronous Session API service.
|
|
207
|
+
|
|
208
|
+
Provides access to session-related API endpoints for WebSocket event subscriptions.
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
async def create_session(self) -> SessionAuthResponse:
|
|
212
|
+
"""Create a session for WebSocket connection (user authentication).
|
|
213
|
+
|
|
214
|
+
Creates a session URL using access token authentication.
|
|
215
|
+
Maximum 3 connections per user.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
SessionAuthResponse containing the WebSocket connection URL.
|
|
219
|
+
|
|
220
|
+
Raises:
|
|
221
|
+
InvalidTokenError: If the access token is invalid.
|
|
222
|
+
ChzzkAPIError: If the API request fails.
|
|
223
|
+
"""
|
|
224
|
+
data = await self._http.get(SESSIONS_AUTH_URL, headers=await self._get_token_headers())
|
|
225
|
+
return SessionAuthResponse.model_validate(data)
|
|
226
|
+
|
|
227
|
+
async def create_client_session(self) -> SessionAuthResponse:
|
|
228
|
+
"""Create a session for WebSocket connection (client authentication).
|
|
229
|
+
|
|
230
|
+
Creates a session URL using client credentials authentication.
|
|
231
|
+
Maximum 10 connections per client.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
SessionAuthResponse containing the WebSocket connection URL.
|
|
235
|
+
|
|
236
|
+
Raises:
|
|
237
|
+
InvalidClientError: If the client credentials are invalid.
|
|
238
|
+
ChzzkAPIError: If the API request fails.
|
|
239
|
+
"""
|
|
240
|
+
data = await self._http.get(SESSIONS_AUTH_CLIENT_URL, headers=self._get_client_headers())
|
|
241
|
+
return SessionAuthResponse.model_validate(data)
|
|
242
|
+
|
|
243
|
+
async def get_sessions(self, *, size: int = 20, page: int = 0) -> list[SessionInfo]:
|
|
244
|
+
"""Get session list (user authentication).
|
|
245
|
+
|
|
246
|
+
Retrieves sessions created with access token authentication.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
size: Number of sessions to retrieve (1-50). Default is 20.
|
|
250
|
+
page: Page number to retrieve (0-indexed). Default is 0.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
List of SessionInfo objects.
|
|
254
|
+
|
|
255
|
+
Raises:
|
|
256
|
+
InvalidTokenError: If the access token is invalid.
|
|
257
|
+
ChzzkAPIError: If the API request fails.
|
|
258
|
+
"""
|
|
259
|
+
params = {"size": size, "page": page}
|
|
260
|
+
data = await self._http.get(
|
|
261
|
+
SESSIONS_URL, params=params, headers=await self._get_token_headers()
|
|
262
|
+
)
|
|
263
|
+
response = SessionListResponse.model_validate(data)
|
|
264
|
+
return response.data
|
|
265
|
+
|
|
266
|
+
async def get_client_sessions(self, *, size: int = 20, page: int = 0) -> list[SessionInfo]:
|
|
267
|
+
"""Get session list (client authentication).
|
|
268
|
+
|
|
269
|
+
Retrieves sessions created with client credentials authentication.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
size: Number of sessions to retrieve (1-50). Default is 20.
|
|
273
|
+
page: Page number to retrieve (0-indexed). Default is 0.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
List of SessionInfo objects.
|
|
277
|
+
|
|
278
|
+
Raises:
|
|
279
|
+
InvalidClientError: If the client credentials are invalid.
|
|
280
|
+
ChzzkAPIError: If the API request fails.
|
|
281
|
+
"""
|
|
282
|
+
params = {"size": size, "page": page}
|
|
283
|
+
data = await self._http.get(
|
|
284
|
+
SESSIONS_CLIENT_URL, params=params, headers=self._get_client_headers()
|
|
285
|
+
)
|
|
286
|
+
response = SessionListResponse.model_validate(data)
|
|
287
|
+
return response.data
|
|
288
|
+
|
|
289
|
+
async def subscribe_chat(self, session_key: str) -> None:
|
|
290
|
+
"""Subscribe to chat events for a session.
|
|
291
|
+
|
|
292
|
+
Maximum 30 events (chat, donation, subscription) per session.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
session_key: The session key to subscribe events to.
|
|
296
|
+
|
|
297
|
+
Raises:
|
|
298
|
+
InvalidTokenError: If the access token is invalid.
|
|
299
|
+
ChzzkAPIError: If the API request fails.
|
|
300
|
+
"""
|
|
301
|
+
await self._http.post(
|
|
302
|
+
SESSIONS_SUBSCRIBE_CHAT_URL,
|
|
303
|
+
params={"sessionKey": session_key},
|
|
304
|
+
headers=await self._get_token_headers(),
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
async def subscribe_donation(self, session_key: str) -> None:
|
|
308
|
+
"""Subscribe to donation events for a session.
|
|
309
|
+
|
|
310
|
+
Maximum 30 events (chat, donation, subscription) per session.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
session_key: The session key to subscribe events to.
|
|
314
|
+
|
|
315
|
+
Raises:
|
|
316
|
+
InvalidTokenError: If the access token is invalid.
|
|
317
|
+
ChzzkAPIError: If the API request fails.
|
|
318
|
+
"""
|
|
319
|
+
await self._http.post(
|
|
320
|
+
SESSIONS_SUBSCRIBE_DONATION_URL,
|
|
321
|
+
params={"sessionKey": session_key},
|
|
322
|
+
headers=await self._get_token_headers(),
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
async def subscribe_subscription(self, session_key: str) -> None:
|
|
326
|
+
"""Subscribe to subscription events for a session.
|
|
327
|
+
|
|
328
|
+
Maximum 30 events (chat, donation, subscription) per session.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
session_key: The session key to subscribe events to.
|
|
332
|
+
|
|
333
|
+
Raises:
|
|
334
|
+
InvalidTokenError: If the access token is invalid.
|
|
335
|
+
ChzzkAPIError: If the API request fails.
|
|
336
|
+
"""
|
|
337
|
+
await self._http.post(
|
|
338
|
+
SESSIONS_SUBSCRIBE_SUBSCRIPTION_URL,
|
|
339
|
+
params={"sessionKey": session_key},
|
|
340
|
+
headers=await self._get_token_headers(),
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
async def unsubscribe_chat(self, session_key: str) -> None:
|
|
344
|
+
"""Unsubscribe from chat events for a session.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
session_key: The session key to unsubscribe events from.
|
|
348
|
+
|
|
349
|
+
Raises:
|
|
350
|
+
InvalidTokenError: If the access token is invalid.
|
|
351
|
+
ChzzkAPIError: If the API request fails.
|
|
352
|
+
"""
|
|
353
|
+
await self._http.post(
|
|
354
|
+
SESSIONS_UNSUBSCRIBE_CHAT_URL,
|
|
355
|
+
params={"sessionKey": session_key},
|
|
356
|
+
headers=await self._get_token_headers(),
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
async def unsubscribe_donation(self, session_key: str) -> None:
|
|
360
|
+
"""Unsubscribe from donation events for a session.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
session_key: The session key to unsubscribe events from.
|
|
364
|
+
|
|
365
|
+
Raises:
|
|
366
|
+
InvalidTokenError: If the access token is invalid.
|
|
367
|
+
ChzzkAPIError: If the API request fails.
|
|
368
|
+
"""
|
|
369
|
+
await self._http.post(
|
|
370
|
+
SESSIONS_UNSUBSCRIBE_DONATION_URL,
|
|
371
|
+
params={"sessionKey": session_key},
|
|
372
|
+
headers=await self._get_token_headers(),
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
async def unsubscribe_subscription(self, session_key: str) -> None:
|
|
376
|
+
"""Unsubscribe from subscription events for a session.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
session_key: The session key to unsubscribe events from.
|
|
380
|
+
|
|
381
|
+
Raises:
|
|
382
|
+
InvalidTokenError: If the access token is invalid.
|
|
383
|
+
ChzzkAPIError: If the API request fails.
|
|
384
|
+
"""
|
|
385
|
+
await self._http.post(
|
|
386
|
+
SESSIONS_UNSUBSCRIBE_SUBSCRIPTION_URL,
|
|
387
|
+
params={"sessionKey": session_key},
|
|
388
|
+
headers=await self._get_token_headers(),
|
|
389
|
+
)
|
chzzk/api/user.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""User API service for Chzzk."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from chzzk.api.base import AsyncBaseService, BaseService
|
|
6
|
+
from chzzk.http import USER_ME_URL
|
|
7
|
+
from chzzk.models.user import UserInfo
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class UserService(BaseService):
|
|
11
|
+
"""Synchronous User API service.
|
|
12
|
+
|
|
13
|
+
Provides access to user-related API endpoints.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def get_me(self) -> UserInfo:
|
|
17
|
+
"""Get the current authenticated user's information.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
UserInfo containing the user's channel ID and name.
|
|
21
|
+
|
|
22
|
+
Raises:
|
|
23
|
+
InvalidTokenError: If the access token is invalid.
|
|
24
|
+
ChzzkAPIError: If the API request fails.
|
|
25
|
+
"""
|
|
26
|
+
data = self._http.get(USER_ME_URL, headers=self._get_token_headers())
|
|
27
|
+
return UserInfo.model_validate(data)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AsyncUserService(AsyncBaseService):
|
|
31
|
+
"""Asynchronous User API service.
|
|
32
|
+
|
|
33
|
+
Provides access to user-related API endpoints.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
async def get_me(self) -> UserInfo:
|
|
37
|
+
"""Get the current authenticated user's information.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
UserInfo containing the user's channel ID and name.
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
InvalidTokenError: If the access token is invalid.
|
|
44
|
+
ChzzkAPIError: If the API request fails.
|
|
45
|
+
"""
|
|
46
|
+
data = await self._http.get(USER_ME_URL, headers=await self._get_token_headers())
|
|
47
|
+
return UserInfo.model_validate(data)
|
chzzk/auth/__init__.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Authentication module for Chzzk SDK."""
|
|
2
|
+
|
|
3
|
+
from chzzk.auth.models import (
|
|
4
|
+
AuthorizationCodeRequest,
|
|
5
|
+
GrantType,
|
|
6
|
+
RefreshTokenRequest,
|
|
7
|
+
RevokeTokenRequest,
|
|
8
|
+
Token,
|
|
9
|
+
TokenResponse,
|
|
10
|
+
TokenTypeHint,
|
|
11
|
+
)
|
|
12
|
+
from chzzk.auth.oauth import (
|
|
13
|
+
AsyncChzzkOAuth,
|
|
14
|
+
ChzzkOAuth,
|
|
15
|
+
InMemoryTokenStorage,
|
|
16
|
+
TokenStorage,
|
|
17
|
+
)
|
|
18
|
+
from chzzk.auth.token import CallbackTokenStorage, FileTokenStorage
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"AsyncChzzkOAuth",
|
|
22
|
+
"AuthorizationCodeRequest",
|
|
23
|
+
"CallbackTokenStorage",
|
|
24
|
+
"ChzzkOAuth",
|
|
25
|
+
"FileTokenStorage",
|
|
26
|
+
"GrantType",
|
|
27
|
+
"InMemoryTokenStorage",
|
|
28
|
+
"RefreshTokenRequest",
|
|
29
|
+
"RevokeTokenRequest",
|
|
30
|
+
"Token",
|
|
31
|
+
"TokenResponse",
|
|
32
|
+
"TokenStorage",
|
|
33
|
+
"TokenTypeHint",
|
|
34
|
+
]
|
chzzk/auth/models.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Pydantic models for Chzzk OAuth authentication."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import UTC, datetime, timedelta
|
|
6
|
+
from enum import StrEnum
|
|
7
|
+
from typing import Self
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field, computed_field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GrantType(StrEnum):
|
|
13
|
+
"""OAuth grant type enumeration."""
|
|
14
|
+
|
|
15
|
+
AUTHORIZATION_CODE = "authorization_code"
|
|
16
|
+
REFRESH_TOKEN = "refresh_token"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TokenTypeHint(StrEnum):
|
|
20
|
+
"""Token type hint for revocation."""
|
|
21
|
+
|
|
22
|
+
ACCESS_TOKEN = "access_token"
|
|
23
|
+
REFRESH_TOKEN = "refresh_token"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TokenResponse(BaseModel):
|
|
27
|
+
"""Response model for token endpoint (camelCase from API)."""
|
|
28
|
+
|
|
29
|
+
access_token: str = Field(alias="accessToken")
|
|
30
|
+
refresh_token: str = Field(alias="refreshToken")
|
|
31
|
+
token_type: str = Field(alias="tokenType")
|
|
32
|
+
expires_in: int = Field(alias="expiresIn")
|
|
33
|
+
scope: str | None = None
|
|
34
|
+
|
|
35
|
+
model_config = {"populate_by_name": True}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Token(BaseModel):
|
|
39
|
+
"""Token model with expiration tracking.
|
|
40
|
+
|
|
41
|
+
This model stores the token data along with the timestamp when it was issued,
|
|
42
|
+
allowing for accurate expiration checking.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
access_token: str
|
|
46
|
+
refresh_token: str
|
|
47
|
+
token_type: str = "Bearer"
|
|
48
|
+
expires_in: int
|
|
49
|
+
scope: str | None = None
|
|
50
|
+
issued_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
51
|
+
|
|
52
|
+
model_config = {"populate_by_name": True}
|
|
53
|
+
|
|
54
|
+
@computed_field
|
|
55
|
+
@property
|
|
56
|
+
def expires_at(self) -> datetime:
|
|
57
|
+
"""Calculate the expiration time of the access token."""
|
|
58
|
+
return self.issued_at + timedelta(seconds=self.expires_in)
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def is_expired(self) -> bool:
|
|
62
|
+
"""Check if the access token is expired.
|
|
63
|
+
|
|
64
|
+
Returns True if the token has expired or will expire within 60 seconds.
|
|
65
|
+
"""
|
|
66
|
+
buffer = timedelta(seconds=60)
|
|
67
|
+
return datetime.now(UTC) >= (self.expires_at - buffer)
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def from_response(cls, response: TokenResponse) -> Self:
|
|
71
|
+
"""Create a Token instance from a TokenResponse."""
|
|
72
|
+
return cls(
|
|
73
|
+
access_token=response.access_token,
|
|
74
|
+
refresh_token=response.refresh_token,
|
|
75
|
+
token_type=response.token_type,
|
|
76
|
+
expires_in=response.expires_in,
|
|
77
|
+
scope=response.scope,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class AuthorizationCodeRequest(BaseModel):
|
|
82
|
+
"""Request model for authorization code token exchange."""
|
|
83
|
+
|
|
84
|
+
grant_type: str = Field(default=GrantType.AUTHORIZATION_CODE, serialization_alias="grantType")
|
|
85
|
+
client_id: str = Field(serialization_alias="clientId")
|
|
86
|
+
client_secret: str = Field(serialization_alias="clientSecret")
|
|
87
|
+
code: str
|
|
88
|
+
state: str
|
|
89
|
+
|
|
90
|
+
model_config = {"populate_by_name": True}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class RefreshTokenRequest(BaseModel):
|
|
94
|
+
"""Request model for token refresh."""
|
|
95
|
+
|
|
96
|
+
grant_type: str = Field(default=GrantType.REFRESH_TOKEN, serialization_alias="grantType")
|
|
97
|
+
client_id: str = Field(serialization_alias="clientId")
|
|
98
|
+
client_secret: str = Field(serialization_alias="clientSecret")
|
|
99
|
+
refresh_token: str = Field(serialization_alias="refreshToken")
|
|
100
|
+
|
|
101
|
+
model_config = {"populate_by_name": True}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class RevokeTokenRequest(BaseModel):
|
|
105
|
+
"""Request model for token revocation."""
|
|
106
|
+
|
|
107
|
+
client_id: str = Field(serialization_alias="clientId")
|
|
108
|
+
client_secret: str = Field(serialization_alias="clientSecret")
|
|
109
|
+
token: str
|
|
110
|
+
token_type_hint: str = Field(
|
|
111
|
+
default=TokenTypeHint.ACCESS_TOKEN,
|
|
112
|
+
serialization_alias="tokenTypeHint",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
model_config = {"populate_by_name": True}
|