api: auto-detect Greader API path for Miniflux and FreshRSS
Miniflux serves the Greader API at the server root while FreshRSS uses /api/greader.php. Instead of hardcoding the FreshRSS suffix, try the URL as-is first (works for Miniflux) and fall back to appending /api/greader.php (works for FreshRSS). The user just enters the server URL without needing to know the API path.
This commit is contained in:
parent
82aabc080a
commit
b49cc69c49
2 changed files with 50 additions and 42 deletions
|
|
@ -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.
|
||||
|
|
|
|||
88
src/api.rs
88
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<String> {
|
||||
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<Self, String> {
|
||||
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<String, String> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue