diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 6d55e63..be184bc 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -354,29 +354,135 @@ pub async fn run_health_check( } async fn check_account_live(hoster: &str, a: &Account) -> AppResult { + // Byse's account-info endpoint returns HTTP 200 with {"status":403} on bad + // keys, so we parse the JSON properly instead of just checking the status. + let timeout = std::time::Duration::from_secs(20); match hoster { "clouddrop.cc" => { if a.api_key.is_empty() { return Err(AppError::BadCredentials); } - let c = reqwest::Client::builder().timeout(std::time::Duration::from_secs(15)).build()?; - let r = c.get("https://clouddrop.cc/api/cloud/files/?limit=1").bearer_auth(&a.api_key).send().await?; - if r.status().is_success() { Ok("API Key gültig".into()) } else { Err(AppError::HosterError("Clouddrop".into(), format!("HTTP {}", r.status().as_u16()))) } + let c = reqwest::Client::builder().timeout(timeout).build()?; + let r = c.get("https://clouddrop.cc/api/cloud/files/?limit=1") + .bearer_auth(&a.api_key).send().await?; + match r.status().as_u16() { + 200..=299 => Ok("API Key gültig".into()), + 401 | 403 => Err(AppError::BadCredentials), + code => Err(AppError::HosterError("Clouddrop".into(), format!("HTTP {code}"))), + } } "byse.sx" => { if a.api_key.is_empty() { return Err(AppError::BadCredentials); } - let c = reqwest::Client::builder().timeout(std::time::Duration::from_secs(15)).build()?; + let c = reqwest::Client::builder().timeout(timeout).build()?; let r = c.get(format!("https://api.byse.sx/api/account/info?key={}", urlencoding::encode(&a.api_key))).send().await?; - if r.status().is_success() { Ok("API Key gültig".into()) } else { Err(AppError::HosterError("Byse".into(), format!("HTTP {}", r.status().as_u16()))) } + let text = r.text().await.unwrap_or_default(); + let v: serde_json::Value = serde_json::from_str(&text).unwrap_or(serde_json::Value::Null); + let status = v.get("status").and_then(|x| x.as_u64()).unwrap_or(200); + if status == 200 { Ok("API Key gültig".into()) } + else if status == 401 || status == 403 { Err(AppError::BadCredentials) } + else { Err(AppError::HosterError("Byse".into(), + v.get("msg").and_then(|x| x.as_str()).unwrap_or("Fehler").to_string())) } + } + "vidmoly.me" => { + if a.username.is_empty() || a.password.is_empty() { return Err(AppError::BadCredentials); } + let c = reqwest::Client::builder() + .timeout(timeout).cookie_store(true).build()?; + let _ = c.get("https://vidmoly.me").send().await; + let res = c.post("https://vidmoly.me/api/auth/login") + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .header("Origin", "https://vidmoly.me") + .header("Referer", "https://vidmoly.me/login") + .json(&serde_json::json!({ "login": a.username, "password": a.password })) + .send().await?; + let code = res.status().as_u16(); + let body = res.text().await.unwrap_or_default(); + if code == 401 || code == 403 || body.to_lowercase().contains("incorrect") + || body.to_lowercase().contains("invalid") { + return Err(AppError::BadCredentials); + } + if !(200..300).contains(&code) { + return Err(AppError::HosterError("Vidmoly".into(), format!("HTTP {code}"))); + } + // Verify session works against the upload-config endpoint. + let probe = c.get("https://vidmoly.me/api/upload/config") + .header("Accept", "application/json").send().await?; + if probe.status().is_success() { Ok("Login gültig".into()) } + else { Err(AppError::BadCredentials) } + } + "doodstream.com" => { + if a.username.is_empty() || a.password.is_empty() { return Err(AppError::BadCredentials); } + let c = reqwest::Client::builder() + .timeout(timeout).cookie_store(true).build()?; + let _ = c.get("https://doodstream.com").send().await; + let body = serde_urlencoded::to_string([ + ("op", "login_ajax"), + ("login", a.username.as_str()), + ("password", a.password.as_str()), + ("loginotp", ""), + ]).unwrap_or_default(); + let res = c.post("https://doodstream.com/") + .header("Content-Type", "application/x-www-form-urlencoded") + .header("X-Requested-With", "XMLHttpRequest") + .header("Referer", "https://doodstream.com/") + .body(body).send().await?; + let text = res.text().await.unwrap_or_default(); + if text.contains("Dashboard") { return Ok("Login gültig".into()); } + if let Ok(v) = serde_json::from_str::(&text) { + if let Some(s) = v.get("status").and_then(|x| x.as_str()) { + if s == "success" { return Ok("Login gültig".into()); } + let msg = v.get("message").and_then(|x| x.as_str()).unwrap_or("Login fehlgeschlagen"); + if msg.to_lowercase().contains("otp") { return Ok("Login gültig (OTP erforderlich)".into()); } + return Err(AppError::BadCredentials); + } + } + // If we scrape a logged-in page successfully that's also good. + let probe = c.get("https://doodstream.com/?op=my_files").send().await?; + if probe.status().is_success() { + let probe_text = probe.text().await.unwrap_or_default(); + if probe_text.contains("logout") || probe_text.contains("Logout") + || probe_text.contains("Dashboard") { return Ok("Login gültig".into()); } + } + Err(AppError::BadCredentials) + } + "voe.sx" => { + if a.username.is_empty() || a.password.is_empty() { return Err(AppError::BadCredentials); } + let c = reqwest::Client::builder() + .timeout(timeout).cookie_store(true).build()?; + let login_html = c.get("https://voe.sx/login").send().await?.text().await.unwrap_or_default(); + let csrf = regex::Regex::new(r#"]*name=["']_token["'][^>]*value=["']([^"']+)["']"#).unwrap() + .captures(&login_html).and_then(|c| c.get(1).map(|m| m.as_str().to_string()))) + .ok_or_else(|| AppError::HosterError("VOE".into(), "CSRF-Token nicht gefunden".into()))?; + let body = serde_urlencoded::to_string([ + ("_token", csrf.as_str()), + ("email", a.username.as_str()), + ("password", a.password.as_str()), + ]).unwrap_or_default(); + let res = c.post("https://voe.sx/login") + .header("Content-Type", "application/x-www-form-urlencoded") + .header("Referer", "https://voe.sx/login") + .body(body).send().await?; + let text = res.text().await.unwrap_or_default(); + if text.contains("credentials do not match") || text.contains("Incorrect") || text.contains("invalid") { + return Err(AppError::BadCredentials); + } + // Confirm session by pulling the upload page and looking for a CSRF + // (only present when logged in). + let upload_html = c.get("https://voe.sx/file-upload").send().await?.text().await.unwrap_or_default(); + if regex::Regex::new(r#"name=["']csrf-token["']"#).unwrap().is_match(&upload_html) { + Ok("Login gültig".into()) + } else { + Err(AppError::BadCredentials) + } } _ => { - if a.auth_type == "login" { - if a.username.is_empty() || a.password.is_empty() { return Err(AppError::BadCredentials); } - Ok("Login hinterlegt".into()) - } else if a.auth_type == "api" { - if a.api_key.is_empty() { return Err(AppError::BadCredentials); } - Ok("API Key hinterlegt".into()) - } else { - Ok("Nicht geprüft".into()) + if a.auth_type == "login" && (a.username.is_empty() || a.password.is_empty()) { + return Err(AppError::BadCredentials); } + if a.auth_type == "api" && a.api_key.is_empty() { + return Err(AppError::BadCredentials); + } + Ok("Nicht implementiert".into()) } } }