diff --git a/.changeset/purple-experts-allow.md b/.changeset/purple-experts-allow.md new file mode 100644 index 00000000000..2421101869f --- /dev/null +++ b/.changeset/purple-experts-allow.md @@ -0,0 +1,5 @@ +--- +"@audius/sdk": patch +--- + +Fix UploadsApi to make start() a function diff --git a/docs/docs/developers/sdk/advanced-options.mdx b/docs/docs/developers/sdk/advanced-options.mdx deleted file mode 100644 index 35df2f47ceb..00000000000 --- a/docs/docs/developers/sdk/advanced-options.mdx +++ /dev/null @@ -1,43 +0,0 @@ ---- -id: advanced-options -title: Advanced Options -pagination_label: Advanced Options -sidebar_label: Advanced Options -description: Audius Protocol Documentation ---- - -import Tabs from '@theme/Tabs' -import TabItem from '@theme/TabItem' - -# AdvancedOptions - -You can pass a `AdvancedOptions` object to any SDK write method for further control of its behavior. - -## Examples - -```ts -await audiusSdk.playlists.favoritePlaylist({ - playlistId: 'x5pJ3Az' - userId: "7eP5n", -}, { - confirmationTimeout: 30000 // Time out if the transaction takes over 30 seconds to confirm -}); -``` - -```ts -await audiusSdk.playlists.favoritePlaylist({ - playlistId: 'x5pJ3Az' - userId: "7eP5n", -}, { - skipConfirmation: true // Don't wait to confirm the transaction; resolve immediately after it is sent. Only for advanced usage. -}); -``` - -## Properties - -`Optional` **confirmationTimeout**: number - The amount of time in milliseconds to wait for the -transaction to be confirmed on the blockchain before timing out. Defaults to 45000. - -`Optional` **skipConfirmation**: boolean - Whether to resolve immediately after the transaction is -sent, instead of waiting for the transaction to be confirmed before resolving. Only for advanced -usage. Defaults to false. diff --git a/docs/docs/developers/sdk/tracks.mdx b/docs/docs/developers/sdk/tracks.mdx index 61acddade79..994c844a9d4 100644 --- a/docs/docs/developers/sdk/tracks.mdx +++ b/docs/docs/developers/sdk/tracks.mdx @@ -31,9 +31,10 @@ Get a track by id. Create an object with the following fields and pass it as the first argument, as shown in the example above. -| Name | Type | Description | Required? | -| :-------- | :------- | :------------------ | :----------- | -| `trackId` | `string` | The ID of the track | **Required** | +| Name | Type | Description | Required? | +| :-------- | :------- | :------------------------------------ | :----------- | +| `trackId` | `string` | The ID of the track | **Required** | +| `userId` | `string` | The ID of the user making the request | _Optional_ | @@ -137,10 +138,12 @@ Get a list of tracks using their IDs or permalinks. Create an object with the following fields and pass it as the first argument, as shown in the example above. -| Name | Type | Description | Required? | -| :---------- | :--------- | :------------------------------- | :--------- | -| `id` | `string[]` | An array of IDs of tracks | _Optional_ | -| `permalink` | `string[]` | An array of permalinks of tracks | _Optional_ | +| Name | Type | Description | Required? | +| :---------- | :--------- | :---------------------------------------- | :--------- | +| `id` | `string[]` | An array of IDs of tracks | _Optional_ | +| `permalink` | `string[]` | An array of permalinks of tracks | _Optional_ | +| `userId` | `string` | The ID of the user making the request | _Optional_ | +| `isrc` | `string[]` | An array of ISRC codes to query tracks by | _Optional_ | @@ -242,10 +245,13 @@ Get the top 100 trending (most popular) tracks on Audius. Optionally create an object with the following fields and pass it as the first argument. -| Name | Type | Description | Required? | -| :------ | :--------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------- | :--------- | -| `genre` | `Genre` (can be imported from `@audius/sdk`) | If provided, the top 100 trending tracks of the genre will be returned | _Optional_ | -| `time` | `GetTrendingTracksTimeEnum` (can be imported from `@audius/sdk`) | A time range for which to return the trending tracks. Default value is **GetTrendingTracksTimeEnum.AllTime** | _Optional_ | +| Name | Type | Description | Required? | +| :------- | :----------------------------------------- | :--------------------------------------------------------------------------------- | :--------- | +| `genre` | `string` | If provided, the top 100 trending tracks of the genre will be returned | _Optional_ | +| `time` | `'week' \| 'month' \| 'year' \| 'allTime'` | A time range for which to return the trending tracks. Default value is `'allTime'` | _Optional_ | +| `offset` | `number` | The number of items to skip (for pagination) | _Optional_ | +| `limit` | `number` | The number of items to fetch (for pagination) | _Optional_ | +| `userId` | `string` | The ID of the user making the request | _Optional_ | @@ -347,6 +353,7 @@ Optionally create an object with the following fields and pass it as the first a | :------- | :------- | :--------------------------------------------------------------------------- | :--------- | | `limit` | `number` | If provided, will return only the given number of tracks. Default is **100** | _Optional_ | | `offset` | `number` | An offset to apply to the list of results. Default value is **0** | _Optional_ | +| `userId` | `string` | The ID of the user making the request | _Optional_ | @@ -447,9 +454,21 @@ Search for tracks. Create an object with the following fields and pass it as the first argument, as shown in the example above. -| Name | Type | Description | Required? | -| :------ | :------- | :---------------------- | :----------- | -| `query` | `string` | The query to search for | **Required** | +| Name | Type | Description | Required? | +| :-------------------- | :--------- | :------------------------------------------------------- | :--------- | +| `query` | `string` | The query to search for | _Optional_ | +| `offset` | `number` | The number of items to skip (for pagination) | _Optional_ | +| `limit` | `number` | The number of items to fetch (for pagination) | _Optional_ | +| `genre` | `string[]` | Array of genres to filter by | _Optional_ | +| `sortMethod` | `string` | Sort method: `'relevant'`, `'popular'`, or `'recent'` | _Optional_ | +| `mood` | `string[]` | Array of moods to filter by | _Optional_ | +| `onlyDownloadable` | `string` | Filter to only downloadable tracks | _Optional_ | +| `includePurchaseable` | `string` | Include purchaseable tracks in results | _Optional_ | +| `isPurchaseable` | `string` | Filter to only purchaseable tracks | _Optional_ | +| `hasDownloads` | `string` | Filter tracks that have stems/downloads | _Optional_ | +| `key` | `string[]` | Array of musical keys to filter by (e.g., `['C', 'Am']`) | _Optional_ | +| `bpmMin` | `string` | Minimum BPM to filter by | _Optional_ | +| `bpmMax` | `string` | Maximum BPM to filter by | _Optional_ | @@ -532,56 +551,22 @@ information about the tracks as described below. --- -## getTrackStreamUrl +## createTrack -> #### getTrackStreamUrl(`params`) +> #### createTrack(`params`, `requestInit?`) -Get the url of the track's streamable mp3 file. +Create a track by registering its metadata on the protocol. This method accepts metadata including +CIDs (Content Identifiers) that reference previously uploaded files — it does **not** accept raw +audio or image files directly. - - +:::info Uploading files -Create an object with the following fields and pass it as the first argument, as shown in the -example above. +To upload audio and image files, use the [Uploads API](/developers/sdk/uploads). Upload your files +first to obtain CIDs, then pass those CIDs in the `metadata` object here. See the +[full upload + create example](/developers/sdk/uploads#full-example-upload-and-create-a-track) for a +complete walkthrough. -| Name | Type | Description | Required? | -| :-------- | :------- | :------------------ | :----------- | -| `trackId` | `string` | The ID of the track | **Required** | - - - - -```ts -const url = await audiusSdk.tracks.getTrackStreamUrl({ - trackId: 'PjdWN', -}) -const audio = new Audio(url) -audio.play() -``` - - - - -Returns a `Promise` containing a `string` url which can be used to stream the track. - - - - ---- - -## uploadTrack - -> #### uploadTrack(`params`, `advancedOptions?`) - -Upload a track. +::: void` | A function that will be called with progress events as the files upload | _Optional_ | -| `audioFile` | `File` | The audio file of the track | **Required** | -| `userId` | `string` | The ID of the user | **Required** | +| Name | Type | Description | Required? | +| :--------- | :-------------------------------------------------- | :-------------------------------------------- | :----------- | +| `userId` | `string` | The ID of the user | **Required** | +| `metadata` | [`CreateTrackRequestBody`](#createtrackrequestbody) | An object containing the details of the track | **Required** | -> #### `advancedOptions` +See [`CreateTrackRequestBody`](#createtrackrequestbody) for all available metadata fields. -You can pass an optional [`advancedOptions`](/developers/sdk/advanced-options) object as the second -argument. +> #### `requestInit` + +You can pass an optional +[`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second +argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). @@ -617,24 +602,39 @@ argument. import { Mood, Genre } from '@audius/sdk' import fs from 'fs' -const coverArtBuffer = fs.readFileSync('path/to/cover-art.png') +// First, upload files using the Uploads API const trackBuffer = fs.readFileSync('path/to/track.mp3') +const audioUpload = audiusSdk.uploads.createAudioUpload({ + file: { + buffer: Buffer.from(trackBuffer), + name: 'track.mp3', + type: 'audio/mpeg', + }, +}) +const audioResult = await audioUpload.start() -const { trackId } = await audiusSdk.tracks.uploadTrack({ - userId: '7eP5n', - imageFile: { +const coverArtBuffer = fs.readFileSync('path/to/cover-art.png') +const imageUpload = audiusSdk.uploads.createImageUpload({ + file: { buffer: Buffer.from(coverArtBuffer), - name: 'coverArt', + name: 'cover-art.png', + type: 'image/png', }, +}) +const coverArtCid = await imageUpload.start() + +// Then, create the track with the returned CIDs. +// The audio upload result fields (trackCid, origFileCid, etc.) match +// the metadata field names, so you can spread them directly. +const { data } = await audiusSdk.tracks.createTrack({ + userId: '7eP5n', metadata: { title: 'Monstera', description: 'Dedicated to my favorite plant', genre: Genre.METAL, mood: Mood.AGGRESSIVE, - }, - audioFile: { - buffer: Buffer.from(trackBuffer), - name: 'monsteraAudio', + ...audioResult, + coverArtCid: coverArtCid, }, }) ``` @@ -660,10 +660,11 @@ hash (`blockHash`) and block number (`blockNumber`) for the transaction. ## updateTrack -> #### updateTrack(`params`, `advancedOptions?`) +> #### updateTrack(`params`, `requestInit?`) -Update a track. If cover art or any metadata fields are not provided, their values will be kept the -same as before. +Update a track's metadata. If any metadata fields are not provided, their values will be kept the +same as before. To replace audio or cover art, upload new files via the +[Uploads API](/developers/sdk/uploads) and pass the new CIDs in `metadata`. ` | An object containing the details of the track | **Required** | -| `generatePreview` | `boolean` | Whether to regenerate the track preview (used when preview start time changes) | _Optional_ | -| `onProgress` | `(progress: number, event: `[`UploadTrackProgressEvent`](/developers/sdk/progress-events#uploadtrackprogressevent)`) => void` | A function that will be called with progress events as files upload | _Optional_ | +| Name | Type | Description | Required? | +| :--------- | :-------------------------------------------------- | :---------------------------------------- | :----------- | +| `trackId` | `string` | The ID of the track | **Required** | +| `userId` | `string` | The ID of the user | **Required** | +| `metadata` | [`UpdateTrackRequestBody`](#updatetrackrequestbody) | An object containing the fields to update | **Required** | -> #### `advancedOptions` +All fields are optional — only include the fields you want to change. See +[`UpdateTrackRequestBody`](#updatetrackrequestbody) for all available fields. -You can pass an optional [`advancedOptions`](/developers/sdk/advanced-options) object as the second -argument. +> #### `requestInit` + +You can pass an optional +[`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second +argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ```ts -import fs from 'fs' import { Mood } from '@audius/sdk' -const coverArtBuffer = fs.readFileSync('path/to/updated-cover-art.png') - -const { trackId } = await audiusSdk.tracks.updateTrack({ +const { data } = await audiusSdk.tracks.updateTrack({ trackId: 'h5pJ3Bz', - imageFile: { - buffer: Buffer.from(coverArtBuffer), - name: 'coverArt', - }, + userId: '7eP5n', metadata: { - description: 'Dedicated to my favorite plant... new cover art!', + description: 'Dedicated to my favorite plant... updated!', mood: Mood.YEARNING, }, - onProgress: (progress) => { - console.log('Progress: ', progress / 100) - }, - userId: '7eP5n', }) ``` @@ -740,7 +731,7 @@ Returns a `Promise` containing an object with the block hash (`blockHash`) and b ## deleteTrack -> #### deleteTrack(`params`, `advancedOptions?`) +> #### deleteTrack(`params`, `requestInit?`) Delete a track @@ -763,10 +754,11 @@ example above. | `trackId` | `string` | The ID of the track | **Required** | | `userId` | `string` | The ID of the user | **Required** | -> #### `advancedOptions` +> #### `requestInit` -You can pass an optional [`advancedOptions`](/developers/sdk/advanced-options) object as the second -argument. +You can pass an optional +[`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second +argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). @@ -798,7 +790,7 @@ Returns a `Promise` containing an object with the block hash (`blockHash`) and b ## favoriteTrack -> #### favoriteTrack(`params`, `advancedOptions?`) +> #### favoriteTrack(`params`, `requestInit?`) Favorite a track @@ -828,10 +820,11 @@ example above. } ``` -> #### `advancedOptions` +> #### `requestInit` -You can pass an optional [`advancedOptions`](/developers/sdk/advanced-options) object as the second -argument. +You can pass an optional +[`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second +argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). @@ -863,7 +856,7 @@ Returns a `Promise` containing an object with the block hash (`blockHash`) and b ## unfavoriteTrack -> #### unfavoriteTrack(`params`, `advancedOptions?`) +> #### unfavoriteTrack(`params`, `requestInit?`) Unfavorite a track @@ -886,10 +879,11 @@ example above. | `trackId` | `string` | The ID of the track | **Required** | | `userId` | `string` | The ID of the user | **Required** | -> #### `advancedOptions` +> #### `requestInit` -You can pass an optional [`advancedOptions`](/developers/sdk/advanced-options) object as the second -argument. +You can pass an optional +[`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second +argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). @@ -921,7 +915,7 @@ Returns a `Promise` containing an object with the block hash (`blockHash`) and b ## repostTrack -> #### repostTrack(`params`, `advancedOptions?`) +> #### repostTrack(`params`, `requestInit?`) Repost a track @@ -951,10 +945,11 @@ example above. } ``` -> #### `advancedOptions` +> #### `requestInit` -You can pass an optional [`advancedOptions`](/developers/sdk/advanced-options) object as the second -argument. +You can pass an optional +[`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second +argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). @@ -988,7 +983,7 @@ Return type: ## unrepostTrack -> #### unrepostTrack(`params`, `advancedOptions?`) +> #### unrepostTrack(`params`, `requestInit?`) Unrepost a track @@ -1012,10 +1007,11 @@ example above. | `trackId` | `string` | The ID of the track | **Required** | | `userId` | `string` | The ID of the user | **Required** | -> #### `advancedOptions` +> #### `requestInit` -You can pass an optional [`advancedOptions`](/developers/sdk/advanced-options) object as the second -argument. +You can pass an optional +[`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second +argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). @@ -1047,3 +1043,260 @@ Returns a `Promise` containing an object with the block hash (`blockHash`) and b --- + +## Type Reference + +### CreateTrackRequestBody + +Metadata object for creating a new track. Fields marked **Required** must be provided. + +| Name | Type | Description | Required? | +| :----------------------------- | :------------------------------------------------------ | :------------------------------------------------ | :----------- | +| `title` | `string` | Track title | **Required** | +| `genre` | `Genre` | Track genre | **Required** | +| `trackCid` | `string` | CID of the transcoded audio (from Uploads API) | **Required** | +| `trackId` | `string` | Track ID (auto-generated if not provided) | _Optional_ | +| `description` | `string` | Track description | _Optional_ | +| `mood` | `Mood` | Track mood | _Optional_ | +| `bpm` | `number` | Beats per minute | _Optional_ | +| `musicalKey` | `string` | Musical key of the track | _Optional_ | +| `tags` | `string` | Comma-separated tags | _Optional_ | +| `license` | `string` | License type | _Optional_ | +| `isrc` | `string` | International Standard Recording Code | _Optional_ | +| `iswc` | `string` | International Standard Musical Work Code | _Optional_ | +| `releaseDate` | `Date` | Release date | _Optional_ | +| `origFileCid` | `string` | CID of the original audio file (from Uploads API) | _Optional_ | +| `origFilename` | `string` | Original filename of the audio file | _Optional_ | +| `coverArtCid` | `string` | CID of the cover art image (from Uploads API) | _Optional_ | +| `coverArtSizes` | `string` | Cover art sizes metadata | _Optional_ | +| `previewCid` | `string` | CID of the preview clip (from Uploads API) | _Optional_ | +| `previewStartSeconds` | `number` | Preview start time in seconds | _Optional_ | +| `duration` | `number` | Track duration in seconds | _Optional_ | +| `isDownloadable` | `boolean` | Whether the track is downloadable | _Optional_ | +| `isUnlisted` | `boolean` | Whether the track is unlisted (hidden) | _Optional_ | +| `isStreamGated` | `boolean` | Whether streaming is behind an access gate | _Optional_ | +| `streamConditions` | [`AccessGate`](#accessgate) | Conditions for stream access gating | _Optional_ | +| `downloadConditions` | [`AccessGate`](#accessgate) | Conditions for download access gating | _Optional_ | +| `fieldVisibility` | [`FieldVisibility`](#fieldvisibility) | Controls visibility of individual track fields | _Optional_ | +| `placementHosts` | `string` | Preferred storage node placement hosts | _Optional_ | +| `stemOf` | [`StemParent`](#stemparent) | Parent track if this track is a stem | _Optional_ | +| `remixOf` | [`RemixParentWrite`](#remixparentwrite) | Parent track(s) if this track is a remix | _Optional_ | +| `noAiUse` | `boolean` | Whether AI use is prohibited | _Optional_ | +| `isOwnedByUser` | `boolean` | Whether the track is owned by the user | _Optional_ | +| `coverOriginalSongTitle` | `string` | Original song title (for cover tracks) | _Optional_ | +| `coverOriginalArtist` | `string` | Original artist (for cover tracks) | _Optional_ | +| `parentalWarningType` | `string` | Parental warning type | _Optional_ | +| `territoryCodes` | `string[]` | Territory codes for distribution | _Optional_ | +| `ddexApp` | `string` | DDEX application identifier | _Optional_ | +| `ddexReleaseIds` | `object` | DDEX release identifiers | _Optional_ | +| `artists` | `object[]` | DDEX resource contributors / artists | _Optional_ | +| `resourceContributors` | [`DdexResourceContributor[]`](#ddexresourcecontributor) | DDEX resource contributors | _Optional_ | +| `indirectResourceContributors` | [`DdexResourceContributor[]`](#ddexresourcecontributor) | DDEX indirect resource contributors | _Optional_ | +| `rightsController` | [`DdexRightsController`](#ddexrightscontroller) | DDEX rights controller | _Optional_ | +| `copyrightLine` | `CopyrightLine` | Copyright line | _Optional_ | +| `producerCopyrightLine` | `ProducerCopyrightLine` | Producer copyright line | _Optional_ | + +--- + +### UpdateTrackRequestBody + +Metadata object for updating an existing track. All fields are optional — only include the fields +you want to change. Omitted fields retain their current values. + +| Name | Type | Description | +| :-------------------- | :-------------------------------------- | :------------------------------------------------ | +| `title` | `string` | Track title | +| `genre` | `Genre` | Track genre | +| `description` | `string` | Track description | +| `mood` | `Mood` | Track mood | +| `bpm` | `number` | Beats per minute | +| `musicalKey` | `string` | Musical key of the track | +| `tags` | `string` | Comma-separated tags | +| `license` | `string` | License type | +| `isrc` | `string` | International Standard Recording Code | +| `iswc` | `string` | International Standard Musical Work Code | +| `releaseDate` | `Date` | Release date | +| `trackCid` | `string` | CID of the transcoded audio (from Uploads API) | +| `origFileCid` | `string` | CID of the original audio file (from Uploads API) | +| `origFilename` | `string` | Original filename of the audio file | +| `coverArtCid` | `string` | CID of the cover art image (from Uploads API) | +| `coverArtSizes` | `string` | Cover art sizes metadata | +| `previewCid` | `string` | CID of the preview clip (from Uploads API) | +| `previewStartSeconds` | `number` | Preview start time in seconds | +| `duration` | `number` | Track duration in seconds | +| `isDownloadable` | `boolean` | Whether the track is downloadable | +| `isUnlisted` | `boolean` | Whether the track is unlisted (hidden) | +| `isStreamGated` | `boolean` | Whether streaming is behind an access gate | +| `streamConditions` | [`AccessGate`](#accessgate) | Conditions for stream access gating | +| `downloadConditions` | [`AccessGate`](#accessgate) | Conditions for download access gating | +| `fieldVisibility` | [`FieldVisibility`](#fieldvisibility) | Controls visibility of individual track fields | +| `placementHosts` | `string` | Preferred storage node placement hosts | +| `stemOf` | [`StemParent`](#stemparent) | Parent track if this track is a stem | +| `remixOf` | [`RemixParentWrite`](#remixparentwrite) | Parent track(s) if this track is a remix | +| `parentalWarningType` | `string` | Parental warning type | +| `ddexApp` | `string` | DDEX application identifier | + +--- + +### AccessGate + +`AccessGate` is a union type representing the conditions required to access a gated track. Used by +both `streamConditions` and `downloadConditions`. Provide **one** of the following: + +#### FollowGate + +Require the listener to follow a specific user. + +```ts +{ + followUserId: number // The user ID that must be followed +} +``` + +#### TipGate + +Require the listener to have tipped a specific user. + +```ts +{ + tipUserId: number // The user ID that must be tipped +} +``` + +#### PurchaseGate + +Require the listener to purchase access with USDC. + +```ts +{ + usdcPurchase: { + price: number // Price in cents + splits: Array<{ + userId: number // User ID of the payment recipient + percentage: number // Percentage of the price (0-100) + }> + } +} +``` + +#### TokenGate + +Require the listener to hold a specific SPL token. + +```ts +{ + tokenGate: { + tokenMint: string // The mint address of the SPL token + tokenAmount: number // The minimum amount of the token required + } +} +``` + +**Example — USDC purchase-gated track:** + +```ts +const metadata = { + title: 'Premium Track', + genre: Genre.ELECTRONIC, + trackCid: '...', + isStreamGated: true, + streamConditions: { + usdcPurchase: { + price: 199, // $1.99 in cents + splits: [{ userId: 12345, percentage: 100 }], + }, + }, +} +``` + +--- + +### FieldVisibility + +Controls which track fields are publicly visible. Each field is a `boolean`. + +```ts +{ + mood: boolean + tags: boolean + genre: boolean + share: boolean + playCount: boolean + remixes: boolean +} +``` + +:::note + +For public tracks, `genre`, `mood`, `tags`, `share`, and `playCount` are set to `true` by default. +For stream-gated (non-USDC) tracks, `remixes` is set to `false` by default. + +::: + +--- + +### RemixParentWrite + +Identifies the parent track(s) that a remix is derived from. + +```ts +{ + tracks: Array<{ + parentTrackId: string // The ID of the parent track + }> +} +``` + +**Example:** + +```ts +const metadata = { + title: 'My Remix', + genre: Genre.ELECTRONIC, + trackCid: '...', + remixOf: { + tracks: [{ parentTrackId: 'D7KyD' }], + }, +} +``` + +--- + +### StemParent + +Identifies the parent track when uploading a stem (e.g. vocals, drums, bass). + +```ts +{ + category: string // Stem category (e.g. 'VOCAL', 'DRUMS', 'BASS', 'INSTRUMENTAL', 'OTHER') + parentTrackId: number // The numeric ID of the parent track +} +``` + +--- + +### DdexResourceContributor + +Represents a contributor to the track (used for DDEX metadata). + +```ts +{ + name: string // Contributor name + roles: string[] // Contributor roles (e.g. 'Composer', 'Lyricist') + sequenceNumber?: number // Optional ordering index +} +``` + +--- + +### DdexRightsController + +Represents the rights controller for the track (used for DDEX metadata). + +```ts +{ + name: string // Rights controller name + roles: string[] // Roles (e.g. 'RightsController') + rightsShareUnknown?: string // Optional rights share info +} +``` diff --git a/docs/docs/developers/sdk/uploads.mdx b/docs/docs/developers/sdk/uploads.mdx new file mode 100644 index 00000000000..7d54c7035c1 --- /dev/null +++ b/docs/docs/developers/sdk/uploads.mdx @@ -0,0 +1,214 @@ +--- +id: uploads +title: Uploads +pagination_label: Uploads +sidebar_label: Uploads +description: Audius Protocol Documentation +--- + +import Tabs from '@theme/Tabs' +import TabItem from '@theme/TabItem' + +--- + +The Uploads API provides methods for uploading audio and image files to Audius storage nodes. These +methods return CIDs (Content Identifiers) that you then pass to write methods like +[`createTrack`](/developers/sdk/tracks#createtrack) or +[`updateTrack`](/developers/sdk/tracks#updatetrack). + +:::tip + +File uploads are a separate step from track/playlist creation. You first upload your files using the +Uploads API to get CIDs, then pass those CIDs as metadata when creating or updating content. + +::: + +--- + +## createAudioUpload + +> #### createAudioUpload(`params`) + +Upload an audio file to a storage node. Returns the resulting CIDs and audio analysis metadata +(duration, BPM, musical key). + + + + +Create an object with the following fields and pass it as the first argument. + +| Name | Type | Description | Required? | +| :-------------------- | :---------------------------------------- | :--------------------------------------------------- | :----------- | +| `file` | `File` | The audio file to upload | **Required** | +| `onProgress` | `(loaded: number, total: number) => void` | A callback for tracking upload progress | _Optional_ | +| `previewStartSeconds` | `number` | Start time in seconds for generating a preview clip | _Optional_ | +| `placementHosts` | `string[]` | A list of storage node hosts to prefer for placement | _Optional_ | + + + + +```ts +import fs from 'fs' + +const trackBuffer = fs.readFileSync('path/to/track.mp3') + +const upload = audiusSdk.uploads.createAudioUpload({ + file: { + buffer: Buffer.from(trackBuffer), + name: 'track.mp3', + type: 'audio/mpeg', + }, + onProgress: (loaded, total) => { + console.log(`Upload progress: ${Math.round((loaded / total) * 100)}%`) + }, + previewStartSeconds: 30, +}) + +const result = await upload.start() +console.log(result) +``` + + + + +The method returns an object with: + +- `abort` — a function you can call to cancel the upload. +- `start()` — a function that begins the upload and returns a `Promise` that resolves with the + upload result once complete. + +The resolved value of `start()` contains: + +```ts +{ + trackCid: string // CID of the transcoded 320kbps audio + previewCid?: string // CID of the preview clip (if previewStartSeconds was provided) + origFileCid: string // CID of the original uploaded file + origFilename: string // Original filename + duration: number // Duration in seconds + bpm?: number // Detected BPM (beats per minute) + musicalKey?: string // Detected musical key +} +``` + + + + +--- + +## createImageUpload + +> #### createImageUpload(`params`) + +Upload an image file (e.g. cover art or profile picture) to a storage node. Returns the resulting +CID. + + + + +Create an object with the following fields and pass it as the first argument. + +| Name | Type | Description | Required? | +| :--------------- | :---------------------------------------- | :---------------------------------------------------------------------------- | :----------- | +| `file` | `File` | The image file to upload | **Required** | +| `onProgress` | `(loaded: number, total: number) => void` | A callback for tracking upload progress | _Optional_ | +| `isBackdrop` | `boolean` | Set to `true` for wide/banner images (e.g. profile banners). Default: `false` | _Optional_ | +| `placementHosts` | `string[]` | A list of storage node hosts to prefer for placement | _Optional_ | + + + + +```ts +import fs from 'fs' + +const coverArtBuffer = fs.readFileSync('path/to/cover-art.png') + +const upload = audiusSdk.uploads.createImageUpload({ + file: { + buffer: Buffer.from(coverArtBuffer), + name: 'cover-art.png', + type: 'image/png', + }, +}) + +const coverArtCid = await upload.start() +console.log(coverArtCid) // CID string +``` + + + + +The method returns an object with: + +- `abort` — a function you can call to cancel the upload. +- `start()` — a function that begins the upload and returns a `Promise` resolving with the CID of + the uploaded image (`string`). + + + + +--- + +## Full Example: Upload and Create a Track + +This example demonstrates the full workflow of uploading files via the Uploads API and then creating +a track with the returned CIDs. + +```ts +import { Mood, Genre } from '@audius/sdk' +import fs from 'fs' + +// Step 1: Upload the audio file +const trackBuffer = fs.readFileSync('path/to/track.mp3') +const audioUpload = audiusSdk.uploads.createAudioUpload({ + file: { + buffer: Buffer.from(trackBuffer), + name: 'track.mp3', + type: 'audio/mpeg', + }, + previewStartSeconds: 30, +}) +const audioResult = await audioUpload.start() + +// Step 2: Upload cover art +const coverArtBuffer = fs.readFileSync('path/to/cover-art.png') +const imageUpload = audiusSdk.uploads.createImageUpload({ + file: { + buffer: Buffer.from(coverArtBuffer), + name: 'cover-art.png', + type: 'image/png', + }, +}) +const coverArtCid = await imageUpload.start() + +// Step 3: Create the track using the CIDs from the uploads. +// The audio upload result fields (trackCid, previewCid, origFileCid, etc.) +// match the metadata field names, so you can spread them directly. +const { data } = await audiusSdk.tracks.createTrack({ + userId: '7eP5n', + metadata: { + title: 'Monstera', + description: 'Dedicated to my favorite plant', + genre: Genre.METAL, + mood: Mood.AGGRESSIVE, + ...audioResult, + coverArtCid: coverArtCid, + }, +}) +``` diff --git a/docs/docs/developers/sdk/users.mdx b/docs/docs/developers/sdk/users.mdx index 660c52f47ea..dbbed38f803 100644 --- a/docs/docs/developers/sdk/users.mdx +++ b/docs/docs/developers/sdk/users.mdx @@ -78,7 +78,7 @@ user as described below. ### getBulkUsers -#### getBulkUser(`params`) +#### getBulkUsers(`params`) Gets a list of users. @@ -97,9 +97,10 @@ console.log(users) Create an object with the following fields and pass it as the first argument, as shown in the example above. -| Name | Type | Description | Required? | -| :--- | :--------- | :------------------- | :----------- | -| `id` | `string[]` | The IDs of the users | **Required** | +| Name | Type | Description | Required? | +| :------- | :--------- | :------------------------------------ | :--------- | +| `id` | `string[]` | The IDs of the users | _Optional_ | +| `userId` | `string` | The ID of the user making the request | _Optional_ | #### Returns @@ -226,47 +227,6 @@ information about the wallets as described below. --- -### getDeveloperApps - -#### getDeveloperApps(`params`) - -Get the developer apps a user owns. - -Example: - -```ts -const { data: developerApps } = await audiusSdk.users.getDeveloperApps({ - id: 'eAZl3', -}) - -console.log(developerApps) -``` - -#### Params - -Create an object with the following fields and pass it as the first argument, as shown in the -example above. - -| Name | Type | Description | Required? | -| :--- | :------- | :----------------- | :----------- | -| `id` | `string` | The ID of the user | **Required** | - -#### Returns - -Returns a `Promise` containing an object with a `data` field. `data` is an array of items containing -information about the developer apps as described below. - -```ts -{ - address: string; - description?: string; - name: string; - userId: string; -}[]; -``` - ---- - ### getFavorites #### getFavorites(`params`) @@ -335,6 +295,7 @@ example above. | `id` | `string` | The ID of the user | **Required** | | `limit` | `number` | The maximum number of followers to return. Default value is **10** | _Optional_ | | `offset` | `number` | The offset to apply to the list of results. Default value is **0** | _Optional_ | +| `userId` | `string` | The ID of the user making the request | _Optional_ | #### Returns @@ -404,6 +365,7 @@ example above. | `id` | `string` | The ID of the user | **Required** | | `limit` | `number` | The maximum number of users to return. Default value is **10** | _Optional_ | | `offset` | `number` | The offset to apply to the list of results. Default value is **0** | _Optional_ | +| `userId` | `string` | The ID of the user making the request | _Optional_ | #### Returns @@ -432,11 +394,13 @@ console.log(relatedUsers) Create an object with the following fields and pass it as the first argument, as shown in the example above. -| Name | Type | Description | Required? | -| :------- | :------- | :----------------------------------------------------------------- | :----------- | -| `id` | `string` | The ID of the user | **Required** | -| `limit` | `number` | The maximum number of users to return. Default value is **10** | _Optional_ | -| `offset` | `number` | The offset to apply to the list of results. Default value is **0** | _Optional_ | +| Name | Type | Description | Required? | +| :--------------- | :-------- | :----------------------------------------------------------------- | :----------- | +| `id` | `string` | The ID of the user | **Required** | +| `limit` | `number` | The maximum number of users to return. Default value is **10** | _Optional_ | +| `offset` | `number` | The offset to apply to the list of results. Default value is **0** | _Optional_ | +| `userId` | `string` | The ID of the user making the request | _Optional_ | +| `filterFollowed` | `boolean` | Filter to only users that the current user follows | _Optional_ | #### Returns @@ -468,6 +432,7 @@ example above. | `id` | `string` | The ID of the user | **Required** | | `limit` | `number` | The maximum number of reposts to return. Default value is **100** | _Optional_ | | `offset` | `number` | The offset to apply to the list of results. Default value is **0** | _Optional_ | +| `userId` | `string` | The ID of the user making the request | _Optional_ | #### Returns @@ -514,6 +479,7 @@ example above. | `id` | `string` | The ID of the user | **Required** | | `limit` | `number` | The maximum number of users to return. Default value is **10** | _Optional_ | | `offset` | `number` | The offset to apply to the list of results. Default value is **0** | _Optional_ | +| `userId` | `string` | The ID of the user making the request | _Optional_ | #### Returns @@ -547,6 +513,7 @@ example above. | `id` | `string` | The ID of the user | **Required** | | `limit` | `number` | The maximum number of users to return. Default value is **10** | _Optional_ | | `offset` | `number` | The offset to apply to the list of results. Default value is **0** | _Optional_ | +| `userId` | `string` | The ID of the user making the request | _Optional_ | #### Returns @@ -596,20 +563,20 @@ information about the supporters as described below. --- -### getSupportings +### getSupportedUsers -#### getSupportings(`params`) +#### getSupportedUsers(`params`) Get users that a user is supporting (they have sent them a tip). Example: ```ts -const { data: supportings } = await audiusSdk.users.getSupportings({ +const { data: supportedUsers } = await audiusSdk.users.getSupportedUsers({ id: 'eAZl3', }) -console.log(supportings) +console.log(supportedUsers) ``` #### Params @@ -622,11 +589,12 @@ example above. | `id` | `string` | The ID of the user | **Required** | | `limit` | `number` | The maximum number of users to return. Default value is **10** | _Optional_ | | `offset` | `number` | The offset to apply to the list of results. Default value is **0** | _Optional_ | +| `userId` | `string` | The ID of the user making the request | _Optional_ | #### Returns Returns a `Promise` containing an object with a `data` field. `data` is an array of items containing -information about the supportings as described below. +information about the supported users as described below. ```ts { @@ -690,11 +658,11 @@ console.log(tags) Create an object with the following fields and pass it as the first argument, as shown in the example above. -| Name | Type | Description | Required? | -| :------- | :------- | :----------------------------------------------------------------- | :----------- | -| `id` | `string` | The ID of the user | **Required** | -| `limit` | `number` | The maximum number of users to return. Default value is **10** | _Optional_ | -| `offset` | `number` | The offset to apply to the list of results. Default value is **0** | _Optional_ | +| Name | Type | Description | Required? | +| :------- | :------- | :------------------------------------------------------------ | :----------- | +| `id` | `string` | The ID of the user | **Required** | +| `limit` | `number` | The maximum number of tags to return. Default value is **10** | _Optional_ | +| `userId` | `string` | The ID of the user making the request | _Optional_ | #### Returns @@ -765,9 +733,10 @@ console.log(user) Create an object with the following fields and pass it as the first argument, as shown in the example above. -| Name | Type | Description | Required? | -| :------- | :------- | :--------------------- | :----------- | -| `handle` | `string` | The handle of the user | **Required** | +| Name | Type | Description | Required? | +| :------- | :------- | :------------------------------------ | :----------- | +| `handle` | `string` | The handle of the user | **Required** | +| `userId` | `string` | The ID of the user making the request | _Optional_ | #### Returns @@ -775,44 +744,6 @@ The return type is the same as [`getUser`](#getuser) --- -### getUserIdByWallet - -#### getUserIdByWallet(`params`) - -Get a user ID by an associated wallet address. - -Example: - -```ts -const { data: userId } = await audiusSdk.users.getUserIdByWallet({ - associatedWallet: '6f229f7e8462f198e5be9139175a0b460a9fa35b', -}) - -console.log(userId) -``` - -#### Params - -Create an object with the following fields and pass it as the first argument, as shown in the -example above. - -| Name | Type | Description | Required? | -| :----------------- | :------- | :---------------------------------------- | :----------- | -| `associatedWallet` | `string` | A wallet address associated with the user | **Required** | - -#### Returns - -Returns a `Promise` containing an object with a `data` field. `data` is an object containing the -user id as described below. - -```ts -{ - userId: string -} -``` - ---- - ### searchUsers #### searchUsers(`params`) @@ -834,9 +765,14 @@ console.log(users) Create an object with the following fields and pass it as the first argument, as shown in the example above. -| Name | Type | Description | Required? | -| :------ | :------- | :---------------------------- | :----------- | -| `query` | `string` | The query for which to search | **Required** | +| Name | Type | Description | Required? | +| :----------- | :--------- | :---------------------------------------------------- | :--------- | +| `query` | `string` | The query for which to search | _Optional_ | +| `offset` | `number` | The number of items to skip (for pagination) | _Optional_ | +| `limit` | `number` | The number of items to fetch (for pagination) | _Optional_ | +| `genre` | `string[]` | Array of genres to filter by | _Optional_ | +| `sortMethod` | `string` | Sort method: `'relevant'`, `'popular'`, or `'recent'` | _Optional_ | +| `isVerified` | `string` | Filter to only verified users | _Optional_ | #### Returns @@ -844,31 +780,34 @@ The return type is the same as [`getFollowers`](#getfollowers) --- -### updateProfile +### updateUser -#### updateProfile(`params`, `advancedOptions?`) +#### updateUser(`params`, `requestInit?`) -Update a user profile. +Update a user profile. To update a profile picture or cover photo, first upload the image via the +[Uploads API](/developers/sdk/uploads) and pass the resulting CID in `metadata`. Example: ```ts -import { Mood, Genre } from '@audius/sdk' import fs from 'fs' +// Upload a new profile picture const profilePicBuffer = fs.readFileSync('path/to/profile-pic.png') - -await audiusSdk.users.updateProfile({ - userId: '7eP5n', - profilePictureFile: { +const profilePicUpload = audiusSdk.uploads.createImageUpload({ + file: { buffer: Buffer.from(profilePicBuffer), - name: 'profilePic', + name: 'profilePic.png', }, +}) +const profilePicCid = await profilePicUpload.start() + +await audiusSdk.users.updateUser({ + id: '7eP5n', + userId: '7eP5n', metadata: { bio: 'up and coming artist from the Bronx', - }, - onProgress: (progress) => { - console.log(`Uploading: ${Math.round((progress.loaded / progress.total) * 100)}%`) + profilePicture: profilePicCid, }, }) ``` @@ -878,39 +817,41 @@ await audiusSdk.users.updateProfile({ Create an object with the following fields and pass it as the first argument, as shown in the example above. -| Name | Type | Description | Required? | -| :------------------- | :-------------------------------------------------------------------------------- | :---------------------------------------------------------------------------- | :----------- | -| `profilePictureFile` | `File` | A file to be used as the profile picture | _Optional_ | -| `coverArtFile` | `File` | A file to be used as the cover art. This is the header on a profile page | _Optional_ | -| `metadata` | _see code block below_ | An object with details about the user | **Required** | -| `onProgress` | `(progress: { loaded: number, total: number, transcode: number }) => void` | A function that will be called with progress events as the image files upload | _Optional_ | -| `userId` | `string` | The ID of the user | **Required** | - -```json title="updateProfile metadata payload" -{ - name?: string; - handle?: string; - bio?: string; - website?: string; - donation?: string; - location?: string; - profileType?: 'label' | null; - metadataMultihash?: string; - isDeactivated?: boolean; - artistPickTrackId?: string; - allowAiAttribution?: boolean; - twitterHandle?: string; - instagramHandle?: string; - tiktokHandle?: string; - splUsdcPayoutWallet?: string | null; - coinFlairMint?: string | null; -} -``` - -#### `advancedOptions` - -You can pass an optional [`advancedOptions`](/developers/sdk/advanced-options) object as the second -argument. +| Name | Type | Description | Required? | +| :--------- | :---------------------- | :------------------------------------ | :----------- | +| `id` | `string` | The ID of the user | **Required** | +| `userId` | `string` | The ID of the user (same as id) | **Required** | +| `metadata` | `UpdateUserRequestBody` | An object with details about the user | **Required** | + +`UpdateUserRequestBody` — all fields are optional, only include the fields you want to change: + +| Name | Type | Description | +| :-------------------- | :---------------- | :-------------------------------------------- | +| `name` | `string` | Display name | +| `handle` | `string` | User handle (set only if not already set) | +| `bio` | `string` | User bio | +| `location` | `string` | User location | +| `website` | `string` | Website URL | +| `donation` | `string` | Donation link | +| `twitterHandle` | `string` | Twitter handle (without @) | +| `instagramHandle` | `string` | Instagram handle (without @) | +| `tiktokHandle` | `string` | TikTok handle (without @) | +| `profilePicture` | `string` | Profile picture CID or URL (from Uploads API) | +| `profilePictureSizes` | `string` | Profile picture sizes metadata | +| `coverPhoto` | `string` | Cover photo CID or URL (from Uploads API) | +| `coverPhotoSizes` | `string` | Cover photo sizes metadata | +| `profileType` | `'label' \| null` | Set to `'label'` for record label profiles | +| `isDeactivated` | `boolean` | Whether the user is deactivated | +| `artistPickTrackId` | `string` | Track hash ID for artist pick | +| `allowAiAttribution` | `boolean` | Whether to allow AI attribution | +| `splUsdcPayoutWallet` | `string` | Solana USDC payout wallet address | +| `coinFlairMint` | `string` | Coin flair mint address | + +> #### `requestInit` + +You can pass an optional +[`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second +argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). #### Returns @@ -928,7 +869,7 @@ Returns a `Promise` containing an object with the block hash (`blockHash`) and b ### followUser -#### followUser(`params`, `advancedOptions?`) +#### followUser(`params`, `requestInit?`) Follow a user. @@ -951,10 +892,11 @@ example above. | `userId` | `string` | The ID of the user | **Required** | | `followeeUserId` | `string` | The ID of the user to follow | **Required** | -#### `advancedOptions` +> #### `requestInit` -You can pass an optional [`advancedOptions`](/developers/sdk/advanced-options) object as the second -argument. +You can pass an optional +[`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second +argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). #### Returns @@ -972,7 +914,7 @@ Returns a `Promise` containing an object with the block hash (`blockHash`) and b ### unfollowUser -#### unfollowUser(`params`, `advancedOptions?`) +#### unfollowUser(`params`, `requestInit?`) Unfollow a user. @@ -995,10 +937,11 @@ example above. | `userId` | `string` | The ID of the user | **Required** | | `followeeUserId` | `string` | The ID of the user to unfollow | **Required** | -#### `advancedOptions` +> #### `requestInit` -You can pass an optional [`advancedOptions`](/developers/sdk/advanced-options) object as the second -argument. +You can pass an optional +[`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second +argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). #### Returns @@ -1016,7 +959,7 @@ Returns a `Promise` containing an object with the block hash (`blockHash`) and b ### subscribeToUser -#### subscribeToUser(`params`, `advancedOptions?`) +#### subscribeToUser(`params`, `requestInit?`) Subscribe to a user. @@ -1039,10 +982,11 @@ example above. | `userId` | `string` | The ID of the user | **Required** | | `subscribeeUserId` | `string` | The ID of the user to subscribe to | **Required** | -#### `advancedOptions +> #### `requestInit` -You can pass an optional [`advancedOptions`](/developers/sdk/advanced-options) object as the second -argument. +You can pass an optional +[`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second +argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). #### Returns @@ -1060,7 +1004,7 @@ Returns a `Promise` containing an object with the block hash (`blockHash`) and b ### unsubscribeFromUser -#### unsubscribeFromUser(`params`, `advancedOptions?`) +#### unsubscribeFromUser(`params`, `requestInit?`) Unsubscribe from a user. @@ -1083,10 +1027,11 @@ example above. | `userId` | `string` | The ID of the user | **Required** | | `subscribeeUserId` | `string` | The ID of the user to unsubscribe from | **Required** | -#### `advancedOptions` +> #### `requestInit` -You can pass an optional [`advancedOptions`](/developers/sdk/advanced-options) object as the second -argument. +You can pass an optional +[`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second +argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). #### Returns diff --git a/docs/docs/developers/upload-track-metadata.mdx b/docs/docs/developers/upload-track-metadata.mdx deleted file mode 100644 index 335407b5619..00000000000 --- a/docs/docs/developers/upload-track-metadata.mdx +++ /dev/null @@ -1,191 +0,0 @@ ---- -id: upload-track-metadata -title: Upload Track Metadata -pagination_label: Upload Track Metadata -sidebar_label: Upload Track Metadata -description: Audius Protocol Documentation ---- - -# **`interface`** UploadTrackMetadata - -Contains required and optional fields for uploading a track. - -## Properties - -```ts -{ - // Required fields: - title: string; - genre: Genre; // Can import `Genre` enum from @audius/sdk - - - // Optional fields: - description?: string; - mood?: Mood; // Can import `Mood` enum from @audius/sdk - releaseDate?: Date; // Should not be in the future. Defaults to today's date. - tags?: string; // Comma separated list of tags - remixOf?: { tracks: Array<{ parentTrackId: string }> }; // For specifying the track(s) that your track is a remix of - isStreamGated?: boolean; // Whether streaming your track is only available to users who meet certain criteria, which must be specified by `streamConditions`. - streamConditions?: AccessConditions; // See "Specifying Stream Conditions" section below - isDownloadGated?: boolean; // Whether downloading your track is only available to users who meet certain criteria, which must be specified by `downloadConditions`. Note that stream gated tracks are automatically download gated, whereas the reverse is not true. - downloadConditions?: AccessConditions; - isUnlisted?: boolean; // If set to true, only users with a link to your track will be able to listen, and your track will not show up in your profile or in any feed. Defaults to false. - fieldVisibility?: { - mood?: boolean; - tags?: boolean; - genre?: boolean; - share?: boolean; - playCount?: boolean; - remixes?: boolean; - }; // For specifying which fields/features are visible on a hidden track. Only applicable if `isUnlisted` is set to true. All default to true. - isrc?: string; // International Standard Recording Code - iswc?: string // International Standard Musical Word Code - license?: string; // License type, e.g. Attribution-NonCommercial-ShareAlike CC BY-NC-SA -} -``` - -## Example with all fields specified - -```ts -import { Mood, Genre } from '@audius/sdk' -import fs from 'fs' - -const coverArtBuffer = fs.readFileSync('path/to/cover-art.png') -const trackBuffer = fs.readFileSync('path/to/track.mp3') - -const { trackId } = await audiusSdk.tracks.uploadTrack({ - userId: '7eP5n', - imageFile: { - buffer: Buffer.from(coverArtBuffer), - name: 'coverArt', - }, - metadata: { - title: 'Monstera', - genre: Genre.METAL, - - // Optional metadata: - description: 'Dedicated to my favorite plant', - mood: Mood.DEVOTIONAL, - releaseDate: new Date('2022-09-30'), - tags: 'plantlife,love,monstera', - remixOf: { tracks: [{ parentTrackId: 'KVx2xpO' }] }, - isStreamGated: true, - streamConditions: { - tipUserId: '7eP5n', // Require tipping user to unlock track for streaming - }, - isDownloadGated: true, - downloadConditions: { - usdcPurchase: { - // Require usdc purchase to unlock track for download - price: 1, - splits: { - // usdc user bank and usdc amount padded with correct number of decimals - FwtT6g2tmwbgY6gf4NWBhupJBqJjgkaHRzCJpA1YHrL2: 10000, - }, - }, - }, - isUnlisted: true, - fieldVisibility: { - mood: true, - tags: true, - genre: true, - share: false, - playCount: false, - remixes: true, - }, - isrc: 'USAT21812345', - iswc: 'T-123.456.789-0', - license: 'Attribution-NonCommercial-ShareAlike CC BY-NC-SA', - }, - audioFile: { - buffer: Buffer.from(trackBuffer), - name: 'monsteraAudio', - }, -}) -``` - -## Specifying Stream Conditions - -Use the `AccessConditions` field to specify the criteria required to unlock a track. - -### Tip-gated - -Require the listener to tip the artist to unlock the track. - -#### Example - -```ts -const { trackId } = await audiusSdk.tracks.uploadTrack({ - // ... - metadata: { - // ... - isStreamGated: true, - streamConditions: { - tipUserId: '7eP5n', // Require tipping user with user ID "7eP5n" to unlock track - }, - }, -}) -``` - -### Follow-gated - -Require the listener to follow the artist to unlock the track. - -#### Example - -```ts -const { trackId } = await audiusSdk.tracks.uploadTrack({ - // ... - metadata: { - // ... - isStreamGated: true, - streamConditions: { - followUserId: '7eP5n', // Require following user with user ID "7eP5n" to unlock track - }, - }, -}) -``` - -### NFT-gated - -Require the listener to hold an Ethereum or Solana NFT to unlock the track. - -#### Ethereum NFT example - -```ts -const { trackId } = await audiusSdk.tracks.uploadTrack({ - // ... - metadata: { - // ... - isStreamGated: true, - streamConditions: { - chain: 'eth', - address: '0xAbCdEfGhIjKlMnOpQrStUvWxYz', // The Ethereum address of the NFT contract - standard: 'ERC-721', // The standard followed by the NFT - either "ERC-721" or "ERC-1155" - name: 'Example NFT', // The name of the NFT - slug: 'example-nft', // The slug of the NFT collection. E.g. if your collection is located at https://opensea.io/collection/example-nft, the slug is "example-nft". - imageUrl: 'https://www.example.com/nft-image.png', // Optional: URL to the image representing the NFT - externalLink: 'https://www.example.com/nft-details', // Optional: URL to an external resource providing more details about the NFT - }, - }, -}) -``` - -#### Solana NFT example - -```ts -const { trackId } = await audiusSdk.tracks.uploadTrack({ - // ... - metadata: { - // ... - isStreamGated: true, - streamConditions: { - chain: 'sol', - address: 'ABCDEF1234567890', // The address of the NFT on the Solana blockchain - name: 'Example NFT', // The name of the NFT - imageUrl: 'https://www.example.com/nft-image.png', // Optional: URL to the image representing the NFT - externalLink: 'https://www.example.com/nft-details', // Optional: URL to an external resource providing more details about the NFT - }, - }, -}) -``` diff --git a/docs/sidebars.js b/docs/sidebars.js index 281c36bf410..b8d872ac744 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -1,4 +1,4 @@ -const apiSidebar = require('./docs/developers/api/sidebar.generated.js'); +const apiSidebar = require('./docs/developers/api/sidebar.generated.js') module.exports = { learn: [], @@ -27,13 +27,12 @@ module.exports = { items: [ 'developers/sdk/overview', 'developers/sdk/tracks', - 'developers/upload-track-metadata', + 'developers/sdk/uploads', 'developers/sdk/users', 'developers/sdk/playlists', 'developers/sdk/albums', 'developers/sdk/resolve', 'developers/sdk/oauth', - 'developers/sdk/advanced-options', ], collapsed: false, }, diff --git a/packages/sdk/src/sdk/api/uploads/UploadsApi.ts b/packages/sdk/src/sdk/api/uploads/UploadsApi.ts index 3960b4cf5c1..85652f029ee 100644 --- a/packages/sdk/src/sdk/api/uploads/UploadsApi.ts +++ b/packages/sdk/src/sdk/api/uploads/UploadsApi.ts @@ -42,18 +42,19 @@ export class UploadsApi { }) return { abort: upload.abort, - start: upload.start().then((res) => ({ - trackCid: res.results['320'], - previewCid: - previewStartSeconds !== undefined && previewStartSeconds !== null - ? res.results[`320_preview|${previewStartSeconds}`] - : undefined, - origFileCid: res.orig_file_cid, - origFilename: res.orig_filename, - duration: parseInt(res?.probe?.format?.duration ?? '0', 10), - bpm: res.audio_analysis_results?.bpm, - musicalKey: res.audio_analysis_results?.key - })) + start: () => + upload.start().then((res) => ({ + trackCid: res.results['320'], + previewCid: + previewStartSeconds !== undefined && previewStartSeconds !== null + ? res.results[`320_preview|${previewStartSeconds}`] + : undefined, + origFileCid: res.orig_file_cid, + origFilename: res.orig_filename, + duration: parseInt(res?.probe?.format?.duration ?? '0', 10), + bpm: res.audio_analysis_results?.bpm, + musicalKey: res.audio_analysis_results?.key + })) } } @@ -88,7 +89,7 @@ export class UploadsApi { }) return { abort: upload.abort, - start: upload.start().then((res) => res.orig_file_cid) + start: () => upload.start().then((res) => res.orig_file_cid) } } } diff --git a/packages/sdk/src/sdk/createSdkWithServices.ts b/packages/sdk/src/sdk/createSdkWithServices.ts index efba71c9fb6..4a5e8febac2 100644 --- a/packages/sdk/src/sdk/createSdkWithServices.ts +++ b/packages/sdk/src/sdk/createSdkWithServices.ts @@ -31,7 +31,6 @@ import { addAppInfoMiddleware, addRequestSignatureMiddleware } from './middleware' -import { addBearerTokenMiddleware } from './middleware/addBearerTokenMiddleware' import { OAuth } from './oauth' import { PaymentRouterClient, @@ -461,19 +460,11 @@ const initializeApis = ({ }) ] - if ('bearerToken' in config) { - middleware.push( - addBearerTokenMiddleware({ - bearerToken: config.bearerToken, - logger: services.logger - }) - ) - } - const apiClientConfig = new Configuration({ fetchApi: fetch, middleware, - basePath + basePath, + accessToken: 'bearerToken' in config ? config.bearerToken : undefined }) const tracks = new TracksApi(apiClientConfig, services) diff --git a/packages/sdk/src/sdk/createSdkWithoutServices.ts b/packages/sdk/src/sdk/createSdkWithoutServices.ts index 9e01fa07989..57325ea8dd3 100644 --- a/packages/sdk/src/sdk/createSdkWithoutServices.ts +++ b/packages/sdk/src/sdk/createSdkWithoutServices.ts @@ -28,7 +28,6 @@ import { addAppInfoMiddleware, addRequestSignatureMiddleware } from './middleware' -import { addBearerTokenMiddleware } from './middleware/addBearerTokenMiddleware' import { OAuth } from './oauth' import { Logger, Storage, StorageNodeSelector } from './services' import { type SdkConfig } from './types' @@ -55,10 +54,6 @@ export const createSdkWithoutServices = (config: SdkConfig) => { const middleware: Middleware[] = [] - if (bearerToken) { - middleware.push(addBearerTokenMiddleware({ bearerToken, logger })) - } - if (apiSecret || services?.audiusWalletClient) { middleware.push( addRequestSignatureMiddleware({ @@ -89,7 +84,8 @@ export const createSdkWithoutServices = (config: SdkConfig) => { const apiConfig = new Configuration({ fetchApi: fetch, middleware, - basePath + basePath, + accessToken: bearerToken }) // Initialize OAuth diff --git a/packages/sdk/src/sdk/middleware/addBearerTokenMiddleware.ts b/packages/sdk/src/sdk/middleware/addBearerTokenMiddleware.ts deleted file mode 100644 index 38e434527fe..00000000000 --- a/packages/sdk/src/sdk/middleware/addBearerTokenMiddleware.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { - FetchParams, - Middleware, - RequestContext -} from '../api/generated/default' -import { Logger, type LoggerService } from '../services' - -export const addBearerTokenMiddleware = (services: { - bearerToken: string - logger?: LoggerService -}): Middleware => { - const bearerToken = services.bearerToken - const logger = services.logger ?? new Logger() - return { - pre: async (context: RequestContext): Promise => { - const existingHeaders = context.init.headers as Record - if (existingHeaders.Authorization) { - logger.warn( - 'Request already has an Authorization header. Skipping adding bearer token.' - ) - return context - } - return { - ...context, - url: context.url, - init: { - ...context.init, - headers: { - ...context.init.headers, - Authorization: `Bearer ${bearerToken}` - } - } - } - } - } -}