musicbrainz-api 0.11.0 → 0.13.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 CHANGED
@@ -34,32 +34,20 @@ If you plan to use this module for submitting metadata, please ensure you comply
34
34
 
35
35
  ## Example
36
36
 
37
- Import the module
38
- JavaScript example, how to import 'musicbrainz-api:
39
- ```js
40
- const MusicBrainzApi = require('musicbrainz-api').MusicBrainzApi;
41
-
42
- const mbApi = new MusicBrainzApi({
43
- appName: 'my-app',
44
- appVersion: '0.1.0',
45
- appContactInfo: 'user@mail.org'
46
- });
47
- ```
48
-
49
- In TypeScript it would look like this:
37
+ Example, how to import 'musicbrainz-api:
50
38
  ```js
51
39
  import {MusicBrainzApi} from 'musicbrainz-api';
52
40
 
53
41
  const mbApi = new MusicBrainzApi({
54
42
  appName: 'my-app',
55
43
  appVersion: '0.1.0',
56
- appContactInfo: 'user@mail.org' // Or URL to application home page
44
+ appContactInfo: 'user@mail.org'
57
45
  });
58
46
  ```
59
47
 
60
48
  The following configuration settings can be passed
61
49
  ```js
62
- import {MusicBrainzApi} from '../src/musicbrainz-api';
50
+ import {MusicBrainzApi} from 'musicbrainz-api';
63
51
 
64
52
  const config = {
65
53
  // MusicBrainz bot account username & password (optional)
@@ -115,6 +103,20 @@ Lookup an `artist` and include their `releases`, `release-groups` and `aliases`
115
103
  const artist = await mbApi.lookupArtist('ab2528d9-719f-4261-8098-21849222a0f2');
116
104
  ```
117
105
 
106
+ ### Lookup collection
107
+
108
+ Lookup an instrument
109
+
110
+ ```js
111
+ const collection = await mbApi.lookupCollection('de4fdfc4-53aa-458a-b463-8761cc7f5af8');
112
+ ```
113
+
114
+ Lookup an event
115
+
116
+ ```js
117
+ const event = await mbApi.lookupEvent('6d32c658-151e-45ec-88c4-fb8787524d61');
118
+ ```
119
+
118
120
  ### Lookup instrument
119
121
 
120
122
  Lookup an instrument
@@ -137,6 +139,10 @@ const label = await mbApi.lookupLabel('25dda9f9-f069-4898-82f0-59330a106c7f');
137
139
  const place = await mbApi.lookupPlace('e6cfb74d-d69b-44c3-b890-1b3f509816e4');
138
140
  ```
139
141
 
142
+ ```js
143
+ const place = await mbApi.lookupSeries('1ae6c9bc-2931-4d75-bee4-3dc53dfd246a');
144
+ ```
145
+
140
146
  The second argument can be used to pass [subqueries](https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2#Subqueries), which will return more (nested) information:
141
147
  ```js
142
148
  const artist = await mbApi.lookupArtist('ab2528d9-719f-4261-8098-21849222a0f2', ['releases', 'recordings', 'url-rels']);
@@ -475,6 +481,27 @@ assert.isTrue(succeed, 'Login successful');
475
481
  await mbApi.addSpotifyIdToRecording(recording, '2AMysGXOe0zzZJMtH3Nizb');
