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 +42 -15
- package/lib/coverartarchive-api.d.ts +31 -0
- package/lib/coverartarchive-api.js +33 -0
- package/lib/digest-auth.js +4 -8
- package/lib/index.d.ts +2 -0
- package/lib/index.js +3 -0
- package/lib/musicbrainz-api.d.ts +12 -7
- package/lib/musicbrainz-api.js +78 -86
- package/lib/musicbrainz.types.d.ts +7 -1
- package/lib/musicbrainz.types.js +2 -5
- package/lib/rate-limiter.d.ts +1 -1
- package/lib/rate-limiter.js +6 -8
- package/lib/xml/xml-isrc-list.d.ts +1 -1
- package/lib/xml/xml-isrc-list.js +3 -7
- package/lib/xml/xml-isrc.js +1 -5
- package/lib/xml/xml-metadata.d.ts +1 -1
- package/lib/xml/xml-metadata.js +4 -8
- package/lib/xml/xml-recording.d.ts +1 -1
- package/lib/xml/xml-recording.js +3 -7
- package/package.json +22 -15
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
|
-
|
|
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'
|
|
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 '
|
|
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
|
package/lib/digest-auth.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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 && (
|
|
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
package/lib/index.js
ADDED
package/lib/musicbrainz-api.d.ts
CHANGED
|
@@ -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
|
package/lib/musicbrainz-api.js
CHANGED
|
@@ -1,43 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
95
|
-
this.getCookies =
|
|
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:
|
|
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
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
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
|
-
},
|
|
367
|
-
|
|
340
|
+
},
|
|
341
|
+
body: postData,
|
|
342
|
+
throwHttpErrors: false
|
|
343
|
+
});
|
|
344
|
+
if (response.statusCode === HttpStatus.UNAUTHORIZED) {
|
|
368
345
|
// Respond to digest challenge
|
|
369
|
-
const auth = new
|
|
370
|
-
const relPath =
|
|
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
|
|
375
|
+
const response = await got.post('login', {
|
|
376
|
+
...this.options,
|
|
377
|
+
followRedirect: false,
|
|
378
|
+
searchParams: {
|
|
399
379
|
returnto: redirectUri
|
|
400
|
-
},
|
|
401
|
-
|
|
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
|
|
394
|
+
const response = await got.get('logout', {
|
|
395
|
+
...this.options,
|
|
396
|
+
followRedirect: false,
|
|
397
|
+
searchParams: {
|
|
413
398
|
returnto: redirectUri
|
|
414
|
-
}
|
|
415
|
-
|
|
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
|
|
436
|
-
|
|
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 ===
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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;
|
package/lib/musicbrainz.types.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
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 =
|
|
13
|
+
})(LinkType = LinkType || (LinkType = {}));
|
|
17
14
|
//# sourceMappingURL=musicbrainz.types.js.map
|
package/lib/rate-limiter.d.ts
CHANGED
package/lib/rate-limiter.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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(
|
|
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
|
package/lib/xml/xml-isrc-list.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
|
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
|
package/lib/xml/xml-isrc.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
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
|
package/lib/xml/xml-metadata.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2#ISRC_submission
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
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
|
package/lib/xml/xml-recording.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
|
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.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "MusicBrainz API client for reading and submitting metadata",
|
|
5
|
-
"
|
|
6
|
-
"types": "lib/
|
|
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.
|
|
43
|
-
"got": "^
|
|
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": "^
|
|
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
|
|
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": "
|
|
97
|
+
"test-coverage": "c8 npm run test",
|
|
91
98
|
"send-codacy": "nyc report --reporter=text-lcov | codacy-coverage"
|
|
92
99
|
},
|
|
93
100
|
"nyc": {
|