Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions scripts/postinstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,12 @@ function main() {
if (!existsSync(spotifyEnvFile)) {
writeFileSync(spotifyEnvFile,
`# Spotify credentials — get them at https://developer.spotify.com/dashboard\n` +
`# Add http://127.0.0.1:8888/callback as a Redirect URI in your Spotify app\n` +
`SPOTIFY_CLIENT_ID=\n` +
`SPOTIFY_CLIENT_SECRET=\n`,
`SPOTIFY_CLIENT_ID=your_spotify_client_id_here\n` +
`SPOTIFY_CLIENT_SECRET=your_spotify_client_secret_here\n`,
'utf8'
);
console.log(`✓ Spotify credentials template created at ${spotifyEnvFile}`);
console.log(` Fill in your Client ID and Secret, then run: opencli spotify auth`);
console.log(` Edit the file and add your Client ID and Secret, then run: opencli spotify auth`);
}

// ── Browser Bridge setup hint ───────────────────────────────────────
Expand Down
42 changes: 25 additions & 17 deletions src/clis/spotify/spotify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,6 @@ import { createServer } from 'http';
import { homedir } from 'os';
import { join } from 'path';
import { exec } from 'child_process';
import {
assertSpotifyCredentialsConfigured,
getFirstSpotifyTrack,
mapSpotifyTrackResults,
parseDotEnv,
resolveSpotifyCredentials,
} from './utils.js';

// ── Credentials ───────────────────────────────────────────────────────────────
// Set SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET as environment variables,
Expand All @@ -23,13 +16,18 @@ const ENV_FILE = join(homedir(), '.opencli', 'spotify.env');

function loadEnv(): Record<string, string> {
if (!existsSync(ENV_FILE)) return {};
return parseDotEnv(readFileSync(ENV_FILE, 'utf-8'));
return Object.fromEntries(
readFileSync(ENV_FILE, 'utf-8')
.split('\n')
.map(l => l.trim())
.filter(l => l && !l.startsWith('#') && l.includes('='))
.map(l => { const i = l.indexOf('='); return [l.slice(0, i).trim(), l.slice(i + 1).trim()] as [string, string]; })
);
}

const env = loadEnv();
const credentials = resolveSpotifyCredentials(env);
const CLIENT_ID = credentials.clientId;
const CLIENT_SECRET = credentials.clientSecret;
const CLIENT_ID = env.SPOTIFY_CLIENT_ID || process.env.SPOTIFY_CLIENT_ID || '';
const CLIENT_SECRET = env.SPOTIFY_CLIENT_SECRET || process.env.SPOTIFY_CLIENT_SECRET || '';
const REDIRECT_URI = 'http://127.0.0.1:8888/callback';
const SCOPES = [
'user-read-playback-state',
Expand Down Expand Up @@ -109,9 +107,9 @@ async function api(method: string, path: string, body?: unknown): Promise<any> {

async function findTrackUri(query: string): Promise<{ uri: string; name: string; artist: string }> {
const data = await api('GET', `/search?q=${encodeURIComponent(query)}&type=track&limit=1`);
const track = getFirstSpotifyTrack(data);
const track = data?.tracks?.items?.[0];
if (!track) throw new CliError('EMPTY_RESULT', `No track found for: ${query}`);
return track;
return { uri: track.uri, name: track.name, artist: track.artists.map((a: any) => a.name).join(', ') };
}

function openBrowser(url: string): void {
Expand All @@ -130,7 +128,18 @@ cli({
args: [],
columns: ['status'],
func: async () => {
assertSpotifyCredentialsConfigured(credentials, ENV_FILE);
if (!CLIENT_ID || !CLIENT_SECRET) {
const envFile = join(homedir(), '.opencli', 'spotify.env');
throw new CliError(
'CONFIG',
`Missing Spotify credentials.\n\n` +
`1. Go to https://developer.spotify.com/dashboard and create an app\n` +
`2. Copy your Client ID and Client Secret\n` +
`3. Open the file: ${envFile}\n` +
`4. Replace the placeholder values and save\n` +
`5. Run: opencli spotify auth`
);
}
return new Promise((resolve, reject) => {
const server = createServer(async (req, res) => {
try {
Expand Down Expand Up @@ -278,9 +287,8 @@ cli({
func: async (_page, kwargs) => {
const limit = Math.min(50, Math.max(1, Math.round(kwargs.limit)));
const data = await api('GET', `/search?q=${encodeURIComponent(kwargs.query)}&type=track&limit=${limit}`);
const results = mapSpotifyTrackResults(data);
if (!results.length) throw new CliError('EMPTY_RESULT', `No results found for: ${kwargs.query}`);
return results;
if (!data?.tracks?.items) throw new CliError('EMPTY_RESULT', `No results found for: ${kwargs.query}`);
return data.tracks.items.map((t: any) => ({ track: t.name, artist: t.artists.map((a: any) => a.name).join(', '), album: t.album.name, uri: t.uri }));
},
});

Expand Down