Changes & Improvements
- Type Refactor
- API Refactor
- New Provider: Subvert
- Fix Bandcamp, Tidal Closes #206
- Providers page

- SAMBL Seeder

- Find page improvements
- Added parallelization for UPC and ISRC lookups
- Should be significantly faster
- Server and Client API Handler
- Package updates
- Tons of optimizations, tweaks, and refactors, and bug fixes Closes #205 #150
- Customizable edit server

- Add environment variables
MUSICBRAINZ_DISABLE_RATE_LIMIT=<1 or 0> [optional] MUSICBRAINZ_BASE_URL=https://musicbrainz.org [optional] BANDCAMP_COOKIE=<string> [optional]
Types
//api-types.ts
export type DeepSearchMethod = "most_common" | "name_similarity"
export class DeepSearchData {
provider: ProviderNamespace
mbArtists: DeepSearchArtist[]
albums: AlbumStack[]
sourceArtist: ArtistObject
}
export class DeepSearchArtist extends PartialArtistObject {
nameSimilarity: number
occurrences: number | null
mostCommonMBID: boolean
}
export class DeepSearchEditData extends DeepSearchData {
method: DeepSearchMethod
mbArtist: DeepSearchArtist
}
export class ArtistData {
providerData: ArtistObject
mbData?: ArtistObject | null
}
export class ArtistLookupData {
mbid?: string | null
provider: ProviderNamespace
provider_id: string
}
export class URLLookupData {
albums: ExtendedAlbumObject[] | AlbumObject[]
tracks: ExtendedTrackObject[]
artists: ArtistObject[]
query: string
}
export type ArtistSearchData = Record<string, AggregatedArtist>
export class SAMBLApiError {
error: string
code?: number
details?: string | null
parameters?: string[]
request?: string
url?: string
provider?: ProviderNamespace
}
export class FindData {
type: 'UPC' | 'ISRC'
data: AlbumObject[] | TrackObject[] | ArtistObject[]
issues: SAMBLApiError[]
}
export class ISRCData {
isrcs: string[]
}
export class UPCData {
upcs: string[]
}
export class LangData {
script: ReleaseScript | null
language: ReleaseLanguage | null
}
export class ReleaseCountData {
releaseCount: number
ownCount: number
featuredCount?: number | null
}
export interface APITimingStage { name: string, duration: number, provider?: ProviderNamespace, subStages?: APITimingStage[] }
export class APITimingData {
totalDuration: number;
stages: APITimingStage[]
}
export class SAMBLAPIResponse<T> {
error?: SAMBLApiError
data?: T
timings: APITimingData
}
export type SingleAlbumData = AlbumStack;
export type PingPongData = "Pong";
export type SAMBLAPIData =
UPCData |
ISRCData |
FindData |
LangData |
SingleAlbumData |
RawAggregateData |
AggregatedData |
DeepSearchData |
AlbumData |
ArtistData |
ReleaseCountData |
ExtendedAlbumData |
ArtistLookupData |
URLLookupData |
PingPongData |
ArtistSearchData//provider-types.ts
export type ProviderNamespace = FullProviderNamespace | "musixmatch"
export type FullProviderNamespace = "spotify" | "tidal" | "deezer" | "musicbrainz" | "soundcloud" | "bandcamp" | "applemusic" | "naver" | "qobuz" | "discogs" | "volumo" | "subvert";
export type ObjectType = "partialArtist" | "artist" | "album" | "track" | "label"
/** Incomplete Format List */
export type MediumFormat = "Digital Media" | "CD" | "Vinyl" | "Cassette"
export class GenericObject {
provider: ProviderNamespace;
id: string | null;
name: string;
url: ExternalUrlData | null;
type: ObjectType
mbid?: string | null;
}
export class ImageObject extends GenericObject {
imageUrl: string | null;
imageUrlSmall: string | null;
}
export class PartialArtistObject extends ImageObject {
name: string
url: ExternalUrlData;
id: string;
provider: ProviderNamespace;
type: "partialArtist" | "artist";
};
export class ArtistObject extends PartialArtistObject {
bannerUrl: string | null;
relevance: string;
info: string;
genres: string[] | null;
followers: number | null;
popularity: number | null;
type: "artist";
};
export class ExtendedArtistObject extends ArtistObject {
aliases: string[] | null;
}
export class AlbumObject extends ImageObject {
provider: ProviderNamespace;
id: string;
name: string;
url: ExternalUrlData;
albumArtists: PartialArtistObject[];
artistNames: string[];
releaseDate: string | null;
trackCount: number | null;
albumType: string | null;
upc: string | null;
mediums: MediumObject[];
labels: LabelObject[] | null;
copyrights: string[] | null;
genres: string[] | null;
type: "album";
script?: ReleaseScript;
language?: ReleaseLanguage
};
export class LabelObject extends GenericObject {
catalogNumber?: string | null;
}
export type UrlType = 'album' | 'track' | 'artist' | 'label';
export class UrlData {
type: UrlType | null;
id: string | null;
};
export class UrlInfo extends UrlData {
provider: ProviderNamespace;
}
export class ExternalUrlData {
url: string;
urlInfo: UrlInfo | null;
mbTypes: number[]
}
export class CommonExtends {
comment: string | null;
externalUrls: string[] | null;
}
export class ExtendedAlbumObject extends AlbumObject {
comment: string | null;
externalUrls: string[] | null;
hasImage: boolean;
albumArtists: ArtistObject[];
override mediums: ExtendedMediumObject[];
};
export class MediumObject {
/**
* Medium format, if unspecified, assumed to be "Digital Media"
*/
format?: MediumFormat | null;
name?: string | null;
tracks: TrackObject[];
number: number | null;
}
export class ExtendedMediumObject extends MediumObject {
tracks: ExtendedTrackObject[];
}
export class TrackObject extends ImageObject {
provider: ProviderNamespace;
id: string | null;
name: string;
url: ExternalUrlData | null;
trackArtists: PartialArtistObject[];
artistNames: string[];
albumName: string | null;
releaseDate: string | null;
trackNumber: number | null;
/**
* Track duration in ms
*/
duration: number | null;
isrcs: string[];
type: "track";
};
export class ExtendedTrackObject extends TrackObject {
comment?: string | null;
externalUrls?: string[] | null;
extraInfo?: {[key: string]: any};
}
export class PagingData {
count: number | null;
current: string | number | null;
next: string | null;
max?: number | null;
}
export class RawAlbumData extends PagingData {
albums: any[];
};
export class AlbumData extends PagingData {
albums: AlbumObject[];
};
export class ExtendedAlbumData extends PagingData {
albums: ExtendedAlbumObject[];
}
export class UrlMBIDDict {
[key: string]: string | undefined;
}
export type IdMBIDDict = UrlMBIDDict;// aggregated-types.ts
export type AlbumIssue = 'noUPC' | 'UPCDiff' | 'missingISRCs' | 'ISRCDiff' | 'trackDiff' | 'noDate' | 'dateDiff' | 'noCover';
export type AlbumStatus = 'green' | 'orange' | 'blue' | 'red';
export type TrackStatus = 'green' | 'orange' | 'blue' | 'grey'; // Track count miss-match doesn't aggregate, so tracks would never be not found
export type TrackIssue = 'noISRC' | 'ISRCDiff' | 'noDuration' | "artistDiff"
/**
* source - Selected source provider
*
* provider - Supplimental provider data (For pulling links and filling data gaps)
*
* target - Target provider existing data (Usually MusicBrainz)
*/
export type AlbumMatchType = 'source' | 'provider' | 'target'
export class AggregatedArtist extends ArtistObject {
mbid?: string | null;
}
export class AggregatedLabel extends LabelObject {
mbid?: string | null;
}
export class AlbumStack {
status: AlbumStatus;
albumIssues: AlbumIssue[];
albums: AlbumMatch[];
aggregated: AggregatedAlbum;
// sourceAlbum: AlbumObject;
// targetAlbum?: AlbumObject;
// providerAlbums?: AlbumObject[]
}
export class AlbumMatch {
type: AlbumMatchType;
album: AlbumObject
}
export class AggregatedAlbum extends AlbumObject{
mediums: AggregatedMedium[];
sourceArtist: PartialArtistObject | null;
}
export class AggregatedMedium extends MediumObject {
override tracks: AggregatedTrack[]
}
export class AggregatedTrack extends TrackObject {
// sourceArtist: PartialArtistObject | null;
status: TrackStatus;
trackIssues: TrackIssue[];
}
export class BasicTrack {
name: string;
isrcs: string[];
}
export class AggregatedData {
albumData: AlbumStack[]
statusText: string
green: number
orange: number
blue: number
red: number
total: number
}
export class RawAggregateData {
sourceAlbums: AlbumObject[]
targetAlbums: ExtendedAlbumObject[]
targetFeaturedAlbums: ExtendedAlbumObject[]
}New Contributors
Full Changelog: v2.3.12...v2.4.0