mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-24 00:32:30 +02:00
changed user.yml to api-proxy.yml because of some other needed properties,
refactored file structure. Added xtream authentication response.
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use actix_cors::Cors;
|
||||
use actix_files::NamedFile;
|
||||
use actix_web::{App, get, HttpRequest, HttpResponse, HttpServer, web};
|
||||
use chrono::{Local};
|
||||
use cron::Schedule;
|
||||
use std::str::FromStr;
|
||||
use actix_web::middleware::Logger;
|
||||
use actix_web::web::Data;
|
||||
|
||||
use crate::api::model_api::{AppState, PlaylistRequest, ServerConfig};
|
||||
use crate::model::config::{Config, ConfigInput, InputType, ProcessTargets};
|
||||
use crate::{exit, playlist_processor};
|
||||
use crate::download::get_m3u_playlist;
|
||||
use crate::api::xtream_player_api::xtream_player_api;
|
||||
use log::{error};
|
||||
|
||||
#[get("/")]
|
||||
async fn index(
|
||||
_req: HttpRequest,
|
||||
_app_state: web::Data<AppState>,
|
||||
) -> std::io::Result<NamedFile> {
|
||||
let path: PathBuf = [_app_state.config.api.web_root.clone(), String::from("index.html")].iter().collect();
|
||||
NamedFile::open(path)
|
||||
}
|
||||
|
||||
pub(crate) async fn playlist(
|
||||
req: web::Json<PlaylistRequest>,
|
||||
_app_state: web::Data<AppState>,
|
||||
) -> HttpResponse {
|
||||
// TODO refactor this
|
||||
let input = ConfigInput {
|
||||
id: 0,
|
||||
input_type: InputType::M3u,
|
||||
headers: Default::default(),
|
||||
url: String::from(&req.url),
|
||||
username: None,
|
||||
password: None,
|
||||
persist: None,
|
||||
prefix: None,
|
||||
suffix: None,
|
||||
enabled: true,
|
||||
};
|
||||
let result = get_m3u_playlist(&_app_state.config, &input, &_app_state.config.working_dir);
|
||||
HttpResponse::Ok().json(result)
|
||||
}
|
||||
|
||||
pub(crate) async fn config(
|
||||
_app_state: web::Data<AppState>,
|
||||
) -> HttpResponse {
|
||||
let sources: Vec<String> = _app_state.config.sources.iter().map(|t| t.input.url.clone()).collect();
|
||||
let result = ServerConfig {
|
||||
sources
|
||||
};
|
||||
HttpResponse::Ok().json(result)
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
pub(crate) async fn start_server(cfg: Config, targets: ProcessTargets) -> futures::io::Result<()> {
|
||||
let host = cfg.api.host.clone();
|
||||
let port = cfg.api.port;
|
||||
let web_dir = cfg.api.web_root.clone();
|
||||
let web_dir_path = PathBuf::from(&web_dir);
|
||||
if !web_dir_path.exists() || !web_dir_path.is_dir() {
|
||||
exit!("web_root does not exists or is not an directory: {:?}", &web_dir_path);
|
||||
}
|
||||
|
||||
let schedule = cfg.schedule.clone();
|
||||
|
||||
|
||||
let shared_data = web::Data::new(AppState {
|
||||
config: cfg,
|
||||
targets,
|
||||
});
|
||||
|
||||
// Scheduler
|
||||
if let Some(expression) = schedule {
|
||||
let cloned_data = shared_data.clone();
|
||||
actix_rt::spawn(async move {
|
||||
start_scheduler(&expression, cloned_data).await
|
||||
});
|
||||
}
|
||||
|
||||
// Web Server
|
||||
HttpServer::new(move || App::new()
|
||||
.wrap(Logger::default())
|
||||
.wrap(Cors::default()
|
||||
.supports_credentials()
|
||||
.allow_any_origin()
|
||||
.allowed_methods(vec!["GET", "POST", "OPTIONS", "HEAD"])
|
||||
.allow_any_header()
|
||||
.max_age(3600)
|
||||
)
|
||||
.app_data(shared_data.clone())
|
||||
.service(
|
||||
web::scope("/api/v1")
|
||||
.route("/playlist", web::post().to(playlist))
|
||||
.route("/config", web::get().to(config))
|
||||
).service(xtream_player_api)
|
||||
.service(index)
|
||||
.service(actix_files::Files::new("/", web_dir.clone()))
|
||||
)
|
||||
.bind(format!("{}:{}", host, port))?
|
||||
.run().await
|
||||
//
|
||||
// .service(actix_files::Files::new("/static", ".").show_files_listing())
|
||||
}
|
||||
|
||||
async fn start_scheduler(expression: &String, data: Data<AppState>) -> ! {
|
||||
let schedule = Schedule::from_str(expression).unwrap();
|
||||
let offset = *Local::now().offset();
|
||||
loop {
|
||||
let mut upcoming = schedule.upcoming(offset).take(1);
|
||||
actix_rt::time::sleep(Duration::from_millis(500)).await;
|
||||
let local = &Local::now();
|
||||
|
||||
if let Some(datetime) = upcoming.next() {
|
||||
if datetime.timestamp() <= local.timestamp() {
|
||||
playlist_processor::process_sources((&data.config).clone(), &data.targets);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub(crate) mod model_api;
|
||||
pub(crate) mod main_api;
|
||||
pub(crate) mod xtream_player_api;
|
||||
@@ -0,0 +1,60 @@
|
||||
use actix_web::web;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::model::config::{Config, ProcessTargets};
|
||||
|
||||
pub(crate) struct AppState {
|
||||
pub config: Config,
|
||||
pub targets: ProcessTargets,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub(crate) struct PlaylistRequest {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
impl From<web::Json<PlaylistRequest>> for PlaylistRequest {
|
||||
fn from(req: web::Json<PlaylistRequest>) -> Self {
|
||||
PlaylistRequest {
|
||||
url: String::from(&req.url),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub(crate) struct ServerConfig {
|
||||
pub sources: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct XtreamUserInfo {
|
||||
pub active_cons: u16, // 0
|
||||
pub allowed_output_formats: Vec<String>, //["ts"],
|
||||
pub auth: u16, // 0 | 1
|
||||
pub created_at: i64, //1623429679,
|
||||
pub exp_date: i64, //1628755200,
|
||||
pub is_trial: u16, // 0 | 1
|
||||
pub max_connections: u16,
|
||||
pub message: String,
|
||||
pub password: String,
|
||||
pub username: String,
|
||||
pub status: String, // "Active"
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct XtreamServerInfo {
|
||||
pub url: String,
|
||||
pub port:u16,
|
||||
pub https_port:u16,
|
||||
pub server_protocol: String, // http, https
|
||||
pub rtmp_port:u16,
|
||||
pub timezone:String,
|
||||
pub timestamp_now:i64,
|
||||
pub time_now: String, //"2021-06-28 17:07:37"
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct XtreamAuthorizationResponse {
|
||||
pub user_info: XtreamUserInfo,
|
||||
pub server_info: XtreamServerInfo,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
// https://github.com/tellytv/go.xtream-codes/blob/master/structs.go
|
||||
|
||||
use actix_web::{HttpResponse, web, get, HttpRequest};
|
||||
use chrono::{Duration, Local};
|
||||
use crate::api::model_api::{AppState, XtreamAuthorizationResponse, XtreamServerInfo, XtreamUserInfo};
|
||||
use crate::model::config::Config;
|
||||
use crate::model::model_config::{default_as_empty_str};
|
||||
use crate::repository::xtream_repository::{COL_CAT_LIVE, COL_CAT_SERIES, COL_CAT_VOD, COL_LIVE, COL_SERIES, COL_VOD, xtream_get_all};
|
||||
|
||||
fn get_user_info(user_name: &str, cfg: &Config) -> XtreamAuthorizationResponse {
|
||||
let server = cfg._api_proxy.as_ref().unwrap().server.clone();
|
||||
let now = Local::now();
|
||||
XtreamAuthorizationResponse {
|
||||
user_info: XtreamUserInfo {
|
||||
active_cons: 0,
|
||||
allowed_output_formats: Vec::from(["ts".to_string()]),
|
||||
auth: 1,
|
||||
created_at: (now - Duration::days(365)).timestamp(),
|
||||
exp_date: (now + Duration::days(365)).timestamp(),
|
||||
is_trial: 0,
|
||||
max_connections: 1,
|
||||
message: server.message.to_string(),
|
||||
password: "dfdfdf".to_string(),
|
||||
username: user_name.to_string(),
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct XtreamApiRequest {
|
||||
username: String,
|
||||
password: String,
|
||||
#[serde(default = "default_as_empty_str")]
|
||||
action: String,
|
||||
}
|
||||
|
||||
#[get("/player_api.php")]
|
||||
pub(crate) async fn xtream_player_api(
|
||||
api_req: web::Query<XtreamApiRequest>,
|
||||
req: HttpRequest,
|
||||
_app_state: web::Data<AppState>,
|
||||
) -> HttpResponse {
|
||||
match _app_state.config.get_target_for_user(api_req.username.as_str(), api_req.password.as_str()) {
|
||||
Some(target_name) => {
|
||||
let target = target_name.as_str();
|
||||
if api_req.action.is_empty() {
|
||||
return HttpResponse::Ok().json(get_user_info(api_req.username.as_str(), &_app_state.config));
|
||||
}
|
||||
match match api_req.action.as_str() {
|
||||
"get_live_categories" => xtream_get_all(&_app_state.config, target, COL_CAT_LIVE),
|
||||
"get_vod_categories" => xtream_get_all(&_app_state.config, target, COL_CAT_VOD),
|
||||
"get_series_categories" => xtream_get_all(&_app_state.config, target, COL_CAT_SERIES),
|
||||
"get_live_streams" => xtream_get_all(&_app_state.config, target, COL_LIVE),
|
||||
"get_vod_streams" => xtream_get_all(&_app_state.config, target, COL_VOD),
|
||||
"get_series" => xtream_get_all(&_app_state.config, target, COL_SERIES),
|
||||
_ => Err(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Cant find action: {}/{}", target, &api_req.action))),
|
||||
} {
|
||||
Ok(file_path) => {
|
||||
let file = actix_files::NamedFile::open_async(file_path).await.unwrap()
|
||||
.set_content_type(mime::APPLICATION_JSON)
|
||||
.disable_content_disposition();
|
||||
file.into_response(&req)
|
||||
}
|
||||
Err(_) => HttpResponse::BadRequest().finish()
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if api_req.action.is_empty() {
|
||||
HttpResponse::Unauthorized().finish()
|
||||
} else {
|
||||
HttpResponse::BadRequest().finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user