import Bottleneck from 'bottleneck' ; import { Headers } from 'cross-fetch' ; import OAuth from 'oauth-1.0a' ; import { CurrenciesEnum , EditOrderStatusesEnum , FolderIdsEnum , InventorySortEnum , InventoryStatusesEnum , ListingStatusesEnum , OrderSortEnum , OrderStatusesEnum , ReleaseConditionsEnum , ReleaseSortEnum , SearchTypeEnum , SleeveConditionsEnum , UserSortEnum , } from './constants' ; import { addQueryToUri , createLimiter , fetch , HTTPVerbsEnum , paginate , Pagination , sortBy , SortOptions , transformData , } from './utils' ; import { AddToFolderResponse , AddToWantlistResponse , Artist , ArtistReleasesResponse , CollectionValueResponse , CommunityReleaseRatingResponse , CreateListingResponse , CustomFieldsResponse , EditUserProfileResponse , EmptyResponse , Fee , Folder , FolderReleasesResponse , FoldersResponse , IdentityResponse , InventoryResponse , Label , LabelReleasesResponse , Listing , MarketplaceStatisticsResponse , Master , MasterVersionsResponse , Order , OrderMessage , OrderMessagesResponse , OrdersResponse , PriceSuggestionsResponse , Release , ReleaseRatingResponse , SearchResponse , UserContributionsResponse , UserListItemsResponse , UserListsResponse , UserProfileResponse , UserSubmissionsResponse , WantlistResponse , } from '../models' ; internal const API_BASE_URL = 'https://api.discogs.com' ; internal const IMG_BASE_URL = 'https://img.discogs.com' ; internal const API_VERSION = 'v2' ; internal const DEFAULT_USER_AGENT = `Discojs/__packageVersion__` ; type UserTokenAuth = { /** User token. */ userToken : string ; } ; type ConsumerKeyAuth = { /** Consumer key. */ consumerKey : string ; /** Consumer secret. */ consumerSecret : string ; /** OAuth token. */ oAuthToken : string ; /** OAuth token secret. */ oAuthTokenSecret : string ; } ; type LimiterOptions = { /** Number of requests per interval for unauthenticated requests. Defaults to 25. */ requestLimit ? : number ; /** Number of requests per interval for authenticated requests. Defaults to 60. */ requestLimitAuth ? : number ; /** Interval to use to throttle requests. Defaults to 60 seconds. */ requestLimitInterval ? : number ; } ; todo enum OutputFormatsEnum { DISCOGS = 'discogs' } interface DiscojsOptions extends Partial < UserTokenAuth > , Partial < ConsumerKeyAuth > , LimiterOptions { default userAgent ? : string ; default outputFormat ? : OutputFormatsEnum ; fetchOptions ? : RequestInit ; } type SearchOptions = { query ? : string ; type ? : SearchTypeEnum ; title ? : string ; releaseTitle ? : string ; credit ? : string ; artist ? : string ; anv ? : string ; label ? : string ; genre ? : string ; style ? : string ; country ? : string ; year ? : string | number ; format ? : string ; catno ? : string ; barcode ? : string ; track ? : string ; submitter ? : string ; contributor ? : string ; } ; type ProfileOptions = { username ? : string ; name ? : string ; homePage ? : string ; location ? : string ; profile ? : string ; currAbbr ? : CurrenciesEnum ; } ; type ListingOptions = { releaseId : number ; condition : ReleaseConditionsEnum ; sleeveCondition ? : SleeveConditionsEnum ; price : number ; comments ? : string ; allowOffers ? : boolean ; status : ListingStatusesEnum ; externalId ? : string ; location ? : string ; weight ? : 'auto' | number ; formatQuantity ? : 'auto' | number ; } ; type RatingValues = 0 | 1 | 2 | 3 | 4 | 5 ; internal function isAuthenticatedWithToken ( options ? ) { return Boolean ( options ) && typeof options ! . userToken === 'string' ; } internal function isAuthenticatedWithConsumerKey ( options ? ) { return ( Boolean ( options ) && typeof options ! . consumerKey === 'string' && typeof options ! . consumerSecret === 'string' && typeof options ! . oAuthToken === 'string' && typeof options ! . oAuthTokenSecret === 'string' ) ; } internal function isAuthenticated ( options ? ) { return isAuthenticatedWithToken ( options ) || isAuthenticatedWithConsumerKey ( options ) ; } /**
 * Discojs.
 */ export class Discojs { public userAgent : string ; public outputFormat : OutputFormatsEnum ; private limiter : Bottleneck ; private fetchHeaders : Headers ; private setAuthorizationHeader ? : ( url ? : string , method ? : HTTPVerbsEnum ) => string ; private fetchOptions : RequestInit ; constructor ( options ? ) { const { userAgent = DEFAULT_USER_AGENT , outputFormat = OutputFormatsEnum . DISCOGS , requestLimit = 25 , requestLimitAuth = 60 , requestLimitInterval = 60 * 1000 , fetchOptions = { } , } = options || { } ; this . userAgent = userAgent ; this . outputFormat = outputFormat ; this . limiter = createLimiter ( { maxRequests : isAuthenticated ( options ) ? requestLimitAuth : requestLimit , requestLimitInterval , } ) ; this . fetchOptions = fetchOptions ; this . fetchHeaders = new Headers ( { Accept : `application/vnd.discogs.${ API_VERSION }.${ this . outputFormat }+json` , 'Accept-Encoding' : 'gzip,deflate' , Connection : 'close' , 'Content-Type' : 'application/json' , 'User-Agent' : this . userAgent , } ) ; if ( isAuthenticatedWithToken ( options ) ) this . setAuthorizationHeader = ( ) => `Discogs token=${ options . userToken }` ; if ( isAuthenticatedWithConsumerKey ( options ) ) { const oAuth = new OAuth ( { consumer : { key : options . consumerKey , secret : options . consumerSecret } , signature_method : 'PLAINTEXT' , version : '1.0' , } ) ; this . setAuthorizationHeader = ( url ? , method ? ) => { if ( ! url || ! method ) return '' ; const authObject = oAuth . authorize ( { url , method } , { key : options . oAuthToken , secret : options . oAuthTokenSecret } ) ; return oAuth . toHeader ( authObject ) . Authorization ; } ; } } category static static getSupportedCurrencies ( ) { return Object . values ( CurrenciesEnum ) ; } category static static getReleaseConditions ( ) { return Object . values ( ReleaseConditionsEnum ) ; } category static static getSleeveConditions ( ) { return Object . values ( SleeveConditionsEnum ) ; } private internal private async fetch < T > ( uri , query ? , method ? , data ? ) { const isImgEndpoint = uri . startsWith ( IMG_BASE_URL ) ; const endpoint = isImgEndpoint ? uri : API_BASE_URL + ( query && typeof query === 'object' ? addQueryToUri ( uri , query ) : uri ) ; const options = { ... this . fetchOptions , method : method || HTTPVerbsEnum . GET , } ; if ( this . setAuthorizationHeader ) this . fetchHeaders . set ( 'Authorization' , this . setAuthorizationHeader ( uri , method || HTTPVerbsEnum . GET ) ) ; const clonedHeaders = new Map ( this . fetchHeaders ) ; if ( data ) { const stringifiedData = JSON . stringify ( transformData ( data ) ) ; options . body = stringifiedData ; clonedHeaders . set ( 'Content-Type' , 'application/json' ) ; clonedHeaders . set ( 'Content-Length' , Buffer . byteLength ( stringifiedData , 'utf8' ) . toString ( ) ) ; } options . headers = Object . fromEntries ( clonedHeaders ) ; return this . limiter . schedule ( ( ) => fetch < T > ( endpoint , options , isImgEndpoint ) ) ; } remarks category link async getIdentity ( ) { return this . fetch < IdentityResponse > ( '/oauth/identity' ) ; } remarks category private private async getUsername ( ) { const { username } = await this . getIdentity ( ) ; return username ; } remarks category link async getProfileForUser ( username ) { return this . fetch < UserProfileResponse > ( `/users/${ username }` ) ; } category link async getProfile ( ) { const username = await this . getUsername ( ) ; return this . getProfileForUser ( username ) ; } category link async editProfile ( options ) { const username = await this . getUsername ( ) ; return this . fetch < EditUserProfileResponse > ( `/users/${ username }` , { } , HTTPVerbsEnum . POST , { username , ... options } ) ; } category link async getSubmissionsForUser ( username , pagination ? ) { return this . fetch < UserSubmissionsResponse > ( `/users/${ username }/submissions` , paginate ( pagination ) ) ; } category link async getSubmissions ( pagination ? ) { const username = await this . getUsername ( ) ; return this . getSubmissionsForUser ( username , pagination ) ; } category link async getContributionsForUser ( username , sort ? , pagination ? ) { return this . fetch < UserContributionsResponse > ( `/users/${ username }/contributions` , { ... sortBy ( UserSortEnum . ADDED , sort ) , ... paginate ( pagination ) , } ) ; } category link async getContributions ( sort ? , pagination ? ) { const username = await this . getUsername ( ) ; return this . getContributionsForUser ( username , sort , pagination ) ; } remarks category link async listFoldersForUser ( username ) { return this . fetch < FoldersResponse > ( `/users/${ username }/collection/folders` ) ; } category link async listFolders ( ) { const username = await this . getUsername ( ) ; return this . listFoldersForUser ( username ) ; } category link async createFolder ( name ) { const username = await this . getUsername ( ) ; return this . fetch < Folder > ( `/users/${ username }/collection/folders` , { } , HTTPVerbsEnum . POST , { name } ) ; } remarks category link async getFolderForUser ( username , folderId ) { return this . fetch < Folder > ( `/users/${ username }/collection/folders/${ folderId }` ) ; } category link async getFolder ( folderId ) { const username = await this . getUsername ( ) ; return this . getFolderForUser ( username , folderId ) ; } remarks category link async editFolder ( folderId , name ) { const username = await this . getUsername ( ) ; return this . fetch < Folder > ( `/users/${ username }/collection/folders/${ folderId }` , { } , HTTPVerbsEnum . POST , { name , } ) ; } remarks category link async deleteFolder ( folderId ) { const username = await this . getUsername ( ) ; return this . fetch < EmptyResponse > ( `/users/${ username }/collection/folders/${ folderId }` , { } , HTTPVerbsEnum . DELETE ) ; } remarks category label link async listItemsByReleaseForUser ( username , release_id , pagination ? ) { return this . fetch < FolderReleasesResponse > ( `/users/${ username }/collection/releases/${ release_id }` , paginate ( pagination ) ) ; } category label link async listItemsByRelease ( release_id , pagination ? ) { const username = await this . getUsername ( ) ; return this . listItemsByReleaseForUser ( username , release_id , pagination ) ; } remarks category label link async listItemsInFolderForUser ( username , folderId , sort ? , pagination ? ) { return this . fetch < FolderReleasesResponse > ( `/users/${ username }/collection/folders/${ folderId }/releases` , { ... sortBy ( UserSortEnum . ADDED , sort ) , ... paginate ( pagination ) , } ) ; } remarks category label link async listItemsInFolder ( folderId , sort ? , pagination ? ) { const username = await this . getUsername ( ) ; return this . listItemsInFolderForUser ( username , folderId , sort , pagination ) ; } category label link async addReleaseToFolder ( releaseId , folderId = FolderIdsEnum . UNCATEGORIZED ) { const username = await this . getUsername ( ) ; return this . fetch < AddToFolderResponse > ( `/users/${ username }/collection/folders/${ folderId }/releases/${ releaseId }` , { } , HTTPVerbsEnum . POST ) ; } category label link async editReleaseInstanceRating ( folderId , releaseId , instanceId , rating ) { const username = await this . getUsername ( ) ; return this . fetch < EmptyResponse > ( `/users/${ username }/collection/folders/${ folderId }/releases/${ releaseId }/instances/${ instanceId }` , { } , HTTPVerbsEnum . POST , { rating } ) ; } category label link async moveReleaseInstanceToFolder ( oldFolderId , releaseId , instanceId , newFolderId ) { const username = await this . getUsername ( ) ; return this . fetch < EmptyResponse > ( `/users/${ username }/collection/folders/${ oldFolderId }/releases/${ releaseId }/instances/${ instanceId }` , { } , HTTPVerbsEnum . POST , { folderId : newFolderId } ) ; } category label link async deleteReleaseInstanceFromFolder ( folderId , releaseId , instanceId ) { const username = await this . getUsername ( ) ; return this . fetch < EmptyResponse > ( `/users/${ username }/collection/folders/${ folderId }/releases/${ releaseId }/instances/${ instanceId }` , { } , HTTPVerbsEnum . DELETE ) ; } remarks category label link async listCustomFieldsForUser ( username ) { return this . fetch < CustomFieldsResponse > ( `/users/${ username }/collection/fields` ) ; } category label link async listCustomFields ( ) { const username = await this . getUsername ( ) ; return this . listCustomFieldsForUser ( username ) ; } category label link async editCustomFieldForInstance ( folderId , releaseId , instanceId , fieldId , value ) { const username = await this . getUsername ( ) ; return this . fetch < EmptyResponse > ( `/users/${ username }/collection/folders/${ folderId }/releases/${ releaseId }/instances/${ instanceId }/fields/${ fieldId }` , { value } , HTTPVerbsEnum . POST ) ; } requires category label link async getCollectionValue ( ) { const username = await this . getUsername ( ) ; return this . fetch < CollectionValueResponse > ( `/users/${ username }/collection/value` ) ; } remarks category label link async getWantlistForUser ( username , pagination ? ) { return this . fetch < WantlistResponse > ( `/users/${ username }/wants` , paginate ( pagination ) ) ; } category label link async getWantlist ( pagination ? ) { const username = await this . getUsername ( ) ; return this . getWantlistForUser ( username , pagination ) ; } category label link async addToWantlist ( releaseId , notes ? , rating ? ) { const username = await this . getUsername ( ) ; return this . fetch < AddToWantlistResponse > ( `/users/${ username }/wants/${ releaseId }` , { notes , rating } , HTTPVerbsEnum . PUT ) ; } category label link async removeFromWantlist ( releaseId ) { const username = await this . getUsername ( ) ; return this . fetch < EmptyResponse > ( `/users/${ username }/wants/${ releaseId }` , { } , HTTPVerbsEnum . DELETE ) ; } remarks category label link async getListsForUser ( username , pagination ? ) { return this . fetch < UserListsResponse > ( `/users/${ username }/lists` , paginate ( pagination ) ) ; } category label link async getLists ( pagination ? ) { const username = await this . getUsername ( ) ; return this . getListsForUser ( username , pagination ) ; } remarks category label link async getListItems ( listId ) { return this . fetch < UserListItemsResponse > ( `/lists/${ listId }` ) ; } category label link async searchDatabase ( options = { } , pagination ? ) { return this . fetch < SearchResponse > ( '/database/search' , { ... options , ... paginate ( pagination ) } ) ; } category label link async searchRelease ( query , options = { } , pagination ? ) { return this . searchDatabase ( { ... options , query , type : SearchTypeEnum . RELEASE } , pagination ) ; } category label link async getRelease ( releaseId , currency ? ) { return this . fetch < Release > ( `/releases/${ releaseId }` , { currency } ) ; } category label link async getReleaseRatingForUser ( username , releaseId ) { return this . fetch < ReleaseRatingResponse > ( `/releases/${ releaseId }/rating/${ username }` ) ; } category label link async getReleaseRating ( releaseId ) { const username = await this . getUsername ( ) ; return this . getReleaseRatingForUser ( username , releaseId ) ; } category label link async updateReleaseRating ( releaseId , rating ) { const username = await this . getUsername ( ) ; return this . fetch < ReleaseRatingResponse > ( `/releases/${ releaseId }/rating/${ username }` , { } , HTTPVerbsEnum . PUT , { rating , } ) ; } category label link async deleteReleaseRating ( releaseId ) { const username = await this . getUsername ( ) ; return this . fetch < EmptyResponse > ( `/releases/${ releaseId }/rating/${ username }` , { } , HTTPVerbsEnum . DELETE ) ; } category label link async getCommunityReleaseRating ( releaseId ) { return this . fetch < CommunityReleaseRatingResponse > ( `/releases/${ releaseId }/rating` ) ; } category label link async searchMaster ( query , options = { } , pagination ? ) { return this . searchDatabase ( { ... options , query , type : SearchTypeEnum . MASTER } , pagination ) ; } category label link async getMaster ( masterId ) { return this . fetch < Master > ( `/masters/${ masterId }` ) ; } category label link async getMasterVersions ( masterId , pagination ? ) { return this . fetch < MasterVersionsResponse > ( `/masters/${ masterId }/versions` , paginate ( pagination ) ) ; } category label link async searchArtist ( query , options = { } , pagination ? ) { return this . searchDatabase ( { ... options , query , type : SearchTypeEnum . ARTIST } , pagination ) ; } category label link async getArtist ( artistId ) { return this . fetch < Artist > ( `/artists/${ artistId }` ) ; } category label link async getArtistReleases ( artistId , sort ? , pagination ? ) { return this . fetch < ArtistReleasesResponse > ( `/artists/${ artistId }/releases` , { ... sortBy ( ReleaseSortEnum . YEAR , sort ) , ... paginate ( pagination ) , } ) ; } category label link async searchLabel ( query , options = { } , pagination ? ) { return this . searchDatabase ( { ... options , query , type : SearchTypeEnum . LABEL } , pagination ) ; } category label link async getLabel ( labelId ) { return this . fetch < Label > ( `/labels/${ labelId }` ) ; } category label link async getLabelReleases ( labelId , pagination ? ) { return this . fetch < LabelReleasesResponse > ( `/labels/${ labelId }/releases` , paginate ( pagination ) ) ; } remarks category label link async getInventoryForUser ( username , status = InventoryStatusesEnum . ALL , sort ? , pagination ? ) { return this . fetch < InventoryResponse > ( `/users/${ username }/inventory` , { status , ... sortBy ( InventorySortEnum . LISTED , sort ) , ... paginate ( pagination ) , } ) ; } category label link async getInventory ( status ? , sort ? , pagination ? ) { const username = await this . getUsername ( ) ; return this . getInventoryForUser ( username , status , sort , pagination ) ; } remarks category label link async getListing ( listingId , currency ? ) { return this . fetch < Listing > ( `/marketplace/listings/${ listingId }` , { currency } ) ; } remarks category label link async editListing ( listingId , options , currency ? ) { return this . fetch < EmptyResponse > ( `/marketplace/listings/${ listingId }` , { currency } , HTTPVerbsEnum . POST , options ) ; } category label link async deleteListing ( listingId ) { return this . fetch < EmptyResponse > ( `/marketplace/listings/${ listingId }` , { } , HTTPVerbsEnum . DELETE ) ; } category label link async createListing ( options ) { return this . fetch < CreateListingResponse > ( '/marketplace/listings/' , { } , HTTPVerbsEnum . POST , options ) ; } category label link async getOrder ( orderId ) { return this . fetch < Order > ( `/marketplace/orders/${ orderId }` ) ; } category label link async editOrder ( orderId , status ? , shipping ? ) { return this . fetch < Order > ( `/marketplace/orders/${ orderId }` , { } , HTTPVerbsEnum . POST , { status , shipping } ) ; } category label link async listOrders ( status ? , archived ? , sort ? , pagination ? ) { return this . fetch < OrdersResponse > ( '/marketplace/orders' , { status , archived , ... sortBy ( OrderSortEnum . ID , sort ) , ... paginate ( pagination ) , } ) ; } category label link async listOrderMessages ( orderId ) { return this . fetch < OrderMessagesResponse > ( `/marketplace/orders/${ orderId }/messages` ) ; } remarks category label link async sendOrderMessage ( orderId , message ? , status ? ) { return this . fetch < OrderMessage > ( `/marketplace/orders/${ orderId }/messages` , { } , HTTPVerbsEnum . POST , { message , status , } ) ; } category label link async getFee ( price , currency ? ) { let uri = `/marketplace/fee/${ price }` ; if ( currency ) uri += `/${ currency }` ; return this . fetch < Fee > ( uri ) ; } remarks category label link async getPriceSuggestions ( releaseId ) { return this . fetch < PriceSuggestionsResponse > ( `/marketplace/price_suggestions/${ releaseId }` ) ; } remarks category label link async getMarketplaceStatistics ( releaseId , currency ? ) { return this . fetch < MarketplaceStatisticsResponse > ( `/marketplace/stats/${ releaseId }` , { currency } ) ; } requires category link async fetchImage ( imageUrl ) { return this . fetch < Blob > ( imageUrl ) ; } }