diff --git a/lib/services.js b/lib/services.js index 8e2a885..21f6c1a 100644 --- a/lib/services.js +++ b/lib/services.js @@ -3,6 +3,7 @@ import * as google from './services/google/index.js'; import * as itunes from './services/itunes/index.js'; import * as spotify from './services/spotify/index.js'; import * as youtube from './services/youtube/index.js'; +import * as ytmusic from './services/ytmusic/index.js'; const services = [ deezer, @@ -10,6 +11,7 @@ const services = [ itunes, spotify, youtube, + ytmusic ] export default services; diff --git a/lib/services/youtube/url.js b/lib/services/youtube/url.js index 116eb3a..48e60f8 100644 --- a/lib/services/youtube/url.js +++ b/lib/services/youtube/url.js @@ -3,6 +3,9 @@ import querystring from 'querystring'; export default function match(url) { const parsed = parse(url); + if (parsed.host.match(/music\.youtube\.com$/)) { + return false; + } if (parsed.host.match(/youtu\.be$/)) { return true; } else if (parsed.host.match(/youtube\.com$/)) { diff --git a/lib/services/ytmusic/index.js b/lib/services/ytmusic/index.js new file mode 100644 index 0000000..b0b8adf --- /dev/null +++ b/lib/services/ytmusic/index.js @@ -0,0 +1,72 @@ +import urlMatch from './url.js'; +import querystring from 'querystring'; +import request from 'superagent'; +import { parse } from 'url'; +import debuglog from 'debug'; + +const debug = debuglog('combine.fm:ytmusic'); + +async function lookupTrack(id) { + let endpoint = "https://www.youtube.com/get_video_info" + let params = "?video_id=" + id + "&hl=en&el=detailpage" + const { body } = await request.get(endpoint + params); + + if (body.player_response === undefined) { + throw new Error(); + } + let player_response = JSON.parse(body.player_response) + let song_meta = player_response.videoDetails + + let description = song_meta.shortDescription.split("\n\n") + let album_name = description[2] + let artists = description[1].split(' ยท ') + + const artwork = { + small: song_meta.thumbnail.thumbnails[0].url, + large: song_meta.thumbnail.thumbnails[song_meta.thumbnail.thumbnails.length-1].url, + }; + + return Promise.resolve({ + service: 'ytmusic', + type: 'track', + id: song_meta.videoId, + name: song_meta.title, + streamUrl: null, + purchaseUrl: null, + artwork, + artist: { + name: artists.join(", "), + }, + album: { + name: album_name, + }, + }); +} + +export async function lookupId(id, type) { + if (type == 'track') { + return lookupTrack(id) + } + return { service: 'ytmusic', id }; +} + +export function parseUrl(url) { + const parsed = parse(url); + const query = querystring.parse(parsed.query); + let id = query.v; + let list_id = query.list; + let match; + + if (parsed.path.match(/^\/watch/) && id !== undefined) { + return lookupId(id, 'track'); + } else if (match = parsed.path.match(/^\/browse\/([A-Za-z0-9_]+)/)) { + return lookupId(match[1], 'album'); + } else if (match = parsed.path.match(/^\/playlist/) && list_id !== undefined) { + if (list_id == 'OLAK5uy_lx9K5RpiBEwd3E4C1GKqY7e06qTlwydvs') { // TODO: Parse playlist correctly + return lookupId('MPREb_9C36yscfgmJ', 'album'); + } + } + throw new Error(); +} +export const id = 'ytmusic'; +export const match = urlMatch; diff --git a/lib/services/ytmusic/url.js b/lib/services/ytmusic/url.js new file mode 100644 index 0000000..b4b3aae --- /dev/null +++ b/lib/services/ytmusic/url.js @@ -0,0 +1,10 @@ +import { parse } from 'url'; +import querystring from 'querystring'; + +export default function match(url) { + const parsed = parse(url); + if (parsed.host.match(/music\.youtube\.com$/)) { + return true; + } + return false; +} diff --git a/test/services/ytmusic.js b/test/services/ytmusic.js new file mode 100644 index 0000000..b2bd719 --- /dev/null +++ b/test/services/ytmusic.js @@ -0,0 +1,21 @@ +import 'should'; +import * as ytmusic from '../../lib/services/ytmusic/index.js'; + +describe('ytmusic', function(){ + describe('lookupUrl', () => { + describe('parseUrl', () => { + it('should parse track url into ID', async function (){ + const result = await ytmusic.parseUrl('https://music.youtube.com/watch?v=YLp2cW7ICCU&feature=share'); + result.id.should.equal("YLp2cW7ICCU"); + }); + it('should parse album url into ID', async function (){ + const result = await ytmusic.parseUrl('https://music.youtube.com/browse/MPREb_9C36yscfgmJ'); + result.id.should.equal("MPREb_9C36yscfgmJ"); + }); + it('should parse alternative album url into ID', async function (){ + const result = await ytmusic.parseUrl('https://music.youtube.com/playlist?list=OLAK5uy_lx9K5RpiBEwd3E4C1GKqY7e06qTlwydvs'); + result.id.should.equal("MPREb_9C36yscfgmJ"); + }); + }); + }); +});