mirror of
https://github.com/euzu/tuliprox.git
synced 2026-09-29 20:42:31 +02:00
PlaylistExplorer search Result returns now the groups instead of flat channel list
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
- Added TimeZone to `epg_timeshift: [-+]hh:mm or TimeZone`, example `Europe/Paris`, `America/New_York`, `-2:30`(-2h30m), `+0:15` (15m), `2` (2h), `:30` (30m), `:3` (3m)
|
||||
If you use TimeZone the timeshift will change on Summer/Winter time if its applied in the TZ.
|
||||
- Fixed: Mappings now automatically reload and reapply after configuration changes, preventing stale settings.
|
||||
- Search in Playlist Explorer now returns groups instead of matching flat channel list.
|
||||
|
||||
# 3.1.7 (2025-10-10)
|
||||
- Added Dark/Bright theme switch
|
||||
|
||||
@@ -1144,7 +1144,7 @@ It is whitespace-tolerant and uses familiar programming concepts with a custom s
|
||||
- Strings / Text: Enclosed in double quotes. "example string"
|
||||
- Null value `null`
|
||||
- Regex Matching: `@FieldName ~ "Regex"` like in filter statements. You can match a `FieldName` or a existing `variable`.
|
||||
- Access a field in a regex match result: with `result.capture`. For example, if you have multiple captures you can access them by their name, or their index beginning at 1.
|
||||
- Access a field in a regex match result: with `result.capture`. For example, if you have multiple captures you can access them by their name, or their index beginning at `1` like `result.1`, `result.2`.
|
||||
- Builtin functions:
|
||||
- concat(a, b, ...)
|
||||
- uppercase(a)
|
||||
|
||||
@@ -108,49 +108,83 @@ impl From<PlaylistCategoriesResponse> for UiPlaylistCategories {
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_channels(groups: &Option<Vec<Rc<UiPlaylistGroup>>>, text: &str) -> Option<Vec<Rc<CommonPlaylistItem>>> {
|
||||
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()
|
||||
.flat_map(|group| &group.channels)
|
||||
.filter(|&c| c.title.to_lowercase().contains(text)
|
||||
|| c.name.to_lowercase().contains(text))
|
||||
.cloned()
|
||||
.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<CommonPlaylistItem>>> {
|
||||
fn filter_channels_re(groups: &Option<Vec<Rc<UiPlaylistGroup>>>, regex: &Regex) -> Option<Vec<Rc<UiPlaylistGroup>>> {
|
||||
groups.as_ref().map(|gs| {
|
||||
gs.iter()
|
||||
.flat_map(|group| &group.channels)
|
||||
.filter(|&c| regex.is_match(&c.title) || regex.is_match(&c.name))
|
||||
.cloned()
|
||||
.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<CommonPlaylistItem>>>,
|
||||
video: Option<Vec<Rc<CommonPlaylistItem>>>,
|
||||
series: Option<Vec<Rc<CommonPlaylistItem>>>) -> Option<UiPlaylistCategories> {
|
||||
if live.is_none() && video.is_none() && series.is_none() {
|
||||
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 {
|
||||
let build_group = |xtream_cluster: XtreamCluster, channels: Vec<Rc<CommonPlaylistItem>>, id: u32| {
|
||||
if channels.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(vec!(Rc::new(UiPlaylistGroup {
|
||||
id,
|
||||
title: format!("{} ({})", xtream_cluster.as_str().to_owned(), channels.len()),
|
||||
channels,
|
||||
xtream_cluster,
|
||||
})))
|
||||
}
|
||||
};
|
||||
|
||||
let live = live.and_then(|g| build_group(XtreamCluster::Live, g, 1));
|
||||
let vod = video.and_then(|g| build_group(XtreamCluster::Video, g, 2));
|
||||
let series = series.and_then(|g| build_group(XtreamCluster::Series, g, 3));
|
||||
Some(UiPlaylistCategories {
|
||||
live,
|
||||
vod,
|
||||
|
||||
Reference in New Issue
Block a user