import axios, { type AxiosInstance } from "axios"; import { ampApiUrl, appleMusicHomepageUrl, licenseApiUrl, webplaybackApiUrl } from "../constants/urls.js"; import type { GetSongResponse } from "./types/appleMusic/responses.js"; import type { SongAttributesExtensionTypes } from "./types/appleMusic/extensions.js"; import { getToken } from "./token.js"; import { config } from "../config.js"; export default class AppleMusicApi { private storefront: string; private http: AxiosInstance; public constructor( storefront: string, language: string, mediaUserToken: string ) { this.storefront = storefront; this.http = axios.create({ baseURL: ampApiUrl, headers: { "Origin": appleMusicHomepageUrl, "Media-User-Token": mediaUserToken, // TODO: move somewhere else // this is only used for `getWidevineLicense` "x-apple-music-user-token": mediaUserToken, "x-apple-renewal": true // do i wanna know what this does? }, params: { "l": language } }); } public async login(): Promise { this.http.defaults.headers.common["Authorization"] = `Bearer ${await getToken(appleMusicHomepageUrl)}`; } async getSong< T extends SongAttributesExtensionTypes = ["extendedAssetUrls"] > ( id: string, extend: T = ["extendedAssetUrls"] as T ): Promise> { return (await this.http.get>(`/v1/catalog/${this.storefront}/songs/${id}`, { params: { extend: extend.join(",") } })).data; } async getWebplayback( trackId: string ): Promise { return (await this.http.post(webplaybackApiUrl, { salableAdamId: trackId, language: config.downloader.api.language })).data; } async getWidevineLicense( trackId: string, trackUri: string, challenge: string ): Promise<{ license: string | undefined }> { // dubious type, doesn't matter much return (await this.http.post(licenseApiUrl, { challenge: challenge, "key-system": "com.widevine.alpha", uri: trackUri, adamId: trackId, isLibrary: false, "user-initiated": true })).data; } }