musicbrainz-api 0.7.2 → 0.8.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.
@@ -0,0 +1,453 @@
1
+ Index: src/musicbrainz.types.ts
2
+ IDEA additional info:
3
+ Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
4
+ <+>import DateTimeFormat = Intl.DateTimeFormat;\r\nimport { IFormData, Includes } from './musicbrainz-api';\r\n\r\nexport interface IPeriod {\r\n 'begin': string,\r\n 'ended': boolean,\r\n 'end': string\r\n}\r\n\r\nexport interface IArea {\r\n id: string,\r\n 'iso-3166-1-codes': string[],\r\n name: string,\r\n 'sort-name': string,\r\n disambiguation: string\r\n}\r\n\r\nexport interface IAlias {\r\n name: string,\r\n 'sort-name': string,\r\n ended: boolean,\r\n 'type-id': string,\r\n type: string,\r\n locale: string,\r\n primary: string,\r\n begin: string,\r\n end: string\r\n}\r\n\r\nexport interface IMatch {\r\n score: number // ToDo: provide feedback: should be a number\r\n}\r\n\r\nexport interface IArtist {\r\n id: string;\r\n name: string;\r\n disambiguation: string;\r\n 'sort-name': string;\r\n 'type-id'?: string;\r\n 'gender-id'?;\r\n 'life-span'?: IPeriod;\r\n country?: string;\r\n ipis?: any[]; // ToDo\r\n isnis?: string[];\r\n aliases?: IAlias[];\r\n gender?: null;\r\n type?: string;\r\n area?: IArea;\r\n begin_area?: IArea;\r\n end_area?: IArea;\r\n relations?: IRelation[];\r\n /**\r\n * Only defined if 'releases' are includes\r\n */\r\n releases?: IRelease[];\r\n 'release-groups'?: IReleaseGroup[];\r\n}\r\n\r\nexport interface IArtistCredit {\r\n artist: IArtist;\r\n joinphrase: string;\r\n name: string;\r\n}\r\n\r\nexport type ReleaseQuality = 'normal'; // ToDo\r\n\r\nexport interface IRelease {\r\n id: string;\r\n title: string;\r\n 'text-representation': { 'language': string, 'script': string },\r\n disambiguation: string;\r\n asin: string,\r\n 'status-id': string;\r\n packaging?: string;\r\n status: string;\r\n 'packaging-id'?: string;\r\n 'release-events'?: IReleaseEvent[];\r\n date: string;\r\n media: IMedium[];\r\n 'cover-art-archive': ICoverArtArchive;\r\n country: string;\r\n quality: string; // type ReleaseQuality doesnt work here\r\n barcode: string;\r\n relations?: IRelation[];\r\n 'artist-credit'?: IArtistCredit[]; // Include 'artist-credits '\r\n 'release-group'?: IReleaseGroup; // Include: 'release-groups'\r\n}\r\n\r\nexport interface IReleaseEvent {\r\n area?: IArea;\r\n date?: string;\r\n}\r\n\r\nexport type MediaFormatType = 'Digital Media'; // ToDo\r\n\r\nexport interface IRecording {\r\n id: string;\r\n video: boolean;\r\n length: number;\r\n title: string;\r\n disambiguation: string;\r\n isrcs?: string[];\r\n releases?: IRelease[];\r\n relations?: IRelation[];\r\n 'artist-credit'?: IArtistCredit[];\r\n aliases?: IAlias[];\r\n}\r\n\r\nexport interface ITrack {\r\n id: string;\r\n position: number;\r\n recording: IRecording;\r\n number: string; // in JSON, this is a string field\r\n length: number;\r\n title: string;\r\n 'artist-credit'?: IArtistCredit[];\r\n}\r\n\r\nexport interface IMedium {\r\n title: string;\r\n format?: string; // optional, type dosent work\r\n 'format-id': string;\r\n 'tracks': ITrack[];\r\n 'track-count': number;\r\n 'track-offset': number;\r\n 'position': number;\r\n}\r\n\r\nexport interface ICoverArtArchive {\r\n count: number;\r\n front: boolean;\r\n darkened: boolean;\r\n artwork: boolean;\r\n back: boolean;\r\n}\r\n\r\nexport interface IReleaseGroup {\r\n id: string;\r\n count: number;\r\n title: string;\r\n 'primary-type': string;\r\n 'sort-name': string;\r\n 'artist-credit': { artist: IArtist, name: string, joinphrase: string }[];\r\n releases?: IRelease[]; // include 'releases'\r\n}\r\n\r\nexport interface IArtistMatch extends IArtist, IMatch {\r\n}\r\n\r\nexport interface IReleaseGroupMatch extends IReleaseGroup, IMatch {\r\n}\r\n\r\nexport interface IReleaseMatch extends IRelease, IMatch {\r\n}\r\n\r\nexport interface IAreaMatch extends IArea, IMatch {\r\n}\r\n\r\nexport interface ISearchResult {\r\n created: DateTimeFormat;\r\n count: number;\r\n offset: number;\r\n}\r\n\r\nexport interface IArtistList extends ISearchResult {\r\n artists: IArtistMatch[]\r\n}\r\n\r\nexport interface IAreaList extends ISearchResult {\r\n areas: IAreaMatch[]\r\n}\r\n\r\nexport interface IReleaseList extends ISearchResult {\r\n releases: IReleaseMatch[]\r\n}\r\n\r\nexport interface IReleaseGroupList extends ISearchResult {\r\n 'release-groups': IReleaseGroupMatch[]\r\n}\r\n\r\nexport interface IUrlList extends ISearchResult {\r\n urls: IUrlMatch[]\r\n}\r\nexport type RelationDirection = 'backward' | 'forward';\r\n\r\nexport interface IRelation {\r\n 'attribute-ids': {};\r\n direction: RelationDirection;\r\n 'target-credit': string;\r\n end: null | object;\r\n 'source-credit': string;\r\n ended: boolean;\r\n 'attribute-values': object;\r\n attributes?: any[];\r\n type: string;\r\n begin?: null | object;\r\n 'target-type'?: 'url';\r\n 'type-id': string;\r\n url?: IURL;\r\n release?: IRelease;\r\n}\r\n\r\nexport interface IURL {\r\n id: string;\r\n resource: string;\r\n}\r\n\r\nexport interface IRelationList {\r\n relations: IRelation[];\r\n}\r\n\r\nexport interface IWork {\r\n id: string;\r\n title: string;\r\n}\r\n\r\nexport interface ILabel {\r\n id: string;\r\n name: string;\r\n}\r\n\r\nexport interface IUrl {\r\n id: string,\r\n resource: string,\r\n 'relation-list': IRelationList[];\r\n}\r\n\r\nexport interface IUrlMatch extends IMatch, IUrl {\r\n}\r\n\r\nexport interface IUrlSearchResult extends ISearchResult {\r\n urls?: IUrlMatch[];\r\n}\r\n\r\nexport interface IIsrcSearchResult {\r\n 'isrc': string;\r\n 'recordings': IRecording[];\r\n}\r\n\r\nexport interface IExernalIds {\r\n [type: string]: string;\r\n}\r\n\r\nexport interface IReleaseSearchResult extends ISearchResult {\r\n releases: IRelease[];\r\n}\r\n\r\n/**\r\n * https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2#Subqueries\r\n */\r\nexport type EntityType = 'area' |\r\n 'artist' |\r\n 'collection' |\r\n 'event' |\r\n 'instrument' |\r\n 'label' |\r\n 'place' |\r\n 'recording' |\r\n 'release' |\r\n 'release-group' |\r\n 'series' |\r\n 'work' |\r\n 'url';\r\n\r\nexport type Relationships = 'area-rels' |\r\n 'artist-rels' |\r\n 'event-rels' |\r\n 'instrument-rels' |\r\n 'label-rels' |\r\n 'place-rels' |\r\n 'recording-rels' |\r\n 'release-rels' |\r\n 'release-group-rels' |\r\n 'series-rels' |\r\n 'url-rels' |\r\n 'work-rels';\r\n\r\nexport enum LinkType {\r\n license = 302,\r\n production = 256,\r\n samples_IMDb_entry = 258,\r\n get_the_music = 257,\r\n purchase_for_download = 254,\r\n download_for_free = 255,\r\n stream_for_free = 268,\r\n crowdfunding_page = 905,\r\n other_databases = 306,\r\n Allmusic = 285\r\n}\r\n\r\n/**\r\n * https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2/Search#Artist\r\n */\r\nexport interface IPagination {\r\n /**\r\n * Return search results starting at a given offset. Used for paging through more than one page of results.\r\n */\r\n offset?: number;\r\n /**\r\n * 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.\r\n */\r\n limit?: number;\r\n}\r\n\r\n/**\r\n * https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2/Search#Artist\r\n */\r\nexport interface ISearchQuery extends IPagination {\r\n /**\r\n * Lucene search query, this is mandatory\r\n */\r\n query?: string | IFormData,\r\n inc?: Includes[]\r\n}\r\n\r\n/**\r\n * https://musicbrainz.org/doc/MusicBrainz_API#Browse\r\n * /ws/2/area collection\r\n */\r\nexport interface ILinkedEntitiesArea {\r\n collection?: string;\r\n}\r\n\r\n/**\r\n * https://musicbrainz.org/doc/MusicBrainz_API#Browse\r\n * /ws/2/artist area, collection, recording, release, release-group, work\r\n */\r\nexport interface ILinkedEntitiesArtist {\r\n area?: string;\r\n collection?: string;\r\n recording?: string;\r\n release?: string;\r\n 'release-group'?: string;\r\n work?: string;\r\n}\r\n\r\n/**\r\n * https://musicbrainz.org/doc/MusicBrainz_API#Browse\r\n * /ws/2/collection area, artist, editor, event, label, place, recording, release, release-group, work\r\n */\r\nexport interface ILinkedEntitiesCollection {\r\n area?: string;\r\n artist?: string;\r\n editor?: string;\r\n event?: string;\r\n label?: string;\r\n place?: string;\r\n recording?: string;\r\n release?: string;\r\n 'release-group'?: string;\r\n work?: string;\r\n}\r\n\r\n/**\r\n * https://musicbrainz.org/doc/MusicBrainz_API#Subqueries\r\n * /ws/2/event area, artist, collection, place\r\n */\r\nexport interface ILinkedEntitiesEvent {\r\n area?: string;\r\n artist?: string;\r\n collection?: string;\r\n place?: string;\r\n}\r\n\r\n/**\r\n * https://musicbrainz.org/doc/MusicBrainz_API#Subqueries\r\n * /ws/2/instrument collection\r\n */\r\nexport interface ILinkedEntitiesInstrument {\r\n collection?: string;\r\n}\r\n\r\n/**\r\n * https://musicbrainz.org/doc/MusicBrainz_API#Subqueries\r\n * /ws/2/label area, collection, release\r\n */\r\nexport interface ILinkedEntitiesLabel {\r\n area?: string;\r\n collection?: string;\r\n release?: string;\r\n}\r\n\r\n/**\r\n * https://musicbrainz.org/doc/MusicBrainz_API#Subqueries\r\n * /ws/2/place area, collection\r\n */\r\nexport interface IBrowseArgumentPlace {\r\n area?: string;\r\n collection?: string;\r\n}\r\n\r\n/**\r\n * https://musicbrainz.org/doc/MusicBrainz_API#Subqueries\r\n * /ws/2/recording artist, collection, release, work\r\n */\r\nexport interface ILinkedEntitiesRecording {\r\n area?: string;\r\n collection?: string;\r\n release?: string;\r\n work?: string;\r\n}\r\n\r\n/**\r\n * https://musicbrainz.org/doc/MusicBrainz_API#Subqueries\r\n * /ws/2/release area, artist, collection, label, track, track_artist, recording, release-group\r\n */\r\nexport interface ILinkedEntitiesRelease {\r\n area?: string;\r\n artist?: string;\r\n collection?: string;\r\n label?: string;\r\n track?: string;\r\n track_artist?: string;\r\n recording?: string;\r\n 'release-group'?: string;\r\n}\r\n\r\n/**\r\n * https://musicbrainz.org/doc/MusicBrainz_API#Subqueries\r\n * /ws/2/release-group artist, collection, release\r\n */\r\nexport interface ILinkedEntitiesReleaseGroup {\r\n artist?: string;\r\n collection?: string;\r\n release?: string;\r\n}\r\n\r\n/**\r\n * https://musicbrainz.org/doc/MusicBrainz_API#Subqueries\r\n * /ws/2/series collection\r\n */\r\nexport interface ILinkedEntitiesSeries {\r\n collection?: string;\r\n}\r\n\r\n/**\r\n * https://musicbrainz.org/doc/MusicBrainz_API#Browse\r\n * /ws/2/work artist, collection\r\n */\r\nexport interface ILinkedEntitiesWork {\r\n artist?: string;\r\n collection?: string;\r\n}\r\n\r\n/**\r\n * https://musicbrainz.org/doc/MusicBrainz_API#Browse\r\n * /ws/2/url resource\r\n */\r\nexport interface ILinkedEntitiesUrl {\r\n resource?: string;\r\n}\r\n\r\n\r\n
5
+ Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
6
+ <+>UTF-8
7
+ ===================================================================
8
+ diff --git a/src/musicbrainz.types.ts b/src/musicbrainz.types.ts
9
+ --- a/src/musicbrainz.types.ts (revision 7cc45f32b7537b2839102c06db1c73b67d7a1c4b)
10
+ +++ b/src/musicbrainz.types.ts (date 1646084256909)
11
+ @@ -450,4 +450,47 @@
12
+ resource?: string;
13
+ }
14
+
15
+ +/**
16
+ + * Browse artist query <entity>: <MBID>
17
+ + */
18
+ +export interface IBrowseArtist extends IPagination {
19
+ + /**
20
+ + * MBID Label
21
+ + * Ref: https://wiki.musicbrainz.org/MusicBrainz_API#Linked_entities
22
+ + */
23
+ + recording?: string;
24
+ + release?: string;
25
+ + 'release-group'?: string;
26
+ + work?: string;
27
+ +}
28
+ +
29
+ +/**
30
+ + * Browse release query <entity>: <MBID>
31
+ + */
32
+ +export interface IBrowseRelease extends IPagination {
33
+ + /**
34
+ + * MBID Label
35
+ + * Ref: https://wiki.musicbrainz.org/MusicBrainz_API#Linked_entities
36
+ + */
37
+ + 'artist-credit'?: string;
38
+ + label?: string;
39
+ + recording?: string;
40
+ + 'release-group'?: string;
41
+ + media: string;
42
+ + discids: string;
43
+ + isrcs: string;
44
+ +}
45
+ +
46
+ +/**
47
+ + * Browse release query <entity>: <MBID>
48
+ + */
49
+ +export interface IReleaseGroups extends IPagination {
50
+ + /**
51
+ + * MBID Label
52
+ + * Ref: https://wiki.musicbrainz.org/MusicBrainz_API#Linked_entities
53
+ + */
54
+ + 'artist-credit'?: string;
55
+ +}
56
+ +
57
+ +
58
+
59
+ Index: src/musicbrainz-api.ts
60
+ IDEA additional info:
61
+ Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
62
+ <+>import * as assert from 'assert';\r\n\r\nimport {StatusCodes as HttpStatus, getReasonPhrase} from 'http-status-codes';\r\nimport * as Url from 'url';\r\nimport * as Debug from 'debug';\r\n\r\nexport { XmlMetadata } from './xml/xml-metadata';\r\nexport { XmlIsrc } from './xml/xml-isrc';\r\nexport { XmlIsrcList } from './xml/xml-isrc-list';\r\nexport { XmlRecording } from './xml/xml-recording';\r\n\r\nimport { XmlMetadata } from './xml/xml-metadata';\r\nimport { DigestAuth } from './digest-auth';\r\n\r\nimport { RateLimiter } from './rate-limiter';\r\nimport * as mb from './musicbrainz.types';\r\n\r\nimport got, { Options } from 'got';\r\nimport * as tough from 'tough-cookie';\r\n\r\nexport * from './musicbrainz.types';\r\n\r\nimport { promisify } from 'util';\r\n\r\nconst retries = 3;\r\n\r\n/**\r\n * https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2#Subqueries\r\n */\r\nexport type Includes =\r\n 'artists'\r\n | 'releases'\r\n | 'recordings'\r\n | 'artist-credits'\r\n | 'isrcs'\r\n | 'url-rels'\r\n | 'release-groups'\r\n | 'aliases'\r\n | 'discids'\r\n | 'annotation'\r\n | 'media' // release-groups\r\n | 'area-rels'\r\n | 'artist-rels'\r\n | 'event-rels'\r\n | 'instrument-rels'\r\n | 'label-rels'\r\n | 'place-rels'\r\n | 'recording-rels'\r\n | 'release-rels'\r\n | 'release-group-rels'\r\n | 'series-rels'\r\n | 'work-rels';\r\n\r\nconst debug = Debug('musicbrainz-api');\r\n\r\nexport interface IFormData {\r\n [key: string]: string | number;\r\n}\r\n\r\nexport interface IMusicBrainzConfig {\r\n botAccount?: {\r\n username: string,\r\n password: string\r\n },\r\n baseUrl?: string,\r\n\r\n appName?: string,\r\n appVersion?: string,\r\n\r\n /**\r\n * HTTP Proxy\r\n */\r\n proxy?: string,\r\n\r\n /**\r\n * User e-mail address or application URL\r\n */\r\n appContactInfo?: string\r\n}\r\n\r\nexport interface ISessionInformation {\r\n csrf: {\r\n sessionKey: string;\r\n token: string;\r\n }\r\n loggedIn?: boolean;\r\n}\r\n\r\nexport class MusicBrainzApi {\r\n\r\n private static escapeText(text) {\r\n let str = '';\r\n for (const chr of text) {\r\n // Escaping Special Characters: + - && || ! ( ) { } [ ] ^ \" ~ * ? : \\ /\r\n // ToDo: && ||\r\n switch (chr) {\r\n case '+':\r\n case '-':\r\n case '!':\r\n case '(':\r\n case ')':\r\n case '{':\r\n case '}':\r\n case '[':\r\n case ']':\r\n case '^':\r\n case '\"':\r\n case '~':\r\n case '*':\r\n case '?':\r\n case ':':\r\n case '\\\\':\r\n case '/':\r\n str += '\\\\';\r\n\r\n }\r\n str += chr;\r\n }\r\n return str;\r\n }\r\n\r\n public readonly config: IMusicBrainzConfig = {\r\n baseUrl: 'https://musicbrainz.org'\r\n };\r\n\r\n private rateLimiter: RateLimiter;\r\n private options: Options;\r\n private session: ISessionInformation;\r\n\r\n public static fetchCsrf(html: string) {\r\n return {\r\n sessionKey: MusicBrainzApi.fetchValue(html, 'csrf_session_key'),\r\n token: MusicBrainzApi.fetchValue(html, 'csrf_token')\r\n };\r\n }\r\n\r\n private static fetchValue(html: string, key: string) {\r\n let pos = html.indexOf(`name=\"${key}\"`);\r\n if (pos >= 0) {\r\n pos = html.indexOf('value=\"', pos + key.length + 7);\r\n if (pos >= 0) {\r\n pos += 7;\r\n const endValuePos = html.indexOf('\"', pos);\r\n const value = html.substring(pos, endValuePos);\r\n return value;\r\n }\r\n }\r\n }\r\n\r\n private getCookies: (currentUrl: string | URL) => Promise<tough.Cookie[]>;\r\n\r\n public constructor(_config?: IMusicBrainzConfig) {\r\n\r\n Object.assign(this.config, _config);\r\n\r\n const cookieJar = new tough.CookieJar();\r\n this.getCookies = promisify(cookieJar.getCookies.bind(cookieJar));\r\n\r\n this.options = {\r\n prefixUrl: this.config.baseUrl,\r\n timeout: 20 * 1000,\r\n headers: {\r\n 'User-Agent': `${this.config.appName}/${this.config.appVersion} ( ${this.config.appContactInfo} )`\r\n },\r\n cookieJar\r\n };\r\n\r\n this.rateLimiter = new RateLimiter(60, 50);\r\n }\r\n\r\n public async restGet<T>(relUrl: string, query: { [key: string]: any; } = {}, attempt: number = 1): Promise<T> {\r\n\r\n query.fmt = 'json';\r\n\r\n let response: any;\r\n await this.rateLimiter.limit();\r\n do {\r\n response = await got.get('ws/2' + relUrl, {\r\n searchParams: query,\r\n responseType: 'json',\r\n ...this.options\r\n });\r\n if (response.statusCode !== 503)\r\n break;\r\n debug('Rate limiter kicked in, slowing down...');\r\n await RateLimiter.sleep(500);\r\n } while (true);\r\n\r\n switch (response.statusCode) {\r\n case HttpStatus.OK:\r\n return response.body;\r\n\r\n case HttpStatus.BAD_REQUEST:\r\n case HttpStatus.NOT_FOUND:\r\n throw new Error(`Got response status ${response.statusCode}: ${getReasonPhrase(response.status)}`);\r\n\r\n case HttpStatus.SERVICE_UNAVAILABLE: // 503\r\n default:\r\n const msg = `Got response status ${response.statusCode} on attempt #${attempt} (${getReasonPhrase(response.status)})`;\r\n debug(msg);\r\n if (attempt < retries) {\r\n return this.restGet<T>(relUrl, query, attempt + 1);\r\n } else\r\n throw new Error(msg);\r\n }\r\n }\r\n\r\n // -----------------------------------------------------------------------------------------------------------------\r\n // Lookup functions\r\n // -----------------------------------------------------------------------------------------------------------------\r\n\r\n /**\r\n * Generic lookup function\r\n * @param entity\r\n * @param mbid\r\n * @param inc\r\n */\r\n public getEntity<T>(entity: mb.EntityType, mbid: string, inc: Includes[] = []): Promise<T> {\r\n return this.restGet<T>(`/${entity}/${mbid}`, {inc: inc.join(' ')});\r\n }\r\n\r\n /**\r\n * Lookup area\r\n * @param areaId Area MBID\r\n * @param inc Sub-queries\r\n */\r\n public getArea(areaId: string, inc: Includes[] = []): Promise<mb.IArea> {\r\n return this.getEntity<mb.IArea>('area', areaId, inc);\r\n }\r\n\r\n /**\r\n * Lookup artist\r\n * @param artistId Artist MBID\r\n * @param inc Sub-queries\r\n */\r\n public getArtist(artistId: string, inc: Includes[] = []): Promise<mb.IArtist> {\r\n return this.getEntity<mb.IArtist>('artist', artistId, inc);\r\n }\r\n\r\n /**\r\n * Lookup release\r\n * @param releaseId Release MBID\r\n * @param inc Include: artist-credits, labels, recordings, release-groups, media, discids, isrcs (with recordings)\r\n * ToDo: ['recordings', 'artists', 'artist-credits', 'isrcs', 'url-rels', 'release-groups']\r\n */\r\n public getRelease(releaseId: string, inc: Includes[] = []): Promise<mb.IRelease> {\r\n return this.getEntity<mb.IRelease>('release', releaseId, inc);\r\n }\r\n\r\n /**\r\n * Lookup release-group\r\n * @param releaseGroupId Release-group MBID\r\n * @param inc Include: ToDo\r\n */\r\n public getReleaseGroup(releaseGroupId: string, inc: Includes[] = []): Promise<mb.IReleaseGroup> {\r\n return this.getEntity<mb.IReleaseGroup>('release-group', releaseGroupId, inc);\r\n }\r\n\r\n /**\r\n * Lookup work\r\n * @param workId Work MBID\r\n */\r\n public getWork(workId: string): Promise<mb.IWork> {\r\n return this.getEntity<mb.IWork>('work', workId);\r\n }\r\n\r\n /**\r\n * Lookup label\r\n * @param labelId Label MBID\r\n */\r\n public getLabel(labelId: string): Promise<mb.ILabel> {\r\n return this.getEntity<mb.ILabel>('label', labelId);\r\n }\r\n\r\n /**\r\n * Lookup recording\r\n * @param recordingId Label MBID\r\n * @param inc Include: artist-credits, isrcs\r\n */\r\n public getRecording(recordingId: string, inc: Includes[] = []): Promise<mb.IRecording> {\r\n return this.getEntity<mb.IRecording>('recording', recordingId, inc);\r\n }\r\n\r\n public async postRecording(xmlMetadata: XmlMetadata): Promise<void> {\r\n return this.post('recording', xmlMetadata);\r\n }\r\n\r\n public async post(entity: mb.EntityType, xmlMetadata: XmlMetadata): Promise<void> {\r\n\r\n if (!this.config.appName || !this.config.appVersion) {\r\n throw new Error(`XML-Post requires the appName & appVersion to be defined`);\r\n }\r\n\r\n const clientId = `${this.config.appName.replace(/-/g, '.')}-${this.config.appVersion}`;\r\n\r\n const path = `ws/2/${entity}/`;\r\n // Get digest challenge\r\n\r\n let digest: string = null;\r\n let n = 1;\r\n const postData = xmlMetadata.toXml();\r\n\r\n do {\r\n await this.rateLimiter.limit();\r\n const response: any = await got.post(path, {\r\n searchParams: {client: clientId},\r\n headers: {\r\n authorization: digest,\r\n 'Content-Type': 'application/xml'\r\n },\r\n body: postData,\r\n throwHttpErrors: false,\r\n ...this.options\r\n });\r\n if (response.statusCode === HttpStatus.UNAUTHORIZED) {\r\n // Respond to digest challenge\r\n const auth = new DigestAuth(this.config.botAccount);\r\n const relPath = Url.parse(response.requestUrl).path; // Ensure path is relative\r\n digest = auth.digest(response.request.method, relPath, response.headers['www-authenticate']);\r\n ++n;\r\n } else {\r\n break;\r\n }\r\n } while (n++ < 5);\r\n }\r\n\r\n public async login(): Promise<boolean> {\r\n\r\n assert.ok(this.config.botAccount.username, 'bot username should be set');\r\n assert.ok(this.config.botAccount.password, 'bot password should be set');\r\n\r\n if (this.session && this.session.loggedIn) {\r\n for (const cookie of await this.getCookies(this.options.prefixUrl)) {\r\n if (cookie.key === 'remember_login') {\r\n return true;\r\n }\r\n }\r\n }\r\n this.session = await this.getSession(this.config.baseUrl);\r\n\r\n const redirectUri = '/success';\r\n\r\n const formData = {\r\n username: this.config.botAccount.username,\r\n password: this.config.botAccount.password,\r\n csrf_session_key: this.session.csrf.sessionKey,\r\n csrf_token: this.session.csrf.token,\r\n remember_me: 1\r\n };\r\n\r\n const response: any = await got.post('login', {\r\n followRedirect: false,\r\n searchParams: {\r\n returnto: redirectUri\r\n },\r\n form: formData,\r\n ...this.options\r\n });\r\n const success = response.statusCode === HttpStatus.MOVED_TEMPORARILY && response.headers.location === redirectUri;\r\n if (success) {\r\n this.session.loggedIn = true;\r\n }\r\n return success;\r\n }\r\n\r\n /**\r\n * Logout\r\n */\r\n public async logout(): Promise<boolean> {\r\n const redirectUri = '/success';\r\n\r\n const response: any = await got.get('logout', {\r\n followRedirect: false,\r\n searchParams: {\r\n returnto: redirectUri\r\n },\r\n ...this.options\r\n });\r\n const success = response.statusCode === HttpStatus.MOVED_TEMPORARILY && response.headers.location === redirectUri;\r\n if (success) {\r\n this.session.loggedIn = true;\r\n }\r\n return success;\r\n }\r\n\r\n /**\r\n * Submit entity\r\n * @param entity Entity type e.g. 'recording'\r\n * @param mbid\r\n * @param formData\r\n */\r\n public async editEntity(entity: mb.EntityType, mbid: string, formData: Record<string, any>): Promise<void> {\r\n\r\n await this.rateLimiter.limit();\r\n\r\n this.session = await this.getSession(this.config.baseUrl);\r\n\r\n formData.csrf_session_key = this.session.csrf.sessionKey;\r\n formData.csrf_token = this.session.csrf.token;\r\n formData.username = this.config.botAccount.username;\r\n formData.password = this.config.botAccount.password;\r\n formData.remember_me = 1;\r\n\r\n const response: any = await got.post(`${entity}/${mbid}/edit`, {\r\n form: formData,\r\n followRedirect: false,\r\n ...this.options\r\n });\r\n if (response.statusCode === HttpStatus.OK)\r\n throw new Error(`Failed to submit form data`);\r\n if (response.statusCode === HttpStatus.MOVED_TEMPORARILY)\r\n return;\r\n throw new Error(`Unexpected status code: ${response.statusCode}`);\r\n }\r\n\r\n /**\r\n * Set URL to recording\r\n * @param recording Recording to update\r\n * @param url2add URL to add to the recording\r\n * @param editNote Edit note\r\n */\r\n public async addUrlToRecording(recording: mb.IRecording, url2add: { linkTypeId: mb.LinkType, text: string }, editNote: string = '') {\r\n\r\n const formData = {};\r\n\r\n formData['edit-recording.name'] = recording.title; // Required\r\n formData['edit-recording.comment'] = recording.disambiguation;\r\n formData['edit-recording.make_votable'] = true;\r\n\r\n formData['edit-recording.url.0.link_type_id'] = url2add.linkTypeId;\r\n formData['edit-recording.url.0.text'] = url2add.text;\r\n\r\n for (const i in recording.isrcs) {\r\n formData[`edit-recording.isrcs.${i}`] = recording.isrcs[i];\r\n }\r\n\r\n formData['edit-recording.edit_note'] = editNote;\r\n\r\n return this.editEntity('recording', recording.id, formData);\r\n }\r\n\r\n /**\r\n * Add ISRC to recording\r\n * @param recording Recording to update\r\n * @param isrc ISRC code to add\r\n * @param editNote Edit note\r\n */\r\n public async addIsrc(recording: mb.IRecording, isrc: string, editNote: string = '') {\r\n\r\n const formData = {};\r\n\r\n formData[`edit-recording.name`] = recording.title; // Required\r\n\r\n if (!recording.isrcs) {\r\n throw new Error('You must retrieve recording with existing ISRC values');\r\n }\r\n\r\n if (recording.isrcs.indexOf(isrc) === -1) {\r\n recording.isrcs.push(isrc);\r\n\r\n for (const i in recording.isrcs) {\r\n formData[`edit-recording.isrcs.${i}`] = recording.isrcs[i];\r\n }\r\n return this.editEntity('recording', recording.id, formData);\r\n }\r\n }\r\n\r\n // -----------------------------------------------------------------------------------------------------------------\r\n // Query functions\r\n // -----------------------------------------------------------------------------------------------------------------\r\n\r\n /**\r\n * Search an entity using a search query\r\n * @param query e.g.: '\" artist: Madonna, track: Like a virgin\"' or object with search terms: {artist: Madonna}\r\n * @param entity e.g. 'recording'\r\n * @param query Arguments\r\n */\r\n public search<T extends mb.ISearchResult>(entity: mb.EntityType, query: mb.ISearchQuery): Promise<T> {\r\n const urlQuery: any = {...query};\r\n if (typeof query.query === 'object') {\r\n urlQuery.query = makeAndQueryString(query.query);\r\n }\r\n if (Array.isArray(query.inc)) {\r\n urlQuery.inc = urlQuery.inc.join(' ');\r\n }\r\n return this.restGet<T>('/' + entity + '/', urlQuery);\r\n }\r\n\r\n // -----------------------------------------------------------------------------------------------------------------\r\n // Helper functions\r\n // -----------------------------------------------------------------------------------------------------------------\r\n\r\n /**\r\n * Add Spotify-ID to MusicBrainz recording.\r\n * This function will automatically lookup the recording title, which is required to submit the recording URL\r\n * @param recording MBID of the recording\r\n * @param spotifyId Spotify ID\r\n * @param editNote Comment to add.\r\n */\r\n public addSpotifyIdToRecording(recording: mb.IRecording, spotifyId: string, editNote: string) {\r\n\r\n assert.strictEqual(spotifyId.length, 22);\r\n\r\n return this.addUrlToRecording(recording, {\r\n linkTypeId: mb.LinkType.stream_for_free,\r\n text: 'https://open.spotify.com/track/' + spotifyId\r\n }, editNote);\r\n }\r\n\r\n public searchArea(query: mb.ISearchQuery & mb.ILinkedEntitiesArea): Promise<mb.IAreaList> {\r\n return this.search<mb.IAreaList>('area', query);\r\n }\r\n\r\n public searchArtist(query: mb.ISearchQuery & mb.ILinkedEntitiesArtist): Promise<mb.IArtistList> {\r\n return this.search<mb.IArtistList>('artist', query);\r\n }\r\n\r\n public searchRelease(query: mb.ISearchQuery & mb.ILinkedEntitiesRelease): Promise<mb.IReleaseList> {\r\n return this.search<mb.IReleaseList>('release', query);\r\n }\r\n\r\n public searchReleaseGroup(query: mb.ISearchQuery & mb.ILinkedEntitiesReleaseGroup): Promise<mb.IReleaseGroupList> {\r\n return this.search<mb.IReleaseGroupList>('release-group', query);\r\n }\r\n\r\n public searchUrl(query: mb.ISearchQuery & mb.ILinkedEntitiesUrl): Promise<mb.IUrlList> {\r\n return this.search<mb.IUrlList>('url', query);\r\n }\r\n\r\n private async getSession(url: string): Promise<ISessionInformation> {\r\n\r\n const response: any = await got.get('login', {\r\n followRedirect: false, // Disable redirects\r\n responseType: 'text',\r\n ...this.options\r\n });\r\n\r\n return {\r\n csrf: MusicBrainzApi.fetchCsrf(response.body)\r\n };\r\n }\r\n}\r\n\r\nexport function makeAndQueryString(keyValuePairs: IFormData): string {\r\n return Object.keys(keyValuePairs).map(key => `${key}:\"${keyValuePairs[key]}\"`).join(' AND ');\r\n}\r\n
63
+ Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
64
+ <+>UTF-8
65
+ ===================================================================
66
+ diff --git a/src/musicbrainz-api.ts b/src/musicbrainz-api.ts
67
+ --- a/src/musicbrainz-api.ts (revision 7cc45f32b7537b2839102c06db1c73b67d7a1c4b)
68
+ +++ b/src/musicbrainz-api.ts (date 1646084941986)
69
+ @@ -21,6 +21,7 @@
70
+ export * from './musicbrainz.types';
71
+
72
+ import { promisify } from 'util';
73
+ +import { IBrowseRelease, IReleaseGroups } from './musicbrainz.types';
74
+
75
+ const retries = 3;
76
+
77
+ @@ -215,7 +216,7 @@
78
+ * @param mbid
79
+ * @param inc
80
+ */
81
+ - public getEntity<T>(entity: mb.EntityType, mbid: string, inc: Includes[] = []): Promise<T> {
82
+ + public lookupEntity<T>(entity: mb.EntityType, mbid: string, inc: Includes[] = []): Promise<T> {
83
+ return this.restGet<T>(`/${entity}/${mbid}`, {inc: inc.join(' ')});
84
+ }
85
+
86
+ @@ -224,8 +225,8 @@
87
+ * @param areaId Area MBID
88
+ * @param inc Sub-queries
89
+ */
90
+ - public getArea(areaId: string, inc: Includes[] = []): Promise<mb.IArea> {
91
+ - return this.getEntity<mb.IArea>('area', areaId, inc);
92
+ + public lookupArea(areaId: string, inc: Includes[] = []): Promise<mb.IArea> {
93
+ + return this.lookupEntity<mb.IArea>('area', areaId, inc);
94
+ }
95
+
96
+ /**
97
+ @@ -233,8 +234,8 @@
98
+ * @param artistId Artist MBID
99
+ * @param inc Sub-queries
100
+ */
101
+ - public getArtist(artistId: string, inc: Includes[] = []): Promise<mb.IArtist> {
102
+ - return this.getEntity<mb.IArtist>('artist', artistId, inc);
103
+ + public lookupArtist(artistId: string, inc: Includes[] = []): Promise<mb.IArtist> {
104
+ + return this.lookupEntity<mb.IArtist>('artist', artistId, inc);
105
+ }
106
+
107
+ /**
108
+ @@ -243,8 +244,8 @@
109
+ * @param inc Include: artist-credits, labels, recordings, release-groups, media, discids, isrcs (with recordings)
110
+ * ToDo: ['recordings', 'artists', 'artist-credits', 'isrcs', 'url-rels', 'release-groups']
111
+ */
112
+ - public getRelease(releaseId: string, inc: Includes[] = []): Promise<mb.IRelease> {
113
+ - return this.getEntity<mb.IRelease>('release', releaseId, inc);
114
+ + public lookupRelease(releaseId: string, inc: Includes[] = []): Promise<mb.IRelease> {
115
+ + return this.lookupEntity<mb.IRelease>('release', releaseId, inc);
116
+ }
117
+
118
+ /**
119
+ @@ -252,24 +253,24 @@
120
+ * @param releaseGroupId Release-group MBID
121
+ * @param inc Include: ToDo
122
+ */
123
+ - public getReleaseGroup(releaseGroupId: string, inc: Includes[] = []): Promise<mb.IReleaseGroup> {
124
+ - return this.getEntity<mb.IReleaseGroup>('release-group', releaseGroupId, inc);
125
+ + public lookupReleaseGroup(releaseGroupId: string, inc: Includes[] = []): Promise<mb.IReleaseGroup> {
126
+ + return this.lookupEntity<mb.IReleaseGroup>('release-group', releaseGroupId, inc);
127
+ }
128
+
129
+ /**
130
+ * Lookup work
131
+ * @param workId Work MBID
132
+ */
133
+ - public getWork(workId: string): Promise<mb.IWork> {
134
+ - return this.getEntity<mb.IWork>('work', workId);
135
+ + public lookupWork(workId: string): Promise<mb.IWork> {
136
+ + return this.lookupEntity<mb.IWork>('work', workId);
137
+ }
138
+
139
+ /**
140
+ * Lookup label
141
+ * @param labelId Label MBID
142
+ */
143
+ - public getLabel(labelId: string): Promise<mb.ILabel> {
144
+ - return this.getEntity<mb.ILabel>('label', labelId);
145
+ + public lookupLabel(labelId: string): Promise<mb.ILabel> {
146
+ + return this.lookupEntity<mb.ILabel>('label', labelId);
147
+ }
148
+
149
+ /**
150
+ @@ -277,9 +278,84 @@
151
+ * @param recordingId Label MBID
152
+ * @param inc Include: artist-credits, isrcs
153
+ */
154
+ - public getRecording(recordingId: string, inc: Includes[] = []): Promise<mb.IRecording> {
155
+ - return this.getEntity<mb.IRecording>('recording', recordingId, inc);
156
+ + public lookupRecording(recordingId: string, inc: Includes[] = []): Promise<mb.IRecording> {
157
+ + return this.lookupEntity<mb.IRecording>('recording', recordingId, inc);
158
+ + }
159
+ +
160
+ + // -----------------------------------------------------------------------------------------------------------------
161
+ + // Browse functions
162
+ + // -----------------------------------------------------------------------------------------------------------------
163
+ +
164
+ + // http://musicbrainz.org/ws/2/release?label=47e718e1-7ee4-460c-b1cc-1192a841c6e5&offset=12&limit=2
165
+ + // http://musicbrainz.org/ws/2/release/59211ea4-ffd2-4ad9-9a4e-941d3148024a?inc=artist-credits+labels+discids+recordings
166
+ +
167
+ + /**
168
+ + * Generic browse function
169
+ + * https://wiki.musicbrainz.org/Development/JSON_Web_Service#Browse_Requests
170
+ + * @param entity MusicBrainz entity
171
+ + * @param query Query, like: {<entity>: <MBID:}
172
+ + */
173
+ + public browseEntity<T>(entity: mb.EntityType, query?: { [key: string]: any; }): Promise<T> {
174
+ + return this.restGet<T>(`/${entity}/entity`, query);
175
+ + }
176
+ +
177
+ + /**
178
+ + * Browse area
179
+ + * @param query Query, like: {<entity>: <MBID:}
180
+ + */
181
+ + public browseArea(query?: { [key: string]: any; }): Promise<mb.IArea> {
182
+ + return this.browseEntity<mb.IArea>('area', query);
183
+ + }
184
+ +
185
+ + /**
186
+ + * Browse artist
187
+ + * @param query Query, like: {<entity>: <MBID:}
188
+ + */
189
+ + public browseArtist(query?: IBrowseRelease): Promise<mb.IArtist> {
190
+ + return this.browseEntity<mb.IArtist>('artist', query);
191
+ + }
192
+ +
193
+ + /**
194
+ + * Browse release
195
+ + * @param query Query, like: {<entity>: <MBID:}
196
+ + */
197
+ + public browseRelease(query?: IBrowseRelease): Promise<mb.IRelease> {
198
+ + return this.browseEntity<mb.IRelease>('release', query);
199
+ + }
200
+ +
201
+ + /**
202
+ + * Browse release-group
203
+ + * @param query Query, like: {<entity>: <MBID:}
204
+ + */
205
+ + public browseReleaseGroup(query?: IReleaseGroups): Promise<mb.IReleaseGroup> {
206
+ + return this.browseEntity<mb.IReleaseGroup>('release-group', query);
207
+ + }
208
+ +
209
+ + /**
210
+ + * Browse work
211
+ + * @param query Query, like: {<entity>: <MBID:}
212
+ + */
213
+ + public browseWork(query?: { [key: string]: any; }): Promise<mb.IWork> {
214
+ + return this.browseEntity<mb.IWork>('work', query);
215
+ + }
216
+ +
217
+ + /**
218
+ + * Browse label
219
+ + * @param query Query, like: {<entity>: <MBID:}
220
+ + */
221
+ + public browseLabel(query?: { [key: string]: any; }): Promise<mb.ILabel> {
222
+ + return this.browseEntity<mb.ILabel>('label', query);
223
+ }
224
+ +
225
+ + /**
226
+ + * Browse recording
227
+ + * @param query Query, like: {<entity>: <MBID:}
228
+ + */
229
+ + public browseRecording(query?: { [key: string]: any; }): Promise<mb.IRecording> {
230
+ + return this.browseEntity<mb.IRecording>('recording', query);
231
+ + }
232
+ +
233
+ + // ---------------------------------------------------------------------------
234
+
235
+ public async postRecording(xmlMetadata: XmlMetadata): Promise<void> {
236
+ return this.post('recording', xmlMetadata);
237
+ Index: test/test-musicbrainz-api.ts
238
+ IDEA additional info:
239
+ Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
240
+ <+>import { IMusicBrainzConfig, LinkType, MusicBrainzApi } from '../src/musicbrainz-api';\r\nimport { assert } from 'chai';\r\nimport { XmlMetadata } from '../src/xml/xml-metadata';\r\nimport * as mb from '../src/musicbrainz.types';\r\nimport * as fs from 'fs';\r\nimport * as path from 'path';\r\n\r\nconst packageInfo = require('../package.json');\r\n\r\nconst appUrl = 'https://github.com/Borewit/musicbrainz-api';\r\n\r\nconst testBotAccount = {\r\n username: process.env.MBUSER,\r\n password: process.env.MBPWD\r\n};\r\n\r\nconst testApiConfig: IMusicBrainzConfig = {\r\n botAccount: testBotAccount,\r\n baseUrl: 'https://test.musicbrainz.org',\r\n\r\n /**\r\n * Enable proxy, like Fiddler\r\n */\r\n proxy: process.env.MBPROXY,\r\n\r\n appName: packageInfo.name,\r\n appVersion: packageInfo.version,\r\n appContactInfo: appUrl\r\n};\r\n\r\nconst searchApiConfig: IMusicBrainzConfig = {\r\n\r\n baseUrl: 'https://musicbrainz.org',\r\n\r\n /**\r\n * Enable proxy, like Fiddler\r\n */\r\n proxy: process.env.MBPROXY,\r\n\r\n appName: packageInfo.name,\r\n appVersion: packageInfo.version,\r\n appContactInfo: appUrl\r\n};\r\n\r\nconst mbTestApi = new MusicBrainzApi(testApiConfig);\r\nconst mbApi = new MusicBrainzApi(searchApiConfig);\r\n\r\n// Hack shared rate-limiter\r\n(mbApi as any).rateLimiter = (mbTestApi as any).rateLimiter;\r\n\r\ndescribe('MusicBrainz-api', function() {\r\n\r\n this.timeout(20000); // MusicBrainz has a rate limiter\r\n\r\n const mbid = {\r\n artist: {\r\n Stromae: 'ab2528d9-719f-4261-8098-21849222a0f2'\r\n },\r\n recording: {\r\n Formidable: '16afa384-174e-435e-bfa3-5591accda31c',\r\n Montilla: '2faab3ff-1b3a-4378-bfa2-0513446644ed'\r\n },\r\n release: {\r\n Formidable: '976e0677-a480-4a5e-a177-6a86c1900bbf',\r\n Anomalie: '478aaba4-9425-4a67-8951-a77739462df4',\r\n RacineCarree: [\r\n '348662a8-54ce-4d14-adf5-3ce2cefd57bb',\r\n 'c22bdb3a-69c0-449a-9ef5-99796bb0f2d7',\r\n 'de57c1d9-5e65-420f-a896-1332e87d4c09'\r\n ]\r\n },\r\n releaseGroup: {\r\n Formidable: '19099ea5-3600-4154-b482-2ec68815883e',\r\n RacineCarree: 'd079dc50-fa9b-4a88-90f4-5e8723accd75'\r\n },\r\n work: {\r\n Formidable: 'b2aa02f4-6c95-43be-a426-aedb9f9a3805'\r\n },\r\n area: {\r\n Belgium: '5b8a5ee5-0bb3-34cf-9a75-c27c44e341fc',\r\n IleDeFrance: 'd79e4501-8cba-431b-96e7-bb9976f0ae76'\r\n },\r\n label: {\r\n Mosaert: '0550200c-22c1-4c62-b761-ef0b3665262b'\r\n }\r\n };\r\n\r\n const spotify = {\r\n album: {\r\n RacineCarree: {\r\n id: '6uyslsVGFsHKzdGUosFwBM'\r\n }\r\n },\r\n track: {\r\n Formidable: {\r\n id: '2AMysGXOe0zzZJMtH3Nizb'\r\n }\r\n }\r\n };\r\n\r\n it('Required environment variable', () => {\r\n assert.isDefined(process.env.MBUSER, 'process.env.MBUSER');\r\n assert.isDefined(process.env.MBPWD, 'process.env.MBPWD');\r\n });\r\n\r\n it('Extract CSRF', () => {\r\n const html = fs.readFileSync(path.join(__dirname, 'csrf.html'), 'utf8');\r\n const csrf = MusicBrainzApi.fetchCsrf(html);\r\n assert.deepStrictEqual(csrf, {\r\n sessionKey: 'csrf_token:x0VIlHob5nPcWKqJIwNPwE5Y3kE+nGQ9fccgTSYbuMU=',\r\n token: '6G9f/xJ6Y4fLVvfYGHrzBUM34j6hy4CJrBi3VkVwO9I='\r\n }, 'CSRF data');\r\n });\r\n\r\n describe('Read metadata', () => {\r\n\r\n describe('Lookup', () => {\r\n\r\n it('get area', async () => {\r\n const area = await mbApi.getArea(mbid.area.Belgium);\r\n assert.strictEqual(area.id, mbid.area.Belgium);\r\n assert.strictEqual(area.name, 'Belgium');\r\n });\r\n\r\n it('get artist', async () => {\r\n const artist = await mbApi.getArtist(mbid.artist.Stromae);\r\n assert.strictEqual(artist.id, mbid.artist.Stromae);\r\n assert.strictEqual(artist.name, 'Stromae');\r\n });\r\n\r\n describe('Release', () => {\r\n\r\n it('get release Formidable', async () => {\r\n const release = await mbApi.getRelease(mbid.release.Formidable);\r\n assert.strictEqual(release.id, mbid.release.Formidable);\r\n assert.strictEqual(release.title, 'Formidable');\r\n });\r\n\r\n it('check release Anomalie', async () => {\r\n const release = await mbApi.getRelease(mbid.release.Anomalie);\r\n assert.strictEqual(release.id, mbid.release.Anomalie);\r\n assert.strictEqual(release.title, 'Anomalie');\r\n });\r\n\r\n [\r\n {inc: 'artist-credits', key: 'artist-credit'},\r\n {inc: 'artists', key: 'artist-credit'},\r\n {inc: 'collections', key: 'collections'},\r\n {inc: 'labels', key: 'release-events'},\r\n {inc: 'media', key: 'media'},\r\n // {inc: 'recordings', key: 'recordings'},\r\n {inc: 'release-groups', key: 'release-group'}\r\n ].forEach(inc => {\r\n\r\n it(`get release, include: '${inc.inc}'`, async () => {\r\n const release = await mbApi.getRelease(mbid.release.Formidable, [inc.inc as any]);\r\n assert.strictEqual(release.id, mbid.release.Formidable);\r\n assert.strictEqual(release.title, 'Formidable');\r\n assert.isDefined(release[inc.key], `Should include '${inc.key}'`);\r\n });\r\n });\r\n\r\n });\r\n\r\n describe('Release-group', () => {\r\n\r\n it('get release-group', async () => {\r\n const releaseGroup = await mbApi.getReleaseGroup(mbid.releaseGroup.Formidable);\r\n assert.strictEqual(releaseGroup.id, mbid.releaseGroup.Formidable);\r\n assert.strictEqual(releaseGroup.title, 'Formidable');\r\n });\r\n\r\n [\r\n {inc: 'artist-credits', key: 'artist-credit'}\r\n ].forEach(inc => {\r\n\r\n it(`get release-group, include: '${inc.inc}'`, async () => {\r\n const group = await mbApi.getReleaseGroup(mbid.releaseGroup.Formidable, [inc.inc as any]);\r\n assert.strictEqual(group.id, mbid.releaseGroup.Formidable);\r\n assert.strictEqual(group.title, 'Formidable');\r\n assert.isDefined(group[inc.key], `Should include '${inc.key}'`);\r\n });\r\n });\r\n\r\n });\r\n\r\n it('get work', async () => {\r\n const work = await mbApi.getWork(mbid.work.Formidable);\r\n assert.strictEqual(work.id, mbid.work.Formidable);\r\n assert.strictEqual(work.title, 'Formidable');\r\n });\r\n\r\n it('get label', async () => {\r\n const label = await mbApi.getLabel(mbid.label.Mosaert);\r\n assert.strictEqual(label.id, mbid.label.Mosaert);\r\n assert.strictEqual(label.name, 'Mosaert');\r\n });\r\n\r\n describe('Recording', () => {\r\n\r\n it('get recording', async () => {\r\n const recording = await mbApi.getRecording(mbid.recording.Formidable);\r\n assert.strictEqual(recording.id, mbid.recording.Formidable);\r\n assert.strictEqual(recording.title, 'Formidable');\r\n assert.isUndefined(recording.isrcs);\r\n assert.isUndefined(recording['artist-credit']);\r\n assert.isUndefined(recording.releases);\r\n });\r\n\r\n [\r\n {inc: 'isrcs', key: 'isrcs'},\r\n {inc: 'artist-credits', key: 'artist-credit'},\r\n {inc: 'artists', key: 'artist-credit'},\r\n {inc: 'releases', key: 'releases'}\r\n ].forEach(inc => {\r\n\r\n it(`get recording, include: '${inc.inc}'`, async () => {\r\n const recording = await mbApi.getRecording(mbid.recording.Formidable, [inc.inc as any]);\r\n assert.strictEqual(recording.id, mbid.recording.Formidable);\r\n assert.strictEqual(recording.title, 'Formidable');\r\n assert.isDefined(recording[inc.key], `Should include '${inc.key}'`);\r\n });\r\n });\r\n\r\n it('get extended recording', async () => {\r\n const recording = await mbApi.getRecording(mbid.recording.Formidable, ['isrcs', 'artists', 'releases', 'url-rels']);\r\n assert.strictEqual(recording.id, mbid.recording.Formidable);\r\n assert.strictEqual(recording.title, 'Formidable');\r\n assert.isDefined(recording.isrcs);\r\n assert.isDefined(recording['artist-credit']);\r\n // assert.isDefined(recording.releases);\r\n });\r\n });\r\n\r\n });\r\n\r\n describe('Query', () => {\r\n\r\n it('query: Queen - We Will Rock You', async () => {\r\n const query = 'query=\"We Will Rock You\" AND arid:0383dadf-2a4e-4d10-a46a-e9e041da8eb3';\r\n const result = await mbApi.search<mb.IReleaseGroupList>('release-group', {query});\r\n assert.isAtLeast(result.count, 1);\r\n });\r\n\r\n });\r\n\r\n describe('Search', () => {\r\n\r\n describe('generic search', () => {\r\n\r\n it('find artist: Stromae', async () => {\r\n const result = await mbApi.search('artist', {query: 'Stromae'});\r\n assert.isAtLeast(result.count, 1);\r\n });\r\n\r\n });\r\n\r\n describe('searchArtist', () => {\r\n\r\n it('find artist: Stromae', async () => {\r\n const result = await mbApi.searchArtist({query: 'Stromae'});\r\n assert.isAtLeast(result.count, 1);\r\n assert.isAtLeast(result.artists.length, 1);\r\n assert.strictEqual(result.artists[0].id, mbid.artist.Stromae);\r\n });\r\n\r\n });\r\n\r\n describe('searchReleaseGroup', () => {\r\n\r\n it('find release-group: Racine carrée', async () => {\r\n const result = await mbApi.searchReleaseGroup({query: 'Racine carrée'});\r\n assert.isAtLeast(result.count, 1);\r\n assert.isAtLeast(result['release-groups'].length, 1);\r\n assert.strictEqual(result['release-groups'][0].id, mbid.releaseGroup.RacineCarree);\r\n });\r\n\r\n it('find release-group: Racine carrée, by artist and group name', async () => {\r\n const result = await mbApi.searchReleaseGroup({query: {release: 'Racine carrée', artist: 'Stromae'}});\r\n assert.isAtLeast(result.count, 1);\r\n assert.isAtLeast(result['release-groups'].length, 1);\r\n assert.strictEqual(result['release-groups'][0].id, mbid.releaseGroup.RacineCarree);\r\n });\r\n });\r\n\r\n describe('searchRelease', () => {\r\n\r\n it('find release-group: Racine carrée', async () => {\r\n const result = await mbApi.searchRelease({query: {release: 'Racine carrée'}});\r\n assert.isAtLeast(result.count, 2);\r\n assert.isAtLeast(result.releases.length, 2);\r\n assert.includeMembers(result.releases.map(release => release.id), mbid.release.RacineCarree);\r\n });\r\n\r\n it('find release by barcode', async () => {\r\n const result = await mbApi.searchRelease({query: {barcode: 602537479870}});\r\n assert.isAtLeast(result.count, 1);\r\n assert.isAtLeast(result.releases.length, 1);\r\n assert.equal(result.releases[0].id, mbid.release.RacineCarree[2]);\r\n });\r\n\r\n it('find release by barcode', async () => {\r\n const result = await mbApi.searchRelease({query: {barcode: 602537479870}});\r\n assert.isAtLeast(result.count, 1);\r\n assert.isAtLeast(result.releases.length, 1);\r\n assert.equal(result.releases[0].id, mbid.release.RacineCarree[2]);\r\n });\r\n\r\n it('find releases by artist use query API', async () => {\r\n const artist_mbid = 'eeb41a1e-4326-4d04-8c47-0f564ceecd68';\r\n const result = await mbApi.searchRelease({query: {arid: artist_mbid} });\r\n assert.isAtLeast(result.count, 1);\r\n assert.isAtLeast(result.releases.length, 1);\r\n });\r\n\r\n it('find releases by artist use browse API', async () => {\r\n const artist_mbid = 'eeb41a1e-4326-4d04-8c47-0f564ceecd68';\r\n const result = await mbApi.searchRelease({artist: artist_mbid});\r\n assert.isAtLeast(result['release-count'], 1);\r\n assert.isAtLeast(result.releases.length, 1);\r\n });\r\n\r\n it('find releases with inc', async () => {\r\n const artist_mbid = '024a7074-dcef-4851-8f9c-090a9746a75a';\r\n const result = await mbApi.searchRelease({\r\n query: `arid:${artist_mbid}`,\r\n inc: ['release-groups', 'media', 'label-rels'],\r\n offset: 0,\r\n limit: 25\r\n });\r\n assert.isAtLeast(result.count, 1);\r\n });\r\n\r\n });\r\n\r\n describe('searchArea', () => {\r\n\r\n it('find area by name', async () => {\r\n const result = await mbApi.searchArea({query: 'Île-de-France'});\r\n assert.isAtLeast(result.count, 1);\r\n assert.isAtLeast(result.areas.length, 1);\r\n assert.strictEqual(result.areas[0].id, mbid.area.IleDeFrance);\r\n });\r\n });\r\n\r\n describe('searchUrl', () => {\r\n\r\n const spotifyUrl = 'https://open.spotify.com/album/' + spotify.album.RacineCarree.id;\r\n\r\n it('find url by url', async () => {\r\n const result = await mbApi.searchUrl({query: {url: spotifyUrl}});\r\n assert.isAtLeast(result.count, 1);\r\n assert.isAtLeast(result.urls.length, 1);\r\n assert.strictEqual(result.urls[0].resource, spotifyUrl);\r\n });\r\n });\r\n });\r\n\r\n });\r\n\r\n describe('Submit API', () => {\r\n\r\n it('Post ISRC Formidable', async () => {\r\n const isrc_Formidable = 'BET671300161';\r\n const xmlMetadata = new XmlMetadata();\r\n const xmlRecording = xmlMetadata.pushRecording(mbid.recording.Formidable);\r\n xmlRecording.isrcList.pushIsrc(isrc_Formidable);\r\n\r\n await mbTestApi.post('recording', xmlMetadata);\r\n });\r\n\r\n });\r\n\r\n /**\r\n * https://wiki.musicbrainz.org/Development/Release_Editor_Seeding\r\n */\r\n describe.skip('User (bot) post form-data API', () => {\r\n\r\n it('login & logout', async () => {\r\n for (let n = 1; n <= 2; ++n) {\r\n assert.isTrue(await mbTestApi.login(), `Login ${n}`);\r\n assert.isTrue(await mbTestApi.logout(), `Logout ${n}`);\r\n }\r\n });\r\n\r\n describe('Recording', () => {\r\n\r\n it('add link', async () => {\r\n const recording = await mbTestApi.getRecording(mbid.recording.Formidable);\r\n assert.strictEqual(recording.id, mbid.recording.Formidable);\r\n assert.strictEqual(recording.title, 'Formidable');\r\n\r\n await mbTestApi.addUrlToRecording(recording, {\r\n linkTypeId: LinkType.stream_for_free,\r\n text: 'https://open.spotify.com/track/' + spotify.track.Formidable.id\r\n });\r\n });\r\n\r\n it('add Spotify-ID', async () => {\r\n const recording = await mbTestApi.getRecording(mbid.recording.Formidable);\r\n\r\n const editNote = `Unit-test musicbrainz-api (${appUrl}), test augment recording with Spotify URL & ISRC`;\r\n await mbTestApi.addSpotifyIdToRecording(recording, spotify.track.Formidable.id, editNote);\r\n });\r\n\r\n it('add Spotify-ID to recording with ISRC', async () => {\r\n // https://test.musicbrainz.org/recording/a75b85bf-63dd-4fe1-8008-d15541b93bac\r\n const recording_id = 'a75b85bf-63dd-4fe1-8008-d15541b93bac';\r\n\r\n const recording = await mbTestApi.getRecording(recording_id);\r\n const editNote = `Unit-test musicbrainz-api (${appUrl}), test augment recording with Spotify URL & ISRC`;\r\n await mbTestApi.addSpotifyIdToRecording(recording, '3ZDO5YINwfoifRQ3ElshPM', editNote);\r\n });\r\n\r\n });\r\n\r\n describe('ISRC', () => {\r\n\r\n it('add ISRC', async () => {\r\n const recording = await mbTestApi.getRecording(mbid.recording.Formidable, ['isrcs']);\r\n assert.strictEqual(recording.id, mbid.recording.Formidable);\r\n assert.strictEqual(recording.title, 'Formidable');\r\n\r\n await mbTestApi.addIsrc(recording, 'BET671300161');\r\n });\r\n\r\n });\r\n\r\n /**\r\n * https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2#ISRC_submission\r\n */\r\n describe('ISRC submission', () => {\r\n\r\n it('add ISRC', async () => {\r\n const xmlMedata = new XmlMetadata();\r\n const xmlRec = xmlMedata.pushRecording('94fb868b-9233-4f9e-966b-e8036bf7461e');\r\n xmlRec.isrcList.pushIsrc('GB5EM1801762');\r\n await mbTestApi.post('recording', xmlMedata);\r\n });\r\n\r\n });\r\n\r\n });\r\n\r\n});\r\n
241
+ Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
242
+ <+>UTF-8
243
+ ===================================================================
244
+ diff --git a/test/test-musicbrainz-api.ts b/test/test-musicbrainz-api.ts
245
+ --- a/test/test-musicbrainz-api.ts (revision 7cc45f32b7537b2839102c06db1c73b67d7a1c4b)
246
+ +++ b/test/test-musicbrainz-api.ts (date 1646085514342)
247
+ @@ -116,28 +116,28 @@
248
+
249
+ describe('Lookup', () => {
250
+
251
+ - it('get area', async () => {
252
+ - const area = await mbApi.getArea(mbid.area.Belgium);
253
+ + it('area', async () => {
254
+ + const area = await mbApi.lookupArea(mbid.area.Belgium);
255
+ assert.strictEqual(area.id, mbid.area.Belgium);
256
+ assert.strictEqual(area.name, 'Belgium');
257
+ });
258
+
259
+ - it('get artist', async () => {
260
+ - const artist = await mbApi.getArtist(mbid.artist.Stromae);
261
+ + it('artist', async () => {
262
+ + const artist = await mbApi.lookupArtist(mbid.artist.Stromae);
263
+ assert.strictEqual(artist.id, mbid.artist.Stromae);
264
+ assert.strictEqual(artist.name, 'Stromae');
265
+ });
266
+
267
+ describe('Release', () => {
268
+
269
+ - it('get release Formidable', async () => {
270
+ - const release = await mbApi.getRelease(mbid.release.Formidable);
271
+ + it('release Formidable', async () => {
272
+ + const release = await mbApi.lookupRelease(mbid.release.Formidable);
273
+ assert.strictEqual(release.id, mbid.release.Formidable);
274
+ assert.strictEqual(release.title, 'Formidable');
275
+ });
276
+
277
+ it('check release Anomalie', async () => {
278
+ - const release = await mbApi.getRelease(mbid.release.Anomalie);
279
+ + const release = await mbApi.lookupRelease(mbid.release.Anomalie);
280
+ assert.strictEqual(release.id, mbid.release.Anomalie);
281
+ assert.strictEqual(release.title, 'Anomalie');
282
+ });
283
+ @@ -153,7 +153,7 @@
284
+ ].forEach(inc => {
285
+
286
+ it(`get release, include: '${inc.inc}'`, async () => {
287
+ - const release = await mbApi.getRelease(mbid.release.Formidable, [inc.inc as any]);
288
+ + const release = await mbApi.lookupRelease(mbid.release.Formidable, [inc.inc as any]);
289
+ assert.strictEqual(release.id, mbid.release.Formidable);
290
+ assert.strictEqual(release.title, 'Formidable');
291
+ assert.isDefined(release[inc.key], `Should include '${inc.key}'`);
292
+ @@ -164,8 +164,8 @@
293
+
294
+ describe('Release-group', () => {
295
+
296
+ - it('get release-group', async () => {
297
+ - const releaseGroup = await mbApi.getReleaseGroup(mbid.releaseGroup.Formidable);
298
+ + it('release-group', async () => {
299
+ + const releaseGroup = await mbApi.lookupReleaseGroup(mbid.releaseGroup.Formidable);
300
+ assert.strictEqual(releaseGroup.id, mbid.releaseGroup.Formidable);
301
+ assert.strictEqual(releaseGroup.title, 'Formidable');
302
+ });
303
+ @@ -175,7 +175,7 @@
304
+ ].forEach(inc => {
305
+
306
+ it(`get release-group, include: '${inc.inc}'`, async () => {
307
+ - const group = await mbApi.getReleaseGroup(mbid.releaseGroup.Formidable, [inc.inc as any]);
308
+ + const group = await mbApi.lookupReleaseGroup(mbid.releaseGroup.Formidable, [inc.inc as any]);
309
+ assert.strictEqual(group.id, mbid.releaseGroup.Formidable);
310
+ assert.strictEqual(group.title, 'Formidable');
311
+ assert.isDefined(group[inc.key], `Should include '${inc.key}'`);
312
+ @@ -184,22 +184,22 @@
313
+
314
+ });
315
+
316
+ - it('get work', async () => {
317
+ - const work = await mbApi.getWork(mbid.work.Formidable);
318
+ + it('work', async () => {
319
+ + const work = await mbApi.lookupWork(mbid.work.Formidable);
320
+ assert.strictEqual(work.id, mbid.work.Formidable);
321
+ assert.strictEqual(work.title, 'Formidable');
322
+ });
323
+
324
+ - it('get label', async () => {
325
+ - const label = await mbApi.getLabel(mbid.label.Mosaert);
326
+ + it('label', async () => {
327
+ + const label = await mbApi.lookupLabel(mbid.label.Mosaert);
328
+ assert.strictEqual(label.id, mbid.label.Mosaert);
329
+ assert.strictEqual(label.name, 'Mosaert');
330
+ });
331
+
332
+ describe('Recording', () => {
333
+
334
+ - it('get recording', async () => {
335
+ - const recording = await mbApi.getRecording(mbid.recording.Formidable);
336
+ + it('recording', async () => {
337
+ + const recording = await mbApi.lookupRecording(mbid.recording.Formidable);
338
+ assert.strictEqual(recording.id, mbid.recording.Formidable);
339
+ assert.strictEqual(recording.title, 'Formidable');
340
+ assert.isUndefined(recording.isrcs);
341
+ @@ -214,16 +214,16 @@
342
+ {inc: 'releases', key: 'releases'}
343
+ ].forEach(inc => {
344
+
345
+ - it(`get recording, include: '${inc.inc}'`, async () => {
346
+ - const recording = await mbApi.getRecording(mbid.recording.Formidable, [inc.inc as any]);
347
+ + it(`recording, include: '${inc.inc}'`, async () => {
348
+ + const recording = await mbApi.lookupRecording(mbid.recording.Formidable, [inc.inc as any]);
349
+ assert.strictEqual(recording.id, mbid.recording.Formidable);
350
+ assert.strictEqual(recording.title, 'Formidable');
351
+ assert.isDefined(recording[inc.key], `Should include '${inc.key}'`);
352
+ });
353
+ });
354
+
355
+ - it('get extended recording', async () => {
356
+ - const recording = await mbApi.getRecording(mbid.recording.Formidable, ['isrcs', 'artists', 'releases', 'url-rels']);
357
+ + it('extended recording', async () => {
358
+ + const recording = await mbApi.lookupRecording(mbid.recording.Formidable, ['isrcs', 'artists', 'releases', 'url-rels']);
359
+ assert.strictEqual(recording.id, mbid.recording.Formidable);
360
+ assert.strictEqual(recording.title, 'Formidable');
361
+ assert.isDefined(recording.isrcs);
362
+ @@ -234,6 +234,55 @@
363
+
364
+ });
365
+
366
+ + describe('Browse', () => {
367
+ +
368
+ + it('area', async () => {
369
+ + const area = await mbApi.browseArea({alias: 'Βέλγιο'});
370
+ + assert.isObject(area);
371
+ + });
372
+ +
373
+ + it('artist', async () => {
374
+ + });
375
+ +
376
+ + describe('Recording', () => {
377
+ +
378
+ + it('by artist-credits', async () => {
379
+ + });
380
+ +
381
+ + });
382
+ +
383
+ + describe('Release', () => {
384
+ +
385
+ + it('by artist-credits', async () => {
386
+ + //
387
+ + });
388
+ +
389
+ + it('by labels', async () => {
390
+ + //
391
+ + });
392
+ + });
393
+ +
394
+ + describe('Release-group', () => {
395
+ +
396
+ + it('by artist-credits', async () => {
397
+ +
398
+ + });
399
+ + });
400
+ +
401
+ +
402
+ + it('label', async () => {
403
+ + });
404
+ +
405
+ + describe('work', async () => {
406
+ + it('by aliases', async () => {
407
+ + });
408
+ + });
409
+ +
410
+ + it('extended recording', async () => {
411
+ + });
412
+ + });
413
+ +
414
+ +
415
+ describe('Query', () => {
416
+
417
+ it('query: Queen - We Will Rock You', async () => {
418
+ @@ -386,7 +435,7 @@
419
+ describe('Recording', () => {
420
+
421
+ it('add link', async () => {
422
+ - const recording = await mbTestApi.getRecording(mbid.recording.Formidable);
423
+ + const recording = await mbTestApi.lookupRecording(mbid.recording.Formidable);
424
+ assert.strictEqual(recording.id, mbid.recording.Formidable);
425
+ assert.strictEqual(recording.title, 'Formidable');
426
+
427
+ @@ -397,7 +446,7 @@
428
+ });
429
+
430
+ it('add Spotify-ID', async () => {
431
+ - const recording = await mbTestApi.getRecording(mbid.recording.Formidable);
432
+ + const recording = await mbTestApi.lookupRecording(mbid.recording.Formidable);
433
+
434
+ const editNote = `Unit-test musicbrainz-api (${appUrl}), test augment recording with Spotify URL & ISRC`;
435
+ await mbTestApi.addSpotifyIdToRecording(recording, spotify.track.Formidable.id, editNote);
436
+ @@ -407,7 +456,7 @@
437
+ // https://test.musicbrainz.org/recording/a75b85bf-63dd-4fe1-8008-d15541b93bac
438
+ const recording_id = 'a75b85bf-63dd-4fe1-8008-d15541b93bac';
439
+
440
+ - const recording = await mbTestApi.getRecording(recording_id);
441
+ + const recording = await mbTestApi.lookupRecording(recording_id);
442
+ const editNote = `Unit-test musicbrainz-api (${appUrl}), test augment recording with Spotify URL & ISRC`;
443
+ await mbTestApi.addSpotifyIdToRecording(recording, '3ZDO5YINwfoifRQ3ElshPM', editNote);
444
+ });
445
+ @@ -417,7 +466,7 @@
446
+ describe('ISRC', () => {
447
+
448
+ it('add ISRC', async () => {
449
+ - const recording = await mbTestApi.getRecording(mbid.recording.Formidable, ['isrcs']);
450
+ + const recording = await mbTestApi.lookupRecording(mbid.recording.Formidable, ['isrcs']);
451
+ assert.strictEqual(recording.id, mbid.recording.Formidable);
452
+ assert.strictEqual(recording.title, 'Formidable');
453
+
@@ -0,0 +1,4 @@
1
+ <changelist name="Uncommitted_changes_before_Checkout_at_28-2-2022_22_59_[Default_Changelist]" date="1646085571687" recycled="true" deleted="true">
2
+ <option name="PATH" value="$PROJECT_DIR$/.idea/shelf/Uncommitted_changes_before_Checkout_at_28-2-2022_22_59_[Default_Changelist]/shelved.patch" />
3
+ <option name="DESCRIPTION" value="Uncommitted changes before Checkout at 28-2-2022 22:59 [Default Changelist]" />
4
+ </changelist>