1use serde::Deserialize;
2
3#[derive(Debug, Clone, Deserialize)]
25pub struct ChannelInfo {
26 pub id: u64,
28
29 pub user_id: u64,
31
32 pub slug: String,
34
35 #[serde(default)]
37 pub is_banned: bool,
38
39 #[serde(default)]
41 pub vod_enabled: bool,
42
43 #[serde(default)]
45 pub subscription_enabled: bool,
46
47 #[serde(default)]
49 pub is_affiliate: bool,
50
51 #[serde(default)]
53 pub verified: bool,
54
55 #[serde(default)]
57 pub followers_count: u64,
58
59 #[serde(default)]
61 pub can_host: bool,
62
63 pub chatroom: ChatroomInfo,
65
66 #[serde(default)]
68 pub subscriber_badges: Vec<SubscriberBadge>,
69
70 #[serde(default)]
72 pub user: Option<ChannelUser>,
73
74 #[serde(default)]
76 pub livestream: Option<LivestreamInfo>,
77}
78
79#[derive(Debug, Clone, Deserialize)]
81pub struct ChatroomInfo {
82 pub id: u64,
84
85 #[serde(default)]
87 pub channel_id: Option<u64>,
88
89 #[serde(default)]
91 pub chat_mode: Option<String>,
92
93 #[serde(default)]
95 pub slow_mode: bool,
96
97 #[serde(default)]
99 pub followers_mode: bool,
100
101 #[serde(default)]
103 pub subscribers_mode: bool,
104
105 #[serde(default)]
107 pub emotes_mode: bool,
108
109 #[serde(default)]
111 pub message_interval: u32,
112
113 #[serde(default)]
115 pub following_min_duration: u32,
116}
117
118#[derive(Debug, Clone, Deserialize)]
120pub struct SubscriberBadge {
121 pub id: u64,
123
124 #[serde(default)]
126 pub channel_id: Option<u64>,
127
128 pub months: u32,
130
131 pub badge_image: BadgeImage,
133}
134
135#[derive(Debug, Clone, Deserialize)]
137pub struct BadgeImage {
138 pub src: String,
140
141 #[serde(default)]
143 pub srcset: Option<String>,
144}
145
146#[derive(Debug, Clone, Deserialize)]
148pub struct ChannelUser {
149 pub id: u64,
151
152 pub username: String,
154
155 #[serde(default)]
157 pub bio: Option<String>,
158
159 #[serde(default)]
161 pub profile_pic: Option<String>,
162
163 #[serde(default)]
165 pub instagram: Option<String>,
166 #[serde(default)]
167 pub twitter: Option<String>,
168 #[serde(default)]
169 pub youtube: Option<String>,
170 #[serde(default)]
171 pub discord: Option<String>,
172 #[serde(default)]
173 pub tiktok: Option<String>,
174}
175
176#[derive(Debug, Clone, Deserialize)]
178pub struct LivestreamInfo {
179 pub id: u64,
181
182 #[serde(default)]
184 pub slug: Option<String>,
185
186 #[serde(default)]
188 pub channel_id: Option<u64>,
189
190 #[serde(default)]
192 pub session_title: Option<String>,
193
194 #[serde(default)]
196 pub is_live: bool,
197
198 #[serde(default)]
200 pub is_mature: bool,
201
202 #[serde(default)]
204 pub language: Option<String>,
205
206 #[serde(default)]
208 pub viewer_count: u64,
209
210 #[serde(default)]
212 pub start_time: Option<String>,
213
214 #[serde(default)]
216 pub tags: Vec<String>,
217
218 #[serde(default)]
220 pub categories: Vec<LivestreamCategory>,
221}
222
223#[derive(Debug, Clone, Deserialize)]
225pub struct LivestreamCategory {
226 pub id: u64,
228
229 pub name: String,
231
232 pub slug: String,
234
235 #[serde(default)]
237 pub tags: Vec<String>,
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn test_deserialize_channel_info() {
246 let json = r##"{
247 "id": 44192640,
248 "user_id": 45297540,
249 "slug": "hello_kiko",
250 "is_banned": false,
251 "vod_enabled": true,
252 "subscription_enabled": true,
253 "is_affiliate": true,
254 "verified": true,
255 "followers_count": 8393,
256 "can_host": true,
257 "chatroom": {
258 "id": 43904307,
259 "channel_id": 44192640,
260 "chat_mode": "public",
261 "slow_mode": false,
262 "followers_mode": false,
263 "subscribers_mode": false,
264 "emotes_mode": false,
265 "message_interval": 5,
266 "following_min_duration": 0
267 },
268 "subscriber_badges": [
269 {
270 "id": 499365,
271 "channel_id": 44192640,
272 "months": 1,
273 "badge_image": {
274 "srcset": "",
275 "src": "https://files.kick.com/channel_subscriber_badges/499365/original"
276 }
277 },
278 {
279 "id": 499366,
280 "channel_id": 44192640,
281 "months": 2,
282 "badge_image": {
283 "srcset": "",
284 "src": "https://files.kick.com/channel_subscriber_badges/499366/original"
285 }
286 }
287 ],
288 "user": {
289 "id": 45297540,
290 "username": "hello_kiko",
291 "bio": "Hi I'm Kiko",
292 "profile_pic": "https://files.kick.com/images/user/45297540/profile_image.webp",
293 "instagram": "bye.kiko",
294 "twitter": "hello_kiko",
295 "youtube": "hello_kiko",
296 "discord": "hellokiko",
297 "tiktok": "bye.kiko"
298 },
299 "livestream": {
300 "id": 103692434,
301 "slug": "some-stream-slug",
302 "channel_id": 44192640,
303 "session_title": "Just Chatting stream!",
304 "is_live": true,
305 "is_mature": false,
306 "language": "English",
307 "viewer_count": 99,
308 "start_time": "2026-04-06 05:03:57",
309 "tags": ["Japan", "Korean"],
310 "categories": [
311 {
312 "id": 15,
313 "name": "Just Chatting",
314 "slug": "just-chatting",
315 "tags": ["IRL", "Casual"]
316 }
317 ]
318 }
319 }"##;
320
321 let info: ChannelInfo = serde_json::from_str(json).unwrap();
322
323 assert_eq!(info.id, 44192640);
325 assert_eq!(info.user_id, 45297540);
326 assert_eq!(info.slug, "hello_kiko");
327 assert!(!info.is_banned);
328 assert!(info.verified);
329 assert!(info.is_affiliate);
330 assert_eq!(info.followers_count, 8393);
331
332 assert_eq!(info.chatroom.id, 43904307);
334 assert_eq!(info.chatroom.chat_mode, Some("public".into()));
335 assert!(!info.chatroom.slow_mode);
336 assert!(!info.chatroom.followers_mode);
337 assert!(!info.chatroom.subscribers_mode);
338 assert_eq!(info.chatroom.message_interval, 5);
339
340 assert_eq!(info.subscriber_badges.len(), 2);
342 assert_eq!(info.subscriber_badges[0].months, 1);
343 assert!(info.subscriber_badges[0].badge_image.src.contains("499365"));
344 assert_eq!(info.subscriber_badges[1].months, 2);
345
346 let user = info.user.unwrap();
348 assert_eq!(user.username, "hello_kiko");
349 assert_eq!(user.bio, Some("Hi I'm Kiko".into()));
350 assert_eq!(user.discord, Some("hellokiko".into()));
351
352 let stream = info.livestream.unwrap();
354 assert!(stream.is_live);
355 assert_eq!(stream.viewer_count, 99);
356 assert_eq!(stream.session_title, Some("Just Chatting stream!".into()));
357 assert_eq!(stream.tags, vec!["Japan", "Korean"]);
358 assert_eq!(stream.categories.len(), 1);
359 assert_eq!(stream.categories[0].name, "Just Chatting");
360 }
361
362 #[test]
363 fn test_deserialize_offline_channel() {
364 let json = r##"{
365 "id": 12345,
366 "user_id": 67890,
367 "slug": "offline_user",
368 "chatroom": {
369 "id": 11111,
370 "slow_mode": true,
371 "followers_mode": true,
372 "subscribers_mode": false,
373 "emotes_mode": false,
374 "message_interval": 10,
375 "following_min_duration": 30
376 },
377 "subscriber_badges": [],
378 "livestream": null
379 }"##;
380
381 let info: ChannelInfo = serde_json::from_str(json).unwrap();
382 assert_eq!(info.slug, "offline_user");
383 assert!(info.livestream.is_none());
384 assert!(info.chatroom.slow_mode);
385 assert!(info.chatroom.followers_mode);
386 assert_eq!(info.chatroom.message_interval, 10);
387 assert_eq!(info.chatroom.following_min_duration, 30);
388 assert!(info.subscriber_badges.is_empty());
389 assert!(info.user.is_none());
390 }
391
392 #[test]
393 fn test_deserialize_minimal_channel() {
394 let json = r##"{
396 "id": 1,
397 "user_id": 2,
398 "slug": "test",
399 "chatroom": { "id": 3 }
400 }"##;
401
402 let info: ChannelInfo = serde_json::from_str(json).unwrap();
403 assert_eq!(info.id, 1);
404 assert_eq!(info.chatroom.id, 3);
405 assert!(!info.chatroom.slow_mode);
406 assert_eq!(info.followers_count, 0);
407 assert!(!info.verified);
408 assert!(info.subscriber_badges.is_empty());
409 }
410}