use std::io::Write;
use anyhow::{Context, Result};
use tracing::info;
use modde_core::paths;
use modde_sources::nexus::auth;
use crate::NexusAction;
pub async fn handle(action: NexusAction) -> Result<()> {
match action {
NexusAction::Auth => handle_auth().await,
NexusAction::Status => handle_status().await,
}
}
fn nexus_client() -> Result<reqwest::Client> {
reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(300))
.connect_timeout(std::time::Duration::from_secs(30))
.build()
.context("failed to build HTTP client")
}
fn map_nexus_error(e: anyhow::Error, rejected_advice: &str) -> anyhow::Error {
let msg = e.to_string();
if msg.contains("401") || msg.contains("403") {
anyhow::anyhow!(
"API key rejected by Nexus (HTTP 401/403). {rejected_advice}\n\
Cause: {e}"
)
} else if msg.contains("429") {
anyhow::anyhow!(
"Nexus API rate limit exceeded (HTTP 429). Wait a few minutes and try again.\n\
Cause: {e}"
)
} else {
anyhow::anyhow!("API key validation failed: {e}")
}
}
async fn handle_auth() -> Result<()> {
print!("Enter your Nexus API key: ");
std::io::stdout().flush()?;
let mut api_key = String::new();
std::io::stdin()
.read_line(&mut api_key)
.context("failed to read API key from stdin")?;
let api_key = api_key.trim().to_string();
if api_key.is_empty() {
anyhow::bail!("API key cannot be empty");
}
let client = nexus_client()?;
let is_premium = auth::check_premium(&client, &api_key).await
.map_err(|e| map_nexus_error(e,
"Verify your key at https://www.nexusmods.com/users/myaccount?tab=api+access"
))?;
let key_path = config_key_path();
if let Some(parent) = key_path.parent() {
tokio::fs::create_dir_all(parent).await?;
}
tokio::fs::write(&key_path, &api_key).await
.context("failed to write API key to config")?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&key_path, std::fs::Permissions::from_mode(0o600))?;
}
info!(key_path = %key_path.display(), "API key stored");
println!("API key validated and saved to {}", key_path.display());
if is_premium {
println!("Account type: Premium (automated downloads enabled)");
} else {
println!("Account type: Free (manual download links only)");
}
Ok(())
}
async fn handle_status() -> Result<()> {
let api_key = auth::load_api_key()
.context("no API key configured; run `modde nexus auth` first")?;
let client = nexus_client()?;
let is_premium = auth::check_premium(&client, &api_key).await
.map_err(|e| map_nexus_error(e,
"Your stored key may have been revoked. Re-run `modde nexus auth` to set a new key."
))?;
println!("Nexus API key: valid");
if is_premium {
println!("Account type: Premium");
} else {
println!("Account type: Free");
}
Ok(())
}
fn config_key_path() -> std::path::PathBuf {
paths::modde_config_dir().join("nexus_api_key")
}