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:
euzu
2023-10-09 00:17:54 +02:00
parent 9e2c8cfb83
commit c8f6031e6b
25 changed files with 324 additions and 161 deletions
+24 -6
View File
@@ -487,19 +487,37 @@ mappings:
group: '|FR|TNT'
```
## 3. User
## 3. Api-Proxy Config
If you use the proxy functionality,
you need to create a `user.yml` configuration.
you need to create a `api-proxy.yml` configuration.
You can specify the path for the file with the `-a` cli argument.
```yaml
server:
protocol: http
ip: 192.168.0.3
http_port: 80
https_port: 443
rtmp_port: 0
timezone: Europe/Paris
message: Welcome to m3u-filter
user:
- target: pl1
credentials:
- {username: xtream, password: xtream.secret}
- {username: x3452, password: ztrhgrGZrt83hjerter}
```
## 4. Compilation
## 4. Logging
Following log levels are supported:
- `debug`
- `info` _default_
- `warn`
- `error`
Use the `-l` or `-log-level` cli-argument to specify the log-level.
## 5. Compilation
### Static binary for docker
#### Install prerequisites
@@ -567,7 +585,7 @@ Compile it with:
cargo build --release --target x86_64-pc-windows-gnu
```
## 5. The EXTM3U format is an extension of the M3U format.
## 6. The EXTM3U format is an extension of the M3U format.
m3u has become almost a standard for the formation of playlists of media players and media devices.
A file in the EXTM3U format is a text file with the extension m3u or m3u8.
+12
View File
@@ -0,0 +1,12 @@
server:
protocol: http
ip: 102.168.9.3
http_port: 80
https_port:
rtmp_port:
timezone: Europe/Paris
message: Welcome to m3u-filter
user:
- target: pl1
credentials:
- {username: x3452, password: ztrhgrGZrt83hjerter}
+5 -3
View File
@@ -7,13 +7,14 @@ 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::model_api::{AppState, PlaylistRequest, ServerConfig};
use crate::config::{Config, ConfigInput, InputType, ProcessTargets};
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::xtream_player_api::xtream_player_api;
use crate::api::xtream_player_api::xtream_player_api;
use log::{error};
#[get("/")]
@@ -84,6 +85,7 @@ pub(crate) async fn start_server(cfg: Config, targets: ProcessTargets) -> future
// Web Server
HttpServer::new(move || App::new()
.wrap(Logger::default())
.wrap(Cors::default()
.supports_credentials()
.allow_any_origin()
+3
View File
@@ -0,0 +1,3 @@
pub(crate) mod model_api;
pub(crate) mod main_api;
pub(crate) mod xtream_player_api;
+60
View File
@@ -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,
}
+86
View File
@@ -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()
}
}
}
}
+2 -2
View File
@@ -2,8 +2,8 @@ use std::path::PathBuf;
use std::sync::atomic::AtomicI32;
use log::debug;
use crate::{m3u_parser, utils, xtream_parser};
use crate::config::{Config, ConfigInput};
use crate::model_m3u::{PlaylistGroup, XtreamCluster};
use crate::model::config::{Config, ConfigInput};
use crate::model::model_m3u::{PlaylistGroup, XtreamCluster};
fn prepare_file_path(input: &ConfigInput, working_dir: &String, action: &str) -> Option<PathBuf> {
let persist_file: Option<PathBuf> =
+2 -2
View File
@@ -5,8 +5,8 @@ use log::{debug, error};
use pest::iterators::Pair;
use pest::Parser;
use petgraph::algo::toposort;
use crate::model_m3u::PlaylistItem;
use crate::model_config::ItemField;
use crate::model::model_m3u::PlaylistItem;
use crate::model::model_config::ItemField;
use petgraph::graph::DiGraph;
use crate::exit;
+2 -2
View File
@@ -1,6 +1,6 @@
use std::cell::RefCell;
use crate::config::Config;
use crate::model_m3u::{PlaylistGroup, PlaylistItem, PlaylistItemHeader, XtreamCluster};
use crate::model::config::Config;
use crate::model::model_m3u::{PlaylistGroup, PlaylistItem, PlaylistItemHeader, XtreamCluster};
fn token_value(it: &mut std::str::Chars) -> String {
if let Some(oc) = it.next() {
+24 -26
View File
@@ -6,19 +6,13 @@ use env_logger::{Builder};
use log::{debug, error, info, LevelFilter, warn};
use clap::Parser;
use crate::config::{Config, validate_targets};
use crate::mapping::Mappings;
use crate::user::User;
use crate::model::api_proxy::ApiProxyConfig;
use crate::model::config::{Config, validate_targets};
use crate::model::mapping::Mappings;
mod api;
mod config;
mod model;
mod filter;
mod m3u_parser;
mod m3u_repository;
mod mapping;
mod model_api;
mod model_config;
mod model_m3u;
mod playlist_processor;
mod repository;
mod download;
@@ -26,8 +20,7 @@ mod utils;
mod messaging;
mod xtream_parser;
mod test;
mod xtream_player_api;
mod user;
mod api;
#[derive(Parser)]
#[command(name = "m3u-filter")]
@@ -48,8 +41,8 @@ struct Args {
mapping: Option<String>,
/// The user file
#[arg(short, long)]
user: Option<String>,
#[arg(short, long = "api-proxy")]
api_proxy: Option<String>,
/// Run in server mode
#[arg(short, long, default_value_t = false, default_missing_value = "true")]
@@ -75,10 +68,10 @@ fn main() {
read_mappings(args.mapping, &mut cfg);
if args.server {
read_users(args.user, &mut cfg);
read_api_proxy_config(args.api_proxy, &mut cfg);
debug!("web_root: {}", &cfg.api.web_root);
info!("server running: http://{}:{}", &cfg.api.host, &cfg.api.port);
match api::start_server(cfg, targets) {
match api::main_api::start_server(cfg, targets) {
Ok(_) => {}
Err(e) => {
exit!("cant start server: {}", e);
@@ -111,14 +104,19 @@ fn read_mappings(args_mapping: Option<String>, cfg: &mut Config) {
cfg.set_mappings(mappings);
}
fn read_users(args_user: Option<String>, cfg: &mut Config) {
let user_file: String = args_user.unwrap_or(utils::get_default_user_path());
fn read_api_proxy_config(args_api_proxy_config: Option<String>, cfg: &mut Config) {
let api_proxy_config_file: String = args_api_proxy_config.unwrap_or(utils::get_default_api_proxy_config_path());
let user = read_user(user_file.as_str());
if user.is_none() {
warn!("cant read user file: {}", user_file.as_str());
let api_proxy_config = read_api_proxy(api_proxy_config_file.as_str());
if api_proxy_config.is_none() {
if cfg.has_published_targets() {
exit!("cant read api_proxy_config file: {}", api_proxy_config_file.as_str());
} else {
warn!("cant read api_proxy_config file: {}", api_proxy_config_file.as_str());
}
} else {
cfg.set_api_proxy(api_proxy_config);
}
cfg.set_user(user);
}
@@ -152,17 +150,17 @@ fn read_mapping(mapping_file: &str) -> Option<Mappings> {
}
}
fn read_user(user_file: &str) -> Option<User> {
match utils::open_file(&std::path::PathBuf::from(user_file), false) {
fn read_api_proxy(api_proxy_file: &str) -> Option<ApiProxyConfig> {
match utils::open_file(&std::path::PathBuf::from(api_proxy_file), false) {
Some(file) => {
let mapping: Result<User, _> = serde_yaml::from_reader(file);
let mapping: Result<ApiProxyConfig, _> = serde_yaml::from_reader(file);
match mapping {
Ok(result) => {
result.prepare();
Some(result)
}
Err(err) => {
error!("cant read user file: {}", err);
error!("cant read api-proxy-config file: {}", err);
None
}
}
+31 -4
View File
@@ -1,3 +1,7 @@
use std::collections::{HashSet};
use crate::exit;
use log::{error};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub(crate) struct UserCredentials {
pub username: String,
@@ -11,6 +15,7 @@ pub(crate) struct TargetUser {
}
impl TargetUser {
pub fn get_target_name(&self, username: &str, password: &str) -> Option<String> {
if self.credentials.iter().find(|c| c.username.eq_ignore_ascii_case(username) && c.password.eq(password)).is_some() {
return Some(self.target.clone());
@@ -20,13 +25,35 @@ impl TargetUser {
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub(crate) struct User {
pub(crate) struct ServerInfo {
pub protocol: String,
pub ip: String,
pub http_port: u16,
pub https_port: u16,
pub rtmp_port: u16,
pub timezone: String,
pub message: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub(crate) struct ApiProxyConfig {
pub server: ServerInfo,
pub user: Vec<TargetUser>,
}
impl User {
impl ApiProxyConfig {
pub fn prepare(&self) {
// TODO check if username is unique, a user can only access one target
let mut usernames = HashSet::new();
for target_user in &self.user {
for user in &target_user.credentials {
if usernames.contains(&user.username) {
exit!("Non unique username found {}", &user.username);
} else {
usernames.insert(user.username.to_string());
}
}
}
}
pub fn get_target_name(&self, username: &str, password: &str) -> Option<String> {
@@ -37,4 +64,4 @@ impl User {
}
None
}
}
}
+21 -9
View File
@@ -4,11 +4,11 @@ use enum_iterator::Sequence;
use log::{debug, error};
use crate::filter::{Filter, get_filter, MockValueProcessor, PatternTemplate, prepare_templates, ValueProvider};
use crate::mapping::Mappings;
use crate::mapping::Mapping;
use crate::model_config::{ItemField, ProcessingOrder, SortOrder, TargetType, default_as_zero, default_as_false, default_as_true};
use crate::user::{User};
use crate::model::mapping::Mappings;
use crate::model::mapping::Mapping;
use crate::model::model_config::{ItemField, ProcessingOrder, SortOrder, TargetType, default_as_zero, default_as_false, default_as_true};
use crate::{exit, utils};
use crate::model::api_proxy::ApiProxyConfig;
use crate::utils::get_working_path;
fn default_as_frm() -> ProcessingOrder { ProcessingOrder::FRM }
@@ -260,17 +260,29 @@ pub(crate) struct Config {
pub video_suffix: Option<Vec<String>>,
pub schedule: Option<String>,
#[serde(skip_serializing, skip_deserializing)]
pub _user: Option<User>,
pub _api_proxy: Option<ApiProxyConfig>,
}
impl Config {
pub fn set_user(&mut self, user: Option<User>) {
self._user = user;
pub(crate) fn has_published_targets(&self) -> bool {
for source in &self.sources {
for target in &source.targets {
if target.publish {
return true;
}
}
}
false
}
pub fn set_api_proxy(&mut self, api_proxy: Option<ApiProxyConfig>) {
self._api_proxy = api_proxy;
}
pub fn get_target_for_user(&self, username: &str, password: &str) -> Option<String> {
match &self._user {
Some(user) => user.get_target_name(username, password),
match &self._api_proxy {
Some(api_proxy) => api_proxy.get_target_name(username, password),
_ => None
}
}
+2 -2
View File
@@ -3,8 +3,8 @@ use std::collections::HashMap;
use log::{debug, error};
use regex::Regex;
use crate::filter::{Filter, get_filter, PatternTemplate, prepare_templates, RegexWithCaptures, ValueProcessor};
use crate::model_m3u::{FieldAccessor, PlaylistItem};
use crate::model_config::{ItemField, MAPPER_ATTRIBUTE_FIELDS, AFFIX_FIELDS,
use crate::model::model_m3u::{FieldAccessor, PlaylistItem};
use crate::model::model_config::{ItemField, MAPPER_ATTRIBUTE_FIELDS, AFFIX_FIELDS,
default_as_empty_str, default_as_false, default_as_empty_map, };
use crate::valid_property;
+5
View File
@@ -0,0 +1,5 @@
pub(crate) mod config;
pub(crate) mod model_config;
pub(crate) mod model_m3u;
pub(crate) mod mapping;
pub(crate) mod api_proxy;
+1 -1
View File
@@ -1,7 +1,7 @@
use std::cell::RefCell;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::config::{ConfigOptions};
use crate::model::config::ConfigOptions;
// https://de.wikipedia.org/wiki/M3U
// https://siptv.eu/howto/playlist.html
-26
View File
@@ -1,26 +0,0 @@
use actix_web::web;
use serde::{Deserialize, Serialize};
use crate::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>,
}
+8 -10
View File
@@ -4,18 +4,16 @@ use std::cell::RefCell;
use std::sync::{Arc};
use std::thread;
use log::{debug, error};
use config::ConfigTarget;
use unidecode::unidecode;
use crate::{config, Config, valid_property};
use crate::config::{ConfigInput, InputAffix, InputType, ProcessTargets};
use crate::model_config::SortOrder::{Asc, Desc};
use crate::{model::config, Config, valid_property};
use crate::model::config::{ConfigInput, ConfigTarget, InputAffix, InputType, ProcessTargets};
use crate::model::model_config::{SortOrder::{Asc, Desc}, ItemField, AFFIX_FIELDS, ProcessingOrder};
use crate::filter::{get_field_value, MockValueProcessor, set_field_value, ValueProvider};
use crate::m3u_repository::write_playlist;
use crate::model_m3u::{FieldAccessor, PlaylistGroup, PlaylistItem, PlaylistItemHeader};
use crate::mapping::{Mapping, MappingValueProcessor};
use crate::model_config::{ItemField, AFFIX_FIELDS, ProcessingOrder};
use crate::repository::m3u_repository::write_playlist;
use crate::model::model_m3u::{FieldAccessor, PlaylistGroup, PlaylistItem, PlaylistItemHeader};
use crate::model::mapping::{Mapping, MappingValueProcessor};
use crate::download::{get_m3u_playlist, get_xtream_playlist};
use crate::repository::save_playlist;
use crate::repository::xtream_repository::xtream_save_playlist;
fn filter_playlist(playlist: &mut [PlaylistGroup], target: &ConfigTarget) -> Option<Vec<PlaylistGroup>> {
debug!("Filtering {} groups", playlist.len());
@@ -349,7 +347,7 @@ pub(crate) fn process_playlist(playlist: &mut [PlaylistGroup],
}
if target.publish {
return save_playlist(target, cfg, &mut new_playlist);
return xtream_save_playlist(target, cfg, &mut new_playlist);
}
Err(std::io::Error::new(std::io::ErrorKind::Other, format!("Persisting playlist failed: {}", &target.name)))
}
@@ -1,9 +1,9 @@
use crate::config::{Config, ConfigTarget};
use crate::model_m3u::PlaylistGroup;
use crate::model::config::{Config, ConfigTarget};
use crate::model::model_m3u::PlaylistGroup;
use crate::utils;
use std::io::Write;
use chrono::Datelike;
use crate::model_config::TargetType;
use crate::model::model_config::TargetType;
use log::{error};
macro_rules! open_file {
+2
View File
@@ -0,0 +1,2 @@
pub(crate) mod m3u_repository;
pub(crate) mod xtream_repository;
@@ -5,9 +5,9 @@ use std::iter::FromIterator;
use std::path::PathBuf;
use serde::Serialize;
use serde_json::{json, Value};
use crate::config::{Config, ConfigTarget};
use crate::model::config::{Config, ConfigTarget};
use crate::messaging::send_message;
use crate::model_m3u::{PlaylistGroup, XtreamCluster};
use crate::model::model_m3u::{PlaylistGroup, XtreamCluster};
use crate::utils;
use log::{error};
@@ -65,7 +65,7 @@ fn get_collection_path(path: &PathBuf, collection: &str) -> PathBuf {
path.join(format!("{}.json", collection))
}
pub(crate) fn save_playlist(target: &ConfigTarget, cfg: &Config, playlist: &mut Vec<PlaylistGroup>) -> Result<(), std::io::Error> {
pub(crate) fn xtream_save_playlist(target: &ConfigTarget, cfg: &Config, playlist: &mut Vec<PlaylistGroup>) -> Result<(), std::io::Error> {
let mut failed = false;
if let Some(path) = get_storage_path(&cfg, &target.name) {
if fs::create_dir_all(&path).is_err() {
@@ -81,6 +81,7 @@ pub(crate) fn save_playlist(target: &ConfigTarget, cfg: &Config, playlist: &mut
let mut series_col = vec![];
let mut vod_col = vec![];
let mut channel_num: i32 = 0;
for plg in playlist {
match &plg.xtream_cluster {
XtreamCluster::LIVE => &mut cat_live_col,
@@ -95,20 +96,30 @@ pub(crate) fn save_playlist(target: &ConfigTarget, cfg: &Config, playlist: &mut
);
for pli in &plg.channels {
channel_num += 1;
let header = &pli.header.borrow();
let mut document = serde_json::Map::from_iter([
("category_id".to_string(), Value::String(format!("{}", &plg.id))),
("category_ids".to_string(), Value::Array(Vec::from([Value::Number(serde_json::Number::from((&plg.id).to_owned()))]))),
//("custom_sid", ""),
("direct_source".to_string(), Value::String(header.source.clone())),
("name".to_string(), Value::String(header.name.clone())),
("num".to_string(), Value::Number(serde_json::Number::from(channel_num.clone()))),
("title".to_string(), Value::String(header.title.clone())),
//"num": channel_num),
("stream_icon".to_string(), Value::String(header.logo.clone())),
("stream_id".to_string(), Value::String(header.id.clone())),
("thumbnail".to_string(), Value::String(header.logo_small.clone())),
]);
match header.xtream_cluster {
XtreamCluster::SERIES =>
document.insert("series_id".to_string(), Value::String(header.id.clone())),
_ => document.insert("stream_id".to_string(), Value::Number(serde_json::Number::from(header.id.parse::<i32>().unwrap()))),
};
if let XtreamCluster::LIVE = header.xtream_cluster {
document.insert("custom_sid".to_string(), Value::String("".to_string())); // is this necessary ?
};
if let Some(add_props) = &header.additional_properties {
for (field_name, field_value) in add_props {
document.insert(field_name.to_string(), field_value.to_owned());
@@ -148,7 +159,7 @@ pub(crate) fn save_playlist(target: &ConfigTarget, cfg: &Config, playlist: &mut
Ok(())
}
pub(crate) fn get_all(cfg: &Config, target_name: &str, collection_name: &str) -> Result<PathBuf, Error> {
pub(crate) fn xtream_get_all(cfg: &Config, target_name: &str, collection_name: &str) -> Result<PathBuf, Error> {
if let Some(path) = get_storage_path(&cfg, target_name) {
let col_path = get_collection_path(&path, collection_name);
if col_path.exists() {
+3 -3
View File
@@ -5,7 +5,7 @@ use log::{debug, error};
use path_absolutize::*;
use reqwest::header;
use reqwest::header::{HeaderName, HeaderValue};
use crate::config::ConfigInput;
use crate::model::config::ConfigInput;
use crate::messaging::send_message;
#[macro_export]
@@ -51,8 +51,8 @@ pub(crate) fn get_default_mappings_path() -> String {
get_default_file_path("mapping.yml".to_string())
}
pub(crate) fn get_default_user_path() -> String {
get_default_file_path("user.yml".to_string())
pub(crate) fn get_default_api_proxy_config_path() -> String {
get_default_file_path("api-proxy.yml".to_string())
}
pub(crate) fn get_working_path(wd: &String) -> String {
+10 -10
View File
@@ -1,11 +1,11 @@
use std::cell::{RefCell};
use std::collections::HashMap;
use std::sync::atomic::{AtomicI32, Ordering};
use serde::{Deserialize, Deserializer};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde::de::DeserializeOwned;
use serde_json::Value;
use crate::model_m3u::{PlaylistGroup, PlaylistItem, PlaylistItemHeader, XtreamCluster};
use crate::model_config::{default_as_empty_str};
use crate::model::model_m3u::{PlaylistGroup, PlaylistItem, PlaylistItemHeader, XtreamCluster};
use crate::model::model_config::{default_as_empty_str};
use log::{error};
fn default_as_empty_list() -> Vec<PlaylistItem> { vec![] }
@@ -44,11 +44,11 @@ fn deserialize_number_from_string<'de, D, T: DeserializeOwned>(
}
}
fn value_to_string_array(value : &Vec<Value>) -> Option<Vec<String>> {
fn value_to_string_array(value: &Vec<Value>) -> Option<Vec<String>> {
Some(value.iter().map(|i| value_to_string(i)).filter(|i| i.is_some()).map(|i| i.unwrap()).collect())
}
fn value_to_string(v : &Value) -> Option<String> {
fn value_to_string(v: &Value) -> Option<String> {
match v {
Value::Bool(value) => Some(value.to_string()),
Value::Number(value) => Some(value.to_string()),
@@ -65,7 +65,7 @@ fn deserialize_as_option_string<'de, D>(deserializer: D) -> Result<Option<String
match &value {
Value::String(s) => Ok(Some(s.clone())),
_ => Ok(Some(value.to_string())),
_ => Ok(None),
}
}
@@ -108,7 +108,7 @@ impl XtreamCategory {
}
}
#[derive(Deserialize)]
#[derive(Serialize, Deserialize)]
struct XtreamStream {
#[serde(default, deserialize_with = "deserialize_as_string")]
pub name: String,
@@ -141,9 +141,9 @@ struct XtreamStream {
#[serde(default, deserialize_with = "deserialize_as_option_string")]
plot: Option<String>,
#[serde(default, deserialize_with = "deserialize_number_from_string")]
rating: Option<f32>,
rating: Option<f64>,
#[serde(default, deserialize_with = "deserialize_number_from_string")]
rating_5based: Option<f32>,
rating_5based: Option<f64>,
#[serde(default, deserialize_with = "deserialize_as_option_string")]
release_date: Option<String>,
#[serde(default, deserialize_with = "deserialize_as_option_string")]
@@ -169,7 +169,7 @@ macro_rules! add_str_property_if_exists {
}
macro_rules! add_i64_property_if_exists {
($vec:expr, $prop:expr, $prop_name:expr) => {
$prop.as_ref().map(|v| $vec.push((String::from($prop_name), Value::Number(serde_json::value::Number::from_f64(f64::from(*v)).unwrap()))));
$prop.as_ref().map(|v| $vec.push((String::from($prop_name), Value::Number(serde_json::value::Number::from(i64::from(*v))))));
}
}
-41
View File
@@ -1,41 +0,0 @@
use actix_web::{HttpResponse, web, get, HttpRequest};
use crate::model_api::AppState;
use crate::repository::{COL_CAT_LIVE, COL_CAT_SERIES, COL_CAT_VOD, COL_LIVE, COL_SERIES, COL_VOD, get_all};
#[derive(serde::Serialize, serde::Deserialize)]
pub struct XtreamApiRequest {
username: String,
password: String,
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();
match match api_req.action.as_str() {
"get_live_categories" => get_all(&_app_state.config, target, COL_CAT_LIVE),
"get_vod_categories" => get_all(&_app_state.config, target, COL_CAT_VOD),
"get_series_categories" => get_all(&_app_state.config, target, COL_CAT_SERIES),
"get_live_streams" => get_all(&_app_state.config, target, COL_LIVE),
"get_vod_streams" => get_all(&_app_state.config, target, COL_VOD),
"get_series" => get_all(&_app_state.config, target, COL_SERIES),
_ => Err(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Cant find collection: {}/{}", 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()
}
}
_ => HttpResponse::BadRequest().finish()
}
}
-4
View File
@@ -1,4 +0,0 @@
user:
- target: pl1
credentials:
- {username: x3452, password: ztrhgrGZrt83hjerter}