PlaylistExplorer search Result returns now the groups instead of flat channel list

This commit is contained in:
euzu
2025-10-14 09:31:30 +02:00
parent 16510c1a5c
commit 4875bd2dfe
3 changed files with 65 additions and 30 deletions
+1
View File
@@ -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
+1 -1
View File
@@ -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)
+63 -29
View File
@@ -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,