diff --git a/README.md b/README.md index 92b2d8f..6030465 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # FeedTheMonkey FeedTheMonkey is a desktop client for any server that implements the -[Greader API](https://github.com/theoldreader/api) (such as FreshRSS or Miniflux). It doesn't work as a standalone feed reader — +[Greader API](https://github.com/theoldreader/api), such as +[FreshRSS](https://freshrss.org) or [Miniflux](https://miniflux.app). +Just enter your server URL — the app detects the API path automatically. It doesn't work as a standalone feed reader — it connects to a server to fetch articles and sync read state. This is version 3, rewritten in Rust with GTK4 and libadwaita. diff --git a/src/api.rs b/src/api.rs index 312cd82..d683e8f 100644 --- a/src/api.rs +++ b/src/api.rs @@ -42,14 +42,18 @@ struct Summary { use crate::model::Article; -fn normalize_base_url(server_url: &str) -> String { +/// Return the candidate base URLs to try in order. +/// Miniflux serves the Greader API at the server root; +/// FreshRSS serves it at /api/greader.php. +fn candidate_base_urls(server_url: &str) -> Vec { let base = server_url.trim_end_matches('/'); - // If the user entered just a host (or host/path) without the FreshRSS - // API suffix, append it automatically. if base.ends_with("/api/greader.php") { - base.to_string() + vec![base.to_string()] } else { - format!("{base}/api/greader.php") + vec![ + base.to_string(), + format!("{base}/api/greader.php"), + ] } } @@ -59,47 +63,49 @@ impl Api { username: &str, password: &str, ) -> Result { - let base = normalize_base_url(server_url); let client = Client::new(); - let url = format!("{base}/accounts/ClientLogin"); - let resp = client - .post(&url) - .form(&[("Email", username), ("Passwd", password)]) - .send() - .await - .map_err(|e| e.to_string())?; + let candidates = candidate_base_urls(server_url); + let mut last_err = String::new(); - let status = resp.status(); - let body = resp.text().await.map_err(|e| e.to_string())?; + for base in candidates { + let url = format!("{base}/accounts/ClientLogin"); + let resp = match client + .post(&url) + .form(&[("Email", username), ("Passwd", password)]) + .send() + .await + { + Ok(r) => r, + Err(e) => { last_err = e.to_string(); continue; } + }; - if !status.is_success() { - let msg = human_error(&body, status.as_u16()); - return Err(format!("Login failed ({}): {}", status.as_u16(), msg)); + let status = resp.status(); + let body = resp.text().await.map_err(|e| e.to_string())?; + + if !status.is_success() { + last_err = format!("Login failed ({}): {}", status.as_u16(), human_error(&body, status.as_u16())); + continue; + } + + let auth_token = match body.lines().find_map(|l| l.strip_prefix("Auth=")) { + Some(t) => t.to_string(), + None => { + last_err = if looks_like_html(&body) { + format!( + "The server at {base} does not appear to be a \ + Greader API endpoint. Check your server URL." + ) + } else { + format!("Unexpected response from server: {}", body.trim()) + }; + continue; + } + }; + + return Ok(Self { client, server_url: base, auth_token }); } - let auth_token = body - .lines() - .find_map(|l| l.strip_prefix("Auth=")) - .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 { - client, - server_url: base, - auth_token, - }) + Err(last_err) } pub async fn fetch_write_token(&self) -> Result {