@kud/qobuz 0.2.0 → 0.3.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/README.md +1 -0
- package/dist/index.d.ts +17 -1
- package/dist/index.js +26 -1
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
- **Pluggable credential store** — ships in-memory and macOS Keychain implementations; bring your own.
|
|
17
17
|
- **Typed resources** — search, albums, artists, tracks, playlists, favourites — clean camelCase domain types mapped from the raw API.
|
|
18
18
|
- **Deep links** — build `open.qobuz.com` URLs to open anything in the Qobuz app.
|
|
19
|
+
- **Now playing** — read the track the Qobuz desktop app is currently playing (macOS), bypassing the OS now-playing system Qobuz never registers with.
|
|
19
20
|
- **ESM + types, zero runtime deps** — tree-shakeable, fully typed, ships nothing extra.
|
|
20
21
|
|
|
21
22
|
## Install
|
package/dist/index.d.ts
CHANGED
|
@@ -84,6 +84,21 @@ type UserFavourites = {
|
|
|
84
84
|
tracks: Track[];
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
+
type NowPlayingOptions = {
|
|
88
|
+
/** Override the Qobuz desktop player-state file (defaults to the macOS path). */
|
|
89
|
+
path?: string;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* The Qobuz desktop app (Electron) persists its play queue here. macOS does not
|
|
93
|
+
* expose Qobuz to its now-playing system, so this local file is the source of truth.
|
|
94
|
+
*/
|
|
95
|
+
declare const defaultPlayerStatePath: () => string;
|
|
96
|
+
/**
|
|
97
|
+
* The id of the track Qobuz is currently playing, read from the desktop app's
|
|
98
|
+
* local state. Returns `undefined` if nothing is playing or the file is absent.
|
|
99
|
+
*/
|
|
100
|
+
declare const readNowPlayingTrackId: (options?: NowPlayingOptions) => Promise<number | undefined>;
|
|
101
|
+
|
|
87
102
|
type StoredCredentials = {
|
|
88
103
|
appId: string;
|
|
89
104
|
token: string;
|
|
@@ -134,6 +149,7 @@ declare const createQobuzClient: ({ store, fetchImpl, }: QobuzClientConfig) => P
|
|
|
134
149
|
removeTracks: (playlistId: number, playlistTrackIds: number[]) => Promise<Playlist>;
|
|
135
150
|
};
|
|
136
151
|
deepLink: DeepLink;
|
|
152
|
+
nowPlaying: (options?: NowPlayingOptions) => Promise<Track | undefined>;
|
|
137
153
|
signOut: () => Promise<void>;
|
|
138
154
|
}>;
|
|
139
155
|
type QobuzClient = Awaited<ReturnType<typeof createQobuzClient>>;
|
|
@@ -167,4 +183,4 @@ type QobuzError = Error & {
|
|
|
167
183
|
status?: number;
|
|
168
184
|
};
|
|
169
185
|
|
|
170
|
-
export { type Album, type AppCredentials, type Artist, type ConnectConfig, type CreatePlaylistParams, type CredentialStore, type DeepLink, type DeepLinkBase, type FavouriteType, type KeychainStoreOptions, type PageOptions, type Playlist, QOBUZ_BASE_URL, type QobuzClient, type QobuzClientConfig, type QobuzError, type QobuzErrorKind, type QobuzImage, type SearchResults, type StoredCredentials, type Track, type Transport, type TransportConfig, type UserFavourites, type ValidateConfig, connect, createDeepLink, createKeychainStore, createMemoryStore, createQobuzClient, createTransport, fetchAppId, validateCredentials };
|
|
186
|
+
export { type Album, type AppCredentials, type Artist, type ConnectConfig, type CreatePlaylistParams, type CredentialStore, type DeepLink, type DeepLinkBase, type FavouriteType, type KeychainStoreOptions, type NowPlayingOptions, type PageOptions, type Playlist, QOBUZ_BASE_URL, type QobuzClient, type QobuzClientConfig, type QobuzError, type QobuzErrorKind, type QobuzImage, type SearchResults, type StoredCredentials, type Track, type Transport, type TransportConfig, type UserFavourites, type ValidateConfig, connect, createDeepLink, createKeychainStore, createMemoryStore, createQobuzClient, createTransport, defaultPlayerStatePath, fetchAppId, readNowPlayingTrackId, validateCredentials };
|
package/dist/index.js
CHANGED
|
@@ -201,6 +201,24 @@ var createTracksResource = (transport) => ({
|
|
|
201
201
|
get: async (trackId) => mapTrack(await transport.get("track/get", { track_id: trackId }))
|
|
202
202
|
});
|
|
203
203
|
|
|
204
|
+
// src/now-playing.ts
|
|
205
|
+
import { readFile } from "fs/promises";
|
|
206
|
+
import { homedir } from "os";
|
|
207
|
+
import { join } from "path";
|
|
208
|
+
var defaultPlayerStatePath = () => join(homedir(), "Library/Application Support/Qobuz/player-0.json");
|
|
209
|
+
var readNowPlayingTrackId = async (options = {}) => {
|
|
210
|
+
try {
|
|
211
|
+
const state = JSON.parse(
|
|
212
|
+
await readFile(options.path ?? defaultPlayerStatePath(), "utf8")
|
|
213
|
+
);
|
|
214
|
+
const queue = state?.playqueue?.data;
|
|
215
|
+
const trackId = queue?.items?.[queue?.currentIndex]?.trackId;
|
|
216
|
+
return typeof trackId === "number" ? trackId : void 0;
|
|
217
|
+
} catch {
|
|
218
|
+
return void 0;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
204
222
|
// src/client.ts
|
|
205
223
|
var createQobuzClient = async ({
|
|
206
224
|
store,
|
|
@@ -214,15 +232,20 @@ var createQobuzClient = async ({
|
|
|
214
232
|
token: credentials.token,
|
|
215
233
|
fetchImpl
|
|
216
234
|
});
|
|
235
|
+
const tracks = createTracksResource(transport);
|
|
217
236
|
return {
|
|
218
237
|
appId: credentials.appId,
|
|
219
238
|
search: createSearchResource(transport),
|
|
220
239
|
albums: createAlbumsResource(transport),
|
|
221
240
|
artists: createArtistsResource(transport),
|
|
222
|
-
tracks
|
|
241
|
+
tracks,
|
|
223
242
|
favourites: createFavouritesResource(transport),
|
|
224
243
|
playlists: createPlaylistsResource(transport),
|
|
225
244
|
deepLink: createDeepLink(),
|
|
245
|
+
nowPlaying: async (options) => {
|
|
246
|
+
const trackId = await readNowPlayingTrackId(options);
|
|
247
|
+
return trackId === void 0 ? void 0 : tracks.get(trackId);
|
|
248
|
+
},
|
|
226
249
|
signOut: () => store.clear()
|
|
227
250
|
};
|
|
228
251
|
};
|
|
@@ -360,7 +383,9 @@ export {
|
|
|
360
383
|
createMemoryStore,
|
|
361
384
|
createQobuzClient,
|
|
362
385
|
createTransport,
|
|
386
|
+
defaultPlayerStatePath,
|
|
363
387
|
fetchAppId,
|
|
388
|
+
readNowPlayingTrackId,
|
|
364
389
|
validateCredentials
|
|
365
390
|
};
|
|
366
391
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/deep-link.ts","../src/http/errors.ts","../src/http/transport.ts","../src/mappers.ts","../src/resources/albums.ts","../src/resources/artists.ts","../src/resources/favourites.ts","../src/resources/playlists.ts","../src/resources/search.ts","../src/resources/tracks.ts","../src/client.ts","../src/auth/bootstrap.ts","../src/auth/validate.ts","../src/auth/connect.ts","../src/auth/credential-store.ts"],"sourcesContent":["export type DeepLinkBase = \"open\" | \"play\"\n\nexport type DeepLink = {\n album: (albumId: string) => string\n track: (trackId: number) => string\n playlist: (playlistId: number) => string\n artist: (artistId: number) => string\n}\n\nexport const createDeepLink = (base: DeepLinkBase = \"open\"): DeepLink => {\n const root = `https://${base}.qobuz.com`\n return {\n album: (albumId) => `${root}/album/${albumId}`,\n track: (trackId) => `${root}/track/${trackId}`,\n playlist: (playlistId) => `${root}/playlist/${playlistId}`,\n artist: (artistId) => `${root}/artist/${artistId}`,\n }\n}\n","export type QobuzErrorKind = \"http\" | \"auth\" | \"bootstrap\"\n\nexport type QobuzError = Error & {\n kind: QobuzErrorKind\n status?: number\n}\n\nconst createError = (\n kind: QobuzErrorKind,\n message: string,\n status?: number,\n): QobuzError =>\n Object.assign(new Error(message), { name: \"QobuzError\", kind, status })\n\nexport const httpError = (message: string, status: number) =>\n createError(\"http\", message, status)\nexport const authError = (message: string) => createError(\"auth\", message)\nexport const bootstrapError = (message: string) =>\n createError(\"bootstrap\", message)\n","import { httpError } from \"./errors.js\"\n\nexport const QOBUZ_BASE_URL = \"https://www.qobuz.com/api.json/0.2\"\n\nexport const USER_AGENT =\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\"\n\nexport type QueryParams = Record<string, string | number | undefined>\n\nexport type Transport = {\n get: <T = unknown>(path: string, query?: QueryParams) => Promise<T>\n}\n\nexport type TransportConfig = {\n appId: string\n token?: string\n baseUrl?: string\n fetchImpl?: typeof fetch\n}\n\nconst toQuery = (params: QueryParams) =>\n new URLSearchParams(\n Object.entries(params)\n .filter(([, value]) => value !== undefined)\n .map(([key, value]) => [key, String(value)]),\n )\n\nexport const createTransport = ({\n appId,\n token,\n baseUrl = QOBUZ_BASE_URL,\n fetchImpl = fetch,\n}: TransportConfig): Transport => {\n const headers = {\n \"User-Agent\": USER_AGENT,\n \"X-App-Id\": appId,\n ...(token ? { \"X-User-Auth-Token\": token } : {}),\n }\n\n const get = async <T>(path: string, query: QueryParams = {}): Promise<T> => {\n const res = await fetchImpl(`${baseUrl}/${path}?${toQuery(query)}`, {\n headers,\n })\n if (!res.ok) {\n const body = await res.text().catch(() => \"\")\n throw httpError(\n `${path} failed (${res.status}): ${body.slice(0, 200)}`,\n res.status,\n )\n }\n return (await res.json()) as T\n }\n\n return { get }\n}\n","import type {\n Album,\n Artist,\n Playlist,\n QobuzImage,\n Track,\n} from \"./types/domain.js\"\n\ntype Raw = any\n\nconst mapImage = (raw: Raw | undefined): QobuzImage | undefined =>\n raw\n ? { thumbnail: raw.thumbnail, small: raw.small, large: raw.large }\n : undefined\n\nexport const mapArtist = (raw: Raw): Artist => ({\n id: raw.id,\n name: raw.name,\n picture: raw.picture ?? raw.image?.medium,\n albumsCount: raw.albums_count,\n})\n\nexport const mapAlbum = (raw: Raw): Album => ({\n id: String(raw.id),\n title: raw.title,\n artist: raw.artist ? mapArtist(raw.artist) : undefined,\n tracksCount: raw.tracks_count,\n releaseDate: raw.release_date_original ?? raw.released_at,\n duration: raw.duration,\n image: mapImage(raw.image),\n genre: raw.genre?.name,\n hires: raw.hires,\n})\n\nexport const mapTrack = (raw: Raw): Track => ({\n id: raw.id,\n title: raw.title,\n album: raw.album ? mapAlbum(raw.album) : undefined,\n artist: raw.performer\n ? mapArtist(raw.performer)\n : raw.artist\n ? mapArtist(raw.artist)\n : undefined,\n trackNumber: raw.track_number,\n duration: raw.duration,\n hires: raw.hires,\n})\n\nexport const mapPlaylist = (raw: Raw): Playlist => ({\n id: raw.id,\n name: raw.name,\n description: raw.description,\n tracksCount: raw.tracks_count,\n isPublic: raw.is_public,\n owner: raw.owner?.name,\n duration: raw.duration,\n})\n","import { mapAlbum } from \"../mappers.js\"\nimport type { Transport } from \"../http/transport.js\"\nimport type { Album } from \"../types/domain.js\"\n\nexport const createAlbumsResource = (transport: Transport) => ({\n get: async (albumId: string): Promise<Album> =>\n mapAlbum(await transport.get(\"album/get\", { album_id: albumId })),\n})\n","import { mapArtist } from \"../mappers.js\"\nimport type { Transport } from \"../http/transport.js\"\nimport type { Artist, PageOptions } from \"../types/domain.js\"\n\ntype RawSimilar = { artists?: { items?: unknown[] } }\n\nexport const createArtistsResource = (transport: Transport) => ({\n get: async (artistId: number): Promise<Artist> =>\n mapArtist(await transport.get(\"artist/get\", { artist_id: artistId })),\n getSimilar: async (\n artistId: number,\n options: PageOptions = {},\n ): Promise<Artist[]> => {\n const raw = await transport.get<RawSimilar>(\"artist/getSimilarArtists\", {\n artist_id: artistId,\n limit: options.limit ?? 20,\n offset: options.offset ?? 0,\n })\n return (raw.artists?.items ?? []).map(mapArtist)\n },\n})\n","import { mapAlbum, mapArtist, mapTrack } from \"../mappers.js\"\nimport type { Transport } from \"../http/transport.js\"\nimport type {\n Album,\n Artist,\n FavouriteType,\n PageOptions,\n Track,\n} from \"../types/domain.js\"\n\nexport type UserFavourites = {\n albums: Album[]\n artists: Artist[]\n tracks: Track[]\n}\n\ntype RawFavourites = {\n albums?: { items?: unknown[] }\n artists?: { items?: unknown[] }\n tracks?: { items?: unknown[] }\n}\n\nconst favouriteIdParam: Record<FavouriteType, string> = {\n albums: \"album_ids\",\n artists: \"artist_ids\",\n tracks: \"track_ids\",\n}\n\nexport const createFavouritesResource = (transport: Transport) => ({\n list: async (\n type: FavouriteType,\n options: PageOptions = {},\n ): Promise<UserFavourites> => {\n const raw = await transport.get<RawFavourites>(\n \"favorite/getUserFavorites\",\n {\n type,\n limit: options.limit ?? 50,\n offset: options.offset ?? 0,\n },\n )\n return {\n albums: (raw.albums?.items ?? []).map(mapAlbum),\n artists: (raw.artists?.items ?? []).map(mapArtist),\n tracks: (raw.tracks?.items ?? []).map(mapTrack),\n }\n },\n add: async (type: FavouriteType, id: string): Promise<void> => {\n await transport.get(\"favorite/create\", { [favouriteIdParam[type]]: id })\n },\n remove: async (type: FavouriteType, id: string): Promise<void> => {\n await transport.get(\"favorite/delete\", { [favouriteIdParam[type]]: id })\n },\n})\n","import { mapPlaylist } from \"../mappers.js\"\nimport type { Transport } from \"../http/transport.js\"\nimport type { PageOptions, Playlist } from \"../types/domain.js\"\n\ntype RawPlaylists = { playlists?: { items?: unknown[] } }\n\nexport type CreatePlaylistParams = {\n name: string\n description?: string\n isPublic?: boolean\n}\n\nexport const createPlaylistsResource = (transport: Transport) => ({\n listForUser: async (options: PageOptions = {}): Promise<Playlist[]> => {\n const raw = await transport.get<RawPlaylists>(\"playlist/getUserPlaylists\", {\n limit: options.limit ?? 50,\n offset: options.offset ?? 0,\n })\n return (raw.playlists?.items ?? []).map(mapPlaylist)\n },\n get: async (\n playlistId: number,\n options: PageOptions = {},\n ): Promise<Playlist> =>\n mapPlaylist(\n await transport.get(\"playlist/get\", {\n playlist_id: playlistId,\n extra: \"tracks\",\n limit: options.limit ?? 500,\n offset: options.offset ?? 0,\n }),\n ),\n create: async ({\n name,\n description,\n isPublic,\n }: CreatePlaylistParams): Promise<Playlist> =>\n mapPlaylist(\n await transport.get(\"playlist/create\", {\n name,\n description,\n is_public: isPublic ? 1 : 0,\n }),\n ),\n remove: async (playlistId: number): Promise<void> => {\n await transport.get(\"playlist/delete\", { playlist_id: playlistId })\n },\n addTracks: async (\n playlistId: number,\n trackIds: number[],\n ): Promise<Playlist> =>\n mapPlaylist(\n await transport.get(\"playlist/addTracks\", {\n playlist_id: playlistId,\n track_ids: trackIds.join(\",\"),\n }),\n ),\n removeTracks: async (\n playlistId: number,\n playlistTrackIds: number[],\n ): Promise<Playlist> =>\n mapPlaylist(\n await transport.get(\"playlist/deleteTracks\", {\n playlist_id: playlistId,\n playlist_track_ids: playlistTrackIds.join(\",\"),\n }),\n ),\n})\n","import { mapAlbum, mapArtist, mapTrack } from \"../mappers.js\"\nimport type { Transport } from \"../http/transport.js\"\nimport type { PageOptions, SearchResults } from \"../types/domain.js\"\n\ntype RawSearch = {\n albums?: { items?: unknown[] }\n tracks?: { items?: unknown[] }\n artists?: { items?: unknown[] }\n}\n\nexport const createSearchResource = (transport: Transport) => ({\n search: async (\n query: string,\n options: PageOptions = {},\n ): Promise<SearchResults> => {\n const raw = await transport.get<RawSearch>(\"catalog/search\", {\n query,\n limit: options.limit ?? 20,\n offset: options.offset ?? 0,\n })\n return {\n query,\n albums: (raw.albums?.items ?? []).map(mapAlbum),\n tracks: (raw.tracks?.items ?? []).map(mapTrack),\n artists: (raw.artists?.items ?? []).map(mapArtist),\n }\n },\n})\n","import { mapTrack } from \"../mappers.js\"\nimport type { Transport } from \"../http/transport.js\"\nimport type { Track } from \"../types/domain.js\"\n\nexport const createTracksResource = (transport: Transport) => ({\n get: async (trackId: number): Promise<Track> =>\n mapTrack(await transport.get(\"track/get\", { track_id: trackId })),\n})\n","import { createDeepLink } from \"./deep-link.js\"\nimport { authError } from \"./http/errors.js\"\nimport { createTransport } from \"./http/transport.js\"\nimport { createAlbumsResource } from \"./resources/albums.js\"\nimport { createArtistsResource } from \"./resources/artists.js\"\nimport { createFavouritesResource } from \"./resources/favourites.js\"\nimport { createPlaylistsResource } from \"./resources/playlists.js\"\nimport { createSearchResource } from \"./resources/search.js\"\nimport { createTracksResource } from \"./resources/tracks.js\"\nimport type { CredentialStore } from \"./auth/credential-store.js\"\n\nexport type QobuzClientConfig = {\n store: CredentialStore\n fetchImpl?: typeof fetch\n}\n\nexport const createQobuzClient = async ({\n store,\n fetchImpl,\n}: QobuzClientConfig) => {\n const credentials = await store.load()\n if (!credentials)\n throw authError(\"not connected — store a valid app_id + token first\")\n\n const transport = createTransport({\n appId: credentials.appId,\n token: credentials.token,\n fetchImpl,\n })\n\n return {\n appId: credentials.appId,\n search: createSearchResource(transport),\n albums: createAlbumsResource(transport),\n artists: createArtistsResource(transport),\n tracks: createTracksResource(transport),\n favourites: createFavouritesResource(transport),\n playlists: createPlaylistsResource(transport),\n deepLink: createDeepLink(),\n signOut: () => store.clear(),\n }\n}\n\nexport type QobuzClient = Awaited<ReturnType<typeof createQobuzClient>>\n","import { bootstrapError } from \"../http/errors.js\"\nimport { USER_AGENT } from \"../http/transport.js\"\n\nconst PLAY_URL = \"https://play.qobuz.com\"\n\nexport type AppCredentials = {\n appId: string\n bundlePath: string\n}\n\nexport const fetchAppId = async (\n options: { fetchImpl?: typeof fetch } = {},\n): Promise<AppCredentials> => {\n const fetchImpl = options.fetchImpl ?? fetch\n\n const getText = async (url: string) => {\n const res = await fetchImpl(url, { headers: { \"User-Agent\": USER_AGENT } })\n if (!res.ok) throw bootstrapError(`GET ${url} failed (${res.status})`)\n return res.text()\n }\n\n const loginPage = await getText(`${PLAY_URL}/login`)\n const bundlePath = loginPage.match(\n /<script src=\"(\\/resources\\/\\d+\\.\\d+\\.\\d+-[a-z]\\d{3}\\/bundle\\.js)\"><\\/script>/,\n )?.[1]\n if (!bundlePath)\n throw bootstrapError(\n \"could not find the bundle.js URL in the Qobuz login page\",\n )\n\n const bundle = await getText(`${PLAY_URL}${bundlePath}`)\n const appId = bundle.match(/production:\\{api:\\{appId:\"(\\d{9})\"/)?.[1]\n if (!appId)\n throw bootstrapError(\"could not extract app_id from the Qobuz web bundle\")\n\n return { appId, bundlePath }\n}\n","import { authError, type QobuzError } from \"../http/errors.js\"\nimport { createTransport } from \"../http/transport.js\"\n\nexport type ValidateConfig = {\n appId: string\n token: string\n fetchImpl?: typeof fetch\n}\n\nexport const validateCredentials = async ({\n appId,\n token,\n fetchImpl,\n}: ValidateConfig): Promise<void> => {\n const transport = createTransport({ appId, token, fetchImpl })\n try {\n await transport.get(\"favorite/getUserFavorites\", {\n type: \"albums\",\n limit: 1,\n })\n } catch (error) {\n const qobuzError = error as QobuzError\n if (qobuzError.status === 401) {\n throw authError(\n \"Qobuz rejected the credentials (401) — the token may be expired or the app_id doesn't match\",\n )\n }\n throw error\n }\n}\n","import { createQobuzClient, type QobuzClient } from \"../client.js\"\nimport { fetchAppId } from \"./bootstrap.js\"\nimport { validateCredentials } from \"./validate.js\"\nimport type { CredentialStore } from \"./credential-store.js\"\n\nexport type ConnectConfig = {\n token: string\n appId?: string\n store: CredentialStore\n fetchImpl?: typeof fetch\n}\n\nexport const connect = async ({\n token,\n appId,\n store,\n fetchImpl,\n}: ConnectConfig): Promise<QobuzClient> => {\n const resolvedAppId = appId ?? (await fetchAppId({ fetchImpl })).appId\n await validateCredentials({ appId: resolvedAppId, token, fetchImpl })\n await store.save({ appId: resolvedAppId, token })\n return createQobuzClient({ store, fetchImpl })\n}\n","import { execFile } from \"node:child_process\"\nimport { promisify } from \"node:util\"\n\nconst exec = promisify(execFile)\n\nexport type StoredCredentials = {\n appId: string\n token: string\n savedAt?: string\n}\n\nexport type CredentialStore = {\n load: () => Promise<StoredCredentials | undefined>\n save: (credentials: StoredCredentials) => Promise<void>\n clear: () => Promise<void>\n}\n\nexport const createMemoryStore = (\n seed?: StoredCredentials,\n): CredentialStore => {\n let current = seed\n return {\n load: async () => current,\n save: async (credentials) => {\n current = credentials\n },\n clear: async () => {\n current = undefined\n },\n }\n}\n\nexport type KeychainStoreOptions = {\n service?: string\n account?: string\n}\n\nexport const createKeychainStore = (\n options: KeychainStoreOptions = {},\n): CredentialStore => {\n const service = options.service ?? \"qobuz\"\n const account = options.account ?? \"credentials\"\n\n return {\n load: async () => {\n try {\n const { stdout } = await exec(\"security\", [\n \"find-generic-password\",\n \"-s\",\n service,\n \"-a\",\n account,\n \"-w\",\n ])\n return JSON.parse(stdout.trim()) as StoredCredentials\n } catch {\n return undefined\n }\n },\n save: async (credentials) => {\n const value = JSON.stringify({\n ...credentials,\n savedAt: credentials.savedAt ?? new Date().toISOString(),\n })\n await exec(\"security\", [\n \"add-generic-password\",\n \"-U\",\n \"-s\",\n service,\n \"-a\",\n account,\n \"-w\",\n value,\n ])\n },\n clear: async () => {\n try {\n await exec(\"security\", [\n \"delete-generic-password\",\n \"-s\",\n service,\n \"-a\",\n account,\n ])\n } catch {\n // nothing stored — nothing to clear\n }\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/deep-link.ts","../src/http/errors.ts","../src/http/transport.ts","../src/mappers.ts","../src/resources/albums.ts","../src/resources/artists.ts","../src/resources/favourites.ts","../src/resources/playlists.ts","../src/resources/search.ts","../src/resources/tracks.ts","../src/now-playing.ts","../src/client.ts","../src/auth/bootstrap.ts","../src/auth/validate.ts","../src/auth/connect.ts","../src/auth/credential-store.ts"],"sourcesContent":["export type DeepLinkBase = \"open\" | \"play\"\n\nexport type DeepLink = {\n album: (albumId: string) => string\n track: (trackId: number) => string\n playlist: (playlistId: number) => string\n artist: (artistId: number) => string\n}\n\nexport const createDeepLink = (base: DeepLinkBase = \"open\"): DeepLink => {\n const root = `https://${base}.qobuz.com`\n return {\n album: (albumId) => `${root}/album/${albumId}`,\n track: (trackId) => `${root}/track/${trackId}`,\n playlist: (playlistId) => `${root}/playlist/${playlistId}`,\n artist: (artistId) => `${root}/artist/${artistId}`,\n }\n}\n","export type QobuzErrorKind = \"http\" | \"auth\" | \"bootstrap\"\n\nexport type QobuzError = Error & {\n kind: QobuzErrorKind\n status?: number\n}\n\nconst createError = (\n kind: QobuzErrorKind,\n message: string,\n status?: number,\n): QobuzError =>\n Object.assign(new Error(message), { name: \"QobuzError\", kind, status })\n\nexport const httpError = (message: string, status: number) =>\n createError(\"http\", message, status)\nexport const authError = (message: string) => createError(\"auth\", message)\nexport const bootstrapError = (message: string) =>\n createError(\"bootstrap\", message)\n","import { httpError } from \"./errors.js\"\n\nexport const QOBUZ_BASE_URL = \"https://www.qobuz.com/api.json/0.2\"\n\nexport const USER_AGENT =\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\"\n\nexport type QueryParams = Record<string, string | number | undefined>\n\nexport type Transport = {\n get: <T = unknown>(path: string, query?: QueryParams) => Promise<T>\n}\n\nexport type TransportConfig = {\n appId: string\n token?: string\n baseUrl?: string\n fetchImpl?: typeof fetch\n}\n\nconst toQuery = (params: QueryParams) =>\n new URLSearchParams(\n Object.entries(params)\n .filter(([, value]) => value !== undefined)\n .map(([key, value]) => [key, String(value)]),\n )\n\nexport const createTransport = ({\n appId,\n token,\n baseUrl = QOBUZ_BASE_URL,\n fetchImpl = fetch,\n}: TransportConfig): Transport => {\n const headers = {\n \"User-Agent\": USER_AGENT,\n \"X-App-Id\": appId,\n ...(token ? { \"X-User-Auth-Token\": token } : {}),\n }\n\n const get = async <T>(path: string, query: QueryParams = {}): Promise<T> => {\n const res = await fetchImpl(`${baseUrl}/${path}?${toQuery(query)}`, {\n headers,\n })\n if (!res.ok) {\n const body = await res.text().catch(() => \"\")\n throw httpError(\n `${path} failed (${res.status}): ${body.slice(0, 200)}`,\n res.status,\n )\n }\n return (await res.json()) as T\n }\n\n return { get }\n}\n","import type {\n Album,\n Artist,\n Playlist,\n QobuzImage,\n Track,\n} from \"./types/domain.js\"\n\ntype Raw = any\n\nconst mapImage = (raw: Raw | undefined): QobuzImage | undefined =>\n raw\n ? { thumbnail: raw.thumbnail, small: raw.small, large: raw.large }\n : undefined\n\nexport const mapArtist = (raw: Raw): Artist => ({\n id: raw.id,\n name: raw.name,\n picture: raw.picture ?? raw.image?.medium,\n albumsCount: raw.albums_count,\n})\n\nexport const mapAlbum = (raw: Raw): Album => ({\n id: String(raw.id),\n title: raw.title,\n artist: raw.artist ? mapArtist(raw.artist) : undefined,\n tracksCount: raw.tracks_count,\n releaseDate: raw.release_date_original ?? raw.released_at,\n duration: raw.duration,\n image: mapImage(raw.image),\n genre: raw.genre?.name,\n hires: raw.hires,\n})\n\nexport const mapTrack = (raw: Raw): Track => ({\n id: raw.id,\n title: raw.title,\n album: raw.album ? mapAlbum(raw.album) : undefined,\n artist: raw.performer\n ? mapArtist(raw.performer)\n : raw.artist\n ? mapArtist(raw.artist)\n : undefined,\n trackNumber: raw.track_number,\n duration: raw.duration,\n hires: raw.hires,\n})\n\nexport const mapPlaylist = (raw: Raw): Playlist => ({\n id: raw.id,\n name: raw.name,\n description: raw.description,\n tracksCount: raw.tracks_count,\n isPublic: raw.is_public,\n owner: raw.owner?.name,\n duration: raw.duration,\n})\n","import { mapAlbum } from \"../mappers.js\"\nimport type { Transport } from \"../http/transport.js\"\nimport type { Album } from \"../types/domain.js\"\n\nexport const createAlbumsResource = (transport: Transport) => ({\n get: async (albumId: string): Promise<Album> =>\n mapAlbum(await transport.get(\"album/get\", { album_id: albumId })),\n})\n","import { mapArtist } from \"../mappers.js\"\nimport type { Transport } from \"../http/transport.js\"\nimport type { Artist, PageOptions } from \"../types/domain.js\"\n\ntype RawSimilar = { artists?: { items?: unknown[] } }\n\nexport const createArtistsResource = (transport: Transport) => ({\n get: async (artistId: number): Promise<Artist> =>\n mapArtist(await transport.get(\"artist/get\", { artist_id: artistId })),\n getSimilar: async (\n artistId: number,\n options: PageOptions = {},\n ): Promise<Artist[]> => {\n const raw = await transport.get<RawSimilar>(\"artist/getSimilarArtists\", {\n artist_id: artistId,\n limit: options.limit ?? 20,\n offset: options.offset ?? 0,\n })\n return (raw.artists?.items ?? []).map(mapArtist)\n },\n})\n","import { mapAlbum, mapArtist, mapTrack } from \"../mappers.js\"\nimport type { Transport } from \"../http/transport.js\"\nimport type {\n Album,\n Artist,\n FavouriteType,\n PageOptions,\n Track,\n} from \"../types/domain.js\"\n\nexport type UserFavourites = {\n albums: Album[]\n artists: Artist[]\n tracks: Track[]\n}\n\ntype RawFavourites = {\n albums?: { items?: unknown[] }\n artists?: { items?: unknown[] }\n tracks?: { items?: unknown[] }\n}\n\nconst favouriteIdParam: Record<FavouriteType, string> = {\n albums: \"album_ids\",\n artists: \"artist_ids\",\n tracks: \"track_ids\",\n}\n\nexport const createFavouritesResource = (transport: Transport) => ({\n list: async (\n type: FavouriteType,\n options: PageOptions = {},\n ): Promise<UserFavourites> => {\n const raw = await transport.get<RawFavourites>(\n \"favorite/getUserFavorites\",\n {\n type,\n limit: options.limit ?? 50,\n offset: options.offset ?? 0,\n },\n )\n return {\n albums: (raw.albums?.items ?? []).map(mapAlbum),\n artists: (raw.artists?.items ?? []).map(mapArtist),\n tracks: (raw.tracks?.items ?? []).map(mapTrack),\n }\n },\n add: async (type: FavouriteType, id: string): Promise<void> => {\n await transport.get(\"favorite/create\", { [favouriteIdParam[type]]: id })\n },\n remove: async (type: FavouriteType, id: string): Promise<void> => {\n await transport.get(\"favorite/delete\", { [favouriteIdParam[type]]: id })\n },\n})\n","import { mapPlaylist } from \"../mappers.js\"\nimport type { Transport } from \"../http/transport.js\"\nimport type { PageOptions, Playlist } from \"../types/domain.js\"\n\ntype RawPlaylists = { playlists?: { items?: unknown[] } }\n\nexport type CreatePlaylistParams = {\n name: string\n description?: string\n isPublic?: boolean\n}\n\nexport const createPlaylistsResource = (transport: Transport) => ({\n listForUser: async (options: PageOptions = {}): Promise<Playlist[]> => {\n const raw = await transport.get<RawPlaylists>(\"playlist/getUserPlaylists\", {\n limit: options.limit ?? 50,\n offset: options.offset ?? 0,\n })\n return (raw.playlists?.items ?? []).map(mapPlaylist)\n },\n get: async (\n playlistId: number,\n options: PageOptions = {},\n ): Promise<Playlist> =>\n mapPlaylist(\n await transport.get(\"playlist/get\", {\n playlist_id: playlistId,\n extra: \"tracks\",\n limit: options.limit ?? 500,\n offset: options.offset ?? 0,\n }),\n ),\n create: async ({\n name,\n description,\n isPublic,\n }: CreatePlaylistParams): Promise<Playlist> =>\n mapPlaylist(\n await transport.get(\"playlist/create\", {\n name,\n description,\n is_public: isPublic ? 1 : 0,\n }),\n ),\n remove: async (playlistId: number): Promise<void> => {\n await transport.get(\"playlist/delete\", { playlist_id: playlistId })\n },\n addTracks: async (\n playlistId: number,\n trackIds: number[],\n ): Promise<Playlist> =>\n mapPlaylist(\n await transport.get(\"playlist/addTracks\", {\n playlist_id: playlistId,\n track_ids: trackIds.join(\",\"),\n }),\n ),\n removeTracks: async (\n playlistId: number,\n playlistTrackIds: number[],\n ): Promise<Playlist> =>\n mapPlaylist(\n await transport.get(\"playlist/deleteTracks\", {\n playlist_id: playlistId,\n playlist_track_ids: playlistTrackIds.join(\",\"),\n }),\n ),\n})\n","import { mapAlbum, mapArtist, mapTrack } from \"../mappers.js\"\nimport type { Transport } from \"../http/transport.js\"\nimport type { PageOptions, SearchResults } from \"../types/domain.js\"\n\ntype RawSearch = {\n albums?: { items?: unknown[] }\n tracks?: { items?: unknown[] }\n artists?: { items?: unknown[] }\n}\n\nexport const createSearchResource = (transport: Transport) => ({\n search: async (\n query: string,\n options: PageOptions = {},\n ): Promise<SearchResults> => {\n const raw = await transport.get<RawSearch>(\"catalog/search\", {\n query,\n limit: options.limit ?? 20,\n offset: options.offset ?? 0,\n })\n return {\n query,\n albums: (raw.albums?.items ?? []).map(mapAlbum),\n tracks: (raw.tracks?.items ?? []).map(mapTrack),\n artists: (raw.artists?.items ?? []).map(mapArtist),\n }\n },\n})\n","import { mapTrack } from \"../mappers.js\"\nimport type { Transport } from \"../http/transport.js\"\nimport type { Track } from \"../types/domain.js\"\n\nexport const createTracksResource = (transport: Transport) => ({\n get: async (trackId: number): Promise<Track> =>\n mapTrack(await transport.get(\"track/get\", { track_id: trackId })),\n})\n","import { readFile } from \"node:fs/promises\"\nimport { homedir } from \"node:os\"\nimport { join } from \"node:path\"\n\nexport type NowPlayingOptions = {\n /** Override the Qobuz desktop player-state file (defaults to the macOS path). */\n path?: string\n}\n\n/**\n * The Qobuz desktop app (Electron) persists its play queue here. macOS does not\n * expose Qobuz to its now-playing system, so this local file is the source of truth.\n */\nexport const defaultPlayerStatePath = (): string =>\n join(homedir(), \"Library/Application Support/Qobuz/player-0.json\")\n\n/**\n * The id of the track Qobuz is currently playing, read from the desktop app's\n * local state. Returns `undefined` if nothing is playing or the file is absent.\n */\nexport const readNowPlayingTrackId = async (\n options: NowPlayingOptions = {},\n): Promise<number | undefined> => {\n try {\n const state = JSON.parse(\n await readFile(options.path ?? defaultPlayerStatePath(), \"utf8\"),\n )\n const queue = state?.playqueue?.data\n const trackId = queue?.items?.[queue?.currentIndex]?.trackId\n return typeof trackId === \"number\" ? trackId : undefined\n } catch {\n return undefined\n }\n}\n","import { createDeepLink } from \"./deep-link.js\"\nimport { authError } from \"./http/errors.js\"\nimport { createTransport } from \"./http/transport.js\"\nimport { createAlbumsResource } from \"./resources/albums.js\"\nimport { createArtistsResource } from \"./resources/artists.js\"\nimport { createFavouritesResource } from \"./resources/favourites.js\"\nimport { createPlaylistsResource } from \"./resources/playlists.js\"\nimport { createSearchResource } from \"./resources/search.js\"\nimport { createTracksResource } from \"./resources/tracks.js\"\nimport { readNowPlayingTrackId, type NowPlayingOptions } from \"./now-playing.js\"\nimport type { CredentialStore } from \"./auth/credential-store.js\"\nimport type { Track } from \"./types/domain.js\"\n\nexport type QobuzClientConfig = {\n store: CredentialStore\n fetchImpl?: typeof fetch\n}\n\nexport const createQobuzClient = async ({\n store,\n fetchImpl,\n}: QobuzClientConfig) => {\n const credentials = await store.load()\n if (!credentials)\n throw authError(\"not connected — store a valid app_id + token first\")\n\n const transport = createTransport({\n appId: credentials.appId,\n token: credentials.token,\n fetchImpl,\n })\n\n const tracks = createTracksResource(transport)\n\n return {\n appId: credentials.appId,\n search: createSearchResource(transport),\n albums: createAlbumsResource(transport),\n artists: createArtistsResource(transport),\n tracks,\n favourites: createFavouritesResource(transport),\n playlists: createPlaylistsResource(transport),\n deepLink: createDeepLink(),\n nowPlaying: async (\n options?: NowPlayingOptions,\n ): Promise<Track | undefined> => {\n const trackId = await readNowPlayingTrackId(options)\n return trackId === undefined ? undefined : tracks.get(trackId)\n },\n signOut: () => store.clear(),\n }\n}\n\nexport type QobuzClient = Awaited<ReturnType<typeof createQobuzClient>>\n","import { bootstrapError } from \"../http/errors.js\"\nimport { USER_AGENT } from \"../http/transport.js\"\n\nconst PLAY_URL = \"https://play.qobuz.com\"\n\nexport type AppCredentials = {\n appId: string\n bundlePath: string\n}\n\nexport const fetchAppId = async (\n options: { fetchImpl?: typeof fetch } = {},\n): Promise<AppCredentials> => {\n const fetchImpl = options.fetchImpl ?? fetch\n\n const getText = async (url: string) => {\n const res = await fetchImpl(url, { headers: { \"User-Agent\": USER_AGENT } })\n if (!res.ok) throw bootstrapError(`GET ${url} failed (${res.status})`)\n return res.text()\n }\n\n const loginPage = await getText(`${PLAY_URL}/login`)\n const bundlePath = loginPage.match(\n /<script src=\"(\\/resources\\/\\d+\\.\\d+\\.\\d+-[a-z]\\d{3}\\/bundle\\.js)\"><\\/script>/,\n )?.[1]\n if (!bundlePath)\n throw bootstrapError(\n \"could not find the bundle.js URL in the Qobuz login page\",\n )\n\n const bundle = await getText(`${PLAY_URL}${bundlePath}`)\n const appId = bundle.match(/production:\\{api:\\{appId:\"(\\d{9})\"/)?.[1]\n if (!appId)\n throw bootstrapError(\"could not extract app_id from the Qobuz web bundle\")\n\n return { appId, bundlePath }\n}\n","import { authError, type QobuzError } from \"../http/errors.js\"\nimport { createTransport } from \"../http/transport.js\"\n\nexport type ValidateConfig = {\n appId: string\n token: string\n fetchImpl?: typeof fetch\n}\n\nexport const validateCredentials = async ({\n appId,\n token,\n fetchImpl,\n}: ValidateConfig): Promise<void> => {\n const transport = createTransport({ appId, token, fetchImpl })\n try {\n await transport.get(\"favorite/getUserFavorites\", {\n type: \"albums\",\n limit: 1,\n })\n } catch (error) {\n const qobuzError = error as QobuzError\n if (qobuzError.status === 401) {\n throw authError(\n \"Qobuz rejected the credentials (401) — the token may be expired or the app_id doesn't match\",\n )\n }\n throw error\n }\n}\n","import { createQobuzClient, type QobuzClient } from \"../client.js\"\nimport { fetchAppId } from \"./bootstrap.js\"\nimport { validateCredentials } from \"./validate.js\"\nimport type { CredentialStore } from \"./credential-store.js\"\n\nexport type ConnectConfig = {\n token: string\n appId?: string\n store: CredentialStore\n fetchImpl?: typeof fetch\n}\n\nexport const connect = async ({\n token,\n appId,\n store,\n fetchImpl,\n}: ConnectConfig): Promise<QobuzClient> => {\n const resolvedAppId = appId ?? (await fetchAppId({ fetchImpl })).appId\n await validateCredentials({ appId: resolvedAppId, token, fetchImpl })\n await store.save({ appId: resolvedAppId, token })\n return createQobuzClient({ store, fetchImpl })\n}\n","import { execFile } from \"node:child_process\"\nimport { promisify } from \"node:util\"\n\nconst exec = promisify(execFile)\n\nexport type StoredCredentials = {\n appId: string\n token: string\n savedAt?: string\n}\n\nexport type CredentialStore = {\n load: () => Promise<StoredCredentials | undefined>\n save: (credentials: StoredCredentials) => Promise<void>\n clear: () => Promise<void>\n}\n\nexport const createMemoryStore = (\n seed?: StoredCredentials,\n): CredentialStore => {\n let current = seed\n return {\n load: async () => current,\n save: async (credentials) => {\n current = credentials\n },\n clear: async () => {\n current = undefined\n },\n }\n}\n\nexport type KeychainStoreOptions = {\n service?: string\n account?: string\n}\n\nexport const createKeychainStore = (\n options: KeychainStoreOptions = {},\n): CredentialStore => {\n const service = options.service ?? \"qobuz\"\n const account = options.account ?? \"credentials\"\n\n return {\n load: async () => {\n try {\n const { stdout } = await exec(\"security\", [\n \"find-generic-password\",\n \"-s\",\n service,\n \"-a\",\n account,\n \"-w\",\n ])\n return JSON.parse(stdout.trim()) as StoredCredentials\n } catch {\n return undefined\n }\n },\n save: async (credentials) => {\n const value = JSON.stringify({\n ...credentials,\n savedAt: credentials.savedAt ?? new Date().toISOString(),\n })\n await exec(\"security\", [\n \"add-generic-password\",\n \"-U\",\n \"-s\",\n service,\n \"-a\",\n account,\n \"-w\",\n value,\n ])\n },\n clear: async () => {\n try {\n await exec(\"security\", [\n \"delete-generic-password\",\n \"-s\",\n service,\n \"-a\",\n account,\n ])\n } catch {\n // nothing stored — nothing to clear\n }\n },\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kud/qobuz",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Reverse-engineered Qobuz API client for Node.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/kud/qobuz.git"
|
|
10
|
+
},
|
|
7
11
|
"exports": {
|
|
8
12
|
".": {
|
|
9
13
|
"types": "./dist/index.d.ts",
|