1use crate::error::Result;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use serde_this_or_that::{as_bool, as_u64};
5#[derive(Debug, Clone, Deserialize, Serialize)]
8pub struct RawLiveDetail {
9 #[serde(rename = "CHANNEL")]
10 pub channel: ChannelInfo,
11}
12
13#[derive(Debug, Clone, Deserialize, Serialize)]
14#[serde(rename_all = "camelCase")]
15pub struct LiveDetail {
16 pub is_live: bool,
17 pub ch_domain: String,
18 pub ch_pt: u64,
19 pub ch_no: String,
20 pub streamer_nick: String,
21 pub title: String,
22 pub categories: Vec<String>,
23}
24
25#[derive(Debug, Clone, Deserialize, Serialize)]
26pub struct LiveDetailToCheck {
27 #[serde(rename = "CHANNEL")]
28 pub channel: ChannelInfoToCheck,
29}
30
31#[derive(Debug, Clone, Deserialize, Serialize)]
32pub struct ChannelInfoToCheck {
33 #[serde(rename = "RESULT")]
34 pub result: i32, }
36
37#[derive(Debug, Clone, Deserialize, Serialize)]
38#[serde(rename_all = "camelCase")]
39pub struct ChannelInfo {
40 #[serde(rename = "RESULT")]
41 pub result: i32, #[serde(rename = "CHDOMAIN")]
43 pub ch_domain: String,
44 #[serde(rename = "CHPT", deserialize_with = "as_u64")]
45 pub ch_pt: u64,
46 #[serde(rename = "CHATNO")]
47 pub chat_no: String,
48 #[serde(rename = "BJNICK")]
49 pub bj_nick: String,
50 #[serde(rename = "TITLE")]
51 pub title: String,
52 #[serde(rename = "CATEGORY_TAGS")]
53 pub categories: Vec<String>,
54}
55
56impl RawVODResponse {
57 pub fn into_vods(self) -> Vec<VOD> {
58 self.data
59 .into_iter()
60 .filter(|vod| vod.auth_no == 101)
61 .map(|vod| VOD {
62 id: vod.title_no,
63 title: vod.title_name,
64 thumbnail_url: vod
65 .ucc
66 .thumb
67 .as_ref()
68 .map(|s| format!("https:{}", s))
69 .unwrap_or_default(),
70 duration: vod.ucc.total_file_duration,
71 reg_date: parse_soop_timestamp(&vod.reg_date),
72 })
73 .collect()
74 }
75}
76
77impl RawVODDetailResponse {
78 pub fn into_vod_detail(self) -> Result<VODDetail> {
79 if self.result != 1 {
80 return Err(crate::error::Error::ApiError("VOD not found".to_string()));
81 }
82
83 let data = self
84 .data
85 .ok_or_else(|| crate::error::Error::ApiError("VOD data not available".to_string()))?;
86
87 Ok(VODDetail {
88 id: data.title_no.to_string(),
89 title: data.full_title,
90 channel_id: data.bj_id,
91 broad_start: data.broad_start,
92 files: data
93 .files
94 .into_iter()
95 .map(|file| VODFile {
96 id: file.idx,
97 order: file.file_order,
98 file_key: file.file_info_key,
99 file_start: file.file_start,
100 chat: file.chat,
101 duration: file.duration,
102 })
103 .collect(),
104 })
105 }
106}
107
108impl LiveDetailToCheck {
109 pub fn is_streaming(&self) -> bool {
111 self.channel.result == 1
112 }
113}
114
115#[derive(Debug, Clone, Deserialize, Serialize)]
116pub struct RawStation {
117 #[serde(rename = "station")]
118 pub station: StationState,
119 #[serde(rename = "broad")]
120 pub broad: BroadState,
121}
122
123#[derive(Debug, Clone, Deserialize, Serialize)]
124#[serde(rename_all = "camelCase")]
125pub struct Station {
126 pub broad_start: DateTime<Utc>,
127 pub is_password: bool,
128 pub viewer_count: u64,
129 pub title: String,
130}
131
132#[derive(Debug, Clone, Deserialize, Serialize)]
133#[serde(rename_all = "camelCase")]
134pub struct StationState {
135 #[serde(rename = "broad_start")]
136 pub broad_start: String,
137}
138
139#[derive(Debug, Clone, Deserialize, Serialize)]
140#[serde(rename_all = "camelCase")]
141pub struct BroadState {
142 #[serde(rename = "is_password", deserialize_with = "as_bool")]
143 pub is_password: bool,
144 #[serde(rename = "current_sum_viewer", deserialize_with = "as_u64")]
145 pub viewer_count: u64,
146 #[serde(rename = "broad_title")]
147 pub title: String,
148}
149
150#[derive(Debug, Clone, Deserialize, Serialize)]
151pub struct SignatureEmoticonResponse {
152 #[serde(rename = "result")]
153 pub result: i32,
154 #[serde(rename = "data")]
155 pub data: SignatureEmoticonData,
156}
157
158#[derive(Debug, Clone, Deserialize, Serialize)]
159#[serde(rename_all = "camelCase")]
160pub struct SignatureEmoticonData {
161 #[serde(rename = "tier1")]
162 pub tier_1: Vec<Emoticon>,
163 #[serde(rename = "tier2")]
164 pub tier_2: Vec<Emoticon>,
165}
166
167#[derive(Debug, Clone, Deserialize, Serialize)]
168pub struct Emoticon {
169 #[serde(rename = "title")]
170 pub title: String,
171 #[serde(rename(serialize = "pcImg", deserialize = "pc_img"))]
172 pub pc_img: String,
173 #[serde(rename(serialize = "mobileImg", deserialize = "mobile_img"))]
174 pub mobile_img: String,
175}
176
177#[derive(Debug, Clone, Deserialize, Serialize)]
178#[serde(rename_all = "camelCase")]
179pub struct VOD {
180 pub id: u64,
181 pub title: String,
182 pub thumbnail_url: String,
183 pub duration: u64,
184 pub reg_date: DateTime<Utc>,
185}
186
187#[derive(Debug, Clone, Deserialize, Serialize)]
188#[serde(rename_all = "camelCase")]
189pub struct VODDetail {
190 pub id: String,
191 pub title: String,
192 pub channel_id: String,
193 pub broad_start: String,
194 pub files: Vec<VODFile>,
195}
196
197#[derive(Debug, Clone, Deserialize, Serialize)]
198#[serde(rename_all = "camelCase")]
199pub struct VODFile {
200 pub id: u64,
201 pub order: u32,
202 pub file_key: String,
203 pub file_start: String,
204 pub chat: String,
205 pub duration: u64,
206}
207
208#[derive(Debug, Clone, Deserialize, Serialize)]
209pub struct RawVODResponse {
210 data: Vec<RawVOD>,
211}
212
213#[derive(Debug, Clone, Deserialize, Serialize)]
214pub struct RawVOD {
215 title_no: u64,
216 title_name: String,
217 auth_no: u32,
218 reg_date: String,
219 ucc: RawVODUcc,
220}
221
222#[derive(Debug, Clone, Deserialize, Serialize)]
223pub struct RawVODUcc {
224 thumb: Option<String>,
225 total_file_duration: u64,
226}
227
228#[derive(Debug, Clone, Deserialize, Serialize)]
229pub struct RawVODDetailResponse {
230 result: i32,
231 data: Option<RawVODDetailData>,
232}
233
234#[derive(Debug, Clone, Deserialize, Serialize)]
235pub struct RawVODDetailData {
236 title_no: u64,
237 full_title: String,
238 bj_id: String,
239 broad_start: String,
240 files: Vec<RawVODDetailFile>,
241}
242
243#[derive(Debug, Clone, Deserialize, Serialize)]
244pub struct RawVODDetailFile {
245 idx: u64,
246 file_order: u32,
247 file_info_key: String,
248 file_start: String,
249 chat: String,
250 duration: u64,
251}
252
253pub fn parse_soop_timestamp(timestamp: &str) -> DateTime<Utc> {
254 let mut date = chrono::NaiveDateTime::parse_from_str(timestamp, "%Y-%m-%d %H:%M:%S")
255 .map_err(|e| anyhow::anyhow!("Failed to parse: {}", e))
256 .unwrap()
257 .and_utc();
258
259 date = date - chrono::Duration::hours(9);
260 date
261}