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: Option<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 broad: Option<BroadState>,
144 pub subscription: StationSubscription,
145 pub regular: u64,
147}
148
149#[derive(Debug, Clone, Deserialize, Serialize)]
150#[serde(rename_all = "camelCase")]
151pub struct StationSubscription {
152 pub total: u64,
153 pub tier1: u64,
154 pub tier2: u64,
155}
156
157#[derive(Debug, Clone, Deserialize, Serialize)]
158#[serde(rename_all = "camelCase")]
159pub struct StationState {
160 #[serde(rename = "broad_start")]
161 pub broad_start: String,
162 #[serde(rename = "upd")]
163 pub upd: StationUpd,
164}
165
166#[derive(Debug, Clone, Deserialize, Serialize)]
167#[serde(rename_all = "camelCase")]
168pub struct StationUpd {
169 #[serde(rename = "fan_cnt", deserialize_with = "as_u64")]
170 pub fan_count: u64,
171}
172
173#[derive(Debug, Clone, Deserialize, Serialize)]
174#[serde(rename_all = "camelCase")]
175pub struct BroadState {
176 #[serde(rename = "is_password", deserialize_with = "as_bool")]
177 pub is_password: bool,
178 #[serde(rename = "current_sum_viewer", deserialize_with = "as_u64")]
179 pub viewer_count: u64,
180 #[serde(rename = "broad_title")]
181 pub title: String,
182 #[serde(rename = "broad_no", deserialize_with = "as_u64")]
183 pub bno: u64,
184 #[serde(rename = "subscription_only", deserialize_with = "as_bool")]
185 pub subscription_only: bool,
186}
187
188#[derive(Debug, Clone, Deserialize, Serialize)]
189#[serde(rename_all = "camelCase")]
190pub struct SubscriptionState {
191 #[serde(rename = "total", deserialize_with = "as_u64")]
192 pub total: u64,
193 #[serde(rename = "tier1", deserialize_with = "as_u64")]
194 pub tier1: u64,
195 #[serde(rename = "tier2", deserialize_with = "as_u64")]
196 pub tier2: u64,
197}
198
199#[derive(Debug, Clone, Deserialize, Serialize)]
200pub struct SignatureEmoticonResponse {
201 #[serde(rename = "result")]
202 pub result: i32,
203 #[serde(rename = "data")]
204 pub data: SignatureEmoticonData,
205}
206
207#[derive(Debug, Clone, Deserialize, Serialize)]
208#[serde(rename_all = "camelCase")]
209pub struct SignatureEmoticonData {
210 #[serde(rename = "tier1")]
211 pub tier_1: Vec<Emoticon>,
212 #[serde(rename = "tier2")]
213 pub tier_2: Vec<Emoticon>,
214}
215
216#[derive(Debug, Clone, Deserialize, Serialize)]
217pub struct Emoticon {
218 #[serde(rename = "title")]
219 pub title: String,
220 #[serde(rename(serialize = "pcImg", deserialize = "pc_img"))]
221 pub pc_img: String,
222 #[serde(rename(serialize = "mobileImg", deserialize = "mobile_img"))]
223 pub mobile_img: String,
224}
225
226#[derive(Debug, Clone, Deserialize, Serialize)]
227#[serde(rename_all = "camelCase")]
228pub struct VOD {
229 pub id: u64,
230 pub title: String,
231 pub thumbnail_url: String,
232 pub duration: u64,
233 pub reg_date: DateTime<Utc>,
234}
235
236#[derive(Debug, Clone, Deserialize, Serialize)]
237#[serde(rename_all = "camelCase")]
238pub struct VODDetail {
239 pub id: String,
240 pub title: String,
241 pub channel_id: String,
242 pub broad_start: String,
243 pub files: Vec<VODFile>,
244}
245
246#[derive(Debug, Clone, Deserialize, Serialize)]
247#[serde(rename_all = "camelCase")]
248pub struct VODFile {
249 pub id: u64,
250 pub order: u32,
251 pub file_key: String,
252 pub file_start: String,
253 pub chat: String,
254 pub duration: u64,
255}
256
257#[derive(Debug, Clone, Serialize)]
258#[serde(rename_all = "camelCase")]
259pub struct VodChatCollection {
260 pub events: Vec<Event>,
261 pub chunks: Vec<VodChatChunk>,
262 pub failures: Vec<VodChatChunkFailure>,
263}
264
265#[derive(Debug, Clone, Deserialize, Serialize)]
266#[serde(rename_all = "camelCase")]
267pub struct VodChatChunk {
268 pub file_id: u64,
269 pub file_order: u32,
270 pub start_time: u64,
271 pub event_count: usize,
272}
273
274#[derive(Debug, Clone, Deserialize, Serialize)]
275#[serde(rename_all = "camelCase")]
276pub struct VodChatChunkFailure {
277 pub file_id: u64,
278 pub file_order: u32,
279 pub start_time: u64,
280 pub reason: String,
281}
282
283#[derive(Debug, Clone, Deserialize, Serialize)]
284pub struct RawVODResponse {
285 data: Vec<RawVOD>,
286}
287
288#[derive(Debug, Clone, Deserialize, Serialize)]
289pub struct RawVOD {
290 title_no: u64,
291 title_name: String,
292 auth_no: u32,
293 reg_date: String,
294 ucc: RawVODUcc,
295}
296
297#[derive(Debug, Clone, Deserialize, Serialize)]
298pub struct RawVODUcc {
299 thumb: Option<String>,
300 total_file_duration: u64,
301}
302
303#[derive(Debug, Clone, Deserialize, Serialize)]
304pub struct RawVODDetailResponse {
305 result: i32,
306 data: Option<RawVODDetailData>,
307}
308
309#[derive(Debug, Clone, Deserialize, Serialize)]
310pub struct RawVODDetailData {
311 title_no: u64,
312 full_title: String,
313 bj_id: String,
314 broad_start: String,
315 files: Vec<RawVODDetailFile>,
316}
317
318#[derive(Debug, Clone, Deserialize, Serialize)]
319pub struct RawVODDetailFile {
320 idx: u64,
321 file_order: u32,
322 file_info_key: String,
323 file_start: String,
324 chat: String,
325 duration: u64,
326}
327
328pub fn parse_soop_timestamp(timestamp: &str) -> Result<DateTime<Utc>> {
329 let mut date = chrono::NaiveDateTime::parse_from_str(timestamp, "%Y-%m-%d %H:%M:%S")
330 .map_err(|e| crate::error::Error::Decode(format!("timestamp parse failed: {e}")))?
331 .and_utc();
332
333 date -= chrono::Duration::hours(9);
334 Ok(date)
335}