diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index cc284ee..0e047c5 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -17,18 +17,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy 0.7.35", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -1175,6 +1163,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1682,28 +1676,22 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] [[package]] name = "hashlink" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.15.2", ] [[package]] @@ -2367,9 +2355,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.30.1" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "947e6816f7825b2b45027c2c32e7085da9934defa535de4a6a46b10a4d5257fa" dependencies = [ "cc", "pkg-config", @@ -3433,7 +3421,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.8.24", + "zerocopy", ] [[package]] @@ -3564,9 +3552,9 @@ dependencies = [ [[package]] name = "r2d2_sqlite" -version = "0.25.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb14dba8247a6a15b7fdbc7d389e2e6f03ee9f184f87117706d509c092dfe846" +checksum = "8998443b32daee2ad6f528afb19ad77c4a8acc4d8d55b3e5072ed42862fe261a" dependencies = [ "r2d2", "rusqlite", @@ -3606,7 +3594,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy 0.8.24", + "zerocopy", ] [[package]] @@ -3837,9 +3825,9 @@ dependencies = [ [[package]] name = "rusqlite" -version = "0.32.1" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" +checksum = "a22715a5d6deef63c637207afbe68d0c72c3f8d0022d7cf9714c442d6157606b" dependencies = [ "bitflags 2.9.0", "fallible-iterator", @@ -3851,9 +3839,9 @@ dependencies = [ [[package]] name = "rusqlite_migration" -version = "1.3.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "923b42e802f7dc20a0a6b5e097ba7c83fe4289da07e49156fecf6af08aa9cd1c" +checksum = "d6bec955369ac93728d3864d56b5d663ec7495f83648c560750a56c50fefcbf8" dependencies = [ "log", "rusqlite", @@ -6578,33 +6566,13 @@ dependencies = [ "zvariant", ] -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "zerocopy-derive 0.7.35", -] - [[package]] name = "zerocopy" version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" dependencies = [ - "zerocopy-derive 0.8.24", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "zerocopy-derive", ] [[package]] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index a921796..5c276e0 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -19,7 +19,7 @@ tauri-build = { version = "2.1.0", features = [] } [dependencies] serde_json = "1.0.140" -rusqlite = { version = "0.32.1", features = ["bundled"] } +rusqlite = { version = "0.35.0", features = ["bundled"] } serde = { version = "1.0.219", features = ["derive"] } tauri = { version = "2.4.0", features = ["tray-icon"] } anyhow = { version = "1.0.97" } @@ -28,7 +28,7 @@ directories = "6.0.0" reqwest = { version = "0.12.15", features = ["json"] } tokio = { version = "1.44.1", features = ["full"] } url = "2.5.4" -r2d2_sqlite = "0.25.0" +r2d2_sqlite = "0.28.0" r2d2 = "0.8.10" bytes = "1.10.1" tauri-plugin-dialog = "2.2.0" @@ -38,7 +38,7 @@ tauri-plugin-clipboard-manager = "2.2.2" tauri-plugin-shell = "2.2.0" simplelog = "0.12.2" log = "0.4.27" -rusqlite_migration = "1.3.1" +rusqlite_migration = "2.1.0" base64 = "0.22.1" tauri-plugin-notification = "2.2.2" warp = "0.3.7" diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 0227507..4c7008d 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -29,6 +29,7 @@ pub mod sql; pub mod types; pub mod utils; pub mod view_type; +pub mod w3u; pub mod xtream; static ENABLE_TRAY_ICON: LazyLock = LazyLock::new(|| { @@ -103,7 +104,9 @@ pub fn run() { restore_favs, abort_download, clear_history, - is_container + is_container, + get_w3u, + get_w3u_from_link ]) .setup(|app| { app.manage(Mutex::new(AppState { @@ -184,9 +187,11 @@ fn map_err_frontend(e: Error) -> String { return format!("{:?}", e); } -#[tauri::command(async)] -fn get_m3u8(source: Source) -> Result<(), String> { - m3u::read_m3u8(source, false).map_err(map_err_frontend) +#[tauri::command] +async fn get_m3u8(source: Source) -> Result<(), String> { + m3u::read_m3u8(source, false) + .await + .map_err(map_err_frontend) } #[tauri::command] @@ -526,3 +531,15 @@ fn clear_history() -> Result<(), String> { fn is_container() -> bool { utils::is_container() } + +#[tauri::command] +async fn get_w3u(source: Source) -> Result<(), String> { + w3u::read_w3u(source, false).await.map_err(map_err_frontend) +} + +#[tauri::command] +async fn get_w3u_from_link(source: Source) -> Result<(), String> { + w3u::get_w3u_from_link(source, false) + .await + .map_err(map_err_frontend) +} diff --git a/src-tauri/src/m3u.rs b/src-tauri/src/m3u.rs index e3d34f4..781ef76 100644 --- a/src-tauri/src/m3u.rs +++ b/src-tauri/src/m3u.rs @@ -1,4 +1,3 @@ -use std::io::Write; use std::sync::LazyLock; use std::{ collections::HashMap, @@ -12,8 +11,9 @@ use rusqlite::Transaction; use types::{Channel, Source}; use crate::types::ChannelPreserve; +use crate::utils::{get_file_http, get_media_type, get_tmp_path}; use crate::{ - log, media_type, source_type, + log, source_type, sql::{self, set_channel_group_id}, types::{self, ChannelHttpHeaders}, }; @@ -36,6 +36,8 @@ static HTTP_REFERRER_REGEX: LazyLock = static HTTP_USER_AGENT_REGEX: LazyLock = LazyLock::new(|| Regex::new(r#"http-user-agent=(?P.+)"#).unwrap()); +const M3U_TEMP_FILENAME: &str = "get.m3u"; + struct M3UProcessing { channel_line: Option, channel_headers: Option, @@ -47,9 +49,9 @@ struct M3UProcessing { line_count: usize, } -pub fn read_m3u8(mut source: Source, wipe: bool) -> Result<()> { +pub async fn read_m3u8(mut source: Source, wipe: bool) -> Result<()> { let path = match source.source_type { - source_type::M3U_LINK => get_tmp_path(), + source_type::M3U_LINK => get_tmp_path(M3U_TEMP_FILENAME).await?, _ => source.url.clone().context("no file path found")?, }; let file = File::open(path).context("Failed to open m3u8 file")?; @@ -169,27 +171,12 @@ fn commit_channel( } pub async fn get_m3u8_from_link(source: Source, wipe: bool) -> Result<()> { - let client = reqwest::Client::new(); - let url = source.url.clone().context("Invalid source")?; - let mut response = client.get(&url).send().await?; - - let mut file = std::fs::File::create(get_tmp_path())?; - while let Some(chunk) = response.chunk().await? { - file.write(&chunk)?; - } - read_m3u8(source, wipe) -} - -fn get_tmp_path() -> String { - let mut path = directories::ProjectDirs::from("dev", "fredol", "open-tv") - .unwrap() - .cache_dir() - .to_owned(); - if !path.exists() { - std::fs::create_dir_all(&path).unwrap(); - } - path.push("get.m3u"); - return path.to_string_lossy().to_string(); + get_file_http( + source.url.as_ref().context("no url")?, + get_tmp_path(M3U_TEMP_FILENAME).await?, + ) + .await?; + read_m3u8(source, wipe).await } fn extract_non_empty_capture(caps: Captures) -> Option { @@ -264,7 +251,7 @@ fn get_channel_from_lines( group: group.map(|x| x.trim().to_string()), image: image.map(|x| x.trim().to_string()), url: Some(second.clone()), - media_type: get_media_type(second), + media_type: get_media_type(&second), source_id: Some(source_id), series_id: None, group_id: None, @@ -274,81 +261,3 @@ fn get_channel_from_lines( }; Ok(channel) } - -fn get_media_type(url: String) -> u8 { - let media_type = if url.ends_with(".mp4") || url.ends_with(".mkv") { - media_type::MOVIE - } else { - media_type::LIVESTREAM - }; - return media_type; -} - -#[cfg(test)] -mod test_m3u { - use std::{env, time::Instant}; - - use crate::{ - m3u::{get_channel_from_lines, get_m3u8_from_link}, - types::Source, - }; - - use super::read_m3u8; - - #[test] - fn test_get_channel_from_lines() { - get_channel_from_lines(r#"#EXTINF:-1 tvg-id="Amazing Channel" tvg-name="Amazing Channel" tvg-logo="http://myurl.local/logos/amazing/amazing-1.png" group-title="The Best Channels"#.to_string() - , r#"http://myurl.local/1234/1234/1234"#.to_string(), 0,Some(true)).unwrap(); - get_channel_from_lines(r#"#EXTINF:-1 tvg-id="Amazing Channel" tvg-name="" tvg-logo="http://myurl.local/logos/amazing/amazing-1.png" group-title="The Best Channels"#.to_string() - , r#"http://myurl.local/1234/1234/1234"#.to_string(), 0, Some(true)).unwrap(); - assert!(get_channel_from_lines(r#"#EXTINF:-1 tvg-id="" tvg-name="" tvg-logo="http://myurl.local/logos/amazing/amazing-1.png" group-title="The Best Channels"#.to_string() - , r#"http://myurl.local/1234/1234/1234"#.to_string(), 0, Some(true)).is_err()); - assert!(get_channel_from_lines(r#"#EXTINF:-1 tvg-id=" " tvg-name="" tvg-logo="http://myurl.local/logos/amazing/amazing-1.png" group-title="The Best Channels"#.to_string() - , r#"http://myurl.local/1234/1234/1234"#.to_string(), 0, Some(true)).is_err()); - assert!(get_channel_from_lines(r#"#EXTINF:-1 tvg-id="Id Of Channel" tvg-name="Name Of Channel" tvg-logo="http://myurl.local/amazing/stuff.png" group-title="|EU| FRANCE HEVC",Alt Name Of Channel"#.to_string(), "http://myurl.local/1111/1111.ts".to_string(), 0, Some(true)).unwrap().name == "Name Of Channel"); - assert!(get_channel_from_lines(r#"#EXTINF:-1 tvg-id="Id Of Channel" tvg-name="" tvg-logo="http://myurl.local/amazing/stuff.png" group-title="|EU| FRANCE HEVC",Alt Name Of Channel"#.to_string(), "http://myurl.local/1111/1111.ts".to_string(), 0, Some(true)).unwrap().name == "Id Of Channel"); - assert!(get_channel_from_lines(r#"#EXTINF:-1 tvg-id="Id Of Channel" tvg-name="" tvg-logo="http://myurl.local/amazing/stuff.png" group-title="|EU| FRANCE HEVC",Alt Name Of Channel"#.to_string(), "http://myurl.local/1111/1111.ts".to_string(), 0, Some(false)).unwrap().name == "Alt Name Of Channel"); - } - - #[test] - fn test_read_m3u8() { - crate::sql::drop_db().unwrap_or_default(); - crate::sql::create_or_initialize_db().unwrap(); - let now = Instant::now(); - let source = Source { - url: Some("/home/fred/Downloads/get.php".to_string()), - name: "main".to_string(), - id: None, - password: None, - username: None, - url_origin: None, - source_type: crate::source_type::M3U, - enabled: true, - use_tvg_id: Some(true), - }; - read_m3u8(source, false).unwrap(); - std::fs::write("bench.txt", now.elapsed().as_millis().to_string()).unwrap(); - } - - #[tokio::test] - async fn test_get_m3u8_from_link() { - crate::sql::drop_db().unwrap_or_default(); - crate::sql::create_or_initialize_db().unwrap(); - let now = Instant::now(); - let source = Source { - url: Some(env::var("OPEN_TV_TEST_LINK").unwrap()), - name: "m3ulink1".to_string(), - id: None, - password: None, - username: None, - url_origin: None, - source_type: crate::source_type::M3U_LINK, - enabled: true, - use_tvg_id: Some(true), - }; - get_m3u8_from_link(source, false).await.unwrap(); - let time = now.elapsed().as_millis().to_string(); - println!("{time}"); - std::fs::write("bench2.txt", time).unwrap(); - } -} diff --git a/src-tauri/src/source_type.rs b/src-tauri/src/source_type.rs index 69db9f1..27d4825 100644 --- a/src-tauri/src/source_type.rs +++ b/src-tauri/src/source_type.rs @@ -2,3 +2,6 @@ pub const M3U: u8 = 0; pub const M3U_LINK: u8 = 1; pub const XTREAM: u8 = 2; pub const CUSTOM: u8 = 3; +pub const CUSTOM_IMPORT: u8 = 4; +pub const W3U: u8 = 5; +pub const W3U_LINK: u8 = 6; diff --git a/src-tauri/src/sql.rs b/src-tauri/src/sql.rs index 3f6a91e..421c5bc 100644 --- a/src-tauri/src/sql.rs +++ b/src-tauri/src/sql.rs @@ -249,8 +249,14 @@ DO UPDATE SET pub fn insert_channel_headers(tx: &Transaction, headers: ChannelHttpHeaders) -> Result<()> { tx.execute( r#" -INSERT OR IGNORE INTO channel_http_headers (channel_id, referrer, user_agent, http_origin, ignore_ssl) -VALUES (?, ?, ?, ?, ?); +INSERT INTO channel_http_headers (channel_id, referrer, user_agent, http_origin, ignore_ssl) +VALUES (?, ?, ?, ?, ?) +ON CONFLICT (channel_id) +DO UPDATE SET + referrer = excluded.referrer, + user_agent = excluded.user_agent, + http_origin = excluded.http_origin, + ignore_ssl = excluded.ignore_ssl; "#, params![ headers.channel_id, @@ -263,7 +269,7 @@ VALUES (?, ?, ?, ?, ?); Ok(()) } -fn get_or_insert_group( +pub fn get_or_insert_group( tx: &Transaction, group: &str, image: &Option, diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index 8e8232e..f553706 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -1,10 +1,10 @@ use crate::{ log::log, - m3u, + m3u, media_type, settings::{get_default_record_path, get_settings}, source_type, sql, types::Source, - xtream, + w3u, xtream, }; use anyhow::{Context, Result, anyhow, bail}; use chrono::{DateTime, Local, Utc}; @@ -34,10 +34,12 @@ static ILLEGAL_CHARS_REGEX: LazyLock = pub async fn refresh_source(source: Source) -> Result<()> { match source.source_type { - source_type::M3U => m3u::read_m3u8(source, true)?, + source_type::M3U => m3u::read_m3u8(source, true).await?, source_type::M3U_LINK => m3u::get_m3u8_from_link(source, true).await?, source_type::XTREAM => xtream::get_xtream(source, true).await?, source_type::CUSTOM => {} + source_type::W3U => w3u::read_w3u(source, true).await?, + source_type::W3U_LINK => w3u::get_w3u_from_link(source, true).await?, _ => return Err(anyhow!("invalid source_type")), } Ok(()) @@ -192,6 +194,37 @@ pub fn is_container() -> bool { std::env::var("container").is_ok() } +pub async fn get_tmp_path(file: &str) -> Result { + let mut path = directories::ProjectDirs::from("dev", "fredol", "open-tv") + .context("no project dir found")? + .cache_dir() + .to_owned(); + if !path.exists() { + tokio::fs::create_dir_all(&path).await?; + } + path.push(file); + Ok(path.to_string_lossy().to_string()) +} + +pub async fn get_file_http(url: &str, path: String) -> Result<()> { + let client = reqwest::Client::new(); + let mut response = client.get(url).send().await?; + let mut file = tokio::fs::File::create(path).await?; + while let Some(chunk) = response.chunk().await? { + file.write(&chunk).await?; + } + Ok(()) +} + +pub fn get_media_type(url: &str) -> u8 { + let media_type = if url.ends_with(".mp4") || url.ends_with(".mkv") { + media_type::MOVIE + } else { + media_type::LIVESTREAM + }; + return media_type; +} + #[cfg(test)] mod test_utils { use super::sanitize; diff --git a/src-tauri/src/w3u.rs b/src-tauri/src/w3u.rs new file mode 100644 index 0000000..b1eb473 --- /dev/null +++ b/src-tauri/src/w3u.rs @@ -0,0 +1,134 @@ +use anyhow::{Context, Result}; +use anyhow::{anyhow, bail}; +use rusqlite::Transaction; +use serde::{Deserialize, Serialize}; +use tokio::fs; + +use crate::sql; +use crate::types::{Channel, ChannelHttpHeaders, ChannelPreserve}; +use crate::utils::get_media_type; +use crate::{ + source_type, + types::Source, + utils::{self, get_tmp_path}, +}; + +const W3U_TEMP_FILENAME: &str = "get.w3u"; + +#[derive(Serialize, Deserialize, Clone, Debug)] +struct W3uData { + #[serde(default)] + groups: serde_json::Value, //Vec + #[serde(default)] + stations: serde_json::Value, //Vec +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +struct W3UGroup { + name: String, + image: Option, + stations: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +struct W3UStream { + name: String, + image: Option, + url: String, + referer: Option, + user_agent: Option, +} + +pub async fn get_w3u_from_link(source: Source, wipe: bool) -> Result<()> { + utils::get_file_http( + source.url.as_ref().context("no url")?, + get_tmp_path(W3U_TEMP_FILENAME).await?, + ) + .await?; + read_w3u(source, wipe).await +} + +pub async fn read_w3u(source: Source, wipe: bool) -> Result<()> { + let path = match source.source_type { + source_type::W3U => source.url.clone().context("no url")?, + source_type::W3U_LINK => get_tmp_path(W3U_TEMP_FILENAME).await?, + _ => return Err(anyhow!("invalid source_type")), + }; + let data = fs::read(path).await?; + let data: W3uData = serde_json::from_slice(&data)?; + let mut channel_preserve: Vec = Vec::new(); + sql::do_tx(|tx| { + let mut source_id = source.id.unwrap_or(-1); + if wipe { + if source_id == -1 { + bail!("invalid source id"); + } + channel_preserve = sql::get_channel_preserve(&tx, source_id).unwrap_or(Vec::new()); + sql::wipe(&tx, source_id)?; + } else { + source_id = sql::create_or_find_source_by_name(&tx, &source)?; + } + let groups: Option> = serde_json::from_value(data.groups).ok(); + let stations: Option> = serde_json::from_value(data.stations).ok(); + if groups.is_none() && stations.is_none() { + bail!("No data in w3u"); + } + if let Some(g) = groups { + for group in g { + process_group(group, source_id, tx)?; + } + } + if let Some(s) = stations { + for station in s { + process_station(station, source_id, None, tx)?; + } + } + if wipe { + sql::restore_preserve(&tx, source_id, channel_preserve)?; + } + Ok(()) + })?; + Ok(()) +} + +fn process_group(group: W3UGroup, source_id: i64, tx: &Transaction) -> Result<()> { + let group_id = sql::get_or_insert_group(tx, &group.name, &group.image, &source_id)?; + for station in group.stations { + process_station(station, source_id, Some(group_id), tx)?; + } + Ok(()) +} + +fn process_station( + station: W3UStream, + source_id: i64, + group_id: Option, + tx: &Transaction, +) -> Result<()> { + let channel = Channel { + name: station.name, + group_id, + image: station.image, + media_type: get_media_type(&station.url), + favorite: false, + group: None, + series_id: None, + stream_id: None, + source_id: Some(source_id), + tv_archive: None, + id: None, + url: Some(station.url), + }; + sql::insert_channel(tx, channel)?; + println!("{}", tx.last_insert_rowid()); + if station.referer.is_some() || station.user_agent.is_some() { + let headers = ChannelHttpHeaders { + channel_id: Some(tx.last_insert_rowid()), + referrer: station.referer, + user_agent: station.user_agent, + ..Default::default() + }; + sql::insert_channel_headers(tx, headers)? + } + Ok(()) +} diff --git a/src/app/models/sourceType.ts b/src/app/models/sourceType.ts index d387d21..d6a9138 100644 --- a/src/app/models/sourceType.ts +++ b/src/app/models/sourceType.ts @@ -1,7 +1,9 @@ export enum SourceType { - M3U = 0, - M3ULink = 1, - Xtream = 2, - Custom = 3, - CustomImport = 4 -} \ No newline at end of file + M3U = 0, + M3ULink = 1, + Xtream = 2, + Custom = 3, + CustomImport = 4, + W3U = 5, + W3ULink = 6, +} diff --git a/src/app/setup/setup.component.html b/src/app/setup/setup.component.html index 25e4f51..1d3eff1 100644 --- a/src/app/setup/setup.component.html +++ b/src/app/setup/setup.component.html @@ -1,117 +1,230 @@ - - + +
- -

Let's add your first source

-

Let's add another source

-
- - - - - + +

Let's add your first source

+

Let's add another source

+
+ + + + + + + +
+
+
+
+ +
+
+
+
+ This source name is already taken +
+
+
+
+
+ +
+
+
+
+
+
+ + +
- -
-
- -
-
-
-
- This source name is already taken -
-
-
-
-
- -
-
-
-
-
-
- - -
-
-
-
-
-
- -
-
-
-
- -
-
-
-
- -
- - - +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+ +
+
\ No newline at end of file + + + + diff --git a/src/app/setup/setup.component.ts b/src/app/setup/setup.component.ts index 4ae9e54..aae5573 100644 --- a/src/app/setup/setup.component.ts +++ b/src/app/setup/setup.component.ts @@ -68,6 +68,12 @@ export class SetupComponent { const file = await open({ multiple: false, directory: false, + filters: [ + { + name: "extensions", + extensions: ["m3u", "m3u8"], + }, + ], }); if (file == null) { return; @@ -111,7 +117,52 @@ export class SetupComponent { case SourceType.CustomImport: await this.customImport(); break; + case SourceType.W3U: + await this.getW3U(); + break; + case SourceType.W3ULink: + await this.getW3ULink(); + break; + } + } + + async getW3U() { + this.removeUnusedFieldsFromSource(); + const file = await open({ + multiple: false, + directory: false, + filters: [ + { + name: "extensions", + extensions: ["w3u"], + }, + ], + }); + if (file == null) { + return; } + this.loading = true; + this.source.url = file; + try { + await invoke("get_w3u", { source: this.source }); + this.success(); + } catch (e) { + this.error.handleError(e, "Could not parse selected file"); + } + this.loading = false; + } + + async getW3ULink() { + this.removeUnusedFieldsFromSource(); + this.source.url = this.source.url?.trim(); + this.loading = true; + try { + await invoke("get_w3u_from_link", { source: this.source }); + this.success(); + } catch (e) { + this.error.handleError(e, "Invalid URL or credentials. Please try again"); + } + this.loading = false; } async customImport() {