musicbrainz-api 0.15.0 → 0.16.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
@@ -391,6 +391,7 @@ await mbApi.addSpotifyIdToRecording(recording, '2AMysGXOe0zzZJMtH3Nizb');
391
391
 
392
392
  Implementation of the [Cover Art Archive API](https://musicbrainz.org/doc/Cover_Art_Archive/API).
393
393
 
394
+ ### Release Cover Art
394
395
  ```js
395
396
  import {CoverArtArchiveApi} from 'musicbrainz-api';
396
397
 
@@ -408,6 +409,25 @@ coverArtArchiveApiClient.getReleaseCovers(releaseMbid, 'back').then(releaseCover
408
409
 
409
410
  ```
410
411
 
412
+ ### Release Group Cover Art
413
+ ```js
414
+ import {CoverArtArchiveApi} from 'musicbrainz-api';
415
+
416
+ coverArtArchiveApiClient.getReleaseGroupCovers(releaseGroupMbid).then(releaseGroupCoverInfo => {
417
+ console.log('Release cover info', releaseGroupCoverInfo);
418
+ });
419
+
420
+ coverArtArchiveApiClient.getReleaseGroupCovers(releaseGroupMbid, 'front').then(releaseGroupCoverInfo => {
421
+ console.log('Get best front cover', releaseGroupCoverInfo);
422
+ });
423
+
424
+ coverArtArchiveApiClient.getReleaseGroupCovers(releaseGroupMbid, 'back').then(releaseGroupCoverInfo => {
425
+ console.log('Get best back cover', releaseGroupCoverInfo);
426
+ });
427
+
428
+ ```
429
+
430
+
411
431
  ## Compatibility
412
432
 
413
433
  The JavaScript in runtime is compliant with [ECMAScript 2017 (ES8)](https://en.wikipedia.org/wiki/ECMAScript#8th_Edition_-_ECMAScript_2017).
@@ -5,9 +5,9 @@ export class CoverArtArchiveApi {
5
5
  this.host = 'coverartarchive.org';
6
6
  }
7
7
  async getJson(path) {
8
- const response = await got.get('https://' + this.host + path, {
8
+ const response = await got.get(`https://${this.host}${path}`, {
9
9
  headers: {
10
- Accept: `application/json`
10
+ Accept: "application/json"
11
11
  },
12
12
  responseType: 'json'
13
13
  });
@@ -39,14 +39,15 @@ export class CoverArtArchiveApi {
39
39
  * @param coverType Cover type
40
40
  */
41
41
  async getCovers(releaseId, releaseType = 'release', coverType) {
42
+ var _a;
42
43
  const path = [releaseType, releaseId];
43
44
  if (coverType) {
44
45
  path.push(coverType);
45
46
  }
46
- const info = await this.getJson('/' + path.join('/'));
47
+ const info = await this.getJson(`/${path.join('/')}`);
47
48
  // Hack to correct http addresses into https
48
- if (info.release && info.release.startsWith('http:')) {
49
- info.release = 'https' + info.release.substring(4);
49
+ if ((_a = info.release) === null || _a === void 0 ? void 0 : _a.startsWith('http:')) {
50
+ info.release = `https${info.release.substring(4)}`;
50
51
  }
51
52
  return info;
52
53
  }
@@ -1,5 +1,5 @@
1
1
  import { v4 as uuidv4 } from 'uuid';
2
- import * as crypto from 'crypto';
2
+ import * as crypto from 'node:crypto';
3
3
  function md5(str) {
4
4
  return crypto.createHash('md5').update(str).digest('hex'); // lgtm [js/insufficient-password-hash]
5
5
  }
@@ -13,8 +13,8 @@ export class DigestAuth {
13
13
  * HA1=MD5(MD5(username:realm:password):nonce:cnonce)
14
14
  */
15
15
  static ha1Compute(algorithm, user, realm, pass, nonce, cnonce) {
16
- const ha1 = md5(user + ':' + realm + ':' + pass); // lgtm [js/insufficient-password-hash]
17
- return algorithm && algorithm.toLowerCase() === 'md5-sess' ? md5(ha1 + ':' + nonce + ':' + cnonce) : ha1;
16
+ const ha1 = md5(`${user}:${realm}:${pass}`); // lgtm [js/insufficient-password-hash]
17
+ return algorithm && algorithm.toLowerCase() === 'md5-sess' ? md5(`${ha1}:${nonce}:${cnonce}`) : ha1;
18
18
  }
19
19
  constructor(credentials) {
20
20
  this.credentials = credentials;
@@ -44,10 +44,10 @@ export class DigestAuth {
44
44
  const nc = qop && '00000001';
45
45
  const cnonce = qop && uuidv4().replace(/-/g, '');
46
46
  const ha1 = DigestAuth.ha1Compute(challenge.algorithm, this.credentials.username, challenge.realm, this.credentials.password, challenge.nonce, cnonce);
47
- const ha2 = md5(method + ':' + path); // lgtm [js/insufficient-password-hash]
47
+ const ha2 = md5(`${method}:${path}`); // lgtm [js/insufficient-password-hash]
48
48
  const digestResponse = qop
49
- ? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2) // lgtm [js/insufficient-password-hash]
50
- : md5(ha1 + ':' + challenge.nonce + ':' + ha2); // lgtm [js/insufficient-password-hash]
49
+ ? md5(`${ha1}:${challenge.nonce}:${nc}:${cnonce}:${qop}:${ha2}`) // lgtm [js/insufficient-password-hash]
50
+ : md5(`${ha1}:${challenge.nonce}:${ha2}`); // lgtm [js/insufficient-password-hash]
51
51
  const authValues = {
52
52
  username: this.credentials.username,
53
53
  realm: challenge.realm,
@@ -64,16 +64,16 @@ export class DigestAuth {
64
64
  Object.entries(authValues).forEach(([key, value]) => {
65
65
  if (value) {
66
66
  if (key === 'qop' || key === 'nc' || key === 'algorithm') {
67
- parts.push(key + '=' + value);
67
+ parts.push(`${key}=${value}`);
68
68
  }
69
69
  else {
70
- parts.push(key + '="' + value + '"');
70
+ parts.push(`${key}="${value}"`);
71
71
  }
72
72
  }
73
73
  });
74
- authHeader = 'Digest ' + parts.join(', ');
74
+ const digest = `Digest ${parts.join(', ')}`;
75
75
  this.sentAuth = true;
76
- return authHeader;
76
+ return digest;
77
77
  }
78
78
  }
79
79
  //# sourceMappingURL=digest-auth.js.map
@@ -2,7 +2,7 @@ export { XmlMetadata } from './xml/xml-metadata.js';
2
2
  export { XmlIsrc } from './xml/xml-isrc.js';
3
3
  export { XmlIsrcList } from './xml/xml-isrc-list.js';
4
4
  export { XmlRecording } from './xml/xml-recording.js';
5
- import { XmlMetadata } from './xml/xml-metadata.js';
5
+ import type { XmlMetadata } from './xml/xml-metadata.js';
6
6
  import * as mb from './musicbrainz.types.js';
7
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';
@@ -1,4 +1,4 @@
1
- import * as assert from 'assert';
1
+ import * as assert from 'node:assert';
2
2
  import { StatusCodes as HttpStatus } from 'http-status-codes';
3
3
  import Debug from 'debug';
4
4
  export { XmlMetadata } from './xml/xml-metadata.js';
@@ -11,7 +11,7 @@ import * as mb from './musicbrainz.types.js';
11
11
  import got from 'got';
12
12
  import { CookieJar } from 'tough-cookie';
13
13
  export * from './musicbrainz.types.js';
14
- import { promisify } from 'util';
14
+ import { promisify } from 'node:util';
15
15
  const debug = Debug('musicbrainz-api');
16
16
  export class MusicBrainzApi {
17
17
  static fetchCsrf(html) {
@@ -55,7 +55,7 @@ export class MusicBrainzApi {
55
55
  query.fmt = 'json';
56
56
  const delay = await this.rateLimiter.limit();
57
57
  debug(`Client side rate limiter activated: cool down for ${Math.round(delay / 100) / 10} s...`);
58
- const response = await got.get('ws/2' + relUrl, {
58
+ const response = await got.get(`ws/2${relUrl}`, {
59
59
  ...this.options,
60
60
  searchParams: query,
61
61
  responseType: 'json',
@@ -79,7 +79,7 @@ export class MusicBrainzApi {
79
79
  if (Array.isArray(query.inc)) {
80
80
  urlQuery.inc = urlQuery.inc.join(' ');
81
81
  }
82
- return this.restGet('/' + entity + '/', urlQuery);
82
+ return this.restGet(`/${entity}/`, urlQuery);
83
83
  }
84
84
  // ---------------------------------------------------------------------------
85
85
  async postRecording(xmlMetadata) {
@@ -87,7 +87,7 @@ export class MusicBrainzApi {
87
87
  }
88
88
  async post(entity, xmlMetadata) {
89
89
  if (!this.config.appName || !this.config.appVersion) {
90
- throw new Error(`XML-Post requires the appName & appVersion to be defined`);
90
+ throw new Error("XML-Post requires the appName & appVersion to be defined");
91
91
  }
92
92
  const clientId = `${this.config.appName.replace(/-/g, '.')}-${this.config.appVersion}`;
93
93
  const path = `ws/2/${entity}/`;
@@ -120,10 +120,10 @@ export class MusicBrainzApi {
120
120
  } while (n++ < 5);
121
121
  }
122
122
  async login() {
123
- var _a, _b;
123
+ var _a, _b, _c;
124
124
  assert.ok((_a = this.config.botAccount) === null || _a === void 0 ? void 0 : _a.username, 'bot username should be set');
125
125
  assert.ok((_b = this.config.botAccount) === null || _b === void 0 ? void 0 : _b.password, 'bot password should be set');
126
- if (this.session && this.session.loggedIn) {
126
+ if ((_c = this.session) === null || _c === void 0 ? void 0 : _c.loggedIn) {
127
127
  for (const cookie of await this.getCookies(this.options.prefixUrl)) {
128
128
  if (cookie.key === 'remember_login') {
129
129
  return true;
@@ -192,7 +192,7 @@ export class MusicBrainzApi {
192
192
  followRedirect: false
193
193
  });
194
194
  if (response.statusCode === HttpStatus.OK)
195
- throw new Error(`Failed to submit form data`);
195
+ throw new Error("Failed to submit form data");
196
196
  if (response.statusCode === HttpStatus.MOVED_TEMPORARILY)
197
197
  return;
198
198
  throw new Error(`Unexpected status code: ${response.statusCode}`);
@@ -224,7 +224,7 @@ export class MusicBrainzApi {
224
224
  */
225
225
  async addIsrc(recording, isrc) {
226
226
  const formData = {};
227
- formData[`edit-recording.name`] = recording.title; // Required
227
+ formData["edit-recording.name"] = recording.title; // Required
228
228
  if (!recording.isrcs) {
229
229
  throw new Error('You must retrieve recording with existing ISRC values');
230
230
  }
@@ -250,7 +250,7 @@ export class MusicBrainzApi {
250
250
  assert.strictEqual(spotifyId.length, 22);
251
251
  return this.addUrlToRecording(recording, {
252
252
  linkTypeId: mb.LinkType.stream_for_free,
253
- text: 'https://open.spotify.com/track/' + spotifyId
253
+ text: `https://open.spotify.com/track/${spotifyId}`
254
254
  }, editNote);
255
255
  }
256
256
  async getSession() {
@@ -36,7 +36,7 @@ export interface IArtist extends IEntity {
36
36
  'gender-id'?: string;
37
37
  'life-span'?: IPeriod;
38
38
  country?: string;
39
- ipis?: any[];
39
+ ipis?: string[];
40
40
  isnis?: string[];
41
41
  aliases?: IAlias[];
42
42
  gender?: string;
@@ -200,13 +200,13 @@ export interface IUrlList extends ISearchResult {
200
200
  }
201
201
  export type RelationDirection = 'backward' | 'forward';
202
202
  export interface IRelation {
203
- 'attribute-ids': any;
203
+ 'attribute-ids': unknown[];
204
204
  direction: RelationDirection;
205
205
  'target-credit': string;
206
206
  end: null | unknown;
207
207
  'source-credit': string;
208
208
  ended: boolean;
209
- 'attribute-values': unknown;
209
+ 'attribute-values': unknown[];
210
210
  attributes?: any[];
211
211
  type: string;
212
212
  begin?: null | unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musicbrainz-api",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "MusicBrainz API client for reading and submitting metadata",
5
5
  "exports": "./lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -45,9 +45,10 @@
45
45
  "url": "https://github.com/Borewit/musicbrainz-api/issues"
46
46
  },
47
47
  "dependencies": {
48
+ "@biomejs/biome": "1.8.3",
48
49
  "@types/caseless": "^0.12.1",
49
50
  "@types/request-promise-native": "^1.0.17",
50
- "@types/uuid": "^9.0.0",
51
+ "@types/uuid": "^10.0.0",
51
52
  "caseless": "^0.12.0",
52
53
  "debug": "^4.3.4",
53
54
  "got": "^14.2.1",
@@ -57,43 +58,34 @@
57
58
  "rate-limit-threshold": "^0.1.5",
58
59
  "source-map-support": "^0.5.16",
59
60
  "tough-cookie": "^4.1.3",
60
- "uuid": "^9.0.0"
61
+ "uuid": "^10.0.0"
61
62
  },
62
63
  "devDependencies": {
63
64
  "@types/chai": "^4.3.0",
64
65
  "@types/jsontoxml": "^1.0.5",
65
66
  "@types/mocha": "^10.0.4",
66
- "@types/node": "^20.8.10",
67
- "@typescript-eslint/eslint-plugin": "^5.13.0",
68
- "@typescript-eslint/parser": "^5.13.0",
69
- "c8": "^9.1.0",
67
+ "@types/node": "^22.1.0",
68
+ "c8": "^10.1.2",
70
69
  "chai": "^5.1.0",
71
70
  "del-cli": "^5.0.0",
72
- "eslint": "^8.10.0",
73
- "eslint-config-prettier": "^9.0.0",
74
- "eslint-import-resolver-typescript": "^3.3.0",
75
- "eslint-plugin-import": "^2.25.4",
76
- "eslint-plugin-jsdoc": "^48.2.2",
77
- "eslint-plugin-node": "^11.1.0",
78
- "eslint-plugin-unicorn": "^49.0.0",
79
71
  "mocha": "^10.1.0",
80
72
  "remark-cli": "^12.0.0",
81
- "remark-preset-lint-recommended": "^6.1.2",
73
+ "remark-preset-lint-recommended": "^7.0.0",
82
74
  "ts-node": "^10.0.0",
83
75
  "typescript": "^5.0.2"
84
76
  },
85
77
  "scripts": {
86
- "clean": "del-cli lib/**/*.js lib/**/*.js.map lib/**/*.d.ts test/**/*.js test/**/*.js.map",
78
+ "clean": "del-cli 'lib/**/*.js' 'lib/**/*.js.map' 'lib/**/*.d.ts' 'test/**/*.js' 'test/**/*.js.map'",
87
79
  "compile-lib": "tsc -p lib",
88
80
  "compile-test": "tsc -p test",
89
- "compile": "npm run compile-lib && npm run compile-test",
90
- "eslint": "eslint lib/**/*.ts --ignore-pattern lib/**/*.d.ts test/**/*.ts",
81
+ "compile": "yarn run compile-lib && yarn run compile-test",
91
82
  "lint-md": "remark -u preset-lint-recommended .",
92
- "lint": "npm run lint-md && npm run eslint",
83
+ "lint-ts": "biome check",
84
+ "lint": "yarn run lint-md && yarn run lint-ts",
93
85
  "test": "mocha",
94
- "build": "npm run clean && npm run compile",
95
- "start": "npm-run-all compile lint cover-test",
96
- "test-coverage": "c8 npm run test",
86
+ "build": "yarn run clean && yarn run compile",
87
+ "start": "yarn run compile && yarn run lint && yarn run cover-test",
88
+ "test-coverage": "c8 yarn run test",
97
89
  "send-codacy": "nyc report --reporter=text-lcov | codacy-coverage"
98
90
  },
99
91
  "nyc": {