{"id":"9b4595d49f1983fcdb8c6a2eb9b5f7a43f77e11f7c3733a0b8809dcce1787406","pubkey":"f7922a0adb3fa4dda5eecaa62f6f7ee6159f7f55e08036686c68e08382c34788","created_at":1770684272,"kind":30817,"tags":[["d","music-playlists-with-pc-20-guids"],["title","Music Playlists with PC 2.0 GUIDS"],["k","34139"],["a","30817:1bc70a0148b3f316da33fe3c89f23e3e71ac4ff998027ec712b905cd24f6a411:music-playlists","","fork"],["p","1bc70a0148b3f316da33fe3c89f23e3e71ac4ff998027ec712b905cd24f6a411"],["client","nostrhub.io"]],"content":"# Music Playlists\n\n`draft` `optional`\n\nThis spec defines an addressable event kind for publishing music playlists on Nostr, with tracks referenced via [Podcast Index](https://podcastindex.org/) GUIDs.\n\n## Event Kind\n\n- `34139`: Playlist (parameterized replaceable per [NIP-33](https://github.com/nostr-protocol/nips/blob/master/33.md))\n\n## Playlist Event\n\nA playlist is an addressable event containing an ordered list of music tracks.\n\n### Format\n\nThe `.content` field SHOULD contain a Markdown track listing as a human-readable fallback:\n\n```\n# Playlist Title\n\nArtist One - Track Title\nArtist Two - Track Title\n\nN tracks\n```\n\nThe `i` tags are the canonical track references. The `.content` listing allows clients to display the playlist even when feed resolution is unavailable.\n\n### Tags\n\n**Required:**\n- `d` - Unique identifier for this playlist\n- `title` - Playlist title\n- `alt` - Human-readable description (NIP-31)\n\n**Optional:**\n- `image` - URL to playlist artwork\n- `i` - Track and feed references (multiple, ordered — see below)\n- `t` - Category tags for discovery\n- `public` - Set to `\"true\"` for public playlists (default)\n\n### Example\n\n```json\n{\n  \"id\": \"<32-byte hex event id>\",\n  \"pubkey\": \"<32-byte hex public key>\",\n  \"created_at\": 1700000000,\n  \"kind\": 34139,\n  \"content\": \"# Summer Vibes 2024\\n\\nSurvival Guide - January Shock\\nJune & The Jets - You Sure Did\\nBear's Snare - Ocean Breeze\\n\\n3 tracks\",\n  \"tags\": [\n    [\"d\", \"summer-vibes-2024\"],\n    [\"title\", \"Summer Vibes 2024\"],\n    [\"alt\", \"Music playlist: Summer Vibes 2024\"],\n    [\"t\", \"playlist\"],\n    [\"t\", \"music\"],\n    [\"public\", \"true\"],\n    [\"image\", \"https://cdn.blossom.example/img/playlist.jpg\"],\n    [\"i\", \"podcast:item:guid:a1b2c3d4-e5f6-7890-abcd-ef1234567890\"],\n    [\"i\", \"podcast:item:guid:b2c3d4e5-f6a7-8901-bcde-f12345678901\"],\n    [\"i\", \"podcast:item:guid:c3d4e5f6-a7b8-9012-cdef-012345678902\"],\n    [\"i\", \"podcast:guid:d7b4abee-1234-5678-9abc-def012345678\"],\n    [\"i\", \"podcast:guid:e8c5bcff-2345-6789-abcd-ef0123456789\"]\n  ],\n  \"sig\": \"<64-byte hex signature>\"\n}\n```\n\n## Track References\n\nPlaylists reference music tracks using `i` tags with prefixes from the [Podcasting 2.0 namespace](https://github.com/Podcastindex-org/podcast-namespace):\n\n```\n[\"i\", \"podcast:item:guid:<itemGuid>\"]\n[\"i\", \"podcast:guid:<feedGuid>\"]\n```\n\nWhere:\n- `podcast:item:guid` references a specific track by its RSS [`<guid>`](https://www.rssboard.org/rss-specification#hrelementsOfLtitemgt) element value. One tag per track, in playlist order.\n- `podcast:guid` references the feed containing one or more tracks, by the feed's [`<podcast:guid>`](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#guid) element value. Deduplicated — one tag per unique feed.\n\nItem GUID tags MUST appear in playlist order. Feed GUID tags MAY appear in any order and MAY be interleaved with item tags or grouped separately.\n\nThe mapping between items and feeds is implicit. Clients resolve all feed GUIDs, parse each feed, and search across them to match each item GUID.\n\n### Resolution\n\nClients resolve tracks by:\n\n1. Collecting `podcast:guid` values from `i` tags\n2. Looking up feed URLs via the Podcast Index API [`podcasts/byguid`](https://podcastindex-org.github.io/docs-api/#get-/podcasts/byguid) endpoint (requires API key + secret)\n3. Fetching and parsing each feed's RSS XML\n4. Matching each `podcast:item:guid` across all parsed feeds\n5. Falling back to the `.content` track listing for unresolved items\n\n### Live Example\n\n```\nnaddr1qvzqqqy9tvpzpauj9g9dk0aymkj7aj4x9ahhaes4nal4tcyqxe5xc68qswpvx3ugqyt8wumn8ghj7cmgv9jxvtnwdaehgu339e3k7mgpzamhxue69uhkv6tvw3jhytnwdaehgu3wwa5kuegpzpmhxue69uhkummnw3ezuamfdejsq9tnw3skymr9ddexzen594nxzan0wf5hgetnnkkw8h\n```\n\n## Implementation Notes\n\n- Playlists are updatable (addressable events — re-publishing with the same `d` tag replaces the previous version)\n- `i` tag order is canonical; clients SHOULD preserve track order when displaying\n- Clients SHOULD handle missing/deleted tracks gracefully — show a placeholder from `.content` or skip\n- When a feed cannot be resolved (dead URL, removed from index), clients MAY show the corresponding `.content` line instead\n- Use `naddr` identifiers to link to playlists\n- Playlists support NIP-25 reactions and NIP-22 comments\n- Artwork images SHOULD be hosted on Blossom servers for permanence\n- Resolved feeds may contain [`<podcast:value>`](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#value) tags — clients MAY parse these to enable Lightning payments and boosts","sig":"8796f24aeb7dcd002ee0a800319e069f01eb5362acb5a068567bbca7e73f0e7e7c1ad032a9fdf33a7b5a5148fefe1990bdf07dc422377416b953b31cd1298a13"}