Skip to main content

bpi_rs/cheese/
params.rs

1use crate::ids::{Aid, Cid, EpisodeId, SeasonId};
2use crate::models::{Fnval, VideoQuality};
3use crate::{BpiError, BpiResult};
4
5/// Identifies a cheese course request by season ID or episode ID.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum CheeseInfoId {
8    Season(SeasonId),
9    Episode(EpisodeId),
10}
11
12/// Parameters for `/pugv/view/web/season`.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct CheeseInfoParams {
15    id: CheeseInfoId,
16}
17
18impl CheeseInfoParams {
19    pub fn from_season_id(season_id: SeasonId) -> Self {
20        Self {
21            id: CheeseInfoId::Season(season_id),
22        }
23    }
24
25    pub fn from_episode_id(episode_id: EpisodeId) -> Self {
26        Self {
27            id: CheeseInfoId::Episode(episode_id),
28        }
29    }
30
31    pub(crate) fn query_pairs(&self) -> Vec<(&'static str, String)> {
32        match self.id {
33            CheeseInfoId::Season(season_id) => vec![("season_id", season_id.to_string())],
34            CheeseInfoId::Episode(episode_id) => vec![("ep_id", episode_id.to_string())],
35        }
36    }
37}
38
39/// Parameters for `/pugv/view/web/ep/list`.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub struct CheeseEpListParams {
42    season_id: SeasonId,
43    page_size: Option<u32>,
44    page: Option<u32>,
45}
46
47impl CheeseEpListParams {
48    pub fn new(season_id: SeasonId) -> Self {
49        Self {
50            season_id,
51            page_size: None,
52            page: None,
53        }
54    }
55
56    pub fn with_page_size(mut self, page_size: u32) -> BpiResult<Self> {
57        self.page_size = Some(validate_positive("ps", page_size)?);
58        Ok(self)
59    }
60
61    pub fn with_page(mut self, page: u32) -> BpiResult<Self> {
62        self.page = Some(validate_positive("pn", page)?);
63        Ok(self)
64    }
65
66    pub(crate) fn query_pairs(&self) -> Vec<(&'static str, String)> {
67        let mut params = vec![("season_id", self.season_id.to_string())];
68
69        if let Some(page_size) = self.page_size {
70            params.push(("ps", page_size.to_string()));
71        }
72
73        if let Some(page) = self.page {
74            params.push(("pn", page.to_string()));
75        }
76
77        params
78    }
79}
80
81/// Parameters for `/pugv/player/web/playurl`.
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub struct CheeseVideoStreamParams {
84    aid: Aid,
85    episode_id: EpisodeId,
86    cid: Cid,
87    quality: Option<VideoQuality>,
88    fnval: Option<Fnval>,
89}
90
91impl CheeseVideoStreamParams {
92    pub fn new(aid: Aid, episode_id: EpisodeId, cid: Cid) -> Self {
93        Self {
94            aid,
95            episode_id,
96            cid,
97            quality: None,
98            fnval: None,
99        }
100    }
101
102    pub fn with_quality(mut self, quality: VideoQuality) -> Self {
103        self.quality = Some(quality);
104        self
105    }
106
107    pub fn with_fnval(mut self, fnval: Fnval) -> Self {
108        self.fnval = Some(fnval);
109        self
110    }
111
112    pub(crate) fn query_pairs(&self) -> Vec<(&'static str, String)> {
113        let mut params = vec![
114            ("avid", self.aid.to_string()),
115            ("ep_id", self.episode_id.to_string()),
116            ("cid", self.cid.to_string()),
117            ("fnver", "0".to_string()),
118        ];
119
120        if self.fnval.is_some_and(|fnval| fnval.is_fourk()) {
121            params.push(("fourk", "1".to_string()));
122        }
123
124        if let Some(quality) = self.quality {
125            params.push(("qn", quality.as_u32().to_string()));
126        }
127
128        if let Some(fnval) = self.fnval {
129            params.push(("fnval", fnval.bits().to_string()));
130        }
131
132        params
133    }
134}
135
136fn validate_positive(field: &'static str, value: u32) -> BpiResult<u32> {
137    if value == 0 {
138        return Err(BpiError::invalid_parameter(
139            field,
140            "value must be greater than zero",
141        ));
142    }
143
144    Ok(value)
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn cheese_info_params_serializes_episode_id() -> BpiResult<()> {
153        let params = CheeseInfoParams::from_episode_id(EpisodeId::new(20_767)?);
154
155        assert_eq!(params.query_pairs(), vec![("ep_id", "20767".to_string())]);
156        Ok(())
157    }
158
159    #[test]
160    fn cheese_ep_list_params_serializes_optional_pagination() -> BpiResult<()> {
161        let params = CheeseEpListParams::new(SeasonId::new(556)?)
162            .with_page_size(50)?
163            .with_page(1)?;
164
165        assert_eq!(
166            params.query_pairs(),
167            vec![
168                ("season_id", "556".to_string()),
169                ("ps", "50".to_string()),
170                ("pn", "1".to_string()),
171            ]
172        );
173        Ok(())
174    }
175
176    #[test]
177    fn cheese_video_stream_params_serializes_required_query() -> BpiResult<()> {
178        let params = CheeseVideoStreamParams::new(
179            Aid::new(997_984_154)?,
180            EpisodeId::new(163_956)?,
181            Cid::new(1_183_682_680)?,
182        );
183
184        assert_eq!(
185            params.query_pairs(),
186            vec![
187                ("avid", "997984154".to_string()),
188                ("ep_id", "163956".to_string()),
189                ("cid", "1183682680".to_string()),
190                ("fnver", "0".to_string()),
191            ]
192        );
193        Ok(())
194    }
195}