mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-01 13:32:04 +02:00
Merge pull request #361 from euzu/feature/series_info_parsing
Feature/series info parsing
This commit is contained in:
@@ -1,13 +1,12 @@
|
||||
use crate::model::{AppConfig, ProxyUserCredentials};
|
||||
use crate::model::{ConfigTarget, XtreamTargetOutput};
|
||||
use shared::model::{xtream_const, PlaylistItem,XtreamPlaylistItem};
|
||||
use shared::utils::{deserialize_as_option_string, deserialize_as_string, deserialize_as_string_array, deserialize_number_from_string,
|
||||
opt_string_or_number_u32, string_default_on_null, string_or_number_f64, string_or_number_u32, get_non_empty_str};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
use std::collections::HashMap;
|
||||
use std::iter::FromIterator;
|
||||
use shared::model::{xtream_const, PlaylistItem, XtreamPlaylistItem};
|
||||
use shared::model::{ClusterFlags, PlaylistEntry, XtreamCluster};
|
||||
use shared::utils::{deserialize_as_option_string, deserialize_as_string, deserialize_as_string_array, deserialize_number_from_string,
|
||||
get_non_empty_str, opt_string_or_number_u32, string_default_on_null, string_or_number_f64, string_or_number_u32};
|
||||
use std::iter::FromIterator;
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct XtreamCategory {
|
||||
@@ -341,14 +340,60 @@ impl XtreamSeriesEpisode {
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_episodes<'de, D>(deserializer: D) -> Result<Option<Vec<XtreamSeriesInfoEpisode>>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
// read as generic value
|
||||
let value = Value::deserialize(deserializer)?;
|
||||
match value {
|
||||
Value::Null => Ok(None),
|
||||
Value::Array(array) => {
|
||||
if array.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
let mut result = Vec::new();
|
||||
for inner in array {
|
||||
if let Some(inner_array) = inner.as_array() {
|
||||
for item in inner_array {
|
||||
let ep: XtreamSeriesInfoEpisode = serde_json::from_value(item.clone())
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
result.push(ep);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Some(result))
|
||||
}
|
||||
}
|
||||
Value::Object(object) => {
|
||||
if object.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
let mut result = Vec::new();
|
||||
for (_key, val) in object {
|
||||
if let Some(inner_array) = val.as_array() {
|
||||
for item in inner_array {
|
||||
let ep: XtreamSeriesInfoEpisode = serde_json::from_value(item.clone())
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
result.push(ep);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Some(result))
|
||||
}
|
||||
}
|
||||
_ => Err(serde::de::Error::custom("Invalid format for episodes")),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct XtreamSeriesInfo {
|
||||
#[serde(default)]
|
||||
pub seasons: Option<Vec<XtreamSeriesInfoSeason>>,
|
||||
#[serde(default)]
|
||||
pub info: Option<XtreamSeriesInfoInfo>,
|
||||
#[serde(default)]
|
||||
pub episodes: Option<HashMap<String, Vec<XtreamSeriesInfoEpisode>>>,
|
||||
#[serde(default, deserialize_with = "deserialize_episodes")]
|
||||
pub episodes: Option<Vec<XtreamSeriesInfoEpisode>>,
|
||||
}
|
||||
|
||||
impl XtreamSeriesInfoEpisode {
|
||||
@@ -408,7 +453,7 @@ pub fn normalize_release_date(document: &mut serde_json::Map<String, Value>) {
|
||||
let date_value = document.get("release_date")
|
||||
.or_else(|| document.get("releaseDate"))
|
||||
.or_else(|| document.get("releasedate"))
|
||||
.filter(|v| v.as_str().is_some_and(|s| !s.is_empty()))
|
||||
.filter(|v| v.as_str().is_some_and(|s| !s.is_empty()))
|
||||
.cloned();
|
||||
|
||||
// Remove unused keys (optional)
|
||||
|
||||
@@ -554,26 +554,31 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parse_test() -> io::Result<()> {
|
||||
//let file_path = PathBuf::from("/tmp/epg.xml.gz");
|
||||
let file_path = PathBuf::from("/tmp/invalid_epg.xml");
|
||||
let run_test = async move || {
|
||||
//let file_path = PathBuf::from("/tmp/epg.xml.gz");
|
||||
let file_path = PathBuf::from("/tmp/invalid_epg.xml");
|
||||
|
||||
if file_path.exists() {
|
||||
let tv_guide = TVGuide::new(vec![PersistedEpgSource { file_path, priority: 0, logo_override: false }]);
|
||||
if file_path.exists() {
|
||||
let tv_guide = TVGuide::new(vec![PersistedEpgSource { file_path, priority: 0, logo_override: false }]);
|
||||
|
||||
let mut id_cache = EpgIdCache::new(None);
|
||||
id_cache.channel_epg_id.insert(Cow::Owned("342".to_string()));
|
||||
//id_cache.collect_epg_id(fp);
|
||||
let mut id_cache = EpgIdCache::new(None);
|
||||
id_cache.channel_epg_id.insert(Cow::Owned("342".to_string()));
|
||||
//id_cache.collect_epg_id(fp);
|
||||
|
||||
let channel_ids = HashSet::from(["342".to_string()]);
|
||||
match tv_guide.filter(&mut id_cache) {
|
||||
None => assert!(false, "No epg filtered"),
|
||||
Some(epgs) => {
|
||||
for epg in epgs {
|
||||
assert_eq!(epg.children.len(), channel_ids.len() * 2, "Epg size does not match")
|
||||
let channel_ids = HashSet::from(["342".to_string()]);
|
||||
match tv_guide.filter(&mut id_cache).await {
|
||||
None => assert!(false, "No epg filtered"),
|
||||
Some(epgs) => {
|
||||
for epg in epgs {
|
||||
assert_eq!(epg.children.len(), channel_ids.len() * 2, "Epg size does not match")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let _result = tokio::runtime::Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(run_test());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ pub fn parse_xtream_series_info(info: &Value, group_title: &str, series_name: &s
|
||||
match serde_json::from_value::<XtreamSeriesInfo>(info.to_owned()) {
|
||||
Ok(series_info) => {
|
||||
if let Some(episodes) = &series_info.episodes {
|
||||
let result: Vec<(XtreamSeriesInfoEpisode, PlaylistItem)> = episodes.values().flatten().map(|episode| {
|
||||
let result: Vec<(XtreamSeriesInfoEpisode, PlaylistItem)> = episodes.iter().map(|episode| {
|
||||
let episode_url = create_xtream_series_episode_url(url, username, password, episode);
|
||||
let mut new_episode = episode.clone();
|
||||
|
||||
@@ -208,8 +208,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_read_json_file_into_struct() {
|
||||
let file_content = fs::read_to_string("series-info.json").expect("Unable to read file");
|
||||
let _info: XtreamSeriesInfo = serde_json::from_str(&file_content).expect("JSON was not well-formatted");
|
||||
let file_content = fs::read_to_string("/tmp/series-info.json").expect("Unable to read file");
|
||||
match serde_json::from_str::<XtreamSeriesInfo>(&file_content) {
|
||||
Ok(series_info) => {
|
||||
println!("{:#?}", series_info);
|
||||
assert!(true);
|
||||
},
|
||||
Err(err) => {
|
||||
assert!(false, "Failed to parse json file: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -194,8 +194,8 @@ http://hd.providerline.com/get.php?username=user4&password=user4&type=m3u_plus;i
|
||||
|
||||
const XTREAM_BATCH: &str = r"
|
||||
#name;username;password;url;max_connections
|
||||
input_1;desanocra;eyCG8SN523KQ;http://provider_1.tv:80;1
|
||||
input_2;desanocra;eyCG8SN523KQ;http://provider_2.tv:8080;1
|
||||
input_1;de566567;de2345f43g5;http://provider_1.tv:80;1
|
||||
input_2;de566567;de2345f43g5;http://provider_2.tv:8080;1
|
||||
";
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -67,6 +67,7 @@ where
|
||||
let value: Value = serde::Deserialize::deserialize(deserializer)?;
|
||||
|
||||
match value {
|
||||
Value::Null => Ok(0u32),
|
||||
Value::Number(num) => {
|
||||
if let Some(v) = num.as_u64() {
|
||||
u32::try_from(v)
|
||||
@@ -114,6 +115,7 @@ where
|
||||
let value: Value = serde::Deserialize::deserialize(deserializer)?;
|
||||
|
||||
match value {
|
||||
Value::Null => Ok(0f64),
|
||||
Value::Number(num) => num
|
||||
.as_f64()
|
||||
.ok_or_else(|| serde::de::Error::custom("Invalid number")),
|
||||
|
||||
Reference in New Issue
Block a user