diff --git a/CHANGELOG.md b/CHANGELOG.md index a330387..b11d593 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,8 @@ Not released yet (bake → vouch → floor, `09` §3 decision 247). Design: felh with the API key named „felhom" read at use from Jellyfin's own database (no key → one household step, said on the page). Keys read at use, never stored or logged; links never undone when an app stops. Idempotent: `TestRun_SecondRunWritesNothing` (red-proved: the measured repeated qBittorrent writes). +- **Fixed on Tester 1 (Part G1):** qBittorrent 5 answers a good login with HTTP 204, not 200 — the wiring refused it + (`login: HTTP 204`). Both logins accept 204; the test fake now answers 204 as the real app does (red-proved). - **Old Sonarr/Radarr installs repaired by themselves (R-1047)** — only when /media is no longer bound: same-path root folder, `moveFiles:false` editor, rescan, old root removed, Radarr's collections too. `TestRepairOldRoot_K7` (red-proved: moveFiles=true is caught). diff --git a/controller/internal/mediawire/mediawire.go b/controller/internal/mediawire/mediawire.go index 909000e..8b339f4 100644 --- a/controller/internal/mediawire/mediawire.go +++ b/controller/internal/mediawire/mediawire.go @@ -216,7 +216,7 @@ func (w *Wirer) qbittorrent(drives []Drive, pw string) Result { form := url.Values{"username": {"admin"}, "password": {pw}} code, _, hd, err := w.do("POST", base+"/api/v2/auth/login", map[string]string{"Referer": base}, strings.NewReader(form.Encode()), "application/x-www-form-urlencoded") m := regexp.MustCompile(`((?:QBT_)?SID[A-Za-z0-9_]*)=([^;]+)`).FindStringSubmatch(strings.Join(hd.Values("Set-Cookie"), "; ")) - if err != nil || code != 200 || m == nil { + if err != nil || (code != 200 && code != 204) || m == nil { // qBittorrent 5 answers a good login 204 (measured on Tester 1) r.State, r.Detail = "error", fmt.Sprintf("login: HTTP %d %v", code, err) return r } @@ -834,7 +834,7 @@ func (w *Wirer) qbLogin(pw string) (string, map[string]string, error) { form := url.Values{"username": {"admin"}, "password": {pw}} code, _, hd, err := w.do("POST", base+"/api/v2/auth/login", map[string]string{"Referer": base}, strings.NewReader(form.Encode()), "application/x-www-form-urlencoded") m := regexp.MustCompile(`((?:QBT_)?SID[A-Za-z0-9_]*)=([^;]+)`).FindStringSubmatch(strings.Join(hd.Values("Set-Cookie"), "; ")) - if err != nil || code != 200 || m == nil { + if err != nil || (code != 200 && code != 204) || m == nil { // qBittorrent 5 answers a good login 204 (measured on Tester 1) return "", nil, fmt.Errorf("qBittorrent login: HTTP %d %v", code, err) } return base, map[string]string{"Referer": base, "Cookie": m[1] + "=" + m[2]}, nil diff --git a/controller/internal/mediawire/mediawire_test.go b/controller/internal/mediawire/mediawire_test.go index 1d271b6..37be5a8 100644 --- a/controller/internal/mediawire/mediawire_test.go +++ b/controller/internal/mediawire/mediawire_test.go @@ -84,6 +84,7 @@ func (w *world) server(t *testing.T) *httptest.Server { return } http.SetCookie(rw, &http.Cookie{Name: "QBT_SID_8080", Value: "sid"}) + rw.WriteHeader(204) // qBittorrent 5.x: a good login is 204 No Content (measured on Tester 1, 2026-10-15) case p == "/api/v2/app/preferences": j(map[string]interface{}{"auto_tmm_enabled": w.autoTMM}) case p == "/api/v2/app/setPreferences":