From abb342f59add7dab138fc4f79f1635a8ede44bb3 Mon Sep 17 00:00:00 2001 From: RXWatcher <14085001+RXWatcher@users.noreply.github.com> Date: Tue, 26 May 2026 06:56:51 +0200 Subject: [PATCH] fix(audiobooks): enrich ABS login envelope to match real client expectations Adds itemTagsAccessible, itemTagsSelected, seriesHideFromContinueListening, lastSeen, createdAt to the user object. Expands permissions to the eight keys real ABS emits. Enriches serverSettings with the dozen-plus flags official iOS/Android apps branch on (coverAspectRatio, dateFormat, timeFormat, scannerDisableWatcher, chromecastEnabled, etc.). Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/audiobooks/abs/login.go | 95 +++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 19 deletions(-) diff --git a/internal/audiobooks/abs/login.go b/internal/audiobooks/abs/login.go index 10746d8f..cf14446f 100644 --- a/internal/audiobooks/abs/login.go +++ b/internal/audiobooks/abs/login.go @@ -197,21 +197,38 @@ func (h *Handler) completeLogin(w http.ResponseWriter, r *http.Request, userID, libraryMaps = append(libraryMaps, audiobookLibraryMap(lib)) } + nowMs := time.Now().UnixMilli() + + // Real ABS clients pattern-match on a richer login envelope than the + // minimum we previously sent. The added fields (itemTagsAccessible, + // itemTagsSelected, lastSeen, createdAt) keep the iOS app off its + // "degraded mode" branch. Permissions stays the canonical eight-key + // object; setting all relevant flags true matches what real ABS does + // for a non-admin user. user := map[string]any{ - "id": userID, - "username": name, - "type": "user", - "defaultLibraryId": defaultLibraryID, - "librariesAccessible": []any{}, // empty = "all libraries accessible" - "mediaProgress": []any{}, - "bookmarks": []any{}, - "isOldToken": false, - "token": access, // legacy field some 2.17- clients still read + "id": userID, + "username": name, + "type": "user", + "defaultLibraryId": defaultLibraryID, + "librariesAccessible": []any{}, // empty = "all libraries accessible" + "itemTagsAccessible": []any{}, // empty = "all tags accessible" + "itemTagsSelected": []any{}, + "mediaProgress": []any{}, + "bookmarks": []any{}, + "seriesHideFromContinueListening": []any{}, + "isOldToken": false, + "token": access, // legacy field some 2.17- clients still read + "lastSeen": nowMs, + "createdAt": nowMs, "permissions": map[string]any{ - "update": true, - "delete": true, - "download": true, - "accessExplicitContent": true, + "download": true, + "update": true, + "delete": true, + "upload": true, + "accessAllLibraries": true, + "accessAllTags": true, + "accessExplicitContent": true, + "selectedTagsNotAccessible": false, }, } @@ -222,15 +239,55 @@ func (h *Handler) completeLogin(w http.ResponseWriter, r *http.Request, userID, user["refreshToken"] = refresh } + // Server settings: real ABS emits many flags; clients use these to + // branch UI. Defaults match the official server's defaults so clients + // pick predictable UX paths. + serverSettings := map[string]any{ + "id": "server-settings", + "version": ServerVersion, + "buildNumber": 1, + "language": "en-us", + "dateFormat": "MM/dd/yyyy", + "timeFormat": "HH:mm", + "timeZone": "UTC", + "coverAspectRatio": 1, + "storeCoverWithItem": false, + "storeMetadataWithItem": false, + "metadataFileFormat": "json", + "scannerDisableWatcher": true, + "scannerParseSubtitle": false, + "scannerFindCovers": false, + "scannerCoverProvider": "google", + "scannerPreferMatchedMetadata": false, + "scannerPreferOverdriveMediaMarker": false, + "sortingIgnorePrefix": false, + "sortingPrefixes": []string{"the", "a"}, + "chromecastEnabled": false, + "enableEReader": false, + "dateString": "", + "logLevel": 1, + "version_id": ServerVersion, + "sessionTimeout": 0, + "backupSchedule": false, + "backupsToKeep": 2, + "maxBackupSize": 1, + "loggerDailyLogsToKeep": 7, + "loggerScannerLogsToKeep": 2, + "homeBookshelfView": 1, + "bookshelfView": 1, + "podcastEpisodeSchedule": "0 * * * *", + "sortingIgnorePrefixesValue": "", + "allowIframe": false, + "authActiveAuthMethods": []string{"local"}, + } + writeJSON(w, http.StatusOK, map[string]any{ "user": user, "userDefaultLibraryId": defaultLibraryID, - "serverSettings": map[string]any{ - "version": ServerVersion, - "language": "en-us", - }, - "ereaderDevices": []any{}, - "libraries": libraryMaps, + "serverSettings": serverSettings, + "Source": "silo", + "ereaderDevices": []any{}, + "libraries": libraryMaps, // Legacy top-level token fields for clients that read them // directly (mainline reads from the user object; some third-party // clients still read top-level).