mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-11 10:22:10 +02:00
192 lines
7.4 KiB
Rust
192 lines
7.4 KiB
Rust
use crate::model::{AppConfig, Config, ConfigInput, ConfigTarget};
|
|
use shared::model::{InputType, M3uPlaylistItem, PlaylistGroup, PlaylistItemType, TargetType, XtreamCluster};
|
|
use crate::repository::{m3u_repository, xtream_repository};
|
|
use crate::utils::{m3u, xtream};
|
|
use axum::response::IntoResponse;
|
|
use serde::Serialize;
|
|
use serde_json::{json, Value};
|
|
use std::sync::Arc;
|
|
use indexmap::IndexMap;
|
|
use crate::utils;
|
|
|
|
#[derive(serde::Serialize, serde::Deserialize)]
|
|
struct PlaylistResponseGroup {
|
|
id: u32,
|
|
title: String,
|
|
channels: serde_json::Value,
|
|
xtream_cluster: XtreamCluster,
|
|
}
|
|
|
|
#[derive(serde::Serialize, serde::Deserialize)]
|
|
struct PlaylistResponse {
|
|
live: Option<Vec<PlaylistResponseGroup>>,
|
|
vod: Option<Vec<PlaylistResponseGroup>>,
|
|
series: Option<Vec<PlaylistResponseGroup>>,
|
|
}
|
|
|
|
fn group_playlist_items<T>(
|
|
cluster: XtreamCluster,
|
|
iter: impl Iterator<Item=T>,
|
|
get_group: fn(&T) -> String,
|
|
) -> Vec<PlaylistResponseGroup>
|
|
where
|
|
T: Serialize,
|
|
{
|
|
let mut groups: IndexMap<String, Vec<T>> = IndexMap::new();
|
|
|
|
for item in iter {
|
|
let group_key = get_group(&item);
|
|
groups.entry(group_key)
|
|
.or_default()
|
|
.push(item);
|
|
}
|
|
|
|
groups
|
|
.into_iter()
|
|
.enumerate()
|
|
.map(|(index, (key, value))| PlaylistResponseGroup {
|
|
#[allow(clippy::cast_possible_truncation)]
|
|
id: index as u32,
|
|
title: key.to_string(),
|
|
channels: serde_json::to_value(value).unwrap_or(Value::Null),
|
|
xtream_cluster: cluster,
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn group_playlist_items_by_cluster(params: Option<(utils::FileReadGuard,
|
|
impl Iterator<Item=(M3uPlaylistItem, bool)>)>) ->
|
|
(Vec<M3uPlaylistItem>, Vec<M3uPlaylistItem>, Vec<M3uPlaylistItem>) {
|
|
if params.is_none() {
|
|
return (vec![], vec![], vec![]);
|
|
}
|
|
let mut live = Vec::new();
|
|
let mut video = Vec::new();
|
|
let mut series = Vec::new();
|
|
let (guard, iter) = params.unwrap();
|
|
for (item, _) in iter {
|
|
match item.item_type {
|
|
PlaylistItemType::Live
|
|
| PlaylistItemType::LiveUnknown
|
|
| PlaylistItemType::LiveHls
|
|
| PlaylistItemType::LiveDash => {
|
|
live.push(item);
|
|
}
|
|
PlaylistItemType::Catchup
|
|
| PlaylistItemType::Video => {
|
|
video.push(item);
|
|
}
|
|
PlaylistItemType::Series
|
|
| PlaylistItemType::SeriesInfo => {
|
|
series.push(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
drop(guard);
|
|
|
|
(live, video, series)
|
|
}
|
|
|
|
fn group_playlist_groups_by_cluster(playlist: Vec<PlaylistGroup>, input_type: InputType) -> (Vec<PlaylistResponseGroup>, Vec<PlaylistResponseGroup>, Vec<PlaylistResponseGroup>) {
|
|
let mut live = Vec::new();
|
|
let mut video = Vec::new();
|
|
let mut series = Vec::new();
|
|
for group in playlist {
|
|
let channels = group.channels.iter().map(|item| if input_type == InputType::M3u { serde_json::to_value(item.to_m3u()).unwrap() } else { serde_json::to_value(item.to_xtream()).unwrap() }).collect();
|
|
let grp = PlaylistResponseGroup {
|
|
id: group.id,
|
|
title: group.title,
|
|
channels,
|
|
xtream_cluster: group.xtream_cluster,
|
|
};
|
|
match group.xtream_cluster {
|
|
XtreamCluster::Live => live.push(grp),
|
|
XtreamCluster::Video => video.push(grp),
|
|
XtreamCluster::Series => series.push(grp),
|
|
}
|
|
}
|
|
(live, video, series)
|
|
}
|
|
|
|
|
|
// async fn get_categories_content(action: Result<(Option<PathBuf>, Option<String>), std::io::Error>) -> Option<String> {
|
|
// if let Ok((Some(file_path), _content)) = action {
|
|
// if let Ok(content) = tokio::fs::read_to_string(&file_path).await {
|
|
// // TODO deserialize like sax parser
|
|
// if let Ok(categories) = serde_json::from_str::<Vec<PlaylistXtreamCategory>>(&content) {
|
|
// return serde_json::to_string(&categories).ok();
|
|
// }
|
|
// }
|
|
// }
|
|
// None
|
|
// }
|
|
|
|
|
|
async fn grouped_channels(
|
|
cfg: &AppConfig,
|
|
target: &ConfigTarget,
|
|
cluster: XtreamCluster,
|
|
) -> Option<Vec<PlaylistResponseGroup>> {
|
|
xtream_repository::iter_raw_xtream_playlist(cfg, target, cluster).await
|
|
.map(|(_guard, iter)| group_playlist_items(
|
|
cluster,
|
|
iter.map(|(v, _)| v),
|
|
|item| item.group.clone(),
|
|
))
|
|
}
|
|
|
|
pub(in crate::api::endpoints) async fn get_playlist_for_target(cfg_target: Option<&ConfigTarget>, cfg: &AppConfig) -> impl axum::response::IntoResponse + Send {
|
|
if let Some(target) = cfg_target {
|
|
if target.has_output(&TargetType::Xtream) {
|
|
let live_channels = grouped_channels(cfg, target, XtreamCluster::Live).await;
|
|
let vod_channels = grouped_channels(cfg, target, XtreamCluster::Video).await;
|
|
let series_channels = grouped_channels(cfg, target, XtreamCluster::Series).await;
|
|
|
|
let response = PlaylistResponse {
|
|
live: live_channels,
|
|
vod: vod_channels,
|
|
series: series_channels,
|
|
};
|
|
|
|
return (axum::http::StatusCode::OK, axum::Json(response)).into_response();
|
|
} else if target.has_output(&TargetType::M3u) {
|
|
let all_channels = m3u_repository::iter_raw_m3u_playlist(cfg, target).await;
|
|
let (live_channels, vod_channels, series_channels) = group_playlist_items_by_cluster(all_channels);
|
|
let response = PlaylistResponse {
|
|
live: Some(group_playlist_items(XtreamCluster::Live, live_channels.into_iter(), |item| item.group.clone())),
|
|
vod: Some(group_playlist_items(XtreamCluster::Video, vod_channels.into_iter(), |item| item.group.clone())),
|
|
series: Some(group_playlist_items(XtreamCluster::Series, series_channels.into_iter(), |item| item.group.clone())),
|
|
};
|
|
|
|
return (axum::http::StatusCode::OK, axum::Json(response)).into_response();
|
|
}
|
|
}
|
|
(axum::http::StatusCode::BAD_REQUEST, axum::Json(json!({"error": "Invalid Arguments"}))).into_response()
|
|
}
|
|
|
|
pub(in crate::api::endpoints) async fn get_playlist(client: Arc<reqwest::Client>, cfg_input: Option<&ConfigInput>, cfg: &Config) -> impl IntoResponse + Send {
|
|
match cfg_input {
|
|
Some(input) => {
|
|
let (result, errors) =
|
|
match input.input_type {
|
|
InputType::M3u | InputType::M3uBatch => m3u::get_m3u_playlist(client, cfg, input, &cfg.working_dir).await,
|
|
InputType::Xtream | InputType::XtreamBatch => xtream::get_xtream_playlist(cfg, client, input, &cfg.working_dir).await,
|
|
};
|
|
if result.is_empty() {
|
|
let error_strings: Vec<String> = errors.iter().map(std::string::ToString::to_string).collect();
|
|
(axum::http::StatusCode::BAD_REQUEST, axum::Json(json!({"error": error_strings.join(", ")}))).into_response()
|
|
} else {
|
|
let (live, vod, series) = group_playlist_groups_by_cluster(result, input.input_type);
|
|
let response = PlaylistResponse {
|
|
live: Some(live),
|
|
vod: Some(vod),
|
|
series: Some(series),
|
|
};
|
|
(axum::http::StatusCode::OK, axum::Json(response)).into_response()
|
|
}
|
|
}
|
|
None => (axum::http::StatusCode::BAD_REQUEST, axum::Json(json!({"error": "Invalid Arguments"}))).into_response(),
|
|
}
|
|
}
|