mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-22 15:52:24 +02:00
168 lines
6.9 KiB
Rust
168 lines
6.9 KiB
Rust
use crate::model::{AppConfig, Config, ConfigInput, ConfigTarget};
|
|
use crate::repository::{m3u_repository, xtream_repository};
|
|
use crate::utils::{m3u, xtream};
|
|
use crate::utils;
|
|
use axum::response::IntoResponse;
|
|
use indexmap::IndexMap;
|
|
use serde::Serialize;
|
|
use serde_json::{json};
|
|
use shared::model::{CommonPlaylistItem, InputType, M3uPlaylistItem, PlaylistCategoriesResponse,
|
|
PlaylistGroup, PlaylistItemType, PlaylistResponseGroup, TargetType, XtreamCluster};
|
|
use std::sync::Arc;
|
|
use crate::api::api_utils::{json_or_bin_response};
|
|
|
|
fn group_playlist_items<T>(
|
|
cluster: XtreamCluster,
|
|
iter: impl Iterator<Item=T>,
|
|
get_group: fn(&T) -> String,
|
|
) -> Vec<PlaylistResponseGroup>
|
|
where
|
|
T: Serialize + Into<CommonPlaylistItem>,
|
|
{
|
|
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.clone(),
|
|
channels: value.into_iter().map(Into::into).collect(),
|
|
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>) {
|
|
match params {
|
|
None => (vec![], vec![], vec![]),
|
|
Some((guard, iter)) => {
|
|
let mut live = Vec::new();
|
|
let mut video = Vec::new();
|
|
let mut series = Vec::new();
|
|
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>) -> (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(shared::model::PlaylistItem::to_common)
|
|
.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 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::<CommonPlaylistItem>(
|
|
cluster,
|
|
iter.map(|(v, _)| v.to_common()),
|
|
|item| item.group.clone(),
|
|
))
|
|
}
|
|
|
|
pub(in crate::api::endpoints) async fn get_playlist_for_target(cfg_target: Option<&ConfigTarget>, cfg: &AppConfig, accept: Option<&String>) -> 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 = PlaylistCategoriesResponse {
|
|
live: live_channels,
|
|
vod: vod_channels,
|
|
series: series_channels,
|
|
};
|
|
|
|
return json_or_bin_response(accept, &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 = PlaylistCategoriesResponse {
|
|
live: Some(group_playlist_items::<M3uPlaylistItem>(XtreamCluster::Live, live_channels.into_iter(), |item| item.group.clone())),
|
|
vod: Some(group_playlist_items::<M3uPlaylistItem>(XtreamCluster::Video, vod_channels.into_iter(), |item| item.group.clone())),
|
|
series: Some(group_playlist_items::<M3uPlaylistItem>(XtreamCluster::Series, series_channels.into_iter(), |item| item.group.clone())),
|
|
};
|
|
|
|
return json_or_bin_response(accept, &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<&Arc<ConfigInput>>, cfg: &Arc<Config>, accept: Option<&String>) -> 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);
|
|
let response = PlaylistCategoriesResponse {
|
|
live: Some(live),
|
|
vod: Some(vod),
|
|
series: Some(series),
|
|
};
|
|
json_or_bin_response(accept, &response).into_response()
|
|
}
|
|
}
|
|
None => (axum::http::StatusCode::BAD_REQUEST, axum::Json(json!({"error": "Invalid Arguments"}))).into_response(),
|
|
}
|
|
}
|