musicbrainz-api 0.22.0 → 0.23.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 +99 -7
- package/package.json +2 -2
- package/lib/coverartarchive-api.d.ts +0 -74
- package/lib/coverartarchive-api.js +0 -110
- package/lib/digest-auth.d.ts +0 -21
- package/lib/digest-auth.js +0 -77
- package/lib/entry-default.d.ts +0 -2
- package/lib/entry-default.js +0 -3
- package/lib/entry-node.d.ts +0 -2
- package/lib/entry-node.js +0 -3
- package/lib/http-client-node.d.ts +0 -11
- package/lib/http-client-node.js +0 -19
- package/lib/http-client.d.ts +0 -28
- package/lib/http-client.js +0 -50
- package/lib/musicbrainz-api-node.d.ts +0 -16
- package/lib/musicbrainz-api-node.js +0 -71
- package/lib/musicbrainz-api.d.ts +0 -180
- package/lib/musicbrainz-api.js +0 -207
- package/lib/musicbrainz.types.d.ts +0 -625
- package/lib/musicbrainz.types.js +0 -14
- package/lib/xml/xml-isrc-list.d.ts +0 -17
- package/lib/xml/xml-isrc-list.js +0 -19
- package/lib/xml/xml-isrc.d.ts +0 -10
- package/lib/xml/xml-isrc.js +0 -14
- package/lib/xml/xml-metadata.d.ts +0 -6
- package/lib/xml/xml-metadata.js +0 -26
- package/lib/xml/xml-recording.d.ts +0 -24
- package/lib/xml/xml-recording.js +0 -17
package/README.md
CHANGED
|
@@ -116,18 +116,34 @@ MusicBrainz API documentation: [XML Web Service/Version 2 Lookups](https://wiki.
|
|
|
116
116
|
### Lookup Function
|
|
117
117
|
|
|
118
118
|
```js
|
|
119
|
-
const artist = await mbApi.lookup('artist', 'ab2528d9-719f-4261-8098-21849222a0f2');
|
|
119
|
+
const artist = await mbApi.lookup('artist', 'ab2528d9-719f-4261-8098-21849222a0f2', ['recordings']);
|
|
120
120
|
```
|
|
121
121
|
|
|
122
122
|
Arguments:
|
|
123
|
-
- entity: `'area'` | `'artist'` | `'collection'` | `'instrument'` | `'label'` | `'place'` | `'release'` | `'release-group'` | `'recording'` | `'series'` | `'work'` | `'url'` | `'event'`
|
|
124
|
-
- MBID [(MusicBrainz identifier)](https://wiki.musicbrainz.org/MusicBrainz_Identifier)
|
|
125
|
-
-
|
|
123
|
+
- entity (`string`): `'area'` | `'artist'` | `'collection'` | `'instrument'` | `'label'` | `'place'` | `'release'` | `'release-group'` | `'recording'` | `'series'` | `'work'` | `'url'` | `'event'`
|
|
124
|
+
- MBID (`string`): [(MusicBrainz identifier)](https://wiki.musicbrainz.org/MusicBrainz_Identifier)
|
|
125
|
+
- include arguments (`string[]`), see [Include arguments](#include-arguments)
|
|
126
126
|
|
|
127
|
+
#### Lookup URLs
|
|
127
128
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
There is special method to lookup URL entity / entities by one, or an array of URLs
|
|
130
|
+
([MusicBrainz documentation](https://musicbrainz.org/doc/MusicBrainz_API#url_(by_text))):
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
const urls = await mbApi.lookupUrl(['https://open.spotify.com/track/2AMysGXOe0zzZJMtH3Nizb', 'https://open.spotify.com/track/78Teboqh9lPIxWlIW5RMQL']);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
or
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
const url = await mbApi.lookupUrl('https://open.spotify.com/track/2AMysGXOe0zzZJMtH3Nizb']);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Arguments:
|
|
143
|
+
- url (`string` | `string[]`): URL or array of URLs
|
|
144
|
+
- include arguments (`string[]`), see [Include arguments](#include-arguments)
|
|
145
|
+
|
|
146
|
+
Note that the return type is different, depending on if a single URL or an array of URLs is provided.
|
|
131
147
|
|
|
132
148
|
### Browse artist
|
|
133
149
|
|
|
@@ -355,6 +371,82 @@ Search a combination of a release-group and an artist.
|
|
|
355
371
|
const result = await mbApi.search('release-group', {artist: 'Racine carrée', releasegroup: 'Stromae'});
|
|
356
372
|
```
|
|
357
373
|
|
|
374
|
+
## Include arguments
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
### Subqueries
|
|
378
|
+
|
|
379
|
+
_Include Arguments_ which allow you to request more information to be included about the entity.
|
|
380
|
+
|
|
381
|
+
| entity | supported _include arguments_ |
|
|
382
|
+
|------------------|----------------------------------------------------------------------------|
|
|
383
|
+
| `area` | |
|
|
384
|
+
| `artist` | `recordings`, `releases`, `release-groups`, `works` |
|
|
385
|
+
| `collection` | `user-collections` (includes private collections, requires authentication) |
|
|
386
|
+
| `event` | |
|
|
387
|
+
| `genre` | |
|
|
388
|
+
| `instrument` | |
|
|
389
|
+
| `label` | `releases` |
|
|
390
|
+
| `place` | |
|
|
391
|
+
| `recording` | `artists`, `releases`, `release-groups`, `isrcs`, `url-rels` |
|
|
392
|
+
| `release` | `artists`, `collections`, `labels`, `recordings`, `release-groups` |
|
|
393
|
+
| `release-group` | `artists`, `releases` |
|
|
394
|
+
| `series` | |
|
|
395
|
+
| `work` | |
|
|
396
|
+
| `url` | |
|
|
397
|
+
|
|
398
|
+
### Arguments which affect subqueries
|
|
399
|
+
|
|
400
|
+
Some additional _include parameters_ are supported to specify how much of the data about the linked entities should be included:
|
|
401
|
+
|
|
402
|
+
| _include argument_ | Description |
|
|
403
|
+
|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
404
|
+
| `discids` | include discids for all media in the releases |
|
|
405
|
+
| `media` | include media for all releases, this includes the # of tracks on each medium and its format. |
|
|
406
|
+
| `isrcs` | user-collections (includes private collections, requires authentication)include isrcs for all recordings |
|
|
407
|
+
| `artist-credits` | include artists credits for all releases and recordings |
|
|
408
|
+
| `various-artists` | include only those releases where the artist appears on one of the tracks, but not in the artist credit for the release itself (this is only valid on entity `"artist"` and _include argument_ `"releases request"`). |
|
|
409
|
+
|
|
410
|
+
### Miscellaneous arguments
|
|
411
|
+
|
|
412
|
+
| _include argument_ | Description |
|
|
413
|
+
|-----------------------------|----------------------------------------------------------------------------------------------------------------------------------------|
|
|
414
|
+
| `aliases` | include artist, label, area or work aliases; treat these as a set, as they are not deliberately ordered |
|
|
415
|
+
| `annotation` | include annotation |
|
|
416
|
+
| `tags`, `ratings` | include tags and/or ratings for the entity |
|
|
417
|
+
| `user-tags`, `user-ratings` | same as above, but only return the tags and/or ratings submitted by the specified user |
|
|
418
|
+
| `genres`, `user-genres` | include genres (tags in the [genres list](https://musicbrainz.org/genres)): either all or the ones submitted by the user, respectively |
|
|
419
|
+
|
|
420
|
+
### Relationships
|
|
421
|
+
|
|
422
|
+
You can request relationships with the appropriate includes:
|
|
423
|
+
- `area-rels`
|
|
424
|
+
- `artist-rels`
|
|
425
|
+
- `event-rels`
|
|
426
|
+
- `genre-rels`
|
|
427
|
+
- `instrument-rels`
|
|
428
|
+
- `label-rels`
|
|
429
|
+
- `place-rels`
|
|
430
|
+
- `recording-rels`
|
|
431
|
+
- `release-rels`
|
|
432
|
+
- `release-group-rels`
|
|
433
|
+
- `series-rels`
|
|
434
|
+
- `url-rels`
|
|
435
|
+
- `work-rels`
|
|
436
|
+
|
|
437
|
+
These will load relationships between the requested entity and the specific entity type.
|
|
438
|
+
For example, if you request "work-rels" when looking up an artist,
|
|
439
|
+
you'll get all the relationships between this artist and any works,
|
|
440
|
+
and if you request "artist-rels" you'll get the relationships between this artist and any other artists.
|
|
441
|
+
As such, keep in mind requesting "artist-rels" for an artist, "release-rels" for a release, etc. will not load all the relationships for the entity, just the ones to other entities of the same type.
|
|
442
|
+
|
|
443
|
+
In a release request, you might also be interested on relationships for the recordings linked to the release, or the release group linked to the release, or even for the works linked to those recordings that are linked to the release (for example, to find out who played guitar on a specific track, who wrote the lyrics for the song being performed, or whether the release group is part of a series). Similarly, for a recording request, you might want to get the relationships for any linked works.
|
|
444
|
+
There are three additional includes for this:
|
|
445
|
+
|
|
446
|
+
- recording-level-rels
|
|
447
|
+
- release-group-level-rels (for releases only)
|
|
448
|
+
- work-level-rels
|
|
449
|
+
|
|
358
450
|
# Submitting data via XML POST
|
|
359
451
|
|
|
360
452
|
[Submitting data via XML POST](https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2#Submitting_data) may be done using personal MusicBrainz credentials.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "musicbrainz-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "MusicBrainz API client for reading and submitting metadata",
|
|
5
5
|
"exports": {
|
|
6
6
|
"node": {
|
|
@@ -115,5 +115,5 @@
|
|
|
115
115
|
],
|
|
116
116
|
"report-dir": "coverage"
|
|
117
117
|
},
|
|
118
|
-
"packageManager": "yarn@4.
|
|
118
|
+
"packageManager": "yarn@4.9.1"
|
|
119
119
|
}
|
|
@@ -1,74 +0,0 @@
|
|
|
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 type CoverType = 'front' | 'back';
|
|
20
|
-
export interface ICoversInfo {
|
|
21
|
-
images: IImage[];
|
|
22
|
-
release: string;
|
|
23
|
-
}
|
|
24
|
-
export interface ICoverInfo {
|
|
25
|
-
url: string | null;
|
|
26
|
-
}
|
|
27
|
-
export declare class CoverArtArchiveApi {
|
|
28
|
-
private httpClient;
|
|
29
|
-
private getJson;
|
|
30
|
-
private getCoverRedirect;
|
|
31
|
-
/**
|
|
32
|
-
* Fetch release
|
|
33
|
-
* @releaseId Release MBID
|
|
34
|
-
* @param releaseId MusicBrainz Release MBID
|
|
35
|
-
*/
|
|
36
|
-
getReleaseCovers(releaseId: string): Promise<ICoversInfo>;
|
|
37
|
-
/**
|
|
38
|
-
* Fetch release-group
|
|
39
|
-
* @releaseGroupId Release-group MBID
|
|
40
|
-
* @param releaseGroupId MusicBrainz Release Group MBID
|
|
41
|
-
*/
|
|
42
|
-
getReleaseGroupCovers(releaseGroupId: string): Promise<ICoversInfo>;
|
|
43
|
-
/**
|
|
44
|
-
* Fetch release cover
|
|
45
|
-
* @releaseId Release MBID
|
|
46
|
-
* @param releaseId MusicBrainz Release MBID
|
|
47
|
-
* @param coverType Front or back cover
|
|
48
|
-
*/
|
|
49
|
-
getReleaseCover(releaseId: string, coverType: CoverType): Promise<ICoverInfo>;
|
|
50
|
-
/**
|
|
51
|
-
* Fetch release-group cover
|
|
52
|
-
* @releaseId Release-group MBID
|
|
53
|
-
* @param releaseGroupId MusicBrainz Release-group MBID
|
|
54
|
-
* @param coverType Front or back cover
|
|
55
|
-
*/
|
|
56
|
-
getReleaseGroupCover(releaseGroupId: string, coverType: CoverType): Promise<ICoverInfo>;
|
|
57
|
-
private static makePath;
|
|
58
|
-
/**
|
|
59
|
-
* Fetch covers
|
|
60
|
-
* @releaseId MBID
|
|
61
|
-
* @param releaseId MusicBrainz Release Group MBID
|
|
62
|
-
* @param releaseType Fetch covers for specific release or release-group
|
|
63
|
-
* @param coverType Cover type
|
|
64
|
-
*/
|
|
65
|
-
private getCovers;
|
|
66
|
-
/**
|
|
67
|
-
* Fetch covers
|
|
68
|
-
* @releaseId MBID
|
|
69
|
-
* @param releaseId MusicBrainz Release Group MBID
|
|
70
|
-
* @param releaseType Fetch covers for specific release or release-group
|
|
71
|
-
* @param coverType Cover type
|
|
72
|
-
*/
|
|
73
|
-
private getCover;
|
|
74
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/* eslint-disable-next-line */
|
|
2
|
-
import { HttpClient } from "./http-client.js";
|
|
3
|
-
export class CoverArtArchiveApi {
|
|
4
|
-
constructor() {
|
|
5
|
-
this.httpClient = new HttpClient({ baseUrl: 'https://coverartarchive.org', userAgent: 'Node.js musicbrains-api', timeout: 20000, followRedirects: false });
|
|
6
|
-
}
|
|
7
|
-
async getJson(path) {
|
|
8
|
-
const response = await this.httpClient.get(path, {
|
|
9
|
-
headers: {
|
|
10
|
-
Accept: "application/json"
|
|
11
|
-
}
|
|
12
|
-
});
|
|
13
|
-
const contentType = response.headers.get("Content-Type");
|
|
14
|
-
if (response.status === 404 && contentType?.toLowerCase() !== "application/json") {
|
|
15
|
-
return {
|
|
16
|
-
"error": "Not Found",
|
|
17
|
-
"help": "For usage, please see: https://musicbrainz.org/development/mmd"
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
return response.json();
|
|
21
|
-
}
|
|
22
|
-
async getCoverRedirect(path) {
|
|
23
|
-
const response = await this.httpClient.get(path, {
|
|
24
|
-
followRedirects: false
|
|
25
|
-
});
|
|
26
|
-
switch (response.status) {
|
|
27
|
-
case 307:
|
|
28
|
-
return response.headers.get('LOCATION');
|
|
29
|
-
case 400:
|
|
30
|
-
throw new Error('Invalid UUID');
|
|
31
|
-
case 404:
|
|
32
|
-
// No release with this MBID
|
|
33
|
-
return null;
|
|
34
|
-
case 405:
|
|
35
|
-
throw new Error('Invalid HTTP method');
|
|
36
|
-
case 503:
|
|
37
|
-
return null;
|
|
38
|
-
default:
|
|
39
|
-
throw new Error(`Unexpected HTTP-status response: ${response.status}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Fetch release
|
|
44
|
-
* @releaseId Release MBID
|
|
45
|
-
* @param releaseId MusicBrainz Release MBID
|
|
46
|
-
*/
|
|
47
|
-
getReleaseCovers(releaseId) {
|
|
48
|
-
return this.getCovers(releaseId, 'release');
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Fetch release-group
|
|
52
|
-
* @releaseGroupId Release-group MBID
|
|
53
|
-
* @param releaseGroupId MusicBrainz Release Group MBID
|
|
54
|
-
*/
|
|
55
|
-
getReleaseGroupCovers(releaseGroupId) {
|
|
56
|
-
return this.getCovers(releaseGroupId, 'release-group');
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Fetch release cover
|
|
60
|
-
* @releaseId Release MBID
|
|
61
|
-
* @param releaseId MusicBrainz Release MBID
|
|
62
|
-
* @param coverType Front or back cover
|
|
63
|
-
*/
|
|
64
|
-
getReleaseCover(releaseId, coverType) {
|
|
65
|
-
return this.getCover(releaseId, 'release', coverType);
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Fetch release-group cover
|
|
69
|
-
* @releaseId Release-group MBID
|
|
70
|
-
* @param releaseGroupId MusicBrainz Release-group MBID
|
|
71
|
-
* @param coverType Front or back cover
|
|
72
|
-
*/
|
|
73
|
-
getReleaseGroupCover(releaseGroupId, coverType) {
|
|
74
|
-
return this.getCover(releaseGroupId, 'release-group', coverType);
|
|
75
|
-
}
|
|
76
|
-
static makePath(releaseId, releaseType = 'release', coverType) {
|
|
77
|
-
const path = [releaseType, releaseId];
|
|
78
|
-
if (coverType) {
|
|
79
|
-
path.push(coverType);
|
|
80
|
-
}
|
|
81
|
-
return `/${path.join('/')}`;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Fetch covers
|
|
85
|
-
* @releaseId MBID
|
|
86
|
-
* @param releaseId MusicBrainz Release Group MBID
|
|
87
|
-
* @param releaseType Fetch covers for specific release or release-group
|
|
88
|
-
* @param coverType Cover type
|
|
89
|
-
*/
|
|
90
|
-
async getCovers(releaseId, releaseType = 'release') {
|
|
91
|
-
const info = await this.getJson(CoverArtArchiveApi.makePath(releaseId, releaseType));
|
|
92
|
-
// Hack to correct http addresses into https
|
|
93
|
-
if (info.release?.startsWith('http:')) {
|
|
94
|
-
info.release = `https${info.release.substring(4)}`;
|
|
95
|
-
}
|
|
96
|
-
return info;
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Fetch covers
|
|
100
|
-
* @releaseId MBID
|
|
101
|
-
* @param releaseId MusicBrainz Release Group MBID
|
|
102
|
-
* @param releaseType Fetch covers for specific release or release-group
|
|
103
|
-
* @param coverType Cover type
|
|
104
|
-
*/
|
|
105
|
-
async getCover(releaseId, releaseType = 'release', coverType) {
|
|
106
|
-
const url = await this.getCoverRedirect(CoverArtArchiveApi.makePath(releaseId, releaseType, coverType));
|
|
107
|
-
return { url: url };
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
//# sourceMappingURL=coverartarchive-api.js.map
|
package/lib/digest-auth.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export interface ICredentials {
|
|
2
|
-
username: string;
|
|
3
|
-
password: string;
|
|
4
|
-
}
|
|
5
|
-
export declare class DigestAuth {
|
|
6
|
-
private credentials;
|
|
7
|
-
/**
|
|
8
|
-
* RFC 2617: handle both MD5 and MD5-sess algorithms.
|
|
9
|
-
*
|
|
10
|
-
* If the algorithm directive's value is "MD5" or unspecified, then HA1 is
|
|
11
|
-
* HA1=MD5(username:realm:password)
|
|
12
|
-
* If the algorithm directive's value is "MD5-sess", then HA1 is
|
|
13
|
-
* HA1=MD5(MD5(username:realm:password):nonce:cnonce)
|
|
14
|
-
*/
|
|
15
|
-
static ha1Compute(algorithm: string, user: string, realm: string, pass: string, nonce: string, cnonce: string): string;
|
|
16
|
-
hasAuth: boolean;
|
|
17
|
-
sentAuth: boolean;
|
|
18
|
-
bearerToken: string | null;
|
|
19
|
-
constructor(credentials: ICredentials);
|
|
20
|
-
digest(method: string, path: string, authHeader: string): string;
|
|
21
|
-
}
|
package/lib/digest-auth.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
2
|
-
import sparkMd5 from 'spark-md5';
|
|
3
|
-
const md5 = sparkMd5.hash;
|
|
4
|
-
export class DigestAuth {
|
|
5
|
-
/**
|
|
6
|
-
* RFC 2617: handle both MD5 and MD5-sess algorithms.
|
|
7
|
-
*
|
|
8
|
-
* If the algorithm directive's value is "MD5" or unspecified, then HA1 is
|
|
9
|
-
* HA1=MD5(username:realm:password)
|
|
10
|
-
* If the algorithm directive's value is "MD5-sess", then HA1 is
|
|
11
|
-
* HA1=MD5(MD5(username:realm:password):nonce:cnonce)
|
|
12
|
-
*/
|
|
13
|
-
static ha1Compute(algorithm, user, realm, pass, nonce, cnonce) {
|
|
14
|
-
const ha1 = md5(`${user}:${realm}:${pass}`); // lgtm [js/insufficient-password-hash]
|
|
15
|
-
return algorithm && algorithm.toLowerCase() === 'md5-sess' ? md5(`${ha1}:${nonce}:${cnonce}`) : ha1;
|
|
16
|
-
}
|
|
17
|
-
constructor(credentials) {
|
|
18
|
-
this.credentials = credentials;
|
|
19
|
-
this.hasAuth = false;
|
|
20
|
-
this.sentAuth = false;
|
|
21
|
-
this.bearerToken = null;
|
|
22
|
-
}
|
|
23
|
-
digest(method, path, authHeader) {
|
|
24
|
-
// TODO: More complete implementation of RFC 2617.
|
|
25
|
-
// - support qop="auth-int" only
|
|
26
|
-
// - handle Authentication-Info (not necessarily?)
|
|
27
|
-
// - check challenge.stale (not necessarily?)
|
|
28
|
-
// - increase nc (not necessarily?)
|
|
29
|
-
// For reference:
|
|
30
|
-
// http://tools.ietf.org/html/rfc2617#section-3
|
|
31
|
-
// https://github.com/bagder/curl/blob/master/lib/http_digest.c
|
|
32
|
-
const challenge = {};
|
|
33
|
-
const re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi;
|
|
34
|
-
while (true) {
|
|
35
|
-
const match = re.exec(authHeader);
|
|
36
|
-
if (!match) {
|
|
37
|
-
break;
|
|
38
|
-
}
|
|
39
|
-
challenge[match[1]] = match[2] || match[3];
|
|
40
|
-
}
|
|
41
|
-
const qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth';
|
|
42
|
-
const nc = qop && '00000001';
|
|
43
|
-
const cnonce = qop && uuidv4().replace(/-/g, '');
|
|
44
|
-
const ha1 = DigestAuth.ha1Compute(challenge.algorithm, this.credentials.username, challenge.realm, this.credentials.password, challenge.nonce, cnonce);
|
|
45
|
-
const ha2 = md5(`${method}:${path}`); // lgtm [js/insufficient-password-hash]
|
|
46
|
-
const digestResponse = qop
|
|
47
|
-
? md5(`${ha1}:${challenge.nonce}:${nc}:${cnonce}:${qop}:${ha2}`) // lgtm [js/insufficient-password-hash]
|
|
48
|
-
: md5(`${ha1}:${challenge.nonce}:${ha2}`); // lgtm [js/insufficient-password-hash]
|
|
49
|
-
const authValues = {
|
|
50
|
-
username: this.credentials.username,
|
|
51
|
-
realm: challenge.realm,
|
|
52
|
-
nonce: challenge.nonce,
|
|
53
|
-
uri: path,
|
|
54
|
-
qop,
|
|
55
|
-
response: digestResponse,
|
|
56
|
-
nc,
|
|
57
|
-
cnonce,
|
|
58
|
-
algorithm: challenge.algorithm,
|
|
59
|
-
opaque: challenge.opaque
|
|
60
|
-
};
|
|
61
|
-
const parts = [];
|
|
62
|
-
Object.entries(authValues).forEach(([key, value]) => {
|
|
63
|
-
if (value) {
|
|
64
|
-
if (key === 'qop' || key === 'nc' || key === 'algorithm') {
|
|
65
|
-
parts.push(`${key}=${value}`);
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
parts.push(`${key}="${value}"`);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
const digest = `Digest ${parts.join(', ')}`;
|
|
73
|
-
this.sentAuth = true;
|
|
74
|
-
return digest;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
//# sourceMappingURL=digest-auth.js.map
|
package/lib/entry-default.d.ts
DELETED
package/lib/entry-default.js
DELETED
package/lib/entry-node.d.ts
DELETED
package/lib/entry-node.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { type Cookie } from "tough-cookie";
|
|
2
|
-
import { HttpClient, type IHttpClientOptions } from "./http-client.js";
|
|
3
|
-
export type HttpFormData = {
|
|
4
|
-
[key: string]: string;
|
|
5
|
-
};
|
|
6
|
-
export declare class HttpClientNode extends HttpClient {
|
|
7
|
-
private cookieJar;
|
|
8
|
-
constructor(options: IHttpClientOptions);
|
|
9
|
-
protected registerCookies(response: Response): Promise<Cookie | undefined>;
|
|
10
|
-
getCookies(): Promise<string | null>;
|
|
11
|
-
}
|
package/lib/http-client-node.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { CookieJar } from "tough-cookie";
|
|
2
|
-
import { HttpClient } from "./http-client.js";
|
|
3
|
-
export class HttpClientNode extends HttpClient {
|
|
4
|
-
constructor(options) {
|
|
5
|
-
super(options);
|
|
6
|
-
this.cookieJar = new CookieJar();
|
|
7
|
-
}
|
|
8
|
-
registerCookies(response) {
|
|
9
|
-
const cookie = response.headers.get('set-cookie');
|
|
10
|
-
if (cookie) {
|
|
11
|
-
return this.cookieJar.setCookie(cookie, response.url);
|
|
12
|
-
}
|
|
13
|
-
return Promise.resolve(undefined);
|
|
14
|
-
}
|
|
15
|
-
getCookies() {
|
|
16
|
-
return this.cookieJar.getCookieString(this.options.baseUrl); // Get cookies for the request
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
//# sourceMappingURL=http-client-node.js.map
|
package/lib/http-client.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { Cookie } from "tough-cookie";
|
|
2
|
-
export type HttpFormData = {
|
|
3
|
-
[key: string]: string;
|
|
4
|
-
};
|
|
5
|
-
export interface IHttpClientOptions {
|
|
6
|
-
baseUrl: string;
|
|
7
|
-
timeout: number;
|
|
8
|
-
userAgent: string;
|
|
9
|
-
followRedirects?: boolean;
|
|
10
|
-
}
|
|
11
|
-
export interface IFetchOptions {
|
|
12
|
-
query?: HttpFormData;
|
|
13
|
-
retryLimit?: number;
|
|
14
|
-
body?: string;
|
|
15
|
-
headers?: HeadersInit;
|
|
16
|
-
followRedirects?: boolean;
|
|
17
|
-
}
|
|
18
|
-
export declare class HttpClient {
|
|
19
|
-
protected options: IHttpClientOptions;
|
|
20
|
-
constructor(options: IHttpClientOptions);
|
|
21
|
-
get(path: string, options?: IFetchOptions): Promise<Response>;
|
|
22
|
-
post(path: string, options?: IFetchOptions): Promise<Response>;
|
|
23
|
-
postForm(path: string, formData: HttpFormData, options?: IFetchOptions): Promise<Response>;
|
|
24
|
-
postJson(path: string, json: Object, options?: IFetchOptions): Promise<Response>;
|
|
25
|
-
private _fetch;
|
|
26
|
-
protected registerCookies(response: Response): Promise<Cookie | undefined>;
|
|
27
|
-
getCookies(): Promise<string | null>;
|
|
28
|
-
}
|
package/lib/http-client.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
export class HttpClient {
|
|
2
|
-
constructor(options) {
|
|
3
|
-
this.options = options;
|
|
4
|
-
}
|
|
5
|
-
get(path, options) {
|
|
6
|
-
return this._fetch('get', path, options);
|
|
7
|
-
}
|
|
8
|
-
post(path, options) {
|
|
9
|
-
return this._fetch('post', path, options);
|
|
10
|
-
}
|
|
11
|
-
postForm(path, formData, options) {
|
|
12
|
-
const encodedFormData = new URLSearchParams(formData).toString();
|
|
13
|
-
return this._fetch('post', path, { ...options, body: encodedFormData, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });
|
|
14
|
-
}
|
|
15
|
-
// biome-ignore lint/complexity/noBannedTypes:
|
|
16
|
-
postJson(path, json, options) {
|
|
17
|
-
const encodedJson = JSON.stringify(json);
|
|
18
|
-
return this._fetch('post', path, { ...options, body: encodedJson, headers: { 'Content-Type': 'application/json.' } });
|
|
19
|
-
}
|
|
20
|
-
async _fetch(method, path, options) {
|
|
21
|
-
if (!options)
|
|
22
|
-
options = {};
|
|
23
|
-
let url = path.startsWith('/') ? `${this.options.baseUrl}${path}` : `${this.options.baseUrl}/${path}`;
|
|
24
|
-
if (options.query) {
|
|
25
|
-
url += `?${new URLSearchParams(options.query)}`;
|
|
26
|
-
}
|
|
27
|
-
const cookies = await this.getCookies();
|
|
28
|
-
const headers = new Headers(options.headers);
|
|
29
|
-
headers.set('User-Agent', this.options.userAgent);
|
|
30
|
-
if (cookies !== null) {
|
|
31
|
-
headers.set('Cookie', cookies);
|
|
32
|
-
}
|
|
33
|
-
const response = await fetch(url, {
|
|
34
|
-
method,
|
|
35
|
-
...options,
|
|
36
|
-
headers,
|
|
37
|
-
body: options.body,
|
|
38
|
-
redirect: options.followRedirects === false ? 'manual' : 'follow'
|
|
39
|
-
});
|
|
40
|
-
await this.registerCookies(response);
|
|
41
|
-
return response;
|
|
42
|
-
}
|
|
43
|
-
registerCookies(response) {
|
|
44
|
-
return Promise.resolve(undefined);
|
|
45
|
-
}
|
|
46
|
-
async getCookies() {
|
|
47
|
-
return Promise.resolve(null);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
//# sourceMappingURL=http-client.js.map
|
|
@@ -1,16 +0,0 @@
|
|
|
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 { HttpClientNode } from "./http-client-node.js";
|
|
6
|
-
import { MusicBrainzApi as MusicBrainzApiDefault } from "./musicbrainz-api.js";
|
|
7
|
-
export * from './musicbrainz.types.js';
|
|
8
|
-
export * from './http-client.js';
|
|
9
|
-
export declare class MusicBrainzApi extends MusicBrainzApiDefault {
|
|
10
|
-
protected initHttpClient(): HttpClientNode;
|
|
11
|
-
login(): Promise<boolean>;
|
|
12
|
-
/**
|
|
13
|
-
* Logout
|
|
14
|
-
*/
|
|
15
|
-
logout(): Promise<boolean>;
|
|
16
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { StatusCodes as HttpStatus } from 'http-status-codes';
|
|
2
|
-
import Debug from 'debug';
|
|
3
|
-
export { XmlMetadata } from './xml/xml-metadata.js';
|
|
4
|
-
export { XmlIsrc } from './xml/xml-isrc.js';
|
|
5
|
-
export { XmlIsrcList } from './xml/xml-isrc-list.js';
|
|
6
|
-
export { XmlRecording } from './xml/xml-recording.js';
|
|
7
|
-
import { HttpClientNode } from "./http-client-node.js";
|
|
8
|
-
import { MusicBrainzApi as MusicBrainzApiDefault } from "./musicbrainz-api.js";
|
|
9
|
-
export * from './musicbrainz.types.js';
|
|
10
|
-
export * from './http-client.js';
|
|
11
|
-
/*
|
|
12
|
-
* https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2#Subqueries
|
|
13
|
-
*/
|
|
14
|
-
const debug = Debug('musicbrainz-api-node');
|
|
15
|
-
export class MusicBrainzApi extends MusicBrainzApiDefault {
|
|
16
|
-
initHttpClient() {
|
|
17
|
-
return new HttpClientNode({
|
|
18
|
-
baseUrl: this.config.baseUrl,
|
|
19
|
-
timeout: 20 * 1000,
|
|
20
|
-
userAgent: `${this.config.appName}/${this.config.appVersion} ( ${this.config.appContactInfo} )`
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
async login() {
|
|
24
|
-
if (!this.config.botAccount?.username)
|
|
25
|
-
throw new Error('bot username should be set');
|
|
26
|
-
if (!this.config.botAccount?.password)
|
|
27
|
-
throw new Error('bot password should be set');
|
|
28
|
-
if (this.session?.loggedIn) {
|
|
29
|
-
const cookies = await this.httpClient.getCookies();
|
|
30
|
-
return cookies.indexOf('musicbrainz_server_session') !== -1;
|
|
31
|
-
}
|
|
32
|
-
this.session = await this.getSession();
|
|
33
|
-
const redirectUri = '/success';
|
|
34
|
-
const formData = {
|
|
35
|
-
username: this.config.botAccount.username,
|
|
36
|
-
password: this.config.botAccount.password,
|
|
37
|
-
csrf_session_key: this.session.csrf.sessionKey,
|
|
38
|
-
csrf_token: this.session.csrf.token,
|
|
39
|
-
remember_me: '1'
|
|
40
|
-
};
|
|
41
|
-
const response = await this.httpClient.postForm('login', formData, {
|
|
42
|
-
query: {
|
|
43
|
-
returnto: redirectUri
|
|
44
|
-
},
|
|
45
|
-
followRedirects: false
|
|
46
|
-
});
|
|
47
|
-
const success = response.status === HttpStatus.MOVED_TEMPORARILY && response.headers.get('location') === redirectUri;
|
|
48
|
-
if (success) {
|
|
49
|
-
this.session.loggedIn = true;
|
|
50
|
-
}
|
|
51
|
-
return success;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Logout
|
|
55
|
-
*/
|
|
56
|
-
async logout() {
|
|
57
|
-
const redirectUri = '/success';
|
|
58
|
-
const response = await this.httpClient.post('logout', {
|
|
59
|
-
followRedirects: false,
|
|
60
|
-
query: {
|
|
61
|
-
returnto: redirectUri
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
const success = response.status === HttpStatus.MOVED_TEMPORARILY && response.headers.get('location') === redirectUri;
|
|
65
|
-
if (success && this.session) {
|
|
66
|
-
this.session.loggedIn = true;
|
|
67
|
-
}
|
|
68
|
-
return success;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
//# sourceMappingURL=musicbrainz-api-node.js.map
|