use std::{io::Write, time::Duration}; use crate::{ error::{PakkerError, Result}, http, model::{PakkerCredentialsFile, set_keyring_secret}, ui_utils::{prompt_secret, prompt_yes_no}, }; pub async fn execute( curseforge_api_key: Option, modrinth_token: Option, github_access_token: Option, ) -> Result<()> { let mut cf_key = curseforge_api_key; let mut mr_token = modrinth_token; let mut gh_token = github_access_token; let any_cli_args = cf_key.is_some() || mr_token.is_some() || gh_token.is_some(); // Enter interactive mode when no CLI args provided if !any_cli_args { println!("No credentials provided via command line."); println!(); if let Some(key) = prompt_secret("CurseForge API key (press Enter to skip)")? { cf_key = Some(key); } if let Some(token) = prompt_secret("Modrinth token (press Enter to skip)")? { mr_token = Some(token); } if let Some(token) = prompt_secret("GitHub access token (press Enter to skip)")? { gh_token = Some(token); } } let updated_any = cf_key.is_some() || mr_token.is_some() || gh_token.is_some(); if !updated_any { println!("No credentials to save."); return Ok(()); } // Verify credentials before saving let client = http::create_http_client(); let mut verified = Vec::new(); if let Some(ref key) = cf_key { print!("Verifying CurseForge API key... "); std::io::stdout().flush().ok(); match verify_curseforge(&client, key).await { Ok(()) => { println!("valid"); verified.push("CurseForge"); }, Err(e) => { println!("failed ({e})"); if !prompt_yes_no( "CurseForge key appears invalid. Save anyway?", false, false, )? { cf_key = None; } }, } } if let Some(ref token) = mr_token { print!("Verifying Modrinth token... "); std::io::stdout().flush().ok(); match verify_modrinth(&client, token).await { Ok(()) => { println!("valid"); verified.push("Modrinth"); }, Err(e) => { println!("failed ({e})"); if !prompt_yes_no( "Modrinth token appears invalid. Save anyway?", false, false, )? { mr_token = None; } }, } } if let Some(ref token) = gh_token { print!("Verifying GitHub access token... "); std::io::stdout().flush().ok(); match verify_github(&client, token).await { Ok(()) => { println!("valid"); verified.push("GitHub"); }, Err(e) => { println!("failed ({e})"); if !prompt_yes_no( "GitHub token appears invalid. Save anyway?", false, false, )? { gh_token = None; } }, } } let mut creds = PakkerCredentialsFile::load()?; if let Some(key) = cf_key { let key = key.trim().to_string(); if !key.is_empty() { set_keyring_secret("curseforge_api_key", &key)?; creds.curseforge_api_key = Some(key); } } if let Some(token) = mr_token { let token = token.trim().to_string(); if !token.is_empty() { set_keyring_secret("modrinth_token", &token)?; creds.modrinth_token = Some(token); } } if let Some(token) = gh_token { let token = token.trim().to_string(); if !token.is_empty() { set_keyring_secret("github_access_token", &token)?; creds.github_access_token = Some(token); } } creds.save()?; println!(); if verified.is_empty() { println!("Credentials saved (unverified)."); } else { println!("Credentials saved and verified: {}", verified.join(", ")); } println!( "Credentials file: {}", PakkerCredentialsFile::get_path()?.display() ); println!("Keyring service: pakker"); Ok(()) } async fn verify_curseforge( client: &reqwest::Client, api_key: &str, ) -> Result<()> { let response = client .get("https://api.curseforge.com/v1/mods/238222") .header("x-api-key", api_key) .timeout(Duration::from_secs(10)) .send() .await?; if response.status().is_success() { Ok(()) } else { Err(PakkerError::PlatformApiError(format!( "HTTP {}", response.status() ))) } } async fn verify_modrinth(client: &reqwest::Client, token: &str) -> Result<()> { let response = client .get("https://api.modrinth.com/v2/user") .header("Authorization", token) .timeout(Duration::from_secs(10)) .send() .await?; if response.status().is_success() { Ok(()) } else { Err(PakkerError::PlatformApiError(format!( "HTTP {}", response.status() ))) } } async fn verify_github(client: &reqwest::Client, token: &str) -> Result<()> { let response = client .get("https://api.github.com/user") .header("Authorization", format!("Bearer {token}")) .header("User-Agent", "pakker") .timeout(Duration::from_secs(10)) .send() .await?; if response.status().is_success() { Ok(()) } else { Err(PakkerError::PlatformApiError(format!( "HTTP {}", response.status() ))) } }