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 +20 -0
- package/lib/coverartarchive-api.js +6 -5
- package/lib/digest-auth.js +10 -10
- package/lib/musicbrainz-api.d.ts +1 -1
- package/lib/musicbrainz-api.js +10 -10
- package/lib/musicbrainz.types.d.ts +3 -3
- package/package.json +14 -22
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(
|
|
8
|
+
const response = await got.get(`https://${this.host}${path}`, {
|
|
9
9
|
headers: {
|
|
10
|
-
Accept:
|
|
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(
|
|
47
|
+
const info = await this.getJson(`/${path.join('/')}`);
|
|
47
48
|
// Hack to correct http addresses into https
|
|
48
|
-
if (info.release
|
|
49
|
-
info.release =
|
|
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
|
}
|
package/lib/digest-auth.js
CHANGED
|
@@ -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
|
|
17
|
-
return algorithm && algorithm.toLowerCase() === 'md5-sess' ? md5(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
|
|
47
|
+
const ha2 = md5(`${method}:${path}`); // lgtm [js/insufficient-password-hash]
|
|
48
48
|
const digestResponse = qop
|
|
49
|
-
? md5(ha1
|
|
50
|
-
: md5(ha1
|
|
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
|
|
67
|
+
parts.push(`${key}=${value}`);
|
|
68
68
|
}
|
|
69
69
|
else {
|
|
70
|
-
parts.push(key
|
|
70
|
+
parts.push(`${key}="${value}"`);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
|
-
|
|
74
|
+
const digest = `Digest ${parts.join(', ')}`;
|
|
75
75
|
this.sentAuth = true;
|
|
76
|
-
return
|
|
76
|
+
return digest;
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
//# sourceMappingURL=digest-auth.js.map
|
package/lib/musicbrainz-api.d.ts
CHANGED
|
@@ -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';
|
package/lib/musicbrainz-api.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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[
|
|
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:
|
|
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?:
|
|
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':
|
|
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.
|
|
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": "^
|
|
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": "^
|
|
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": "^
|
|
67
|
-
"
|
|
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": "^
|
|
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": "
|
|
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": "
|
|
83
|
+
"lint-ts": "biome check",
|
|
84
|
+
"lint": "yarn run lint-md && yarn run lint-ts",
|
|
93
85
|
"test": "mocha",
|
|
94
|
-
"build": "
|
|
95
|
-
"start": "
|
|
96
|
-
"test-coverage": "c8
|
|
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": {
|