Files
SongRequest/.cursor/plans/apple_music_integration_a6a44bfc.plan.md
cottongin ea40251e0e Add web dashboard auth system and session management actions
Implement per-admin authentication with IRC-managed accounts
(addSongAdmin/removeSongAdmin/listSongAdmins), session-based login,
and admin presence tracking via WebSocket. Legacy webAuthToken
retained as fallback.

Add rename, clear, and delete actions for archived sessions with
themed modal confirmations (admin-only UI).

Made-with: Cursor
2026-03-28 12:02:42 -04:00

11 KiB

name, overview, todos, isProject
name overview todos isProject
Apple Music Integration Exploratory investigation of Apple Music API integration options for adding requests to a user's Apple Music queue/playlist and automatically detecting when songs are played, with assessment of feasibility, prerequisites, and architectural approaches.
id content status
prereqs Set up Apple Developer Program account, create MusicKit identifier, generate private key pending
id content status
apple-music-module Create SongRequest/apple_music.py with JWT generation and REST API wrapper pending
id content status
musickit-js-embed Embed MusicKit JS v3 in the dashboard, add authorize flow and queue control pending
id content status
now-playing-detection Add playbackStateDidChange listener in dashboard JS + /api/now-playing endpoint in web.py pending
id content status
auto-played-matching Implement trackId matching logic in store/plugin to auto-mark requests as played pending
id content status
config-values Add config entries for Apple Developer key, team ID, key ID, playlist ID, and feature toggles pending
id content status
recently-played-poller (Optional) Add server-side polling of recently-played API as a fallback pending
false

Apple Music Integration: Feasibility and Implementation Plan

Prerequisites (Required for Both Features)

Before any integration work, you need:

  • Paid Apple Developer Program membership ($99/year) -- required to generate MusicKit keys
  • MusicKit identifier + private key from Certificates, Identifiers & Profiles in the Apple Developer portal
  • Developer Token (JWT) -- an ES256-signed JWT with your Team ID and key ID, valid up to 6 months
  • Music User Token -- obtained via MusicKit JS authorization flow in the browser; required for any personal library/playlist operations. The dashboard operator must have an active Apple Music subscription

The current plugin uses the public iTunes Search API (no auth needed). Both proposed features require authenticated access to a specific user's Apple Music account.


Feature 1: Add Requests to Apple Music Playlist/Queue

How It Would Work

There are two viable approaches, each with different trade-offs:

Approach A: Server-Side REST API (Add to Playlist)

The Apple Music REST API supports adding tracks to a user's library playlist:

POST https://api.music.apple.com/v1/me/library/playlists/{playlist_id}/tracks
Authorization: Bearer {developer_token}
Music-User-Token: {user_token}

{
  "data": [
    { "id": "{catalog_song_id}", "type": "songs" }
  ]
}

Architecture:

sequenceDiagram
    participant IRC as IRC User
    participant Bot as Limnoria Bot
    participant iTunes as iTunes Search API
    participant Dashboard as Web Dashboard
    participant AM as Apple Music API
    
    IRC->>Bot: !request Artist - Title
    Bot->>iTunes: Search query
    iTunes-->>Bot: Track results (includes trackId)
    Bot->>Dashboard: WebSocket push (new card)
    Dashboard->>AM: POST /v1/me/.../tracks (on approve)
    AM-->>Dashboard: 200 OK (added to playlist)
    Dashboard->>Bot: WebSocket notify (added)

Implementation steps:

  • Add a new module [SongRequest/apple_music.py](SongRequest/apple_music.py) for JWT generation (using PyJWT + cryptography) and REST API calls
  • Add config values for Apple Developer key path, key ID, team ID, target playlist ID, and stored Music User Token
  • The web dashboard handles the MusicKit JS authorization flow once (popup login), stores the Music User Token, and sends it to the bot backend
  • When a request is approved (or auto-approved), the bot calls POST /v1/me/library/playlists/{id}/tracks with the iTunes trackId (which maps to Apple Music catalog IDs)
  • New config option: appleMusicAutoAdd (boolean) to toggle this behavior

Key concern: The trackId from the iTunes Search API should map to Apple Music catalog song IDs, but this needs verification. The iTunes Search API returns trackId (numeric), while the Apple Music API expects string catalog IDs. These are typically the same value, just as a string.

Pros: Fully server-side, works without the dashboard open, songs appear in a persistent playlist Cons: Adds to a playlist only (not the live playback queue), tracks go to the end of the playlist, no position control, Music User Token expires and needs periodic re-auth

Approach B: MusicKit JS Web Player (Add to Queue + Play)

Embed MusicKit JS in the web dashboard to directly control Apple Music playback:

const music = MusicKit.getInstance();
await music.authorize();
await music.setQueue({ songs: [catalogSongId] });
// Or append to existing queue
await music.playNext({ songs: [catalogSongId] });

Architecture:

sequenceDiagram
    participant IRC as IRC User
    participant Bot as Limnoria Bot
    participant Dashboard as Web Dashboard + MusicKit JS
    participant AM as Apple Music (Playback)
    
    IRC->>Bot: !request Artist - Title
    Bot->>Dashboard: WebSocket push (new request)
    Note over Dashboard: Operator approves
    Dashboard->>AM: MusicKit.playNext(songId)
    AM-->>Dashboard: Song added to queue
    Note over Dashboard: Apple Music plays on this device

