combine.fm/lib/services/xbox/index.js

148 lines
3.9 KiB
JavaScript
Raw Normal View History

2015-08-20 23:22:57 +01:00
import { parse } from 'url';
import request from 'superagent';
import 'superagent-bluebird-promise';
2015-08-21 18:33:50 +01:00
import debuglog from 'debug';
2017-07-20 14:31:07 +01:00
import urlMatch from './url';
2015-08-21 18:33:50 +01:00
2017-10-23 17:58:23 +01:00
const debug = debuglog('combine.fm:xbox');
2014-12-21 01:19:37 +00:00
if (!process.env.XBOX_CLIENT_ID || !process.env.XBOX_CLIENT_SECRET) {
2017-07-20 14:31:07 +01:00
debug('XBOX_CLIENT_ID and XBOX_CLIENT_SECRET environment variables not found, deactivating Xbox Music.');
2015-08-20 23:22:57 +01:00
}
2014-12-21 01:19:37 +00:00
2015-08-20 23:22:57 +01:00
const credentials = {
2014-12-21 01:19:37 +00:00
clientId: process.env.XBOX_CLIENT_ID,
2017-07-20 14:31:07 +01:00
clientSecret: process.env.XBOX_CLIENT_SECRET,
2014-12-21 01:19:37 +00:00
};
2015-08-21 18:33:50 +01:00
const apiRoot = 'https://music.xboxlive.com/1/content';
2015-08-20 23:22:57 +01:00
function* getAccessToken() {
2017-05-06 20:30:55 +01:00
const authUrl = 'https://login.live.com/accesstoken.srf';
const scope = 'app.music.xboxlive.com';
2015-08-21 18:33:50 +01:00
const grantType = 'client_credentials';
2014-12-21 01:19:37 +00:00
2015-08-20 23:22:57 +01:00
const data = {
client_id: credentials.clientId,
client_secret: credentials.clientSecret,
2017-07-20 14:31:07 +01:00
scope,
grant_type: grantType,
2015-08-20 23:22:57 +01:00
};
2017-07-20 14:31:07 +01:00
const result = yield request.post(authUrl)
.timeout(10000)
.send(data)
.set('Content-type', 'application/x-www-form-urlencoded')
.promise();
2015-08-20 23:22:57 +01:00
return result.body.access_token;
2014-12-21 01:19:37 +00:00
}
2017-05-07 22:47:06 +01:00
function formatResponse(match) {
2015-08-21 18:33:50 +01:00
const item = {
service: 'xbox',
2017-05-07 22:47:06 +01:00
type: match.Album ? 'track' : 'album',
id: match.Id,
name: match.Name,
streamUrl: match.Link,
2014-12-21 01:19:37 +00:00
purchaseUrl: null,
artwork: {
2017-07-20 14:31:07 +01:00
small: `${match.ImageUrl.replace('http://', 'https://')}&w=250&h=250`,
large: `${match.ImageUrl.replace('http://', 'https://')}&w=500&h=500`,
2014-12-21 01:19:37 +00:00
},
artist: {
2017-07-20 14:31:07 +01:00
name: match.Artists[0].Artist.Name,
},
2014-12-21 01:19:37 +00:00
};
2017-05-07 22:47:06 +01:00
if (match.Album) {
2017-07-20 14:31:07 +01:00
item.album = { name: match.Album.Name };
2014-12-21 01:19:37 +00:00
}
return item;
}
2015-08-21 18:33:50 +01:00
function* apiCall(path) {
2017-07-20 14:31:07 +01:00
const accessToken = yield getAccessToken();
return request.get(apiRoot + path).timeout(10000).set('Authorization', `Bearer ${accessToken}`).promise();
2015-08-21 18:33:50 +01:00
}
2017-07-20 14:31:07 +01:00
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' };
}
}
2014-12-21 01:19:37 +00:00
2015-08-20 23:22:57 +01:00
export function* parseUrl(url) {
const parsed = parse(url);
2015-08-21 18:33:50 +01:00
const parts = parsed.path.split('/');
2015-08-20 23:22:57 +01:00
const type = parts[1];
2017-07-20 14:31:07 +01:00
const idMatches = parts[4].match(/bz.[\w-]+/);
2017-05-26 19:47:17 +01:00
const id = idMatches[0].replace('bz.', 'music.');
if (!id) {
return false;
}
2017-05-26 19:47:17 +01:00
return yield lookupId(id, type);
2014-12-21 01:19:37 +00:00
}
2017-07-20 14:31:07 +01:00
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;
2015-08-21 18:33:50 +01:00
}
2017-07-20 14:31:07 +01:00
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;
});
}
2014-12-21 01:19:37 +00:00
2015-08-20 23:22:57 +01:00
export function* search(data) {
2017-07-20 14:31:07 +01:00
function cleanParam(str) {
return str.replace(/[:?&()[\]]+/g, '');
}
2017-07-20 14:31:07 +01:00
let query;
2015-08-20 23:22:57 +01:00
const type = data.type;
2014-12-21 01:19:37 +00:00
2017-07-20 14:31:07 +01:00
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)}`;
2014-12-21 01:19:37 +00:00
}
2017-05-07 22:47:06 +01:00
const name = data.name;
2017-07-20 14:31:07 +01:00
const path = `/music/search?q=${encodeURIComponent(query.trim())}&filters=${type}s`;
2017-05-26 19:47:17 +01:00
try {
const result = yield apiCall(path);
2017-05-07 22:47:06 +01:00
2017-07-20 14:31:07 +01:00
const apiType = `${type.charAt(0).toUpperCase() + type.substr(1)}s`;
2017-05-07 22:47:06 +01:00
2017-05-26 19:47:17 +01:00
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);
}
2017-05-07 22:47:06 +01:00
2017-05-26 19:47:17 +01:00
if (match) {
return formatResponse(match);
}
} catch (err) {
2017-07-20 14:31:07 +01:00
return { service: 'xbox' };
2015-08-21 18:33:50 +01:00
}
2017-07-20 14:31:07 +01:00
return { service: 'xbox' };
2017-05-07 22:47:06 +01:00
}
2017-07-20 14:31:07 +01:00
export const id = 'xbox';
export const match = urlMatch;