refactoring for favourites

- implemented favourites config to target config
- added add_favroutie() function to mapping
This commit is contained in:
euzu
2025-12-29 15:55:50 +01:00
parent c4889ce0ed
commit e94a869c73
11 changed files with 183 additions and 100 deletions
+60 -78
View File
@@ -26,17 +26,16 @@ use crate::processing::processor::xtream_vod::playlist_resolve_vod;
use crate::repository::playlist_repository::{persist_input_playlist, persist_playlist};
use crate::utils::StepMeasure;
use crate::utils::{debug_if_enabled, trace_if_enabled};
use deunicode::deunicode;
use futures::StreamExt;
use log::{debug, error, info, log_enabled, trace, warn, Level};
use shared::error::{get_errors_notify_message, notify_err, TuliproxError};
use shared::foundation::filter::{get_field_value, set_field_value, Filter, ValueAccessor, ValueProvider};
use shared::model::{CounterModifier, FieldGetAccessor, FieldSetAccessor, InputType, ItemField, MsgKind, PlaylistEntry, PlaylistGroup, PlaylistItem, PlaylistUpdateState, ProcessingOrder, UUIDType, XtreamCluster};
use shared::utils::{default_as_default, hash_bytes};
use shared::model::{CounterModifier, FieldGetAccessor, FieldSetAccessor, InputType, ItemField, MsgKind, PlaylistEntry, PlaylistGroup, PlaylistItem, PlaylistItemType, PlaylistUpdateState, ProcessingOrder, UUIDType, XtreamCluster};
use shared::utils::{create_alias_uuid, default_as_default};
use std::time::Instant;
fn is_valid(pli: &PlaylistItem, filter: &Filter) -> bool {
let provider = ValueProvider { pli };
fn is_valid(pli: &PlaylistItem, filter: &Filter, match_as_ascii: bool) -> bool {
let provider = ValueProvider { pli, match_as_ascii };
filter.filter(&provider)
}
@@ -46,7 +45,7 @@ pub fn apply_filter_to_playlist(playlist: &mut [PlaylistGroup], filter: &Filter)
let mut new_playlist = Vec::with_capacity(128);
for pg in playlist.iter_mut() {
let channels = pg.channels.iter()
.filter(|&pli| is_valid(pli, filter)).cloned().collect::<Vec<PlaylistItem>>();
.filter(|&pli| is_valid(pli, filter, false)).cloned().collect::<Vec<PlaylistItem>>();
trace!("Filtered group {} has now {}/{} items", pg.title, channels.len(), pg.channels.len());
if !channels.is_empty() {
new_playlist.push(PlaylistGroup {
@@ -60,52 +59,8 @@ pub fn apply_filter_to_playlist(playlist: &mut [PlaylistGroup], filter: &Filter)
Some(new_playlist)
}
pub fn apply_favourites_to_playlist(
_playlist: &mut [PlaylistGroup],
_favourites_cfg: Option<&[ConfigFavourites]>,
) {
// TODO implement favourites
// if let Some(favourites) = favourites_cfg {
// let mut fav_groups: HashMap<String, Vec<PlaylistItem>> = HashMap::new();
//
// for pg in playlist.iter_mut() {
// for pli in &pg.channels {
// for fav in favourites {
// if is_valid(pli, &fav.filter) {
// let mut channel = pli.clone();
// channel.header.copy = true;
// channel.header.group.clone_from(&fav.group);
// channel.header.gen_uuid();
// fav_groups
// .entry(fav.group.clone())
// .or_default()
// .push(channel);
// }
// }
// }
// }
//
// for (group_name, channels) in fav_groups {
// if !channels.is_empty() {
// let xtream_cluster = channels[0].header.xtream_cluster;
// playlist.push(PlaylistGroup {
// id: 0,
// title: group_name,
// channels,
// xtream_cluster,
// });
// }
// }
// }
}
fn filter_playlist(playlist: &mut [PlaylistGroup], target: &ConfigTarget) -> Option<Vec<PlaylistGroup>> {
if let Some(mut filtered_playlist) = apply_filter_to_playlist(playlist, &target.filter) {
apply_favourites_to_playlist(&mut filtered_playlist, target.favourites.as_deref());
Some(filtered_playlist)
} else {
None
}
apply_filter_to_playlist(playlist, &target.filter)
}
fn assign_channel_no_playlist(new_playlist: &mut [PlaylistGroup]) {
@@ -170,53 +125,39 @@ fn rename_playlist(playlist: &mut [PlaylistGroup], target: &ConfigTarget) -> Opt
}
}
fn create_alias_uuid(base_uuid: &UUIDType, mapping_id: &str) -> UUIDType {
let mut data = Vec::with_capacity(base_uuid.len() + mapping_id.len());
data.extend_from_slice(base_uuid.as_ref());
data.extend_from_slice(mapping_id.as_bytes());
hash_bytes(&data)
}
fn map_channel(mut channel: PlaylistItem, mapping: &Mapping) -> (PlaylistItem, bool) {
fn map_channel(mut channel: PlaylistItem, mapping: &Mapping) -> (PlaylistItem, Vec<PlaylistItem>, bool) {
let mut matched = false;
let mut virtual_items = vec![];
if let Some(mapper) = &mapping.mapper {
if !mapper.is_empty() {
let header = &channel.header;
let channel_name = if mapping.match_as_ascii { deunicode(&header.name) } else { header.name.clone() };
if mapping.match_as_ascii && log_enabled!(Level::Trace) { trace!("Decoded {} for matching to {}", &header.name, &channel_name); }
let ref_chan = &mut channel;
let templates = mapping.templates.as_ref();
for m in mapper {
if let Some(script) = m.t_script.as_ref() {
if let Some(filter) = &m.t_filter {
let provider = ValueProvider { pli: ref_chan };
let provider = ValueProvider { pli: ref_chan, match_as_ascii: mapping.match_as_ascii };
if filter.filter(&provider) {
matched = true;
let mut accessor = ValueAccessor { pli: ref_chan };
let mut accessor = ValueAccessor { pli: ref_chan, virtual_items: vec![], match_as_ascii: mapping.match_as_ascii };
script.eval(&mut accessor, templates);
virtual_items.extend(accessor.virtual_items.into_iter().map(|(_, pli)| pli));
}
}
}
}
}
}
(channel, matched)
(channel, virtual_items, matched)
}
fn map_channel_with_aliases(channel: PlaylistItem, mapping: &Mapping) -> Vec<PlaylistItem> {
if mapping.create_alias {
let original = channel.clone();
let (mut mapped_channel, matched) = map_channel(channel, mapping);
if matched {
mapped_channel.header.uuid = create_alias_uuid(original.header.get_uuid(), &mapping.id);
vec![original, mapped_channel]
} else {
vec![mapped_channel]
}
} else {
let (mapped_channel, _) = map_channel(channel, mapping);
vec![mapped_channel]
}
let (mapped_channel, mut virtual_items, _matched) = map_channel(channel, mapping);
let mut result = Vec::with_capacity(1 + virtual_items.len());
result.push(mapped_channel);
result.append(&mut virtual_items);
result
}
fn map_playlist(playlist: &mut [PlaylistGroup], target: &ConfigTarget) -> Option<Vec<PlaylistGroup>> {
@@ -264,7 +205,7 @@ fn map_playlist_counter(target: &ConfigTarget, playlist: &mut [PlaylistGroup]) {
for counter in counter_list {
for plg in &mut *playlist {
for channel in &mut plg.channels {
let provider = ValueProvider { pli: channel };
let provider = ValueProvider { pli: channel, match_as_ascii: mapping.match_as_ascii };
if counter.filter.filter(&provider) {
let cntval = counter.value.fetch_add(1, core::sync::atomic::Ordering::AcqRel);
let padded_cntval = if counter.padding > 0 {
@@ -587,6 +528,9 @@ async fn process_playlist_for_target(app_config: &Arc<AppConfig>,
let (new_epg, mut new_playlist) = process_epg(&mut processed_fetched_playlists).await;
step.tick("epg");
process_favourites(&mut new_playlist, target.favourites.as_deref());
if new_playlist.is_empty() {
step.stop("");
info!("Playlist is empty: {}", &target.name);
@@ -618,6 +562,44 @@ async fn process_playlist_for_target(app_config: &Arc<AppConfig>,
}
}
pub fn process_favourites(playlist: &mut Vec<PlaylistGroup>, favourites_cfg: Option<&[ConfigFavourites]>) {
if let Some(favourites) = favourites_cfg {
let mut fav_groups: HashMap<String, Vec<PlaylistItem>> = HashMap::new();
for pg in playlist.iter() {
for pli in &pg.channels {
// skip episodes, only containers or videos/live
if pli.header.item_type == PlaylistItemType::Series {
continue;
}
for fav in favourites {
if is_valid(pli, &fav.filter, false) {
let mut channel = pli.clone();
channel.header.group.clone_from(&fav.group);
// Update UUID to be an alias of the original
channel.header.uuid = create_alias_uuid(&pli.header.uuid, &fav.group);
fav_groups
.entry(fav.group.clone())
.or_default()
.push(channel);
}
}
}
}
for (group_name, channels) in fav_groups {
if !channels.is_empty() {
let xtream_cluster = channels[0].header.xtream_cluster;
playlist.push(PlaylistGroup {
id: 0,
title: group_name,
channels,
xtream_cluster,
});
}
}
}
}
async fn trakt_playlist(client: &reqwest::Client, target: &ConfigTarget, errors: &mut Vec<TuliproxError>, playlist: &mut Vec<PlaylistGroup>) -> bool {
match process_trakt_categories_for_target(client, playlist, target).await {
Ok(Some(trakt_categories)) => {