Implementation steps:

  • Add MusicKit JS SDK to [SongRequest/templates/index.html](SongRequest/templates/index.html) via <script src="https://js-cdn.music.apple.com/musickit/v3/musickit.js">
  • Add a "Connect Apple Music" button in the dashboard that calls music.authorize()
  • On approve/auto-approve, call music.playNext({ songs: [trackId] }) to queue the song
  • Optionally, add a mini player widget to the dashboard showing the current queue

Pros: Actually controls the live playback queue, songs play in order, real-time control from the dashboard Cons: Requires the dashboard to be open in a browser on the machine doing playback, browser tab must stay active, only controls playback on that specific device/session

Recommendation

Approach B (MusicKit JS) is better suited to the DJ/streamer use case -- it puts songs directly into the playback queue rather than just appending to a playlist. Approach A is useful as a secondary feature (e.g., building a "requested songs" playlist for later). Both can coexist.


Feature 2: Track When Requests Get Played Automatically

The Problem

Apple Music has no server-side webhook or push notification for "now playing" events. There is no way for the bot to passively learn that a song started playing without active client-side monitoring.

Viable Approaches

Approach A: MusicKit JS Event Monitoring (Best Fit)

If MusicKit JS is already embedded (from Feature 1, Approach B), the dashboard can listen for playback events:

const music = MusicKit.getInstance();
music.addEventListener('playbackStateDidChange', (event) => {
  if (event.state === MusicKit.PlaybackStates.playing) {
    const item = music.nowPlayingItem;
    // Send track ID to bot backend via WebSocket/fetch
    fetch('/api/now-playing', {
      method: 'POST',
      body: JSON.stringify({ trackId: item.id, title: item.title })
    });
  }
});

Architecture:

sequenceDiagram
    participant AM as Apple Music (Playback)
    participant Dashboard as Web Dashboard + MusicKit JS
    participant Bot as Limnoria Bot
    participant IRC as IRC Channel
    
    AM->>Dashboard: playbackStateDidChange (playing)
    Dashboard->>Dashboard: Check nowPlayingItem.id
    Dashboard->>Bot: POST /api/now-playing {trackId}
    Bot->>Bot: Match trackId against approved requests
    Bot->>Bot: Update status to "played"
    Bot->>IRC: [NOW PLAYING] Song - Artist (requested by nick)
    Bot->>Dashboard: WebSocket push (card updated)

Implementation steps:

  • Add playbackStateDidChange and nowPlayingItemDidChange event listeners in the dashboard JS
  • When a new song starts playing, the dashboard POSTs the track info to a new /api/now-playing endpoint
  • The bot matches the trackId against approved requests in the database
  • If matched, auto-updates the request status to "played" and announces in IRC
  • Add a played_at timestamp column to the requests table for precise tracking

Pros: Automatic, real-time, no manual "Mark Played" clicking needed Cons: Requires the dashboard + MusicKit JS to be running, only detects playback from the MusicKit JS session (not from the Apple Music app directly)

Approach B: Recently Played Polling (Server-Side Fallback)

Poll the Apple Music REST API's recently-played endpoint:

GET https://api.music.apple.com/v1/me/recent/played/tracks
Authorization: Bearer {developer_token}
Music-User-Token: {user_token}

Implementation steps:

  • Add a background polling loop (every 30-60 seconds) that fetches recently played tracks
  • Compare against approved requests by trackId
  • Auto-mark matched requests as "played"

Pros: Works without the dashboard being open, detects plays from any device/app Cons: Not real-time (30-60s delay), the recently-played API has undocumented limitations (unclear update frequency, unclear history depth, may miss short plays), Music User Token must be stored server-side and refreshed

Approach C: Hybrid

Use MusicKit JS events (Approach A) when the dashboard is open, and fall back to recently-played polling (Approach B) when it's not. The bot prefers the real-time signal but has the poller as a safety net.

Recommendation

Approach A (MusicKit JS events) is the most reliable and real-time option, especially since the dashboard is already the operator's control panel. Approach B could supplement it but has reliability concerns due to the poorly-documented recently-played API.


Summary of Feasibility

Aspect Feasibility Notes
Add to playlist (REST) High Well-documented API, straightforward
Add to queue (MusicKit JS) High Requires dashboard open during playback
Auto-detect played (MusicKit JS) High Real-time, but scoped to dashboard session
Auto-detect played (server-side) Medium Recently-played API is poorly documented
Server-side webhook/push Not possible Apple provides no such mechanism

Key Files to Modify

  • [SongRequest/templates/index.html](SongRequest/templates/index.html) -- embed MusicKit JS, add authorize flow, add event listeners
  • [SongRequest/web.py](SongRequest/web.py) -- new /api/now-playing endpoint, MusicKit config endpoint
  • [SongRequest/plugin.py](SongRequest/plugin.py) -- auto-play detection callback, IRC announcements
  • [SongRequest/config.py](SongRequest/config.py) -- new config values for Apple Developer credentials
  • New: [SongRequest/apple_music.py](SongRequest/apple_music.py) -- JWT generation, REST API wrapper (for Approach A playlist add and/or recently-played polling)
  • [SongRequest/store.py](SongRequest/store.py) -- optional played_at column, query for matching trackId against approved requests

New Dependencies

  • PyJWT + cryptography -- for generating Apple Developer JWT tokens (server-side)
  • No new JS dependencies -- MusicKit JS is loaded from Apple's CDN