Add support for matching from Youtube urls
This commit is contained in:
parent
7d3173c57e
commit
599e9b0850
12 changed files with 130 additions and 14 deletions
2
app.js
2
app.js
|
@ -105,7 +105,7 @@ app.use(function(req, res, next) {
|
|||
// will print stacktrace
|
||||
if (app.get('env') === 'development') {
|
||||
app.use(function(err, req, res, next) {
|
||||
console.log(err)
|
||||
console.log(err.stack)
|
||||
res.status(err.status || 500);
|
||||
|
||||
var content = React.renderToString(ErrorView({status: err.status || 500, message: err.message, error: err}));
|
||||
|
|
|
@ -25,5 +25,4 @@ module.exports = function(url) {
|
|||
});
|
||||
});
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
|
|
@ -120,7 +120,11 @@ module.exports.search = function(data) {
|
|||
albumClean = data.name.match(/([^\(\[]+)/)[0];
|
||||
} else if (type == "track") {
|
||||
query = data.artist.name + " " + data.album.name + " " + data.name;
|
||||
albumClean = data.album.name.match(/([^\(\[]+)/)[0];
|
||||
try {
|
||||
albumClean = data.album.name.match(/([^\(\[]+)/)[0];
|
||||
} catch(e) {
|
||||
albumClean = "";
|
||||
}
|
||||
}
|
||||
|
||||
return rdio.apiAsync("", "", {query: query, method: 'search', types: type}).then(function(results) {
|
||||
|
@ -129,14 +133,14 @@ module.exports.search = function(data) {
|
|||
var result = results.filter(function(result) {
|
||||
if (type == "album" && result.name.match(/([^\(\[]+)/)[0] == albumClean) {
|
||||
return result;
|
||||
} else if (type == "track" && result.album.match(/([^\(\[]+)/)[0] == albumClean) {
|
||||
} else if (type == "track" && (result.album.match(/([^\(\[]+)/)[0] == albumClean || !albumClean)) {
|
||||
return result;
|
||||
}
|
||||
}).shift();
|
||||
|
||||
if (!result) {
|
||||
var matches = albumClean.match(/^[^\(\[]+/);
|
||||
if (matches[0] && matches[0] != albumClean) {
|
||||
if (matches && matches[0] && matches[0] != albumClean) {
|
||||
var cleanedData = JSON.parse(JSON.stringify(data));
|
||||
if (type == "album") {
|
||||
cleanedData.name = matches[0].trim();
|
||||
|
|
|
@ -72,14 +72,14 @@ module.exports.search = function(data) {
|
|||
query = "artist:" + data.artist.name.replace(":", "") + " album:" + data.name.replace(":", "");
|
||||
album = data.name;
|
||||
} else if (type == "track") {
|
||||
query = "artist:" + data.artist.name.replace(":", "") + " album:" + data.album.name.replace(":", "") + " track:" + data.name.replace(":", "");
|
||||
query = "artist:" + data.artist.name.replace(":", "") + " track:" + data.name.replace(":", "") + ( data.album.name.length > 0 ? " album: " + data.album.name.replace(":", ""): "");
|
||||
album = data.album.name;
|
||||
}
|
||||
|
||||
return spotify.searchAsync({query: query, type: type}).then(function(results) {
|
||||
if (!results[type + "s"].items[0]) {
|
||||
var matches = album.match(/^[^\(\[]+/);
|
||||
if (matches[0] && matches[0] != album) {
|
||||
if (matches && matches[0] && matches[0] != album) {
|
||||
var cleanedData = JSON.parse(JSON.stringify(data));
|
||||
if (type == "album") {
|
||||
cleanedData.name = matches[0].trim();
|
||||
|
|
20
lib/services/youtube/freebase.js
Normal file
20
lib/services/youtube/freebase.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
"use strict";
|
||||
var parse = require('url').parse;
|
||||
var Promise = require('bluebird');
|
||||
var request = require('superagent');
|
||||
require('superagent-bluebird-promise');
|
||||
|
||||
var credentials = {
|
||||
key: process.env.YOUTUBE_KEY,
|
||||
};
|
||||
|
||||
var apiRoot = "https://www.googleapis.com/freebase/v1/topic";
|
||||
|
||||
module.exports.get = function(topic) {
|
||||
return request.get(apiRoot + topic + "?key=" + credentials.key).promise().then(function(res) {
|
||||
return res.body;
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
module.exports.get("/m/0dwcrm_");
|
|
@ -1,5 +1,7 @@
|
|||
"use strict";
|
||||
var parse = require('url').parse;
|
||||
var freebase = require('./freebase');
|
||||
var querystring = require('querystring');
|
||||
var Promise = require('bluebird');
|
||||
var request = require('superagent');
|
||||
require('superagent-bluebird-promise');
|
||||
|
@ -19,6 +21,65 @@ var apiRoot = "https://www.googleapis.com/youtube/v3";
|
|||
|
||||
module.exports.match = require('./url').match;
|
||||
|
||||
module.exports.parseUrl = function(url) {
|
||||
var parsed = parse(url);
|
||||
var query = querystring.parse(parsed.query);
|
||||
var id = query.v;
|
||||
|
||||
if (!id) {
|
||||
id = parsed.path.substr(1);
|
||||
if (!id) {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
return module.exports.lookupId(id, "track");
|
||||
}
|
||||
|
||||
module.exports.lookupId = function(id, type) {
|
||||
|
||||
var path = "/videos?part=snippet%2Cstatus%2CtopicDetails&id=" + id + "&key=" + credentials.key;
|
||||
|
||||
return request.get(apiRoot + path).promise().then(function(res) {
|
||||
var item = res.body.items[0];
|
||||
if (item.topicDetails.topicIds) {
|
||||
var promises = [];
|
||||
item.topicDetails.topicIds.forEach(function(topicId) {
|
||||
promises.push(freebase.get(topicId).then(function(topic) {
|
||||
return topic.property["/music/recording/song"] ? topic : false;
|
||||
}, function(err) {
|
||||
console.log(err)
|
||||
}));
|
||||
})
|
||||
return Promise.all(promises).then(function(topics) {
|
||||
for (var key in topics) {
|
||||
var topic = topics[key];
|
||||
if (topic) {
|
||||
console.log(topic.property['/music/recording/song'])
|
||||
return {
|
||||
id: id,
|
||||
service: "youtube",
|
||||
type: "track",
|
||||
name: topic.property['/music/recording/song'].values[0].text,
|
||||
artist: {name: topic.property['/music/recording/artist'].values[0].text},
|
||||
album: {name: ""},
|
||||
streamUrl: "https://youtu.be/" + id,
|
||||
purchaseUrl: null,
|
||||
artwork: {
|
||||
small: item.snippet.thumbnails.medium.url,
|
||||
large: item.snippet.thumbnails.high.url,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return {service: "youtube"};
|
||||
}
|
||||
}, function(res) {
|
||||
return {service: "youtube"};
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.search = function(data) {
|
||||
var query, album;
|
||||
var type = data.type;
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
"use strict";
|
||||
var parse = require("url").parse;
|
||||
var querystring = require('querystring');
|
||||
|
||||
module.exports.match = function(url, type) {
|
||||
var parsed = parse(url);
|
||||
|
||||
if (parsed.host.match(/youtu\.be$/)) {
|
||||
return true;
|
||||
} else if (parsed.host.match(/youtube\.com$/)) {
|
||||
var query = querystring.parse(parsed.query);
|
||||
return !!query.v;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
|
|
@ -105,6 +105,10 @@ h3 {
|
|||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.share-form .alert {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.btn-custom {
|
||||
background-color: #FE4365;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,16 @@ module.exports = function(req, res, next) {
|
|||
return res.json({error:{message:"You need to submit a url."}});
|
||||
}
|
||||
|
||||
lookup(req.body.url).then(function(item) {
|
||||
var promise = lookup(req.body.url);
|
||||
|
||||
if (!promise) {
|
||||
return res.json({error: {message: "No supported music found at that link :("}});
|
||||
}
|
||||
|
||||
promise.then(function(item) {
|
||||
if (!item) {
|
||||
return res.json({error: {message: "No supported music found at that link :("}});
|
||||
}
|
||||
item.matched_at = new Date();
|
||||
var matches = {};
|
||||
matches[item.service] = item;
|
||||
|
@ -30,7 +39,7 @@ module.exports = function(req, res, next) {
|
|||
res.json(item);
|
||||
});
|
||||
}, function(error) {
|
||||
console.log(error.stack)
|
||||
res.json({error: "No matches found for url"});
|
||||
console.log(error.stack);
|
||||
res.json({error: {message: "No matches found for this link, sorry :("}});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@ module.exports = React.createClass({
|
|||
render: function() {
|
||||
return (
|
||||
<html>
|
||||
<Head />
|
||||
<Head {...this.props} />
|
||||
<body>
|
||||
<div className="error">
|
||||
<header>
|
||||
|
|
|
@ -22,6 +22,10 @@ module.exports = React.createClass({
|
|||
<h3>Where do I find a link to paste in the box?</h3>
|
||||
<p>Most music services have a "share" dialog for albums and tracks in their interface. If you have them open in a web browser instead of an app, you can simply copy and paste the address bar and we'll work out the rest.</p>
|
||||
</li>
|
||||
<li>
|
||||
<h3>Why don't you guys support Bandcamp, Amazon Music, Sony Music Unlimited… ?</h3>
|
||||
<p>Let me stop you there. <a href="https://github.com/kudos/match.audio">Match Audio is open source</a>, that means any capable programmer who wants to add other music services can look at our code and submit changes. If you're not a programmer, you can always <a href="https://github.com/kudos/match.audio/issues">submit a request</a> and maybe we'll do it for you.</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -47,7 +47,8 @@ var SearchForm = React.createClass({
|
|||
|
||||
getInitialState: function () {
|
||||
return {
|
||||
submitting: true
|
||||
submitting: true,
|
||||
error: false
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -69,7 +70,7 @@ var SearchForm = React.createClass({
|
|||
submitting: false
|
||||
});
|
||||
if (res.body.error) {
|
||||
return alert(res.body.error.message)
|
||||
that.setState({error: res.body.error.message});
|
||||
}
|
||||
that.transitionTo("share", res.body);
|
||||
});
|
||||
|
@ -77,7 +78,8 @@ var SearchForm = React.createClass({
|
|||
|
||||
componentDidMount: function () {
|
||||
this.setState({
|
||||
submitting: false
|
||||
submitting: false,
|
||||
error: false
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -90,6 +92,9 @@ var SearchForm = React.createClass({
|
|||
<input type="submit" className="btn btn-lg btn-custom" value="Share Music" disabled={this.state.submitting} />
|
||||
</span>
|
||||
</div>
|
||||
<div className={this.state.error ? "alert alert-warning" : ""} role="alert">
|
||||
{this.state.error}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue