mirror of
https://github.com/euzu/tuliprox.git
synced 2026-09-30 13:02:10 +02:00
220 lines
7.2 KiB
Rust
220 lines
7.2 KiB
Rust
use crate::model::{PlaylistItemType, SearchRequest, XtreamCluster};
|
|
use regex::Regex;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::rc::Rc;
|
|
|
|
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
|
|
pub struct PlaylistRequestXtream {
|
|
pub username: String,
|
|
pub password: String,
|
|
pub url: String,
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
|
|
pub struct PlaylistRequestM3u {
|
|
pub url: String,
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
|
|
pub enum PlaylistRequest {
|
|
Target(u16),
|
|
Input(u16),
|
|
CustomXtream(PlaylistRequestXtream),
|
|
CustomM3u(PlaylistRequestM3u)
|
|
}
|
|
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
|
|
pub struct CommonPlaylistItem {
|
|
pub virtual_id: u32,
|
|
pub provider_id: String,
|
|
pub name: String,
|
|
pub chno: String,
|
|
pub logo: String,
|
|
pub logo_small: String,
|
|
pub group: String,
|
|
pub title: String,
|
|
pub parent_code: String,
|
|
pub audio_track: String,
|
|
pub time_shift: String,
|
|
pub rec: String,
|
|
pub url: String,
|
|
pub input_name: String,
|
|
pub item_type: PlaylistItemType,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub epg_channel_id: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub xtream_cluster: Option<XtreamCluster>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub additional_properties: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub category_id: Option<u32>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
|
|
pub struct PlaylistResponseGroup {
|
|
pub id: u32,
|
|
pub title: String,
|
|
pub channels: Vec<CommonPlaylistItem>,
|
|
pub xtream_cluster: XtreamCluster,
|
|
}
|
|
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
|
|
pub struct UiPlaylistGroup {
|
|
pub id: u32,
|
|
pub title: String,
|
|
pub channels: Vec<Rc<CommonPlaylistItem>>,
|
|
pub xtream_cluster: XtreamCluster,
|
|
}
|
|
|
|
impl From<PlaylistResponseGroup> for UiPlaylistGroup {
|
|
fn from(response: PlaylistResponseGroup) -> Self {
|
|
Self {
|
|
id: response.id,
|
|
title: response.title,
|
|
channels: response.channels.into_iter().map(Rc::new).collect(),
|
|
xtream_cluster: response.xtream_cluster,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
|
|
pub struct PlaylistCategoriesResponse {
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub live: Option<Vec<PlaylistResponseGroup>>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub vod: Option<Vec<PlaylistResponseGroup>>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub series: Option<Vec<PlaylistResponseGroup>>,
|
|
}
|
|
|
|
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
|
|
pub struct UiPlaylistCategories {
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub live: Option<Vec<Rc<UiPlaylistGroup>>>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub vod: Option<Vec<Rc<UiPlaylistGroup>>>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub series: Option<Vec<Rc<UiPlaylistGroup>>>,
|
|
}
|
|
|
|
impl From<PlaylistCategoriesResponse> for UiPlaylistCategories {
|
|
fn from(response: PlaylistCategoriesResponse) -> Self {
|
|
Self {
|
|
live: response.live.map(|groups| groups.into_iter().map(Into::into).map(Rc::new).collect()),
|
|
vod: response.vod.map(|groups| groups.into_iter().map(Into::into).map(Rc::new).collect()),
|
|
series: response.series.map(|groups| groups.into_iter().map(Into::into).map(Rc::new).collect()),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn filter_channels(
|
|
groups: &Option<Vec<Rc<UiPlaylistGroup>>>,
|
|
text: &str,
|
|
) -> Option<Vec<Rc<UiPlaylistGroup>>> {
|
|
// normalize search text (lowercase)
|
|
let text = text.to_lowercase();
|
|
|
|
groups.as_ref().map(|gs| {
|
|
gs.iter()
|
|
.filter_map(|group| {
|
|
let title_lower = group.title.to_lowercase();
|
|
|
|
if title_lower.contains(&text) {
|
|
return Some(Rc::clone(group));
|
|
}
|
|
|
|
let filtered_channels: Vec<Rc<CommonPlaylistItem>> = group
|
|
.channels
|
|
.iter()
|
|
.filter(|c| {
|
|
c.title.to_lowercase().contains(&text)
|
|
|| c.name.to_lowercase().contains(&text)
|
|
})
|
|
.cloned()
|
|
.collect();
|
|
|
|
if filtered_channels.is_empty() {
|
|
None
|
|
} else {
|
|
Some(Rc::new(UiPlaylistGroup {
|
|
id: group.id,
|
|
title: group.title.clone(),
|
|
channels: filtered_channels,
|
|
xtream_cluster: group.xtream_cluster,
|
|
}))
|
|
}
|
|
})
|
|
.collect::<Vec<_>>()
|
|
})
|
|
}
|
|
|
|
fn filter_channels_re(groups: &Option<Vec<Rc<UiPlaylistGroup>>>, regex: &Regex) -> Option<Vec<Rc<UiPlaylistGroup>>> {
|
|
groups.as_ref().map(|gs| {
|
|
gs.iter()
|
|
.filter_map(|group| {
|
|
if regex.is_match(&group.title) {
|
|
return Some(Rc::clone(group));
|
|
}
|
|
|
|
let filtered_channels: Vec<Rc<CommonPlaylistItem>> = group
|
|
.channels
|
|
.iter()
|
|
.filter(|c| regex.is_match(&c.title) || regex.is_match(&c.name))
|
|
.cloned()
|
|
.collect();
|
|
|
|
if filtered_channels.is_empty() {
|
|
None
|
|
} else {
|
|
Some(Rc::new(UiPlaylistGroup {
|
|
id: group.id,
|
|
title: group.title.clone(),
|
|
channels: filtered_channels,
|
|
xtream_cluster: group.xtream_cluster,
|
|
}))
|
|
}
|
|
})
|
|
.collect::<Vec<_>>()
|
|
})
|
|
}
|
|
|
|
fn build_result(live: Option<Vec<Rc<UiPlaylistGroup>>>,
|
|
vod: Option<Vec<Rc<UiPlaylistGroup>>>,
|
|
series: Option<Vec<Rc<UiPlaylistGroup>>>) -> Option<UiPlaylistCategories> {
|
|
if live.is_none() && vod.is_none() && series.is_none() {
|
|
None
|
|
} else {
|
|
Some(UiPlaylistCategories {
|
|
live,
|
|
vod,
|
|
series,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl UiPlaylistCategories {
|
|
pub fn filter(&self, search_req: &SearchRequest) -> Option<Self> {
|
|
match search_req {
|
|
SearchRequest::Clear => None,
|
|
SearchRequest::Text(text, _search_fields) => {
|
|
let text_lc = text.to_lowercase();
|
|
let live = filter_channels(&self.live, &text_lc);
|
|
let video = filter_channels(&self.vod, &text_lc);
|
|
let series = filter_channels(&self.series, &text_lc);
|
|
build_result(live, video, series)
|
|
}
|
|
SearchRequest::Regexp(text, _search_fields) => {
|
|
if let Ok(regex) = Regex::new(text) {
|
|
let live = filter_channels_re(&self.live, ®ex);
|
|
let video = filter_channels_re(&self.vod, ®ex);
|
|
let series = filter_channels_re(&self.series, ®ex);
|
|
build_result(live, video, series)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|