2023-10-09 00:17:54 +02:00
|
|
|
// https://github.com/tellytv/go.xtream-codes/blob/master/structs.go
|
|
|
|
|
|
2023-11-22 21:06:58 +01:00
|
|
|
use std::io::Error;
|
|
|
|
|
use std::str::FromStr;
|
2023-11-02 20:03:22 +01:00
|
|
|
use actix_web::{HttpRequest, HttpResponse, web, Resource};
|
2023-10-09 00:17:54 +02:00
|
|
|
use chrono::{Duration, Local};
|
2023-10-25 21:07:38 +02:00
|
|
|
use log::debug;
|
2023-10-20 12:18:44 +02:00
|
|
|
|
2023-11-22 21:06:58 +01:00
|
|
|
use crate::api::api_utils::{get_user_target, get_user_target_by_credentials, serve_file};
|
2023-10-25 09:57:44 +02:00
|
|
|
use crate::api::api_model::{AppState, UserApiRequest, XtreamAuthorizationResponse, XtreamServerInfo, XtreamUserInfo};
|
|
|
|
|
use crate::model::api_proxy::{UserCredentials};
|
2023-11-22 21:06:58 +01:00
|
|
|
use crate::model::config::{Config};
|
2023-10-23 09:58:36 +02:00
|
|
|
use crate::model::model_config::{TargetType};
|
2023-11-24 22:53:33 +01:00
|
|
|
use crate::model::model_m3u::XtreamCluster;
|
|
|
|
|
use crate::repository::xtream_repository::{COL_CAT_LIVE, COL_CAT_SERIES, COL_CAT_VOD, COL_LIVE, COL_SERIES, COL_VOD,
|
|
|
|
|
xtream_get_all,
|
|
|
|
|
xtream_get_short_epg, xtream_get_stream_info};
|
2023-11-22 21:06:58 +01:00
|
|
|
use crate::utils::get_client_request;
|
2023-10-09 00:17:54 +02:00
|
|
|
|
2023-10-25 09:57:44 +02:00
|
|
|
fn get_user_info(user: &UserCredentials, cfg: &Config) -> XtreamAuthorizationResponse {
|
2023-10-27 08:56:32 +02:00
|
|
|
let server = cfg._api_proxy.read().unwrap().as_ref().unwrap().server.clone();
|
2023-10-09 00:17:54 +02:00
|
|
|
let now = Local::now();
|
|
|
|
|
XtreamAuthorizationResponse {
|
|
|
|
|
user_info: XtreamUserInfo {
|
2023-10-26 19:50:35 +02:00
|
|
|
active_cons: "0".to_string(),
|
2023-10-09 00:17:54 +02:00
|
|
|
allowed_output_formats: Vec::from(["ts".to_string()]),
|
|
|
|
|
auth: 1,
|
2023-10-09 18:18:27 +02:00
|
|
|
created_at: (now - Duration::days(365)).timestamp(), // fake
|
|
|
|
|
exp_date: (now + Duration::days(365)).timestamp(),// fake
|
2023-10-26 19:50:35 +02:00
|
|
|
is_trial: "0".to_string(),
|
|
|
|
|
max_connections: "1".to_string(),
|
2023-10-09 00:17:54 +02:00
|
|
|
message: server.message.to_string(),
|
2023-10-25 09:57:44 +02:00
|
|
|
password: user.password.to_string(),
|
|
|
|
|
username: user.username.to_string(),
|
2023-10-09 00:17:54 +02:00
|
|
|
status: "Active".to_string(),
|
|
|
|
|
},
|
|
|
|
|
server_info: XtreamServerInfo {
|
|
|
|
|
url: server.ip.to_string(),
|
|
|
|
|
port: server.http_port,
|
|
|
|
|
https_port: server.https_port,
|
|
|
|
|
server_protocol: server.protocol.clone(),
|
|
|
|
|
rtmp_port: server.rtmp_port,
|
|
|
|
|
timezone: server.timezone.to_string(),
|
|
|
|
|
timestamp_now: now.timestamp(),
|
|
|
|
|
time_now: now.format("%Y-%m-%d %H:%M:%S").to_string(),
|
2023-10-09 18:18:27 +02:00
|
|
|
},
|
2023-10-09 00:17:54 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-22 21:06:58 +01:00
|
|
|
async fn xtream_player_api_stream(
|
|
|
|
|
api_req: &web::Query<UserApiRequest>,
|
|
|
|
|
_app_state: &web::Data<AppState>,
|
|
|
|
|
context: &str,
|
|
|
|
|
username: &str,
|
|
|
|
|
password: &str,
|
2023-11-24 22:53:33 +01:00
|
|
|
action_path: &str,
|
2023-11-22 21:06:58 +01:00
|
|
|
) -> HttpResponse {
|
2023-11-24 22:53:33 +01:00
|
|
|
if let Some((_user, target)) = get_user_target_by_credentials(username, password, api_req, _app_state) {
|
2023-11-22 21:06:58 +01:00
|
|
|
let target_name = &target.name;
|
|
|
|
|
if target.has_output(&TargetType::Xtream) {
|
|
|
|
|
match _app_state.config.get_xtream_input_for_target(target_name) {
|
|
|
|
|
None => {}
|
|
|
|
|
Some(input) => {
|
|
|
|
|
let username = input.username.as_ref().unwrap().clone();
|
|
|
|
|
let password = input.password.as_ref().unwrap().clone();
|
2023-11-24 22:53:33 +01:00
|
|
|
let stream_url = format!("{}/{}/{}/{}/{}", input.url, context, username, password, action_path);
|
2023-11-22 21:06:58 +01:00
|
|
|
let url = reqwest::Url::parse(&stream_url).unwrap();
|
|
|
|
|
let client = get_client_request(input, url);
|
|
|
|
|
if let Ok(response) = client.send().await {
|
|
|
|
|
if response.status().is_success() {
|
|
|
|
|
return HttpResponse::Ok().streaming(response.bytes_stream());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
HttpResponse::BadRequest().finish()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn xtream_player_api_live_stream(
|
|
|
|
|
api_req: web::Query<UserApiRequest>,
|
|
|
|
|
path: web::Path<(String, String, String)>,
|
|
|
|
|
_app_state: web::Data<AppState>,
|
|
|
|
|
) -> HttpResponse {
|
|
|
|
|
let (username, password, stream_id) = path.into_inner();
|
|
|
|
|
xtream_player_api_stream(&api_req, &_app_state, "live", &username, &password, &stream_id).await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn xtream_player_api_series_stream(
|
|
|
|
|
api_req: web::Query<UserApiRequest>,
|
|
|
|
|
path: web::Path<(String, String, String)>,
|
|
|
|
|
_app_state: web::Data<AppState>,
|
|
|
|
|
) -> HttpResponse {
|
|
|
|
|
let (username, password, stream_id) = path.into_inner();
|
|
|
|
|
xtream_player_api_stream(&api_req, &_app_state, "series", &username, &password, &stream_id).await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn xtream_player_api_movie_stream(
|
|
|
|
|
api_req: web::Query<UserApiRequest>,
|
|
|
|
|
path: web::Path<(String, String, String)>,
|
|
|
|
|
_app_state: web::Data<AppState>,
|
|
|
|
|
) -> HttpResponse {
|
|
|
|
|
let (username, password, stream_id) = path.into_inner();
|
|
|
|
|
xtream_player_api_stream(&api_req, &_app_state, "movie", &username, &password, &stream_id).await
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 22:53:33 +01:00
|
|
|
async fn xtream_player_api_timeshift_stream(
|
|
|
|
|
api_req: web::Query<UserApiRequest>,
|
|
|
|
|
path: web::Path<(String, String, String, String, String)>,
|
|
|
|
|
_app_state: web::Data<AppState>,
|
|
|
|
|
) -> HttpResponse {
|
|
|
|
|
let (username, password, duration, start, stream_id) = path.into_inner();
|
|
|
|
|
let action_path = format!("{}/{}/{}", duration, start, stream_id);
|
|
|
|
|
xtream_player_api_stream(&api_req, &_app_state, "timeshift", &username, &password, &action_path).await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn xtream_get_stream_info_response(app_state: &AppState, target_name: &str, stream_id: &str, cluster: XtreamCluster,
|
|
|
|
|
user: &UserCredentials) -> HttpResponse {
|
|
|
|
|
match FromStr::from_str(stream_id) {
|
|
|
|
|
Ok(xtream_stream_id) => {
|
|
|
|
|
match xtream_get_stream_info(app_state, target_name, xtream_stream_id, cluster, user).await {
|
|
|
|
|
Ok(content) => HttpResponse::Ok().content_type(mime::APPLICATION_JSON).body(content),
|
|
|
|
|
Err(_) => HttpResponse::NoContent().finish()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(_) => HttpResponse::BadRequest().finish()
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-11-22 21:06:58 +01:00
|
|
|
|
2023-11-02 20:03:22 +01:00
|
|
|
async fn xtream_player_api(
|
2023-10-23 09:58:36 +02:00
|
|
|
api_req: web::Query<UserApiRequest>,
|
2023-10-09 00:17:54 +02:00
|
|
|
req: HttpRequest,
|
|
|
|
|
_app_state: web::Data<AppState>,
|
|
|
|
|
) -> HttpResponse {
|
2023-10-23 09:58:36 +02:00
|
|
|
match get_user_target(&api_req, &_app_state) {
|
2023-10-25 09:57:44 +02:00
|
|
|
Some((user, target)) => {
|
2023-10-25 21:07:38 +02:00
|
|
|
let action = api_req.action.trim();
|
2023-10-20 12:18:44 +02:00
|
|
|
let target_name = &target.name;
|
2023-10-20 13:32:33 +02:00
|
|
|
if target.has_output(&TargetType::Xtream) {
|
2023-10-25 21:07:38 +02:00
|
|
|
if action.is_empty() {
|
2023-10-26 16:23:32 +02:00
|
|
|
return HttpResponse::Ok().json(get_user_info(&user, &_app_state.config));
|
2023-10-20 13:32:33 +02:00
|
|
|
}
|
2023-11-22 21:06:58 +01:00
|
|
|
|
|
|
|
|
match action {
|
|
|
|
|
"get_series_info" => {
|
2023-11-24 22:53:33 +01:00
|
|
|
xtream_get_stream_info_response(&_app_state, target_name,
|
|
|
|
|
api_req.series_id.trim(),
|
|
|
|
|
XtreamCluster::Series, &user).await
|
2023-11-22 21:06:58 +01:00
|
|
|
}
|
|
|
|
|
"get_vod_info" => {
|
2023-11-24 22:53:33 +01:00
|
|
|
xtream_get_stream_info_response(&_app_state, target_name,
|
|
|
|
|
api_req.vod_id.trim(),
|
|
|
|
|
XtreamCluster::Video, &user).await
|
|
|
|
|
}
|
|
|
|
|
"get_short_epg" => {
|
|
|
|
|
match xtream_get_short_epg(&_app_state, target_name, api_req.stream_id.trim(), api_req.limit.trim(), &user).await {
|
|
|
|
|
Ok(content) => HttpResponse::Ok().content_type(mime::APPLICATION_JSON).body(content),
|
|
|
|
|
Err(_) => HttpResponse::NoContent().finish()
|
2023-11-22 21:06:58 +01:00
|
|
|
}
|
2023-10-20 13:32:33 +02:00
|
|
|
}
|
2023-11-22 21:06:58 +01:00
|
|
|
_ => {
|
|
|
|
|
match match action {
|
|
|
|
|
"get_live_categories" => xtream_get_all(&_app_state.config, target_name, COL_CAT_LIVE),
|
|
|
|
|
"get_vod_categories" => xtream_get_all(&_app_state.config, target_name, COL_CAT_VOD),
|
|
|
|
|
"get_series_categories" => xtream_get_all(&_app_state.config, target_name, COL_CAT_SERIES),
|
|
|
|
|
"get_live_streams" => xtream_get_all(&_app_state.config, target_name, COL_LIVE),
|
|
|
|
|
"get_vod_streams" => xtream_get_all(&_app_state.config, target_name, COL_VOD),
|
|
|
|
|
"get_series" => xtream_get_all(&_app_state.config, target_name, COL_SERIES),
|
|
|
|
|
_ => Err(Error::new(std::io::ErrorKind::Unsupported, format!("Cant find action: {}/{}", target_name, action))),
|
|
|
|
|
} {
|
|
|
|
|
Ok(maybe_file_path_or_content) => {
|
|
|
|
|
let (path, content) = maybe_file_path_or_content;
|
|
|
|
|
if let Some(file_path) = path {
|
|
|
|
|
serve_file(&file_path, &req).await
|
|
|
|
|
} else if let Some(payload) = content {
|
|
|
|
|
HttpResponse::Ok().body(payload)
|
|
|
|
|
} else {
|
|
|
|
|
HttpResponse::NoContent().finish()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
debug!("Could not open file for xtream target: {} {}", target_name, err);
|
|
|
|
|
HttpResponse::NoContent().finish()
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-25 21:07:38 +02:00
|
|
|
}
|
2023-10-09 00:17:54 +02:00
|
|
|
}
|
2023-10-20 13:32:33 +02:00
|
|
|
} else {
|
2023-10-25 21:07:38 +02:00
|
|
|
debug!("Target has not output xtream {}", target_name);
|
2023-10-20 13:32:33 +02:00
|
|
|
HttpResponse::BadRequest().finish()
|
2023-10-09 00:17:54 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
if api_req.action.is_empty() {
|
2023-10-25 21:07:38 +02:00
|
|
|
debug!("Paremeter action is empty!");
|
2023-10-09 00:17:54 +02:00
|
|
|
HttpResponse::Unauthorized().finish()
|
|
|
|
|
} else {
|
2023-10-25 21:07:38 +02:00
|
|
|
debug!("cant find user!");
|
2023-10-09 00:17:54 +02:00
|
|
|
HttpResponse::BadRequest().finish()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-20 12:18:44 +02:00
|
|
|
}
|
2023-11-02 20:03:22 +01:00
|
|
|
|
|
|
|
|
pub(crate) fn xtream_api_register() -> Vec<Resource> {
|
|
|
|
|
vec![
|
|
|
|
|
web::resource("/player_api.php").route(web::get().to(xtream_player_api)),
|
2023-11-24 22:53:33 +01:00
|
|
|
web::resource("/player_api.php").route(web::post().to(xtream_player_api)),
|
2023-11-02 20:03:22 +01:00
|
|
|
web::resource("/xtream").route(web::get().to(xtream_player_api)),
|
2023-11-22 21:06:58 +01:00
|
|
|
web::resource("/live/{username}/{password}/{stream_id}").route(web::get().to(xtream_player_api_live_stream)),
|
|
|
|
|
web::resource("/movie/{username}/{password}/{stream_id}").route(web::get().to(xtream_player_api_movie_stream)),
|
|
|
|
|
web::resource("/series/{username}/{password}/{stream_id}").route(web::get().to(xtream_player_api_series_stream)),
|
2023-11-24 22:53:33 +01:00
|
|
|
web::resource("/timeshift/{username}/{password}/{duration}/{start}{stream_id}").route(web::get().to(xtream_player_api_timeshift_stream)),
|
|
|
|
|
/* TODO
|
|
|
|
|
web::resource("/hlsr/{token}/{username}/{password}/{channel}/{hash}/{chunk}").route(web::get().to(xtream_player_api_hlsr_stream))
|
|
|
|
|
web::resource("/hls/{token}/{chunk}").route(web::get().to(xtream_player_api_hls_stream))
|
|
|
|
|
web::resource("/play/{token}/{type}").route(web::get().to(xtream_player_api_play_stream))
|
|
|
|
|
*/
|
2023-11-02 20:03:22 +01:00
|
|
|
]
|
|
|
|
|
}
|