Add initial Amazon Music support
This commit is contained in:
parent
bd85678303
commit
b0fcf55071
25 changed files with 248 additions and 222 deletions
19
Dockerfile.dev
Normal file
19
Dockerfile.dev
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
FROM node:8.7.0-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apk add --update git
|
||||||
|
|
||||||
|
COPY package.json package.json
|
||||||
|
COPY yarn.lock yarn.lock
|
||||||
|
|
||||||
|
RUN yarn
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN yarn run build
|
||||||
|
|
||||||
|
ENV PORT 3000
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["yarn", "start"]
|
2
Makefile
2
Makefile
|
@ -32,5 +32,5 @@ docker-compose-up: ## Start (and create) docker containers
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
|
|
||||||
.PHONY: yarn
|
.PHONY: yarn
|
||||||
yarn: ## Migrate database schema
|
yarn: ## Update yarn dependencies
|
||||||
docker-compose run --rm app yarn
|
docker-compose run --rm app yarn
|
||||||
|
|
|
@ -15,6 +15,9 @@ services:
|
||||||
YOUTUBE_KEY:
|
YOUTUBE_KEY:
|
||||||
SPOTIFY_CLIENT_ID:
|
SPOTIFY_CLIENT_ID:
|
||||||
SPOTIFY_CLIENT_SECRET:
|
SPOTIFY_CLIENT_SECRET:
|
||||||
|
AWS_ACCESS_KEY_ID:
|
||||||
|
AWS_SECRET_ACCESS_KEY:
|
||||||
|
AWS_TAG:
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/app:cached
|
- ./:/app:cached
|
||||||
ports:
|
ports:
|
||||||
|
@ -34,6 +37,9 @@ services:
|
||||||
YOUTUBE_KEY:
|
YOUTUBE_KEY:
|
||||||
SPOTIFY_CLIENT_ID:
|
SPOTIFY_CLIENT_ID:
|
||||||
SPOTIFY_CLIENT_SECRET:
|
SPOTIFY_CLIENT_SECRET:
|
||||||
|
AWS_ACCESS_KEY_ID:
|
||||||
|
AWS_SECRET_ACCESS_KEY:
|
||||||
|
AWS_TAG:
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/app:cached
|
- ./:/app:cached
|
||||||
command: yarn run worker
|
command: yarn run worker
|
||||||
|
|
|
@ -13,7 +13,6 @@ fs.readdirSync(path.join(__dirname, 'services')).forEach(function(file) {
|
||||||
export default function* (url) {
|
export default function* (url) {
|
||||||
let matchedService;
|
let matchedService;
|
||||||
for (let service of services) {
|
for (let service of services) {
|
||||||
console.log(service)
|
|
||||||
matchedService = service.match(url);
|
matchedService = service.match(url);
|
||||||
if (matchedService) {
|
if (matchedService) {
|
||||||
const result = yield service.parseUrl(url);
|
const result = yield service.parseUrl(url);
|
||||||
|
|
128
lib/services/amazon/index.js
Normal file
128
lib/services/amazon/index.js
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
import { parse } from 'url';
|
||||||
|
import { inspect } from 'util';
|
||||||
|
import amazon from 'amazon-product-api';
|
||||||
|
import urlMatch from './url';
|
||||||
|
|
||||||
|
const client = amazon.createClient({
|
||||||
|
awsId: process.env.AWS_ACCESS_KEY_ID,
|
||||||
|
awsSecret: process.env.AWS_SECRET_ACCESS_KEY,
|
||||||
|
awsTag: process.env.AWS_TAG,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function* lookupId(id, type) {
|
||||||
|
|
||||||
|
const results = yield client.itemLookup({
|
||||||
|
itemId: id,
|
||||||
|
responseGroup: 'ItemAttributes,Images,ItemIds',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = results[0];
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return { service: 'amazon' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'album') {
|
||||||
|
return {
|
||||||
|
service: 'amazon',
|
||||||
|
type: 'album',
|
||||||
|
id: result.ASIN[0],
|
||||||
|
name: result.ItemAttributes[0].Title[0],
|
||||||
|
streamUrl: result.DetailPageURL[0],
|
||||||
|
purchaseUrl: result.DetailPageURL[0],
|
||||||
|
artwork: {
|
||||||
|
small: result.SmallImage[0].URL[0],
|
||||||
|
large: result.LargeImage[0].URL[0],
|
||||||
|
},
|
||||||
|
artist: {
|
||||||
|
name: result.ItemAttributes[0].Creator[0]._,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (type === 'track') {
|
||||||
|
return {
|
||||||
|
service: 'amazon',
|
||||||
|
type: 'track',
|
||||||
|
id: result.ASIN[0],
|
||||||
|
name: result.ItemAttributes[0].Title[0],
|
||||||
|
streamUrl: result.DetailPageURL[0],
|
||||||
|
purchaseUrl: result.DetailPageURL[0],
|
||||||
|
artwork: {
|
||||||
|
small: result.SmallImage[0].URL[0],
|
||||||
|
large: result.LargeImage[0].URL[0],
|
||||||
|
},
|
||||||
|
album: {
|
||||||
|
name: result.ItemAttributes[0],
|
||||||
|
},
|
||||||
|
artist: {
|
||||||
|
name: result.ItemAttributes[0].Creator[0]._,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { service: 'amazon' };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* search(data, original = {}) {
|
||||||
|
|
||||||
|
const type = data.type;
|
||||||
|
const results = yield client.itemSearch({
|
||||||
|
author: data.artist.name,
|
||||||
|
title: data.name,
|
||||||
|
searchIndex: 'MP3Downloads',
|
||||||
|
responseGroup: 'ItemAttributes,Tracks,Images,ItemIds',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = results[0];
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
if (type === 'album') {
|
||||||
|
return {
|
||||||
|
service: 'amazon',
|
||||||
|
type,
|
||||||
|
id: result.ASIN[0],
|
||||||
|
name: result.ItemAttributes[0].Title[0],
|
||||||
|
streamUrl: result.DetailPageURL[0],
|
||||||
|
purchaseUrl: result.DetailPageURL[0],
|
||||||
|
artwork: {
|
||||||
|
small: result.SmallImage[0].URL[0],
|
||||||
|
large: result.LargeImage[0].URL[0],
|
||||||
|
},
|
||||||
|
artist: {
|
||||||
|
name: result.ItemAttributes[0].Creator[0]._,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (type === 'track') {
|
||||||
|
return {
|
||||||
|
service: 'amazon',
|
||||||
|
type,
|
||||||
|
id: result.ASIN[0],
|
||||||
|
name: result.ItemAttributes[0].Title[0],
|
||||||
|
streamUrl: result.DetailPageURL[0],
|
||||||
|
purchaseUrl: result.DetailPageURL[0],
|
||||||
|
artwork: {
|
||||||
|
small: result.SmallImage[0].URL[0],
|
||||||
|
large: result.LargeImage[0].URL[0],
|
||||||
|
},
|
||||||
|
artist: {
|
||||||
|
name: result.ItemAttributes[0].Creator[0]._,
|
||||||
|
},
|
||||||
|
album: {
|
||||||
|
name: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { service: 'amazon' };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* parseUrl(url) {
|
||||||
|
const matches = parse(url).path.match(/\/(albums|tracks)[/]+([^?]+)/);
|
||||||
|
|
||||||
|
if (matches && matches[2]) {
|
||||||
|
return { type: matches[1].substring(0, 5), id: matches[2] };
|
||||||
|
}
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const id = 'amazon';
|
||||||
|
export const match = urlMatch;
|
11
lib/services/amazon/url.js
Normal file
11
lib/services/amazon/url.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { parse } from 'url';
|
||||||
|
|
||||||
|
export default function match(url) {
|
||||||
|
const parsed = parse(url);
|
||||||
|
if (!parsed.host.match(/\.amazon\.com$/)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matches = parse(url).path.match(/\/(albums)[/]+([^/]+)/);
|
||||||
|
return (matches && !!matches[2]);
|
||||||
|
}
|
|
@ -18,7 +18,7 @@ export function parseUrl(url) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function exactMatch(needle, haystack, type, various) {
|
function exactMatch(needle, haystack, type, various) {
|
||||||
// try to find exact match
|
// try to find exact match
|
||||||
return haystack.find((entry) => {
|
return haystack.find((entry) => {
|
||||||
if (!entry[type] || (various && (entry.artist.name !== 'Various' || entry.artist.name !== 'Various Artists'))) {
|
if (!entry[type] || (various && (entry.artist.name !== 'Various' || entry.artist.name !== 'Various Artists'))) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -32,7 +32,7 @@ function exactMatch(needle, haystack, type, various) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function looseMatch(needle, haystack, type, various) {
|
function looseMatch(needle, haystack, type, various) {
|
||||||
// try to find exact match
|
// try to find exact match
|
||||||
return haystack.find((entry) => {
|
return haystack.find((entry) => {
|
||||||
if (!entry[type] || (various && (entry.artist.name !== 'Various' || entry.artist.name !== 'Various Artists'))) {
|
if (!entry[type] || (various && (entry.artist.name !== 'Various' || entry.artist.name !== 'Various Artists'))) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -58,8 +58,8 @@ export function* lookupId(possibleId, type, countrycode) {
|
||||||
streamUrl: null,
|
streamUrl: null,
|
||||||
purchaseUrl: result.collectionViewUrl,
|
purchaseUrl: result.collectionViewUrl,
|
||||||
artwork: {
|
artwork: {
|
||||||
small: `${result.artworkUrl100.replace('100x100', '200x200').replace('.mzstatic.com', '-ssl.mzstatic.com').replace('http://', 'https://')}`,
|
small: `${result.artworkUrl100.replace('100x100', '200x200').replace('.mzstatic.com', '.mzstatic.com').replace('http://', 'https://')}`,
|
||||||
large: `${result.artworkUrl100.replace('100x100', '600x600').replace('.mzstatic.com', '-ssl.mzstatic.com').replace('http://', 'https://')}`,
|
large: `${result.artworkUrl100.replace('100x100', '600x600').replace('.mzstatic.com', '.mzstatic.com').replace('http://', 'https://')}`,
|
||||||
},
|
},
|
||||||
artist: {
|
artist: {
|
||||||
name: result.artistName,
|
name: result.artistName,
|
||||||
|
@ -120,8 +120,8 @@ export function* search(data) {
|
||||||
streamUrl: result.collectionViewUrl,
|
streamUrl: result.collectionViewUrl,
|
||||||
purchaseUrl: result.collectionViewUrl,
|
purchaseUrl: result.collectionViewUrl,
|
||||||
artwork: {
|
artwork: {
|
||||||
small: `${result.artworkUrl100.replace('100x100', '200x200').replace('.mzstatic.com', '-ssl.mzstatic.com').replace('http://', 'https://')}`,
|
small: `${result.artworkUrl100.replace('100x100', '200x200').replace('.mzstatic.com', '.mzstatic.com').replace('http://', 'https://')}`,
|
||||||
large: `${result.artworkUrl100.replace('100x100', '600x600').replace('.mzstatic.com', '-ssl.mzstatic.com').replace('http://', 'https://')}`,
|
large: `${result.artworkUrl100.replace('100x100', '600x600').replace('.mzstatic.com', '.mzstatic.com').replace('http://', 'https://')}`,
|
||||||
},
|
},
|
||||||
artist: {
|
artist: {
|
||||||
name: result.artistName,
|
name: result.artistName,
|
||||||
|
|
|
@ -1,147 +0,0 @@
|
||||||
import { parse } from 'url';
|
|
||||||
import request from 'superagent';
|
|
||||||
import 'superagent-bluebird-promise';
|
|
||||||
import debuglog from 'debug';
|
|
||||||
import urlMatch from './url';
|
|
||||||
|
|
||||||
const debug = debuglog('combine.fm:xbox');
|
|
||||||
|
|
||||||
if (!process.env.XBOX_CLIENT_ID || !process.env.XBOX_CLIENT_SECRET) {
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
const apiRoot = 'https://music.xboxlive.com/1/content';
|
|
||||||
|
|
||||||
function* getAccessToken() {
|
|
||||||
const authUrl = 'https://login.live.com/accesstoken.srf';
|
|
||||||
const scope = 'app.music.xboxlive.com';
|
|
||||||
const grantType = 'client_credentials';
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
client_id: credentials.clientId,
|
|
||||||
client_secret: credentials.clientSecret,
|
|
||||||
scope,
|
|
||||||
grant_type: grantType,
|
|
||||||
};
|
|
||||||
const result = yield request.post(authUrl)
|
|
||||||
.timeout(10000)
|
|
||||||
.send(data)
|
|
||||||
.set('Content-type', 'application/x-www-form-urlencoded')
|
|
||||||
.promise();
|
|
||||||
return result.body.access_token;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatResponse(match) {
|
|
||||||
const item = {
|
|
||||||
service: 'xbox',
|
|
||||||
type: match.Album ? 'track' : 'album',
|
|
||||||
id: match.Id,
|
|
||||||
name: match.Name,
|
|
||||||
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`,
|
|
||||||
},
|
|
||||||
artist: {
|
|
||||||
name: match.Artists[0].Artist.Name,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if (match.Album) {
|
|
||||||
item.album = { name: match.Album.Name };
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
function* apiCall(path) {
|
|
||||||
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`;
|
|
||||||
try {
|
|
||||||
const result = yield apiCall(path);
|
|
||||||
return formatResponse(result.body[apiType].Items[0]);
|
|
||||||
} catch (e) {
|
|
||||||
if (e.status !== 404) {
|
|
||||||
debug(e.body);
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
function cleanParam(str) {
|
|
||||||
return str.replace(/[:?&()[\]]+/g, '');
|
|
||||||
}
|
|
||||||
let query;
|
|
||||||
const type = data.type;
|
|
||||||
|
|
||||||
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`;
|
|
||||||
try {
|
|
||||||
const result = yield apiCall(path);
|
|
||||||
|
|
||||||
const apiType = `${type.charAt(0).toUpperCase() + type.substr(1)}s`;
|
|
||||||
|
|
||||||
let match = exactMatch(name, data.artist.name, result.body[apiType].Items, type);
|
|
||||||
if (!match) {
|
|
||||||
match = looseMatch(name, data.artist.name, result.body[apiType].Items, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match) {
|
|
||||||
return formatResponse(match);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return { service: 'xbox' };
|
|
||||||
}
|
|
||||||
return { service: 'xbox' };
|
|
||||||
}
|
|
||||||
|
|
||||||
export const id = 'xbox';
|
|
||||||
export const match = urlMatch;
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { parse } from 'url';
|
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
|
|
@ -56,8 +56,6 @@ export function findMatchesAsync(share) {
|
||||||
}
|
}
|
||||||
co(function* gen() { // eslint-disable-line no-loop-func
|
co(function* gen() { // eslint-disable-line no-loop-func
|
||||||
const match = yield service.search(share);
|
const match = yield service.search(share);
|
||||||
console.log(service.id)
|
|
||||||
console.log(match)
|
|
||||||
if (match.id) {
|
if (match.id) {
|
||||||
models.match.create({
|
models.match.create({
|
||||||
trackId: share.$modelOptions.name.singular == 'track' ? share.id : null,
|
trackId: share.$modelOptions.name.singular == 'track' ? share.id : null,
|
||||||
|
|
|
@ -3,6 +3,7 @@ export default function (sequelize, DataTypes) {
|
||||||
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
|
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
|
||||||
externalId: { type: DataTypes.STRING(50), index: true }, // eslint-disable-line new-cap
|
externalId: { type: DataTypes.STRING(50), index: true }, // eslint-disable-line new-cap
|
||||||
service: DataTypes.ENUM( // eslint-disable-line new-cap
|
service: DataTypes.ENUM( // eslint-disable-line new-cap
|
||||||
|
'amazon',
|
||||||
'deezer',
|
'deezer',
|
||||||
'google',
|
'google',
|
||||||
'itunes',
|
'itunes',
|
||||||
|
|
|
@ -5,6 +5,7 @@ export default function (sequelize, DataTypes) {
|
||||||
albumId: DataTypes.INTEGER,
|
albumId: DataTypes.INTEGER,
|
||||||
externalId: { type: DataTypes.STRING(50), index: true }, // eslint-disable-line new-cap
|
externalId: { type: DataTypes.STRING(50), index: true }, // eslint-disable-line new-cap
|
||||||
service: DataTypes.ENUM( // eslint-disable-line new-cap
|
service: DataTypes.ENUM( // eslint-disable-line new-cap
|
||||||
|
'amazon',
|
||||||
'deezer',
|
'deezer',
|
||||||
'google',
|
'google',
|
||||||
'itunes',
|
'itunes',
|
||||||
|
|
|
@ -3,6 +3,7 @@ export default function (sequelize, DataTypes) {
|
||||||
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
|
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
|
||||||
externalId: { type: DataTypes.STRING(50), index: true }, // eslint-disable-line new-cap
|
externalId: { type: DataTypes.STRING(50), index: true }, // eslint-disable-line new-cap
|
||||||
service: DataTypes.ENUM( // eslint-disable-line new-cap
|
service: DataTypes.ENUM( // eslint-disable-line new-cap
|
||||||
|
'amazon',
|
||||||
'deezer',
|
'deezer',
|
||||||
'google',
|
'google',
|
||||||
'itunes',
|
'itunes',
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
"node": "^8.6.0"
|
"node": "^8.6.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"amazon-product-api": "^0.4.4",
|
||||||
"apple-music-jwt": "^0.1.2",
|
"apple-music-jwt": "^0.1.2",
|
||||||
"babel": "^6.1.18",
|
"babel": "^6.1.18",
|
||||||
"babel-cli": "^6.26.0",
|
"babel-cli": "^6.26.0",
|
||||||
|
|
BIN
public/assets/images/amazon.png
Normal file
BIN
public/assets/images/amazon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.5 KiB |
|
@ -16,12 +16,10 @@ const store = new Vuex.Store({
|
||||||
actions: {
|
actions: {
|
||||||
// ensure data for rendering given list type
|
// ensure data for rendering given list type
|
||||||
FETCH_RECENTS: ({ commit }) => fetchRecents()
|
FETCH_RECENTS: ({ commit }) => fetchRecents()
|
||||||
.then(res => commit('SET_RECENTS', { recents: res.body.recents })),
|
.then(res => commit('SET_RECENTS', { recents: res.body.recents })),
|
||||||
|
|
||||||
FETCH_ITEM: ({ commit, state }, { service, type, id }) => fetchItem(service, type, id)
|
FETCH_ITEM: ({ commit }, { service, type, id }) => fetchItem(service, type, id)
|
||||||
.then(item => {
|
.then(item => commit('SET_ITEM', { item })),
|
||||||
return commit('SET_ITEM', { item })
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mutations: {
|
mutations: {
|
||||||
|
@ -30,7 +28,7 @@ const store = new Vuex.Store({
|
||||||
},
|
},
|
||||||
|
|
||||||
SET_ITEM: (state, { item }) => {
|
SET_ITEM: (state, { item }) => {
|
||||||
state.item = item.body;
|
state.item = item.body; // eslint-disable-line
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -56,7 +56,7 @@ import search from '../components/search.vue';
|
||||||
export default {
|
export default {
|
||||||
name: 'index-view',
|
name: 'index-view',
|
||||||
components: { search },
|
components: { search },
|
||||||
created () {
|
created() {
|
||||||
// fetch the data when the view is created and the data is
|
// fetch the data when the view is created and the data is
|
||||||
// already being observed
|
// already being observed
|
||||||
this.fetch();
|
this.fetch();
|
||||||
|
@ -68,16 +68,15 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'$route': 'fetch',
|
$route: 'fetch',
|
||||||
recents: function () {
|
recents() {
|
||||||
if (typeof document !== 'undefined') {
|
if (typeof document !== 'undefined') {
|
||||||
const recents = this.$store.state.recents;
|
document.title = 'Combine.fm • Share Music';
|
||||||
document.title = `Combine.fm • Share Music`;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetch () {
|
fetch() {
|
||||||
if (!this.$store.state.recents) {
|
if (!this.$store.state.recents) {
|
||||||
fetchRecents().then((res) => {
|
fetchRecents().then((res) => {
|
||||||
this.recents = res.body.recents;
|
this.recents = res.body.recents;
|
||||||
|
@ -87,7 +86,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { parse } from 'url';
|
import { parse } from 'url';
|
||||||
import kue from 'kue';
|
import kue from 'kue';
|
||||||
import debuglog from 'debug';
|
import debuglog from 'debug';
|
||||||
|
import { inspect } from 'util';
|
||||||
|
|
||||||
import lookup from '../lib/lookup';
|
import lookup from '../lib/lookup';
|
||||||
import services from '../lib/services';
|
import services from '../lib/services';
|
||||||
|
@ -57,7 +58,7 @@ export default function* () {
|
||||||
|
|
||||||
this.body = share;
|
this.body = share;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debug(e);
|
debug(inspect(e, {showHidden: false, depth: null}));
|
||||||
this.throw(400, { error: { message: 'Unexpected error looking up music. Please try again later.' } });
|
this.throw(400, { error: { message: 'Unexpected error looking up music. Please try again later.' } });
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
34
test/services/amazon.js
Normal file
34
test/services/amazon.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import 'should';
|
||||||
|
import * as amazon from '../../lib/services/amazon';
|
||||||
|
|
||||||
|
describe('Amazon', function () {
|
||||||
|
describe('lookupId', function () {
|
||||||
|
it('should find album by ID', function* () {
|
||||||
|
const result = yield amazon.lookupId('B00WMW3HFY', 'album');
|
||||||
|
result.name.should.equal('In Colour [Explicit]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find track by ID', function* (){
|
||||||
|
const result = yield amazon.lookupId('B00WMW3TUM', 'track');
|
||||||
|
result.name.should.equal('Sleep Sound');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('search', function(){
|
||||||
|
it('should find album by search', function* () {
|
||||||
|
const result = yield amazon.search({type: 'album', artist: {name: 'Jamie xx'}, name: 'In Colour'});
|
||||||
|
result.name.should.equal('In Colour [Explicit]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find track by search', function* (){
|
||||||
|
const result = yield amazon.search({type: 'track', artist: {name: 'Jamie xx'}, albumName: 'In Colour', name: 'Loud Places'});
|
||||||
|
result.name.should.equal('Loud Places');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find awkward track by search', function* (){
|
||||||
|
const result = yield amazon.search({type: 'track', artist: {name: 'Jamie xx'}, albumName: 'In Colour (Remixes)', name: 'Loud Places [Tessela Remix]'});
|
||||||
|
result.name.should.equal('Loud Places [Tessela Remix]');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -26,7 +26,7 @@ describe('Google Play Music', function(){
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should find awkward track by search', function* (){
|
it('should find awkward track by search', function* (){
|
||||||
const result = yield google.search({type: 'track', artist: {name: 'Jamie xx'}, albumName: 'In Colour (Remixes)', name: 'Loud Places [Tessela Remix]'});
|
const result = yield google.search({type: 'track', artist: {name: 'Jamie xx'}, albumName: 'Loud Places (Remixes)', name: 'Loud Places [Tessela Remix]'});
|
||||||
result.name.should.equal('Loud Places [Tessela Remix]');
|
result.name.should.equal('Loud Places [Tessela Remix]');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@ describe('Spotify', function(){
|
||||||
describe('lookupId', function(){
|
describe('lookupId', function(){
|
||||||
it('should find album by ID', function* (){
|
it('should find album by ID', function* (){
|
||||||
const result = yield spotify.lookupId('77UW17CZFyCaRLHdHeofZu', 'album');
|
const result = yield spotify.lookupId('77UW17CZFyCaRLHdHeofZu', 'album');
|
||||||
result.name.should.equal('Listen (Deluxe)');
|
result.name.should.equal('Listen');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should find track by ID', function* (){
|
it('should find track by ID', function* (){
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
import 'should';
|
|
||||||
import * as xbox from '../../lib/services/xbox';
|
|
||||||
|
|
||||||
describe('Xbox Music', function(){
|
|
||||||
describe('lookupId', function(){
|
|
||||||
it('should find album by ID', function* (){
|
|
||||||
const result = yield xbox.lookupId('music.8b558d00-0100-11db-89ca-0019b92a3933', 'album');
|
|
||||||
result.name.should.equal('Muchas Gracias: The Best Of Kyuss');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should find track by ID', function* (){
|
|
||||||
const result = yield xbox.lookupId('music.8f558d00-0100-11db-89ca-0019b92a3933', 'track');
|
|
||||||
result.name.should.equal('Shine');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('search', function(){
|
|
||||||
it('should find album by search', function* (){
|
|
||||||
const result = yield xbox.search({type: 'album', artist: {name: 'Kyuss'}, name: 'Muchas Gracias: The Best Of Kyuss'});
|
|
||||||
result.name.should.equal('Muchas Gracias: The Best Of Kyuss');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should find awkward album by search', function* (){
|
|
||||||
const result = yield xbox.search({type: 'album', artist: {name: 'Anavitória'}, name: 'Fica'});
|
|
||||||
result.name.should.equal('Fica');
|
|
||||||
result.artist.name.should.equal('Anavitória');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('lookupUrl', function(){
|
|
||||||
it('should parse regular url into album ID', function* (){
|
|
||||||
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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -5,7 +5,7 @@ describe('Youtube', function(){
|
||||||
describe('lookup', function(){
|
describe('lookup', function(){
|
||||||
it('should find album by lookup', function* (){
|
it('should find album by lookup', function* (){
|
||||||
const result = yield youtube.lookupId('6JnGBs88sL0');
|
const result = yield youtube.lookupId('6JnGBs88sL0');
|
||||||
result.name.should.equal('Nelly Furtado - Say It Right');
|
result.name.should.equal('Say It Right');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
25
yarn.lock
25
yarn.lock
|
@ -131,6 +131,14 @@ alphanum-sort@^1.0.1, alphanum-sort@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
|
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
|
||||||
|
|
||||||
|
amazon-product-api@^0.4.4:
|
||||||
|
version "0.4.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/amazon-product-api/-/amazon-product-api-0.4.4.tgz#4e67203d5c15bfee70aaf83edc0f439215ba06bc"
|
||||||
|
dependencies:
|
||||||
|
es6-promise "^3.0.2"
|
||||||
|
request "^2.81.0"
|
||||||
|
xml2js "^0.4.17"
|
||||||
|
|
||||||
amdefine@>=0.0.4:
|
amdefine@>=0.0.4:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
|
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
|
||||||
|
@ -2009,7 +2017,7 @@ es-feature-detect@^1.0.0:
|
||||||
colour "^0.7.1"
|
colour "^0.7.1"
|
||||||
split-camelcase "^1.0.1"
|
split-camelcase "^1.0.1"
|
||||||
|
|
||||||
es6-promise@^3.3.1:
|
es6-promise@^3.0.2, es6-promise@^3.3.1:
|
||||||
version "3.3.1"
|
version "3.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613"
|
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613"
|
||||||
|
|
||||||
|
@ -5205,6 +5213,10 @@ sax@0.5.x:
|
||||||
version "0.5.8"
|
version "0.5.8"
|
||||||
resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1"
|
resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1"
|
||||||
|
|
||||||
|
sax@>=0.6.0:
|
||||||
|
version "1.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||||
|
|
||||||
sax@~1.2.1:
|
sax@~1.2.1:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828"
|
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828"
|
||||||
|
@ -6237,6 +6249,17 @@ xdg-basedir@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
|
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
|
||||||
|
|
||||||
|
xml2js@^0.4.17:
|
||||||
|
version "0.4.19"
|
||||||
|
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
|
||||||
|
dependencies:
|
||||||
|
sax ">=0.6.0"
|
||||||
|
xmlbuilder "~9.0.1"
|
||||||
|
|
||||||
|
xmlbuilder@~9.0.1:
|
||||||
|
version "9.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
|
||||||
|
|
||||||
xtend@^4.0.0, xtend@^4.0.1:
|
xtend@^4.0.0, xtend@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue