diff --git a/src/api.rs b/src/api.rs index c9a0e71..312cd82 100644 --- a/src/api.rs +++ b/src/api.rs @@ -73,13 +73,26 @@ impl Api { let body = resp.text().await.map_err(|e| e.to_string())?; if !status.is_success() { - return Err(format!("Login failed ({}): {}", status, body.trim())); + let msg = human_error(&body, status.as_u16()); + return Err(format!("Login failed ({}): {}", status.as_u16(), msg)); } let auth_token = body .lines() .find_map(|l| l.strip_prefix("Auth=")) - .ok_or_else(|| "No Auth token in response".to_string())? + .ok_or_else(|| { + // The server returned 200 but not the expected API response — + // most likely the URL points to a web page, not a FreshRSS API. + if looks_like_html(&body) { + format!( + "The server at {} does not appear to be a FreshRSS \ + Google Reader API endpoint. Check your server URL.", + base + ) + } else { + format!("Unexpected response from server: {}", body.trim()) + } + })? .to_string(); Ok(Self { @@ -195,6 +208,28 @@ impl Api { } } +fn looks_like_html(body: &str) -> bool { + let trimmed = body.trim_start(); + trimmed.starts_with(" String { + if looks_like_html(body) { + match status { + 401 | 403 => "Wrong username or password.".to_string(), + 404 => "API endpoint not found. Check your server URL.".to_string(), + _ => format!("Server returned HTTP {status}. Check your server URL."), + } + } else { + let trimmed = body.trim(); + if trimmed.is_empty() { + format!("Server returned HTTP {status} with no message.") + } else { + trimmed.to_string() + } + } +} + fn plain_text_excerpt(html: &str, max_chars: usize) -> String { // Very simple HTML stripper — remove tags, collapse whitespace let mut out = String::with_capacity(html.len());