Skip to main content

soup_sdk/
models.rs

1use crate::error::Result;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use serde_this_or_that::{as_bool, as_u64};
5// --- LiveDetail 관련 구조체들 ---
6
7#[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, // 1이면 방송 중, 0이면 방송 중 아님
35}
36
37#[derive(Debug, Clone, Deserialize, Serialize)]
38#[serde(rename_all = "camelCase")]
39pub struct ChannelInfo {
40    #[serde(rename = "RESULT")]
41    pub result: i32, // 1이면 방송 중, 0이면 방송 중 아님
42    #[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    // 방송 중인지 여부를 쉽게 확인할 수 있는 헬퍼 메서드
110    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}