musiciwant 1.0.0 → 1.1.1

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.
Files changed (3) hide show
  1. package/index.d.ts +35 -0
  2. package/index.js +54 -0
  3. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -33,10 +33,18 @@ export interface Song {
33
33
  youtube_id: string | null;
34
34
  spotify_url: string | null;
35
35
  youtube_url: string | null;
36
+ /** ISRC (MusicBrainz-sourced; null until a confident match exists — never fabricated). */
37
+ isrc: string | null;
38
+ /** First release date (MusicBrainz). */
39
+ release_date: string | null;
40
+ /** Genres/tags (MusicBrainz). */
41
+ genres: string[];
36
42
  /** Present on single-song lookups. */
37
43
  moods?: string[];
38
44
  /** Present on single-song lookups. */
39
45
  recommended_for?: string[];
46
+ /** All-platform listen links. Present on single-song lookups. */
47
+ listen?: Record<string, string>;
40
48
  url: string;
41
49
  }
42
50
 
@@ -73,12 +81,39 @@ export interface StatsResponse {
73
81
  most_intense_genres: GenreStat[];
74
82
  }
75
83
 
84
+ export interface FilterOptions {
85
+ minBpm?: number; maxBpm?: number; minIntensity?: number; maxIntensity?: number;
86
+ maxDynamicRange?: number; sensoryLevel?: 'safe' | 'moderate' | 'intense';
87
+ suddenChanges?: string | string[]; vocalStyle?: string; mood?: string;
88
+ misoSafe?: boolean; limit?: number; offset?: number;
89
+ }
90
+ export interface FilterResponse { count: number; results: Song[]; }
91
+ export type SongRef = { title: string; artist?: string } | { slug: string };
92
+ export interface BatchResponse { count: number; results: { query: SongRef; found: boolean; song: Song | null }[]; }
93
+ export interface PlaylistTrack { query: SongRef; matched: boolean; title?: string; artist?: string; slug?: string; intensity?: number; sensory_level?: string; }
94
+ export interface PlaylistResponse {
95
+ tracks: PlaylistTrack[];
96
+ stats: { avg: number; max_jump: number; ambushes: number[]; peak_index: number; calm_index: number } | null;
97
+ unmatched: number;
98
+ }
99
+ export interface FingerprintResponse {
100
+ found: boolean;
101
+ song?: { title: string; artist: string; slug: string };
102
+ vector?: Record<string, number>;
103
+ vector_array?: number[];
104
+ dims?: string[];
105
+ }
106
+
76
107
  export class MusicIWant {
77
108
  constructor(opts?: ClientOptions);
78
109
  song(params: { title: string; artist?: string } | { slug: string }): Promise<SongResponse>;
79
110
  search(q: string): Promise<SearchResponse>;
80
111
  gentler(params: { title: string; artist?: string } | { slug: string }): Promise<GentlerResponse>;
81
112
  stats(): Promise<StatsResponse>;
113
+ filter(opts?: FilterOptions): Promise<FilterResponse>;
114
+ batch(songs: SongRef[]): Promise<BatchResponse>;
115
+ playlist(songs: SongRef[]): Promise<PlaylistResponse>;
116
+ fingerprint(params: { title: string; artist?: string } | { slug: string }): Promise<FingerprintResponse>;
82
117
  usage(): Promise<UsageResponse>;
83
118
  static getKey(email: string, opts?: ClientOptions): Promise<KeyResponse>;
84
119
  }
package/index.js CHANGED
@@ -74,6 +74,60 @@ class MusicIWant {
74
74
  return this._get('/api/v1/stats', {});
75
75
  }
76
76
 
77
+ /**
78
+ * Query the catalog by feel. All options optional: { minBpm, maxBpm,
79
+ * minIntensity, maxIntensity, maxDynamicRange, sensoryLevel, suddenChanges
80
+ * (string or array), vocalStyle, mood, misoSafe (bool), limit, offset }.
81
+ * @returns {Promise<object>} { count, results }
82
+ */
83
+ filter(opts = {}) {
84
+ const sc = Array.isArray(opts.suddenChanges) ? opts.suddenChanges.join(',') : opts.suddenChanges;
85
+ return this._get('/api/v1/filter', {
86
+ min_bpm: opts.minBpm, max_bpm: opts.maxBpm,
87
+ min_intensity: opts.minIntensity, max_intensity: opts.maxIntensity,
88
+ max_dynamic_range: opts.maxDynamicRange, sensory_level: opts.sensoryLevel,
89
+ sudden_changes: sc, vocal_style: opts.vocalStyle, mood: opts.mood,
90
+ miso_safe: opts.misoSafe ? 'true' : undefined,
91
+ limit: opts.limit, offset: opts.offset
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Batch lookup (up to 100). songs: array of { title, artist } or { slug }.
97
+ * @returns {Promise<object>} { count, results: [{ query, found, song }] }
98
+ */
99
+ batch(songs) {
100
+ if (!Array.isArray(songs) || !songs.length) throw new Error('batch(songs) needs a non-empty array');
101
+ return this._post('/api/v1/songs', { songs });
102
+ }
103
+
104
+ /**
105
+ * Sensory shape of an ordered playlist (up to 60). songs: array of
106
+ * { title, artist } or { slug }. @returns {Promise<object>} { tracks, stats, unmatched }
107
+ */
108
+ playlist(songs) {
109
+ if (!Array.isArray(songs) || !songs.length) throw new Error('playlist(songs) needs a non-empty array');
110
+ return this._post('/api/v1/playlist', { songs });
111
+ }
112
+
113
+ /**
114
+ * Normalized 0–1 feature vector (the Spotify audio-features replacement).
115
+ * Provide {title, artist} or {slug}. @returns {Promise<object>} { found, song, vector, vector_array, dims }
116
+ */
117
+ fingerprint({ title, artist, slug } = {}) {
118
+ if (!title && !slug) throw new Error('fingerprint() needs { title, artist } or { slug }');
119
+ return this._get('/api/v1/fingerprint', { title, artist, slug });
120
+ }
121
+
122
+ async _post(path, body) {
123
+ const headers = { 'Accept': 'application/json', 'Content-Type': 'application/json' };
124
+ if (this.apiKey) headers['X-API-Key'] = this.apiKey;
125
+ const res = await this._fetch(this.baseUrl + path, { method: 'POST', headers, body: JSON.stringify(body) });
126
+ const out = await res.json().catch(() => ({}));
127
+ if (!res.ok) throw new MusicIWantError((out && out.error) || ('HTTP ' + res.status), res.status, out);
128
+ return out;
129
+ }
130
+
77
131
  /** Current key's tier + remaining quota. Requires an apiKey. */
78
132
  usage() {
79
133
  if (!this.apiKey) throw new Error('usage() requires an apiKey');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musiciwant",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Independent music analysis API client — BPM, dynamic range, sensory intensity, moods, misophonia flags. A Spotify Audio Features alternative.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",