1use std::collections::HashMap;
8
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct XtreamProfile {
18 pub user_info: XtreamUserProfile,
19 pub server_info: XtreamServerInfo,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct XtreamUserProfile {
25 #[serde(default)]
26 pub username: String,
27
28 #[serde(default)]
29 pub password: String,
30
31 #[serde(default)]
33 pub message: String,
34
35 #[serde(default)]
37 pub auth: u8,
38
39 #[serde(default)]
41 pub status: String,
42
43 #[serde(default)]
45 pub exp_date: Option<String>,
46
47 #[serde(default)]
49 pub is_trial: Option<String>,
50
51 #[serde(default)]
53 pub active_cons: Option<serde_json::Value>,
54
55 #[serde(default)]
57 pub created_at: Option<String>,
58
59 #[serde(default)]
61 pub max_connections: Option<serde_json::Value>,
62
63 #[serde(default)]
65 pub allowed_output_formats: Vec<String>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct XtreamServerInfo {
71 #[serde(default)]
73 pub xui: Option<serde_json::Value>,
74
75 #[serde(default)]
77 pub version: Option<String>,
78
79 #[serde(default)]
81 pub revision: Option<String>,
82
83 #[serde(default)]
85 pub url: Option<String>,
86
87 #[serde(default)]
89 pub port: Option<serde_json::Value>,
90
91 #[serde(default)]
93 pub https_port: Option<serde_json::Value>,
94
95 #[serde(default)]
97 pub server_protocol: Option<String>,
98
99 #[serde(default)]
101 pub rtmp_port: Option<serde_json::Value>,
102
103 #[serde(default)]
105 pub timezone: Option<String>,
106
107 #[serde(default)]
109 pub timestamp_now: Option<i64>,
110
111 #[serde(default)]
113 pub time_now: Option<String>,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct XtreamCategory {
123 #[serde(default)]
124 pub category_id: String,
125
126 #[serde(default)]
127 pub category_name: String,
128
129 #[serde(default)]
130 pub parent_id: Option<serde_json::Value>,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct XtreamChannel {
140 #[serde(default)]
142 pub num: Option<i64>,
143
144 #[serde(default)]
146 pub name: String,
147
148 #[serde(default)]
150 pub stream_type: Option<String>,
151
152 #[serde(default)]
154 pub stream_id: i64,
155
156 #[serde(default)]
158 pub stream_icon: Option<String>,
159
160 #[serde(default)]
162 pub thumbnail: Option<String>,
163
164 #[serde(default)]
166 pub epg_channel_id: Option<String>,
167
168 #[serde(default)]
170 pub added: Option<String>,
171
172 #[serde(default)]
174 pub category_id: Option<String>,
175
176 #[serde(default)]
178 pub category_ids: Vec<serde_json::Value>,
179
180 #[serde(default)]
182 pub custom_sid: Option<String>,
183
184 #[serde(default)]
186 pub tv_archive: Option<i64>,
187
188 #[serde(default)]
190 pub direct_source: Option<String>,
191
192 #[serde(default)]
194 pub tv_archive_duration: Option<i64>,
195
196 #[serde(default)]
198 pub is_adult: bool,
199
200 #[serde(skip_serializing_if = "Option::is_none")]
202 pub url: Option<String>,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct XtreamMovieListing {
212 #[serde(default)]
213 pub num: Option<i64>,
214
215 #[serde(default)]
216 pub name: String,
217
218 #[serde(default)]
219 pub year: Option<String>,
220
221 #[serde(default)]
222 pub title: Option<String>,
223
224 #[serde(default)]
225 pub stream_type: Option<String>,
226
227 #[serde(default)]
228 pub stream_id: i64,
229
230 #[serde(default)]
231 pub stream_icon: Option<String>,
232
233 #[serde(default)]
234 pub rating: Option<serde_json::Value>,
235
236 #[serde(default)]
237 pub rating_5based: Option<serde_json::Value>,
238
239 #[serde(default)]
240 pub genre: Option<String>,
241
242 #[serde(default)]
243 pub added: Option<String>,
244
245 #[serde(default)]
246 pub episode_run_time: Option<serde_json::Value>,
247
248 #[serde(default)]
249 pub category_id: Option<String>,
250
251 #[serde(default)]
252 pub category_ids: Vec<serde_json::Value>,
253
254 #[serde(default)]
255 pub container_extension: Option<String>,
256
257 #[serde(default)]
258 pub custom_sid: Option<serde_json::Value>,
259
260 #[serde(default)]
261 pub direct_source: Option<String>,
262
263 #[serde(default)]
264 pub release_date: Option<String>,
265
266 #[serde(default)]
267 pub cast: Option<String>,
268
269 #[serde(default)]
270 pub director: Option<String>,
271
272 #[serde(default)]
273 pub plot: Option<String>,
274
275 #[serde(default)]
276 pub youtube_trailer: Option<String>,
277
278 #[serde(skip_serializing_if = "Option::is_none")]
280 pub url: Option<String>,
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct XtreamMovie {
286 pub info: Option<XtreamMovieInfo>,
287 pub movie_data: Option<XtreamMovieData>,
288
289 #[serde(skip_serializing_if = "Option::is_none")]
291 pub url: Option<String>,
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize)]
296pub struct XtreamMovieData {
297 #[serde(default)]
298 pub stream_id: i64,
299
300 #[serde(default)]
301 pub name: Option<String>,
302
303 #[serde(default)]
304 pub title: Option<String>,
305
306 #[serde(default)]
307 pub year: Option<String>,
308
309 #[serde(default)]
310 pub added: Option<String>,
311
312 #[serde(default)]
313 pub category_id: Option<String>,
314
315 #[serde(default)]
316 pub category_ids: Vec<serde_json::Value>,
317
318 #[serde(default)]
319 pub container_extension: Option<String>,
320
321 #[serde(default)]
322 pub custom_sid: Option<String>,
323
324 #[serde(default)]
325 pub direct_source: Option<String>,
326}
327
328#[derive(Debug, Clone, Serialize, Deserialize)]
330pub struct XtreamMovieInfo {
331 #[serde(default)]
332 pub kinopoisk_url: Option<String>,
333
334 #[serde(default)]
335 pub tmdb_id: Option<serde_json::Value>,
336
337 #[serde(default)]
338 pub name: Option<String>,
339
340 #[serde(default, alias = "o_name")]
341 pub original_name: Option<String>,
342
343 #[serde(default)]
344 pub cover_big: Option<String>,
345
346 #[serde(default)]
347 pub movie_image: Option<String>,
348
349 #[serde(default)]
350 pub release_date: Option<String>,
351
352 #[serde(default)]
353 pub episode_run_time: Option<serde_json::Value>,
354
355 #[serde(default)]
356 pub youtube_trailer: Option<String>,
357
358 #[serde(default)]
359 pub director: Option<String>,
360
361 #[serde(default)]
362 pub actors: Option<String>,
363
364 #[serde(default)]
365 pub cast: Option<String>,
366
367 #[serde(default)]
368 pub description: Option<String>,
369
370 #[serde(default)]
371 pub plot: Option<String>,
372
373 #[serde(default)]
374 pub age: Option<String>,
375
376 #[serde(default)]
377 pub mpaa_rating: Option<String>,
378
379 #[serde(default)]
380 pub rating_count_kinopoisk: Option<serde_json::Value>,
381
382 #[serde(default)]
383 pub country: Option<String>,
384
385 #[serde(default)]
386 pub genre: Option<String>,
387
388 #[serde(default)]
389 pub backdrop_path: Option<serde_json::Value>,
390
391 #[serde(default)]
392 pub duration_secs: Option<i64>,
393
394 #[serde(default)]
395 pub duration: Option<String>,
396
397 #[serde(default)]
398 pub bitrate: Option<serde_json::Value>,
399
400 #[serde(default, alias = "releasedate")]
401 pub release_date_alt: Option<String>,
402
403 #[serde(default)]
404 pub subtitles: Option<serde_json::Value>,
405
406 #[serde(default)]
407 pub rating: Option<serde_json::Value>,
408}
409
410#[derive(Debug, Clone, Serialize, Deserialize)]
416pub struct XtreamShowListing {
417 #[serde(default)]
418 pub num: Option<i64>,
419
420 #[serde(default)]
421 pub name: String,
422
423 #[serde(default)]
424 pub title: Option<String>,
425
426 #[serde(default)]
427 pub year: Option<String>,
428
429 #[serde(default)]
430 pub series_id: i64,
431
432 #[serde(default)]
433 pub stream_type: Option<String>,
434
435 #[serde(default)]
436 pub cover: Option<String>,
437
438 #[serde(default)]
439 pub plot: Option<String>,
440
441 #[serde(default)]
442 pub cast: Option<String>,
443
444 #[serde(default)]
445 pub director: Option<String>,
446
447 #[serde(default)]
448 pub genre: Option<String>,
449
450 #[serde(default, alias = "releaseDate")]
451 pub release_date: Option<String>,
452
453 #[serde(default)]
454 pub last_modified: Option<String>,
455
456 #[serde(default)]
457 pub rating: Option<serde_json::Value>,
458
459 #[serde(default)]
460 pub rating_5based: Option<serde_json::Value>,
461
462 #[serde(default)]
463 pub backdrop_path: Option<serde_json::Value>,
464
465 #[serde(default)]
466 pub youtube_trailer: Option<String>,
467
468 #[serde(default)]
469 pub episode_run_time: Option<serde_json::Value>,
470
471 #[serde(default)]
472 pub category_id: Option<String>,
473
474 #[serde(default)]
475 pub category_ids: Vec<serde_json::Value>,
476}
477
478#[derive(Debug, Clone, Serialize, Deserialize)]
480pub struct XtreamShow {
481 #[serde(default)]
482 pub seasons: Vec<XtreamSeason>,
483
484 pub info: Option<XtreamShowInfo>,
485
486 #[serde(default)]
488 pub episodes: HashMap<String, Vec<XtreamEpisode>>,
489}
490
491#[derive(Debug, Clone, Serialize, Deserialize)]
493pub struct XtreamShowInfo {
494 #[serde(default)]
495 pub name: Option<String>,
496
497 #[serde(default)]
498 pub title: Option<String>,
499
500 #[serde(default)]
501 pub year: Option<String>,
502
503 #[serde(default)]
504 pub series_id: Option<i64>,
505
506 #[serde(default)]
507 pub cover: Option<String>,
508
509 #[serde(default)]
510 pub plot: Option<String>,
511
512 #[serde(default)]
513 pub cast: Option<String>,
514
515 #[serde(default)]
516 pub director: Option<String>,
517
518 #[serde(default)]
519 pub genre: Option<String>,
520
521 #[serde(default, alias = "releaseDate")]
522 pub release_date: Option<String>,
523
524 #[serde(default)]
525 pub last_modified: Option<String>,
526
527 #[serde(default)]
528 pub rating: Option<serde_json::Value>,
529
530 #[serde(default)]
531 pub rating_5based: Option<serde_json::Value>,
532
533 #[serde(default)]
534 pub backdrop_path: Option<serde_json::Value>,
535
536 #[serde(default)]
537 pub youtube_trailer: Option<String>,
538
539 #[serde(default)]
540 pub episode_run_time: Option<serde_json::Value>,
541
542 #[serde(default)]
543 pub category_id: Option<String>,
544
545 #[serde(default)]
546 pub category_ids: Vec<serde_json::Value>,
547}
548
549#[derive(Debug, Clone, Serialize, Deserialize)]
551pub struct XtreamSeason {
552 #[serde(default)]
553 pub id: Option<i64>,
554
555 #[serde(default)]
556 pub name: Option<String>,
557
558 #[serde(default)]
559 pub episode_count: Option<i64>,
560
561 #[serde(default)]
562 pub overview: Option<String>,
563
564 #[serde(default)]
565 pub air_date: Option<String>,
566
567 #[serde(default)]
568 pub cover: Option<String>,
569
570 #[serde(default)]
571 pub season_number: Option<i64>,
572
573 #[serde(default)]
574 pub cover_big: Option<String>,
575
576 #[serde(default)]
577 pub vote_average: Option<f64>,
578}
579
580#[derive(Debug, Clone, Serialize, Deserialize)]
582pub struct XtreamEpisode {
583 #[serde(default)]
584 pub id: Option<serde_json::Value>,
585
586 #[serde(default)]
587 pub episode_num: Option<serde_json::Value>,
588
589 #[serde(default)]
590 pub title: Option<String>,
591
592 #[serde(default)]
593 pub container_extension: Option<String>,
594
595 #[serde(default)]
596 pub info: Option<XtreamEpisodeInfo>,
597
598 #[serde(default)]
599 pub custom_sid: Option<String>,
600
601 #[serde(default)]
602 pub added: Option<String>,
603
604 #[serde(default)]
605 pub season: Option<serde_json::Value>,
606
607 #[serde(default)]
608 pub direct_source: Option<String>,
609
610 #[serde(default)]
611 pub subtitles: Option<serde_json::Value>,
612
613 #[serde(skip_serializing_if = "Option::is_none")]
615 pub url: Option<String>,
616}
617
618#[derive(Debug, Clone, Serialize, Deserialize)]
620pub struct XtreamEpisodeInfo {
621 #[serde(default)]
622 pub air_date: Option<String>,
623
624 #[serde(default)]
625 pub release_date: Option<String>,
626
627 #[serde(default)]
628 pub plot: Option<String>,
629
630 #[serde(default)]
631 pub rating: Option<serde_json::Value>,
632
633 #[serde(default)]
634 pub movie_image: Option<String>,
635
636 #[serde(default)]
637 pub cover_big: Option<String>,
638
639 #[serde(default)]
640 pub duration_secs: Option<i64>,
641
642 #[serde(default)]
643 pub duration: Option<String>,
644
645 #[serde(default)]
646 pub tmdb_id: Option<serde_json::Value>,
647
648 #[serde(default)]
649 pub video: Option<serde_json::Value>,
650
651 #[serde(default)]
652 pub audio: Option<serde_json::Value>,
653
654 #[serde(default)]
655 pub bitrate: Option<serde_json::Value>,
656
657 #[serde(default)]
658 pub season: Option<serde_json::Value>,
659}
660
661#[derive(Debug, Clone, Serialize, Deserialize)]
667pub struct XtreamShortEpg {
668 #[serde(default)]
669 pub epg_listings: Vec<XtreamEpgListing>,
670}
671
672#[derive(Debug, Clone, Serialize, Deserialize)]
674pub struct XtreamEpgListing {
675 #[serde(default)]
676 pub id: Option<String>,
677
678 #[serde(default)]
679 pub epg_id: Option<String>,
680
681 #[serde(default)]
683 pub title: Option<String>,
684
685 #[serde(default)]
686 pub lang: Option<String>,
687
688 #[serde(default)]
689 pub start: Option<String>,
690
691 #[serde(default)]
692 pub end: Option<String>,
693
694 #[serde(default)]
696 pub description: Option<String>,
697
698 #[serde(default)]
699 pub channel_id: Option<String>,
700
701 #[serde(default)]
702 pub start_timestamp: Option<serde_json::Value>,
703
704 #[serde(default)]
705 pub stop_timestamp: Option<serde_json::Value>,
706
707 #[serde(default)]
708 pub stop: Option<String>,
709}
710
711#[derive(Debug, Clone, Serialize, Deserialize)]
713pub struct XtreamFullEpg {
714 #[serde(default)]
715 pub epg_listings: Vec<XtreamFullEpgListing>,
716}
717
718#[derive(Debug, Clone, Serialize, Deserialize)]
720pub struct XtreamFullEpgListing {
721 #[serde(default)]
722 pub id: Option<String>,
723
724 #[serde(default)]
725 pub epg_id: Option<String>,
726
727 #[serde(default)]
729 pub title: Option<String>,
730
731 #[serde(default)]
732 pub lang: Option<String>,
733
734 #[serde(default)]
735 pub start: Option<String>,
736
737 #[serde(default)]
738 pub end: Option<String>,
739
740 #[serde(default)]
742 pub description: Option<String>,
743
744 #[serde(default)]
745 pub channel_id: Option<String>,
746
747 #[serde(default)]
748 pub start_timestamp: Option<serde_json::Value>,
749
750 #[serde(default)]
751 pub stop_timestamp: Option<serde_json::Value>,
752
753 #[serde(default)]
755 pub now_playing: Option<i64>,
756
757 #[serde(default)]
759 pub has_archive: Option<i64>,
760}
761
762#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
768pub enum StreamFormat {
769 #[default]
771 Ts,
772 M3u8,
774 Rtmp,
776}
777
778impl StreamFormat {
779 pub fn extension(self) -> &'static str {
781 match self {
782 Self::Ts => "ts",
783 Self::M3u8 => "m3u8",
784 Self::Rtmp => "ts", }
786 }
787}
788
789impl std::fmt::Display for StreamFormat {
790 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
791 f.write_str(self.extension())
792 }
793}
794
795#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
797pub enum StreamType {
798 Channel,
800 Movie,
802 Episode,
804}
805
806impl StreamType {
807 pub fn path_segment(self) -> &'static str {
809 match self {
810 Self::Channel => "live",
811 Self::Movie => "movie",
812 Self::Episode => "series",
813 }
814 }
815}
816
817#[cfg(test)]
818mod tests {
819 use super::*;
820
821 #[test]
822 fn stream_format_extensions() {
823 assert_eq!(StreamFormat::Ts.extension(), "ts");
824 assert_eq!(StreamFormat::M3u8.extension(), "m3u8");
825 assert_eq!(StreamFormat::Rtmp.extension(), "ts");
826 }
827
828 #[test]
829 fn stream_type_segments() {
830 assert_eq!(StreamType::Channel.path_segment(), "live");
831 assert_eq!(StreamType::Movie.path_segment(), "movie");
832 assert_eq!(StreamType::Episode.path_segment(), "series");
833 }
834
835 #[test]
836 fn deserialize_profile() {
837 let json = r#"{
838 "user_info": {
839 "username": "test",
840 "password": "pass",
841 "message": "",
842 "auth": 1,
843 "status": "Active",
844 "exp_date": "1735689600",
845 "is_trial": "0",
846 "active_cons": 0,
847 "created_at": "1704067200",
848 "max_connections": "1",
849 "allowed_output_formats": ["ts", "m3u8"]
850 },
851 "server_info": {
852 "xui": true,
853 "version": "1.5.12",
854 "revision": null,
855 "url": "example.com",
856 "port": "80",
857 "https_port": "443",
858 "server_protocol": "http",
859 "rtmp_port": "8880",
860 "timezone": "UTC",
861 "timestamp_now": 1704067200,
862 "time_now": "2024-01-01 00:00:00"
863 }
864 }"#;
865
866 let profile: XtreamProfile = serde_json::from_str(json).unwrap();
867 assert_eq!(profile.user_info.username, "test");
868 assert_eq!(profile.user_info.auth, 1);
869 assert_eq!(profile.user_info.status, "Active");
870 assert_eq!(profile.user_info.allowed_output_formats.len(), 2);
871 assert_eq!(profile.server_info.timezone.as_deref(), Some("UTC"));
872 }
873
874 #[test]
875 fn deserialize_category() {
876 let json = r#"{"category_id": "1", "category_name": "Sports", "parent_id": 0}"#;
877 let cat: XtreamCategory = serde_json::from_str(json).unwrap();
878 assert_eq!(cat.category_id, "1");
879 assert_eq!(cat.category_name, "Sports");
880 }
881
882 #[test]
883 fn deserialize_channel() {
884 let json = r#"{
885 "num": 1,
886 "name": "BBC One",
887 "stream_type": "live",
888 "stream_id": 42,
889 "stream_icon": "http://img.example.com/bbc1.png",
890 "thumbnail": "",
891 "epg_channel_id": "bbc1.uk",
892 "added": "1704067200",
893 "category_id": "1",
894 "category_ids": [1],
895 "custom_sid": "",
896 "tv_archive": 1,
897 "direct_source": "",
898 "tv_archive_duration": 7
899 }"#;
900
901 let ch: XtreamChannel = serde_json::from_str(json).unwrap();
902 assert_eq!(ch.stream_id, 42);
903 assert_eq!(ch.name, "BBC One");
904 assert_eq!(ch.epg_channel_id.as_deref(), Some("bbc1.uk"));
905 assert_eq!(ch.tv_archive, Some(1));
906 }
907
908 #[test]
909 fn deserialize_epg_listing() {
910 let json = r#"{
911 "id": "123",
912 "epg_id": "bbc1.uk",
913 "title": "TmV3cw==",
914 "lang": "en",
915 "start": "2024-01-01 10:00:00",
916 "end": "2024-01-01 11:00:00",
917 "description": "RGFpbHkgbmV3cw==",
918 "channel_id": "bbc1.uk",
919 "start_timestamp": "1704106800",
920 "stop_timestamp": "1704110400",
921 "stop": "2024-01-01 11:00:00"
922 }"#;
923
924 let listing: XtreamEpgListing = serde_json::from_str(json).unwrap();
925 assert_eq!(listing.id.as_deref(), Some("123"));
926 assert_eq!(listing.title.as_deref(), Some("TmV3cw=="));
927 }
928}