From 76c56cf13b69dc6688e1de716adee0411ab7e5de Mon Sep 17 00:00:00 2001 From: Administrator Date: Thu, 28 May 2026 21:48:13 +0200 Subject: [PATCH] fix(doodstream): extend 3.3.29 account-poison protection to the API path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new API upload path POSTs to the same cloudatacdn.com nodes as the web path, so it can hit the same backend flake — a 2xx response with no filecode, or a transient "no servers available" from /api/upload/server (now that the stale fallback node is gone). hosters.uploadFile threw GENERIC errors for both, which the upload-manager would treat as an account failure → mark-failed → pre-job-swap-blocked on the next batch: the exact symptom 3.3.29 fixed for the web path, reintroduced via the unprotected API path. Tag both API-path analogs of the empty form as err.hosterTransient=true: - codeless 2xx ("lieferte keine file_code-Antwort") — bytes accepted, no link. - transient "no upload server" (shouldRetryServerLookup true: no-servers/busy/ try-again) — but NOT genuine auth failures (invalid key/unauthorized), which stay classified as account errors. The upload-manager checks _isHosterTransientError (flag-based) before the account-error classifier, so both now fail the file without blacklisting the account. Consumption side already covered by the 3.3.29 regression test. 173/173. Co-Authored-By: Claude Opus 4.7 --- lib/hosters.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/hosters.js b/lib/hosters.js index f9f9961..434fbe5 100644 --- a/lib/hosters.js +++ b/lib/hosters.js @@ -378,7 +378,13 @@ async function getUploadServer(hosterName, hosterConfig, apiKey, signal) { } if (lastMessage) { - throw new Error(`Kein Upload-Server erhalten: ${lastMessage}`); + const e = new Error(`Kein Upload-Server erhalten: ${lastMessage}`); + // "no servers available" / busy / try-again is a transient hoster-side + // condition, not an account fault — tag it so the account isn't blacklisted. + // Genuine auth failures (invalid key / unauthorized / forbidden) make + // shouldRetryServerLookup return false and stay classified as account errors. + if (shouldRetryServerLookup(lastMessage)) e.hosterTransient = true; + throw e; } throw new Error('Kein Upload-Server erhalten. API-Key pruefen.'); } @@ -545,9 +551,18 @@ async function uploadFile(hosterName, filePath, apiKey, onProgress, signal, thro const isOkishNoPayload = /^(ok|success|done|accepted)$/i.test(msg); if (isOkishNoPayload || !msg) { const snippet = JSON.stringify(payload).slice(0, 400); - throw new Error( + // 2xx with no filecode: the hoster accepted the upload (bytes sent, status + // OK) but returned no usable link. For doodstream this is the API-path + // analog of the web empty-form — the backend file-registration timing out + // under large-file load. It's a hoster-side flake, NOT an account problem, + // so tag it hosterTransient: the upload-manager then fails this file WITHOUT + // blacklisting the account (same protection the web path got in 3.3.29) and + // the account stays usable for the next retry/batch. + const err = new Error( `Upload zu ${hosterName} lieferte keine file_code-Antwort (Payload: ${snippet})` ); + err.hosterTransient = true; + throw err; } throw new Error(msg); }