1use crate::error::Result;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use serde_this_or_that::{as_bool, as_u64};
5
6use crate::chat::events::Event;
7#[derive(Debug, Clone, Deserialize, Serialize)]
10pub struct RawLiveDetail {
11 #[serde(rename = "CHANNEL")]
12 pub channel: ChannelInfo,
13}
14
15#[derive(Debug, Clone, Deserialize, Serialize)]
16#[serde(rename_all = "camelCase")]
17pub struct LiveDetail {
18 pub is_live: bool,
19 pub ch_domain: String,
20 pub ch_pt: u64,
21 pub ch_no: String,
22 pub bno: u64,
23 pub streamer_nick: String,
24 pub title: String,
25 pub categories: Vec<String>,
26}
27
28#[derive(Debug, Clone, Deserialize, Serialize)]
29#[serde(rename_all = "camelCase")]
30pub enum LiveStatus {
31 Offline,
32 Live(LiveDetail),
33}
34
35#[derive(Debug, Clone, Deserialize, Serialize)]
36pub struct LiveDetailToCheck {
37 #[serde(rename = "CHANNEL")]
38 pub channel: ChannelInfoToCheck,
39}
40
41#[derive(Debug, Clone, Deserialize, Serialize)]
42pub struct ChannelInfoToCheck {
43 #[serde(rename = "RESULT")]
44 pub result: i32, }
46
47#[derive(Debug, Clone, Deserialize, Serialize)]
48#[serde(rename_all = "camelCase")]
49pub struct ChannelInfo {
50 #[serde(rename = "RESULT")]
51 pub result: i32, #[serde(rename = "BNO", deserialize_with = "as_u64")]
53 pub bno: u64,
54 #[serde(rename = "CHDOMAIN")]
55 pub ch_domain: String,
56 #[serde(rename = "CHPT", deserialize_with = "as_u64")]
57 pub ch_pt: u64,
58 #[serde(rename = "CHATNO")]
59 pub chat_no: String,
60 #[serde(rename = "BJNICK")]
61 pub bj_nick: String,
62 #[serde(rename = "TITLE")]
63 pub title: String,
64 #[serde(rename = "CATEGORY_TAGS")]
65 pub categories: Vec<String>,
66}
67
68impl RawVODResponse {
69 pub fn into_vods(self) -> Result<Vec<VOD>> {
70 self.data
71 .into_iter()
72 .filter(|vod| vod.auth_no == 101)
73 .map(|vod| {
74 Ok(VOD {
75 id: vod.title_no,
76 title: vod.title_name,
77 thumbnail_url: vod
78 .ucc
79 .thumb
80 .as_ref()
81 .map(|s| format!("https:{}", s))
82 .unwrap_or_default(),
83 duration: vod.ucc.total_file_duration,
84 reg_date: parse_soop_timestamp(&vod.reg_date)?,
85 })
86 })
87 .collect()
88 }
89}
90
91impl RawVODDetailResponse {
92 pub fn into_vod_detail(self) -> Result<VODDetail> {
93 if self.result != 1 {
94 return Err(crate::error::Error::Protocol("VOD not found".to_string()));
95 }
96
97 let data = self
98 .data
99 .ok_or_else(|| crate::error::Error::Protocol("VOD data not available".to_string()))?;
100
101 Ok(VODDetail {
102 id: data.title_no.to_string(),
103 title: data.full_title,
104 channel_id: data.bj_id,
105 broad_start: data.broad_start,
106 files: data
107 .files
108 .into_iter()
109 .map(|file| VODFile {
110 id: file.idx,
111 order: file.file_order,
112 file_key: file.file_info_key,
113 file_start: file.file_start,
114 chat: file.chat,
115 duration: file.duration,
116 })
117 .collect(),
118 })
119 }
120}
121
122impl LiveDetailToCheck {
123 pub fn is_streaming(&self) -> bool {
125 self.channel.result == 1
126 }
127}
128
129#[derive(Debug, Clone, Deserialize, Serialize)]
130pub struct RawStation {
131 #[serde(rename = "station")]
132 pub station: StationState,
133 #[serde(rename = "broad")]
134 pub broad: BroadState,
135 #[serde(rename = "subscription")]
136 pub subscription: SubscriptionState,
137}
138
139#[derive(Debug, Clone, Deserialize, Serialize)]
140#[serde(rename_all = "camelCase")]
141pub struct Station {
142 pub broad_start: DateTime<Utc>,
143 pub is_password: bool,
144 pub viewer_count: u64,
145 pub title: String,
146 pub subscription: StationSubscription,
147 pub regular: u64,
149}
150
151#[derive(Debug, Clone, Deserialize, Serialize)]
152#[serde(rename_all = "camelCase")]
153pub struct StationSubscription {
154 pub total: u64,
155 pub tier1: u64,
156 pub tier2: u64,
157}
158
159#[derive(Debug, Clone, Deserialize, Serialize)]
160#[serde(rename_all = "camelCase")]
161pub struct StationState {
162 #[serde(rename = "broad_start")]
163 pub broad_start: String,
164 #[serde(rename = "upd")]
165 pub upd: StationUpd,
166}
167
168#[derive(Debug, Clone, Deserialize, Serialize)]
169#[serde(rename_all = "camelCase")]
170pub struct StationUpd {
171 #[serde(rename = "fan_cnt", deserialize_with = "as_u64")]
172 pub fan_count: u64,
173}
174
175#[derive(Debug, Clone, Deserialize, Serialize)]
176#[serde(rename_all = "camelCase")]
177pub struct BroadState {
178 #[serde(rename = "is_password", deserialize_with = "as_bool")]
179 pub is_password: bool,
180 #[serde(rename = "current_sum_viewer", deserialize_with = "as_u64")]
181 pub viewer_count: u64,
182 #[serde(rename = "broad_title")]
183 pub title: String,
184}
185
186#[derive(Debug, Clone, Deserialize, Serialize)]
187#[serde(rename_all = "camelCase")]
188pub struct SubscriptionState {
189 #[serde(rename = "total", deserialize_with = "as_u64")]
190 pub total: u64,
191 #[serde(rename = "tier1", deserialize_with = "as_u64")]
192 pub tier1: u64,
193 #[serde(rename = "tier2", deserialize_with = "as_u64")]
194 pub tier2: u64,
195}
196
197#[derive(Debug, Clone, Deserialize, Serialize)]
198pub struct SignatureEmoticonResponse {
199 #[serde(rename = "result")]
200 pub result: i32,
201 #[serde(rename = "data")]
202 pub data: SignatureEmoticonData,
203}
204
205#[derive(Debug, Clone, Deserialize, Serialize)]
206#[serde(rename_all = "camelCase")]
207pub struct SignatureEmoticonData {
208 #[serde(rename = "tier1")]
209 pub tier_1: Vec<Emoticon>,
210 #[serde(rename = "tier2")]
211 pub tier_2: Vec<Emoticon>,
212}
213
214#[derive(Debug, Clone, Deserialize, Serialize)]
215pub struct Emoticon {
216 #[serde(rename = "title")]
217 pub title: String,
218 #[serde(rename(serialize = "pcImg", deserialize = "pc_img"))]
219 pub pc_img: String,
220 #[serde(rename(serialize = "mobileImg", deserialize = "mobile_img"))]
221 pub mobile_img: String,
222}
223
224#[derive(Debug, Clone, Deserialize, Serialize)]
225#[serde(rename_all = "camelCase")]
226pub struct VOD {
227 pub id: u64,
228 pub title: String,
229 pub thumbnail_url: String,
230 pub duration: u64,
231 pub reg_date: DateTime<Utc>,
232}
233
234#[derive(Debug, Clone, Deserialize, Serialize)]
235#[serde(rename_all = "camelCase")]
236pub struct VODDetail {
237 pub id: String,
238 pub title: String,
239 pub channel_id: String,
240 pub broad_start: String,
241 pub files: Vec<VODFile>,
242}
243
244#[derive(Debug, Clone, Deserialize, Serialize)]
245#[serde(rename_all = "camelCase")]
246pub struct VODFile {
247 pub id: u64,
248 pub order: u32,
249 pub file_key: String,
250 pub file_start: String,
251 pub chat: String,
252 pub duration: u64,
253}
254
255#[derive(Debug, Clone, Serialize)]
256#[serde(rename_all = "camelCase")]
257pub struct VodChatCollection {
258 pub events: Vec<Event>,
259 pub chunks: Vec<VodChatChunk>,
260 pub failures: Vec<VodChatChunkFailure>,
261}
262
263#[derive(Debug, Clone, Deserialize, Serialize)]
264#[serde(rename_all = "camelCase")]
265pub struct VodChatChunk {
266 pub file_id: u64,
267 pub file_order: u32,
268 pub start_time: u64,
269 pub event_count: usize,
270}
271
272#[derive(Debug, Clone, Deserialize, Serialize)]
273#[serde(rename_all = "camelCase")]
274pub struct VodChatChunkFailure {
275 pub file_id: u64,
276 pub file_order: u32,
277 pub start_time: u64,
278 pub reason: String,
279}
280
281#[derive(Debug, Clone, Deserialize, Serialize)]
282pub struct RawVODResponse {
283 data: Vec<RawVOD>,
284}
285
286#[derive(Debug, Clone, Deserialize, Serialize)]
287pub struct RawVOD {
288 title_no: u64,
289 title_name: String,
290 auth_no: u32,
291 reg_date: String,
292 ucc: RawVODUcc,
293}
294
295#[derive(Debug, Clone, Deserialize, Serialize)]
296pub struct RawVODUcc {
297 thumb: Option<String>,
298 total_file_duration: u64,
299}
300
301#[derive(Debug, Clone, Deserialize, Serialize)]
302pub struct RawVODDetailResponse {
303 result: i32,
304 data: Option<RawVODDetailData>,
305}
306
307#[derive(Debug, Clone, Deserialize, Serialize)]
308pub struct RawVODDetailData {
309 title_no: u64,
310 full_title: String,
311 bj_id: String,
312 broad_start: String,
313 files: Vec<RawVODDetailFile>,
314}
315
316#[derive(Debug, Clone, Deserialize, Serialize)]
317pub struct RawVODDetailFile {
318 idx: u64,
319 file_order: u32,
320 file_info_key: String,
321 file_start: String,
322 chat: String,
323 duration: u64,
324}
325
326pub fn parse_soop_timestamp(timestamp: &str) -> Result<DateTime<Utc>> {
327 let mut date = chrono::NaiveDateTime::parse_from_str(timestamp, "%Y-%m-%d %H:%M:%S")
328 .map_err(|e| crate::error::Error::Decode(format!("timestamp parse failed: {e}")))?
329 .and_utc();
330
331 date -= chrono::Duration::hours(9);
332 Ok(date)
333}