[READ-ONLY] Mirror of https://github.com/FoxxMD/multi-scrobbler. Scrobble plays from multiple sources to multiple clients docs.multi-scrobbler.app
deezer docker jellyfin koito lastfm listenbrainz maloja mopidy mpris music music-assistant plex scrobble self-hosted spotify subsonic tautulli youtube-music
0

Configure Feed

Select the types of activity you want to include in your feed.

refactor: replace Apple Music headers config with origin field

Replaces the generic `headers` configuration field with a specific `origin` field for Apple Music. Updates the config schema, environment variable handling, documentation, and source implementation to simplify origin header management.

Exerra (Jul 15, 2026, 5:12 PM +0300) b3c332de 93a1042e

+17 -28
+5 -5
config/applemusic.json.example
··· 13 13 }, 14 14 "token": "your-jwt-token-here", 15 15 "mediaUserToken": "your-media-user-token-here", 16 - // optional: custom headers for API requests (e.g. Origin) 17 - //"headers": { 18 - // "Origin": "https://music.apple.com" 19 - //} 16 + 17 + // optional: Origin header for API requests (required when using a browser token) 18 + "origin": "https://music.apple.com", 19 + 20 20 // optional: polling interval in seconds (default 60) 21 - //"interval": 60 21 + "interval": 60 22 22 }, 23 23 "options": { 24 24 "logDiff": true,
+4 -6
docsite/docs/configuration/sources/applemusic.mdx
··· 48 48 "data": { 49 49 "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsIm...", 50 50 "mediaUserToken": "your-media-user-token-here", 51 - "headers": { 52 - "Origin": "https://music.apple.com" 53 - } 51 + "origin": "https://music.apple.com" 54 52 } 55 53 } 56 54 ``` 57 55 58 - :::info[Custom Header Required] 59 - When using a JWT extracted from the browser, Apple requires the request to match the domain it was issued to. You **must** include the `Origin` header in your config as shown above. 56 + :::info[Origin Header Required] 57 + When using a JWT extracted from the browser, Apple requires the request to match the domain it was issued to. You **must** include the `origin` field in your config as shown above. 60 58 ::: 61 59 62 60 *Note: Browser-generated JWTs are valid for a maximum of 35 days and must be updated manually when they expire.* ··· 136 134 | `APPLEMUSIC_KEY_P8` | No | | The contents of your MusicKit `.p8` private key file.| 137 135 | `APPLEMUSIC_TOKEN` | No | | The authentication JWT extracted from the browser. | 138 136 | `APPLEMUSIC_INTERVAL` | No | `60` | Polling interval in seconds. | 139 - | `APPLEMUSIC_HEADERS` | No | | JSON string of custom headers for API requests, e.g. `{"Origin":"https://music.apple.com"}` | 137 + | `APPLEMUSIC_ORIGIN_HEADER` | No | | Origin header for API requests, e.g. `https://music.apple.com`. Required when using a browser token. | 140 138 | `APPLEMUSIC_RECOVER_UNCHANGED_TOP_HISTORY` | No | `true` | Apple Music deduplicates its history by bumping re-played tracks to the top. If you listen to Song A → Song B → Song A, the history changes from `[A]` to `[A, B]` (bumping A). MS detects this "top-rebound" to recover the intermediate play (B) and the re-listen (A). Disable this only if you see incorrect duplicate scrobbles. | 141 139 | `APPLEMUSIC_NAME` | No | | A vanity name different than the ID. | 142 140 </Config>
+4 -4
src/backend/common/infrastructure/config/source/applemusic.ts
··· 12 12 token?: string 13 13 mediaUserToken?: string 14 14 /** 15 - * Custom headers to include in every Apple Music API request. 16 - * Useful for setting Origin header, etc. 15 + * Origin header to include in every Apple Music API request. 16 + * Required when using a browser token (not a MusicKit key). 17 17 * 18 - * @examples [{"Origin": "https://music.apple.com"}] 18 + * @example "https://music.apple.com" 19 19 */ 20 - headers?: Record<string, string> 20 + origin?: string 21 21 } 22 22 23 23 export interface AppleMusicSourceConfig extends CommonSourceConfig {
+3 -3
src/backend/sources/AppleMusicSource.ts
··· 55 55 key, 56 56 token, 57 57 mediaUserToken, 58 - headers, 58 + origin, 59 59 } = this.config.data || {}; 60 60 61 61 if (!key && !token) { ··· 75 75 this.musicKit.token = token!; 76 76 } 77 77 78 - if (headers) { 79 - this.musicKit.headers = headers; 78 + if (origin) { 79 + this.musicKit.headers = { Origin: origin }; 80 80 } 81 81 82 82 return true;
+1 -10
src/backend/sources/ScrobbleSources.ts
··· 842 842 } 843 843 return undefined; 844 844 })(); 845 - const headersEnv = process.env.APPLEMUSIC_HEADERS; 846 - let headers: Record<string, string> | undefined; 847 - if (headersEnv !== undefined && headersEnv.trim() !== '') { 848 - try { 849 - headers = JSON.parse(headersEnv); 850 - } catch (e) { 851 - throw new Error(`APPLEMUSIC_HEADERS could not be parsed as JSON: ${headersEnv}`, { cause: e }); 852 - } 853 - } 854 845 const data = removeUndefinedKeys({ 855 846 mediaUserToken: process.env.APPLEMUSIC_MEDIA_USER_TOKEN, 856 847 token: process.env.APPLEMUSIC_TOKEN, 857 848 key, 858 - headers, 849 + origin: nonEmptyStringOrDefault(process.env.APPLEMUSIC_ORIGIN_HEADER, undefined), 859 850 }, false); 860 851 const recoverEnv = process.env.APPLEMUSIC_RECOVER_UNCHANGED_TOP_HISTORY; 861 852 const recoverUnchangedTopHistory = recoverEnv !== undefined && recoverEnv.trim() !== ''