musicbrainz-api 0.19.1 → 0.20.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 CHANGED
@@ -308,9 +308,9 @@ Arguments:
308
308
  - `query.offset`: optional, return search results starting at a given offset. Used for paging through more than one page of results.
309
309
  - `limit.query`: optional, an integer value defining how many entries should be returned. Only values between 1 and 100 (both inclusive) are allowed. If not given, this defaults to 25.
310
310
 
311
- For example, to find any recordings of _'We Will Rock You'_ by Queen:
311
+ For example, to search for _release-group_: _"We Will Rock You"_ by _Queen_:
312
312
  ```js
313
- const query = 'query="We Will Rock You" AND arid:0383dadf-2a4e-4d10-a46a-e9e041da8eb3';
313
+ const query = 'query=artist:"Queen" AND release:"We Will Rock You"';
314
314
  const result = await mbApi.search('release-group', {query});
315
315
  ```
316
316
 
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable-next-line */
2
- import { HttpClient } from "./httpClient.js";
2
+ import { HttpClient } from "./http-client.js";
3
3
  export class CoverArtArchiveApi {
4
4
  constructor() {
5
5
  this.httpClient = new HttpClient({ baseUrl: 'https://coverartarchive.org', userAgent: 'Node.js musicbrains-api', timeout: 20000 });
@@ -0,0 +1,5 @@
1
+ // CommonJS entry point
2
+ "use strict";
3
+ module.exports = {
4
+ loadMusicBrainzApi: () => import('./entry-default.js'),
5
+ };
@@ -1,3 +1,3 @@
1
1
  export * from './coverartarchive-api.js';
2
2
  export * from './musicbrainz-api.js';
3
- //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=entry-default.js.map
@@ -0,0 +1,5 @@
1
+ // Node.js CommonJS entry point
2
+ "use strict";
3
+ module.exports = {
4
+ loadMusicBrainzApi: () => import('./entry-node.js'),
5
+ };
@@ -0,0 +1,2 @@
1
+ export * from './coverartarchive-api.js';
2
+ export * from './musicbrainz-api-node.js';
@@ -0,0 +1,3 @@
1
+ export * from './coverartarchive-api.js';
2
+ export * from './musicbrainz-api-node.js';
3
+ //# sourceMappingURL=entry-node.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
@@ -1,3 +1,4 @@
1
+ import type { Cookie } from "tough-cookie";
1
2
  export type HttpFormData = {
2
3
  [key: string]: string;
3
4
  };
@@ -14,14 +15,13 @@ export interface IFetchOptions {
14
15
  followRedirects?: boolean;
15
16
  }
16
17
  export declare class HttpClient {
17
- private options;
18
- private cookieJar;
18
+ protected options: IHttpClientOptions;
19
19
  constructor(options: IHttpClientOptions);
20
20
  get(path: string, options?: IFetchOptions): Promise<Response>;
21
21
  post(path: string, options?: IFetchOptions): Promise<Response>;
22
22
  postForm(path: string, formData: HttpFormData, options?: IFetchOptions): Promise<Response>;
23
23
  postJson(path: string, json: Object, options?: IFetchOptions): Promise<Response>;
24
24
  private _fetch;
25
- private registerCookies;
26
- getCookies(): Promise<string>;
25
+ protected registerCookies(response: Response): Promise<Cookie | undefined>;
26
+ getCookies(): Promise<string | null>;
27
27
  }
@@ -1,8 +1,6 @@
1
- import { CookieJar } from "tough-cookie";
2
1
  export class HttpClient {
3
2
  constructor(options) {
4
3
  this.options = options;
5
- this.cookieJar = new CookieJar();
6
4
  }
7
5
  get(path, options) {
8
6
  return this._fetch('get', path, options);
@@ -27,11 +25,11 @@ export class HttpClient {
27
25
  url += `?${new URLSearchParams(options.query)}`;
28
26
  }
29
27
  const cookies = await this.getCookies();
30
- const headers = {
31
- ...options.headers,
32
- 'User-Agent': this.options.userAgent,
33
- 'Cookie': cookies
34
- };
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
+ }
35
33
  const response = await fetch(url, {
36
34
  method,
37
35
  ...options,
@@ -43,13 +41,10 @@ export class HttpClient {
43
41
  return response;
44
42
  }
45
43
  registerCookies(response) {
46
- const cookie = response.headers.get('set-cookie');
47
- if (cookie) {
48
- return this.cookieJar.setCookie(cookie, response.url);
49
- }
44
+ return Promise.resolve(undefined);
50
45
  }
51
- getCookies() {
52
- return this.cookieJar.getCookieString(this.options.baseUrl); // Get cookies for the request
46
+ async getCookies() {
47
+ return Promise.resolve(null);
53
48
  }
54
49
  }
55
- //# sourceMappingURL=httpClient.js.map
50
+ //# 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
@@ -3,7 +3,9 @@ export { XmlIsrc } from './xml/xml-isrc.js';
3
3
  export { XmlIsrcList } from './xml/xml-isrc-list.js';
4
4
  export { XmlRecording } from './xml/xml-recording.js';
5
5
  import type { XmlMetadata } from './xml/xml-metadata.js';
6
+ import { RateLimitThreshold } from 'rate-limit-threshold';
6
7
  import * as mb from './musicbrainz.types.js';
8
+ import { HttpClient } from "./http-client.js";
7
9
  export * from './musicbrainz.types.js';
8
10
  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
11
  export type SubQueryIncludes =
@@ -76,12 +78,13 @@ export interface ISessionInformation {
76
78
  }
77
79
  export declare class MusicBrainzApi {
78
80
  readonly config: IInternalConfig;
79
- private rateLimiter;
80
- private httpClient;
81
- private session?;
81
+ protected rateLimiter: RateLimitThreshold;
82
+ protected httpClient: HttpClient;
83
+ protected session?: ISessionInformation;
82
84
  static fetchCsrf(html: string): ICsrfSession;
83
85
  private static fetchValue;
84
86
  constructor(_config?: IMusicBrainzConfig);
87
+ protected initHttpClient(): HttpClient;
85
88
  restGet<T>(relUrl: string, query?: {
86
89
  [key: string]: string;
87
90
  }): Promise<T>;
@@ -140,11 +143,6 @@ export declare class MusicBrainzApi {
140
143
  search(artist: 'url', query: mb.ISearchQuery<UrlIncludes> & mb.ILinkedEntitiesArea): Promise<mb.IUrlList>;
141
144
  postRecording(xmlMetadata: XmlMetadata): Promise<void>;
142
145
  post(entity: mb.EntityType, xmlMetadata: XmlMetadata): Promise<void>;
143
- login(): Promise<boolean>;
144
- /**
145
- * Logout
146
- */
147
- logout(): Promise<boolean>;
148
146
  /**
149
147
  * Submit entity
150
148
  * @param entity Entity type e.g. 'recording'
@@ -176,7 +174,7 @@ export declare class MusicBrainzApi {
176
174
  * @param editNote Comment to add.
177
175
  */
178
176
  addSpotifyIdToRecording(recording: mb.IRecording, spotifyId: string, editNote: string): Promise<void>;
179
- private getSession;
180
- private applyRateLimiter;
177
+ protected getSession(): Promise<ISessionInformation>;
178
+ protected applyRateLimiter(): Promise<void>;
181
179
  }
182
180
  export declare function makeAndQueryString(keyValuePairs: IFormData): string;
@@ -7,7 +7,7 @@ export { XmlRecording } from './xml/xml-recording.js';
7
7
  import { DigestAuth } from './digest-auth.js';
8
8
  import { RateLimitThreshold } from 'rate-limit-threshold';
9
9
  import * as mb from './musicbrainz.types.js';
10
- import { HttpClient } from "./httpClient.js";
10
+ import { HttpClient } from "./http-client.js";
11
11
  export * from './musicbrainz.types.js';
12
12
  const debug = Debug('musicbrainz-api');
13
13
  export class MusicBrainzApi {
@@ -35,12 +35,15 @@ export class MusicBrainzApi {
35
35
  },
36
36
  ..._config
37
37
  };
38
- this.httpClient = new HttpClient({
38
+ this.httpClient = this.initHttpClient();
39
+ this.rateLimiter = new RateLimitThreshold(15, 18);
40
+ }
41
+ initHttpClient() {
42
+ return new HttpClient({
39
43
  baseUrl: this.config.baseUrl,
40
44
  timeout: 20 * 1000,
41
45
  userAgent: `${this.config.appName}/${this.config.appVersion} ( ${this.config.appContactInfo} )`
42
46
  });
43
- this.rateLimiter = new RateLimitThreshold(15, 18);
44
47
  }
45
48
  async restGet(relUrl, query = {}) {
46
49
  query.fmt = 'json';
@@ -103,53 +106,6 @@ export class MusicBrainzApi {
103
106
  }
104
107
  } while (n++ < 5);
105
108
  }
106
- async login() {
107
- if (!this.config.botAccount?.username)
108
- throw new Error('bot username should be set');
109
- if (!this.config.botAccount?.password)
110
- throw new Error('bot password should be set');
111
- if (this.session?.loggedIn) {
112
- const cookies = await this.httpClient.getCookies();
113
- return cookies.indexOf('musicbrainz_server_session') !== -1;
114
- }
115
- this.session = await this.getSession();
116
- const redirectUri = '/success';
117
- const formData = {
118
- username: this.config.botAccount.username,
119
- password: this.config.botAccount.password,
120
- csrf_session_key: this.session.csrf.sessionKey,
121
- csrf_token: this.session.csrf.token,
122
- remember_me: '1'
123
- };
124
- const response = await this.httpClient.postForm('login', formData, {
125
- query: {
126
- returnto: redirectUri
127
- },
128
- followRedirects: false
129
- });
130
- const success = response.status === HttpStatus.MOVED_TEMPORARILY && response.headers.get('location') === redirectUri;
131
- if (success) {
132
- this.session.loggedIn = true;
133
- }
134
- return success;
135
- }
136
- /**
137
- * Logout
138
- */
139
- async logout() {
140
- const redirectUri = '/success';
141
- const response = await this.httpClient.post('logout', {
142
- followRedirects: false,
143
- query: {
144
- returnto: redirectUri
145
- }
146
- });
147
- const success = response.status === HttpStatus.MOVED_TEMPORARILY && response.headers.get('location') === redirectUri;
148
- if (success && this.session) {
149
- this.session.loggedIn = true;
150
- }
151
- return success;
152
- }
153
109
  /**
154
110
  * Submit entity
155
111
  * @param entity Entity type e.g. 'recording'
package/package.json CHANGED
@@ -1,16 +1,23 @@
1
1
  {
2
2
  "name": "musicbrainz-api",
3
- "version": "0.19.1",
3
+ "version": "0.20.0",
4
4
  "description": "MusicBrainz API client for reading and submitting metadata",
5
5
  "exports": {
6
- "import": "./lib/index.js",
7
- "require": "./lib/default.cjs"
6
+ "node": {
7
+ "import": "./lib/entry-node.js",
8
+ "require": "./lib/entry-node.cjs"
9
+ },
10
+ "default": {
11
+ "import": "./lib/entry-default.js",
12
+ "require": "./lib/entry-default.cjs"
13
+ }
8
14
  },
9
15
  "types": "lib/index.d.ts",
10
16
  "files": [
11
17
  "lib/**/*.js",
12
18
  "lib/**/*.d.ts",
13
- "lib/default.cjs"
19
+ "lib/entry-node.cjs",
20
+ "lib/entry-default.cjs"
14
21
  ],
15
22
  "type": "module",
16
23
  "author": {
@@ -56,11 +63,11 @@
56
63
  "rate-limit-threshold": "^0.2.0",
57
64
  "spark-md5": "^3.0.2",
58
65
  "tough-cookie": "^5.0.0",
59
- "uuid": "^10.0.0"
66
+ "uuid": "^11.0.3"
60
67
  },
61
68
  "devDependencies": {
62
69
  "@biomejs/biome": "^1.8.3",
63
- "@types/chai": "^4.3.0",
70
+ "@types/chai": "^5.0.0",
64
71
  "@types/jsontoxml": "^1.0.5",
65
72
  "@types/mocha": "^10.0.4",
66
73
  "@types/node": "^22.5.0",
@@ -71,8 +78,8 @@
71
78
  "@types/uuid": "^10.0.0",
72
79
  "c8": "^10.1.2",
73
80
  "chai": "^5.1.1",
74
- "del-cli": "^5.1.0",
75
- "mocha": "^10.7.3",
81
+ "del-cli": "^6.0.0",
82
+ "mocha": "^11.0.1",
76
83
  "remark-cli": "^12.0.0",
77
84
  "remark-preset-lint-recommended": "^7.0.0",
78
85
  "sinon": "^19.0.2",
@@ -109,5 +116,5 @@
109
116
  ],
110
117
  "report-dir": "coverage"
111
118
  },
112
- "packageManager": "yarn@4.1.1"
119
+ "packageManager": "yarn@4.6.0"
113
120
  }
package/lib/default.cjs DELETED
@@ -1,5 +0,0 @@
1
- // CommonJS entry point
2
- "use strict";
3
- module.exports = {
4
- loadMusicBrainzApi: () => import('./index.js'),
5
- };
File without changes