mirror of https://github.com/calzoneman/sync.git
Refactor get-info module to be a Server property
This commit is contained in:
parent
aa3234eb6a
commit
1aeec527f6
127
channel.js
127
channel.js
|
@ -15,7 +15,6 @@ var Poll = require("./poll.js").Poll;
|
||||||
var Media = require("./media.js").Media;
|
var Media = require("./media.js").Media;
|
||||||
var formatTime = require("./media.js").formatTime;
|
var formatTime = require("./media.js").formatTime;
|
||||||
var Logger = require("./logger.js");
|
var Logger = require("./logger.js");
|
||||||
var InfoGetter = require("./get-info.js");
|
|
||||||
var Rank = require("./rank.js");
|
var Rank = require("./rank.js");
|
||||||
var Auth = require("./auth.js");
|
var Auth = require("./auth.js");
|
||||||
var ChatCommand = require("./chatcommand.js");
|
var ChatCommand = require("./chatcommand.js");
|
||||||
|
@ -527,7 +526,7 @@ Channel.prototype.search = function(query, callback) {
|
||||||
if(query.trim() == "") {
|
if(query.trim() == "") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
InfoGetter.Getters["ytSearch"](query, function(err, vids) {
|
this.server.infogetter.Getters["ytSearch"](query, function(err, vids) {
|
||||||
if(!err) {
|
if(!err) {
|
||||||
callback(vids);
|
callback(vids);
|
||||||
}
|
}
|
||||||
|
@ -1056,130 +1055,6 @@ Channel.prototype.autoTemp = function(item, user) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Channel.prototype.enqueue = function(data, user, callback) {
|
|
||||||
var after = "";
|
|
||||||
var current = this.playlist.current;
|
|
||||||
if(data.pos == "next") {
|
|
||||||
after = current ? current.uid : "prepend";
|
|
||||||
}
|
|
||||||
else if(data.pos == "end") {
|
|
||||||
after = "append";
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isLive(data.type) && !this.hasPermission(user, "playlistaddlive")) {
|
|
||||||
user.socket.emit("queueFail", "You don't have permission to queue livestreams");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prefer cache over looking up new data
|
|
||||||
if(data.id in this.library) {
|
|
||||||
var media = this.library[data.id].dup();
|
|
||||||
var item = this.playlist.makeItem(media);
|
|
||||||
item.queueby = user ? user.name : "";
|
|
||||||
this.autoTemp(item, user);
|
|
||||||
this.queueAdd(item, after);
|
|
||||||
this.logger.log("*** Queued from cache: id=" + data.id);
|
|
||||||
if(callback)
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
switch(data.type) {
|
|
||||||
case "yt":
|
|
||||||
case "yp":
|
|
||||||
case "vi":
|
|
||||||
case "dm":
|
|
||||||
case "sc":
|
|
||||||
InfoGetter.getMedia(data.id, data.type, function(err, media) {
|
|
||||||
if(err) {
|
|
||||||
if(callback)
|
|
||||||
callback();
|
|
||||||
if(err === true)
|
|
||||||
err = false;
|
|
||||||
user.socket.emit("queueFail", err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var item = this.playlist.makeItem(media);
|
|
||||||
item.queueby = user ? user.name : "";
|
|
||||||
this.autoTemp(item, user);
|
|
||||||
this.queueAdd(item, after);
|
|
||||||
this.cacheMedia(media);
|
|
||||||
if(data.type == "yp")
|
|
||||||
after = item.uid;
|
|
||||||
if(callback)
|
|
||||||
callback();
|
|
||||||
}.bind(this));
|
|
||||||
break;
|
|
||||||
case "li":
|
|
||||||
var media = new Media(data.id, "Livestream.com - " + data.id, "--:--", "li");
|
|
||||||
var item = this.playlist.makeItem(media);
|
|
||||||
item.queueby = user ? user.name : "";
|
|
||||||
this.autoTemp(item, user);
|
|
||||||
this.queueAdd(item, after);
|
|
||||||
if(callback)
|
|
||||||
callback();
|
|
||||||
break;
|
|
||||||
case "tw":
|
|
||||||
var media = new Media(data.id, "Twitch.tv - " + data.id, "--:--", "tw");
|
|
||||||
var item = this.playlist.makeItem(media);
|
|
||||||
item.queueby = user ? user.name : "";
|
|
||||||
this.autoTemp(item, user);
|
|
||||||
this.queueAdd(item, after);
|
|
||||||
if(callback)
|
|
||||||
callback();
|
|
||||||
break;
|
|
||||||
case "jt":
|
|
||||||
var media = new Media(data.id, "Justin.tv - " + data.id, "--:--", "jt");
|
|
||||||
var item = this.playlist.makeItem(media);
|
|
||||||
item.queueby = user ? user.name : "";
|
|
||||||
this.autoTemp(item, user);
|
|
||||||
this.queueAdd(item, after);
|
|
||||||
if(callback)
|
|
||||||
callback();
|
|
||||||
break;
|
|
||||||
case "us":
|
|
||||||
InfoGetter.getUstream(data.id, function(id) {
|
|
||||||
var media = new Media(id, "Ustream.tv - " + data.id, "--:--", "us");
|
|
||||||
var item = this.playlist.makeItem(media);
|
|
||||||
item.queueby = user ? user.name : "";
|
|
||||||
this.autoTemp(item, user);
|
|
||||||
this.queueAdd(item, after);
|
|
||||||
if(callback)
|
|
||||||
callback();
|
|
||||||
}.bind(this));
|
|
||||||
break;
|
|
||||||
case "rt":
|
|
||||||
var media = new Media(data.id, "Livestream", "--:--", "rt");
|
|
||||||
var item = this.playlist.makeItem(media);
|
|
||||||
item.queueby = user ? user.name : "";
|
|
||||||
this.autoTemp(item, user);
|
|
||||||
this.queueAdd(item, after);
|
|
||||||
if(callback)
|
|
||||||
callback();
|
|
||||||
break;
|
|
||||||
case "jw":
|
|
||||||
var media = new Media(data.id, "JWPlayer Stream - " + data.id, "--:--", "jw");
|
|
||||||
var item = this.playlist.makeItem(media);
|
|
||||||
item.queueby = user ? user.name : "";
|
|
||||||
this.autoTemp(item, user);
|
|
||||||
this.queueAdd(item, after);
|
|
||||||
if(callback)
|
|
||||||
callback();
|
|
||||||
break;
|
|
||||||
case "im":
|
|
||||||
var media = new Media(data.id, "Imgur Album", "--:--", "im");
|
|
||||||
var item = this.playlist.makeItem(media);
|
|
||||||
item.queueby = user ? user.name : "";
|
|
||||||
this.autoTemp(item, user);
|
|
||||||
this.queueAdd(item, after);
|
|
||||||
if(callback)
|
|
||||||
callback();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Channel.prototype.tryQueue = function(user, data) {
|
Channel.prototype.tryQueue = function(user, data) {
|
||||||
if(!this.hasPermission(user, "playlistadd")) {
|
if(!this.hasPermission(user, "playlistadd")) {
|
||||||
return;
|
return;
|
||||||
|
|
847
get-info.js
847
get-info.js
|
@ -14,361 +14,98 @@ var https = require("https");
|
||||||
var Logger = require("./logger.js");
|
var Logger = require("./logger.js");
|
||||||
var Media = require("./media.js").Media;
|
var Media = require("./media.js").Media;
|
||||||
|
|
||||||
var Server = false;
|
module.exports = function (Server) {
|
||||||
function init(srv) {
|
function urlRetrieve(transport, options, callback) {
|
||||||
Server = srv;
|
var req = transport.request(options, function (res) {
|
||||||
}
|
var buffer = "";
|
||||||
exports.init = init;
|
res.setEncoding("utf-8");
|
||||||
|
res.on("data", function (chunk) {
|
||||||
function urlRetrieve(transport, options, callback) {
|
buffer += chunk;
|
||||||
var req = transport.request(options, function (res) {
|
});
|
||||||
var buffer = "";
|
res.on("end", function () {
|
||||||
res.setEncoding("utf-8");
|
callback(res.statusCode, buffer);
|
||||||
res.on("data", function (chunk) {
|
});
|
||||||
buffer += chunk;
|
|
||||||
});
|
});
|
||||||
res.on("end", function () {
|
|
||||||
callback(res.statusCode, buffer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
req.end();
|
req.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
var Getters = {
|
var Getters = {
|
||||||
/* youtube.com */
|
/* youtube.com */
|
||||||
yt: function (id, callback) {
|
yt: function (id, callback) {
|
||||||
if(Server.cfg["ytapikey"]) {
|
if(Server.cfg["ytapikey"]) {
|
||||||
Getters["ytv3"](id, callback);
|
Getters["ytv3"](id, callback);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var options = {
|
|
||||||
host: "gdata.youtube.com",
|
|
||||||
port: 443,
|
|
||||||
path: "/feeds/api/videos/" + id + "?v=2&alt=json",
|
|
||||||
method: "GET",
|
|
||||||
dataType: "jsonp",
|
|
||||||
timeout: 1000
|
|
||||||
};
|
|
||||||
|
|
||||||
urlRetrieve(https, options, function (status, data) {
|
|
||||||
if(status === 404) {
|
|
||||||
callback("Video not found", null);
|
|
||||||
return;
|
|
||||||
} else if(status === 403) {
|
|
||||||
callbacK("Private video", null);
|
|
||||||
return;
|
|
||||||
} else if(status !== 200) {
|
|
||||||
callback(true, null);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
var options = {
|
||||||
data = JSON.parse(data);
|
host: "gdata.youtube.com",
|
||||||
var seconds = data.entry.media$group.yt$duration.seconds;
|
|
||||||
var title = data.entry.title.$t;
|
|
||||||
var media = new Media(id, title, seconds, "yt");
|
|
||||||
callback(false, media);
|
|
||||||
} catch(e) {
|
|
||||||
// Gdata version 2 has the rather silly habit of
|
|
||||||
// returning error codes in XML when I explicitly asked
|
|
||||||
// for JSON
|
|
||||||
var m = buffer.match(/<internalReason>([^<]+)<\/internalReason>/);
|
|
||||||
if(m === null)
|
|
||||||
m = buffer.match(/<code>([^<]+)<\/code>/);
|
|
||||||
|
|
||||||
var err = true;
|
|
||||||
if(m) {
|
|
||||||
if(m[1] === "too_many_recent_calls") {
|
|
||||||
err = "YouTube is throttling the server right "+
|
|
||||||
"now for making too many requests. "+
|
|
||||||
"Please try again in a moment.";
|
|
||||||
} else {
|
|
||||||
err = m[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(err, null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/* youtube.com API v3 (requires API key) */
|
|
||||||
ytv3: function (id, callback) {
|
|
||||||
var params = [
|
|
||||||
"part=" + encodeURIComponent("id,snippet,contentDetails"),
|
|
||||||
"id=" + id,
|
|
||||||
"key=" + Server.cfg["ytapikey"]
|
|
||||||
].join("&");
|
|
||||||
var options = {
|
|
||||||
host: "www.googleapis.com",
|
|
||||||
port: 443,
|
|
||||||
path: "/youtube/v3/videos?" + params,
|
|
||||||
method: "GET",
|
|
||||||
dataType: "jsonp",
|
|
||||||
timeout: 1000
|
|
||||||
};
|
|
||||||
|
|
||||||
urlRetrieve(https, options, function (status, data) {
|
|
||||||
if(status !== 200) {
|
|
||||||
callback(true, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = JSON.parse(data);
|
|
||||||
// I am a bit disappointed that the API v3 just doesn't
|
|
||||||
// return anything in any error case
|
|
||||||
if(data.pageInfo.totalResults !== 1) {
|
|
||||||
callback(true, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var vid = data.items[0];
|
|
||||||
var title = vid.snippet.title;
|
|
||||||
// No, it's not possible to get a number representing
|
|
||||||
// the video length. Instead, I get a time of the format
|
|
||||||
// PT#M#S which represents
|
|
||||||
// "Period of Time" # Minutes, # Seconds
|
|
||||||
var m = vid.contentDetails.duration.match(/PT(\d+)M(\d+)S/);
|
|
||||||
var seconds = parseInt(m[1]) * 60 + parseInt(m[2]);
|
|
||||||
var media = new Media(id, title, seconds, "yt");
|
|
||||||
callback(false, media);
|
|
||||||
} catch(e) {
|
|
||||||
callback(true, media);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/* youtube.com playlists */
|
|
||||||
yp: function (id, callback, url) {
|
|
||||||
var path = "/feeds/api/playlists/" + id + "?v=2&alt=json";
|
|
||||||
// YouTube only returns 25 at a time, so I have to keep asking
|
|
||||||
// for more with the URL they give me
|
|
||||||
if(url !== undefined) {
|
|
||||||
path = "/" + url.split("gdata.youtube.com")[1];
|
|
||||||
}
|
|
||||||
var options = {
|
|
||||||
host: "gdata.youtube.com",
|
|
||||||
port: 443,
|
|
||||||
path: path,
|
|
||||||
method: "GET",
|
|
||||||
dataType: "jsonp",
|
|
||||||
timeout: 1000
|
|
||||||
};
|
|
||||||
|
|
||||||
urlRetrieve(https, options, function (status, data) {
|
|
||||||
if(status === 404) {
|
|
||||||
callback("Playlist not found", null);
|
|
||||||
return;
|
|
||||||
} else if(status === 403) {
|
|
||||||
callback("Playlist is private", null);
|
|
||||||
return;
|
|
||||||
} else if(status !== 200) {
|
|
||||||
callback(true, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = JSON.parse(data);
|
|
||||||
var vids = [];
|
|
||||||
for(var i in data.feed.entry) {
|
|
||||||
try {
|
|
||||||
var item = data.feed.entry[i];
|
|
||||||
var id = item.media$group.yt$videoid.$t;
|
|
||||||
var title = item.title.$t;
|
|
||||||
var seconds = item.media$group.yt$duration.seconds;
|
|
||||||
var media = new Media(id, title, seconds, "yt");
|
|
||||||
vids.push(media);
|
|
||||||
} catch(e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(false, vids);
|
|
||||||
|
|
||||||
var links = data.feed.link;
|
|
||||||
for(var i in links) {
|
|
||||||
if(links[i].rel === "next")
|
|
||||||
Getters["yp"](id, callback, links[i].href);
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
callback(true, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/* youtube.com search */
|
|
||||||
ytSearch: function (terms, callback) {
|
|
||||||
for(var i in terms)
|
|
||||||
terms[i] = encodeURIComponent(terms[i]);
|
|
||||||
var query = terms.join("+");
|
|
||||||
|
|
||||||
var options = {
|
|
||||||
host: "gdata.youtube.com",
|
|
||||||
port: 443,
|
|
||||||
path: "/feeds/api/videos/?q=" + query + "&v=2&alt=json",
|
|
||||||
method: "GET",
|
|
||||||
dataType: "jsonp",
|
|
||||||
timeout: 1000
|
|
||||||
};
|
|
||||||
|
|
||||||
urlRetrieve(https, options, function (status, data) {
|
|
||||||
if(status !== 200) {
|
|
||||||
callback(true, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = JSON.parse(data);
|
|
||||||
var vids = [];
|
|
||||||
for(var i in data.feed.entry) {
|
|
||||||
try {
|
|
||||||
var item = data.feed.entry[i];
|
|
||||||
var id = item.media$group.yt$videoid.$t;
|
|
||||||
var title = item.title.$t;
|
|
||||||
var seconds = item.media$group.yt$duration.seconds;
|
|
||||||
var media = new Media(id, title, seconds, "yt");
|
|
||||||
media.thumb = item.media$group.media$thumbnail[0];
|
|
||||||
vids.push(media);
|
|
||||||
} catch(e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(false, vids);
|
|
||||||
} catch(e) {
|
|
||||||
callback(true, null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/* vimeo.com */
|
|
||||||
vi: function (id, callback) {
|
|
||||||
var options = {
|
|
||||||
host: "vimeo.com",
|
|
||||||
port: 443,
|
|
||||||
path: "/api/v2/video/" + id + ".json",
|
|
||||||
method: "GET",
|
|
||||||
dataType: "jsonp",
|
|
||||||
timeout: 1000
|
|
||||||
};
|
|
||||||
|
|
||||||
urlRetrieve(https, options, function (status, data) {
|
|
||||||
if(status === 404) {
|
|
||||||
callback("Video not found", null);
|
|
||||||
return;
|
|
||||||
} else if(status === 403) {
|
|
||||||
callbacK("Private video", null);
|
|
||||||
return;
|
|
||||||
} else if(status !== 200) {
|
|
||||||
callback(true, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = JSON.parse(data);
|
|
||||||
data = data[0];
|
|
||||||
var seconds = data.duration;
|
|
||||||
var title = data.title;
|
|
||||||
var media = new Media(id, title, seconds, "vi");
|
|
||||||
callback(false, media);
|
|
||||||
} catch(e) {
|
|
||||||
var err = true;
|
|
||||||
if(buffer.match(/not found/))
|
|
||||||
err = "Video not found";
|
|
||||||
|
|
||||||
callback(err, null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/* dailymotion.com */
|
|
||||||
dm: function (id, callback) {
|
|
||||||
// Dailymotion's API is an example of an API done right
|
|
||||||
// - Supports SSL
|
|
||||||
// - I can ask for exactly which fields I want
|
|
||||||
// - URL is simple
|
|
||||||
// - Field names are sensible
|
|
||||||
// Other media providers take notes, please
|
|
||||||
var options = {
|
|
||||||
host: "api.dailymotion.com",
|
|
||||||
port: 443,
|
|
||||||
path: "/video/" + id + "?fields=duration,title",
|
|
||||||
method: "GET",
|
|
||||||
dataType: "jsonp",
|
|
||||||
timeout: 1000
|
|
||||||
};
|
|
||||||
|
|
||||||
urlRetrieve(https, options, function (status, data) {
|
|
||||||
if(status !== 200) {
|
|
||||||
callback(true, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = JSON.parse(data);
|
|
||||||
var title = data.title;
|
|
||||||
var seconds = data.duration;
|
|
||||||
if(title === "Deleted video" && seconds === 10) {
|
|
||||||
callback("Video not found", null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var media = new Media(id, title, seconds, "dm");
|
|
||||||
callback(false, media);
|
|
||||||
} catch(e) {
|
|
||||||
callback(err, null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/* soundcloud.com */
|
|
||||||
sc: function (id, callback) {
|
|
||||||
// Soundcloud's API is badly designed and badly documented
|
|
||||||
// In order to lookup track data from a URL, I have to first
|
|
||||||
// make a call to /resolve to get the track id, then make a second
|
|
||||||
// call to /tracks/{track.id} to actally get useful data
|
|
||||||
// This is a waste of bandwidth and a pain in the ass
|
|
||||||
|
|
||||||
const SC_CLIENT = "2e0c82ab5a020f3a7509318146128abd";
|
|
||||||
|
|
||||||
var options = {
|
|
||||||
host: "api.soundcloud.com",
|
|
||||||
port: 443,
|
|
||||||
path: "/resolve.json?url=" + id + "&client_id=" + SC_CLIENT,
|
|
||||||
method: "GET",
|
|
||||||
dataType: "jsonp",
|
|
||||||
timeout: 1000
|
|
||||||
};
|
|
||||||
|
|
||||||
urlRetrieve(https, options, function (status, data) {
|
|
||||||
if(status === 404) {
|
|
||||||
callback("Sound not found", null);
|
|
||||||
return;
|
|
||||||
} else if(status !== 302) {
|
|
||||||
callback(true, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var track = null;
|
|
||||||
try {
|
|
||||||
data = JSON.parse(data);
|
|
||||||
track = data.location;
|
|
||||||
} catch(e) {
|
|
||||||
callback(true, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var options2 = {
|
|
||||||
host: "api.soundcloud.com",
|
|
||||||
port: 443,
|
port: 443,
|
||||||
path: track,
|
path: "/feeds/api/videos/" + id + "?v=2&alt=json",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
dataType: "jsonp",
|
dataType: "jsonp",
|
||||||
timeout: 1000
|
timeout: 1000
|
||||||
};
|
};
|
||||||
|
|
||||||
// I want to get off async's wild ride
|
urlRetrieve(https, options, function (status, data) {
|
||||||
urlRetrieve(https, options2, function (status, data) {
|
if(status === 404) {
|
||||||
|
callback("Video not found", null);
|
||||||
|
return;
|
||||||
|
} else if(status === 403) {
|
||||||
|
callbacK("Private video", null);
|
||||||
|
return;
|
||||||
|
} else if(status !== 200) {
|
||||||
|
callback(true, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
var seconds = data.entry.media$group.yt$duration.seconds;
|
||||||
|
var title = data.entry.title.$t;
|
||||||
|
var media = new Media(id, title, seconds, "yt");
|
||||||
|
callback(false, media);
|
||||||
|
} catch(e) {
|
||||||
|
// Gdata version 2 has the rather silly habit of
|
||||||
|
// returning error codes in XML when I explicitly asked
|
||||||
|
// for JSON
|
||||||
|
var m = buffer.match(/<internalReason>([^<]+)<\/internalReason>/);
|
||||||
|
if(m === null)
|
||||||
|
m = buffer.match(/<code>([^<]+)<\/code>/);
|
||||||
|
|
||||||
|
var err = true;
|
||||||
|
if(m) {
|
||||||
|
if(m[1] === "too_many_recent_calls") {
|
||||||
|
err = "YouTube is throttling the server right "+
|
||||||
|
"now for making too many requests. "+
|
||||||
|
"Please try again in a moment.";
|
||||||
|
} else {
|
||||||
|
err = m[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(err, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/* youtube.com API v3 (requires API key) */
|
||||||
|
ytv3: function (id, callback) {
|
||||||
|
var params = [
|
||||||
|
"part=" + encodeURIComponent("id,snippet,contentDetails"),
|
||||||
|
"id=" + id,
|
||||||
|
"key=" + Server.cfg["ytapikey"]
|
||||||
|
].join("&");
|
||||||
|
var options = {
|
||||||
|
host: "www.googleapis.com",
|
||||||
|
port: 443,
|
||||||
|
path: "/youtube/v3/videos?" + params,
|
||||||
|
method: "GET",
|
||||||
|
dataType: "jsonp",
|
||||||
|
timeout: 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
urlRetrieve(https, options, function (status, data) {
|
||||||
if(status !== 200) {
|
if(status !== 200) {
|
||||||
callback(true, null);
|
callback(true, null);
|
||||||
return;
|
return;
|
||||||
|
@ -376,99 +113,359 @@ var Getters = {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(data);
|
data = JSON.parse(data);
|
||||||
// Duration is in ms, but I want s
|
// I am a bit disappointed that the API v3 just doesn't
|
||||||
var seconds = data.duration / 1000;
|
// return anything in any error case
|
||||||
var title = data.title;
|
if(data.pageInfo.totalResults !== 1) {
|
||||||
var media = new Media(id, title, seconds, "sc");
|
callback(true, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var vid = data.items[0];
|
||||||
|
var title = vid.snippet.title;
|
||||||
|
// No, it's not possible to get a number representing
|
||||||
|
// the video length. Instead, I get a time of the format
|
||||||
|
// PT#M#S which represents
|
||||||
|
// "Period of Time" # Minutes, # Seconds
|
||||||
|
var m = vid.contentDetails.duration.match(/PT(\d+)M(\d+)S/);
|
||||||
|
var seconds = parseInt(m[1]) * 60 + parseInt(m[2]);
|
||||||
|
var media = new Media(id, title, seconds, "yt");
|
||||||
callback(false, media);
|
callback(false, media);
|
||||||
|
} catch(e) {
|
||||||
|
callback(true, media);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/* youtube.com playlists */
|
||||||
|
yp: function (id, callback, url) {
|
||||||
|
var path = "/feeds/api/playlists/" + id + "?v=2&alt=json";
|
||||||
|
// YouTube only returns 25 at a time, so I have to keep asking
|
||||||
|
// for more with the URL they give me
|
||||||
|
if(url !== undefined) {
|
||||||
|
path = "/" + url.split("gdata.youtube.com")[1];
|
||||||
|
}
|
||||||
|
var options = {
|
||||||
|
host: "gdata.youtube.com",
|
||||||
|
port: 443,
|
||||||
|
path: path,
|
||||||
|
method: "GET",
|
||||||
|
dataType: "jsonp",
|
||||||
|
timeout: 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
urlRetrieve(https, options, function (status, data) {
|
||||||
|
if(status === 404) {
|
||||||
|
callback("Playlist not found", null);
|
||||||
|
return;
|
||||||
|
} else if(status === 403) {
|
||||||
|
callback("Playlist is private", null);
|
||||||
|
return;
|
||||||
|
} else if(status !== 200) {
|
||||||
|
callback(true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
var vids = [];
|
||||||
|
for(var i in data.feed.entry) {
|
||||||
|
try {
|
||||||
|
var item = data.feed.entry[i];
|
||||||
|
var id = item.media$group.yt$videoid.$t;
|
||||||
|
var title = item.title.$t;
|
||||||
|
var seconds = item.media$group.yt$duration.seconds;
|
||||||
|
var media = new Media(id, title, seconds, "yt");
|
||||||
|
vids.push(media);
|
||||||
|
} catch(e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(false, vids);
|
||||||
|
|
||||||
|
var links = data.feed.link;
|
||||||
|
for(var i in links) {
|
||||||
|
if(links[i].rel === "next")
|
||||||
|
Getters["yp"](id, callback, links[i].href);
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
callback(true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/* youtube.com search */
|
||||||
|
ytSearch: function (terms, callback) {
|
||||||
|
for(var i in terms)
|
||||||
|
terms[i] = encodeURIComponent(terms[i]);
|
||||||
|
var query = terms.join("+");
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
host: "gdata.youtube.com",
|
||||||
|
port: 443,
|
||||||
|
path: "/feeds/api/videos/?q=" + query + "&v=2&alt=json",
|
||||||
|
method: "GET",
|
||||||
|
dataType: "jsonp",
|
||||||
|
timeout: 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
urlRetrieve(https, options, function (status, data) {
|
||||||
|
if(status !== 200) {
|
||||||
|
callback(true, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
var vids = [];
|
||||||
|
for(var i in data.feed.entry) {
|
||||||
|
try {
|
||||||
|
var item = data.feed.entry[i];
|
||||||
|
var id = item.media$group.yt$videoid.$t;
|
||||||
|
var title = item.title.$t;
|
||||||
|
var seconds = item.media$group.yt$duration.seconds;
|
||||||
|
var media = new Media(id, title, seconds, "yt");
|
||||||
|
media.thumb = item.media$group.media$thumbnail[0];
|
||||||
|
vids.push(media);
|
||||||
|
} catch(e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(false, vids);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
callback(true, null);
|
callback(true, null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
});
|
/* vimeo.com */
|
||||||
},
|
vi: function (id, callback) {
|
||||||
|
var options = {
|
||||||
|
host: "vimeo.com",
|
||||||
|
port: 443,
|
||||||
|
path: "/api/v2/video/" + id + ".json",
|
||||||
|
method: "GET",
|
||||||
|
dataType: "jsonp",
|
||||||
|
timeout: 1000
|
||||||
|
};
|
||||||
|
|
||||||
/* livestream.com */
|
urlRetrieve(https, options, function (status, data) {
|
||||||
li: function (id, callback) {
|
if(status === 404) {
|
||||||
var title = "Livestream.com - " + id;
|
callback("Video not found", null);
|
||||||
var media = new Media(id, title, "--:--", "li");
|
return;
|
||||||
callback(false, media);
|
} else if(status === 403) {
|
||||||
},
|
callbacK("Private video", null);
|
||||||
|
return;
|
||||||
|
} else if(status !== 200) {
|
||||||
|
callback(true, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* twitch.tv */
|
try {
|
||||||
tw: function (id, callback) {
|
data = JSON.parse(data);
|
||||||
var title = "Twitch.tv - " + id;
|
data = data[0];
|
||||||
var media = new Media(id, title, "--:--", "tw");
|
var seconds = data.duration;
|
||||||
callback(false, media);
|
var title = data.title;
|
||||||
},
|
var media = new Media(id, title, seconds, "vi");
|
||||||
|
callback(false, media);
|
||||||
|
} catch(e) {
|
||||||
|
var err = true;
|
||||||
|
if(buffer.match(/not found/))
|
||||||
|
err = "Video not found";
|
||||||
|
|
||||||
/* justin.tv */
|
callback(err, null);
|
||||||
jt: function (id, callback) {
|
}
|
||||||
var title = "Justin.tv - " + id;
|
});
|
||||||
var media = new Media(id, title, "--:--", "jt");
|
},
|
||||||
callback(false, media);
|
|
||||||
},
|
|
||||||
|
|
||||||
/* ustream.tv */
|
/* dailymotion.com */
|
||||||
us: function (id, callback) {
|
dm: function (id, callback) {
|
||||||
var options = {
|
// Dailymotion's API is an example of an API done right
|
||||||
host: "www.ustream.tv",
|
// - Supports SSL
|
||||||
port: 80,
|
// - I can ask for exactly which fields I want
|
||||||
path: "/" + id,
|
// - URL is simple
|
||||||
method: "GET",
|
// - Field names are sensible
|
||||||
timeout: 1000
|
// Other media providers take notes, please
|
||||||
};
|
var options = {
|
||||||
|
host: "api.dailymotion.com",
|
||||||
|
port: 443,
|
||||||
|
path: "/video/" + id + "?fields=duration,title",
|
||||||
|
method: "GET",
|
||||||
|
dataType: "jsonp",
|
||||||
|
timeout: 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
urlRetrieve(https, options, function (status, data) {
|
||||||
|
if(status !== 200) {
|
||||||
|
callback(true, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
var title = data.title;
|
||||||
|
var seconds = data.duration;
|
||||||
|
if(title === "Deleted video" && seconds === 10) {
|
||||||
|
callback("Video not found", null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var media = new Media(id, title, seconds, "dm");
|
||||||
|
callback(false, media);
|
||||||
|
} catch(e) {
|
||||||
|
callback(err, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/* soundcloud.com */
|
||||||
|
sc: function (id, callback) {
|
||||||
|
// Soundcloud's API is badly designed and badly documented
|
||||||
|
// In order to lookup track data from a URL, I have to first
|
||||||
|
// make a call to /resolve to get the track id, then make a second
|
||||||
|
// call to /tracks/{track.id} to actally get useful data
|
||||||
|
// This is a waste of bandwidth and a pain in the ass
|
||||||
|
|
||||||
|
const SC_CLIENT = "2e0c82ab5a020f3a7509318146128abd";
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
host: "api.soundcloud.com",
|
||||||
|
port: 443,
|
||||||
|
path: "/resolve.json?url=" + id + "&client_id=" + SC_CLIENT,
|
||||||
|
method: "GET",
|
||||||
|
dataType: "jsonp",
|
||||||
|
timeout: 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
urlRetrieve(https, options, function (status, data) {
|
||||||
|
if(status === 404) {
|
||||||
|
callback("Sound not found", null);
|
||||||
|
return;
|
||||||
|
} else if(status !== 302) {
|
||||||
|
callback(true, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var track = null;
|
||||||
|
try {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
track = data.location;
|
||||||
|
} catch(e) {
|
||||||
|
callback(true, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var options2 = {
|
||||||
|
host: "api.soundcloud.com",
|
||||||
|
port: 443,
|
||||||
|
path: track,
|
||||||
|
method: "GET",
|
||||||
|
dataType: "jsonp",
|
||||||
|
timeout: 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
// I want to get off async's wild ride
|
||||||
|
urlRetrieve(https, options2, function (status, data) {
|
||||||
|
if(status !== 200) {
|
||||||
|
callback(true, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
// Duration is in ms, but I want s
|
||||||
|
var seconds = data.duration / 1000;
|
||||||
|
var title = data.title;
|
||||||
|
var media = new Media(id, title, seconds, "sc");
|
||||||
|
callback(false, media);
|
||||||
|
} catch(e) {
|
||||||
|
callback(true, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/* livestream.com */
|
||||||
|
li: function (id, callback) {
|
||||||
|
var title = "Livestream.com - " + id;
|
||||||
|
var media = new Media(id, title, "--:--", "li");
|
||||||
|
callback(false, media);
|
||||||
|
},
|
||||||
|
|
||||||
|
/* twitch.tv */
|
||||||
|
tw: function (id, callback) {
|
||||||
|
var title = "Twitch.tv - " + id;
|
||||||
|
var media = new Media(id, title, "--:--", "tw");
|
||||||
|
callback(false, media);
|
||||||
|
},
|
||||||
|
|
||||||
|
/* justin.tv */
|
||||||
|
jt: function (id, callback) {
|
||||||
|
var title = "Justin.tv - " + id;
|
||||||
|
var media = new Media(id, title, "--:--", "jt");
|
||||||
|
callback(false, media);
|
||||||
|
},
|
||||||
|
|
||||||
|
/* ustream.tv */
|
||||||
|
us: function (id, callback) {
|
||||||
|
var options = {
|
||||||
|
host: "www.ustream.tv",
|
||||||
|
port: 80,
|
||||||
|
path: "/" + id,
|
||||||
|
method: "GET",
|
||||||
|
timeout: 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
urlRetrieve(http, options, function (status, data) {
|
||||||
|
if(status !== 200) {
|
||||||
|
callback(true, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regexing the ID out of the HTML because
|
||||||
|
// Ustream's API is so horribly documented
|
||||||
|
// I literally could not figure out how to retrieve
|
||||||
|
// this information.
|
||||||
|
//
|
||||||
|
// [](/eatadick)
|
||||||
|
var m = data.match(/cid":([0-9]+)/);
|
||||||
|
if(m) {
|
||||||
|
var title = "Ustream.tv - " + id;
|
||||||
|
var media = new Media(m[1], title, "--:--", "us");
|
||||||
|
callback(false, media);
|
||||||
|
}
|
||||||
|
|
||||||
urlRetrieve(http, options, function (status, data) {
|
|
||||||
if(status !== 200) {
|
|
||||||
callback(true, null);
|
callback(true, null);
|
||||||
return;
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/* JWPlayer */
|
||||||
|
jw: function (id, callback) {
|
||||||
|
var title = "JWPlayer - " + id;
|
||||||
|
var media = new Media(id, title, "--:--", "jw");
|
||||||
|
callback(false, media);
|
||||||
|
},
|
||||||
|
|
||||||
|
/* rtmp stream */
|
||||||
|
rt: function (id, callback) {
|
||||||
|
var title = "Livestream";
|
||||||
|
var media = new Media(id, title, "--:--", "rt");
|
||||||
|
callback(false, media);
|
||||||
|
},
|
||||||
|
|
||||||
|
/* imgur.com albums */
|
||||||
|
im: function (id, callback) {
|
||||||
|
var title = "Imgur Album - " + id;
|
||||||
|
var media = new Media(id, title, "--:--", "im");
|
||||||
|
callback(false, media);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
Getters: Getters,
|
||||||
|
getMedia: function (id, type, callback) {
|
||||||
|
if(type in this.Getters) {
|
||||||
|
this.Getters[type](id, callback);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Regexing the ID out of the HTML because
|
};
|
||||||
// Ustream's API is so horribly documented
|
|
||||||
// I literally could not figure out how to retrieve
|
|
||||||
// this information.
|
|
||||||
//
|
|
||||||
// [](/eatadick)
|
|
||||||
var m = data.match(/cid":([0-9]+)/);
|
|
||||||
if(m) {
|
|
||||||
var title = "Ustream.tv - " + id;
|
|
||||||
var media = new Media(m[1], title, "--:--", "us");
|
|
||||||
callback(false, media);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(true, null);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/* JWPlayer */
|
|
||||||
jw: function (id, callback) {
|
|
||||||
var title = "JWPlayer - " + id;
|
|
||||||
var media = new Media(id, title, "--:--", "jw");
|
|
||||||
callback(false, media);
|
|
||||||
},
|
|
||||||
|
|
||||||
/* rtmp stream */
|
|
||||||
rt: function (id, callback) {
|
|
||||||
var title = "Livestream";
|
|
||||||
var media = new Media(id, title, "--:--", "rt");
|
|
||||||
callback(false, media);
|
|
||||||
},
|
|
||||||
|
|
||||||
/* imgur.com albums */
|
|
||||||
im: function (id, callback) {
|
|
||||||
var title = "Imgur Album - " + id;
|
|
||||||
var media = new Media(id, title, "--:--", "im");
|
|
||||||
callback(false, media);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.Getters = Getters;
|
|
||||||
|
|
||||||
exports.getMedia = function(id, type, callback) {
|
|
||||||
if(type in Getters) {
|
|
||||||
Getters[type](id, callback);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
38
playlist.js
38
playlist.js
|
@ -11,7 +11,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
||||||
|
|
||||||
ULList = require("./ullist").ULList;
|
ULList = require("./ullist").ULList;
|
||||||
var Media = require("./media").Media;
|
var Media = require("./media").Media;
|
||||||
var InfoGetter = require("./get-info");
|
|
||||||
var AllPlaylists = {};
|
var AllPlaylists = {};
|
||||||
|
|
||||||
function PlaylistItem(media, uid) {
|
function PlaylistItem(media, uid) {
|
||||||
|
@ -56,24 +55,23 @@ function Playlist(chan) {
|
||||||
this._qaInterval = false;
|
this._qaInterval = false;
|
||||||
AllPlaylists[name] = this;
|
AllPlaylists[name] = this;
|
||||||
|
|
||||||
if(chan) {
|
this.channel = chan;
|
||||||
this.channel = chan;
|
this.server = chan.server;
|
||||||
var pl = this;
|
var pl = this;
|
||||||
this.on("mediaUpdate", function(m) {
|
this.on("mediaUpdate", function(m) {
|
||||||
chan.sendAll("mediaUpdate", m.timeupdate());
|
chan.sendAll("mediaUpdate", m.timeupdate());
|
||||||
|
});
|
||||||
|
this.on("changeMedia", function(m) {
|
||||||
|
chan.onVideoChange();
|
||||||
|
chan.sendAll("setCurrent", pl.current.uid);
|
||||||
|
chan.sendAll("changeMedia", m.fullupdate());
|
||||||
|
});
|
||||||
|
this.on("remove", function(item) {
|
||||||
|
chan.broadcastPlaylistMeta();
|
||||||
|
chan.sendAll("delete", {
|
||||||
|
uid: item.uid
|
||||||
});
|
});
|
||||||
this.on("changeMedia", function(m) {
|
});
|
||||||
chan.onVideoChange();
|
|
||||||
chan.sendAll("setCurrent", pl.current.uid);
|
|
||||||
chan.sendAll("changeMedia", m.fullupdate());
|
|
||||||
});
|
|
||||||
this.on("remove", function(item) {
|
|
||||||
chan.broadcastPlaylistMeta();
|
|
||||||
chan.sendAll("delete", {
|
|
||||||
uid: item.uid
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Playlist.prototype.queueAction = function(data) {
|
Playlist.prototype.queueAction = function(data) {
|
||||||
|
@ -252,7 +250,7 @@ Playlist.prototype.addMedia = function(data, callback) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoGetter.getMedia(data.id, data.type, function(err, media) {
|
this.server.infogetter.getMedia(data.id, data.type, function(err, media) {
|
||||||
if(err) {
|
if(err) {
|
||||||
action.expire = 0;
|
action.expire = 0;
|
||||||
callback(err, null);
|
callback(err, null);
|
||||||
|
@ -321,7 +319,7 @@ Playlist.prototype.addYouTubePlaylist = function(data, callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var pl = this;
|
var pl = this;
|
||||||
InfoGetter.getMedia(data.id, data.type, function(err, vids) {
|
this.server.infogetter.getMedia(data.id, data.type, function(err, vids) {
|
||||||
if(err) {
|
if(err) {
|
||||||
callback(err, null);
|
callback(err, null);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -208,7 +208,7 @@ var Server = {
|
||||||
this.stats = require("./stats")(this);
|
this.stats = require("./stats")(this);
|
||||||
|
|
||||||
// init media retriever
|
// init media retriever
|
||||||
require("./get-info").init(this);
|
this.infogetter = require("./get-info")(this);
|
||||||
},
|
},
|
||||||
shutdown: function () {
|
shutdown: function () {
|
||||||
Logger.syslog.log("Unloading channels");
|
Logger.syslog.log("Unloading channels");
|
||||||
|
|
Loading…
Reference in New Issue