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/http/__init__.py ADDED
@@ -0,0 +1,68 @@
1
+ """HTTP client and endpoint utilities for Chzzk SDK."""
2
+
3
+ from chzzk.http.client import AsyncHTTPClient, HTTPClient
4
+ from chzzk.http.endpoints import (
5
+ AUTH_INTERLOCK_URL,
6
+ AUTH_REVOKE_URL,
7
+ AUTH_TOKEN_URL,
8
+ CATEGORIES_SEARCH_URL,
9
+ CHANNEL_FOLLOWERS_URL,
10
+ CHANNEL_ROLES_URL,
11
+ CHANNEL_SUBSCRIBERS_URL,
12
+ CHANNELS_URL,
13
+ CHAT_NOTICE_URL,
14
+ CHAT_SEND_URL,
15
+ CHAT_SETTINGS_URL,
16
+ CHZZK_BASE_URL,
17
+ LIVE_SETTING_URL,
18
+ LIVES_URL,
19
+ OPEN_API_PREFIX,
20
+ OPENAPI_BASE_URL,
21
+ RESTRICT_CHANNELS_URL,
22
+ SESSIONS_AUTH_CLIENT_URL,
23
+ SESSIONS_AUTH_URL,
24
+ SESSIONS_CLIENT_URL,
25
+ SESSIONS_SUBSCRIBE_CHAT_URL,
26
+ SESSIONS_SUBSCRIBE_DONATION_URL,
27
+ SESSIONS_SUBSCRIBE_SUBSCRIPTION_URL,
28
+ SESSIONS_UNSUBSCRIBE_CHAT_URL,
29
+ SESSIONS_UNSUBSCRIBE_DONATION_URL,
30
+ SESSIONS_UNSUBSCRIBE_SUBSCRIPTION_URL,
31
+ SESSIONS_URL,
32
+ STREAM_KEY_URL,
33
+ USER_ME_URL,
34
+ )
35
+
36
+ __all__ = [
37
+ "AUTH_INTERLOCK_URL",
38
+ "AUTH_REVOKE_URL",
39
+ "AUTH_TOKEN_URL",
40
+ "AsyncHTTPClient",
41
+ "CATEGORIES_SEARCH_URL",
42
+ "CHANNELS_URL",
43
+ "CHANNEL_FOLLOWERS_URL",
44
+ "CHANNEL_ROLES_URL",
45
+ "CHANNEL_SUBSCRIBERS_URL",
46
+ "CHAT_NOTICE_URL",
47
+ "CHAT_SEND_URL",
48
+ "CHAT_SETTINGS_URL",
49
+ "CHZZK_BASE_URL",
50
+ "HTTPClient",
51
+ "LIVES_URL",
52
+ "LIVE_SETTING_URL",
53
+ "OPEN_API_PREFIX",
54
+ "OPENAPI_BASE_URL",
55
+ "RESTRICT_CHANNELS_URL",
56
+ "SESSIONS_AUTH_CLIENT_URL",
57
+ "SESSIONS_AUTH_URL",
58
+ "SESSIONS_CLIENT_URL",
59
+ "SESSIONS_SUBSCRIBE_CHAT_URL",
60
+ "SESSIONS_SUBSCRIBE_DONATION_URL",
61
+ "SESSIONS_SUBSCRIBE_SUBSCRIPTION_URL",
62
+ "SESSIONS_UNSUBSCRIBE_CHAT_URL",
63
+ "SESSIONS_UNSUBSCRIBE_DONATION_URL",
64
+ "SESSIONS_UNSUBSCRIBE_SUBSCRIPTION_URL",
65
+ "SESSIONS_URL",
66
+ "STREAM_KEY_URL",
67
+ "USER_ME_URL",
68
+ ]
chzzk/http/client.py ADDED
@@ -0,0 +1,310 @@
1
+ """HTTP client wrapper for Chzzk API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ import httpx
8
+
9
+ from chzzk.exceptions import (
10
+ ChzzkAPIError,
11
+ ForbiddenError,
12
+ InvalidClientError,
13
+ InvalidTokenError,
14
+ NotFoundError,
15
+ RateLimitError,
16
+ ServerError,
17
+ )
18
+
19
+ if TYPE_CHECKING:
20
+ from collections.abc import Mapping
21
+
22
+ DEFAULT_TIMEOUT = 30.0
23
+ USER_AGENT = "chzzk-python/0.1.0"
24
+
25
+
26
+ def _extract_content(data: Any) -> Any:
27
+ """Extract content from Chzzk API response wrapper.
28
+
29
+ Chzzk API wraps responses in { code, message, content }.
30
+ This function extracts the actual content.
31
+ """
32
+ if isinstance(data, dict) and "content" in data:
33
+ return data["content"]
34
+ return data
35
+
36
+
37
+ def _handle_error_response(response: httpx.Response) -> None:
38
+ """Raise appropriate exception based on response status and content."""
39
+ status_code = response.status_code
40
+
41
+ try:
42
+ data = response.json()
43
+ error_code = data.get("code") or data.get("error")
44
+ message = data.get("message") or data.get("error_description") or str(data)
45
+ except Exception:
46
+ error_code = None
47
+ message = response.text or f"HTTP {status_code}"
48
+
49
+ if status_code == 401:
50
+ if error_code == "INVALID_CLIENT":
51
+ raise InvalidClientError(message)
52
+ if error_code == "INVALID_TOKEN":
53
+ raise InvalidTokenError(message)
54
+ raise ChzzkAPIError(message, status_code=401, error_code=error_code)
55
+
56
+ if status_code == 403:
57
+ raise ForbiddenError(message)
58
+
59
+ if status_code == 404:
60
+ raise NotFoundError(message)
61
+
62
+ if status_code == 429:
63
+ raise RateLimitError(message)
64
+
65
+ if status_code >= 500:
66
+ raise ServerError(message)
67
+
68
+ raise ChzzkAPIError(message, status_code=status_code, error_code=error_code)
69
+
70
+
71
+ class HTTPClient:
72
+ """Synchronous HTTP client for Chzzk API."""
73
+
74
+ def __init__(
75
+ self,
76
+ *,
77
+ timeout: float = DEFAULT_TIMEOUT,
78
+ headers: Mapping[str, str] | None = None,
79
+ ) -> None:
80
+ default_headers = {
81
+ "User-Agent": USER_AGENT,
82
+ "Content-Type": "application/json",
83
+ "Accept": "application/json",
84
+ }
85
+ if headers:
86
+ default_headers.update(headers)
87
+
88
+ self._client = httpx.Client(
89
+ timeout=timeout,
90
+ headers=default_headers,
91
+ )
92
+
93
+ def post(
94
+ self,
95
+ url: str,
96
+ *,
97
+ params: dict[str, Any] | None = None,
98
+ json: dict[str, Any] | None = None,
99
+ headers: Mapping[str, str] | None = None,
100
+ ) -> dict[str, Any]:
101
+ """Send a POST request and return JSON response."""
102
+ response = self._client.post(url, params=params, json=json, headers=headers)
103
+
104
+ if response.status_code >= 400:
105
+ _handle_error_response(response)
106
+
107
+ if response.status_code == 204 or not response.content:
108
+ return {}
109
+
110
+ return _extract_content(response.json())
111
+
112
+ def get(
113
+ self,
114
+ url: str,
115
+ *,
116
+ params: dict[str, Any] | None = None,
117
+ headers: Mapping[str, str] | None = None,
118
+ ) -> dict[str, Any]:
119
+ """Send a GET request and return JSON response."""
120
+ response = self._client.get(url, params=params, headers=headers)
121
+
122
+ if response.status_code >= 400:
123
+ _handle_error_response(response)
124
+
125
+ return _extract_content(response.json())
126
+
127
+ def put(
128
+ self,
129
+ url: str,
130
+ *,
131
+ json: dict[str, Any] | None = None,
132
+ headers: Mapping[str, str] | None = None,
133
+ ) -> dict[str, Any]:
134
+ """Send a PUT request and return JSON response."""
135
+ response = self._client.put(url, json=json, headers=headers)
136
+
137
+ if response.status_code >= 400:
138
+ _handle_error_response(response)
139
+
140
+ if response.status_code == 204 or not response.content:
141
+ return {}
142
+
143
+ return _extract_content(response.json())
144
+
145
+ def patch(
146
+ self,
147
+ url: str,
148
+ *,
149
+ json: dict[str, Any] | None = None,
150
+ headers: Mapping[str, str] | None = None,
151
+ ) -> dict[str, Any]:
152
+ """Send a PATCH request and return JSON response."""
153
+ response = self._client.patch(url, json=json, headers=headers)
154
+
155
+ if response.status_code >= 400:
156
+ _handle_error_response(response)
157
+
158
+ if response.status_code == 204 or not response.content:
159
+ return {}
160
+
161
+ return _extract_content(response.json())
162
+
163
+ def delete(
164
+ self,
165
+ url: str,
166
+ *,
167
+ json: dict[str, Any] | None = None,
168
+ headers: Mapping[str, str] | None = None,
169
+ ) -> dict[str, Any]:
170
+ """Send a DELETE request and return JSON response."""
171
+ response = self._client.request("DELETE", url, json=json, headers=headers)
172
+
173
+ if response.status_code >= 400:
174
+ _handle_error_response(response)
175
+
176
+ if response.status_code == 204 or not response.content:
177
+ return {}
178
+
179
+ return _extract_content(response.json())
180
+
181
+ def close(self) -> None:
182
+ """Close the HTTP client."""
183
+ self._client.close()
184
+
185
+ def __enter__(self) -> HTTPClient:
186
+ return self
187
+
188
+ def __exit__(self, *args: object) -> None:
189
+ self.close()
190
+
191
+
192
+ class AsyncHTTPClient:
193
+ """Asynchronous HTTP client for Chzzk API."""
194
+
195
+ def __init__(
196
+ self,
197
+ *,
198
+ timeout: float = DEFAULT_TIMEOUT,
199
+ headers: Mapping[str, str] | None = None,
200
+ ) -> None:
201
+ default_headers = {
202
+ "User-Agent": USER_AGENT,
203
+ "Content-Type": "application/json",
204
+ "Accept": "application/json",
205
+ }
206
+ if headers:
207
+ default_headers.update(headers)
208
+
209
+ self._client = httpx.AsyncClient(
210
+ timeout=timeout,
211
+ headers=default_headers,
212
+ )
213
+
214
+ async def post(
215
+ self,
216
+ url: str,
217
+ *,
218
+ params: dict[str, Any] | None = None,
219
+ json: dict[str, Any] | None = None,
220
+ headers: Mapping[str, str] | None = None,
221
+ ) -> dict[str, Any]:
222
+ """Send a POST request and return JSON response."""
223
+ response = await self._client.post(url, params=params, json=json, headers=headers)
224
+
225
+ if response.status_code >= 400:
226
+ _handle_error_response(response)
227
+
228
+ if response.status_code == 204 or not response.content:
229
+ return {}
230
+
231
+ return _extract_content(response.json())
232
+
233
+ async def get(
234
+ self,
235
+ url: str,
236
+ *,
237
+ params: dict[str, Any] | None = None,
238
+ headers: Mapping[str, str] | None = None,
239
+ ) -> dict[str, Any]:
240
+ """Send a GET request and return JSON response."""
241
+ response = await self._client.get(url, params=params, headers=headers)
242
+
243
+ if response.status_code >= 400:
244
+ _handle_error_response(response)
245
+
246
+ return _extract_content(response.json())
247
+
248
+ async def put(
249
+ self,
250
+ url: str,
251
+ *,
252
+ json: dict[str, Any] | None = None,
253
+ headers: Mapping[str, str] | None = None,
254
+ ) -> dict[str, Any]:
255
+ """Send a PUT request and return JSON response."""
256
+ response = await self._client.put(url, json=json, headers=headers)
257
+
258
+ if response.status_code >= 400:
259
+ _handle_error_response(response)
260
+
261
+ if response.status_code == 204 or not response.content:
262
+ return {}
263
+
264
+ return _extract_content(response.json())
265
+
266
+ async def patch(
267
+ self,
268
+ url: str,
269
+ *,
270
+ json: dict[str, Any] | None = None,
271
+ headers: Mapping[str, str] | None = None,
272
+ ) -> dict[str, Any]:
273
+ """Send a PATCH request and return JSON response."""
274
+ response = await self._client.patch(url, json=json, headers=headers)
275
+
276
+ if response.status_code >= 400:
277
+ _handle_error_response(response)
278
+
279
+ if response.status_code == 204 or not response.content:
280
+ return {}
281
+
282
+ return _extract_content(response.json())
283
+
284
+ async def delete(
285
+ self,
286
+ url: str,
287
+ *,
288
+ json: dict[str, Any] | None = None,
289
+ headers: Mapping[str, str] | None = None,
290
+ ) -> dict[str, Any]:
291
+ """Send a DELETE request and return JSON response."""
292
+ response = await self._client.request("DELETE", url, json=json, headers=headers)
293
+
294
+ if response.status_code >= 400:
295
+ _handle_error_response(response)
296
+
297
+ if response.status_code == 204 or not response.content:
298
+ return {}
299
+
300
+ return _extract_content(response.json())
301
+
302
+ async def close(self) -> None:
303
+ """Close the HTTP client."""
304
+ await self._client.aclose()
305
+
306
+ async def __aenter__(self) -> AsyncHTTPClient:
307
+ return self
308
+
309
+ async def __aexit__(self, *args: object) -> None:
310
+ await self.close()
@@ -0,0 +1,52 @@
1
+ """API endpoints and base URLs for Chzzk API."""
2
+
3
+ # Base URLs
4
+ CHZZK_BASE_URL = "https://chzzk.naver.com"
5
+ OPENAPI_BASE_URL = "https://openapi.chzzk.naver.com"
6
+
7
+ # Authorization endpoints
8
+ AUTH_INTERLOCK_URL = f"{CHZZK_BASE_URL}/account-interlock"
9
+ AUTH_TOKEN_URL = f"{OPENAPI_BASE_URL}/auth/v1/token"
10
+ AUTH_REVOKE_URL = f"{OPENAPI_BASE_URL}/auth/v1/token/revoke"
11
+
12
+ # Open API prefix
13
+ OPEN_API_PREFIX = f"{OPENAPI_BASE_URL}/open/v1"
14
+
15
+ # User endpoints
16
+ USER_ME_URL = f"{OPEN_API_PREFIX}/users/me"
17
+
18
+ # Channel endpoints
19
+ CHANNELS_URL = f"{OPEN_API_PREFIX}/channels"
20
+ CHANNEL_ROLES_URL = f"{OPEN_API_PREFIX}/channels/streaming-roles"
21
+ CHANNEL_FOLLOWERS_URL = f"{OPEN_API_PREFIX}/channels/followers"
22
+ CHANNEL_SUBSCRIBERS_URL = f"{OPEN_API_PREFIX}/channels/subscribers"
23
+
24
+ # Category endpoints
25
+ CATEGORIES_SEARCH_URL = f"{OPEN_API_PREFIX}/categories/search"
26
+
27
+ # Live endpoints
28
+ LIVES_URL = f"{OPEN_API_PREFIX}/lives"
29
+ STREAM_KEY_URL = f"{OPEN_API_PREFIX}/streams/key"
30
+ LIVE_SETTING_URL = f"{OPEN_API_PREFIX}/lives/setting"
31
+
32
+ # Chat endpoints
33
+ CHAT_SEND_URL = f"{OPEN_API_PREFIX}/chats/send"
34
+ CHAT_NOTICE_URL = f"{OPEN_API_PREFIX}/chats/notice"
35
+ CHAT_SETTINGS_URL = f"{OPEN_API_PREFIX}/chats/settings"
36
+
37
+ # Restriction endpoints
38
+ RESTRICT_CHANNELS_URL = f"{OPEN_API_PREFIX}/restrict-channels"
39
+
40
+ # Session endpoints
41
+ SESSIONS_AUTH_URL = f"{OPEN_API_PREFIX}/sessions/auth"
42
+ SESSIONS_AUTH_CLIENT_URL = f"{OPEN_API_PREFIX}/sessions/auth/client"
43
+ SESSIONS_URL = f"{OPEN_API_PREFIX}/sessions"
44
+ SESSIONS_CLIENT_URL = f"{OPEN_API_PREFIX}/sessions/client"
45
+ SESSIONS_SUBSCRIBE_CHAT_URL = f"{OPEN_API_PREFIX}/sessions/events/subscribe/chat"
46
+ SESSIONS_SUBSCRIBE_DONATION_URL = f"{OPEN_API_PREFIX}/sessions/events/subscribe/donation"
47
+ SESSIONS_SUBSCRIBE_SUBSCRIPTION_URL = f"{OPEN_API_PREFIX}/sessions/events/subscribe/subscription"
48
+ SESSIONS_UNSUBSCRIBE_CHAT_URL = f"{OPEN_API_PREFIX}/sessions/events/unsubscribe/chat"
49
+ SESSIONS_UNSUBSCRIBE_DONATION_URL = f"{OPEN_API_PREFIX}/sessions/events/unsubscribe/donation"
50
+ SESSIONS_UNSUBSCRIBE_SUBSCRIPTION_URL = (
51
+ f"{OPEN_API_PREFIX}/sessions/events/unsubscribe/subscription"
52
+ )
@@ -0,0 +1,86 @@
1
+ """Pydantic models for Chzzk API responses."""
2
+
3
+ from chzzk.models.category import Category
4
+ from chzzk.models.channel import (
5
+ ChannelInfo,
6
+ ChannelManager,
7
+ Follower,
8
+ Subscriber,
9
+ SubscriberSortType,
10
+ UserRole,
11
+ )
12
+ from chzzk.models.chat import (
13
+ ChatAvailableCondition,
14
+ ChatAvailableGroup,
15
+ ChatMessageResponse,
16
+ ChatSettings,
17
+ UpdateChatSettingsRequest,
18
+ )
19
+ from chzzk.models.common import CategoryType, Page
20
+ from chzzk.models.live import (
21
+ LiveInfo,
22
+ LiveListResponse,
23
+ LiveSetting,
24
+ LiveSettingCategory,
25
+ StreamKey,
26
+ UpdateLiveSettingRequest,
27
+ )
28
+ from chzzk.models.restriction import RestrictedChannel
29
+ from chzzk.models.session import (
30
+ Badge,
31
+ ChatEvent,
32
+ ChatProfile,
33
+ DonationEvent,
34
+ DonationType,
35
+ EventType,
36
+ SessionAuthResponse,
37
+ SessionInfo,
38
+ SessionListResponse,
39
+ SubscribedEvent,
40
+ SubscriptionEvent,
41
+ SystemEvent,
42
+ SystemEventData,
43
+ SystemMessageType,
44
+ UserRoleCode,
45
+ )
46
+ from chzzk.models.user import UserInfo
47
+
48
+ __all__ = [
49
+ "Badge",
50
+ "Category",
51
+ "CategoryType",
52
+ "ChannelInfo",
53
+ "ChannelManager",
54
+ "ChatAvailableCondition",
55
+ "ChatAvailableGroup",
56
+ "ChatEvent",
57
+ "ChatMessageResponse",
58
+ "ChatProfile",
59
+ "ChatSettings",
60
+ "DonationEvent",
61
+ "DonationType",
62
+ "EventType",
63
+ "Follower",
64
+ "LiveInfo",
65
+ "LiveListResponse",
66
+ "LiveSetting",
67
+ "LiveSettingCategory",
68
+ "Page",
69
+ "RestrictedChannel",
70
+ "SessionAuthResponse",
71
+ "SessionInfo",
72
+ "SessionListResponse",
73
+ "StreamKey",
74
+ "SubscribedEvent",
75
+ "Subscriber",
76
+ "SubscriberSortType",
77
+ "SubscriptionEvent",
78
+ "SystemEvent",
79
+ "SystemEventData",
80
+ "SystemMessageType",
81
+ "UpdateChatSettingsRequest",
82
+ "UpdateLiveSettingRequest",
83
+ "UserInfo",
84
+ "UserRole",
85
+ "UserRoleCode",
86
+ ]
@@ -0,0 +1,18 @@
1
+ """Pydantic models for Category API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+ from chzzk.models.common import CategoryType
8
+
9
+
10
+ class Category(BaseModel):
11
+ """Category information."""
12
+
13
+ category_type: CategoryType = Field(alias="categoryType")
14
+ category_id: str = Field(alias="categoryId")
15
+ category_value: str = Field(alias="categoryValue")
16
+ poster_image_url: str | None = Field(default=None, alias="posterImageUrl")
17
+
18
+ model_config = {"populate_by_name": True}
@@ -0,0 +1,69 @@
1
+ """Pydantic models for Channel API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from enum import StrEnum
7
+
8
+ from pydantic import BaseModel, Field
9
+
10
+
11
+ class UserRole(StrEnum):
12
+ """User role enumeration for channel management."""
13
+
14
+ STREAMING_CHANNEL_OWNER = "STREAMING_CHANNEL_OWNER"
15
+ STREAMING_CHANNEL_MANAGER = "STREAMING_CHANNEL_MANAGER"
16
+ STREAMING_CHAT_MANAGER = "STREAMING_CHAT_MANAGER"
17
+ STREAMING_SETTLEMENT_MANAGER = "STREAMING_SETTLEMENT_MANAGER"
18
+
19
+
20
+ class SubscriberSortType(StrEnum):
21
+ """Sort type enumeration for subscriber list."""
22
+
23
+ RECENT = "RECENT"
24
+ LONGER = "LONGER"
25
+
26
+
27
+ class ChannelInfo(BaseModel):
28
+ """Channel information."""
29
+
30
+ channel_id: str = Field(alias="channelId")
31
+ channel_name: str = Field(alias="channelName")
32
+ channel_image_url: str | None = Field(default=None, alias="channelImageUrl")
33
+ follower_count: int = Field(alias="followerCount")
34
+ verified_mark: bool = Field(alias="verifiedMark")
35
+
36
+ model_config = {"populate_by_name": True}
37
+
38
+
39
+ class ChannelManager(BaseModel):
40
+ """Channel manager information."""
41
+
42
+ manager_channel_id: str = Field(alias="managerChannelId")
43
+ manager_channel_name: str = Field(alias="managerChannelName")
44
+ user_role: UserRole = Field(alias="userRole")
45
+ created_date: datetime = Field(alias="createdDate")
46
+
47
+ model_config = {"populate_by_name": True}
48
+
49
+
50
+ class Follower(BaseModel):
51
+ """Follower information."""
52
+
53
+ channel_id: str = Field(alias="channelId")
54
+ channel_name: str = Field(alias="channelName")
55
+ created_date: datetime = Field(alias="createdDate")
56
+
57
+ model_config = {"populate_by_name": True}
58
+
59
+
60
+ class Subscriber(BaseModel):
61
+ """Subscriber information."""
62
+
63
+ channel_id: str = Field(alias="channelId")
64
+ channel_name: str = Field(alias="channelName")
65
+ created_date: datetime = Field(alias="createdDate")
66
+ tier_no: int = Field(alias="tierNo")
67
+ month: int
68
+
69
+ model_config = {"populate_by_name": True}
chzzk/models/chat.py ADDED
@@ -0,0 +1,63 @@
1
+ """Pydantic models for Chat API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import StrEnum
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+
10
+ class ChatAvailableCondition(StrEnum):
11
+ """Chat available condition enumeration."""
12
+
13
+ NONE = "NONE"
14
+ REAL_NAME = "REAL_NAME"
15
+
16
+
17
+ class ChatAvailableGroup(StrEnum):
18
+ """Chat available group enumeration."""
19
+
20
+ ALL = "ALL"
21
+ FOLLOWER = "FOLLOWER"
22
+ MANAGER = "MANAGER"
23
+ SUBSCRIBER = "SUBSCRIBER"
24
+
25
+
26
+ class ChatMessageResponse(BaseModel):
27
+ """Response model for sending a chat message."""
28
+
29
+ message_id: str = Field(alias="messageId")
30
+
31
+ model_config = {"populate_by_name": True}
32
+
33
+
34
+ class ChatSettings(BaseModel):
35
+ """Chat settings information."""
36
+
37
+ chat_available_condition: ChatAvailableCondition = Field(alias="chatAvailableCondition")
38
+ chat_available_group: ChatAvailableGroup = Field(alias="chatAvailableGroup")
39
+ allow_subscriber_in_follower_mode: bool = Field(alias="allowSubscriberInFollowerMode")
40
+ min_follower_minute: int = Field(alias="minFollowerMinute")
41
+ chat_emoji_mode: bool = Field(alias="chatEmojiMode")
42
+ chat_slow_mode_sec: int = Field(alias="chatSlowModeSec")
43
+
44
+ model_config = {"populate_by_name": True}
45
+
46
+
47
+ class UpdateChatSettingsRequest(BaseModel):
48
+ """Request model for updating chat settings."""
49
+
50
+ chat_available_condition: ChatAvailableCondition | None = Field(
51
+ default=None, serialization_alias="chatAvailableCondition"
52
+ )
53
+ chat_available_group: ChatAvailableGroup | None = Field(
54
+ default=None, serialization_alias="chatAvailableGroup"
55
+ )
56
+ allow_subscriber_in_follower_mode: bool | None = Field(
57
+ default=None, serialization_alias="allowSubscriberInFollowerMode"
58
+ )
59
+ min_follower_minute: int | None = Field(default=None, serialization_alias="minFollowerMinute")
60
+ chat_emoji_mode: bool | None = Field(default=None, serialization_alias="chatEmojiMode")
61
+ chat_slow_mode_sec: int | None = Field(default=None, serialization_alias="chatSlowModeSec")
62
+
63
+ model_config = {"populate_by_name": True}
chzzk/models/common.py ADDED
@@ -0,0 +1,23 @@
1
+ """Common models shared across API modules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import StrEnum
6
+
7
+ from pydantic import BaseModel
8
+
9
+
10
+ class Page(BaseModel):
11
+ """Pagination information for list responses."""
12
+
13
+ next: str | None = None
14
+
15
+ model_config = {"populate_by_name": True}
16
+
17
+
18
+ class CategoryType(StrEnum):
19
+ """Category type enumeration."""
20
+
21
+ GAME = "GAME"
22
+ SPORTS = "SPORTS"
23
+ ETC = "ETC"