musicbrainz-api 0.5.2 → 0.7.2
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/.idea/$CACHE_FILE$ +6 -0
- package/.idea/$PRODUCT_WORKSPACE_FILE$ +19 -0
- package/.idea/checkstyle-idea.xml +16 -0
- package/.idea/codeStyles/Project.xml +38 -0
- package/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/shelf/Uncommitted_changes_before_Update_at_6-1-2022_11_38_[Default_Changelist]/shelved.patch +58 -0
- package/.idea/shelf/Uncommitted_changes_before_Update_at_6-1-2022_11_38__Default_Changelist_.xml +4 -0
- package/.idea/shelf/Uncommitted_changes_before_rebase_[Default_Changelist]/shelved.patch +738 -0
- package/.idea/shelf/Uncommitted_changes_before_rebase_[Default_Changelist]1/shelved.patch +0 -0
- package/.idea/shelf/Uncommitted_changes_before_rebase__Default_Changelist_.xml +4 -0
- package/.idea/vcs.xml +6 -0
- package/.idea/workspace.xml +722 -0
- package/README.md +290 -287
- package/etc/config.js +32 -0
- package/lib/digest-auth.d.ts +21 -21
- package/lib/digest-auth.js +87 -86
- package/lib/musicbrainz-api.d.ts +156 -140
- package/lib/musicbrainz-api.js +390 -372
- package/lib/musicbrainz.types.d.ts +379 -252
- package/lib/musicbrainz.types.js +16 -15
- package/lib/rate-limiter.d.ts +8 -8
- package/lib/rate-limiter.js +31 -30
- package/lib/xml/xml-isrc-list.d.ts +17 -17
- package/lib/xml/xml-isrc-list.js +22 -21
- package/lib/xml/xml-isrc.d.ts +10 -10
- package/lib/xml/xml-isrc.js +17 -16
- package/lib/xml/xml-metadata.d.ts +6 -6
- package/lib/xml/xml-metadata.js +29 -28
- package/lib/xml/xml-recording.d.ts +24 -24
- package/lib/xml/xml-recording.js +20 -19
- package/package.json +98 -99
- package/yarn-error.log +3608 -0
package/lib/musicbrainz-api.js
CHANGED
|
@@ -1,373 +1,391 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
function
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
exports.XmlIsrc =
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
case '
|
|
60
|
-
case '
|
|
61
|
-
case '
|
|
62
|
-
case '
|
|
63
|
-
case '
|
|
64
|
-
case '
|
|
65
|
-
case '
|
|
66
|
-
case '
|
|
67
|
-
case '
|
|
68
|
-
case '
|
|
69
|
-
case '
|
|
70
|
-
case '
|
|
71
|
-
case '
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
*
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
*
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
*
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
assert.ok(this.config.botAccount.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
*
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
return this.
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
return this.
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
10
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.makeAndQueryString = exports.MusicBrainzApi = exports.XmlRecording = exports.XmlIsrcList = exports.XmlIsrc = exports.XmlMetadata = void 0;
|
|
14
|
+
const assert = require("assert");
|
|
15
|
+
const http_status_codes_1 = require("http-status-codes");
|
|
16
|
+
const Url = require("url");
|
|
17
|
+
const Debug = require("debug");
|
|
18
|
+
var xml_metadata_1 = require("./xml/xml-metadata");
|
|
19
|
+
Object.defineProperty(exports, "XmlMetadata", { enumerable: true, get: function () { return xml_metadata_1.XmlMetadata; } });
|
|
20
|
+
var xml_isrc_1 = require("./xml/xml-isrc");
|
|
21
|
+
Object.defineProperty(exports, "XmlIsrc", { enumerable: true, get: function () { return xml_isrc_1.XmlIsrc; } });
|
|
22
|
+
var xml_isrc_list_1 = require("./xml/xml-isrc-list");
|
|
23
|
+
Object.defineProperty(exports, "XmlIsrcList", { enumerable: true, get: function () { return xml_isrc_list_1.XmlIsrcList; } });
|
|
24
|
+
var xml_recording_1 = require("./xml/xml-recording");
|
|
25
|
+
Object.defineProperty(exports, "XmlRecording", { enumerable: true, get: function () { return xml_recording_1.XmlRecording; } });
|
|
26
|
+
const digest_auth_1 = require("./digest-auth");
|
|
27
|
+
const rate_limiter_1 = require("./rate-limiter");
|
|
28
|
+
const mb = require("./musicbrainz.types");
|
|
29
|
+
const got_1 = require("got");
|
|
30
|
+
const tough = require("tough-cookie");
|
|
31
|
+
__exportStar(require("./musicbrainz.types"), exports);
|
|
32
|
+
const util_1 = require("util");
|
|
33
|
+
const retries = 3;
|
|
34
|
+
const debug = Debug('musicbrainz-api');
|
|
35
|
+
class MusicBrainzApi {
|
|
36
|
+
constructor(_config) {
|
|
37
|
+
this.config = {
|
|
38
|
+
baseUrl: 'https://musicbrainz.org'
|
|
39
|
+
};
|
|
40
|
+
Object.assign(this.config, _config);
|
|
41
|
+
const cookieJar = new tough.CookieJar();
|
|
42
|
+
this.getCookies = (0, util_1.promisify)(cookieJar.getCookies.bind(cookieJar));
|
|
43
|
+
this.options = {
|
|
44
|
+
prefixUrl: this.config.baseUrl,
|
|
45
|
+
timeout: 20 * 1000,
|
|
46
|
+
headers: {
|
|
47
|
+
'User-Agent': `${this.config.appName}/${this.config.appVersion} ( ${this.config.appContactInfo} )`
|
|
48
|
+
},
|
|
49
|
+
cookieJar
|
|
50
|
+
};
|
|
51
|
+
this.rateLimiter = new rate_limiter_1.RateLimiter(60, 50);
|
|
52
|
+
}
|
|
53
|
+
static escapeText(text) {
|
|
54
|
+
let str = '';
|
|
55
|
+
for (const chr of text) {
|
|
56
|
+
// Escaping Special Characters: + - && || ! ( ) { } [ ] ^ " ~ * ? : \ /
|
|
57
|
+
// ToDo: && ||
|
|
58
|
+
switch (chr) {
|
|
59
|
+
case '+':
|
|
60
|
+
case '-':
|
|
61
|
+
case '!':
|
|
62
|
+
case '(':
|
|
63
|
+
case ')':
|
|
64
|
+
case '{':
|
|
65
|
+
case '}':
|
|
66
|
+
case '[':
|
|
67
|
+
case ']':
|
|
68
|
+
case '^':
|
|
69
|
+
case '"':
|
|
70
|
+
case '~':
|
|
71
|
+
case '*':
|
|
72
|
+
case '?':
|
|
73
|
+
case ':':
|
|
74
|
+
case '\\':
|
|
75
|
+
case '/':
|
|
76
|
+
str += '\\';
|
|
77
|
+
}
|
|
78
|
+
str += chr;
|
|
79
|
+
}
|
|
80
|
+
return str;
|
|
81
|
+
}
|
|
82
|
+
static fetchCsrf(html) {
|
|
83
|
+
return {
|
|
84
|
+
sessionKey: MusicBrainzApi.fetchValue(html, 'csrf_session_key'),
|
|
85
|
+
token: MusicBrainzApi.fetchValue(html, 'csrf_token')
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
static fetchValue(html, key) {
|
|
89
|
+
let pos = html.indexOf(`name="${key}"`);
|
|
90
|
+
if (pos >= 0) {
|
|
91
|
+
pos = html.indexOf('value="', pos + key.length + 7);
|
|
92
|
+
if (pos >= 0) {
|
|
93
|
+
pos += 7;
|
|
94
|
+
const endValuePos = html.indexOf('"', pos);
|
|
95
|
+
const value = html.substring(pos, endValuePos);
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async restGet(relUrl, query = {}, attempt = 1) {
|
|
101
|
+
query.fmt = 'json';
|
|
102
|
+
let response;
|
|
103
|
+
await this.rateLimiter.limit();
|
|
104
|
+
do {
|
|
105
|
+
response = await got_1.default.get('ws/2' + relUrl, Object.assign({ searchParams: query, responseType: 'json' }, this.options));
|
|
106
|
+
if (response.statusCode !== 503)
|
|
107
|
+
break;
|
|
108
|
+
debug('Rate limiter kicked in, slowing down...');
|
|
109
|
+
await rate_limiter_1.RateLimiter.sleep(500);
|
|
110
|
+
} while (true);
|
|
111
|
+
switch (response.statusCode) {
|
|
112
|
+
case http_status_codes_1.StatusCodes.OK:
|
|
113
|
+
return response.body;
|
|
114
|
+
case http_status_codes_1.StatusCodes.BAD_REQUEST:
|
|
115
|
+
case http_status_codes_1.StatusCodes.NOT_FOUND:
|
|
116
|
+
throw new Error(`Got response status ${response.statusCode}: ${(0, http_status_codes_1.getReasonPhrase)(response.status)}`);
|
|
117
|
+
case http_status_codes_1.StatusCodes.SERVICE_UNAVAILABLE: // 503
|
|
118
|
+
default:
|
|
119
|
+
const msg = `Got response status ${response.statusCode} on attempt #${attempt} (${(0, http_status_codes_1.getReasonPhrase)(response.status)})`;
|
|
120
|
+
debug(msg);
|
|
121
|
+
if (attempt < retries) {
|
|
122
|
+
return this.restGet(relUrl, query, attempt + 1);
|
|
123
|
+
}
|
|
124
|
+
else
|
|
125
|
+
throw new Error(msg);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
129
|
+
// Lookup functions
|
|
130
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
131
|
+
/**
|
|
132
|
+
* Generic lookup function
|
|
133
|
+
* @param entity
|
|
134
|
+
* @param mbid
|
|
135
|
+
* @param inc
|
|
136
|
+
*/
|
|
137
|
+
getEntity(entity, mbid, inc = []) {
|
|
138
|
+
return this.restGet(`/${entity}/${mbid}`, { inc: inc.join(' ') });
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Lookup area
|
|
142
|
+
* @param areaId Area MBID
|
|
143
|
+
* @param inc Sub-queries
|
|
144
|
+
*/
|
|
145
|
+
getArea(areaId, inc = []) {
|
|
146
|
+
return this.getEntity('area', areaId, inc);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Lookup artist
|
|
150
|
+
* @param artistId Artist MBID
|
|
151
|
+
* @param inc Sub-queries
|
|
152
|
+
*/
|
|
153
|
+
getArtist(artistId, inc = []) {
|
|
154
|
+
return this.getEntity('artist', artistId, inc);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Lookup release
|
|
158
|
+
* @param releaseId Release MBID
|
|
159
|
+
* @param inc Include: artist-credits, labels, recordings, release-groups, media, discids, isrcs (with recordings)
|
|
160
|
+
* ToDo: ['recordings', 'artists', 'artist-credits', 'isrcs', 'url-rels', 'release-groups']
|
|
161
|
+
*/
|
|
162
|
+
getRelease(releaseId, inc = []) {
|
|
163
|
+
return this.getEntity('release', releaseId, inc);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Lookup release-group
|
|
167
|
+
* @param releaseGroupId Release-group MBID
|
|
168
|
+
* @param inc Include: ToDo
|
|
169
|
+
*/
|
|
170
|
+
getReleaseGroup(releaseGroupId, inc = []) {
|
|
171
|
+
return this.getEntity('release-group', releaseGroupId, inc);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Lookup work
|
|
175
|
+
* @param workId Work MBID
|
|
176
|
+
*/
|
|
177
|
+
getWork(workId) {
|
|
178
|
+
return this.getEntity('work', workId);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Lookup label
|
|
182
|
+
* @param labelId Label MBID
|
|
183
|
+
*/
|
|
184
|
+
getLabel(labelId) {
|
|
185
|
+
return this.getEntity('label', labelId);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Lookup recording
|
|
189
|
+
* @param recordingId Label MBID
|
|
190
|
+
* @param inc Include: artist-credits, isrcs
|
|
191
|
+
*/
|
|
192
|
+
getRecording(recordingId, inc = []) {
|
|
193
|
+
return this.getEntity('recording', recordingId, inc);
|
|
194
|
+
}
|
|
195
|
+
async postRecording(xmlMetadata) {
|
|
196
|
+
return this.post('recording', xmlMetadata);
|
|
197
|
+
}
|
|
198
|
+
async post(entity, xmlMetadata) {
|
|
199
|
+
if (!this.config.appName || !this.config.appVersion) {
|
|
200
|
+
throw new Error(`XML-Post requires the appName & appVersion to be defined`);
|
|
201
|
+
}
|
|
202
|
+
const clientId = `${this.config.appName.replace(/-/g, '.')}-${this.config.appVersion}`;
|
|
203
|
+
const path = `ws/2/${entity}/`;
|
|
204
|
+
// Get digest challenge
|
|
205
|
+
let digest = null;
|
|
206
|
+
let n = 1;
|
|
207
|
+
const postData = xmlMetadata.toXml();
|
|
208
|
+
do {
|
|
209
|
+
await this.rateLimiter.limit();
|
|
210
|
+
const response = await got_1.default.post(path, Object.assign({ searchParams: { client: clientId }, headers: {
|
|
211
|
+
authorization: digest,
|
|
212
|
+
'Content-Type': 'application/xml'
|
|
213
|
+
}, body: postData, throwHttpErrors: false }, this.options));
|
|
214
|
+
if (response.statusCode === http_status_codes_1.StatusCodes.UNAUTHORIZED) {
|
|
215
|
+
// Respond to digest challenge
|
|
216
|
+
const auth = new digest_auth_1.DigestAuth(this.config.botAccount);
|
|
217
|
+
const relPath = Url.parse(response.requestUrl).path; // Ensure path is relative
|
|
218
|
+
digest = auth.digest(response.request.method, relPath, response.headers['www-authenticate']);
|
|
219
|
+
++n;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
} while (n++ < 5);
|
|
225
|
+
}
|
|
226
|
+
async login() {
|
|
227
|
+
assert.ok(this.config.botAccount.username, 'bot username should be set');
|
|
228
|
+
assert.ok(this.config.botAccount.password, 'bot password should be set');
|
|
229
|
+
if (this.session && this.session.loggedIn) {
|
|
230
|
+
for (const cookie of await this.getCookies(this.options.prefixUrl)) {
|
|
231
|
+
if (cookie.key === 'remember_login') {
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
this.session = await this.getSession(this.config.baseUrl);
|
|
237
|
+
const redirectUri = '/success';
|
|
238
|
+
const formData = {
|
|
239
|
+
username: this.config.botAccount.username,
|
|
240
|
+
password: this.config.botAccount.password,
|
|
241
|
+
csrf_session_key: this.session.csrf.sessionKey,
|
|
242
|
+
csrf_token: this.session.csrf.token,
|
|
243
|
+
remember_me: 1
|
|
244
|
+
};
|
|
245
|
+
const response = await got_1.default.post('login', Object.assign({ followRedirect: false, searchParams: {
|
|
246
|
+
returnto: redirectUri
|
|
247
|
+
}, form: formData }, this.options));
|
|
248
|
+
const success = response.statusCode === http_status_codes_1.StatusCodes.MOVED_TEMPORARILY && response.headers.location === redirectUri;
|
|
249
|
+
if (success) {
|
|
250
|
+
this.session.loggedIn = true;
|
|
251
|
+
}
|
|
252
|
+
return success;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Logout
|
|
256
|
+
*/
|
|
257
|
+
async logout() {
|
|
258
|
+
const redirectUri = '/success';
|
|
259
|
+
const response = await got_1.default.get('logout', Object.assign({ followRedirect: false, searchParams: {
|
|
260
|
+
returnto: redirectUri
|
|
261
|
+
} }, this.options));
|
|
262
|
+
const success = response.statusCode === http_status_codes_1.StatusCodes.MOVED_TEMPORARILY && response.headers.location === redirectUri;
|
|
263
|
+
if (success) {
|
|
264
|
+
this.session.loggedIn = true;
|
|
265
|
+
}
|
|
266
|
+
return success;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Submit entity
|
|
270
|
+
* @param entity Entity type e.g. 'recording'
|
|
271
|
+
* @param mbid
|
|
272
|
+
* @param formData
|
|
273
|
+
*/
|
|
274
|
+
async editEntity(entity, mbid, formData) {
|
|
275
|
+
await this.rateLimiter.limit();
|
|
276
|
+
this.session = await this.getSession(this.config.baseUrl);
|
|
277
|
+
formData.csrf_session_key = this.session.csrf.sessionKey;
|
|
278
|
+
formData.csrf_token = this.session.csrf.token;
|
|
279
|
+
formData.username = this.config.botAccount.username;
|
|
280
|
+
formData.password = this.config.botAccount.password;
|
|
281
|
+
formData.remember_me = 1;
|
|
282
|
+
const response = await got_1.default.post(`${entity}/${mbid}/edit`, Object.assign({ form: formData, followRedirect: false }, this.options));
|
|
283
|
+
if (response.statusCode === http_status_codes_1.StatusCodes.OK)
|
|
284
|
+
throw new Error(`Failed to submit form data`);
|
|
285
|
+
if (response.statusCode === http_status_codes_1.StatusCodes.MOVED_TEMPORARILY)
|
|
286
|
+
return;
|
|
287
|
+
throw new Error(`Unexpected status code: ${response.statusCode}`);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Set URL to recording
|
|
291
|
+
* @param recording Recording to update
|
|
292
|
+
* @param url2add URL to add to the recording
|
|
293
|
+
* @param editNote Edit note
|
|
294
|
+
*/
|
|
295
|
+
async addUrlToRecording(recording, url2add, editNote = '') {
|
|
296
|
+
const formData = {};
|
|
297
|
+
formData['edit-recording.name'] = recording.title; // Required
|
|
298
|
+
formData['edit-recording.comment'] = recording.disambiguation;
|
|
299
|
+
formData['edit-recording.make_votable'] = true;
|
|
300
|
+
formData['edit-recording.url.0.link_type_id'] = url2add.linkTypeId;
|
|
301
|
+
formData['edit-recording.url.0.text'] = url2add.text;
|
|
302
|
+
for (const i in recording.isrcs) {
|
|
303
|
+
formData[`edit-recording.isrcs.${i}`] = recording.isrcs[i];
|
|
304
|
+
}
|
|
305
|
+
formData['edit-recording.edit_note'] = editNote;
|
|
306
|
+
return this.editEntity('recording', recording.id, formData);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Add ISRC to recording
|
|
310
|
+
* @param recording Recording to update
|
|
311
|
+
* @param isrc ISRC code to add
|
|
312
|
+
* @param editNote Edit note
|
|
313
|
+
*/
|
|
314
|
+
async addIsrc(recording, isrc, editNote = '') {
|
|
315
|
+
const formData = {};
|
|
316
|
+
formData[`edit-recording.name`] = recording.title; // Required
|
|
317
|
+
if (!recording.isrcs) {
|
|
318
|
+
throw new Error('You must retrieve recording with existing ISRC values');
|
|
319
|
+
}
|
|
320
|
+
if (recording.isrcs.indexOf(isrc) === -1) {
|
|
321
|
+
recording.isrcs.push(isrc);
|
|
322
|
+
for (const i in recording.isrcs) {
|
|
323
|
+
formData[`edit-recording.isrcs.${i}`] = recording.isrcs[i];
|
|
324
|
+
}
|
|
325
|
+
return this.editEntity('recording', recording.id, formData);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
329
|
+
// Query functions
|
|
330
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
331
|
+
/**
|
|
332
|
+
* Search an entity using a search query
|
|
333
|
+
* @param query e.g.: '" artist: Madonna, track: Like a virgin"' or object with search terms: {artist: Madonna}
|
|
334
|
+
* @param entity e.g. 'recording'
|
|
335
|
+
* @param query Arguments
|
|
336
|
+
*/
|
|
337
|
+
search(entity, query) {
|
|
338
|
+
const urlQuery = Object.assign({}, query);
|
|
339
|
+
if (typeof query.query === 'object') {
|
|
340
|
+
urlQuery.query = makeAndQueryString(query.query);
|
|
341
|
+
}
|
|
342
|
+
if (Array.isArray(query.inc)) {
|
|
343
|
+
urlQuery.inc = urlQuery.inc.join(' ');
|
|
344
|
+
}
|
|
345
|
+
return this.restGet('/' + entity + '/', urlQuery);
|
|
346
|
+
}
|
|
347
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
348
|
+
// Helper functions
|
|
349
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
350
|
+
/**
|
|
351
|
+
* Add Spotify-ID to MusicBrainz recording.
|
|
352
|
+
* This function will automatically lookup the recording title, which is required to submit the recording URL
|
|
353
|
+
* @param recording MBID of the recording
|
|
354
|
+
* @param spotifyId Spotify ID
|
|
355
|
+
* @param editNote Comment to add.
|
|
356
|
+
*/
|
|
357
|
+
addSpotifyIdToRecording(recording, spotifyId, editNote) {
|
|
358
|
+
assert.strictEqual(spotifyId.length, 22);
|
|
359
|
+
return this.addUrlToRecording(recording, {
|
|
360
|
+
linkTypeId: mb.LinkType.stream_for_free,
|
|
361
|
+
text: 'https://open.spotify.com/track/' + spotifyId
|
|
362
|
+
}, editNote);
|
|
363
|
+
}
|
|
364
|
+
searchArea(query) {
|
|
365
|
+
return this.search('area', query);
|
|
366
|
+
}
|
|
367
|
+
searchArtist(query) {
|
|
368
|
+
return this.search('artist', query);
|
|
369
|
+
}
|
|
370
|
+
searchRelease(query) {
|
|
371
|
+
return this.search('release', query);
|
|
372
|
+
}
|
|
373
|
+
searchReleaseGroup(query) {
|
|
374
|
+
return this.search('release-group', query);
|
|
375
|
+
}
|
|
376
|
+
searchUrl(query) {
|
|
377
|
+
return this.search('url', query);
|
|
378
|
+
}
|
|
379
|
+
async getSession(url) {
|
|
380
|
+
const response = await got_1.default.get('login', Object.assign({ followRedirect: false, responseType: 'text' }, this.options));
|
|
381
|
+
return {
|
|
382
|
+
csrf: MusicBrainzApi.fetchCsrf(response.body)
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
exports.MusicBrainzApi = MusicBrainzApi;
|
|
387
|
+
function makeAndQueryString(keyValuePairs) {
|
|
388
|
+
return Object.keys(keyValuePairs).map(key => `${key}:"${keyValuePairs[key]}"`).join(' AND ');
|
|
389
|
+
}
|
|
390
|
+
exports.makeAndQueryString = makeAndQueryString;
|
|
373
391
|
//# sourceMappingURL=musicbrainz-api.js.map
|