use crate::error::Result;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_this_or_that::{as_bool, as_u64};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RawLiveDetail {
#[serde(rename = "CHANNEL")]
pub channel: ChannelInfo,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LiveDetail {
pub is_live: bool,
pub ch_domain: String,
pub ch_pt: u64,
pub ch_no: String,
pub streamer_nick: String,
pub title: String,
pub categories: Vec<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LiveDetailToCheck {
#[serde(rename = "CHANNEL")]
pub channel: ChannelInfoToCheck,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ChannelInfoToCheck {
#[serde(rename = "RESULT")]
pub result: i32, }
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ChannelInfo {
#[serde(rename = "RESULT")]
pub result: i32, #[serde(rename = "CHDOMAIN")]
pub ch_domain: String,
#[serde(rename = "CHPT", deserialize_with = "as_u64")]
pub ch_pt: u64,
#[serde(rename = "CHATNO")]
pub chat_no: String,
#[serde(rename = "BJNICK")]
pub bj_nick: String,
#[serde(rename = "TITLE")]
pub title: String,
#[serde(rename = "CATEGORY_TAGS")]
pub categories: Vec<String>,
}
impl RawVODResponse {
pub fn into_vods(self) -> Vec<VOD> {
self.data
.into_iter()
.filter(|vod| vod.auth_no == 101)
.map(|vod| VOD {
id: vod.title_no,
title: vod.title_name,
thumbnail_url: vod
.ucc
.thumb
.as_ref()
.map(|s| format!("https:{}", s))
.unwrap_or_default(),
duration: vod.ucc.total_file_duration,
reg_date: parse_soop_timestamp(&vod.reg_date),
})
.collect()
}
}
impl RawVODDetailResponse {
pub fn into_vod_detail(self) -> Result<VODDetail> {
if self.result != 1 {
return Err(crate::error::Error::ApiError("VOD not found".to_string()));
}
let data = self
.data
.ok_or_else(|| crate::error::Error::ApiError("VOD data not available".to_string()))?;
Ok(VODDetail {
id: data.title_no.to_string(),
title: data.full_title,
channel_id: data.bj_id,
broad_start: data.broad_start,
files: data
.files
.into_iter()
.map(|file| VODFile {
id: file.idx,
order: file.file_order,
file_key: file.file_info_key,
file_start: file.file_start,
chat: file.chat,
duration: file.duration,
})
.collect(),
})
}
}
impl LiveDetailToCheck {
pub fn is_streaming(&self) -> bool {
self.channel.result == 1
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RawStation {
#[serde(rename = "station")]
pub station: StationState,
#[serde(rename = "broad")]
pub broad: BroadState,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Station {
pub broad_start: DateTime<Utc>,
pub is_password: bool,
pub viewer_count: u64,
pub title: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct StationState {
#[serde(rename = "broad_start")]
pub broad_start: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BroadState {
#[serde(rename = "is_password", deserialize_with = "as_bool")]
pub is_password: bool,
#[serde(rename = "current_sum_viewer", deserialize_with = "as_u64")]
pub viewer_count: u64,
#[serde(rename = "broad_title")]
pub title: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SignatureEmoticonResponse {
#[serde(rename = "result")]
pub result: i32,
#[serde(rename = "data")]
pub data: SignatureEmoticonData,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SignatureEmoticonData {
#[serde(rename = "tier1")]
pub tier_1: Vec<Emoticon>,
#[serde(rename = "tier2")]
pub tier_2: Vec<Emoticon>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Emoticon {
#[serde(rename = "title")]
pub title: String,
#[serde(rename(serialize = "pcImg", deserialize = "pc_img"))]
pub pc_img: String,
#[serde(rename(serialize = "mobileImg", deserialize = "mobile_img"))]
pub mobile_img: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct VOD {
pub id: u64,
pub title: String,
pub thumbnail_url: String,
pub duration: u64,
pub reg_date: DateTime<Utc>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct VODDetail {
pub id: String,
pub title: String,
pub channel_id: String,
pub broad_start: String,
pub files: Vec<VODFile>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct VODFile {
pub id: u64,
pub order: u32,
pub file_key: String,
pub file_start: String,
pub chat: String,
pub duration: u64,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RawVODResponse {
data: Vec<RawVOD>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RawVOD {
title_no: u64,
title_name: String,
auth_no: u32,
reg_date: String,
ucc: RawVODUcc,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RawVODUcc {
thumb: Option<String>,
total_file_duration: u64,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RawVODDetailResponse {
result: i32,
data: Option<RawVODDetailData>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RawVODDetailData {
title_no: u64,
full_title: String,
bj_id: String,
broad_start: String,
files: Vec<RawVODDetailFile>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RawVODDetailFile {
idx: u64,
file_order: u32,
file_info_key: String,
file_start: String,
chat: String,
duration: u64,
}
pub fn parse_soop_timestamp(timestamp: &str) -> DateTime<Utc> {
let mut date = chrono::NaiveDateTime::parse_from_str(timestamp, "%Y-%m-%d %H:%M:%S")
.map_err(|e| anyhow::anyhow!("Failed to parse: {}", e))
.unwrap()
.and_utc();
date = date - chrono::Duration::hours(9);
date
}