mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-18 13:52:20 +02:00
user boquet for m3u playlist
This commit is contained in:
@@ -1,41 +1,47 @@
|
||||
use std::sync::Arc;
|
||||
use crate::api::api_utils::{get_user_target_by_username, get_username_from_auth_header};
|
||||
use crate::api::model::app_state::AppState;
|
||||
use crate::auth::authenticator::validator_user;
|
||||
use crate::model::config::TargetType;
|
||||
use crate::model::playlist_categories::PlaylistCategoriesDto;
|
||||
use crate::model::config::{Config, ConfigTarget, TargetType};
|
||||
use crate::model::playlist::XtreamCluster;
|
||||
use crate::model::playlist_categories::PlaylistBouquetDto;
|
||||
use crate::model::xtream::PlaylistXtreamCategory;
|
||||
use crate::repository::user_repository::{load_user_bouquet_as_json, save_user_bouquet};
|
||||
use crate::repository::xtream_repository;
|
||||
use crate::repository::{m3u_repository};
|
||||
use actix_web::body::BodyStream;
|
||||
use actix_web::middleware::Compress;
|
||||
use actix_web::{web, HttpResponse};
|
||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
use actix_web_httpauth::middleware::HttpAuthentication;
|
||||
use bytes::Bytes;
|
||||
use futures::stream;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::Error;
|
||||
use std::path::PathBuf;
|
||||
use futures::{stream, StreamExt};
|
||||
use log::error;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use crate::repository::xtream_repository::xtream_get_playlist_categories;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct PlaylistXtreamCategory {
|
||||
#[serde(alias = "category_id")]
|
||||
pub id: String,
|
||||
#[serde(alias = "category_name")]
|
||||
pub name: String,
|
||||
fn get_categories_from_xtream(categories: Option<Vec<PlaylistXtreamCategory>>) -> Vec<String> {
|
||||
let mut groups: Vec<String> = Vec::new();
|
||||
if let Some(cats) = categories {
|
||||
for category in cats {
|
||||
groups.push(category.name.to_string());
|
||||
}
|
||||
}
|
||||
groups
|
||||
}
|
||||
|
||||
pub(crate) async fn get_categories_content(action: Result<(Option<PathBuf>, Option<String>), 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();
|
||||
|
||||
fn get_categories_from_m3u_playlist(target: &ConfigTarget, config: &Arc<Config>) -> Vec<String> {
|
||||
let mut groups = Vec::new();
|
||||
if let Some((_guard, iter)) = m3u_repository::iter_raw_m3u_playlist(config, target) {
|
||||
let mut unique_groups = HashSet::new();
|
||||
for (item, _has_next) in iter {
|
||||
if !unique_groups.contains(item.group.as_str()) {
|
||||
unique_groups.insert(item.group.to_string());
|
||||
groups.push(item.group.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
groups
|
||||
}
|
||||
|
||||
async fn playlist_categories(
|
||||
@@ -49,24 +55,44 @@ async fn playlist_categories(
|
||||
}
|
||||
let config = &app_state.config;
|
||||
let target_name = &target.name;
|
||||
if target.has_output(&TargetType::Xtream) {
|
||||
let live_categories = get_categories_content(xtream_repository::xtream_get_collection_path(config, target_name, xtream_repository::COL_CAT_LIVE)).await;
|
||||
let vod_categories = get_categories_content(xtream_repository::xtream_get_collection_path(config, target_name, xtream_repository::COL_CAT_VOD)).await;
|
||||
let series_categories = get_categories_content(xtream_repository::xtream_get_collection_path(config, target_name, xtream_repository::COL_CAT_SERIES)).await;
|
||||
let json_stream =
|
||||
stream::iter(vec![
|
||||
Ok::<Bytes, String>(Bytes::from(r#"{"live": "#.to_string())),
|
||||
Ok::<Bytes, String>(Bytes::from(live_categories.unwrap_or("null".to_string()))),
|
||||
Ok::<Bytes, String>(Bytes::from(r#", "vod": "#.to_string())),
|
||||
Ok::<Bytes, String>(Bytes::from(vod_categories.unwrap_or("null".to_string()))),
|
||||
Ok::<Bytes, String>(Bytes::from(r#", "series": "#.to_string())),
|
||||
Ok::<Bytes, String>(Bytes::from(series_categories.unwrap_or("null".to_string()))),
|
||||
Ok::<Bytes, String>(Bytes::from(r"}".to_string())),
|
||||
]);
|
||||
return HttpResponse::Ok()
|
||||
.content_type(mime::APPLICATION_JSON)
|
||||
.body(BodyStream::new(json_stream));
|
||||
} else if target.has_output(&TargetType::M3u) {}
|
||||
let xtream_stream = if target.has_output(&TargetType::Xtream) {
|
||||
let live_categories = get_categories_from_xtream(xtream_get_playlist_categories(config, target_name, XtreamCluster::Live).await);
|
||||
let vod_categories = get_categories_from_xtream(xtream_get_playlist_categories(config, target_name, XtreamCluster::Video).await);
|
||||
let series_categories = get_categories_from_xtream(xtream_get_playlist_categories(config, target_name, XtreamCluster::Series).await);
|
||||
stream::iter(vec![
|
||||
Ok::<Bytes, String>(Bytes::from(r#"{"live": "#)),
|
||||
Ok::<Bytes, String>(Bytes::from(serde_json::to_string(&live_categories).unwrap_or("[]".to_string()))),
|
||||
Ok::<Bytes, String>(Bytes::from(r#", "vod": "#.to_string())),
|
||||
Ok::<Bytes, String>(Bytes::from(serde_json::to_string(&vod_categories).unwrap_or("[]".to_string()))),
|
||||
Ok::<Bytes, String>(Bytes::from(r#", "series": "#)),
|
||||
Ok::<Bytes, String>(Bytes::from(serde_json::to_string(&series_categories).unwrap_or("[]".to_string()))),
|
||||
Ok::<Bytes, String>(Bytes::from(r"}")),
|
||||
])
|
||||
} else {
|
||||
stream::iter(vec![Ok::<Bytes, String>(Bytes::from(r#"{"live":[],"vod":[],"series":[]}"#))])
|
||||
};
|
||||
|
||||
let m3u_stream = if target.has_output(&TargetType::M3u) {
|
||||
let live_categories = get_categories_from_m3u_playlist(target, config);
|
||||
stream::iter(vec![
|
||||
Ok::<Bytes, String>(Bytes::from(r#"{"live": "#)),
|
||||
Ok::<Bytes, String>(Bytes::from(serde_json::to_string(&live_categories).unwrap_or("[]".to_string()))),
|
||||
Ok::<Bytes, String>(Bytes::from(r#","vod":[],"series":[]}"#)),
|
||||
])
|
||||
} else {
|
||||
stream::iter(vec![Ok::<Bytes, String>(Bytes::from(r#"{"live":[],"vod":[],"series":[]}"#))])
|
||||
};
|
||||
|
||||
let json_stream = stream::once(async { Ok::<Bytes, String>(Bytes::from(r#"{"xtream": "#)) })
|
||||
.chain(xtream_stream)
|
||||
.chain(stream::once(async { Ok::<Bytes, String>(Bytes::from(r#", "m3u": "#)) }))
|
||||
.chain(m3u_stream)
|
||||
.chain(stream::once(async { Ok::<Bytes, String>(Bytes::from("}")) }));
|
||||
|
||||
|
||||
return HttpResponse::Ok()
|
||||
.content_type(mime::APPLICATION_JSON)
|
||||
.body(BodyStream::new(json_stream));
|
||||
}
|
||||
}
|
||||
HttpResponse::BadRequest().finish()
|
||||
@@ -75,17 +101,17 @@ async fn playlist_categories(
|
||||
async fn save_playlist_bouquet(
|
||||
credentials: Option<BearerAuth>,
|
||||
app_state: web::Data<Arc<AppState>>,
|
||||
req: web::Json<PlaylistCategoriesDto>,
|
||||
req: web::Json<PlaylistBouquetDto>,
|
||||
) -> HttpResponse {
|
||||
if let Some(username) = get_username_from_auth_header(credentials, &app_state) {
|
||||
if let Some((user, _target)) = get_user_target_by_username(username.as_str(), &app_state).await {
|
||||
if !user.has_permissions(&app_state) {
|
||||
return HttpResponse::Forbidden().finish();
|
||||
}
|
||||
match save_user_bouquet(&app_state.config, &username, &req.0).await {
|
||||
match save_user_bouquet(&app_state.config, &username, &req.0) {
|
||||
Ok(()) => {
|
||||
return HttpResponse::Ok().finish();
|
||||
},
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Saving bouquet for {username} failed: {err}");
|
||||
}
|
||||
@@ -104,9 +130,10 @@ async fn playlist_bouquet(
|
||||
if !user.has_permissions(&app_state) {
|
||||
return HttpResponse::Forbidden().finish();
|
||||
}
|
||||
if let Some(bouquet) = load_user_bouquet_as_json(&app_state.config, &username).await {
|
||||
return HttpResponse::Ok().content_type(mime::APPLICATION_JSON).body(bouquet);
|
||||
}
|
||||
let xtream = load_user_bouquet_as_json(&app_state.config, &username, TargetType::Xtream).await;
|
||||
let m3u = load_user_bouquet_as_json(&app_state.config, &username, TargetType::M3u).await;
|
||||
return HttpResponse::Ok().content_type(mime::APPLICATION_JSON).body(
|
||||
format!(r#"{{"xtream": {}, "m3u": {} }}"#, xtream.unwrap_or("null".to_string()), m3u.unwrap_or("null".to_string())));
|
||||
}
|
||||
}
|
||||
HttpResponse::Ok().content_type(mime::APPLICATION_JSON).body("{}")
|
||||
|
||||
+28
-14
@@ -1,4 +1,5 @@
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::body::BodyStream;
|
||||
@@ -11,7 +12,7 @@ use log::error;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::api::endpoints::user_api::user_api_register;
|
||||
use crate::api::endpoints::{download_api, user_api};
|
||||
use crate::api::endpoints::{download_api};
|
||||
use crate::api::model::app_state::AppState;
|
||||
use crate::api::model::config::{ServerConfig, ServerInputConfig, ServerSourceConfig, ServerTargetConfig};
|
||||
use crate::api::model::request::{PlaylistRequest, PlaylistRequestType};
|
||||
@@ -20,6 +21,7 @@ use crate::m3u_filter_error::M3uFilterError;
|
||||
use crate::model::api_proxy::{ApiProxyConfig, ApiProxyServerInfo, TargetUser};
|
||||
use crate::model::config::{validate_targets, Config, ConfigDto, ConfigInput, ConfigInputOptions, ConfigSource, ConfigTarget, InputType, TargetType};
|
||||
use crate::model::playlist::{XtreamCluster};
|
||||
use crate::model::xtream::PlaylistXtreamCategory;
|
||||
use crate::processing::processor::playlist;
|
||||
use crate::repository::user_repository::store_api_user;
|
||||
use crate::repository::xtream_repository;
|
||||
@@ -208,13 +210,25 @@ async fn get_playlist(client: Arc<reqwest::Client>, cfg_input: Option<&ConfigInp
|
||||
|
||||
|
||||
|
||||
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 get_playlist_for_target(cfg_target: Option<&ConfigTarget>, cfg: &Arc<Config>) -> HttpResponse {
|
||||
if let Some(target) = cfg_target {
|
||||
let target_name = &target.name;
|
||||
if target.has_output(&TargetType::Xtream) {
|
||||
let live_categories = user_api::get_categories_content(xtream_repository::xtream_get_collection_path(cfg, target_name, xtream_repository::COL_CAT_LIVE)).await;
|
||||
let vod_categories = user_api::get_categories_content(xtream_repository::xtream_get_collection_path(cfg, target_name, xtream_repository::COL_CAT_VOD)).await;
|
||||
let series_categories = user_api::get_categories_content(xtream_repository::xtream_get_collection_path(cfg, target_name, xtream_repository::COL_CAT_SERIES)).await;
|
||||
let live_categories = get_categories_content(xtream_repository::xtream_get_collection_path(cfg, target_name, xtream_repository::COL_CAT_LIVE)).await;
|
||||
let vod_categories = get_categories_content(xtream_repository::xtream_get_collection_path(cfg, target_name, xtream_repository::COL_CAT_VOD)).await;
|
||||
let series_categories = get_categories_content(xtream_repository::xtream_get_collection_path(cfg, target_name, xtream_repository::COL_CAT_SERIES)).await;
|
||||
|
||||
let live_channels = xtream_repository::iter_raw_xtream_playlist(cfg, target, XtreamCluster::Live);
|
||||
let vod_channels = xtream_repository::iter_raw_xtream_playlist(cfg, target, XtreamCluster::Video);
|
||||
@@ -226,19 +240,19 @@ async fn get_playlist_for_target(cfg_target: Option<&ConfigTarget>, cfg: &Arc<Co
|
||||
|
||||
let json_stream =
|
||||
stream::iter(vec![
|
||||
Ok::<Bytes, String>(Bytes::from(r#"{"categories": {"live": "#.to_string())),
|
||||
Ok::<Bytes, String>(Bytes::from(live_categories.unwrap_or("null".to_string()))),
|
||||
Ok::<Bytes, String>(Bytes::from(r#", "vod": "#.to_string())),
|
||||
Ok::<Bytes, String>(Bytes::from(vod_categories.unwrap_or("null".to_string()))),
|
||||
Ok::<Bytes, String>(Bytes::from(r#", "series": "#.to_string())),
|
||||
Ok::<Bytes, String>(Bytes::from(series_categories.unwrap_or("null".to_string()))),
|
||||
Ok::<Bytes, String>(Bytes::from(r#"},"channels": {"live": ["#.to_string())),
|
||||
Ok::<Bytes, String>(Bytes::from(r#"{"categories": {"live": "#)),
|
||||
Ok::<Bytes, String>(Bytes::from(live_categories.unwrap_or("[]".to_string()))),
|
||||
Ok::<Bytes, String>(Bytes::from(r#", "vod": "#)),
|
||||
Ok::<Bytes, String>(Bytes::from(vod_categories.unwrap_or("[]".to_string()))),
|
||||
Ok::<Bytes, String>(Bytes::from(r#", "series": "#)),
|
||||
Ok::<Bytes, String>(Bytes::from(series_categories.unwrap_or("[]".to_string()))),
|
||||
Ok::<Bytes, String>(Bytes::from(r#"},"channels": {"live": ["#)),
|
||||
]).chain(live_stream).chain(stream::iter(vec![
|
||||
Ok::<Bytes, String>(Bytes::from(r#"], "vod": ["#.to_string())),
|
||||
Ok::<Bytes, String>(Bytes::from(r#"], "vod": ["#)),
|
||||
])).chain(vod_stream).chain(stream::iter(vec![
|
||||
Ok::<Bytes, String>(Bytes::from(r#"], "series": ["#.to_string())),
|
||||
Ok::<Bytes, String>(Bytes::from(r#"], "series": ["#)),
|
||||
])).chain(series_stream).chain(stream::iter(vec![
|
||||
Ok::<Bytes, String>(Bytes::from(r"]}}".to_string())),
|
||||
Ok::<Bytes, String>(Bytes::from(r"]}}")),
|
||||
]));
|
||||
return HttpResponse::Ok().content_type(mime::APPLICATION_JSON).body(BodyStream::new(json_stream));
|
||||
} else if target.has_output(&TargetType::M3u) {
|
||||
|
||||
@@ -514,9 +514,9 @@ async fn xtream_player_api_handle_content_action(config: &Config, target_name: &
|
||||
if let Some(file_path) = path {
|
||||
// load user bouquet
|
||||
let filter = match action {
|
||||
ACTION_GET_LIVE_CATEGORIES => user_repository::user_get_bouquet_filter(config, &user.username, category_id, XtreamCluster::Live).await,
|
||||
ACTION_GET_VOD_CATEGORIES => user_repository::user_get_bouquet_filter(config, &user.username, category_id, XtreamCluster::Video).await,
|
||||
ACTION_GET_SERIES_CATEGORIES => user_repository::user_get_bouquet_filter(config, &user.username, category_id, XtreamCluster::Series).await,
|
||||
ACTION_GET_LIVE_CATEGORIES => user_repository::user_get_bouquet_filter(config, target_name,&user.username, category_id, TargetType::Xtream, XtreamCluster::Live).await,
|
||||
ACTION_GET_VOD_CATEGORIES => user_repository::user_get_bouquet_filter(config, target_name, &user.username, category_id, TargetType::Xtream, XtreamCluster::Video).await,
|
||||
ACTION_GET_SERIES_CATEGORIES => user_repository::user_get_bouquet_filter(config, target_name, &user.username, category_id, TargetType::Xtream, XtreamCluster::Series).await,
|
||||
_ => None
|
||||
};
|
||||
if let Some(flt) = filter {
|
||||
|
||||
Reference in New Issue
Block a user