@bzenky/spoti 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +137 -0
- package/dist/app.d.ts +14 -0
- package/dist/app.js +122 -0
- package/dist/app.js.map +1 -0
- package/dist/auth/callback.d.ts +7 -0
- package/dist/auth/callback.js +79 -0
- package/dist/auth/callback.js.map +1 -0
- package/dist/auth/config.d.ts +7 -0
- package/dist/auth/config.js +31 -0
- package/dist/auth/config.js.map +1 -0
- package/dist/auth/index.d.ts +8 -0
- package/dist/auth/index.js +5 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/pkce.d.ts +7 -0
- package/dist/auth/pkce.js +15 -0
- package/dist/auth/pkce.js.map +1 -0
- package/dist/auth/spotify-auth-client.d.ts +38 -0
- package/dist/auth/spotify-auth-client.js +152 -0
- package/dist/auth/spotify-auth-client.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +25 -0
- package/dist/cli.js.map +1 -0
- package/dist/services/auth.service.d.ts +36 -0
- package/dist/services/auth.service.js +127 -0
- package/dist/services/auth.service.js.map +1 -0
- package/dist/services/models.d.ts +20 -0
- package/dist/services/models.js +2 -0
- package/dist/services/models.js.map +1 -0
- package/dist/services/player.service.d.ts +12 -0
- package/dist/services/player.service.js +45 -0
- package/dist/services/player.service.js.map +1 -0
- package/dist/services/search.service.d.ts +7 -0
- package/dist/services/search.service.js +28 -0
- package/dist/services/search.service.js.map +1 -0
- package/dist/spotify/client.d.ts +27 -0
- package/dist/spotify/client.js +113 -0
- package/dist/spotify/client.js.map +1 -0
- package/dist/spotify/scopes.d.ts +1 -0
- package/dist/spotify/scopes.js +5 -0
- package/dist/spotify/scopes.js.map +1 -0
- package/dist/spotify/types.d.ts +50 -0
- package/dist/spotify/types.js +2 -0
- package/dist/spotify/types.js.map +1 -0
- package/dist/storage/credentials.d.ts +28 -0
- package/dist/storage/credentials.js +94 -0
- package/dist/storage/credentials.js.map +1 -0
- package/dist/storage/index.d.ts +2 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/ui/output.d.ts +8 -0
- package/dist/ui/output.js +21 -0
- package/dist/ui/output.js.map +1 -0
- package/dist/ui/prompts.d.ts +2 -0
- package/dist/ui/prompts.js +22 -0
- package/dist/ui/prompts.js.map +1 -0
- package/dist/utils/errors.d.ts +26 -0
- package/dist/utils/errors.js +45 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/time.d.ts +2 -0
- package/dist/utils/time.js +13 -0
- package/dist/utils/time.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/spotify/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SPOTIFY_SCOPES: readonly ["user-read-playback-state", "user-modify-playback-state"];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scopes.js","sourceRoot":"","sources":["../../src/spotify/scopes.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,0BAA0B;IAC1B,4BAA4B;CACpB,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface SpotifyImage {
|
|
2
|
+
url: string;
|
|
3
|
+
height: number | null;
|
|
4
|
+
width: number | null;
|
|
5
|
+
}
|
|
6
|
+
export interface SpotifyUserProfile {
|
|
7
|
+
id: string;
|
|
8
|
+
display_name: string | null;
|
|
9
|
+
product?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface SpotifyArtist {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
}
|
|
15
|
+
export interface SpotifyTrack {
|
|
16
|
+
id: string;
|
|
17
|
+
uri: string;
|
|
18
|
+
name: string;
|
|
19
|
+
duration_ms: number;
|
|
20
|
+
artists: SpotifyArtist[];
|
|
21
|
+
album: {
|
|
22
|
+
name: string;
|
|
23
|
+
images: SpotifyImage[];
|
|
24
|
+
};
|
|
25
|
+
external_urls?: {
|
|
26
|
+
spotify?: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export interface SpotifyPlaybackState {
|
|
30
|
+
is_playing: boolean;
|
|
31
|
+
progress_ms: number | null;
|
|
32
|
+
item: SpotifyTrack | null;
|
|
33
|
+
device: {
|
|
34
|
+
id: string | null;
|
|
35
|
+
name: string;
|
|
36
|
+
is_active: boolean;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export interface SpotifySearchResponse {
|
|
40
|
+
tracks: {
|
|
41
|
+
items: SpotifyTrack[];
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export interface SpotifyErrorBody {
|
|
45
|
+
error?: {
|
|
46
|
+
status?: number;
|
|
47
|
+
message?: string;
|
|
48
|
+
} | string;
|
|
49
|
+
error_description?: string;
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/spotify/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const credentialsSchema: z.ZodObject<{
|
|
3
|
+
accessToken: z.ZodString;
|
|
4
|
+
refreshToken: z.ZodString;
|
|
5
|
+
expiresAt: z.ZodNumber;
|
|
6
|
+
}, z.core.$strict>;
|
|
7
|
+
export type Credentials = z.infer<typeof credentialsSchema>;
|
|
8
|
+
export interface CredentialStore {
|
|
9
|
+
read(): Promise<Credentials | null>;
|
|
10
|
+
write(credentials: Credentials): Promise<void>;
|
|
11
|
+
delete(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export interface FileCredentialStoreOptions {
|
|
14
|
+
directory?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function getConfigDirectory(environment?: NodeJS.ProcessEnv, homeDirectory?: string): string;
|
|
17
|
+
export declare function isTokenExpired(credentials: Pick<Credentials, 'expiresAt'>, now?: number, bufferMs?: number): boolean;
|
|
18
|
+
export declare class FileCredentialStore implements CredentialStore {
|
|
19
|
+
readonly path: string;
|
|
20
|
+
private readonly directory;
|
|
21
|
+
constructor(options?: FileCredentialStoreOptions);
|
|
22
|
+
read(): Promise<Credentials | null>;
|
|
23
|
+
write(credentials: Credentials): Promise<void>;
|
|
24
|
+
delete(): Promise<void>;
|
|
25
|
+
private readFromPath;
|
|
26
|
+
private deletePath;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { chmod, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { ConfigurationError, toError } from '../utils/errors.js';
|
|
7
|
+
const credentialsSchema = z.strictObject({
|
|
8
|
+
accessToken: z.string().min(1),
|
|
9
|
+
refreshToken: z.string().min(1),
|
|
10
|
+
expiresAt: z.number().int().nonnegative(),
|
|
11
|
+
});
|
|
12
|
+
export function getConfigDirectory(environment = process.env, homeDirectory = homedir()) {
|
|
13
|
+
const xdgConfigHome = environment.XDG_CONFIG_HOME?.trim();
|
|
14
|
+
return join(xdgConfigHome || join(homeDirectory, '.config'), 'spoti');
|
|
15
|
+
}
|
|
16
|
+
export function isTokenExpired(credentials, now = Date.now(), bufferMs = 60_000) {
|
|
17
|
+
return credentials.expiresAt <= now + bufferMs;
|
|
18
|
+
}
|
|
19
|
+
export class FileCredentialStore {
|
|
20
|
+
path;
|
|
21
|
+
directory;
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
this.directory = options.directory ?? getConfigDirectory();
|
|
24
|
+
this.path = join(this.directory, 'credentials.json');
|
|
25
|
+
}
|
|
26
|
+
async read() {
|
|
27
|
+
return this.readFromPath(this.path);
|
|
28
|
+
}
|
|
29
|
+
async write(credentials) {
|
|
30
|
+
const validated = credentialsSchema.safeParse(credentials);
|
|
31
|
+
if (!validated.success) {
|
|
32
|
+
throw new ConfigurationError('Cannot store invalid Spotify credentials.');
|
|
33
|
+
}
|
|
34
|
+
await mkdir(this.directory, { recursive: true, mode: 0o700 });
|
|
35
|
+
const temporaryPath = join(this.directory, `.credentials-${randomUUID()}.tmp`);
|
|
36
|
+
try {
|
|
37
|
+
await writeFile(temporaryPath, `${JSON.stringify(validated.data, null, 2)}\n`, {
|
|
38
|
+
encoding: 'utf8',
|
|
39
|
+
flag: 'wx',
|
|
40
|
+
mode: 0o600,
|
|
41
|
+
});
|
|
42
|
+
await chmod(temporaryPath, 0o600);
|
|
43
|
+
await rename(temporaryPath, this.path);
|
|
44
|
+
await chmod(this.path, 0o600);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
await unlink(temporaryPath).catch(() => undefined);
|
|
48
|
+
throw new ConfigurationError(`Unable to store Spotify credentials: ${toError(error).message}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async delete() {
|
|
52
|
+
await this.deletePath(this.path);
|
|
53
|
+
}
|
|
54
|
+
async readFromPath(path) {
|
|
55
|
+
let contents;
|
|
56
|
+
try {
|
|
57
|
+
contents = await readFile(path, 'utf8');
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (isFileNotFound(error))
|
|
61
|
+
return null;
|
|
62
|
+
throw new ConfigurationError(`Unable to read stored Spotify credentials: ${toError(error).message}`);
|
|
63
|
+
}
|
|
64
|
+
let parsed;
|
|
65
|
+
try {
|
|
66
|
+
parsed = JSON.parse(contents);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
throw new ConfigurationError('Stored Spotify credentials are not valid JSON.');
|
|
70
|
+
}
|
|
71
|
+
const credentials = credentialsSchema.safeParse(parsed);
|
|
72
|
+
if (!credentials.success) {
|
|
73
|
+
throw new ConfigurationError('Stored Spotify credentials have an invalid format.');
|
|
74
|
+
}
|
|
75
|
+
return credentials.data;
|
|
76
|
+
}
|
|
77
|
+
async deletePath(path) {
|
|
78
|
+
try {
|
|
79
|
+
await unlink(path);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (!isFileNotFound(error)) {
|
|
83
|
+
throw new ConfigurationError(`Unable to delete stored Spotify credentials: ${toError(error).message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function isFileNotFound(error) {
|
|
89
|
+
return (typeof error === 'object' &&
|
|
90
|
+
error !== null &&
|
|
91
|
+
'code' in error &&
|
|
92
|
+
error.code === 'ENOENT');
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/storage/credentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,GACf,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CurrentPlayback, Track } from '../services/models.js';
|
|
2
|
+
export interface Output {
|
|
3
|
+
log(message: string): void;
|
|
4
|
+
error(message: string): void;
|
|
5
|
+
}
|
|
6
|
+
export declare const consoleOutput: Output;
|
|
7
|
+
export declare function formatPlayback(playback: CurrentPlayback): string;
|
|
8
|
+
export declare function formatTrack(track: Track, index?: number): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createProgressBar, formatDuration } from '../utils/time.js';
|
|
2
|
+
export const consoleOutput = {
|
|
3
|
+
log: (message) => console.log(message),
|
|
4
|
+
error: (message) => console.error(message),
|
|
5
|
+
};
|
|
6
|
+
export function formatPlayback(playback) {
|
|
7
|
+
const indicator = playback.isPlaying ? '▶' : '⏸';
|
|
8
|
+
const artists = playback.track.artists.join(', ');
|
|
9
|
+
const progress = Math.min(playback.progressMs, playback.track.durationMs);
|
|
10
|
+
return [
|
|
11
|
+
`${indicator} ${playback.track.name}`,
|
|
12
|
+
`${artists} · ${playback.track.album}`,
|
|
13
|
+
'',
|
|
14
|
+
`${formatDuration(progress)} ${createProgressBar(progress, playback.track.durationMs)} ${formatDuration(playback.track.durationMs)}`,
|
|
15
|
+
].join('\n');
|
|
16
|
+
}
|
|
17
|
+
export function formatTrack(track, index) {
|
|
18
|
+
const prefix = index === undefined ? '' : `${index + 1}. `;
|
|
19
|
+
return `${prefix}${track.name} — ${track.artists.join(', ')} · ${track.album}`;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/ui/output.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOrE,MAAM,CAAC,MAAM,aAAa,GAAW;IACnC,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;IACtC,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;CAC3C,CAAC;AAEF,MAAM,UAAU,cAAc,CAAC,QAAyB;IACtD,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IACjD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC1E,OAAO;QACL,GAAG,SAAS,IAAI,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE;QACrC,GAAG,OAAO,MAAM,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE;QACtC,EAAE;QACF,GAAG,cAAc,CAAC,QAAQ,CAAC,IAAI,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,cAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE;KACrI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAY,EAAE,KAAc;IACtD,MAAM,MAAM,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,IAAI,CAAC;IAC3D,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC,IAAI,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;AACjF,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline/promises';
|
|
2
|
+
import { stdin, stdout } from 'node:process';
|
|
3
|
+
import { formatTrack } from './output.js';
|
|
4
|
+
export async function selectTrack(tracks) {
|
|
5
|
+
if (tracks.length === 0)
|
|
6
|
+
return null;
|
|
7
|
+
if (!stdin.isTTY || !stdout.isTTY)
|
|
8
|
+
return tracks[0] ?? null;
|
|
9
|
+
stdout.write(`${tracks.map((track, index) => formatTrack(track, index)).join('\n')}\n\n`);
|
|
10
|
+
const prompt = createInterface({ input: stdin, output: stdout });
|
|
11
|
+
try {
|
|
12
|
+
const answer = await prompt.question(`Select [1-${tracks.length}] (Enter to cancel): `);
|
|
13
|
+
if (!answer.trim())
|
|
14
|
+
return null;
|
|
15
|
+
const index = Number.parseInt(answer, 10) - 1;
|
|
16
|
+
return tracks[index] ?? null;
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
prompt.close();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/ui/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG7C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAe;IAC/C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,KAAK;QAAE,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IAE5D,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1F,MAAM,MAAM,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACjE,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,aAAa,MAAM,CAAC,MAAM,uBAAuB,CAAC,CAAC;QACxF,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;YAAE,OAAO,IAAI,CAAC;QAChC,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;QAC9C,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC;IAC/B,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare class AppError extends Error {
|
|
2
|
+
readonly exitCode: number;
|
|
3
|
+
constructor(message: string, exitCode?: number);
|
|
4
|
+
}
|
|
5
|
+
export declare class ConfigurationError extends AppError {
|
|
6
|
+
}
|
|
7
|
+
export declare class AuthenticationRequiredError extends AppError {
|
|
8
|
+
constructor(message?: string);
|
|
9
|
+
}
|
|
10
|
+
export declare class AuthorizationDeniedError extends AppError {
|
|
11
|
+
}
|
|
12
|
+
export declare class NoActiveDeviceError extends AppError {
|
|
13
|
+
constructor();
|
|
14
|
+
}
|
|
15
|
+
export declare class PremiumRequiredError extends AppError {
|
|
16
|
+
constructor();
|
|
17
|
+
}
|
|
18
|
+
export declare class RateLimitedError extends AppError {
|
|
19
|
+
readonly retryAfterSeconds: number;
|
|
20
|
+
constructor(retryAfterSeconds: number);
|
|
21
|
+
}
|
|
22
|
+
export declare class SpotifyApiError extends AppError {
|
|
23
|
+
readonly status: number;
|
|
24
|
+
constructor(message: string, status: number);
|
|
25
|
+
}
|
|
26
|
+
export declare function toError(value: unknown): Error;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export class AppError extends Error {
|
|
2
|
+
exitCode;
|
|
3
|
+
constructor(message, exitCode = 1) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.exitCode = exitCode;
|
|
6
|
+
this.name = new.target.name;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class ConfigurationError extends AppError {
|
|
10
|
+
}
|
|
11
|
+
export class AuthenticationRequiredError extends AppError {
|
|
12
|
+
constructor(message = 'You are not logged in.\n\nRun: spoti login') {
|
|
13
|
+
super(message);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export class AuthorizationDeniedError extends AppError {
|
|
17
|
+
}
|
|
18
|
+
export class NoActiveDeviceError extends AppError {
|
|
19
|
+
constructor() {
|
|
20
|
+
super('No active Spotify device found.\n\nOpen Spotify on one of your devices and try again.');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class PremiumRequiredError extends AppError {
|
|
24
|
+
constructor() {
|
|
25
|
+
super('Spotify Premium is required for playback control.');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class RateLimitedError extends AppError {
|
|
29
|
+
retryAfterSeconds;
|
|
30
|
+
constructor(retryAfterSeconds) {
|
|
31
|
+
super(`Spotify rate limit reached. Try again in ${retryAfterSeconds} seconds.`);
|
|
32
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export class SpotifyApiError extends AppError {
|
|
36
|
+
status;
|
|
37
|
+
constructor(message, status) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.status = status;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function toError(value) {
|
|
43
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,QAAS,SAAQ,KAAK;IAGf;IAFlB,YACE,OAAe,EACC,WAAW,CAAC;QAE5B,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,aAAQ,GAAR,QAAQ,CAAI;QAG5B,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;IAC9B,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,QAAQ;CAAG;AAEnD,MAAM,OAAO,2BAA4B,SAAQ,QAAQ;IACvD,YAAY,OAAO,GAAG,4CAA4C;QAChE,KAAK,CAAC,OAAO,CAAC,CAAC;IACjB,CAAC;CACF;AAED,MAAM,OAAO,wBAAyB,SAAQ,QAAQ;CAAG;AAEzD,MAAM,OAAO,mBAAoB,SAAQ,QAAQ;IAC/C;QACE,KAAK,CACH,uFAAuF,CACxF,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,oBAAqB,SAAQ,QAAQ;IAChD;QACE,KAAK,CAAC,mDAAmD,CAAC,CAAC;IAC7D,CAAC;CACF;AAED,MAAM,OAAO,gBAAiB,SAAQ,QAAQ;IAChB;IAA5B,YAA4B,iBAAyB;QACnD,KAAK,CAAC,4CAA4C,iBAAiB,WAAW,CAAC,CAAC;QADtD,sBAAiB,GAAjB,iBAAiB,CAAQ;IAErD,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,QAAQ;IAGzB;IAFlB,YACE,OAAe,EACC,MAAc;QAE9B,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,WAAM,GAAN,MAAM,CAAQ;IAGhC,CAAC;CACF;AAED,MAAM,UAAU,OAAO,CAAC,KAAc;IACpC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function formatDuration(milliseconds) {
|
|
2
|
+
const totalSeconds = Math.max(0, Math.floor(milliseconds / 1_000));
|
|
3
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
4
|
+
const seconds = totalSeconds % 60;
|
|
5
|
+
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
|
6
|
+
}
|
|
7
|
+
export function createProgressBar(progressMs, durationMs, width = 24) {
|
|
8
|
+
const safeWidth = Math.max(1, Math.floor(width));
|
|
9
|
+
const ratio = durationMs > 0 ? Math.min(1, Math.max(0, progressMs / durationMs)) : 0;
|
|
10
|
+
const completed = Math.round(ratio * safeWidth);
|
|
11
|
+
return `${'━'.repeat(completed)}${'─'.repeat(safeWidth - completed)}`;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=time.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time.js","sourceRoot":"","sources":["../../src/utils/time.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,cAAc,CAAC,YAAoB;IACjD,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,CAAC,CAAC;IACnE,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,EAAE,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,YAAY,GAAG,EAAE,CAAC;IAClC,OAAO,GAAG,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,UAAkB,EAClB,UAAkB,EAClB,KAAK,GAAG,EAAE;IAEV,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrF,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IAChD,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,GAAG,SAAS,CAAC,EAAE,CAAC;AACxE,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bzenky/spoti",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A local-first command-line client for controlling Spotify",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"spoti": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=20"
|
|
14
|
+
},
|
|
15
|
+
"packageManager": "npm@10.9.8",
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/bzenky/spoti.git"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/bzenky/spoti#readme",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/bzenky/spoti/issues"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"dev": "tsx src/cli.ts",
|
|
29
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
30
|
+
"build": "npm run clean && tsc && node -e \"require('node:fs').chmodSync('dist/cli.js', 0o755)\"",
|
|
31
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json",
|
|
32
|
+
"lint": "eslint .",
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"test:watch": "vitest",
|
|
35
|
+
"check": "npm run typecheck && npm run lint && npm test",
|
|
36
|
+
"verify": "npm run check && npm run build",
|
|
37
|
+
"prepack": "npm run build",
|
|
38
|
+
"prepublishOnly": "npm run check"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"spotify",
|
|
42
|
+
"cli",
|
|
43
|
+
"spotify-connect"
|
|
44
|
+
],
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"commander": "^15.0.0",
|
|
48
|
+
"open": "^11.0.2",
|
|
49
|
+
"zod": "^4.5.4"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@eslint/js": "^10.0.1",
|
|
53
|
+
"@types/node": "^26.4.1",
|
|
54
|
+
"eslint": "^10.10.0",
|
|
55
|
+
"tsx": "^4.23.13",
|
|
56
|
+
"typescript": "^6.0.3",
|
|
57
|
+
"typescript-eslint": "^8.69.0",
|
|
58
|
+
"vitest": "^5.0.0"
|
|
59
|
+
}
|
|
60
|
+
}
|