mirror of https://github.com/miraclx/freyr-js
feat(Apple Music): add support for song-type URLs (#552)
This commit is contained in:
parent
ed4fedfbdd
commit
8c57292441
14
README.md
14
README.md
|
|
@ -952,14 +952,22 @@ To preview filter rules specification, use the `filter` subcommand.
|
|||
<td> <code> spotify:playlist:37i9dQZF1DXcBWIGoYBM5M </code> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan=8> Apple Music </td>
|
||||
<td rowspan=2> track </td>
|
||||
<td rowspan=10> Apple Music </td>
|
||||
<td rowspan=4> track </td>
|
||||
<td> URL </td>
|
||||
<td> <a href="https://music.apple.com/us/album/say-so-feat-nicki-minaj/1510821672?i=1510821685"> https://music.apple.com/us/album/say-so-feat-nicki-minaj/1510821672?i=1510821685 </a> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> URI </td>
|
||||
<td> <code> apple_music:track:1510821672i1510821685 </code> </td>
|
||||
<td> <code> apple_music:track:1510821685 </code> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> URL </td>
|
||||
<td> <a href="https://music.apple.com/us/song/1510821685"> https://music.apple.com/us/song/1510821685 </a> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> URI </td>
|
||||
<td> <code> apple_music:track:1510821685 </code> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan=2> album </td>
|
||||
|
|
|
|||
2
cli.js
2
cli.js
|
|
@ -2169,7 +2169,7 @@ function prepCli(packageJson) {
|
|||
console.log(' spotify:album:2D23kwwoy2JpZVuJwzE42B');
|
||||
console.log('');
|
||||
console.log(' $ freyr urify -t https://music.apple.com/us/album/say-so-feat-nicki-minaj/1510821672?i=1510821685');
|
||||
console.log(' apple_music:track:1510821672i1510821685');
|
||||
console.log(' apple_music:track:1510821685');
|
||||
console.log('');
|
||||
console.log(
|
||||
[
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ export default class AppleMusic {
|
|||
isSearchable: false,
|
||||
isSourceable: false,
|
||||
},
|
||||
// https://www.debuggex.com/r/nbRgm3fyDn2oampX
|
||||
// https://www.debuggex.com/r/Pv_Prjinkz1m2FOB
|
||||
VALID_URL:
|
||||
/(?:(?:(?:(?:https?:\/\/)?(?:www\.)?)(?:(?:music|(?:geo\.itunes))\.apple.com)\/([a-z]{2})\/(album|artist|playlist)\/(?:([^/]+)\/)?\w+)|(?:apple_music:(track|album|artist|playlist):([\w.]+)))/,
|
||||
/(?:(?:(?:(?:https?:\/\/)?(?:www\.)?)(?:(?:music|(?:geo\.itunes))\.apple.com)\/([a-z]{2})\/(song|album|artist|playlist)\/(?:([^/]+)\/)?\w+)|(?:apple_music:(track|album|artist|playlist):([\w.]+)))/,
|
||||
PROP_SCHEMA: {},
|
||||
};
|
||||
|
||||
|
|
@ -90,20 +90,17 @@ export default class AppleMusic {
|
|||
if (!match) return null;
|
||||
const isURI = !!match[4];
|
||||
const parsedURL = xurl.parse(uri, true);
|
||||
let collection_type = match[isURI ? 4 : 2];
|
||||
let id = (isURI && match[4] === 'track' ? match[5] : parsedURL.query.i) || null;
|
||||
const type = isURI ? match[4] : collection_type === 'album' && id ? 'track' : collection_type;
|
||||
collection_type = type === 'track' && !id ? 'album' : collection_type;
|
||||
let refID = isURI ? (type !== 'track' ? match[5] : null) : path.basename(parsedURL.pathname);
|
||||
if (type === 'track' && !refID) if (id.match(/^(\d+)i(\d+)$/)) [refID, id] = id.split('i');
|
||||
storefront = match[1] || storefront || (#store in this && this.#store.defaultStorefront) || 'us';
|
||||
const collection_type = isURI ? match[4] : match[2] === 'song' ? 'track' : match[2];
|
||||
const id = isURI ? match[5] : parsedURL.query.i || path.basename(parsedURL.pathname);
|
||||
const type = isURI ? match[4] : collection_type == 'album' && parsedURL.query.i ? 'track' : collection_type;
|
||||
const scope = collection_type == 'track' || (collection_type == 'album' && parsedURL.query.i) ? 'song' : collection_type;
|
||||
storefront = match[1] || storefront || (#store in this ? this.#store.defaultStorefront : 'us');
|
||||
return {
|
||||
id,
|
||||
type,
|
||||
refID,
|
||||
key: match[3] || null,
|
||||
uri: `apple_music:${type}:${id ? `${refID}i` : refID}${id || ''}`,
|
||||
url: `https://music.apple.com/${storefront}/${collection_type}/${refID}${id ? `?i=${id}` : ''}`,
|
||||
uri: `apple_music:${type}:${id}`,
|
||||
url: `https://music.apple.com/${storefront}/${scope}/${id}`,
|
||||
storefront,
|
||||
collection_type,
|
||||
};
|
||||
|
|
@ -117,7 +114,7 @@ export default class AppleMusic {
|
|||
name: trackInfo.attributes.name,
|
||||
artists: [trackInfo.attributes.artistName],
|
||||
album: albumInfo.name,
|
||||
album_uri: `apple_music:album:${albumInfo.id || this.parseURI(trackInfo.attributes.url).refID}`,
|
||||
album_uri: `apple_music:album:${albumInfo.id}`,
|
||||
album_type: albumInfo.type,
|
||||
images: trackInfo.attributes.artwork,
|
||||
duration: trackInfo.attributes.durationInMillis,
|
||||
|
|
@ -206,7 +203,7 @@ export default class AppleMusic {
|
|||
const parsed = this.parseURI(_uri, store);
|
||||
if (!parsed) return [];
|
||||
parsed.value = this.#store.cache.get(parsed.uri);
|
||||
return [[parsed.id || parsed.refID, parsed]];
|
||||
return [[parsed.id, parsed]];
|
||||
});
|
||||
const packs = uris.filter(([, {value}]) => !value).map(([, parsed]) => parsed);
|
||||
uris = Object.fromEntries(uris);
|
||||
|
|
@ -246,7 +243,7 @@ export default class AppleMusic {
|
|||
return this.processData(uris, 300, store, async (items, storefront) => {
|
||||
const {data: tracks} = await this.#store.core.songs.get(`?ids=${items.map(item => item.id).join(',')}`, {storefront});
|
||||
await this.getAlbum(
|
||||
items.map(item => `apple_music:album:${item.refID}`),
|
||||
tracks.flatMap(item => item.relationships.albums.data.map(item => `apple_music:album:${item.id}`)),
|
||||
storefront,
|
||||
);
|
||||
return Promise.mapSeries(tracks, async track => {
|
||||
|
|
@ -260,9 +257,14 @@ export default class AppleMusic {
|
|||
throw err;
|
||||
// this.#store.core.songs.get(`${track.id}${nextUrl.split(track.href)[1]}`, {storefront});
|
||||
});
|
||||
if (track.albums.length > 1) {
|
||||
let err = new Error('Unimplemented: track with multiple albums');
|
||||
[err.trackId, err.trackHref] = [track.id, track.href];
|
||||
throw err;
|
||||
}
|
||||
return this.wrapTrackMeta(
|
||||
track,
|
||||
await this.getAlbum(`apple_music:album:${this.parseURI(track.attributes.url).refID}`, storefront),
|
||||
await this.getAlbum(`apple_music:album:${track.relationships.albums.data[0].id}`, storefront),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -271,7 +273,7 @@ export default class AppleMusic {
|
|||
async getAlbum(uris, store) {
|
||||
return this.processData(uris, 100, store, async (items, storefront) =>
|
||||
Promise.mapSeries(
|
||||
(await this.#store.core.albums.get(`?ids=${items.map(item => item.refID).join(',')}`, {storefront})).data,
|
||||
(await this.#store.core.albums.get(`?ids=${items.map(item => item.id).join(',')}`, {storefront})).data,
|
||||
async album => {
|
||||
album.tracks = await this.depaginate(album.relationships.tracks, nextUrl => {
|
||||
let err = new Error('Unimplemented: album tracks pagination');
|
||||
|
|
@ -295,7 +297,7 @@ export default class AppleMusic {
|
|||
async getArtist(uris, store) {
|
||||
return this.processData(uris, 25, store, async (items, storefront) =>
|
||||
Promise.mapSeries(
|
||||
(await this.#store.core.artists.get(`?ids=${items.map(item => item.refID).join(',')}`, {storefront})).data,
|
||||
(await this.#store.core.artists.get(`?ids=${items.map(item => item.id).join(',')}`, {storefront})).data,
|
||||
async artist => {
|
||||
artist.albums = await this.depaginate(artist.relationships.albums, nextUrl =>
|
||||
this.#store.core.artists.get(`${artist.id}${nextUrl.split(artist.href)[1]}`, {storefront}),
|
||||
|
|
@ -309,7 +311,7 @@ export default class AppleMusic {
|
|||
async getPlaylist(uris, store) {
|
||||
return this.processData(uris, 25, store, async (items, storefront) =>
|
||||
Promise.mapSeries(
|
||||
(await this.#store.core.playlists.get(`?ids=${items.map(item => item.refID).join(',')}`, {storefront})).data,
|
||||
(await this.#store.core.playlists.get(`?ids=${items.map(item => item.id).join(',')}`, {storefront})).data,
|
||||
async playlist => {
|
||||
playlist.tracks = await this.depaginate(playlist.relationships.tracks, nextUrl =>
|
||||
this.#store.core.playlists.get(`${playlist.id}${nextUrl.split(playlist.href)[1]}`, {storefront}),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
import FreyrCore from '../src/freyr.js';
|
||||
|
||||
let corpus = [
|
||||
{
|
||||
url: 'https://open.spotify.com/track/127QTOFJsJQp5LbJbu3A1y',
|
||||
uri: 'spotify:track:127QTOFJsJQp5LbJbu3A1y',
|
||||
},
|
||||
{
|
||||
url: 'https://open.spotify.com/album/623PL2MBg50Br5dLXC9E9e',
|
||||
uri: 'spotify:album:623PL2MBg50Br5dLXC9E9e',
|
||||
},
|
||||
{
|
||||
url: 'https://open.spotify.com/artist/6M2wZ9GZgrQXHCFfjv46we',
|
||||
uri: 'spotify:artist:6M2wZ9GZgrQXHCFfjv46we',
|
||||
},
|
||||
{
|
||||
url: 'https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M',
|
||||
uri: 'spotify:playlist:37i9dQZF1DXcBWIGoYBM5M',
|
||||
},
|
||||
{
|
||||
url: 'https://music.apple.com/us/album/say-so-feat-nicki-minaj/1510821672?i=1510821685',
|
||||
uri: 'apple_music:track:1510821685',
|
||||
},
|
||||
{
|
||||
url: 'https://music.apple.com/us/song/1510821685',
|
||||
uri: 'apple_music:track:1510821685',
|
||||
},
|
||||
{
|
||||
url: 'https://music.apple.com/us/album/birds-of-prey-the-album/1493581254',
|
||||
uri: 'apple_music:album:1493581254',
|
||||
},
|
||||
{
|
||||
url: 'https://music.apple.com/us/artist/412778295',
|
||||
uri: 'apple_music:artist:412778295',
|
||||
},
|
||||
{
|
||||
url: 'https://music.apple.com/us/playlist/todays-hits/pl.f4d106fed2bd41149aaacabb233eb5eb',
|
||||
uri: 'apple_music:playlist:pl.f4d106fed2bd41149aaacabb233eb5eb',
|
||||
},
|
||||
{
|
||||
url: 'https://www.deezer.com/en/track/642674232',
|
||||
uri: 'deezer:track:642674232',
|
||||
},
|
||||
{
|
||||
url: 'https://www.deezer.com/en/album/99687992',
|
||||
uri: 'deezer:album:99687992',
|
||||
},
|
||||
{
|
||||
url: 'https://www.deezer.com/en/artist/5340439',
|
||||
uri: 'deezer:artist:5340439',
|
||||
},
|
||||
{
|
||||
url: 'https://www.deezer.com/en/playlist/1963962142',
|
||||
uri: 'deezer:playlist:1963962142',
|
||||
},
|
||||
];
|
||||
|
||||
function main() {
|
||||
for (let item of corpus) {
|
||||
for (let key in item) {
|
||||
let parsed = FreyrCore.parseURI(item[key]);
|
||||
if (parsed) {
|
||||
console.log(`⏩┬[ \x1b[36m${item[key]}\x1b[39m ]`);
|
||||
if (parsed.uri === item.uri) {
|
||||
console.log(` ├ ✅ asURI -> \x1b[36m${parsed.uri}\x1b[39m`);
|
||||
} else {
|
||||
console.log(` ├ ❌ asURI -> \x1b[36m${parsed.uri}\x1b[39m (expected \x1b[33m${item.uri}\x1b[39m)`);
|
||||
}
|
||||
if (parsed.url === item.url) {
|
||||
console.log(` └ ✅ asURL -> \x1b[36m${parsed.url}\x1b[39m`);
|
||||
} else {
|
||||
console.log(` └ ❌ asURL -> \x1b[36m${parsed.url}\x1b[39m (expected \x1b[33m${item.url}\x1b[39m)`);
|
||||
}
|
||||
} else {
|
||||
console.log(`❌─[ \x1b[36m${item[key]}\x1b[39m ]`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Loading…
Reference in New Issue