musicbrainz-api 0.23.0 → 0.23.1
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/lib/coverartarchive-api.d.ts +74 -0
- package/lib/coverartarchive-api.js +110 -0
- package/lib/digest-auth.d.ts +21 -0
- package/lib/digest-auth.js +77 -0
- package/lib/entry-default.d.ts +2 -0
- package/lib/entry-default.js +3 -0
- package/lib/entry-node.d.ts +2 -0
- package/lib/entry-node.js +3 -0
- package/lib/http-client-node.d.ts +11 -0
- package/lib/http-client-node.js +19 -0
- package/lib/http-client.d.ts +34 -0
- package/lib/http-client.js +57 -0
- package/lib/musicbrainz-api-node.d.ts +16 -0
- package/lib/musicbrainz-api-node.js +71 -0
- package/lib/musicbrainz-api.d.ts +180 -0
- package/lib/musicbrainz-api.js +219 -0
- package/lib/musicbrainz.types.d.ts +630 -0
- package/lib/musicbrainz.types.js +14 -0
- package/lib/xml/xml-isrc-list.d.ts +17 -0
- package/lib/xml/xml-isrc-list.js +19 -0
- package/lib/xml/xml-isrc.d.ts +10 -0
- package/lib/xml/xml-isrc.js +14 -0
- package/lib/xml/xml-metadata.d.ts +6 -0
- package/lib/xml/xml-metadata.js +26 -0
- package/lib/xml/xml-recording.d.ts +24 -0
- package/lib/xml/xml-recording.js +17 -0
- package/package.json +4 -6
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
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
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Cookie } from "tough-cookie";
|
|
2
|
+
export type HttpFormData = {
|
|
3
|
+
[key: string]: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Allows multiple entries for the same key
|
|
7
|
+
*/
|
|
8
|
+
export type MultiQueryFormData = {
|
|
9
|
+
[key: string]: string | string[];
|
|
10
|
+
};
|
|
11
|
+
export interface IHttpClientOptions {
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
timeout: number;
|
|
14
|
+
userAgent: string;
|
|
15
|
+
followRedirects?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface IFetchOptions {
|
|
18
|
+
query?: MultiQueryFormData;
|
|
19
|
+
retryLimit?: number;
|
|
20
|
+
body?: string;
|
|
21
|
+
headers?: HeadersInit;
|
|
22
|
+
followRedirects?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare class HttpClient {
|
|
25
|
+
protected options: IHttpClientOptions;
|
|
26
|
+
constructor(options: IHttpClientOptions);
|
|
27
|
+
get(path: string, options?: IFetchOptions): Promise<Response>;
|
|
28
|
+
post(path: string, options?: IFetchOptions): Promise<Response>;
|
|
29
|
+
postForm(path: string, formData: HttpFormData, options?: IFetchOptions): Promise<Response>;
|
|
30
|
+
postJson(path: string, json: Object, options?: IFetchOptions): Promise<Response>;
|
|
31
|
+
private _fetch;
|
|
32
|
+
protected registerCookies(response: Response): Promise<Cookie | undefined>;
|
|
33
|
+
getCookies(): Promise<string | null>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
const urlSearchParams = new URLSearchParams();
|
|
26
|
+
for (const key of Object.keys(options.query)) {
|
|
27
|
+
const value = options.query[key];
|
|
28
|
+
(Array.isArray(value) ? value : [value]).forEach(value => {
|
|
29
|
+
urlSearchParams.append(key, value);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
url += `?${urlSearchParams.toString()}`;
|
|
33
|
+
}
|
|
34
|
+
const cookies = await this.getCookies();
|
|
35
|
+
const headers = new Headers(options.headers);
|
|
36
|
+
headers.set('User-Agent', this.options.userAgent);
|
|
37
|
+
if (cookies !== null) {
|
|
38
|
+
headers.set('Cookie', cookies);
|
|
39
|
+
}
|
|
40
|
+
const response = await fetch(url, {
|
|
41
|
+
method,
|
|
42
|
+
...options,
|
|
43
|
+
headers,
|
|
44
|
+
body: options.body,
|
|
45
|
+
redirect: options.followRedirects === false ? 'manual' : 'follow'
|
|
46
|
+
});
|
|
47
|
+
await this.registerCookies(response);
|
|
48
|
+
return response;
|
|
49
|
+
}
|
|
50
|
+
registerCookies(response) {
|
|
51
|
+
return Promise.resolve(undefined);
|
|
52
|
+
}
|
|
53
|
+
async getCookies() {
|
|
54
|
+
return Promise.resolve(null);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=http-client.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
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
|