fix(fetch): one status table — a persistent 4xx is permanent everywhere

status_error's catch-all called every unlisted status Transient, so a 400/405/418/451 got three retries per link before the same answer (twitter syndication's broken-token 400 being the live example), and download_status_error plus misskey's and bilibili's local fallbacks each carried their own copy of the table — bilibili and misskey classifying a 404 as Transient while the center classified it NotFound. The center now makes any client error except 408/429 a refusal (permanent), the media path delegates to it as status_error("media", ...) and its duplicated fn is deleted, and misskey/bilibili fall through to the center after their own special statuses (misskey's 400 body, bilibili's 412). A table test pins every class.
This commit is contained in:
2026-09-24 02:52:10 +08:00
parent e6800fd27b
commit 3fb6b3b4da
4 changed files with 64 additions and 31 deletions
+6 -4
View File
@@ -51,7 +51,8 @@ pub fn cache_key(url: &str) -> Option<String> {
/// Fetches a note from misskey.io by id. The API answers client failures
/// with HTTP 400 + `{"error":{"code":...}}` (NO_SUCH_NOTE → NotFound);
/// everything else non-success is transient and retried by [`crate::site::fetch`].
/// every other non-success status falls through to the shared classes in
/// [`crate::site::status_error`] — persistent 4xx permanent, 429/5xx retried.
pub async fn fetch(note_id: &str) -> Result<model::Note, FetchError> {
let response = crate::site::CLIENT
.post(API_URL)
@@ -62,9 +63,10 @@ pub async fn fetch(note_id: &str) -> Result<model::Note, FetchError> {
if !status.is_success() {
return Err(match status.as_u16() {
400 => not_found_or_invalid(response).await,
// A refusal or an auth demand is not a bad moment.
401 | 403 => FetchError::Blocked,
_ => FetchError::Transient(format!("misskey status {status}")),
// The local fallback used to disagree with the center: a misskey
// 404 came back Transient here and was fetched three more times
// for a note that is simply gone.
_ => crate::site::status_error("misskey", status),
});
}
response.json().await.map_err(|e| FetchError::Site {