From b0fcf550714b89c117840d54420ab1199174dd51 Mon Sep 17 00:00:00 2001 From: Jonathan Cremin Date: Sun, 8 Apr 2018 16:31:46 +0100 Subject: [PATCH] Add initial Amazon Music support --- Dockerfile.dev | 19 +++++ Makefile | 2 +- docker-compose.yml | 6 ++ lib/lookup.js | 1 - lib/services/amazon/index.js | 128 +++++++++++++++++++++++++++ lib/services/amazon/url.js | 11 +++ lib/services/deezer/index.js | 4 +- lib/services/itunes/index.js | 8 +- lib/services/xbox/index.js | 147 -------------------------------- lib/services/xbox/url.js | 11 --- lib/share.js | 2 - models/album.js | 1 + models/match.js | 1 + models/track.js | 1 + package.json | 1 + public/assets/images/amazon.png | Bin 0 -> 8724 bytes public/src/store/index.js | 10 +-- public/src/views/index.vue | 13 ++- routes/search.js | 3 +- test/services/amazon.js | 34 ++++++++ test/services/google.js | 2 +- test/services/spotify.js | 2 +- test/services/xbox.js | 36 -------- test/services/youtube.js | 2 +- yarn.lock | 25 +++++- 25 files changed, 248 insertions(+), 222 deletions(-) create mode 100644 Dockerfile.dev create mode 100644 lib/services/amazon/index.js create mode 100644 lib/services/amazon/url.js delete mode 100644 lib/services/xbox/index.js delete mode 100644 lib/services/xbox/url.js create mode 100644 public/assets/images/amazon.png create mode 100644 test/services/amazon.js delete mode 100644 test/services/xbox.js diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..2edafe2 --- /dev/null +++ b/Dockerfile.dev @@ -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"] diff --git a/Makefile b/Makefile index 11029a8..71bc38b 100644 --- a/Makefile +++ b/Makefile @@ -32,5 +32,5 @@ docker-compose-up: ## Start (and create) docker containers docker-compose up -d .PHONY: yarn -yarn: ## Migrate database schema +yarn: ## Update yarn dependencies docker-compose run --rm app yarn diff --git a/docker-compose.yml b/docker-compose.yml index f3d0bc3..4e731f0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,9 @@ services: YOUTUBE_KEY: SPOTIFY_CLIENT_ID: SPOTIFY_CLIENT_SECRET: + AWS_ACCESS_KEY_ID: + AWS_SECRET_ACCESS_KEY: + AWS_TAG: volumes: - ./:/app:cached ports: @@ -34,6 +37,9 @@ services: YOUTUBE_KEY: SPOTIFY_CLIENT_ID: SPOTIFY_CLIENT_SECRET: + AWS_ACCESS_KEY_ID: + AWS_SECRET_ACCESS_KEY: + AWS_TAG: volumes: - ./:/app:cached command: yarn run worker diff --git a/lib/lookup.js b/lib/lookup.js index fc4361b..3218851 100644 --- a/lib/lookup.js +++ b/lib/lookup.js @@ -13,7 +13,6 @@ fs.readdirSync(path.join(__dirname, 'services')).forEach(function(file) { export default function* (url) { let matchedService; for (let service of services) { - console.log(service) matchedService = service.match(url); if (matchedService) { const result = yield service.parseUrl(url); diff --git a/lib/services/amazon/index.js b/lib/services/amazon/index.js new file mode 100644 index 0000000..f42dc05 --- /dev/null +++ b/lib/services/amazon/index.js @@ -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; diff --git a/lib/services/amazon/url.js b/lib/services/amazon/url.js new file mode 100644 index 0000000..e31f5f7 --- /dev/null +++ b/lib/services/amazon/url.js @@ -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]); +} diff --git a/lib/services/deezer/index.js b/lib/services/deezer/index.js index 6c49193..706814b 100644 --- a/lib/services/deezer/index.js +++ b/lib/services/deezer/index.js @@ -18,7 +18,7 @@ export function parseUrl(url) { } function exactMatch(needle, haystack, type, various) { - // try to find exact match + // try to find exact match return haystack.find((entry) => { if (!entry[type] || (various && (entry.artist.name !== 'Various' || entry.artist.name !== 'Various Artists'))) { return false; @@ -32,7 +32,7 @@ function exactMatch(needle, haystack, type, various) { } function looseMatch(needle, haystack, type, various) { - // try to find exact match + // try to find exact match return haystack.find((entry) => { if (!entry[type] || (various && (entry.artist.name !== 'Various' || entry.artist.name !== 'Various Artists'))) { return false; diff --git a/lib/services/itunes/index.js b/lib/services/itunes/index.js index 3529764..e72ad3e 100644 --- a/lib/services/itunes/index.js +++ b/lib/services/itunes/index.js @@ -58,8 +58,8 @@ export function* lookupId(possibleId, type, countrycode) { streamUrl: null, purchaseUrl: result.collectionViewUrl, artwork: { - small: `${result.artworkUrl100.replace('100x100', '200x200').replace('.mzstatic.com', '-ssl.mzstatic.com').replace('http://', 'https://')}`, - large: `${result.artworkUrl100.replace('100x100', '600x600').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', '.mzstatic.com').replace('http://', 'https://')}`, }, artist: { name: result.artistName, @@ -120,8 +120,8 @@ export function* search(data) { streamUrl: result.collectionViewUrl, purchaseUrl: result.collectionViewUrl, artwork: { - small: `${result.artworkUrl100.replace('100x100', '200x200').replace('.mzstatic.com', '-ssl.mzstatic.com').replace('http://', 'https://')}`, - large: `${result.artworkUrl100.replace('100x100', '600x600').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', '.mzstatic.com').replace('http://', 'https://')}`, }, artist: { name: result.artistName, diff --git a/lib/services/xbox/index.js b/lib/services/xbox/index.js deleted file mode 100644 index bcb0519..0000000 --- a/lib/services/xbox/index.js +++ /dev/null @@ -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; diff --git a/lib/services/xbox/url.js b/lib/services/xbox/url.js deleted file mode 100644 index bae28d6..0000000 --- a/lib/services/xbox/url.js +++ /dev/null @@ -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]; -} diff --git a/lib/share.js b/lib/share.js index 7a504e8..d975c67 100644 --- a/lib/share.js +++ b/lib/share.js @@ -56,8 +56,6 @@ export function findMatchesAsync(share) { } co(function* gen() { // eslint-disable-line no-loop-func const match = yield service.search(share); - console.log(service.id) - console.log(match) if (match.id) { models.match.create({ trackId: share.$modelOptions.name.singular == 'track' ? share.id : null, diff --git a/models/album.js b/models/album.js index 040fc49..8352760 100644 --- a/models/album.js +++ b/models/album.js @@ -3,6 +3,7 @@ export default function (sequelize, DataTypes) { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, externalId: { type: DataTypes.STRING(50), index: true }, // eslint-disable-line new-cap service: DataTypes.ENUM( // eslint-disable-line new-cap + 'amazon', 'deezer', 'google', 'itunes', diff --git a/models/match.js b/models/match.js index 89f7017..46359bb 100644 --- a/models/match.js +++ b/models/match.js @@ -5,6 +5,7 @@ export default function (sequelize, DataTypes) { albumId: DataTypes.INTEGER, externalId: { type: DataTypes.STRING(50), index: true }, // eslint-disable-line new-cap service: DataTypes.ENUM( // eslint-disable-line new-cap + 'amazon', 'deezer', 'google', 'itunes', diff --git a/models/track.js b/models/track.js index 5d9b50c..79e3f8f 100644 --- a/models/track.js +++ b/models/track.js @@ -3,6 +3,7 @@ export default function (sequelize, DataTypes) { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, externalId: { type: DataTypes.STRING(50), index: true }, // eslint-disable-line new-cap service: DataTypes.ENUM( // eslint-disable-line new-cap + 'amazon', 'deezer', 'google', 'itunes', diff --git a/package.json b/package.json index a86026e..6391cb6 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "node": "^8.6.0" }, "dependencies": { + "amazon-product-api": "^0.4.4", "apple-music-jwt": "^0.1.2", "babel": "^6.1.18", "babel-cli": "^6.26.0", diff --git a/public/assets/images/amazon.png b/public/assets/images/amazon.png new file mode 100644 index 0000000000000000000000000000000000000000..bdf648e5408d3cbeac625272295b1bd305525423 GIT binary patch literal 8724 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 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) - .then(item => { - return commit('SET_ITEM', { item }) - }), + FETCH_ITEM: ({ commit }, { service, type, id }) => fetchItem(service, type, id) + .then(item => commit('SET_ITEM', { item })), }, mutations: { @@ -30,7 +28,7 @@ const store = new Vuex.Store({ }, SET_ITEM: (state, { item }) => { - state.item = item.body; + state.item = item.body; // eslint-disable-line }, }, }); diff --git a/public/src/views/index.vue b/public/src/views/index.vue index e1ae1ad..f04ad1e 100644 --- a/public/src/views/index.vue +++ b/public/src/views/index.vue @@ -56,7 +56,7 @@ import search from '../components/search.vue'; export default { name: 'index-view', components: { search }, - created () { + created() { // fetch the data when the view is created and the data is // already being observed this.fetch(); @@ -68,16 +68,15 @@ export default { }; }, watch: { - '$route': 'fetch', - recents: function () { + $route: 'fetch', + recents() { if (typeof document !== 'undefined') { - const recents = this.$store.state.recents; - document.title = `Combine.fm • Share Music`; + document.title = 'Combine.fm • Share Music'; } }, }, methods: { - fetch () { + fetch() { if (!this.$store.state.recents) { fetchRecents().then((res) => { this.recents = res.body.recents; @@ -87,7 +86,7 @@ export default { } }, }, -} +};