476
482
  ```
477
483
 
484
+ ## Cover Art Archive API
485
+
486
+ Implementation of the [Cover Art Archive API](https://musicbrainz.org/doc/Cover_Art_Archive/API).
487
+
488
+ ```js
489
+ import {CoverArtArchiveApi} from 'musicbrainz-api';
490
+
491
+ coverArtArchiveApiClient.getReleaseCovers(releaseMbid).then(releaseCoverInfo => {
492
+ console.log('Release cover info', releaseCoverInfo);
493
+ });
494
+
495
+ coverArtArchiveApiClient.getReleaseCovers(releaseMbid, 'front').then(releaseCoverInfo => {
496
+ console.log('Get best front cover', releaseCoverInfo);
497
+ });
498
+
499
+ coverArtArchiveApiClient.getReleaseCovers(releaseMbid, 'back').then(releaseCoverInfo => {
500
+ console.log('Get best back cover', releaseCoverInfo);
501
+ });
502
+
503
+ ```
504
+
478
505
  ## Compatibility
479
506
 
480
507
  The JavaScript in runtime is compliant with [ECMAScript 2017 (ES8)](https://en.wikipedia.org/wiki/ECMAScript#8th_Edition_-_ECMAScript_2017).
@@ -0,0 +1,31 @@
1
+ export type CovertType = 'Front' | 'Back' | 'Booklet' | 'Medium' | 'Obi' | 'Spine' | 'Track' | 'Tray' | 'Sticker' | 'Poster' | 'Liner' | 'Watermark' | 'Raw/Unedited' | 'Matrix/Runout' | 'Top' | 'Bottom' | 'Other';
2
+ export interface IImage {
3
+ types: CovertType[];
4
+ front: boolean;
5
+ back: boolean;
6
+ edit: number;
7
+ image: string;
8
+ comment: string;
9
+ approved: boolean;
10
+ id: string;
11
+ thumbnails: {
12
+ large: string;
13
+ small: string;
14
+ '250': string;
15
+ '500'?: string;
16
+ '1200'?: string;
17
+ };
18
+ }
19
+ export interface ICoverInfo {
20
+ images: IImage[];
21
+ release: string;
22
+ }
23
+ export declare class CoverArtArchiveApi {
24
+ private host;
25
+ private getJson;
26
+ /**
27
+ *
28
+ * @param releaseId MusicBrainz Release MBID
29
+ */
30
+ getReleaseCovers(releaseId: string, coverType?: 'front' | 'back'): Promise<ICoverInfo>;
31
+ }
@@ -0,0 +1,33 @@
1
+ /* eslint-disable-next-line */
2
+ import got from 'got';
3
+ export class CoverArtArchiveApi {
4
+ constructor() {
5
+ this.host = 'coverartarchive.org';
6
+ }
7
+ async getJson(path) {
8
+ const response = await got.get('https://' + this.host + path, {
9
+ headers: {
10
+ Accept: `application/json`
11
+ },
12
+ responseType: 'json'
13
+ });
14
+ return response.body;
15
+ }
16
+ /**
17
+ *
18
+ * @param releaseId MusicBrainz Release MBID
19
+ */
20
+ async getReleaseCovers(releaseId, coverType) {
21
+ const path = ['release', releaseId];
22
+ if (coverType) {
23
+ path.push(coverType);
24
+ }
25
+ const info = await this.getJson('/' + path.join('/'));
26
+ // Hack to correct http addresses into https
27
+ if (info.release && info.release.startsWith('http:')) {
28
+ info.release = 'https' + info.release.substring(4);
29
+ }
30
+ return info;
31
+ }
32
+ }
33
+ //# sourceMappingURL=coverartarchive-api.js.map
@@ -1,12 +1,9 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DigestAuth = void 0;
4
- const uuid_1 = require("uuid");
5
- const crypto = require("crypto");
1
+ import { v4 as uuidv4 } from 'uuid';
2
+ import * as crypto from 'crypto';
6
3
  function md5(str) {
7
4
  return crypto.createHash('md5').update(str).digest('hex'); // lgtm [js/insufficient-password-hash]
8
5
  }
9
- class DigestAuth {
6
+ export class DigestAuth {
10
7
  /**
11
8
  * RFC 2617: handle both MD5 and MD5-sess algorithms.
12
9
  *
@@ -45,7 +42,7 @@ class DigestAuth {
45
42
  }
46
43
  const qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth';
47
44
  const nc = qop && '00000001';
48
- const cnonce = qop && (0, uuid_1.v4)().replace(/-/g, '');
45
+ const cnonce = qop && uuidv4().replace(/-/g, '');
49
46
  const ha1 = DigestAuth.ha1Compute(challenge.algorithm, this.credentials.username, challenge.realm, this.credentials.password, challenge.nonce, cnonce);
50
47
  const ha2 = md5(method + ':' + path); // lgtm [js/insufficient-password-hash]
51
48
  const digestResponse = qop
@@ -79,5 +76,4 @@ class DigestAuth {
79
76
  return authHeader;
80
77
  }
81
78
  }
82
- exports.DigestAuth = DigestAuth;
83
79
  //# sourceMappingURL=digest-auth.js.map
package/lib/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './coverartarchive-api.js';
2
+ export * from './musicbrainz-api.js';
package/lib/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './coverartarchive-api.js';
2
+ export * from './musicbrainz-api.js';
3
+ //# sourceMappingURL=index.js.map
@@ -1,10 +1,10 @@
1
- export { XmlMetadata } from './xml/xml-metadata';
2
- export { XmlIsrc } from './xml/xml-isrc';
3
- export { XmlIsrcList } from './xml/xml-isrc-list';
4
- export { XmlRecording } from './xml/xml-recording';
5
- import { XmlMetadata } from './xml/xml-metadata';
6
- import * as mb from './musicbrainz.types';
7
- export * from './musicbrainz.types';
1
+ export { XmlMetadata } from './xml/xml-metadata.js';
2
+ export { XmlIsrc } from './xml/xml-isrc.js';
3
+ export { XmlIsrcList } from './xml/xml-isrc-list.js';
4
+ export { XmlRecording } from './xml/xml-recording.js';
5
+ import { XmlMetadata } from './xml/xml-metadata.js';
6
+ import * as mb from './musicbrainz.types.js';
7
+ export * from './musicbrainz.types.js';
8
8
  export type RelationsIncludes = 'area-rels' | 'artist-rels' | 'event-rels' | 'instrument-rels' | 'label-rels' | 'place-rels' | 'recording-rels' | 'release-rels' | 'release-group-rels' | 'series-rels' | 'url-rels' | 'work-rels';
9
9
  export type SubQueryIncludes =
10
10
  /**
@@ -145,6 +145,11 @@ export declare class MusicBrainzApi {
145
145
  * @param inc Include: artist-credits, isrcs
146
146
  */
147
147
  lookupRecording(recordingId: string, inc?: RecordingIncludes[]): Promise<mb.IRecording>;
148
+ /**
149
+ * Lookup series
150
+ * @param seriesId Series MBID
151
+ */
152
+ lookupSeries(seriesId: string): Promise<mb.ISeries>;
148
153
  /**
149
154
  * Lookup work
150
155
  * @param workId Work MBID
@@ -1,43 +1,19 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.makeAndQueryString = exports.MusicBrainzApi = exports.XmlRecording = exports.XmlIsrcList = exports.XmlIsrc = exports.XmlMetadata = void 0;
18
- const assert = require("assert");
19
- const http_status_codes_1 = require("http-status-codes");
20
- const Url = require("url");
21
- const Debug = require("debug");
22
- var xml_metadata_1 = require("./xml/xml-metadata");
23
- Object.defineProperty(exports, "XmlMetadata", { enumerable: true, get: function () { return xml_metadata_1.XmlMetadata; } });
24
- var xml_isrc_1 = require("./xml/xml-isrc");
25
- Object.defineProperty(exports, "XmlIsrc", { enumerable: true, get: function () { return xml_isrc_1.XmlIsrc; } });
26
- var xml_isrc_list_1 = require("./xml/xml-isrc-list");
27
- Object.defineProperty(exports, "XmlIsrcList", { enumerable: true, get: function () { return xml_isrc_list_1.XmlIsrcList; } });
28
- var xml_recording_1 = require("./xml/xml-recording");
29
- Object.defineProperty(exports, "XmlRecording", { enumerable: true, get: function () { return xml_recording_1.XmlRecording; } });
30
- const digest_auth_1 = require("./digest-auth");
31
- const rate_limiter_1 = require("./rate-limiter");
32
- const mb = require("./musicbrainz.types");
33
- /* eslint-disable-next-line */
34
- const got_1 = require("got");
35
- const tough_cookie_1 = require("tough-cookie");
36
- __exportStar(require("./musicbrainz.types"), exports);
37
- const util_1 = require("util");
38
- const retries = 3;
1
+ import * as assert from 'assert';
2
+ import { StatusCodes as HttpStatus } from 'http-status-codes';
3
+ import Debug from 'debug';
4
+ export { XmlMetadata } from './xml/xml-metadata.js';
5
+ export { XmlIsrc } from './xml/xml-isrc.js';
6
+ export { XmlIsrcList } from './xml/xml-isrc-list.js';
7
+ export { XmlRecording } from './xml/xml-recording.js';
8
+ import { DigestAuth } from './digest-auth.js';
9
+ import { RateLimiter } from './rate-limiter.js';
10
+ import * as mb from './musicbrainz.types.js';
11
+ import got from 'got';
12
+ import { CookieJar } from 'tough-cookie';
13
+ export * from './musicbrainz.types.js';
14
+ import { promisify } from 'util';
39
15
  const debug = Debug('musicbrainz-api');
40
- class MusicBrainzApi {
16
+ export class MusicBrainzApi {
41
17
  static escapeText(text) {
42
18
  let str = '';
43
19
  for (const chr of text) {
@@ -91,45 +67,33 @@ class MusicBrainzApi {
91
67
  botAccount: {}
92
68
  };
93
69
  Object.assign(this.config, _config);
94
- const cookieJar = new tough_cookie_1.CookieJar();
95
- this.getCookies = (0, util_1.promisify)(cookieJar.getCookies.bind(cookieJar));
70
+ const cookieJar = new CookieJar();
71
+ this.getCookies = promisify(cookieJar.getCookies.bind(cookieJar));
72
+ // @ts-ignore
96
73
  this.options = {
97
74
  prefixUrl: this.config.baseUrl,
98
- timeout: 20 * 1000,
75
+ timeout: {
76
+ read: 20 * 1000
77
+ },
99
78
  headers: {
100
79
  'User-Agent': `${this.config.appName}/${this.config.appVersion} ( ${this.config.appContactInfo} )`
101
80
  },
102
81
  cookieJar: cookieJar
103
82
  };
104
- this.rateLimiter = new rate_limiter_1.RateLimiter(60, 50);
83
+ this.rateLimiter = new RateLimiter(15, 18);
105
84
  }
106
85
  async restGet(relUrl, query = {}, attempt = 1) {
107
86
  query.fmt = 'json';
108
- let response;
109
87
  await this.rateLimiter.limit();
110
- do {
111
- response = await got_1.default.get('ws/2' + relUrl, Object.assign({ searchParams: query, responseType: 'json' }, this.options));
112
- if (response.statusCode !== 503)
113
- break;
114
- debug('Rate limiter kicked in, slowing down...');
115
- await rate_limiter_1.RateLimiter.sleep(500);
116
- } while (true);
117
- switch (response.statusCode) {
118
- case http_status_codes_1.StatusCodes.OK:
119
- return response.body;
120
- case http_status_codes_1.StatusCodes.BAD_REQUEST:
121
- case http_status_codes_1.StatusCodes.NOT_FOUND:
122
- throw new Error(`Got response status ${response.statusCode}: ${(0, http_status_codes_1.getReasonPhrase)(response.status)}`);
123
- case http_status_codes_1.StatusCodes.SERVICE_UNAVAILABLE: // 503
124
- default:
125
- const msg = `Got response status ${response.statusCode} on attempt #${attempt} (${(0, http_status_codes_1.getReasonPhrase)(response.status)})`;
126
- debug(msg);
127
- if (attempt < retries) {
128
- return this.restGet(relUrl, query, attempt + 1);
129
- }
130
- else
131
- throw new Error(msg);
132
- }
88
+ const response = await got.get('ws/2' + relUrl, {
89
+ ...this.options,
90
+ searchParams: query,
91
+ responseType: 'json',
92
+ retry: {
93
+ limit: 10
94
+ }
95
+ });
96
+ return response.body;
133
97
  }
