Fix Google Play Music and Spotify
This commit is contained in:
parent
688ec0f2f9
commit
6c29d50f1e
21 changed files with 566 additions and 559 deletions
|
@ -1,19 +0,0 @@
|
|||
image: node:6-slim
|
||||
services:
|
||||
- mongo
|
||||
|
||||
before_script:
|
||||
- npm install
|
||||
|
||||
test:
|
||||
stage: test
|
||||
script:
|
||||
- npm test
|
||||
tags:
|
||||
- docker
|
||||
|
||||
cache:
|
||||
key: "$CI_BUILD_REF_NAME"
|
||||
untracked: true
|
||||
paths:
|
||||
- node_modules
|
30
docker-compose.yml
Normal file
30
docker-compose.yml
Normal file
|
@ -0,0 +1,30 @@
|
|||
version: "2"
|
||||
|
||||
services:
|
||||
app:
|
||||
build: ./
|
||||
environment:
|
||||
DEBUG: "match.audio*"
|
||||
VUE_ENV: server
|
||||
DATABASE_URL:
|
||||
GOOGLE_EMAIL:
|
||||
GOOGLE_PASSWORD:
|
||||
XBOX_CLIENT_ID:
|
||||
XBOX_CLIENT_SECRET:
|
||||
YOUTUBE_KEY:
|
||||
SPOTIFY_CLIENT_ID:
|
||||
SPOTIFY_CLIENT_SECRET:
|
||||
volumes:
|
||||
- ./:/app:cached
|
||||
ports:
|
||||
- "3000:3000"
|
||||
command: yarn run watch-server
|
||||
database:
|
||||
image: "postgres:9.6"
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
POSTGRES_PASSWORD: "password"
|
||||
POSTGRES_USER: "matchaudio"
|
||||
POSTGRES_DB: "matchaudio"
|
||||
|
|
@ -13,7 +13,8 @@ fs.readdirSync(path.join(__dirname, 'services')).forEach(function(file) {
|
|||
export default function* (url) {
|
||||
let matchedService;
|
||||
for (let service of services) {
|
||||
matchedService = yield service.match(url);
|
||||
console.log(service)
|
||||
matchedService = service.match(url);
|
||||
if (matchedService) {
|
||||
const result = yield service.parseUrl(url);
|
||||
return yield service.lookupId(result.id, result.type);
|
||||
|
|
|
@ -1,86 +1,110 @@
|
|||
import { parse } from 'url';
|
||||
import request from 'superagent';
|
||||
import 'superagent-bluebird-promise';
|
||||
import { match as urlMatch } from './url';
|
||||
|
||||
export let id = 'deezer';
|
||||
import urlMatch from './url';
|
||||
|
||||
const apiRoot = 'https://api.deezer.com';
|
||||
|
||||
export const match = urlMatch;
|
||||
|
||||
export function parseUrl(url) {
|
||||
let matches = parse(url).path.match(/\/(album|track)[\/]+([^\/]+)/);
|
||||
const matches = parse(url).path.match(/\/(album|track)[/]+([^/]+)/);
|
||||
|
||||
if (matches && matches[2]) {
|
||||
return module.exports.lookupId(matches[2], matches[1]);
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
};
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
function exactMatch(needle, haystack, type, various) {
|
||||
// try to find exact match
|
||||
return haystack.find((entry) => {
|
||||
if (!entry[type] || (various && (entry.artist.name !== 'Various' || entry.artist.name !== 'Various Artists'))) {
|
||||
return false;
|
||||
}
|
||||
const title = entry[type].title;
|
||||
if (title) {
|
||||
return entry;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function looseMatch(needle, haystack, type, various) {
|
||||
// try to find exact match
|
||||
return haystack.find((entry) => {
|
||||
if (!entry[type] || (various && (entry.artist.name !== 'Various' || entry.artist.name !== 'Various Artists'))) {
|
||||
return false;
|
||||
}
|
||||
const name = entry[type].title || entry[type].name;
|
||||
if (name.indexOf(needle) >= 0) {
|
||||
return entry[type];
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function* lookupId(id, type) {
|
||||
let path = '/' + type + '/' + id;
|
||||
const path = `/${type}/${id}`;
|
||||
|
||||
let {body} = yield request.get(apiRoot + path).promise();
|
||||
const { body } = yield request.get(apiRoot + path).promise();
|
||||
if (!body || body.error) {
|
||||
let error = new Error('Not Found');
|
||||
const error = new Error('Not Found');
|
||||
error.status = 404;
|
||||
return Promise.reject(error);
|
||||
}
|
||||
let item = body;
|
||||
let coverUrl = item.cover || item.album.cover;
|
||||
const item = body;
|
||||
const coverUrl = item.cover || item.album.cover;
|
||||
let cover = 'test';
|
||||
// nasty hacks for superagent-bluebird-promise
|
||||
try {
|
||||
cover = yield request.get(coverUrl).redirects(0);
|
||||
} catch(err) {
|
||||
} catch (err) {
|
||||
cover = err.originalError.response;
|
||||
}
|
||||
let artwork = {
|
||||
const artwork = {
|
||||
small: cover.headers.location.replace('120x120', '200x200'),
|
||||
large: cover.headers.location.replace('120x120', '800x800')
|
||||
large: cover.headers.location.replace('120x120', '800x800'),
|
||||
};
|
||||
if (type === 'album') {
|
||||
return Promise.resolve({
|
||||
service: 'deezer',
|
||||
type: type,
|
||||
type,
|
||||
id: item.id,
|
||||
name: item.title,
|
||||
streamUrl: item.link,
|
||||
purchaseUrl: null,
|
||||
artwork: artwork,
|
||||
artwork,
|
||||
artist: {
|
||||
name: item.artist.name
|
||||
}
|
||||
name: item.artist.name,
|
||||
},
|
||||
});
|
||||
} else if (type === 'track') {
|
||||
return Promise.resolve({
|
||||
service: 'deezer',
|
||||
type: type,
|
||||
type,
|
||||
id: item.id,
|
||||
name: item.title,
|
||||
streamUrl: item.album.link,
|
||||
purchaseUrl: null,
|
||||
artwork: artwork,
|
||||
artwork,
|
||||
artist: {
|
||||
name: item.artist.name
|
||||
name: item.artist.name,
|
||||
},
|
||||
album: {
|
||||
name: item.album.title
|
||||
}
|
||||
name: item.album.title,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return Promise.reject(new Error());
|
||||
}
|
||||
};
|
||||
return Promise.reject(new Error());
|
||||
}
|
||||
|
||||
export function* search(data, original={}) {
|
||||
let cleanParam = function(str) {
|
||||
return str.replace(/[\:\?\&]+/, '');
|
||||
};
|
||||
let query, album;
|
||||
let {type} = data;
|
||||
export function* search(data, original = {}) {
|
||||
function cleanParam(str) {
|
||||
return str.replace(/[:?&]+/, '');
|
||||
}
|
||||
let query;
|
||||
let album;
|
||||
const { type } = data;
|
||||
|
||||
const various = data.artist.name === 'Various Artists' || data.artist.name === 'Various';
|
||||
|
||||
|
@ -89,65 +113,40 @@ export function* search(data, original={}) {
|
|||
if (various) {
|
||||
query = cleanParam(data.name);
|
||||
} else {
|
||||
query = cleanParam(data.artist.name) + ' ' + cleanParam(data.name);
|
||||
query = `${cleanParam(data.artist.name)} ${cleanParam(data.name)}`;
|
||||
}
|
||||
album = data.name;
|
||||
} else if (type === 'track') {
|
||||
query = cleanParam(data.artist.name) + ' ' + cleanParam(data.albumName) + ' ' + cleanParam(data.name);
|
||||
query = `${cleanParam(data.artist.name)} ${cleanParam(data.albumName)} ${cleanParam(data.name)}`;
|
||||
album = data.albumName;
|
||||
}
|
||||
|
||||
var path = '/search/' + type + '?q=' + encodeURIComponent(query);
|
||||
const path = `/search/${type}?q=${encodeURIComponent(query)}`;
|
||||
|
||||
let response = yield request.get(apiRoot + path);
|
||||
const response = yield request.get(apiRoot + path);
|
||||
|
||||
const name = original.name || data.name;
|
||||
|
||||
if (response.body.data.length > 0) {
|
||||
let match;
|
||||
if (!(match = exactMatch(name, response.body.data, data.type, various))) {
|
||||
let match = exactMatch(name, response.body.data, data.type, various);
|
||||
if (!match) {
|
||||
match = looseMatch(name, response.body.data, data.type, various);
|
||||
}
|
||||
|
||||
return yield module.exports.lookupId(response.body.data[0].id, type);
|
||||
} else {
|
||||
var matches = album.match(/^[^\(\[]+/);
|
||||
if (matches && matches[0] && matches[0] !== album) {
|
||||
var cleanedData = JSON.parse(JSON.stringify(data));
|
||||
if (type === 'album') {
|
||||
cleanedData.name = matches[0].trim();
|
||||
} else if (type === 'track') {
|
||||
cleanedData.albumName = matches[0].trim();
|
||||
}
|
||||
return yield module.exports.search(cleanedData, data);
|
||||
} else {
|
||||
return Promise.resolve({service: 'deezer'});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function exactMatch(needle, haystack, type, various) {
|
||||
// try to find exact match
|
||||
return haystack.find(function(entry) {
|
||||
if (!entry[type] || (various && (entry.artist.name !== 'Various' || entry.artist.name !== 'Various Artists'))) {
|
||||
return false;
|
||||
const matches = album.match(/^[^([]+/);
|
||||
if (matches && matches[0] && matches[0] !== album) {
|
||||
const cleanedData = JSON.parse(JSON.stringify(data));
|
||||
if (type === 'album') {
|
||||
cleanedData.name = matches[0].trim();
|
||||
} else if (type === 'track') {
|
||||
cleanedData.albumName = matches[0].trim();
|
||||
}
|
||||
entry = entry[type];
|
||||
if (entry.title === needle) {
|
||||
return entry;
|
||||
}
|
||||
});
|
||||
return yield module.exports.search(cleanedData, data);
|
||||
}
|
||||
return Promise.resolve({ service: 'deezer' });
|
||||
}
|
||||
|
||||
function looseMatch(needle, haystack, type, various) {
|
||||
// try to find exact match
|
||||
return haystack.find(function(entry) {
|
||||
if (!entry[type] || (various && (entry.artist.name !== 'Various' || entry.artist.name !== 'Various Artists'))) {
|
||||
return false;
|
||||
}
|
||||
const name = entry[type].title || entry[type].name;
|
||||
if (name.indexOf(needle) >= 0) {
|
||||
return entry[type];
|
||||
}
|
||||
});
|
||||
}
|
||||
export const id = 'deezer';
|
||||
export const match = urlMatch;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { parse } from 'url';
|
||||
|
||||
export function* match(url) {
|
||||
export default function match(url) {
|
||||
const parsed = parse(url);
|
||||
if (!parsed.host.match(/deezer\.com$/)) {
|
||||
return false;
|
||||
}
|
||||
const matches = parsed.path.match(/\/(album|track)[\/]+([^\/]+)/);
|
||||
const matches = parsed.path.match(/\/(album|track)[/]+([^/]+)/);
|
||||
return matches.length > 1;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,21 +1,143 @@
|
|||
import { parse } from 'url';
|
||||
import bluebird from 'bluebird';
|
||||
import PlayMusic from 'playmusic';
|
||||
import { match as urlMatch } from './url';
|
||||
import debuglog from 'debug';
|
||||
import urlMatch from './url';
|
||||
|
||||
const debug = debuglog('match.audio');
|
||||
|
||||
const pm = bluebird.promisifyAll(new PlayMusic());
|
||||
|
||||
export let id = 'google';
|
||||
|
||||
if (!process.env.GOOGLE_EMAIL || !process.env.GOOGLE_PASSWORD) {
|
||||
console.warn('GOOGLE_EMAIL or GOOGLE_PASSWORD environment variables not found, deactivating Google Play Music.');
|
||||
debug('GOOGLE_EMAIL or GOOGLE_PASSWORD environment variables not found, deactivating Google Play Music.');
|
||||
}
|
||||
|
||||
let ready = pm.initAsync({email: process.env.GOOGLE_EMAIL, password: process.env.GOOGLE_PASSWORD}).catch(function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
const ready = pm.initAsync({
|
||||
email: process.env.GOOGLE_EMAIL,
|
||||
password: process.env.GOOGLE_PASSWORD })
|
||||
.catch((err) => {
|
||||
debug(err);
|
||||
});
|
||||
|
||||
export const match = urlMatch;
|
||||
export function* lookupId(id, type) {
|
||||
yield ready;
|
||||
if (type === 'album') {
|
||||
const album = yield 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 = yield 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,
|
||||
},
|
||||
};
|
||||
}
|
||||
return { service: 'google' };
|
||||
}
|
||||
|
||||
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 function* search(data, original = {}) {
|
||||
yield 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 = yield pm.searchAsync(query, 5);
|
||||
|
||||
if (!result.entries) {
|
||||
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 yield search(cleanedData, data);
|
||||
}
|
||||
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 yield lookupId(match.album.albumId, type);
|
||||
} else if (type === 'track') {
|
||||
return yield lookupId(match.track.storeId, type);
|
||||
}
|
||||
return { service: 'google' };
|
||||
}
|
||||
|
||||
export function* parseUrl(url) {
|
||||
yield ready;
|
||||
|
@ -34,131 +156,16 @@ export function* parseUrl(url) {
|
|||
}
|
||||
|
||||
if (id.length > 0) {
|
||||
return {id: id, type: type};
|
||||
} else {
|
||||
return yield search({type: type, name: album, artist: {name: artist}});
|
||||
return { id, type };
|
||||
}
|
||||
} else if(path) {
|
||||
return yield 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 yield lookupId(matches[1], type);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export function* lookupId(id, type) {
|
||||
yield ready;
|
||||
if (type === 'album') {
|
||||
const album = yield 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 = yield 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
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export function* search(data, original={}) {
|
||||
yield ready;
|
||||
let query, 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;
|
||||
}
|
||||
|
||||
let result = yield pm.searchAsync(query, 5)
|
||||
|
||||
if (!result.entries) {
|
||||
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 yield search(cleanedData, data);
|
||||
} else {
|
||||
return {service: 'google'};
|
||||
}
|
||||
}
|
||||
|
||||
const name = original.name || data.name;
|
||||
|
||||
let match;
|
||||
if (!(match = exactMatch(name, result.entries, data.type))) {
|
||||
match = looseMatch(name, result.entries, data.type);
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
return {service: 'google'};
|
||||
} else {
|
||||
if (type === 'album') {
|
||||
return yield lookupId(match.album.albumId, type);
|
||||
} else if (type === 'track') {
|
||||
return yield lookupId(match.track.storeId, type);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function exactMatch(needle, haystack, type) {
|
||||
// try to find exact match
|
||||
return haystack.find(function(entry) {
|
||||
if (!entry[type]) {
|
||||
return false;
|
||||
}
|
||||
entry = entry[type];
|
||||
if (entry.title === needle) {
|
||||
return entry;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function looseMatch(needle, haystack, type) {
|
||||
// try to find exact match
|
||||
return haystack.find(function(entry) {
|
||||
if (!entry[type]) {
|
||||
return false;
|
||||
}
|
||||
const name = entry[type].title || entry[type].name;
|
||||
if (name.indexOf(needle) >= 0) {
|
||||
return entry[type];
|
||||
}
|
||||
});
|
||||
}
|
||||
export const match = urlMatch;
|
||||
export const id = 'google';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { parse } from 'url';
|
||||
|
||||
export function* match(url) {
|
||||
var parsed = parse(url.replace(/\+/g, "%20"));
|
||||
export default function match(url) {
|
||||
const parsed = parse(url.replace(/\+/g, '%20'));
|
||||
if (!parsed.host.match(/play\.google\.com$/)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ export function* match(url) {
|
|||
const hash = parsed.hash;
|
||||
|
||||
if (hash) {
|
||||
const parts = hash.split("/");
|
||||
const parts = hash.split('/');
|
||||
const id = parts[2];
|
||||
const artist = parts[3];
|
||||
|
||||
|
@ -19,11 +19,11 @@ export function* match(url) {
|
|||
} else if (artist.length > 0) {
|
||||
return true;
|
||||
}
|
||||
} else if(path) {
|
||||
} else if (path) {
|
||||
const matches = path.match(/\/music\/m\/([\w]+)/);
|
||||
if (matches[1]) {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -2,17 +2,13 @@ import { parse } from 'url';
|
|||
import querystring from 'querystring';
|
||||
import request from 'superagent';
|
||||
import 'superagent-bluebird-promise';
|
||||
import { match as urlMatch } from './url';
|
||||
|
||||
export let id = 'itunes';
|
||||
import urlMatch from './url';
|
||||
|
||||
const apiRoot = 'https://itunes.apple.com';
|
||||
|
||||
export const match = urlMatch;
|
||||
|
||||
export function* parseUrl(url) {
|
||||
const parsed = parse(url);
|
||||
const matches = parsed.path.match(/[\/]?([\/]?[a-z]{2}?)?[\/]+album[\/]+([^\/]+)[\/]+([^\?]+)/);
|
||||
const matches = parsed.path.match(/[/]?([/]?[a-z]{2}?)?[/]+album[/]+([^/]+)[/]+([^?]+)/);
|
||||
const query = querystring.parse(parsed.query);
|
||||
|
||||
if (matches) {
|
||||
|
@ -23,20 +19,21 @@ export function* parseUrl(url) {
|
|||
id = query.i;
|
||||
}
|
||||
return yield module.exports.lookupId(id, type, matches[1] || 'us');
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
};
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
export function* lookupId(id, type, cc) {
|
||||
if (String(id).match(/^[a-z]{2}/)) {
|
||||
cc = id.substr(0, 2);
|
||||
id = id.substr(2);
|
||||
export function* lookupId(possibleId, type, countrycode) {
|
||||
let cc = countrycode;
|
||||
let id = possibleId;
|
||||
if (String(possibleId).match(/^[a-z]{2}/)) {
|
||||
cc = possibleId.substr(0, 2);
|
||||
id = possibleId.substr(2);
|
||||
}
|
||||
|
||||
let path = '/lookup?id=' + id;
|
||||
let path = `/lookup?id=${id}`;
|
||||
if (cc) {
|
||||
path = '/' + cc + path;
|
||||
path = `/${cc}${path}`;
|
||||
}
|
||||
|
||||
const response = yield request.get(apiRoot + path);
|
||||
|
@ -49,54 +46,56 @@ export function* lookupId(id, type, cc) {
|
|||
} else {
|
||||
result = result.results[0];
|
||||
|
||||
let item = {
|
||||
const item = {
|
||||
service: 'itunes',
|
||||
type: type,
|
||||
type,
|
||||
id: cc + id,
|
||||
name: result.trackName ? result.trackName : result.collectionName,
|
||||
streamUrl: null,
|
||||
purchaseUrl: result.collectionViewUrl,
|
||||
artwork: {
|
||||
small: 'https://match.audio/itunes/' + result.artworkUrl100.replace('100x100', '200x200').replace('http://', ''),
|
||||
large: 'https://match.audio/itunes/' + result.artworkUrl100.replace('100x100', '600x600').replace('http://', '')
|
||||
small: `https://match.audio/itunes/${result.artworkUrl100.replace('100x100', '200x200').replace('http://', '')}`,
|
||||
large: `https://match.audio/itunes/${result.artworkUrl100.replace('100x100', '600x600').replace('http://', '')}`,
|
||||
},
|
||||
artist: {
|
||||
name: result.artistName
|
||||
}
|
||||
name: result.artistName,
|
||||
},
|
||||
};
|
||||
|
||||
if (type === 'track') {
|
||||
item.album = {
|
||||
name: result.collectionName
|
||||
name: result.collectionName,
|
||||
};
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function* search(data) {
|
||||
const markets = ['us', 'gb', 'jp', 'br', 'de', 'es'];
|
||||
let query, album, entity;
|
||||
let query;
|
||||
let album;
|
||||
let entity;
|
||||
const type = data.type;
|
||||
|
||||
if (type === 'album') {
|
||||
query = data.artist.name + ' ' + data.name;
|
||||
query = `${data.artist.name} ${data.name}`;
|
||||
album = data.name;
|
||||
entity = 'album';
|
||||
} else if (type === 'track') {
|
||||
query = data.artist.name + ' ' + data.albumName + ' ' + data.name;
|
||||
query = `${data.artist.name} ${data.albumName} ${data.name}`;
|
||||
album = data.albumName;
|
||||
entity = 'musicTrack';
|
||||
}
|
||||
|
||||
for (let market of markets) {
|
||||
const path = '/' + market + '/search?term=' + encodeURIComponent(query) + '&media=music&entity=' + entity;
|
||||
for (const market of markets) { // eslint-disable-line
|
||||
const path = `/${market}/search?term=${encodeURIComponent(query)}&media=music&entity=${entity}`;
|
||||
const response = yield request.get(apiRoot + path);
|
||||
|
||||
let result = JSON.parse(response.text);
|
||||
if (!result.results[0]) {
|
||||
const matches = album.match(/^[^\(\[]+/);
|
||||
const matches = album.match(/^[^([]+/);
|
||||
if (matches && matches[0] && matches[0] !== album) {
|
||||
const cleanedData = JSON.parse(JSON.stringify(data));
|
||||
if (type === 'album') {
|
||||
|
@ -111,27 +110,30 @@ export function* search(data) {
|
|||
|
||||
const item = {
|
||||
service: 'itunes',
|
||||
type: type,
|
||||
id: 'us' + result.collectionId,
|
||||
type,
|
||||
id: `us${result.collectionId}`,
|
||||
name: result.trackName ? result.trackName : result.collectionName,
|
||||
streamUrl: null,
|
||||
purchaseUrl: result.collectionViewUrl,
|
||||
artwork: {
|
||||
small: 'https://match.audio/itunes/' + result.artworkUrl100.replace('100x100', '200x200').replace('http://', ''),
|
||||
large: 'https://match.audio/itunes/' + result.artworkUrl100.replace('100x100', '600x600').replace('http://', '')
|
||||
small: `https://match.audio/itunes/${result.artworkUrl100.replace('100x100', '200x200').replace('http://', '')}`,
|
||||
large: `https://match.audio/itunes/${result.artworkUrl100.replace('100x100', '600x600').replace('http://', '')}`,
|
||||
},
|
||||
artist: {
|
||||
name: result.artistName
|
||||
}
|
||||
name: result.artistName,
|
||||
},
|
||||
};
|
||||
|
||||
if (type === 'track') {
|
||||
item.album = {
|
||||
name: result.collectionName
|
||||
name: result.collectionName,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return {service: 'itunes'};
|
||||
};
|
||||
return { service: 'itunes' };
|
||||
}
|
||||
|
||||
export const id = 'itunes';
|
||||
export const match = urlMatch;
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
import { parse } from 'url';
|
||||
import querystring from 'querystring';
|
||||
|
||||
export function* match(url, type) {
|
||||
export default function match(url) {
|
||||
const parsed = parse(url);
|
||||
|
||||
if (!parsed.host.match(/itunes.apple\.com$/)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const matches = parsed.path.match(/[\/]?([\/]?[a-z]{2}?)?[\/]+album[\/]+([^\/]+)[\/]+([^\?]+)/);
|
||||
const query = querystring.parse(parsed.query);
|
||||
const matches = parsed.path.match(/[/]?([/]?[a-z]{2}?)?[/]+album[/]+([^/]+)[/]+([^?]+)/);
|
||||
|
||||
return !!matches[3];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,101 +1,122 @@
|
|||
import { parse } from 'url';
|
||||
import bluebird from 'bluebird';
|
||||
import spotifyCB from 'spotify';
|
||||
import request from 'superagent';
|
||||
import 'superagent-bluebird-promise';
|
||||
const spotify = bluebird.promisifyAll(spotifyCB);
|
||||
import { match as urlMatch } from './url';
|
||||
import SpotifyWebApi from 'spotify-web-api-node';
|
||||
import urlMatch from './url';
|
||||
|
||||
export let id = "spotify";
|
||||
const spotify = new SpotifyWebApi({
|
||||
clientId: process.env.SPOTIFY_CLIENT_ID,
|
||||
clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
|
||||
redirectUri: 'https://match.audio',
|
||||
});
|
||||
|
||||
export const match = urlMatch;
|
||||
|
||||
export function* parseUrl(url) {
|
||||
var matches = parse(url).path.match(/\/(album|track)[\/]+([^\/]+)/);
|
||||
function exactMatch(needle, haystack, type) {
|
||||
// try to find exact match
|
||||
return haystack.find((entry) => {
|
||||
if (entry.type !== type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (matches && matches[2]) {
|
||||
return yield lookupId(matches[2], matches[1]);
|
||||
}
|
||||
if (entry.name === needle) {
|
||||
return entry;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function looseMatch(needle, haystack, type) {
|
||||
// try to find exact match
|
||||
return haystack.find((entry) => {
|
||||
if (entry.type !== type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.name.indexOf(needle) >= 0) {
|
||||
return entry;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
export function* lookupId(id, type) {
|
||||
const data = yield spotify.lookupAsync({id: id, type: type});
|
||||
if (data.error) {
|
||||
var error = new Error("Not Found");
|
||||
error.status = 404;
|
||||
throw error;
|
||||
}
|
||||
const token = yield spotify.clientCredentialsGrant();
|
||||
spotify.setAccessToken(token.body['access_token']);
|
||||
let data = yield spotify[`get${type.charAt(0).toUpperCase()}${type.slice(1)}s`]([id]);
|
||||
|
||||
var artist = data.artists[0];
|
||||
data = data.body[`${type}s`][0];
|
||||
|
||||
if (type == "album") {
|
||||
const artist = data.artists[0];
|
||||
|
||||
if (type === 'album') {
|
||||
return {
|
||||
service: "spotify",
|
||||
type: type,
|
||||
service: 'spotify',
|
||||
type,
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
streamUrl: "https://play.spotify.com/" + type + "/" + data.id,
|
||||
streamUrl: `https://play.spotify.com/${type}/${data.id}`,
|
||||
purchaseUrl: null,
|
||||
artwork: {
|
||||
small: data.images[1].url.replace("http:", "https:"),
|
||||
large: data.images[0].url.replace("http:", "https:"),
|
||||
small: data.images[1].url.replace('http:', 'https:'),
|
||||
large: data.images[0].url.replace('http:', 'https:'),
|
||||
},
|
||||
artist: {
|
||||
name: artist.name
|
||||
}
|
||||
name: artist.name,
|
||||
},
|
||||
};
|
||||
} else if (type == "track") {
|
||||
} else if (type === 'track') {
|
||||
return {
|
||||
service: "spotify",
|
||||
type: type,
|
||||
service: 'spotify',
|
||||
type,
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
streamUrl: "https://play.spotify.com/" + type + "/" + data.id,
|
||||
streamUrl: `https://play.spotify.com/${type}/${data.id}`,
|
||||
purchaseUrl: null,
|
||||
artwork: {
|
||||
small: data.album.images[1].url.replace("http:", "https:"),
|
||||
large: data.album.images[0].url.replace("http:", "https:"),
|
||||
small: data.album.images[1].url.replace('http:', 'https:'),
|
||||
large: data.album.images[0].url.replace('http:', 'https:'),
|
||||
},
|
||||
artist: {
|
||||
name: artist.name
|
||||
name: artist.name,
|
||||
},
|
||||
album: {
|
||||
name: data.album.name
|
||||
}
|
||||
name: data.album.name,
|
||||
},
|
||||
};
|
||||
}
|
||||
return { service: 'spotify' };
|
||||
}
|
||||
|
||||
export function* search(data, original={}) {
|
||||
export function* search(data, original = {}) {
|
||||
const token = yield spotify.clientCredentialsGrant();
|
||||
spotify.setAccessToken(token.body['access_token']);
|
||||
|
||||
const markets = ['US', 'GB', 'JP', 'BR', 'DE', 'ES'];
|
||||
const cleanParam = function(str) {
|
||||
var chopChars = ['&', '[', '('];
|
||||
chopChars.forEach(function(chr) {
|
||||
function cleanParam(str) {
|
||||
const chopChars = ['&', '[', '('];
|
||||
chopChars.forEach((chr) => {
|
||||
if (data.artist.name.indexOf('&') > 0) {
|
||||
str = str.substring(0, data.artist.name.indexOf(chr));
|
||||
str = str.substring(0, data.artist.name.indexOf(chr)); // eslint-disable-line no-param-reassign,max-len
|
||||
}
|
||||
})
|
||||
return str.replace(/[\:\?]+/, "");
|
||||
});
|
||||
return str.replace(/[:?]+/, '');
|
||||
}
|
||||
let query, album;
|
||||
let query;
|
||||
const type = data.type;
|
||||
|
||||
if (type == "album") {
|
||||
query = "artist:" + cleanParam(data.artist.name) + " album:" + cleanParam(data.name);
|
||||
album = data.name;
|
||||
} else if (type == "track") {
|
||||
query = "artist:" + cleanParam(data.artist.name) + " track:" + cleanParam(data.name) + ( cleanParam(data.albumName).length > 0 ? " album:" + cleanParam(data.albumName): "");
|
||||
album = data.albumName;
|
||||
if (type === 'album') {
|
||||
query = `artist:${cleanParam(data.artist.name)} album:${cleanParam(data.name)}`;
|
||||
} else if (type === 'track') {
|
||||
query = `artist:${cleanParam(data.artist.name)} track:${cleanParam(data.name)}${cleanParam(data.albumName).length > 0 ? ` album:${cleanParam(data.albumName)}` : ''}`;
|
||||
}
|
||||
|
||||
for (let market of markets) {
|
||||
const response = yield request.get('https://api.spotify.com/v1/search?type=' + type + '&q=' + encodeURI(query) + '&market=' + market);
|
||||
const items = response.body[type + 's'].items;
|
||||
for (const market of markets) { // eslint-disable-line
|
||||
const response = yield spotify[`search${type.charAt(0).toUpperCase()}${type.slice(1)}s`](query, { market });
|
||||
|
||||
const items = response.body[`${type}s`].items;
|
||||
|
||||
const name = original.name || data.name;
|
||||
|
||||
let match;
|
||||
if (!(match = exactMatch(name, items, type))) {
|
||||
let match = exactMatch(name, items, type);
|
||||
if (!match) {
|
||||
match = looseMatch(name, items, type);
|
||||
}
|
||||
|
||||
|
@ -107,31 +128,17 @@ export function* search(data, original={}) {
|
|||
}
|
||||
}
|
||||
}
|
||||
return {service: "spotify"};
|
||||
return { service: 'spotify' };
|
||||
}
|
||||
|
||||
function exactMatch(needle, haystack, type) {
|
||||
// try to find exact match
|
||||
return haystack.find(function(entry) {
|
||||
if (entry.type !== type) {
|
||||
return false;
|
||||
}
|
||||
export function* parseUrl(url) {
|
||||
const matches = parse(url).path.match(/\/(album|track)[/]+([^/]+)/);
|
||||
|
||||
if (entry.name === needle) {
|
||||
return entry;
|
||||
}
|
||||
});
|
||||
if (matches && matches[2]) {
|
||||
return yield lookupId(matches[2], matches[1]);
|
||||
}
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
function looseMatch(needle, haystack, type) {
|
||||
// try to find exact match
|
||||
return haystack.find(function(entry) {
|
||||
if (entry.type !== type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.name.indexOf(needle) >= 0) {
|
||||
return entry
|
||||
}
|
||||
});
|
||||
}
|
||||
export const id = 'spotify';
|
||||
export const match = urlMatch;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { parse } from 'url';
|
||||
|
||||
export function* match(url, type) {
|
||||
export default function match(url) {
|
||||
const parsed = parse(url);
|
||||
if (!parsed.host.match(/spotify\.com$/)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const matches = parse(url).path.match(/\/(album|track)[\/]+([^\/]+)/);
|
||||
const matches = parse(url).path.match(/\/(album|track)[/]+([^/]+)/);
|
||||
return matches && !!matches[2];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,21 +1,18 @@
|
|||
import { parse } from 'url';
|
||||
import querystring from 'querystring';
|
||||
import request from 'superagent';
|
||||
import 'superagent-bluebird-promise';
|
||||
import { match as urlMatch } from './url';
|
||||
|
||||
import debuglog from 'debug';
|
||||
import urlMatch from './url';
|
||||
|
||||
const debug = debuglog('match.audio:xbox');
|
||||
|
||||
export let id = 'xbox';
|
||||
|
||||
if (!process.env.XBOX_CLIENT_ID || !process.env.XBOX_CLIENT_SECRET) {
|
||||
console.warn('XBOX_CLIENT_ID and XBOX_CLIENT_SECRET environment variables not found, deactivating Xbox Music.');
|
||||
debug('XBOX_CLIENT_ID and XBOX_CLIENT_SECRET environment variables not found, deactivating Xbox Music.');
|
||||
}
|
||||
|
||||
const credentials = {
|
||||
clientId: process.env.XBOX_CLIENT_ID,
|
||||
clientSecret: process.env.XBOX_CLIENT_SECRET
|
||||
clientSecret: process.env.XBOX_CLIENT_SECRET,
|
||||
};
|
||||
|
||||
const apiRoot = 'https://music.xboxlive.com/1/content';
|
||||
|
@ -28,10 +25,14 @@ function* getAccessToken() {
|
|||
const data = {
|
||||
client_id: credentials.clientId,
|
||||
client_secret: credentials.clientSecret,
|
||||
scope: scope,
|
||||
grant_type: grantType
|
||||
scope,
|
||||
grant_type: grantType,
|
||||
};
|
||||
const result = yield request.post(authUrl).timeout(10000).send(data).set('Content-type', 'application/x-www-form-urlencoded').promise();
|
||||
const result = yield request.post(authUrl)
|
||||
.timeout(10000)
|
||||
.send(data)
|
||||
.set('Content-type', 'application/x-www-form-urlencoded')
|
||||
.promise();
|
||||
return result.body.access_token;
|
||||
}
|
||||
|
||||
|
@ -44,41 +45,27 @@ function formatResponse(match) {
|
|||
streamUrl: match.Link,
|
||||
purchaseUrl: null,
|
||||
artwork: {
|
||||
small: match.ImageUrl.replace('http://', 'https://') + '&w=250&h=250',
|
||||
large: match.ImageUrl.replace('http://', 'https://') + '&w=500&h=500'
|
||||
small: `${match.ImageUrl.replace('http://', 'https://')}&w=250&h=250`,
|
||||
large: `${match.ImageUrl.replace('http://', 'https://')}&w=500&h=500`,
|
||||
},
|
||||
artist: {
|
||||
name: match.Artists[0].Artist.Name
|
||||
}
|
||||
name: match.Artists[0].Artist.Name,
|
||||
},
|
||||
};
|
||||
if (match.Album) {
|
||||
item.album = {name: match.Album.Name}
|
||||
item.album = { name: match.Album.Name };
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
function* apiCall(path) {
|
||||
const access_token = yield getAccessToken();
|
||||
return request.get(apiRoot + path).timeout(10000).set('Authorization', 'Bearer ' + access_token).promise();
|
||||
}
|
||||
|
||||
export const match = urlMatch;
|
||||
|
||||
export function* parseUrl(url) {
|
||||
const parsed = parse(url);
|
||||
const parts = parsed.path.split('/');
|
||||
const type = parts[1];
|
||||
const idMatches = parts[4].match(/bz.[\w\-]+/);
|
||||
const id = idMatches[0].replace('bz.', 'music.');
|
||||
if (!id) {
|
||||
return false;
|
||||
}
|
||||
return yield lookupId(id, type);
|
||||
const accessToken = yield getAccessToken();
|
||||
return request.get(apiRoot + path).timeout(10000).set('Authorization', `Bearer ${accessToken}`).promise();
|
||||
}
|
||||
|
||||
export function* lookupId(id, type) {
|
||||
const path = '/' + id + '/lookup';
|
||||
const apiType = type.charAt(0).toUpperCase() + type.substr(1) + 's';
|
||||
const path = `/${id}/lookup`;
|
||||
const apiType = `${type.charAt(0).toUpperCase() + type.substr(1)}s`;
|
||||
try {
|
||||
const result = yield apiCall(path);
|
||||
return formatResponse(result.body[apiType].Items[0]);
|
||||
|
@ -86,31 +73,61 @@ export function* lookupId(id, type) {
|
|||
if (e.status !== 404) {
|
||||
debug(e.body);
|
||||
}
|
||||
return {service: 'xbox'};
|
||||
return { service: 'xbox' };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function* parseUrl(url) {
|
||||
const parsed = parse(url);
|
||||
const parts = parsed.path.split('/');
|
||||
const type = parts[1];
|
||||
const idMatches = parts[4].match(/bz.[\w-]+/);
|
||||
const id = idMatches[0].replace('bz.', 'music.');
|
||||
if (!id) {
|
||||
return false;
|
||||
}
|
||||
return yield lookupId(id, type);
|
||||
}
|
||||
|
||||
function exactMatch(item, artist, haystack) {
|
||||
// try to find exact match
|
||||
return haystack.find((entry) => {
|
||||
if (entry.Name === item && entry.Artists[0].Artist.Name === artist) {
|
||||
return entry;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function looseMatch(item, artist, haystack) {
|
||||
// try to find exact match
|
||||
return haystack.find((entry) => {
|
||||
if (entry.Name.indexOf(item) >= 0 && entry.Artists[0].Artist.Name.indexOf(artist) >= 0) {
|
||||
return entry;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
export function* search(data) {
|
||||
var cleanParam = function(str) {
|
||||
return str.replace(/[\:\?\&\(\)\[\]]+/g, '');
|
||||
function cleanParam(str) {
|
||||
return str.replace(/[:?&()[\]]+/g, '');
|
||||
}
|
||||
let query, album;
|
||||
let query;
|
||||
const type = data.type;
|
||||
|
||||
if (type == 'album') {
|
||||
query = cleanParam(data.artist.name.substring(0, data.artist.name.indexOf('&'))) + ' ' + cleanParam(data.name);
|
||||
album = data.name;
|
||||
} else if (type == 'track') {
|
||||
query = cleanParam(data.artist.name.substring(0, data.artist.name.indexOf('&'))) + ' ' + cleanParam(data.name);
|
||||
album = data.albumName
|
||||
if (type === 'album') {
|
||||
query = `${cleanParam(data.artist.name.substring(0, data.artist.name.indexOf('&')))} ${cleanParam(data.name)}`;
|
||||
} else if (type === 'track') {
|
||||
query = `${cleanParam(data.artist.name.substring(0, data.artist.name.indexOf('&')))} ${cleanParam(data.name)}`;
|
||||
}
|
||||
|
||||
const name = data.name;
|
||||
const path = '/music/search?q=' + encodeURIComponent(query.trim()) + '&filters=' + type + 's';
|
||||
const path = `/music/search?q=${encodeURIComponent(query.trim())}&filters=${type}s`;
|
||||
try {
|
||||
const result = yield apiCall(path);
|
||||
|
||||
const apiType = type.charAt(0).toUpperCase() + type.substr(1) + 's';
|
||||
const apiType = `${type.charAt(0).toUpperCase() + type.substr(1)}s`;
|
||||
|
||||
let match = exactMatch(name, data.artist.name, result.body[apiType].Items, type);
|
||||
if (!match) {
|
||||
|
@ -121,24 +138,10 @@ export function* search(data) {
|
|||
return formatResponse(match);
|
||||
}
|
||||
} catch (err) {
|
||||
return {service: 'xbox'};
|
||||
return { service: 'xbox' };
|
||||
}
|
||||
};
|
||||
|
||||
function exactMatch(item, artist, haystack, type) {
|
||||
// try to find exact match
|
||||
return haystack.find(function(entry) {
|
||||
if (entry.Name === item && entry.Artists[0].Artist.Name === artist) {
|
||||
return entry;
|
||||
}
|
||||
});
|
||||
return { service: 'xbox' };
|
||||
}
|
||||
|
||||
function looseMatch(item, artist, haystack, type) {
|
||||
// try to find exact match
|
||||
return haystack.find(function(entry) {
|
||||
if (entry.Name.indexOf(item) >= 0 && entry.Artists[0].Artist.Name.indexOf(artist) >= 0) {
|
||||
return entry;
|
||||
}
|
||||
});
|
||||
}
|
||||
export const id = 'xbox';
|
||||
export const match = urlMatch;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { parse } from 'url';
|
||||
|
||||
export function* match(url, type) {
|
||||
export default function match(url) {
|
||||
const parsed = parse(url);
|
||||
if (!parsed.host.match(/music.microsoft.com$/)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parts = parsed.path.split('/');
|
||||
return (parts[1] == 'album' || parts[1] == 'track') && parts[4];
|
||||
};
|
||||
return (parts[1] === 'album' || parts[1] === 'track') && parts[4];
|
||||
}
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
import { parse } from 'url';
|
||||
import querystring from 'querystring';
|
||||
import request from 'superagent';
|
||||
import 'superagent-bluebird-promise';
|
||||
|
||||
const credentials = {
|
||||
key: process.env.YOUTUBE_KEY,
|
||||
};
|
||||
|
||||
const apiRoot = 'https://www.googleapis.com/freebase/v1/topic';
|
||||
|
||||
export function* get(topic) {
|
||||
const result = yield request.get(apiRoot + topic + '?key=' + credentials.key).promise();
|
||||
return result.body;
|
||||
}
|
|
@ -1,18 +1,15 @@
|
|||
import { parse } from 'url';
|
||||
import querystring from 'querystring';
|
||||
import moment from 'moment';
|
||||
import request from 'superagent';
|
||||
import Nodebrainz from 'nodebrainz';
|
||||
import 'superagent-bluebird-promise';
|
||||
import { match as urlMatch } from './url';
|
||||
import freebase from './freebase';
|
||||
|
||||
import debuglog from 'debug';
|
||||
import urlMatch from './url';
|
||||
|
||||
const debug = debuglog('match.audio:youtube');
|
||||
|
||||
module.exports.id = 'youtube';
|
||||
|
||||
if (!process.env.YOUTUBE_KEY) {
|
||||
console.warn('YOUTUBE_KEY environment variable not found, deactivating Youtube.');
|
||||
debug('YOUTUBE_KEY environment variable not found, deactivating Youtube.');
|
||||
}
|
||||
|
||||
const credentials = {
|
||||
|
@ -21,7 +18,77 @@ const credentials = {
|
|||
|
||||
const apiRoot = 'https://www.googleapis.com/youtube/v3';
|
||||
|
||||
export const match = urlMatch;
|
||||
const nodebrainz = new Nodebrainz({
|
||||
userAgent: 'match-audio ( https://match.audio )',
|
||||
defaultLimit: 10,
|
||||
retryOn: true,
|
||||
retryDelay: 3000,
|
||||
retryCount: 10,
|
||||
});
|
||||
|
||||
export function* lookupId(id) {
|
||||
const path = `/videos?part=snippet%2CtopicDetails%2CcontentDetails&id=${id}&key=${credentials.key}`;
|
||||
try {
|
||||
const result = yield request.get(apiRoot + path).promise();
|
||||
const item = result.body.items[0];
|
||||
|
||||
nodebrainz.luceneSearch('release', { query: item.snippet.title }, (err, response) => {
|
||||
response.releases.forEach((release) => {
|
||||
//console.log(release);
|
||||
});
|
||||
});
|
||||
|
||||
const match = {
|
||||
id,
|
||||
service: 'youtube',
|
||||
name: item.snippet.title,
|
||||
type: 'track',
|
||||
album: { name: '' },
|
||||
streamUrl: `https://youtu.be/${id}`,
|
||||
purchaseUrl: null,
|
||||
artwork: {
|
||||
small: item.snippet.thumbnails.medium.url,
|
||||
large: item.snippet.thumbnails.high.url,
|
||||
},
|
||||
};
|
||||
|
||||
return match;
|
||||
} catch (err) {
|
||||
debug(err);
|
||||
return { service: 'youtube' };
|
||||
}
|
||||
}
|
||||
|
||||
export function* search(data) {
|
||||
let query;
|
||||
const type = data.type;
|
||||
|
||||
if (type === 'album') {
|
||||
query = `${data.artist.name} ${data.name}`;
|
||||
} else if (type === 'track') {
|
||||
query = `${data.artist.name} ${data.name}`;
|
||||
}
|
||||
|
||||
const path = `/search?part=snippet&q=${encodeURIComponent(query)}&type=video&videoCaption=any&videoCategoryId=10&key=${credentials.key}`;
|
||||
const result = yield request.get(apiRoot + path).promise();
|
||||
const item = result.body.items[0];
|
||||
|
||||
if (!item) {
|
||||
return { service: 'youtube', type: 'video' };
|
||||
}
|
||||
return {
|
||||
service: 'youtube',
|
||||
type: 'video',
|
||||
id: item.id.videoId,
|
||||
name: item.snippet.title,
|
||||
streamUrl: `https://www.youtube.com/watch?v=${item.id.videoId}`,
|
||||
purchaseUrl: null,
|
||||
artwork: {
|
||||
small: item.snippet.thumbnails.medium.url,
|
||||
large: item.snippet.thumbnails.high.url,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function parseUrl(url) {
|
||||
const parsed = parse(url);
|
||||
|
@ -37,93 +104,5 @@ export function parseUrl(url) {
|
|||
return lookupId(id, 'track');
|
||||
}
|
||||
|
||||
export function* lookupId(id, type) {
|
||||
|
||||
const path = '/videos?part=snippet%2CtopicDetails%2CcontentDetails&id=' + id + '&key=' + credentials.key;
|
||||
try {
|
||||
const result = yield request.get(apiRoot + path).promise();
|
||||
const item = result.body.items[0];
|
||||
if (!item.topicDetails.topicIds) {
|
||||
return {service: 'youtube'};
|
||||
}
|
||||
|
||||
const match = {
|
||||
id: id,
|
||||
service: 'youtube',
|
||||
name: item.snippet.title,
|
||||
type: 'track',
|
||||
album: {name: ''},
|
||||
streamUrl: 'https://youtu.be/' + id,
|
||||
purchaseUrl: null,
|
||||
artwork: {
|
||||
small: item.snippet.thumbnails.medium.url,
|
||||
large: item.snippet.thumbnails.high.url,
|
||||
}
|
||||
};
|
||||
|
||||
for (let topic of yield freebase.get(topicId)) {
|
||||
const musicalArtist = topic.property['/type/object/type'].values.some((value) => {
|
||||
return value.text == 'Musical Artist';
|
||||
});
|
||||
|
||||
const musicalRecording = topic.property['/type/object/type'].values.some(function(value) {
|
||||
return value.text == 'Musical Recording';
|
||||
});
|
||||
|
||||
const musicalAlbum = topic.property['/type/object/type'].values.some(function(value) {
|
||||
return value.text == 'Musical Album';
|
||||
})
|
||||
|
||||
if (musicalArtist) {
|
||||
match.artist = {name: topic.property['/type/object/name'].values[0].text};
|
||||
} else if (musicalRecording) {
|
||||
match.name = topic.property['/type/object/name'].values[0].text;
|
||||
if (topic.property['/music/recording/releases']) {
|
||||
match.type = 'album';
|
||||
match.albumName = topic.property['/music/recording/releases'].values[0].text;
|
||||
}
|
||||
} else if (musicalAlbum) {
|
||||
match.name = topic.property['/type/object/name'].values[0].text;
|
||||
match.type = 'album';
|
||||
}
|
||||
}
|
||||
return match;
|
||||
} catch (e) {
|
||||
debug(e.body);
|
||||
return {'service': 'youtube'};
|
||||
}
|
||||
};
|
||||
|
||||
export function* search(data) {
|
||||
let query, 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.name;
|
||||
album = data.albumName
|
||||
}
|
||||
|
||||
const path = '/search?part=snippet&q=' + encodeURIComponent(query) + '&type=video&videoCaption=any&videoCategoryId=10&key=' + credentials.key;
|
||||
const result = yield request.get(apiRoot + path).promise();
|
||||
const item = result.body.items[0];
|
||||
|
||||
if (!item) {
|
||||
return {service:'youtube', type: 'video'};
|
||||
} else {
|
||||
return {
|
||||
service: 'youtube',
|
||||
type: 'video',
|
||||
id: item.id.videoId,
|
||||
name: item.snippet.title,
|
||||
streamUrl: 'https://www.youtube.com/watch?v=' + item.id.videoId,
|
||||
purchaseUrl: null,
|
||||
artwork: {
|
||||
small: item.snippet.thumbnails.medium.url,
|
||||
large: item.snippet.thumbnails.high.url,
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
export const id = 'youtube';
|
||||
export const match = urlMatch;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { parse } from 'url';
|
||||
import querystring from 'querystring';
|
||||
|
||||
export function* match(url, type) {
|
||||
export default function match(url) {
|
||||
const parsed = parse(url);
|
||||
if (parsed.host.match(/youtu\.be$/)) {
|
||||
return true;
|
||||
|
@ -10,4 +10,4 @@ export function* match(url, type) {
|
|||
return !!query.v;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -70,11 +70,12 @@
|
|||
"koa-websocket": "^2.1.0",
|
||||
"moment": "^2.14.1",
|
||||
"node-uuid": "~1.4.2",
|
||||
"nodebrainz": "^2.1.1",
|
||||
"pg": "^6.1.0",
|
||||
"playmusic": "~2.2.1",
|
||||
"playmusic": "https://github.com/jamon/playmusic.git#37e98f39c33fc5359a8a30b8c8e422161a4be9a8",
|
||||
"raven": "^2.0.2",
|
||||
"sequelize": "^3.24.3",
|
||||
"spotify": "~0.3.0",
|
||||
"spotify-web-api-node": "^2.4.0",
|
||||
"style-loader": "^0.17.0",
|
||||
"superagent": "^2.1.0",
|
||||
"superagent-bluebird-promise": "^3.0.2",
|
||||
|
|
|
@ -21,8 +21,8 @@ describe('Deezer', function(){
|
|||
});
|
||||
|
||||
it('should find album with various artists by search', function* (){
|
||||
const result = yield deezer.search({type: 'album', artist: {name: 'Various Artists'}, name: 'The Trevor Nelson Collection'});
|
||||
result.name.should.equal('The Trevor Nelson Collection');
|
||||
const result = yield deezer.search({type: 'album', artist: {name: 'Various Artists'}, name: 'The Trevor Nelson Collection 2'});
|
||||
result.name.should.equal('The Trevor Nelson Collection 2');
|
||||
});
|
||||
|
||||
it('should find track by search', function* (){
|
||||
|
|
|
@ -29,7 +29,7 @@ describe('Xbox Music', function(){
|
|||
|
||||
describe('lookupUrl', function(){
|
||||
it('should parse regular url into album ID', function* (){
|
||||
const result = yield xbox.parseUrl('https://music.xbox.com/album/kyuss/muchas-gracias-the-best-of-kyuss/8b558d00-0100-11db-89ca-0019b92a3933');
|
||||
const result = yield xbox.parseUrl('https://music.microsoft.com/album/kyuss/muchas-gracias-the-best-of-kyuss/bz.8b558d00-0100-11db-89ca-0019b92a3933');
|
||||
result.id.should.equal('music.8D6KGX5BZ8WB');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,6 +2,13 @@ import 'should';
|
|||
import * as youtube from '../../lib/services/youtube';
|
||||
|
||||
describe('Youtube', function(){
|
||||
describe('lookup', function(){
|
||||
it('should find album by lookup', function* (){
|
||||
const result = yield youtube.lookupId('6JnGBs88sL0');
|
||||
result.name.should.equal('Nelly Furtado - Say It Right');
|
||||
});
|
||||
});
|
||||
|
||||
describe('search', function(){
|
||||
it('should find album by search', function* (){
|
||||
const result = yield youtube.search({type: 'track', artist: {name: 'Aesop Rock'}, album: {name: 'Skeconsthon'}, name: 'Zero Dark Thirty'});
|
||||
|
|
29
yarn.lock
29
yarn.lock
|
@ -3317,6 +3317,10 @@ node-uuid@~1.4.1, node-uuid@~1.4.2:
|
|||
version "1.4.8"
|
||||
resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907"
|
||||
|
||||
nodebrainz@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/nodebrainz/-/nodebrainz-2.1.1.tgz#debf0cbf69ffeaec7439a36409ed9c10404b112f"
|
||||
|
||||
nodemon@^1.10.2:
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.11.0.tgz#226c562bd2a7b13d3d7518b49ad4828a3623d06c"
|
||||
|
@ -3669,12 +3673,13 @@ pkg-up@^1.0.0:
|
|||
dependencies:
|
||||
find-up "^1.0.0"
|
||||
|
||||
playmusic@~2.2.1:
|
||||
"playmusic@https://github.com/jamon/playmusic.git#37e98f39c33fc5359a8a30b8c8e422161a4be9a8":
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/playmusic/-/playmusic-2.2.1.tgz#32c5a4f3dee6e350e61ca879d4ce779f8e801a07"
|
||||
resolved "https://github.com/jamon/playmusic.git#37e98f39c33fc5359a8a30b8c8e422161a4be9a8"
|
||||
dependencies:
|
||||
crypto-js ">= 3.1"
|
||||
node-uuid "~1.4.1"
|
||||
rsa-pem-from-mod-exp "^0.8.4"
|
||||
|
||||
pluralize@^1.2.1:
|
||||
version "1.2.1"
|
||||
|
@ -4333,6 +4338,10 @@ ripemd160@^1.0.0:
|
|||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-1.0.1.tgz#93a4bbd4942bc574b69a8fa57c71de10ecca7d6e"
|
||||
|
||||
rsa-pem-from-mod-exp@^0.8.4:
|
||||
version "0.8.4"
|
||||
resolved "https://registry.yarnpkg.com/rsa-pem-from-mod-exp/-/rsa-pem-from-mod-exp-0.8.4.tgz#362a42c6d304056d493b3f12bceabb2c6576a6d4"
|
||||
|
||||
run-async@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389"
|
||||
|
@ -4587,9 +4596,11 @@ split@^1.0.0:
|
|||
dependencies:
|
||||
through "2"
|
||||
|
||||
spotify@~0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/spotify/-/spotify-0.3.0.tgz#42b85105cfc30f174c050f2227c21a9d7edb1be4"
|
||||
spotify-web-api-node@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/spotify-web-api-node/-/spotify-web-api-node-2.4.0.tgz#948f5bcfe098e5027367361dd2b003c7e3ce4cd5"
|
||||
dependencies:
|
||||
superagent "^2.0.0"
|
||||
|
||||
sprintf-js@~1.0.2:
|
||||
version "1.0.3"
|
||||
|
@ -4714,7 +4725,7 @@ superagent-bluebird-promise@^3.0.2:
|
|||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/superagent-bluebird-promise/-/superagent-bluebird-promise-3.0.2.tgz#3562fc7f26fe07306119ca8ab9943e1571b1deec"
|
||||
|
||||
superagent@^2.1.0:
|
||||
superagent@^2.0.0, superagent@^2.1.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/superagent/-/superagent-2.3.0.tgz#703529a0714e57e123959ddefbce193b2e50d115"
|
||||
dependencies:
|
||||
|
@ -4984,7 +4995,7 @@ util@0.10.3, util@^0.10.3:
|
|||
dependencies:
|
||||
inherits "2.0.1"
|
||||
|
||||
uuid@3.0.0:
|
||||
uuid@3.0.0, uuid@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.0.tgz#6728fc0459c450d796a99c31837569bdf672d728"
|
||||
|
||||
|
@ -4992,10 +5003,6 @@ uuid@^2.0.1:
|
|||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
|
||||
|
||||
uuid@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1"
|
||||
|
||||
v8flags@^2.0.10:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue