Skip to content

Commit 1ae1c82

Browse files
bhutanoclaudejackwener
authored
feat(spotify): add Spotify playback adapter (#560)
* feat(spotify): add Spotify playback adapter Adds a new adapter for controlling Spotify via the official Web API. Uses Strategy.PUBLIC with OAuth2 — no browser session required. Commands: auth, status, play, pause, next, prev, volume, search, queue, shuffle, repeat. Credentials are loaded from ~/.opencli/spotify.env or environment variables. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(spotify): rename index.ts → spotify.ts and fix CliError calls - Renamed src/clis/spotify/index.ts to spotify.ts so the build-manifest picks it up (index.js is intentionally excluded from manifest scanning) - Fixed 4 CliError calls: constructor now requires (code, message, hint?) so each throw now passes an appropriate error code as first argument Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(spotify): fix token refresh corruption, env parse, null guards, validation - refreshAccessToken: check res.ok before parsing; construct Tokens object directly instead of mutating loadTokens() result to avoid writing undefined/NaN on Spotify error responses; preserve existing refresh_token when Spotify omits it from the response - loadEnv: split on first '=' only so values containing '=' are preserved - SCOPES: remove write/library/top scopes not used by any command - status: guard against data.item being null (active device but no track) - volume: validate 0-100 range before API call - auth: check tokenRes.ok on initial token exchange; add server.on('error') handler for EADDRINUSE; add 5-minute timeout with clearTimeout on close * feat(postinstall): auto-create ~/.opencli/spotify.env template on install Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(spotify): guard null progress, podcast items, missing tracks data, corrupted tokens, invalid search limit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(spotify): improve missing credentials error with step-by-step guidance * fix(spotify): harden setup and add docs coverage --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: jackwener <jakevingoo@gmail.com>
1 parent 440c001 commit 1ae1c82

File tree

9 files changed

+589
-0
lines changed

9 files changed

+589
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ git clone git@github.com:jackwener/opencli.git && cd opencli && npm install && n
121121
| **bilibili** | `hot` `search` `history` `feed` `ranking` `download` `comments` `dynamic` `favorite` `following` `me` `subtitle` `user-videos` |
122122
| **twitter** | `trending` `search` `timeline` `bookmarks` `post` `download` `profile` `article` `like` `likes` `notifications` `reply` `reply-dm` `thread` `follow` `unfollow` `followers` `following` `block` `unblock` `bookmark` `unbookmark` `delete` `hide-reply` `accept` |
123123
| **reddit** | `hot` `frontpage` `popular` `search` `subreddit` `user` `user-posts` `user-comments` `read` `save` `saved` `subscribe` `upvote` `upvoted` `comment` |
124+
| **spotify** | `auth` `status` `play` `pause` `next` `prev` `volume` `search` `queue` `shuffle` `repeat` |
124125

125126
66+ adapters in total — **[→ see all supported sites & commands](./docs/adapters/index.md)**
126127

README.zh-CN.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ npm install -g @jackwener/opencli@latest
173173
| **douban** | `search` `top250` `subject` `photos` `download` `marks` `reviews` `movie-hot` `book-hot` | 浏览器 |
174174
| **facebook** | `feed` `profile` `search` `friends` `groups` `events` `notifications` `memories` `add-friend` `join-group` | 浏览器 |
175175
| **google** | `news` `search` `suggest` `trends` | 公开 |
176+
| **spotify** | `auth` `status` `play` `pause` `next` `prev` `volume` `search` `queue` `shuffle` `repeat` | OAuth API |
176177
| **36kr** | `news` `hot` `search` `article` | 公开 / 浏览器 |
177178
| **imdb** | `search` `title` `top` `trending` `person` `reviews` | 公开 |
178179
| **producthunt** | `posts` `today` `hot` `browse` | 公开 / 浏览器 |

docs/.vitepress/config.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export default defineConfig({
105105
{ text: 'Barchart', link: '/adapters/browser/barchart' },
106106
{ text: 'Hugging Face', link: '/adapters/browser/hf' },
107107
{ text: 'Sina Finance', link: '/adapters/browser/sinafinance' },
108+
{ text: 'Spotify', link: '/adapters/browser/spotify' },
108109
{ text: 'Stack Overflow', link: '/adapters/browser/stackoverflow' },
109110
{ text: 'Wikipedia', link: '/adapters/browser/wikipedia' },
110111
{ text: 'Lobsters', link: '/adapters/browser/lobsters' },

docs/adapters/browser/spotify.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Spotify
2+
3+
**Mode**: 🔑 OAuth API · **Domains**: `accounts.spotify.com`, `api.spotify.com`
4+
5+
## Commands
6+
7+
| Command | Description |
8+
|---------|-------------|
9+
| `opencli spotify auth` | Authenticate with Spotify and store tokens locally |
10+
| `opencli spotify status` | Show current playback status |
11+
| `opencli spotify play [query]` | Resume playback or search-and-play a track |
12+
| `opencli spotify pause` | Pause playback |
13+
| `opencli spotify next` | Skip to the next track |
14+
| `opencli spotify prev` | Skip to the previous track |
15+
| `opencli spotify volume <0-100>` | Set playback volume |
16+
| `opencli spotify search <query>` | Search Spotify tracks |
17+
| `opencli spotify queue <query>` | Add a track to the playback queue |
18+
| `opencli spotify shuffle <on|off>` | Toggle shuffle |
19+
| `opencli spotify repeat <off|track|context>` | Set repeat mode |
20+
21+
## Usage Examples
22+
23+
```bash
24+
# First-time setup
25+
opencli spotify auth
26+
27+
# What is playing right now?
28+
opencli spotify status
29+
30+
# Resume playback
31+
opencli spotify play
32+
33+
# Search and immediately play a track
34+
opencli spotify play "Numb Linkin Park"
35+
36+
# Search without playing
37+
opencli spotify search "Daft Punk" --limit 5 -f json
38+
39+
# Queue a track
40+
opencli spotify queue "Get Lucky"
41+
42+
# Playback controls
43+
opencli spotify pause
44+
opencli spotify next
45+
opencli spotify prev
46+
opencli spotify volume 35
47+
opencli spotify shuffle on
48+
opencli spotify repeat track
49+
```
50+
51+
## Setup
52+
53+
1. Create a Spotify app at <https://developer.spotify.com/dashboard>
54+
2. Add `http://127.0.0.1:8888/callback` to the app's Redirect URIs
55+
3. Fill in `SPOTIFY_CLIENT_ID` and `SPOTIFY_CLIENT_SECRET` in `~/.opencli/spotify.env`
56+
4. Run `opencli spotify auth`
57+
58+
## Notes
59+
60+
- Browser Bridge is not required.
61+
- Tokens are stored locally at `~/.opencli/spotify-tokens.json`.
62+
- Playback commands work best when you already have an active Spotify device/session.

docs/adapters/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Run `opencli list` for the live registry.
6262
| **[barchart](/adapters/browser/barchart)** | `quote` `options` `greeks` `flow` | 🌐 Public |
6363
| **[hf](/adapters/browser/hf)** | `top` | 🌐 Public |
6464
| **[sinafinance](/adapters/browser/sinafinance)** | `news` | 🌐 Public |
65+
| **[spotify](/adapters/browser/spotify)** | `auth` `status` `play` `pause` `next` `prev` `volume` `search` `queue` `shuffle` `repeat` | 🔑 OAuth API |
6566
| **[stackoverflow](/adapters/browser/stackoverflow)** | `hot` `search` `bounties` `unanswered` | 🌐 Public |
6667
| **[wikipedia](/adapters/browser/wikipedia)** | `search` `summary` `random` `trending` | 🌐 Public |
6768
| **[lobsters](/adapters/browser/lobsters)** | `hot` `newest` `active` `tag` | 🌐 Public |

scripts/postinstall.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,22 @@ function main() {
196196
}
197197
}
198198

199+
// ── Spotify credentials template ────────────────────────────────────
200+
const opencliDir = join(home, '.opencli');
201+
const spotifyEnvFile = join(opencliDir, 'spotify.env');
202+
ensureDir(opencliDir);
203+
if (!existsSync(spotifyEnvFile)) {
204+
writeFileSync(spotifyEnvFile,
205+
`# Spotify credentials — get them at https://developer.spotify.com/dashboard\n` +
206+
`# Add http://127.0.0.1:8888/callback as a Redirect URI in your Spotify app\n` +
207+
`SPOTIFY_CLIENT_ID=\n` +
208+
`SPOTIFY_CLIENT_SECRET=\n`,
209+
'utf8'
210+
);
211+
console.log(`✓ Spotify credentials template created at ${spotifyEnvFile}`);
212+
console.log(` Fill in your Client ID and Secret, then run: opencli spotify auth`);
213+
}
214+
199215
// ── Browser Bridge setup hint ───────────────────────────────────────
200216
console.log('');
201217
console.log(' \x1b[1mNext step — Browser Bridge setup\x1b[0m');

0 commit comments

Comments
 (0)