| import { SpotifyMyProfile, SpotifyPlaylistContents, SpotifyPlaylistMetadata } from "./types"; | ||
| export declare class SpotiflyBase { | ||
| protected token: string; | ||
| protected tokenExpirationTimestampMs: number; | ||
| protected cookie: string; | ||
| private myProfileId; | ||
| constructor(cookie?: string); | ||
| protected refreshToken(): Promise<void>; | ||
| protected fetch<T>(url: string, optionalHeaders?: { | ||
| [index: string]: string; | ||
| }): Promise<T>; | ||
| protected post<T>(url: string, body: string): Promise<T>; | ||
| protected getPlaylistMetadata(id: string, limit?: number): Promise<SpotifyPlaylistMetadata>; | ||
| protected getPlaylistContents(id: string, limit?: number): Promise<SpotifyPlaylistContents>; | ||
| protected getMyProfile(): Promise<SpotifyMyProfile>; | ||
| protected getMyProfileId(): Promise<string>; | ||
| } |
+50
| export class SpotiflyBase { | ||
| token = ""; | ||
| tokenExpirationTimestampMs = -1; | ||
| cookie; | ||
| myProfileId = ""; | ||
| constructor(cookie) { | ||
| this.cookie = cookie ?? ""; | ||
| } | ||
| async refreshToken() { | ||
| if (this.tokenExpirationTimestampMs > Date.now()) | ||
| return; | ||
| const response = await (await fetch("https://open.spotify.com/get_access_token", { | ||
| headers: { cookie: this.cookie } | ||
| })).json(); | ||
| this.token = "Bearer " + response.accessToken; | ||
| this.tokenExpirationTimestampMs = response.accessTokenExpirationTimestampMs; | ||
| } | ||
| async fetch(url, optionalHeaders) { | ||
| await this.refreshToken(); | ||
| return (await fetch(url, { | ||
| headers: { authorization: this.token, ...optionalHeaders } | ||
| })).json(); | ||
| } | ||
| async post(url, body) { | ||
| await this.refreshToken(); | ||
| return (await fetch(url, { | ||
| headers: { | ||
| authorization: this.token, | ||
| accept: "application/json", | ||
| "content-type": "application/json" | ||
| }, | ||
| method: "POST", | ||
| body: body | ||
| })).json(); | ||
| } | ||
| async getPlaylistMetadata(id, limit = 50) { | ||
| return this.fetch(`https://api-partner.spotify.com/pathfinder/v1/query?operationName=fetchPlaylistMetadata&variables=%7B%22uri%22%3A%22spotify%3Aplaylist%3A${id}%22%2C%22offset%22%3A0%2C%22limit%22%3A${limit}%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%226f7fef1ef9760ba77aeb68d8153d458eeec2dce3430cef02b5f094a8ef9a465d%22%7D%7D`); | ||
| } | ||
| async getPlaylistContents(id, limit = 50) { | ||
| return this.fetch(`https://api-partner.spotify.com/pathfinder/v1/query?operationName=fetchPlaylistContents&variables=%7B%22uri%22%3A%22spotify%3Aplaylist%3A${id}%22%2C%22offset%22%3A0%2C%22limit%22%3A${limit}%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22c56c706a062f82052d87fdaeeb300a258d2d54153222ef360682a0ee625284d9%22%7D%7D`); | ||
| } | ||
| async getMyProfile() { | ||
| if (!this.cookie) | ||
| throw Error("no cookie provided"); | ||
| return this.fetch("https://api.spotify.com/v1/me"); | ||
| } | ||
| async getMyProfileId() { | ||
| return this.myProfileId === "" ? this.myProfileId = (await this.getMyProfile()).id : this.myProfileId; | ||
| } | ||
| } |
| import { SpotiflyBase } from "./base.js"; | ||
| export declare class SpotiflyPlaylist extends SpotiflyBase { | ||
| id: string; | ||
| constructor(cookie: string); | ||
| create(name: string): Promise<{ | ||
| uri: string; | ||
| revision: string; | ||
| }>; | ||
| rename(newName: string): Promise<unknown>; | ||
| changeDescription(newDescription: string): Promise<unknown>; | ||
| fetchMetadata(limit?: number): Promise<{ | ||
| __typename: string; | ||
| uri: string; | ||
| name: string; | ||
| description: string; | ||
| ownerV2: { | ||
| data: { | ||
| __typename: string; | ||
| uri: string; | ||
| username: string; | ||
| name: string; | ||
| avatar: { | ||
| sources: { | ||
| url: string; | ||
| width: number; | ||
| height: number; | ||
| }[]; | ||
| }; | ||
| }; | ||
| }; | ||
| images: { | ||
| items: { | ||
| extractedColors: { | ||
| colorRaw: { | ||
| hex: string; | ||
| isFallback: boolean; | ||
| }; | ||
| }; | ||
| sources: { | ||
| url: string; | ||
| width: any; | ||
| height: any; | ||
| }[]; | ||
| }[]; | ||
| }; | ||
| collaborative: boolean; | ||
| followers: number; | ||
| format: string; | ||
| attributes: any[]; | ||
| sharingInfo: { | ||
| shareUrl: string; | ||
| }; | ||
| content: { | ||
| __typename: string; | ||
| totalCount: number; | ||
| pagingInfo: { | ||
| limit: number; | ||
| }; | ||
| items: { | ||
| itemV2: { | ||
| __typename: string; | ||
| data: { | ||
| __typename: string; | ||
| uri: string; | ||
| trackDuration: { | ||
| totalMilliseconds: number; | ||
| }; | ||
| }; | ||
| }; | ||
| }[]; | ||
| }; | ||
| }>; | ||
| fetchContents(limit?: number): Promise<{ | ||
| uid: string; | ||
| addedAt: { | ||
| isoString: string; | ||
| }; | ||
| addedBy: { | ||
| data: { | ||
| __typename: string; | ||
| uri: string; | ||
| username: string; | ||
| name: string; | ||
| avatar: { | ||
| sources: { | ||
| url: string; | ||
| width: number; | ||
| height: number; | ||
| }[]; | ||
| }; | ||
| }; | ||
| }; | ||
| attributes: any[]; | ||
| itemV2: { | ||
| __typename: string; | ||
| data: { | ||
| __typename: string; | ||
| uri: string; | ||
| name: string; | ||
| trackDuration: { | ||
| totalMilliseconds: number; | ||
| }; | ||
| playcount: string; | ||
| albumOfTrack: { | ||
| uri: string; | ||
| name: string; | ||
| artists: { | ||
| items: { | ||
| uri: string; | ||
| profile: { | ||
| name: string; | ||
| }; | ||
| }[]; | ||
| }; | ||
| coverArt: { | ||
| sources: { | ||
| url: string; | ||
| width: number; | ||
| height: number; | ||
| }[]; | ||
| }; | ||
| }; | ||
| artists: { | ||
| items: { | ||
| uri: string; | ||
| profile: { | ||
| name: string; | ||
| }; | ||
| }[]; | ||
| }; | ||
| discNumber: number; | ||
| trackNumber: number; | ||
| playability: { | ||
| playable: boolean; | ||
| reason: string; | ||
| }; | ||
| contentRating: { | ||
| label: string; | ||
| }; | ||
| }; | ||
| }; | ||
| }[]>; | ||
| add(...trackUris: string[]): Promise<unknown>; | ||
| remove(...trackUris: string[]): Promise<unknown>; | ||
| cloneFrom(id: string, config?: { | ||
| name?: string; | ||
| description?: string; | ||
| limit?: number; | ||
| }): Promise<void>; | ||
| delete(): Promise<unknown>; | ||
| } |
| import { SpotiflyBase } from "./base.js"; | ||
| import { Parse } from "./parse.js"; | ||
| export class SpotiflyPlaylist extends SpotiflyBase { | ||
| id = ""; | ||
| constructor(cookie) { | ||
| super(cookie); | ||
| } | ||
| async create(name) { | ||
| const [myProfileId, newPlaylist] = await Promise.all([ | ||
| this.getMyProfileId(), | ||
| this.post("https://spclient.wg.spotify.com/playlist/v2/playlist", `{"ops":[{"kind":6,"updateListAttributes":{"newAttributes":{"values":{"name":"${name}","formatAttributes":[],"pictureSize":[]},"noValue":[]}}}]}`) | ||
| ]); | ||
| await this.post(`https://spclient.wg.spotify.com/playlist/v2/user/${myProfileId}/rootlist/changes`, `{"deltas":[{"ops":[{"kind":2,"add":{"items":[{"uri":"${newPlaylist.uri}","attributes":{"timestamp":"${Date.now()}","formatAttributes":[],"availableSignals":[]}}],"addFirst":true}}],"info":{"source":{"client":5}}}],"wantResultingRevisions":false,"wantSyncResult":false,"nonces":[]}`); | ||
| this.id = Parse.uriToId(newPlaylist.uri); | ||
| return newPlaylist; | ||
| } | ||
| async rename(newName) { | ||
| return this.post(`https://spclient.wg.spotify.com/playlist/v2/playlist/${this.id}/changes`, `{"deltas":[{"ops":[{"kind":6,"updateListAttributes":{"newAttributes":{"values":{"name":"${newName}","formatAttributes":[],"pictureSize":[]},"noValue":[]}}}],"info":{"source":{"client":5}}}],"wantResultingRevisions":false,"wantSyncResult":false,"nonces":[]}`); | ||
| } | ||
| async changeDescription(newDescription) { | ||
| return this.post(`https://spclient.wg.spotify.com/playlist/v2/playlist/${this.id}/changes`, `{"deltas":[{"ops":[{"kind":6,"updateListAttributes":{"newAttributes":{"values":{"description":"${newDescription}","formatAttributes":[],"pictureSize":[]},"noValue":[]}}}],"info":{"source":{"client":5}}}],"wantResultingRevisions":false,"wantSyncResult":false,"nonces":[]}`); | ||
| } | ||
| async fetchMetadata(limit = 50) { | ||
| return (await this.getPlaylistMetadata(this.id, limit)).data.playlistV2; | ||
| } | ||
| async fetchContents(limit = 50) { | ||
| return (await this.getPlaylistContents(this.id, limit)).data.playlistV2.content.items; | ||
| } | ||
| async add(...trackUris) { | ||
| return this.post("https://api-partner.spotify.com/pathfinder/v1/query", `{"variables":{"uris":${JSON.stringify(trackUris)},"playlistUri":"spotify:playlist:${this.id}","newPosition":{"moveType":"BOTTOM_OF_PLAYLIST","fromUid":null}},"operationName":"addToPlaylist","extensions":{"persistedQuery":{"version":1,"sha256Hash":"200b7618afd05364c4aafb95e2070249ed87ee3f08fc4d2f1d5d04fdf1a516d9"}}}`); | ||
| } | ||
| async remove(...trackUris) { | ||
| const contents = await this.fetchContents(); | ||
| const uids = []; | ||
| contents.forEach(x => { if (trackUris.includes(x.itemV2.data.uri)) | ||
| uids.push(x.uid); }); | ||
| return this.post("https://api-partner.spotify.com/pathfinder/v1/query", `{"variables":{"playlistUri":"spotify:playlist:${this.id}","uids":${JSON.stringify(uids)}},"operationName":"removeFromPlaylist","extensions":{"persistedQuery":{"version":1,"sha256Hash":"c0202852f3743f013eb453bfa15637c9da2d52a437c528960f4d10a15f6dfb49"}}}`); | ||
| } | ||
| async cloneFrom(id, config) { | ||
| const metadata = await this.getPlaylistMetadata(id, config?.limit ?? 50); | ||
| await this.create(config?.name ?? metadata.data.playlistV2.name); | ||
| this.changeDescription(config?.description ?? metadata.data.playlistV2.description); | ||
| this.add(...metadata.data.playlistV2.content.items.map(x => x.itemV2.data.uri)); | ||
| } | ||
| async delete() { | ||
| const myProfileId = await this.getMyProfileId(); | ||
| const response = await this.post(`https://spclient.wg.spotify.com/playlist/v2/user/${myProfileId}/rootlist/changes`, `{"deltas":[{"ops":[{"kind":3,"rem":{"items":[{"uri":"spotify:playlist:${this.id}"}],"itemsAsKey":true}}],"info":{"source":{"client":5}}}],"wantResultingRevisions":false,"wantSyncResult":false,"nonces":[]}`); | ||
| this.id = ""; | ||
| return response; | ||
| } | ||
| } |
| export type SpotifyColorLyrics = { | ||
| lyrics: { | ||
| syncType: string; | ||
| lines: Array<{ | ||
| startTimeMs: string; | ||
| words: string; | ||
| syllables: Array<any>; | ||
| endTimeMs: string; | ||
| }>; | ||
| provider: string; | ||
| providerLyricsId: string; | ||
| providerDisplayName: string; | ||
| syncLyricsUri: string; | ||
| isDenseTypeface: boolean; | ||
| alternatives: Array<any>; | ||
| language: string; | ||
| isRtlLanguage: boolean; | ||
| fullscreenAction: string; | ||
| }; | ||
| colors: { | ||
| background: number; | ||
| text: number; | ||
| highlightText: number; | ||
| }; | ||
| hasVocalRemoval: boolean; | ||
| }; |
| export {}; |
| export type SpotifyLikedSongs = { | ||
| data: { | ||
| me: { | ||
| library: { | ||
| tracks: { | ||
| __typename: string; | ||
| pagingInfo: { | ||
| offset: number; | ||
| limit: number; | ||
| }; | ||
| items: Array<{ | ||
| __typename: string; | ||
| addedAt: { | ||
| isoString: string; | ||
| }; | ||
| track: { | ||
| _uri: string; | ||
| data: { | ||
| __typename: string; | ||
| name: string; | ||
| duration: { | ||
| totalMilliseconds: number; | ||
| }; | ||
| albumOfTrack: { | ||
| uri: string; | ||
| name: string; | ||
| artists: { | ||
| items: Array<{ | ||
| uri: string; | ||
| profile: { | ||
| name: string; | ||
| }; | ||
| }>; | ||
| }; | ||
| coverArt: { | ||
| sources: Array<{ | ||
| url: string; | ||
| width: number; | ||
| height: number; | ||
| }>; | ||
| }; | ||
| }; | ||
| artists: { | ||
| items: Array<{ | ||
| uri: string; | ||
| profile: { | ||
| name: string; | ||
| }; | ||
| }>; | ||
| }; | ||
| discNumber: number; | ||
| trackNumber: number; | ||
| contentRating: { | ||
| label: string; | ||
| }; | ||
| playability: { | ||
| playable: boolean; | ||
| }; | ||
| }; | ||
| }; | ||
| }>; | ||
| totalCount: number; | ||
| }; | ||
| }; | ||
| }; | ||
| }; | ||
| extensions: {}; | ||
| }; | ||
| export type SpotifyLikedSongsAdd = { | ||
| data: { | ||
| addLibraryItems: { | ||
| __typename: string; | ||
| }; | ||
| }; | ||
| extensions: {}; | ||
| }; | ||
| export type SpotifyLikedSongsRemove = { | ||
| data: { | ||
| removeLibraryItems: { | ||
| __typename: string; | ||
| }; | ||
| }; | ||
| extensions: {}; | ||
| }; |
| export {}; |
| export type SpotifyMyLibrary = { | ||
| data: { | ||
| me: { | ||
| libraryV2: { | ||
| page: { | ||
| __typename: string; | ||
| availableFilters: Array<{ | ||
| id: string; | ||
| name: string; | ||
| }>; | ||
| selectedFilters: Array<any>; | ||
| availableSortOrders: Array<{ | ||
| id: string; | ||
| name: string; | ||
| }>; | ||
| selectedSortOrder: { | ||
| id: string; | ||
| name: string; | ||
| }; | ||
| breadcrumbs: Array<any>; | ||
| items: Array<{ | ||
| addedAt: { | ||
| isoString: string; | ||
| }; | ||
| pinnable: boolean; | ||
| pinned: boolean; | ||
| depth: number; | ||
| playedAt?: { | ||
| isoString: string; | ||
| }; | ||
| item: { | ||
| __typename: string; | ||
| _uri: string; | ||
| data: { | ||
| __typename: string; | ||
| uri: string; | ||
| name?: string; | ||
| count?: number; | ||
| image?: { | ||
| extractedColors: { | ||
| colorDark: { | ||
| hex: string; | ||
| isFallback: boolean; | ||
| }; | ||
| }; | ||
| sources: Array<{ | ||
| url: string; | ||
| width: number; | ||
| height: number; | ||
| }>; | ||
| }; | ||
| collaborative?: boolean; | ||
| ownerV2?: { | ||
| data: { | ||
| __typename: string; | ||
| id: string; | ||
| name: string; | ||
| uri: string; | ||
| avatar: { | ||
| sources: Array<{ | ||
| url: string; | ||
| height: number; | ||
| width: number; | ||
| }>; | ||
| }; | ||
| username: string; | ||
| }; | ||
| }; | ||
| images?: { | ||
| items: Array<{ | ||
| extractedColors: { | ||
| colorDark: { | ||
| hex: string; | ||
| isFallback: boolean; | ||
| }; | ||
| }; | ||
| sources: Array<{ | ||
| url: string; | ||
| width: any; | ||
| height: any; | ||
| }>; | ||
| }>; | ||
| }; | ||
| profile?: { | ||
| name: string; | ||
| }; | ||
| visuals?: { | ||
| avatarImage: { | ||
| extractedColors: { | ||
| colorDark: { | ||
| hex: string; | ||
| isFallback: boolean; | ||
| }; | ||
| }; | ||
| sources: Array<{ | ||
| url: string; | ||
| width: number; | ||
| height: number; | ||
| }>; | ||
| }; | ||
| }; | ||
| }; | ||
| }; | ||
| }>; | ||
| pagingInfo: { | ||
| offset: number; | ||
| limit: number; | ||
| }; | ||
| totalCount: number; | ||
| }; | ||
| }; | ||
| }; | ||
| }; | ||
| extensions: { | ||
| cacheControl: { | ||
| version: number; | ||
| hints: Array<{ | ||
| path: Array<string>; | ||
| maxAge: number; | ||
| scope: string; | ||
| }>; | ||
| }; | ||
| }; | ||
| }; |
| export {}; |
| export type SpotifyMyProfile = { | ||
| birthdate: string; | ||
| country: string; | ||
| display_name: string; | ||
| email: string; | ||
| explicit_content: { | ||
| filter_enabled: boolean; | ||
| filter_locked: boolean; | ||
| }; | ||
| external_urls: { | ||
| spotify: string; | ||
| }; | ||
| followers: { | ||
| href: string; | ||
| total: number; | ||
| }; | ||
| href: string; | ||
| id: string; | ||
| images: Array<{ | ||
| height: number; | ||
| url: string; | ||
| width: number; | ||
| }>; | ||
| policies: { | ||
| opt_in_trial_premium_only_market: boolean; | ||
| }; | ||
| product: string; | ||
| type: string; | ||
| uri: string; | ||
| }; |
| export {}; |
| export type SpotifyPlaylistContents = { | ||
| data: { | ||
| playlistV2: { | ||
| __typename: string; | ||
| content: { | ||
| __typename: string; | ||
| totalCount: number; | ||
| pagingInfo: { | ||
| offset: number; | ||
| limit: number; | ||
| }; | ||
| items: Array<{ | ||
| uid: string; | ||
| addedAt: { | ||
| isoString: string; | ||
| }; | ||
| addedBy: { | ||
| data: { | ||
| __typename: string; | ||
| uri: string; | ||
| username: string; | ||
| name: string; | ||
| avatar: { | ||
| sources: Array<{ | ||
| url: string; | ||
| width: number; | ||
| height: number; | ||
| }>; | ||
| }; | ||
| }; | ||
| }; | ||
| attributes: Array<any>; | ||
| itemV2: { | ||
| __typename: string; | ||
| data: { | ||
| __typename: string; | ||
| uri: string; | ||
| name: string; | ||
| trackDuration: { | ||
| totalMilliseconds: number; | ||
| }; | ||
| playcount: string; | ||
| albumOfTrack: { | ||
| uri: string; | ||
| name: string; | ||
| artists: { | ||
| items: Array<{ | ||
| uri: string; | ||
| profile: { | ||
| name: string; | ||
| }; | ||
| }>; | ||
| }; | ||
| coverArt: { | ||
| sources: Array<{ | ||
| url: string; | ||
| width: number; | ||
| height: number; | ||
| }>; | ||
| }; | ||
| }; | ||
| artists: { | ||
| items: Array<{ | ||
| uri: string; | ||
| profile: { | ||
| name: string; | ||
| }; | ||
| }>; | ||
| }; | ||
| discNumber: number; | ||
| trackNumber: number; | ||
| playability: { | ||
| playable: boolean; | ||
| reason: string; | ||
| }; | ||
| contentRating: { | ||
| label: string; | ||
| }; | ||
| }; | ||
| }; | ||
| }>; | ||
| }; | ||
| }; | ||
| }; | ||
| extensions: { | ||
| cacheControl: { | ||
| version: number; | ||
| hints: Array<{ | ||
| path: [string, number, string]; | ||
| maxAge: number; | ||
| scope: string; | ||
| }>; | ||
| }; | ||
| }; | ||
| }; |
| export {}; |
| export type SpotifyPlaylistMetadata = { | ||
| data: { | ||
| playlistV2: { | ||
| __typename: string; | ||
| uri: string; | ||
| name: string; | ||
| description: string; | ||
| ownerV2: { | ||
| data: { | ||
| __typename: string; | ||
| uri: string; | ||
| username: string; | ||
| name: string; | ||
| avatar: { | ||
| sources: Array<{ | ||
| url: string; | ||
| width: number; | ||
| height: number; | ||
| }>; | ||
| }; | ||
| }; | ||
| }; | ||
| images: { | ||
| items: Array<{ | ||
| extractedColors: { | ||
| colorRaw: { | ||
| hex: string; | ||
| isFallback: boolean; | ||
| }; | ||
| }; | ||
| sources: Array<{ | ||
| url: string; | ||
| width: any; | ||
| height: any; | ||
| }>; | ||
| }>; | ||
| }; | ||
| collaborative: boolean; | ||
| followers: number; | ||
| format: string; | ||
| attributes: Array<any>; | ||
| sharingInfo: { | ||
| shareUrl: string; | ||
| }; | ||
| content: { | ||
| __typename: string; | ||
| totalCount: number; | ||
| pagingInfo: { | ||
| limit: number; | ||
| }; | ||
| items: Array<{ | ||
| itemV2: { | ||
| __typename: string; | ||
| data: { | ||
| __typename: string; | ||
| uri: string; | ||
| trackDuration: { | ||
| totalMilliseconds: number; | ||
| }; | ||
| }; | ||
| }; | ||
| }>; | ||
| }; | ||
| }; | ||
| }; | ||
| extensions: { | ||
| cacheControl: { | ||
| version: number; | ||
| hints: Array<any>; | ||
| }; | ||
| }; | ||
| }; |
| export {}; |
| export type SpotifyProductState = { | ||
| ads: string; | ||
| catalogue: string; | ||
| country: string; | ||
| "preferred-locale": string; | ||
| "selected-language": string; | ||
| product: string; | ||
| "on-demand": string; | ||
| "multiuserplan-current-size": string; | ||
| "multiuserplan-member-type": string; | ||
| }; |
| export {}; |
| export type SpotifyTrackCredits = { | ||
| trackUri: string; | ||
| trackTitle: string; | ||
| roleCredits: Array<{ | ||
| roleTitle: string; | ||
| artists: Array<{ | ||
| uri: string; | ||
| name: string; | ||
| imageUri: string; | ||
| subroles: Array<string>; | ||
| weight: number; | ||
| }>; | ||
| }>; | ||
| extendedCredits: Array<any>; | ||
| sourceNames: Array<string>; | ||
| }; |
| export {}; |
+21
-7
@@ -0,10 +1,9 @@ | ||
| import { SpotiflyBase } from "./base.js"; | ||
| import { Musixmatch } from "./musixmatch.js"; | ||
| import { SpotifyAlbum, SpotifyArtist, SpotifyEpisode, SpotifyExtractedColors, SpotifyHome, SpotifyPlaylist, SpotifyPodcast, SpotifyPodcastEpisodes, SpotifyRelatedTrackArtists, SpotifySearchAlbums, SpotifySearchAll, SpotifySearchArtists, SpotifySearchPlaylists, SpotifySearchPodcasts, SpotifySearchTracks, SpotifySearchUsers, SpotifySection, SpotifyTrack, SpotifyUser } from "./types/index"; | ||
| export declare class Spotifly { | ||
| private token; | ||
| private tokenExpirationTimestampMs; | ||
| private refreshToken; | ||
| private fetch; | ||
| import { SpotifyAlbum, SpotifyArtist, SpotifyColorLyrics, SpotifyEpisode, SpotifyExtractedColors, SpotifyHome, SpotifyLikedSongs, SpotifyLikedSongsAdd, SpotifyLikedSongsRemove, SpotifyMyLibrary, SpotifyPlaylist, SpotifyPodcast, SpotifyPodcastEpisodes, SpotifyProductState, SpotifyRelatedTrackArtists, SpotifySearchAlbums, SpotifySearchAll, SpotifySearchArtists, SpotifySearchPlaylists, SpotifySearchPodcasts, SpotifySearchTracks, SpotifySearchUsers, SpotifySection, SpotifyTrack, SpotifyTrackCredits, SpotifyUser } from "./types"; | ||
| declare class SpotiflyMain extends SpotiflyBase { | ||
| constructor(cookie?: string); | ||
| getHomepage(): Promise<SpotifyHome>; | ||
| getTrack(id: string): Promise<SpotifyTrack>; | ||
| getTrackCredits(id: string): Promise<SpotifyTrackCredits>; | ||
| getRelatedTrackArtists(id: string): Promise<SpotifyRelatedTrackArtists>; | ||
@@ -14,2 +13,4 @@ getArtist(id: string): Promise<SpotifyArtist>; | ||
| getPlaylist(id: string, limit?: number): Promise<SpotifyPlaylist>; | ||
| getPlaylistMetadata(id: string, limit?: number): Promise<import("./types").SpotifyPlaylistMetadata>; | ||
| getPlaylistContents(id: string, limit?: number): Promise<import("./types").SpotifyPlaylistContents>; | ||
| getUser(id: string, config?: { | ||
@@ -33,4 +34,17 @@ playlistLimit: number; | ||
| extractImageColors(...urls: string[]): Promise<SpotifyExtractedColors>; | ||
| getMyProfile(): Promise<import("./types").SpotifyMyProfile>; | ||
| getMyLibrary(config?: Partial<{ | ||
| filter: [] | ["Playlists"] | ["Playlists", "By you"] | ["Artists"]; | ||
| order: "Recents" | "Recently Added" | "Alphabetical" | "Creator" | "Custom Order"; | ||
| textFilter: string; | ||
| limit: number; | ||
| }>): Promise<SpotifyMyLibrary>; | ||
| getMyProductState(): Promise<SpotifyProductState>; | ||
| getMyLikedSongs(limit?: number): Promise<SpotifyLikedSongs>; | ||
| addToLikedSongs(...trackUris: string[]): Promise<SpotifyLikedSongsAdd>; | ||
| removeFromLikedSongs(...trackUris: string[]): Promise<SpotifyLikedSongsRemove>; | ||
| getTrackColorLyrics(id: string, imgUrl?: string): Promise<SpotifyColorLyrics>; | ||
| } | ||
| export { Parse } from "./parse.js"; | ||
| export { Musixmatch }; | ||
| export { SpotiflyPlaylist } from "./playlist.js"; | ||
| export { Musixmatch, SpotiflyMain as Spotifly }; |
+49
-14
@@ -0,16 +1,7 @@ | ||
| import { SpotiflyBase } from "./base.js"; | ||
| import { Musixmatch } from "./musixmatch.js"; | ||
| export class Spotifly { | ||
| token = ""; | ||
| tokenExpirationTimestampMs = -1; | ||
| async refreshToken() { | ||
| if (this.tokenExpirationTimestampMs > Date.now()) | ||
| return; | ||
| const response = await (await fetch("https://open.spotify.com/get_access_token")).json(); | ||
| this.token = "Bearer " + response.accessToken; | ||
| this.tokenExpirationTimestampMs = response.accessTokenExpirationTimestampMs; | ||
| class SpotiflyMain extends SpotiflyBase { | ||
| constructor(cookie) { | ||
| super(cookie); | ||
| } | ||
| async fetch(url) { | ||
| await this.refreshToken(); | ||
| return (await fetch(url, { headers: { authorization: this.token } })).json(); | ||
| } | ||
| async getHomepage() { | ||
@@ -22,2 +13,5 @@ return this.fetch(`https://api-partner.spotify.com/pathfinder/v1/query?operationName=home&variables=%7B%22timeZone%22%3A%22${encodeURIComponent(Intl.DateTimeFormat().resolvedOptions().timeZone)}%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22bbc1b1a421216c1299382b076c1aa8d52b91a0dfc91a4ae431a05b0a43a721e0%22%7D%7D`); | ||
| } | ||
| async getTrackCredits(id) { | ||
| return this.fetch(`https://spclient.wg.spotify.com/track-credits-view/v0/experimental/${id}/credits`); | ||
| } | ||
| async getRelatedTrackArtists(id) { | ||
@@ -35,2 +29,8 @@ return this.fetch(`https://api-partner.spotify.com/pathfinder/v1/query?operationName=getRichTrackArtists&variables=%7B%22uri%22%3A%22spotify%3Atrack%3A${id}%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22b73a738f01c30e4dd90bc7e4c0e59f4d690a74f2b0c48a2eabbfd798a4a7e76a%22%7D%7D`); | ||
| } | ||
| async getPlaylistMetadata(id, limit = 50) { | ||
| return super.getPlaylistMetadata(id, limit); | ||
| } | ||
| async getPlaylistContents(id, limit = 50) { | ||
| return super.getPlaylistContents(id, limit); | ||
| } | ||
| async getUser(id, config = { playlistLimit: 10, artistLimit: 10, episodeLimit: 10 }) { | ||
@@ -79,4 +79,39 @@ return this.fetch(`https://spclient.wg.spotify.com/user-profile-view/v3/profile/${id}?playlist_limit=${config.playlistLimit}&artist_limit=${config.artistLimit}&episode_limit=${config.episodeLimit}&market=from_token`); | ||
| } | ||
| /* Cookie Exclusive Functions */ | ||
| async getMyProfile() { | ||
| return super.getMyProfile(); | ||
| } | ||
| async getMyLibrary(config = { filter: [], order: "Recents", textFilter: "", limit: 50 }) { | ||
| if (!this.cookie) | ||
| throw Error("no cookie provided"); | ||
| return this.fetch(`https://api-partner.spotify.com/pathfinder/v1/query?operationName=libraryV2&variables=%7B%22filters%22%3A${encodeURIComponent(JSON.stringify(config.filter))}%2C%22order%22%3A%22${config.order}%22%2C%22textFilter%22%3A%22${config.textFilter}%22%2C%22features%22%3A%5B%22LIKED_SONGS%22%2C%22YOUR_EPISODES%22%5D%2C%22limit%22%3A${config.limit}%2C%22offset%22%3A0%2C%22flatten%22%3Atrue%2C%22folderUri%22%3Anull%2C%22includeFoldersWhenFlattening%22%3Atrue%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22e1f99520ac4e82cba64e9ebdee4ed5532024ee5af6956e8465e99709a8f8348f%22%7D%7D`); | ||
| } | ||
| async getMyProductState() { | ||
| if (!this.cookie) | ||
| throw Error("no cookie provided"); | ||
| return this.fetch("https://spclient.wg.spotify.com/melody/v1/product_state?market=from_token"); | ||
| } | ||
| async getMyLikedSongs(limit = 25) { | ||
| if (!this.cookie) | ||
| throw Error("no cookie provided"); | ||
| return this.fetch(`https://api-partner.spotify.com/pathfinder/v1/query?operationName=fetchLibraryTracks&variables=%7B%22offset%22%3A0%2C%22limit%22%3A${limit}%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%228474ec383b530ce3e54611fca2d8e3da57ef5612877838b8dbf00bd9fc692dfb%22%7D%7D`); | ||
| } | ||
| async addToLikedSongs(...trackUris) { | ||
| if (!this.cookie) | ||
| throw Error("no cookie provided"); | ||
| return this.post("https://api-partner.spotify.com/pathfinder/v1/query", `{"variables":{"uris":${JSON.stringify(trackUris)}},"operationName":"addToLibrary","extensions":{"persistedQuery":{"version":1,"sha256Hash":"656c491c3f65d9d08d259be6632f4ef1931540ebcf766488ed17f76bb9156d15"}}}`); | ||
| } | ||
| async removeFromLikedSongs(...trackUris) { | ||
| if (!this.cookie) | ||
| throw Error("no cookie provided"); | ||
| return this.post("https://api-partner.spotify.com/pathfinder/v1/query", `{"variables":{"uris":${JSON.stringify(trackUris)}},"operationName":"removeFromLibrary","extensions":{"persistedQuery":{"version":1,"sha256Hash":"1103bfd4b9d80275950bff95ef6d41a02cec3357e8f7ecd8974528043739677c"}}}`); | ||
| } | ||
| async getTrackColorLyrics(id, imgUrl) { | ||
| if (!this.cookie) | ||
| throw Error("no cookie provided"); | ||
| return this.fetch(`https://spclient.wg.spotify.com/color-lyrics/v2/track/${id}${imgUrl ? `/image/${encodeURIComponent(imgUrl)}` : ""}?format=json&vocalRemoval=false&market=from_token`, { "app-platform": "WebPlayer" }); | ||
| } | ||
| } | ||
| export { Parse } from "./parse.js"; | ||
| export { Musixmatch }; | ||
| export { SpotiflyPlaylist } from "./playlist.js"; | ||
| export { Musixmatch, SpotiflyMain as Spotifly }; |
+2
-0
| export declare namespace Parse { | ||
| function urlToId(url: string): string; | ||
| function uriToId(uri: string): string; | ||
| function urlToUri(url: string): string; | ||
| function uriToUrl(uri: string): string; | ||
| } |
+10
-0
@@ -11,2 +11,12 @@ export var Parse; | ||
| Parse.uriToId = uriToId; | ||
| function urlToUri(url) { | ||
| const parts = new URL(url).pathname.split("/"); | ||
| return `spotify:${parts[1]}:${parts[2]}`; | ||
| } | ||
| Parse.urlToUri = urlToUri; | ||
| function uriToUrl(uri) { | ||
| const parts = uri.split(":"); | ||
| return `https://open.spotify.com/${parts[1]}/${parts[2]}`; | ||
| } | ||
| Parse.uriToUrl = uriToUrl; | ||
| })(Parse || (Parse = {})); |
@@ -9,2 +9,4 @@ export { SpotifyAlbum } from "./album"; | ||
| export { SpotifyPlaylist } from "./playlist"; | ||
| export { SpotifyPlaylistMetadata } from "./playlistMetadata"; | ||
| export { SpotifyPlaylistContents } from "./playlistContents"; | ||
| export { SpotifyPodcast } from "./podcast"; | ||
@@ -23,1 +25,7 @@ export { SpotifyPodcastEpisodes } from "./podcastEpisodes"; | ||
| export { SpotifyUser } from "./user"; | ||
| export { SpotifyMyProfile } from "./myProfile"; | ||
| export { SpotifyMyLibrary } from "./myLibrary"; | ||
| export { SpotifyProductState } from "./productState"; | ||
| export { SpotifyColorLyrics } from "./colorLyrics"; | ||
| export { SpotifyTrackCredits } from "./trackCredits"; | ||
| export { SpotifyLikedSongs, SpotifyLikedSongsAdd, SpotifyLikedSongsRemove } from "./likedSongs"; |
+3
-3
| { | ||
| "name": "spotifly", | ||
| "version": "0.1.0", | ||
| "version": "0.1.1", | ||
| "description": "Spotify library in typescript without using the Spotify Web API.", | ||
@@ -40,4 +40,4 @@ "main": "dist/index.js", | ||
| "devDependencies": { | ||
| "bun-types": "^0.5.8" | ||
| "bun-types": "^0.6.12" | ||
| } | ||
| } | ||
| } |
+150
-5
@@ -11,2 +11,3 @@ #  `spotifly` | ||
| - Strongly typed API functions. | ||
| - Personalized fetching and automation using cookies. | ||
| - Automatic internal token refreshing. | ||
@@ -22,3 +23,3 @@ | ||
| `node.js (>=17.5.0)` or `bun` runtime. | ||
| `node.js (>=17.5.0)`, `bun` or `deno` runtime. | ||
@@ -43,5 +44,8 @@ - ### Installation | ||
| Functions marked with an asterisk (*) require your spotify cookies to work. [How to get your Spotify cookies ?](#-how-to-get-your-spotify-cookies-) | ||
| - [`Spotifly` module](#spotifly-module) | ||
| - [`getHomepage`](#gethomepage-promisespotifyhome) | ||
| - [`getTrack`](#gettrackid-string-promisespotifytrack) | ||
| - [`getTrackCredits`](#gettrackcreditsid-string-promisespotifytrackcredits) | ||
| - [`getRelatedTrackArtists`](#getrelatedtrackartistsid-string-promisespotifyrelatedtrackartists) | ||
@@ -51,2 +55,4 @@ - [`getArtist`](#getartistid-string-promisespotifyartist) | ||
| - [`getPlaylist`](#getplaylistid-string-limit-number-promisespotifyplaylist) | ||
| - [`getPlaylistMetadata`](#getplaylistmetadataid-string-limit-number-promisespotifyplaylistmetadata) | ||
| - [`getPlaylistContents`](#getplaylistcontentsid-string-limit-number-promisespotifyplaylistcontents) | ||
| - [`getUser`](#getuserid-string-config---playlistlimit-number-artistlimit-number-episodelimit-number--promisespotifyuser) | ||
@@ -66,2 +72,20 @@ - [`getSection`](#getsectionid-string-promisespotifysection) | ||
| - [`extractImageColors`](#extractimagecolorsurls-string-promisespotifyextractedcolors) | ||
| - *[`getMyProfile`](#getmyprofile-promisespotifymyprofile) | ||
| - *[`getMyLibrary`](#getmylibraryconfig-promisespotifymylibrary) | ||
| - *[`getMyProductState`](#getmyproductstate-promisespotifyproductstate) | ||
| - *[`getMyLikedSongs`](#getmylikedsongs-promisespotifylikedsongs) | ||
| - *[`addToLikedSongs`](#addtolikedsongstrackuris-string-promisespotifylikedsongsadd) | ||
| - *[`removeFromLikedSongs`](#removefromlikedsongstrackuris-string-promisespotifylikedsongsremove) | ||
| - *[`getTrackColorLyrics`](#gettrackcolorlyricsid-string-imgurl-string-promisespotifycolorlyrics) | ||
| - *[`SpotiflyPlaylist` module](#spotiflyplaylist-module) | ||
| - [`id`](#id-string) | ||
| - [`create`](#createname-string) | ||
| - [`rename`](#renamenewname-string) | ||
| - [`changeDescription`](#changedescriptionnewdescription-string) | ||
| - [`fetchMetadata`](#fetchmetadatalimit-number) | ||
| - [`fetchContents`](#fetchcontentslimit-number) | ||
| - [`add`](#addtrackuris-string) | ||
| - [`remove`](#removetrackuris-string) | ||
| - [`cloneFrom`](#clonefromid-string-config--name-string-description-string-limit-number-) | ||
| - [`delete`](#delete) | ||
| - [`Musixmatch` module](#musixmatch-module) | ||
@@ -74,2 +98,5 @@ - [`search`](#searchterms-string-musixmatchsearch) | ||
| - [`uriToId`](#uritoiduri-string-string) | ||
| - [`urlToUri`](#urltouriurl-string-string) | ||
| - [`uriToUrl`](#uritourluri-string-string) | ||
| - [How to get your Spotify cookies ?](#-how-to-get-your-spotify-cookies-) | ||
@@ -80,2 +107,4 @@ | ||
| ### `new Spotifly(cookie?: string)` | ||
| The main module containing all the Spotify API functions. | ||
@@ -91,2 +120,6 @@ | ||
| - ### `getTrackCredits(id: string)`: [*`Promise<SpotifyTrackCredits>`*](https://github.com/tr1ckydev/spotifly/blob/main/src/types/trackCredits.ts) | ||
| Fetch the credits of the provided track id. | ||
| - ### `getRelatedTrackArtists(id: string)`: [*`Promise<SpotifyRelatedTrackArtists>`*](https://github.com/tr1ckydev/spotifly/blob/main/src/types/relatedTrackArtists.ts) | ||
@@ -106,6 +139,14 @@ | ||
| Fetch the details of the provided playlist id, with optional limit for amount of tracks to fetch. | ||
| Fetch all the details of the provided playlist id, with optional limit for amount of tracks to fetch. | ||
| - ### `getUser(id: string, config?: { playlistLimit?: number, artistLimit?: number, episodeLimit?: number })`: [*`Promise<SpotifyUser>`*](https://github.com/tr1ckydev/spotifly/blob/main/src/types/user.ts) | ||
| - ### `getPlaylistMetadata(id: string, limit?: number)`: [*`Promise<SpotifyPlaylistMetadata>`*](https://github.com/tr1ckydev/spotifly/blob/main/src/types/playlistMetadata.ts) | ||
| Fetch the metadata only of the provided playlist id, with optional limit for amount of tracks to fetch. | ||
| - ### `getPlaylistContents(id: string, limit?: number)`: [*`Promise<SpotifyPlaylistContents>`*](https://github.com/tr1ckydev/spotifly/blob/main/src/types/playlistContents.ts) | ||
| Fetch the contents of the provided playlist id, with optional limit for amount of tracks to fetch. | ||
| - ### `getUser(id: string, config?: { playlistLimit?: number, artistLimit?: number, episodeLimit?: number })`: [*`Promise<SpotifyUser>`*](https://github.com/tr1ckydev/spotifly/blob/main/src/types/user.ts) | ||
| Fetch the details of the provided user id, with optional limit for amount of tracks to fetch. | ||
@@ -161,2 +202,4 @@ | ||
| If you want to fetch lyrics directly from Spotify, see [`getTrackColorLyrics`](#gettrackcolorlyricsid-string-imgurl-string-promisespotifycolorlyrics). | ||
| - ### `extractImageColors(...urls: string[])`: [*`Promise<SpotifyExtractedColors>`*](https://github.com/tr1ckydev/spotifly/blob/main/src/types/extractedColors.ts) | ||
@@ -166,4 +209,87 @@ | ||
| > The following functions require cookies to work. [How to get your Spotify cookies ?](#-how-to-get-your-spotify-cookies-) | ||
| - ### `getMyProfile()`: [*`Promise<SpotifyMyProfile>`*](https://github.com/tr1ckydev/spotifly/blob/main/src/types/myProfile.ts) | ||
| Fetch the details of your Spotify profile. | ||
| - ### `getMyLibrary(config?)`: [*`Promise<SpotifyMyLibrary>`*](https://github.com/tr1ckydev/spotifly/blob/main/src/types/myLibrary.ts) | ||
| Fetch your Spotify library. | ||
| - config.filter?: `[] | ["Playlists"] | ["Playlists", "By you"] | ["Artists"]` | ||
| - config.order?: `"Recents" | "Recently Added" | "Alphabetical" | "Creator" | "Custom Order"` | ||
| - config.textFilter?: `string` | ||
| - config.limit?: `number` | ||
| - ### `getMyProductState()`: [*`Promise<SpotifyProductState>`*](https://github.com/tr1ckydev/spotifly/blob/main/src/types/productState.ts) | ||
| Fetch the details of your Spotify product state like premium plan, etc. | ||
| - ### `getMyLikedSongs()`: [*`Promise<SpotifyLikedSongs>`*](https://github.com/tr1ckydev/spotifly/blob/main/src/types/likedSongs.ts) | ||
| Fetch the songs you have liked from your Spotify library. | ||
| - ### `addToLikedSongs(...trackUris: string[])`: [*`Promise<SpotifyLikedSongsAdd>`*](https://github.com/tr1ckydev/spotifly/blob/main/src/types/likedSongs.ts) | ||
| Add the tracks to your liked songs library. | ||
| - ### `removeFromLikedSongs(...trackUris: string[])`: [*`Promise<SpotifyLikedSongsRemove>`*](https://github.com/tr1ckydev/spotifly/blob/main/src/types/likedSongs.ts) | ||
| Remove the tracks from your liked songs library. | ||
| - ### `getTrackColorLyrics(id: string, imgUrl?: string)`: [*`Promise<SpotifyColorLyrics>`*](https://github.com/tr1ckydev/spotifly/blob/main/src/types/likedSongs.ts) | ||
| Fetch the track lyrics directly from Spotify's internal Musixmatch API with an optional image url to fetch the colors of that image. | ||
| ## `SpotiflyPlaylist` module | ||
| ### `new SpotiflyPlaylist(cookie: string)` | ||
| The module containing all the functions to interact with playlists in your Spotify library using the cookies provided. [How to get your Spotify cookies ?](#-how-to-get-your-spotify-cookies-) | ||
| - ### `id`: `string` | ||
| Property to get or set the playlist id with whom the following functions will be interacting. | ||
| - ### `create(name: string)` | ||
| Create a new empty playlist with the provided name in your Spotify library and sets the `id` with the newly created one. | ||
| - ### `rename(newName: string)` | ||
| Change the name of the playlist with the new name provided. | ||
| - ### `changeDescription(newDescription: string)` | ||
| Change the description of the playlist with the new description provided. | ||
| - ### `fetchMetadata(limit?: number)` | ||
| Fetch the metadata of the playlist. | ||
| - ### `fetchContents(limit?: number)` | ||
| Fetch the contents of the playlist. | ||
| - ### `add(...trackUris: string[])` | ||
| Add tracks to the playlist from the provided track uris. | ||
| - ### `remove(...trackUris: string[])` | ||
| Remove tracks from the playlist from the provided track uris. | ||
| - ### `cloneFrom(id: string, config?: { name?: string, description?: string, limit?: number; })` | ||
| Create a new playlist in your Spotify library by cloning from another playlist with optional config to change the data of the created playlist and sets the `id` with the newly created one. | ||
| - ### `delete()` | ||
| Delete the playlist from your Spotify library. | ||
| ## `Musixmatch` module | ||
@@ -195,12 +321,31 @@ | ||
| - ### `uriToId(uri: string)`: *`string`* | ||
| Extract the id from a Spotify uri (i.e. `spotify:track:abcdefghijk`). | ||
| - ### `urlToUri(url: string)`: `string` | ||
| - ### `uriToId(uri: string)`: *`string`* | ||
| Convert an `open.spotify.com` url to a Spotify uri (i.e. `spotify:track:abcdefghijk`). | ||
| Extract the id from a spotify uri (i.e. `spotify:track:abcdefghijk`). | ||
| - ### `uriToUrl(uri: string)`: `string` | ||
| Convert a Spotify uri (i.e. `spotify:track:abcdefghijk`) to an `open.spotify.com` url. | ||
| ## 🍪 How to get your Spotify cookies ? | ||
| - Login to your Spotify account in your browser. | ||
| - Open *Developer Tools* of your browser and switch to *Network* tab. | ||
| - Go to https://open.spotify.com/. | ||
| - Find the request with the name `open.spotify.com` and open it. | ||
| - From the *Headers* tab, scroll to *Request Headers* section. | ||
| - Copy the contents of the `Cookie` header value. | ||
| The copied value is your Spotify cookies. | ||
| ## 📜 License | ||
| This repository uses MIT License. See [LICENSE](https://github.com/tr1ckydev/spotifly/blob/main/LICENSE) for full license text. |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
173124
28.98%73
37.74%3835
27.07%340
74.36%36
28.57%