134
98
  // -----------------------------------------------------------------------------------------------------------------
135
99
  // Lookup functions
@@ -216,6 +180,13 @@ class MusicBrainzApi {
216
180
  lookupRecording(recordingId, inc = []) {
217
181
  return this.lookupEntity('recording', recordingId, inc);
218
182
  }
183
+ /**
184
+ * Lookup series
185
+ * @param seriesId Series MBID
186
+ */
187
+ lookupSeries(seriesId) {
188
+ return this.lookupEntity('series', seriesId);
189
+ }
219
190
  /**
220
191
  * Lookup work
221
192
  * @param workId Work MBID
@@ -360,14 +331,20 @@ class MusicBrainzApi {
360
331
  const postData = xmlMetadata.toXml();
361
332
  do {
362
333
  await this.rateLimiter.limit();
363
- const response = await got_1.default.post(path, Object.assign({ searchParams: { client: clientId }, headers: {
334
+ const response = await got.post(path, {
335
+ ...this.options,
336
+ searchParams: { client: clientId },
337
+ headers: {
364
338
  authorization: digest,
365
339
  'Content-Type': 'application/xml'
366
- }, body: postData, throwHttpErrors: false }, this.options));
367
- if (response.statusCode === http_status_codes_1.StatusCodes.UNAUTHORIZED) {
340
+ },
341
+ body: postData,
342
+ throwHttpErrors: false
343
+ });
344
+ if (response.statusCode === HttpStatus.UNAUTHORIZED) {
368
345
  // Respond to digest challenge
369
- const auth = new digest_auth_1.DigestAuth(this.config.botAccount);
370
- const relPath = Url.parse(response.requestUrl).path; // Ensure path is relative
346
+ const auth = new DigestAuth(this.config.botAccount);
347
+ const relPath = response.requestUrl.pathname; // Ensure path is relative
371
348
  digest = auth.digest(response.request.method, relPath, response.headers['www-authenticate']);
372
349
  ++n;
373
350
  }
@@ -395,10 +372,15 @@ class MusicBrainzApi {
395
372
  csrf_token: this.session.csrf.token,
396
373
  remember_me: 1
397
374
  };
398
- const response = await got_1.default.post('login', Object.assign({ followRedirect: false, searchParams: {
375
+ const response = await got.post('login', {
376
+ ...this.options,
377
+ followRedirect: false,
378
+ searchParams: {
399
379
  returnto: redirectUri
400
- }, form: formData }, this.options));
401
- const success = response.statusCode === http_status_codes_1.StatusCodes.MOVED_TEMPORARILY && response.headers.location === redirectUri;
380
+ },
381
+ form: formData
382
+ });
383
+ const success = response.statusCode === HttpStatus.MOVED_TEMPORARILY && response.headers.location === redirectUri;
402
384
  if (success) {
403
385
  this.session.loggedIn = true;
404
386
  }
@@ -409,10 +391,14 @@ class MusicBrainzApi {
409
391
  */
410
392
  async logout() {
411
393
  const redirectUri = '/success';
412
- const response = await got_1.default.get('logout', Object.assign({ followRedirect: false, searchParams: {
394
+ const response = await got.get('logout', {
395
+ ...this.options,
396
+ followRedirect: false,
397
+ searchParams: {
413
398
  returnto: redirectUri
414
- } }, this.options));
415
- const success = response.statusCode === http_status_codes_1.StatusCodes.MOVED_TEMPORARILY && response.headers.location === redirectUri;
399
+ }
400
+ });
401
+ const success = response.statusCode === HttpStatus.MOVED_TEMPORARILY && response.headers.location === redirectUri;
416
402
  if (success && this.session) {
417
403
  this.session.loggedIn = true;
418
404
  }
@@ -432,10 +418,14 @@ class MusicBrainzApi {
432
418
  formData.username = this.config.botAccount.username;
433
419
  formData.password = this.config.botAccount.password;
434
420
  formData.remember_me = 1;
435
- const response = await got_1.default.post(`${entity}/${mbid}/edit`, Object.assign({ form: formData, followRedirect: false }, this.options));
436
- if (response.statusCode === http_status_codes_1.StatusCodes.OK)
421
+ const response = await got.post(`${entity}/${mbid}/edit`, {
422
+ ...this.options,
423
+ form: formData,
424
+ followRedirect: false
425
+ });
426
+ if (response.statusCode === HttpStatus.OK)
437
427
  throw new Error(`Failed to submit form data`);
438
- if (response.statusCode === http_status_codes_1.StatusCodes.MOVED_TEMPORARILY)
428
+ if (response.statusCode === HttpStatus.MOVED_TEMPORARILY)
439
429
  return;
440
430
  throw new Error(`Unexpected status code: ${response.statusCode}`);
441
431
  }
@@ -489,7 +479,7 @@ class MusicBrainzApi {
489
479
  * @param query Arguments
490
480
  */
491
481
  search(entity, query) {
492
- const urlQuery = Object.assign({}, query);
482
+ const urlQuery = { ...query };
493
483
  if (typeof query.query === 'object') {
494
484
  urlQuery.query = makeAndQueryString(query.query);
495
485
  }
@@ -531,15 +521,17 @@ class MusicBrainzApi {
531
521
  return this.search('url', query);
532
522
  }
533
523
  async getSession() {
534
- const response = await got_1.default.get('login', Object.assign({ followRedirect: false, responseType: 'text' }, this.options));
524
+ const response = await got.get('login', {
525
+ ...this.options,
526
+ followRedirect: false,
527
+ responseType: 'text'
528
+ });
535
529
  return {
536
530
  csrf: MusicBrainzApi.fetchCsrf(response.body)
537
531
  };
538
532
  }
539
533
  }
540
- exports.MusicBrainzApi = MusicBrainzApi;
541
- function makeAndQueryString(keyValuePairs) {
534
+ export function makeAndQueryString(keyValuePairs) {
542
535
  return Object.keys(keyValuePairs).map(key => `${key}:"${keyValuePairs[key]}"`).join(' AND ');
543
536
  }
544
- exports.makeAndQueryString = makeAndQueryString;
545
537
  //# sourceMappingURL=musicbrainz-api.js.map
@@ -1,5 +1,5 @@
1
1
  import DateTimeFormat = Intl.DateTimeFormat;
2
- import { IFormData } from './musicbrainz-api';
2
+ import type { IFormData } from './musicbrainz-api.js';
3
3
  export interface IPeriod {
4
4
  'begin': string;
5
5
  'ended': boolean;
@@ -220,6 +220,12 @@ export interface ILabel extends IEntity {
220
220
  export interface IPlace extends IEntity {
221
221
  name: string;
222
222
  }
223
+ export interface ISeries extends IEntity {
224
+ name: string;
225
+ type: string;
226
+ disambiguation: string;
227
+ 'type-id': string;
228
+ }
223
229
  export interface IUrl extends IEntity {
224
230
  id: string;
225
231
  resource: string;
@@ -1,7 +1,4 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LinkType = void 0;
4
- var LinkType;
1
+ export var LinkType;
5
2
  (function (LinkType) {
6
3
  LinkType[LinkType["license"] = 302] = "license";
7
4
  LinkType[LinkType["production"] = 256] = "production";
@@ -13,5 +10,5 @@ var LinkType;
13
10
  LinkType[LinkType["crowdfunding_page"] = 905] = "crowdfunding_page";
14
11
  LinkType[LinkType["other_databases"] = 306] = "other_databases";
15
12
  LinkType[LinkType["Allmusic"] = 285] = "Allmusic";
16
- })(LinkType = exports.LinkType || (exports.LinkType = {}));
13
+ })(LinkType = LinkType || (LinkType = {}));
17
14
  //# sourceMappingURL=musicbrainz.types.js.map
@@ -3,6 +3,6 @@ export declare class RateLimiter {
3
3
  static sleep(ms: number): Promise<void>;
4
4
  queue: number[];
5
5
  private readonly period;
6
- constructor(period: number, maxCalls: number);
6
+ constructor(maxCalls: number, period: number);
7
7
  limit(): Promise<void>;
8
8
  }
@@ -1,15 +1,13 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RateLimiter = void 0;
4
- const debug_1 = require("debug");
5
- const debug = (0, debug_1.default)('musicbrainz-api:rate-limiter');
6
- class RateLimiter {
1
+ import Debug from 'debug';
2
+ const debug = Debug('musicbrainz-api:rate-limiter');
3
+ export class RateLimiter {
7
4
  static sleep(ms) {
8
5
  return new Promise(resolve => setTimeout(resolve, ms));
9
6
  }
10
- constructor(period, maxCalls) {
7
+ constructor(maxCalls, period) {
11
8
  this.maxCalls = maxCalls;
12
9
  this.queue = [];
10
+ debug(`Rate limiter initialized with max ${maxCalls} calls in ${period} seconds.`);
13
11
  this.period = 1000 * period;
14
12
  }
15
13
  async limit() {
@@ -18,6 +16,7 @@ class RateLimiter {
18
16
  while (this.queue.length > 0 && this.queue[0] < t0) {
19
17
  this.queue.shift();
20
18
  }
19
+ // debug(`Current rate is ${this.queue.length} per ${this.period / 1000} sec`);
21
20
  if (this.queue.length >= this.maxCalls) {
22
21
  const delay = this.queue[0] + this.period - now;
23
22
  debug(`Client side rate limiter activated: cool down for ${delay / 1000} s...`);
@@ -28,5 +27,4 @@ class RateLimiter {
28
27
  // const ratePerSec = 1000 * this.queue.length / (now - this.queue[0]);
29
28
  }
30
29
  }
31
- exports.RateLimiter = RateLimiter;
32
30
  //# sourceMappingURL=rate-limiter.js.map
@@ -1,4 +1,4 @@
1
- import { XmlIsrc } from './xml-isrc';
1
+ import { XmlIsrc } from './xml-isrc.js';
2
2
  export declare class XmlIsrcList {
3
3
  items: XmlIsrc[];
4
4
  pushIsrc(isrc: string): void;
@@ -1,13 +1,10 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.XmlIsrcList = void 0;
4
- const xml_isrc_1 = require("./xml-isrc");
5
- class XmlIsrcList {
1
+ import { XmlIsrc } from './xml-isrc.js';
2
+ export class XmlIsrcList {
6
3
  constructor() {
7
4
  this.items = [];
8
5
  }
9
6
  pushIsrc(isrc) {
10
- this.items.push(new xml_isrc_1.XmlIsrc(isrc));
7
+ this.items.push(new XmlIsrc(isrc));
11
8
  }
12
9
  toXml() {
13
10
  return this.items.length === 0 ? null : {
@@ -19,5 +16,4 @@ class XmlIsrcList {
19
16
  };
20
17
  }
21
18
  }
22
- exports.XmlIsrcList = XmlIsrcList;
23
19
  //# sourceMappingURL=xml-isrc-list.js.map
@@ -1,7 +1,4 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.XmlIsrc = void 0;
4
- class XmlIsrc {
1
+ export class XmlIsrc {
5
2
  constructor(isrc) {
6
3
  this.isrc = isrc;
7
4
  }
@@ -14,5 +11,4 @@ class XmlIsrc {
14
11
  };
15
12
  }
16
13
  }
17
- exports.XmlIsrc = XmlIsrc;
18
14
  //# sourceMappingURL=xml-isrc.js.map
@@ -1,4 +1,4 @@
1
- import { XmlRecording } from './xml-recording';
1
+ import { XmlRecording } from './xml-recording.js';
2
2
  export declare class XmlMetadata {
3
3
  recordings: XmlRecording[];
4
4
  pushRecording(id: string): XmlRecording;
@@ -1,16 +1,13 @@
1
- "use strict";
2
1
  // https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2#ISRC_submission
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.XmlMetadata = void 0;
5
- const jsontoxml = require("jsontoxml");
6
- const xml_recording_1 = require("./xml-recording");
2
+ import jsontoxml from 'jsontoxml';
3
+ import { XmlRecording } from './xml-recording.js';
7
4
  const ns_metadata = 'http://musicbrainz.org/ns/mmd-2.0#';
8
- class XmlMetadata {
5
+ export class XmlMetadata {
9
6
  constructor() {
10
7
  this.recordings = [];
11
8
  }
12
9
  pushRecording(id) {
13
- const rec = new xml_recording_1.XmlRecording(id);
10
+ const rec = new XmlRecording(id);
14
11
  this.recordings.push(rec);
15
12
  return rec;
16
13
  }
@@ -26,5 +23,4 @@ class XmlMetadata {
26
23
  }], { prettyPrint: false, escape: true, xmlHeader: true });
27
24
  }
28
25
  }
29
- exports.XmlMetadata = XmlMetadata;
30
26
  //# sourceMappingURL=xml-metadata.js.map
@@ -1,4 +1,4 @@
1
- import { XmlIsrcList } from './xml-isrc-list';
1
+ import { XmlIsrcList } from './xml-isrc-list.js';
2
2
  export declare class XmlRecording {
3
3
  id: string;
4
4
  isrcList: XmlIsrcList;
@@ -1,11 +1,8 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.XmlRecording = void 0;
4
- const xml_isrc_list_1 = require("./xml-isrc-list");
5
- class XmlRecording {
1
+ import { XmlIsrcList } from './xml-isrc-list.js';
2
+ export class XmlRecording {
6
3
  constructor(id) {
7
4
  this.id = id;
8
- this.isrcList = new xml_isrc_list_1.XmlIsrcList();
5
+ this.isrcList = new XmlIsrcList();
9
6
  }
10
7
  toXml() {
11
8
  return {
@@ -17,5 +14,4 @@ class XmlRecording {
17
14
  };
18
15
  }
19
16
  }
20
- exports.XmlRecording = XmlRecording;
21
17
  //# sourceMappingURL=xml-recording.js.map
package/package.json CHANGED
@@ -1,9 +1,14 @@
1
1
  {
2
2
  "name": "musicbrainz-api",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "MusicBrainz API client for reading and submitting metadata",
5
- "main": "lib/musicbrainz-api",
6
- "types": "lib/musicbrainz-api",
5
+ "exports": "./lib/index.js",
6
+ "types": "lib/index.d.ts",
7
+ "files": [
8
+ "lib/**/*.js",
9
+ "lib/**/*.d.ts"
10
+ ],
11
+ "type": "module",
7
12
  "author": {
8
13
  "name": "Borewit",
9
14
  "url": "https://github.com/Borewit"
@@ -20,12 +25,18 @@
20
25
  "web",
21
26
  "service",
22
27
  "submit",
23
- "metabrainz"
28
+ "metabrainz",
29
+ "Cover Art Archive",
30
+ "coverartarchive",
31
+ "coverartarchive.org",
32
+ "album art",
33
+ "covers",
34
+ "download covers"
24
35
  ],
25
36
  "license": "MIT",
26
37
  "private": false,
27
38
  "engines": {
28
- "node": "*"
39
+ "node": "^14.13.1 || >=16.0.0"
29
40
  },
30
41
  "repository": {
31
42
  "type": "git",
@@ -39,8 +50,8 @@
39
50
  "@types/request-promise-native": "^1.0.17",
40
51
  "@types/uuid": "^9.0.0",
41
52
  "caseless": "^0.12.0",
42
- "debug": "^4.1.1",
43
- "got": "^11.8.5",
53
+ "debug": "^4.3.4",
54
+ "got": "^13.0.0",
44
55
  "http-status-codes": "^2.1.4",
45
56
  "json-stringify-safe": "^5.0.1",
46
57
  "jsontoxml": "^1.0.1",
@@ -55,6 +66,7 @@
55
66
  "@types/node": "^20.8.10",
56
67
  "@typescript-eslint/eslint-plugin": "^5.13.0",
57
68
  "@typescript-eslint/parser": "^5.13.0",
69
+ "c8": "^8.0.1",
58
70
  "chai": "^4.2.0",
59
71
  "del-cli": "^5.0.0",
60
72
  "eslint": "^8.10.0",
@@ -64,18 +76,13 @@
64
76
  "eslint-plugin-jsdoc": "^46.8.2",
65
77
  "eslint-plugin-node": "^11.1.0",
66
78
  "eslint-plugin-unicorn": "^46.0.0",
67
- "mocha": "^9.0.1",
68
- "nyc": "^15.0.0",
79
+ "mocha": "^10.1.0",
69
80
  "remark-cli": "^11.0.0",
70
81
  "remark-preset-lint-recommended": "^6.1.2",
71
82
  "ts-node": "^10.0.0",
72
83
  "tslint": "^6.1.1",
73
84
  "typescript": "^5.0.2"
74
85
  },
75
- "files": [
76
- "lib/**/*.js",
77
- "lib/**/*.d.ts"
78
- ],
79
86
  "scripts": {
80
87
  "clean": "del-cli lib/**/*.js lib/**/*.js.map lib/**/*.d.ts test/**/*.js test/**/*.js.map",
81
88
  "compile-lib": "tsc -p lib",
@@ -84,10 +91,10 @@
84
91
  "eslint": "eslint lib/**/*.ts --ignore-pattern lib/**/*.d.ts test/**/*.ts",
85
92
  "lint-md": "remark -u preset-lint-recommended .",
86
93
  "lint": "npm run lint-md && npm run eslint",
87
- "test": "mocha --require ts-node/register --require source-map-support/register --full-trace test/test-*.ts",
94
+ "test": "mocha",
88
95
  "build": "npm run clean && npm run compile",
89
96
  "start": "npm-run-all compile lint cover-test",
90
- "test-coverage": "nyc npm run test",
97
+ "test-coverage": "c8 npm run test",
91
98
  "send-codacy": "nyc report --reporter=text-lcov | codacy-coverage"
92
99
  },
93
100
  "nyc": {