1use serde::{Deserialize, Serialize};
8
9#[derive(Clone, Serialize, Deserialize)]
11pub struct StalkerCredentials {
12 pub base_url: String,
14
15 pub mac_address: String,
17
18 #[serde(default)]
21 pub timezone: Option<String>,
22}
23
24impl std::fmt::Debug for StalkerCredentials {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 f.debug_struct("StalkerCredentials")
27 .field("base_url", &self.base_url)
28 .field("mac_address", &"[REDACTED]")
29 .finish()
30 }
31}
32
33#[derive(Debug, Clone, Default, Serialize, Deserialize)]
35pub struct StalkerChannel {
36 pub id: String,
38
39 pub name: String,
41
42 pub number: Option<u32>,
44
45 pub cmd: String,
47
48 pub tv_genre_id: Option<String>,
50
51 pub logo: Option<String>,
53
54 pub epg_channel_id: Option<String>,
56
57 #[serde(default)]
59 pub has_archive: bool,
60
61 #[serde(default)]
63 pub archive_days: u32,
64
65 #[serde(default)]
67 pub is_censored: bool,
68}
69
70#[derive(Debug, Clone, Default, Serialize, Deserialize)]
72pub struct StalkerVodItem {
73 pub id: String,
75
76 pub name: String,
78
79 pub cmd: String,
81
82 pub category_id: Option<String>,
84
85 pub logo: Option<String>,
87
88 pub description: Option<String>,
90
91 pub year: Option<String>,
93
94 pub genre: Option<String>,
96
97 pub rating: Option<String>,
99
100 pub director: Option<String>,
102
103 pub cast: Option<String>,
105
106 pub duration: Option<String>,
108
109 pub tmdb_id: Option<i64>,
111}
112
113#[derive(Debug, Clone, Default, Serialize, Deserialize)]
115pub struct StalkerSeriesItem {
116 pub id: String,
118
119 pub name: String,
121
122 pub category_id: Option<String>,
124
125 pub logo: Option<String>,
127
128 pub description: Option<String>,
130
131 pub year: Option<String>,
133
134 pub genre: Option<String>,
136
137 pub rating: Option<String>,
139
140 pub director: Option<String>,
142
143 pub cast: Option<String>,
145}
146
147#[derive(Debug, Clone, Default, Serialize, Deserialize)]
151pub struct StalkerSeason {
152 pub id: String,
154
155 pub name: String,
157
158 pub movie_id: String,
160
161 pub logo: Option<String>,
163
164 pub description: Option<String>,
166}
167
168#[derive(Debug, Clone, Default, Serialize, Deserialize)]
172pub struct StalkerEpisode {
173 pub id: String,
175
176 pub name: String,
178
179 pub movie_id: String,
181
182 pub season_id: String,
184
185 pub episode_number: Option<u32>,
187
188 pub cmd: String,
190
191 pub logo: Option<String>,
193
194 pub description: Option<String>,
196
197 pub duration: Option<String>,
199}
200
201#[derive(Debug, Clone, Default, Serialize, Deserialize)]
205pub struct StalkerEpgEntry {
206 pub name: String,
208
209 pub start_timestamp: Option<i64>,
211
212 pub end_timestamp: Option<i64>,
214
215 pub description: Option<String>,
217
218 pub category: Option<String>,
220
221 pub duration: Option<i64>,
223}
224
225#[derive(Debug, Clone, Default, Serialize, Deserialize)]
227pub struct StalkerCategory {
228 pub id: String,
230
231 pub title: String,
233
234 #[serde(default)]
236 pub is_adult: bool,
237}
238
239#[derive(Debug, Clone)]
241pub struct PaginatedResult<T> {
242 pub items: Vec<T>,
244
245 pub total_items: u32,
247
248 pub max_page_items: u32,
250}
251
252impl<T> PaginatedResult<T> {
253 pub fn total_pages(&self) -> u32 {
255 if self.max_page_items == 0 {
256 return 1;
257 }
258 self.total_items.div_ceil(self.max_page_items)
259 }
260}
261
262#[derive(Debug, Clone, Default, Serialize, Deserialize)]
264pub struct StalkerAccountInfo {
265 pub login: Option<String>,
267
268 pub mac: Option<String>,
270
271 pub status: Option<String>,
273
274 pub expiration: Option<String>,
276
277 pub subscribed_till: Option<String>,
279}
280
281#[derive(Debug, Clone, Default, Serialize, Deserialize)]
285pub struct StalkerSeriesDetail {
286 pub series: StalkerSeriesItem,
288
289 pub seasons: Vec<StalkerSeason>,
291
292 pub episodes: std::collections::HashMap<String, Vec<StalkerEpisode>>,
294}
295
296#[derive(Debug, Clone, Default, Serialize, Deserialize)]
298pub struct StalkerProfile {
299 pub timezone: Option<String>,
301
302 pub locale: Option<String>,
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309
310 #[test]
311 fn credentials_debug_redacts_mac() {
312 let creds = StalkerCredentials {
313 base_url: "http://example.com".into(),
314 mac_address: "00:1A:79:AB:CD:EF".into(),
315 timezone: None,
316 };
317 let debug = format!("{creds:?}");
318 assert!(debug.contains("[REDACTED]"));
319 assert!(!debug.contains("00:1A:79"));
320 }
321
322 #[test]
323 fn paginated_result_total_pages() {
324 let result: PaginatedResult<()> = PaginatedResult {
325 items: vec![],
326 total_items: 25,
327 max_page_items: 10,
328 };
329 assert_eq!(result.total_pages(), 3);
330 }
331
332 #[test]
333 fn paginated_result_exact_division() {
334 let result: PaginatedResult<()> = PaginatedResult {
335 items: vec![],
336 total_items: 20,
337 max_page_items: 10,
338 };
339 assert_eq!(result.total_pages(), 2);
340 }
341
342 #[test]
343 fn paginated_result_zero_max_page_items() {
344 let result: PaginatedResult<()> = PaginatedResult {
345 items: vec![],
346 total_items: 5,
347 max_page_items: 0,
348 };
349 assert_eq!(result.total_pages(), 1);
350 }
351}