import { parse } from 'url'; import bluebird from 'bluebird'; import PlayMusic from 'playmusic'; import debuglog from 'debug'; import urlMatch from './url.js'; const debug = debuglog('combine.fm:google'); const pm = bluebird.promisifyAll(new PlayMusic()); if (!(process.env.GOOGLE_EMAIL && process.env.GOOGLE_PASSWORD) && !(process.env.GOOGLE_ANDROID_ID && process.env.GOOGLE_MASTER_TOKEN)) { debug('Required GOOGLE_* environment variables not found, deactivating Google Play Music.'); } const creds = { androidId: process.env.GOOGLE_ANDROID_ID, masterToken: process.env.GOOGLE_MASTER_TOKEN, }; let ready = pm.initAsync(creds).catch(function(err) { debug(err); }); export async function lookupId(id, type) { await ready; try { if (type === 'album') { const album = await pm.getAlbumAsync(id, false); return { service: 'google', type: 'album', id: album.albumId, name: album.name, streamUrl: `https://play.google.com/music/m/${album.albumId}?signup_if_needed=1`, purchaseUrl: `https://play.google.com/store/music/album?id=${album.albumId}`, artwork: { small: album.albumArtRef.replace('http:', 'https:'), large: album.albumArtRef.replace('http:', 'https:'), }, artist: { name: album.artist, }, }; } else if (type === 'track') { const track = await pm.getAllAccessTrackAsync(id); return { service: 'google', type: 'track', id: track.nid, name: track.title, streamUrl: `https://play.google.com/music/m/${track.nid}?signup_if_needed=1`, purchaseUrl: `https://play.google.com/store/music/album?id=${track.albumId}`, artwork: { small: track.albumArtRef[0].url.replace('http:', 'https:'), large: track.albumArtRef[0].url.replace('http:', 'https:'), }, album: { name: track.album, }, artist: { name: track.artist, }, }; } } catch(e) { const error = new Error('Not Found'); error.status = 404; return Promise.reject(error); } const error = new Error('Not Found'); error.status = 404; return Promise.reject(error); } function exactMatch(needle, haystack, type) { // try to find exact match return haystack.find((entry) => { if (!entry[type]) { return false; } const title = entry[type].title; if (title === needle) { return entry; } return false; }); } function looseMatch(needle, haystack, type) { // try to find exact match return haystack.find((entry) => { if (!entry[type]) { return false; } const name = entry[type].title || entry[type].name; if (name.indexOf(needle) >= 0) { return entry[type]; } return false; }); } export async function search(data, original = {}, cleaned = false) { await ready; let query; let album; const type = data.type; if (type === 'album') { query = `${data.artist.name} ${data.name}`; album = data.name; } else if (type === 'track') { query = `${data.artist.name} ${data.albumName} ${data.name}`; album = data.albumName; } const result = await pm.searchAsync(query, 5); if (!result.entries) { if (cleaned) { return { service: 'google' }; } const matches = album.match(/^[^([]+/); if (matches && matches[0]) { const cleanedData = JSON.parse(JSON.stringify(data)); if (type === 'album') { cleanedData.name = data.name.match(/^[^([]+/)[0].trim(); } else if (type === 'track') { cleanedData.albumName = data.albumName.match(/^[^([]+/)[0].trim(); cleanedData.name = data.name.match(/^[^([]+/)[0].trim(); } return await search(cleanedData, data, true); } return { service: 'google' }; } const name = original.name || data.name; let match = exactMatch(name, result.entries, data.type); if (!match) { match = looseMatch(name, result.entries, data.type); } if (!match) { return { service: 'google' }; } if (type === 'album') { return await lookupId(match.album.albumId, type); } else if (type === 'track') { return await lookupId(match.track.storeId, type); } return { service: 'google' }; } export async function parseUrl(url) { await ready; const parsed = parse(url.replace(/\+/g, '%20')); const path = parsed.path; const hash = parsed.hash; if (hash) { const parts = hash.split('/'); const type = parts[1]; const id = parts[2]; const artist = decodeURIComponent(parts[3]); const album = decodeURIComponent(parts[4]); if (type !== 'album' && type !== 'track') { return false; } if (id.length > 0) { return { id, type }; } return await search({ type, name: album, artist: { name: artist } }); } else if (path) { const matches = path.match(/\/music\/m\/([\w]+)/); const type = matches[1][0] === 'T' ? 'track' : 'album'; return await lookupId(matches[1], type); } return false; } export const match = urlMatch; export const id = 'google';