diff --git a/index.js b/index.js index e288822a..77c32321 100644 --- a/index.js +++ b/index.js @@ -11,6 +11,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI var Server = require("./lib/server"); var Config = require("./lib/config"); +var Logger = require("./lib/logger"); Config.load("cfg.json", function (cfg) { var sv = Server.init(cfg); diff --git a/lib/acp.js b/lib/acp.js index 26756fa8..7456d628 100644 --- a/lib/acp.js +++ b/lib/acp.js @@ -10,170 +10,170 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ var Logger = require("./logger"); +var Server = require("./server"); +var ActionLog = require("./actionlog"); -module.exports = function (Server) { - var db = Server.db; - var ActionLog = Server.actionlog; - return { - init: function(user) { - ActionLog.record(user.ip, user.name, "acp-init"); - user.socket.on("acp-announce", function(data) { - ActionLog.record(user.ip, user.name, "acp-announce", data); - Server.announcement = data; - Server.io.sockets.emit("announcement", data); - if (Server.cfg["enable-ssl"]) - Server.sslio.sockets.emit("announcement", data); - }); +module.exports = { + init: function (user) { + var sv = Server.getServer(); + var db = sv.db; + ActionLog.record(user.ip, user.name, "acp-init"); + user.socket.on("acp-announce", function(data) { + ActionLog.record(user.ip, user.name, "acp-announce", data); + sv.announcement = data; + sv.io.sockets.emit("announcement", data); + if (sv.cfg["enable-ssl"]) + sv.ioSecure.sockets.emit("announcement", data); + }); - user.socket.on("acp-announce-clear", function() { - ActionLog.record(user.ip, user.name, "acp-announce-clear"); - Server.announcement = null; - }); + user.socket.on("acp-announce-clear", function() { + ActionLog.record(user.ip, user.name, "acp-announce-clear"); + sv.announcement = null; + }); - user.socket.on("acp-global-ban", function(data) { - ActionLog.record(user.ip, user.name, "acp-global-ban", data.ip); - db.setGlobalIPBan(data.ip, data.note, function (err, res) { - db.listGlobalIPBans(function (err, res) { - res = res || []; - user.socket.emit("acp-global-banlist", res); - }); + user.socket.on("acp-global-ban", function(data) { + ActionLog.record(user.ip, user.name, "acp-global-ban", data.ip); + db.setGlobalIPBan(data.ip, data.note, function (err, res) { + db.listGlobalIPBans(function (err, res) { + res = res || []; + user.socket.emit("acp-global-banlist", res); }); }); + }); - user.socket.on("acp-global-unban", function(ip) { - ActionLog.record(user.ip, user.name, "acp-global-unban", ip); - db.clearGlobalIPBan(ip, function (err, res) { - db.listGlobalIPBans(function (err, res) { - res = res || []; - user.socket.emit("acp-global-banlist", res); - }); + user.socket.on("acp-global-unban", function(ip) { + ActionLog.record(user.ip, user.name, "acp-global-unban", ip); + db.clearGlobalIPBan(ip, function (err, res) { + db.listGlobalIPBans(function (err, res) { + res = res || []; + user.socket.emit("acp-global-banlist", res); }); }); + }); - db.listGlobalIPBans(function (err, res) { + db.listGlobalIPBans(function (err, res) { + res = res || []; + user.socket.emit("acp-global-banlist", res); + }); + + user.socket.on("acp-lookup-user", function(name) { + db.searchUser(name, function (err, res) { res = res || []; - user.socket.emit("acp-global-banlist", res); + user.socket.emit("acp-userdata", res); }); + }); - user.socket.on("acp-lookup-user", function(name) { - db.searchUser(name, function (err, res) { - res = res || []; - user.socket.emit("acp-userdata", res); - }); + user.socket.on("acp-lookup-channel", function (data) { + db.searchChannel(data.field, data.value, function (e, res) { + res = res || []; + user.socket.emit("acp-channeldata", res); }); + }); - user.socket.on("acp-lookup-channel", function (data) { - db.searchChannel(data.field, data.value, function (e, res) { - res = res || []; - user.socket.emit("acp-channeldata", res); - }); - }); - - user.socket.on("acp-reset-password", function(data) { - db.getGlobalRank(data.name, function (err, rank) { - if(err || rank >= user.global_rank) - return; - - db.genPasswordReset(user.ip, data.name, data.email, - function (err, hash) { - var pkt = { - success: !err - }; - - if(err) { - pkt.error = err; - } else { - pkt.hash = hash; - } - - user.socket.emit("acp-reset-password", pkt); - ActionLog.record(user.ip, user.name, - "acp-reset-password", data.name); - }); - }); - }); - - user.socket.on("acp-set-rank", function(data) { - if(data.rank < 1 || data.rank >= user.global_rank) + user.socket.on("acp-reset-password", function(data) { + db.getGlobalRank(data.name, function (err, rank) { + if(err || rank >= user.global_rank) return; - db.getGlobalRank(data.name, function (err, rank) { - if(err || rank >= user.global_rank) - return; + db.genPasswordReset(user.ip, data.name, data.email, + function (err, hash) { + var pkt = { + success: !err + }; - db.setGlobalRank(data.name, data.rank, - function (err, res) { - ActionLog.record(user.ip, user.name, "acp-set-rank", - data); - if(!err) - user.socket.emit("acp-set-rank", data); - }); + if(err) { + pkt.error = err; + } else { + pkt.hash = hash; + } + + user.socket.emit("acp-reset-password", pkt); + ActionLog.record(user.ip, user.name, + "acp-reset-password", data.name); }); }); + }); - user.socket.on("acp-list-loaded", function() { - var chans = []; - var all = Server.channels; - for(var c in all) { - var chan = all[c]; + user.socket.on("acp-set-rank", function(data) { + if(data.rank < 1 || data.rank >= user.global_rank) + return; - chans.push({ - name: chan.name, - title: chan.opts.pagetitle, - usercount: chan.users.length, - mediatitle: chan.playlist.current ? chan.playlist.current.media.title : "-", - is_public: chan.opts.show_public, - registered: chan.registered - }); - } + db.getGlobalRank(data.name, function (err, rank) { + if(err || rank >= user.global_rank) + return; - user.socket.emit("acp-list-loaded", chans); - }); - - user.socket.on("acp-channel-unload", function(data) { - if(Server.isChannelLoaded(data.name)) { - var c = Server.getChannel(data.name); - if(!c) - return; - ActionLog.record(user.ip, user.name, "acp-channel-unload"); - c.initialized = data.save; - // copy the list of users to prevent concurrent - // modification - var users = Array.prototype.slice.call(c.users); - users.forEach(function (u) { - c.kick(u, "Channel shutting down"); - }); - - // At this point c should be unloaded - // if it's still loaded, kill it - if(Server.isChannelLoaded(data.name)) - Server.unloadChannel(Server.getChannel(data.name)); - } - }); - - user.socket.on("acp-actionlog-list", function () { - ActionLog.listActionTypes(function (err, types) { + db.setGlobalRank(data.name, data.rank, + function (err, res) { + ActionLog.record(user.ip, user.name, "acp-set-rank", + data); if(!err) - user.socket.emit("acp-actionlog-list", types); + user.socket.emit("acp-set-rank", data); }); }); + }); - user.socket.on("acp-actionlog-clear", function(data) { - ActionLog.clear(data); - ActionLog.record(user.ip, user.name, "acp-actionlog-clear", data); - }); + user.socket.on("acp-list-loaded", function() { + var chans = []; + var all = sv.channels; + for(var c in all) { + var chan = all[c]; - user.socket.on("acp-actionlog-clear-one", function(data) { - ActionLog.clearOne(data); - ActionLog.record(user.ip, user.name, "acp-actionlog-clear-one", data); - }); - - user.socket.on("acp-view-stats", function () { - db.listStats(function (err, res) { - if(!err) - user.socket.emit("acp-view-stats", res); + chans.push({ + name: chan.name, + title: chan.opts.pagetitle, + usercount: chan.users.length, + mediatitle: chan.playlist.current ? chan.playlist.current.media.title : "-", + is_public: chan.opts.show_public, + registered: chan.registered }); + } + + user.socket.emit("acp-list-loaded", chans); + }); + + user.socket.on("acp-channel-unload", function(data) { + if(sv.isChannelLoaded(data.name)) { + var c = sv.getChannel(data.name); + if(!c) + return; + ActionLog.record(user.ip, user.name, "acp-channel-unload"); + c.initialized = data.save; + // copy the list of users to prevent concurrent + // modification + var users = Array.prototype.slice.call(c.users); + users.forEach(function (u) { + c.kick(u, "Channel shutting down"); + }); + + // At this point c should be unloaded + // if it's still loaded, kill it + if(sv.isChannelLoaded(data.name)) + sv.unloadChannel(sv.getChannel(data.name)); + } + }); + + user.socket.on("acp-actionlog-list", function () { + ActionLog.listActionTypes(function (err, types) { + if(!err) + user.socket.emit("acp-actionlog-list", types); }); - } + }); + + user.socket.on("acp-actionlog-clear", function(data) { + ActionLog.clear(data); + ActionLog.record(user.ip, user.name, "acp-actionlog-clear", data); + }); + + user.socket.on("acp-actionlog-clear-one", function(data) { + ActionLog.clearOne(data); + ActionLog.record(user.ip, user.name, "acp-actionlog-clear-one", data); + }); + + user.socket.on("acp-view-stats", function () { + db.listStats(function (err, res) { + if(!err) + user.socket.emit("acp-view-stats", res); + }); + }); } } diff --git a/lib/actionlog.js b/lib/actionlog.js index 4fb864b6..3f0e52b2 100644 --- a/lib/actionlog.js +++ b/lib/actionlog.js @@ -10,49 +10,53 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ var Logger = require("./logger"); +var Server = require("./server"); -module.exports = function (Server) { - var db = Server.db; - return { - record: function (ip, name, action, args) { - if(!args) +module.exports = { + record: function (ip, name, action, args) { + var db = Server.getServer().db; + if(!args) + args = ""; + else { + try { + args = JSON.stringify(args); + } catch(e) { args = ""; - else { - try { - args = JSON.stringify(args); - } catch(e) { - args = ""; - } } - - db.recordAction(ip, name, action, args); - }, - - clear: function (actions) { - db.clearActions(actions); - }, - - clearOne: function (item) { - db.clearSingleAction(item); - }, - - throttleRegistrations: function (ip, callback) { - db.recentRegistrationCount(ip, function (err, count) { - if(err) { - callback(err, null); - return; - } - - callback(null, count > 4); - }); - }, - - listActionTypes: function (callback) { - db.listActionTypes(callback); - }, - - listActions: function (types, callback) { - db.listActions(types, callback); } - }; + + db.recordAction(ip, name, action, args); + }, + + clear: function (actions) { + var db = Server.getServer().db; + db.clearActions(actions); + }, + + clearOne: function (item) { + var db = Server.getServer().db; + db.clearSingleAction(item); + }, + + throttleRegistrations: function (ip, callback) { + var db = Server.getServer().db; + db.recentRegistrationCount(ip, function (err, count) { + if(err) { + callback(err, null); + return; + } + + callback(null, count > 4); + }); + }, + + listActionTypes: function (callback) { + var db = Server.getServer().db; + db.listActionTypes(callback); + }, + + listActions: function (types, callback) { + var db = Server.getServer().db; + db.listActions(types, callback); + } }; diff --git a/lib/api.js b/lib/api.js index f1661c9f..f5eb9f4d 100644 --- a/lib/api.js +++ b/lib/api.js @@ -13,9 +13,9 @@ var Logger = require("./logger"); var fs = require("fs"); var path = require("path"); var $util = require("./utilities"); +var ActionLog = require("./actionlog"); module.exports = function (Server) { - var ActionLog = Server.actionlog; function getIP(req) { var raw = req.connection.remoteAddress; var forward = req.header("x-forwarded-for"); diff --git a/lib/channel.js b/lib/channel.js index dc3033f3..f3b57e4e 100644 --- a/lib/channel.js +++ b/lib/channel.js @@ -12,24 +12,25 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI var fs = require("fs"); var path = require("path"); +var url = require("url"); var Poll = require("./poll.js").Poll; var Media = require("./media.js").Media; var Logger = require("./logger.js"); -var Rank = require("./rank.js"); var ChatCommand = require("./chatcommand.js"); var Filter = require("./filter.js").Filter; var Playlist = require("./playlist"); var sanitize = require("validator").sanitize; var $util = require("./utilities"); -var url = require("url"); var AsyncQueue = require("./asyncqueue"); +var ActionLog = require("./actionlog"); +var InfoGetter = require("./get-info"); -var Channel = function(name, Server) { +var Channel = function(name) { var self = this; Logger.syslog.log("Opening channel " + name); self.initialized = false; self.dbloaded = false; - self.server = Server; + self.server = Server.getServer(); self.name = name; self.canonical_name = name.toLowerCase(); @@ -384,7 +385,7 @@ Channel.prototype.tryReadLog = function (user) { Channel.prototype.tryRegister = function (user) { var self = this; if(self.registered) { - self.server.actionlog.record(user.ip, user.name, "channel-register-failure", + ActionLog.record(user.ip, user.name, "channel-register-failure", [self.name, "Channel already registered"]); user.socket.emit("registerChannel", { success: false, @@ -392,7 +393,7 @@ Channel.prototype.tryRegister = function (user) { }); } else if(!user.loggedIn) { - self.server.actionlog.record(user.ip, user.name, "channel-register-failure", + ActionLog.record(user.ip, user.name, "channel-register-failure", [self.name, "Not logged in"]); user.socket.emit("registerChannel", { success: false, @@ -400,8 +401,8 @@ Channel.prototype.tryRegister = function (user) { }); } - else if(!Rank.hasPermission(user, "registerChannel")) { - self.server.actionlog.record(user.ip, user.name, "channel-register-failure", + else if(user.rank < 10) { + ActionLog.record(user.ip, user.name, "channel-register-failure", [self.name, "Insufficient permissions"]); user.socket.emit("registerChannel", { success: false, @@ -419,7 +420,7 @@ Channel.prototype.tryRegister = function (user) { return; } - self.server.actionlog.record(user.ip, user.name, + ActionLog.record(user.ip, user.name, "channel-register-success", self.name); if (self.dead) return; @@ -927,7 +928,7 @@ Channel.prototype.sendBanlist = function(user) { var name = this.ipbans[ip][0]; var ip_hidden = this.hideIP(ip); var disp = ip; - if(user.rank < Rank.Siteadmin) { + if(user.rank < 255) { disp = $util.maskIP(ip); } ents.push({ @@ -971,7 +972,7 @@ Channel.prototype.sendRankStuff = function(user) { } Channel.prototype.sendChannelRanks = function(user) { - if(Rank.hasPermission(user, "acl") && this.registered) { + if(user.rank >= 3 && this.registered) { this.server.db.listChannelRanks(this.name, function (err, res) { if(err) { user.socket.emit("errorMsg", { @@ -1031,9 +1032,9 @@ Channel.prototype.sendAll = function(message, data) { this.server.ioSecure.sockets.in(this.name).emit(message, data); } -Channel.prototype.sendAllWithPermission = function(perm, msg, data) { +Channel.prototype.sendAllWithRank = function(rank, msg, data) { for(var i = 0; i < this.users.length; i++) { - if(Rank.hasPermission(this.users[i], perm)) { + if(this.users[i].rank >= rank) { this.users[i].socket.emit(msg, data); } } @@ -1101,7 +1102,7 @@ Channel.prototype.broadcastNewUser = function(user) { profile: user.profile }); - if(user.rank > Rank.Guest) { + if(user.rank > 0) { self.saveRank(user); } @@ -1209,7 +1210,7 @@ Channel.prototype.broadcastBanlist = function() { } for(var i = 0; i < this.users.length; i++) { if(this.hasPermission(this.users[i], "ban")) { - if(this.users[i].rank >= Rank.Siteadmin) { + if(this.users[i].rank >= 255) { this.users[i].socket.emit("banlist", adminents); } else { @@ -1248,8 +1249,7 @@ Channel.prototype.broadcastVoteskipUpdate = function() { var count = this.calcVoteskipMax(); var need = this.voteskip ? Math.ceil(count * this.opts.voteskip_ratio) : 0; for(var i = 0; i < this.users.length; i++) { - if(Rank.hasPermission(this.users[i], "seeVoteskip") || - this.leader == this.users[i]) { + if(this.users[i].rank >= 1.5) { this.users[i].socket.emit("voteskip", { count: amt, need: need @@ -1332,7 +1332,7 @@ Channel.prototype.tryQueue = function(user, data) { sustained: 1 }; - if (user.rank >= Rank.Moderator || this.leader == user) { + if (user.rank >= 2 || this.leader == user) { limit = { burst: 10, sustained: 2 @@ -1471,8 +1471,8 @@ Channel.prototype.addMedia = function(data, user) { self.plqueue.queue(function (q) { if (self.dead) return; - self.server.infogetter.getMedia(data.id, data.type, - function (e, vids) { + InfoGetter.getMedia(data.id, data.type, + function (e, vids) { if (e) { user.socket.emit("queueFail", { msg: e, @@ -1506,8 +1506,8 @@ Channel.prototype.addMedia = function(data, user) { if (self.dead) return; var cb = afterData.bind(self, q, false); - self.server.infogetter.getMedia(data.id, data.type, - function (e, m) { + InfoGetter.getMedia(data.id, data.type, + function (e, m) { if (self.dead) return; if (e) { @@ -1550,8 +1550,8 @@ Channel.prototype.addMedia = function(data, user) { afterData.bind(self, q, true)(item); } else { - self.server.infogetter.getMedia(data.id, data.type, - function (e, m) { + InfoGetter.getMedia(data.id, data.type, + function (e, m) { if (self.dead) return; if (e) { @@ -1671,7 +1671,7 @@ Channel.prototype.tryDequeue = function(user, data) { Channel.prototype.tryUncache = function(user, data) { var self = this; - if(!Rank.hasPermission(user, "uncache")) { + if(user.rank < 2) { return; } if(typeof data.id != "string") { @@ -1923,7 +1923,7 @@ Channel.prototype.setLock = function(locked) { } Channel.prototype.trySetLock = function(user, data) { - if(!Rank.hasPermission(user, "qlock")) { + if(user.rank < 2) { return; } @@ -1936,7 +1936,7 @@ Channel.prototype.trySetLock = function(user, data) { } Channel.prototype.tryToggleLock = function(user) { - if(!Rank.hasPermission(user, "qlock")) { + if(user.rank < 2) { return; } @@ -2028,7 +2028,7 @@ Channel.prototype.tryMoveFilter = function(user, data) { } Channel.prototype.tryUpdatePermissions = function(user, perms) { - if(!Rank.hasPermission(user, "channelperms")) { + if(user.rank < 3) { return; } for(var key in perms) { @@ -2039,7 +2039,7 @@ Channel.prototype.tryUpdatePermissions = function(user, perms) { } Channel.prototype.tryUpdateOptions = function(user, data) { - if(!Rank.hasPermission(user, "channelOpts")) { + if(user.rank < 2) { return; } @@ -2058,7 +2058,7 @@ Channel.prototype.tryUpdateOptions = function(user, data) { for(var key in this.opts) { if(key in data) { - if(key in adminonly && user.rank < Rank.Owner) { + if(key in adminonly && user.rank < 3) { continue; } if(key === "afk_timeout" && this.opts[key] != data[key]) { @@ -2075,7 +2075,7 @@ Channel.prototype.tryUpdateOptions = function(user, data) { } Channel.prototype.trySetCSS = function(user, data) { - if(!Rank.hasPermission(user, "setcss")) { + if(user.rank < 3) { return; } @@ -2092,7 +2092,7 @@ Channel.prototype.trySetCSS = function(user, data) { } Channel.prototype.trySetJS = function(user, data) { - if(!Rank.hasPermission(user, "setjs")) { + if(user.rank < 3) { return; } @@ -2234,7 +2234,7 @@ Channel.prototype.sendMessage = function(username, msg, msgclass, data) { Channel.prototype.trySetRank = function(user, data) { var self = this; - if(!Rank.hasPermission(user, "promote")) + if(user.rank < 2) return; if(typeof data.user !== "string" || typeof data.rank !== "number") @@ -2265,7 +2265,7 @@ Channel.prototype.trySetRank = function(user, data) { self.logger.log("*** " + user.name + " set " + data.user + "'s rank to " + data.rank); - self.sendAllWithPermission("acl", "setChannelRank", data); + self.sendAllWithRank(3, "setChannelRank", data); }); } self.broadcastUserUpdate(receiver); @@ -2286,7 +2286,7 @@ Channel.prototype.trySetRank = function(user, data) { self.logger.log("*** " + user.name + " set " + data.user + "'s rank to " + data.rank); - self.sendAllWithPermission("acl", "setChannelRank", data); + self.sendAllWithRank(3, "setChannelRank", data); }); }); } @@ -2329,7 +2329,7 @@ Channel.prototype.changeLeader = function(name) { } Channel.prototype.tryChangeLeader = function(user, data) { - if(!Rank.hasPermission(user, "assignLeader")) { + if(user.rank < 2) { return; } diff --git a/lib/chatcommand.js b/lib/chatcommand.js index dfc87031..4ab5b6d8 100644 --- a/lib/chatcommand.js +++ b/lib/chatcommand.js @@ -9,8 +9,6 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -var Rank = require("./rank.js"); -var Poll = require("./poll.js").Poll; var Logger = require("./logger.js"); function handle(chan, user, msg, data) { @@ -19,7 +17,7 @@ function handle(chan, user, msg, data) { else if(msg.indexOf("/sp ") == 0) chan.sendMessage(user.name, msg.substring(4), "spoiler", data); else if(msg.indexOf("/say ") == 0) { - if(Rank.hasPermission(user, "shout") || chan.leader == user) { + if(user.rank >= 1.5) { chan.sendMessage(user.name, msg.substring(5), "shout", data); } } @@ -27,12 +25,12 @@ function handle(chan, user, msg, data) { user.setAFK(!user.meta.afk); } else if(msg.indexOf("/m ") == 0) { - if(user.rank >= Rank.Moderator) { + if(user.rank >= 2) { chan.chainMessage(user, msg.substring(3), {modflair: user.rank}) } } else if(msg.indexOf("/a ") == 0) { - if(user.rank >= Rank.Siteadmin) { + if(user.rank >= 255) { var flair = { superadminflair: { labelclass: "label-important", @@ -241,7 +239,7 @@ function handleDrink(chan, user, msg, data) { } function handleClear(chan, user) { - if(user.rank < Rank.Moderator) { + if(user.rank < 2) { return; } diff --git a/lib/get-info.js b/lib/get-info.js index 0b56e6d9..3ef66159 100644 --- a/lib/get-info.js +++ b/lib/get-info.js @@ -15,613 +15,616 @@ var domain = require("domain"); var Logger = require("./logger.js"); var Media = require("./media.js").Media; var CustomEmbedFilter = require("./customembed").filter; +var Server = require("./server"); -module.exports = function (Server) { - var urlRetrieve = function (transport, options, callback) { - // Catch any errors that crop up along the way of the request - // in order to prevent them from reaching the global handler. - // This should cut down on needing to restart the server - var d = domain.create(); - d.on("error", function (err) { - Logger.errlog.log(err.trace()); - Logger.errlog.log("urlRetrieve failed: " + err); - Logger.errlog.log("Request was: " + options.host + options.path); - callback(503, err); - }); - d.run(function () { - var req = transport.request(options, function (res) { - var buffer = ""; - res.setEncoding("utf-8"); - res.on("data", function (chunk) { - buffer += chunk; - }); - res.on("end", function () { - callback(res.statusCode, buffer); - }); +var urlRetrieve = function (transport, options, callback) { + // Catch any errors that crop up along the way of the request + // in order to prevent them from reaching the global handler. + // This should cut down on needing to restart the server + var d = domain.create(); + d.on("error", function (err) { + Logger.errlog.log(err.trace()); + Logger.errlog.log("urlRetrieve failed: " + err); + Logger.errlog.log("Request was: " + options.host + options.path); + callback(503, err); + }); + d.run(function () { + var req = transport.request(options, function (res) { + var buffer = ""; + res.setEncoding("utf-8"); + res.on("data", function (chunk) { + buffer += chunk; + }); + res.on("end", function () { + callback(res.statusCode, buffer); }); - - req.end(); }); - }; - var Getters = { - /* youtube.com */ - yt: function (id, callback) { - if(Server.cfg["enable-ytv3"] && Server.cfg["ytv3apikey"]) { - Getters["ytv3"](id, callback); - return; - } + req.end(); + }); +}; - var m = id.match(/([\w-]+)/); - if (m) { - id = m[1]; - } else { - callback("Invalid ID", null); - return; - } +var Getters = { + /* youtube.com */ + yt: function (id, callback) { + var sv = Server.getServer(); + if (sv.cfg["enable-ytv3"] && sv.cfg["ytv3apikey"]) { + 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 + var m = id.match(/([\w-]+)/); + if (m) { + id = m[1]; + } else { + callback("Invalid ID", null); + return; + } + + var options = { + host: "gdata.youtube.com", + port: 443, + path: "/feeds/api/videos/" + id + "?v=2&alt=json", + method: "GET", + dataType: "jsonp", + timeout: 1000 + }; + + if(sv.cfg["ytv2devkey"]) { + options.headers = { + "X-Gdata-Key": "key=" + sv.cfg["ytv2devkey"] }; + } - if(Server.cfg["ytv2devkey"]) { - options.headers = { - "X-Gdata-Key": "key=" + Server.cfg["ytv2devkey"] - }; + 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 === 400) { + callback("Invalid video", null); + return; + } else if(status === 503) { + callback("API failure", null); + return; + } else if(status !== 200) { + callback("HTTP " + status, null); + return; } - - 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 === 400) { - callback("Invalid video", null); - return; - } else if(status === 503) { - callback("API failure", null); - return; - } else if(status !== 200) { - callback("HTTP " + status, null); - return; - } - - var buffer = data; - try { - data = JSON.parse(data); - if (data.entry.yt$accessControl) { - var ac = data.entry.yt$accessControl; - for (var i = 0; i < ac.length; i++) { - if (ac[i].action === "embed") { - if (ac[i].permission === "denied") { - callback("Embedding disabled", null); - return; - } - break; + + var buffer = data; + try { + data = JSON.parse(data); + if (data.entry.yt$accessControl) { + var ac = data.entry.yt$accessControl; + for (var i = 0; i < ac.length; i++) { + if (ac[i].action === "embed") { + if (ac[i].permission === "denied") { + callback("Embedding disabled", null); + return; } + break; } } - 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>/); - if(m === null) - m = buffer.match(/([^<]+)<\/code>/); - - var err = e; - 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); } - }); - }, + 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>/); + if(m === null) + m = buffer.match(/([^<]+)<\/code>/); - /* youtube.com API v3 (requires API key) */ - ytv3: function (id, callback) { - var m = id.match(/([\w-]+)/); - if (m) { - id = m[1]; - } else { - callback("Invalid ID", null); + var err = e; + 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 sv = Server.getServer(); + var m = id.match(/([\w-]+)/); + if (m) { + id = m[1]; + } else { + callback("Invalid ID", null); + return; + } + var params = [ + "part=" + encodeURIComponent("id,snippet,contentDetails"), + "id=" + id, + "key=" + sv.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("YTv3: HTTP " + status, null); return; } - 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("YTv3: HTTP " + status, 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("Video not found", 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(e, null); - } - }); - }, - - /* youtube.com playlists */ - yp: function (id, callback, url) { - var m = id.match(/([\w-]+)/); - if (m) { - id = m[1]; - } else { - callback("Invalid ID", null); - return; - } - 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 - }; - - if(Server.cfg["ytv2devkey"]) { - options.headers = { - "X-Gdata-Key": "key=" + Server.cfg["ytv2devkey"] - }; - } - - 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 === 503) { - callback("API failure", null); - return; - } else if(status !== 200) { - callback("YTPlaylist HTTP " + status, 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(e, 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 - }; - - if(Server.cfg["ytv2devkey"]) { - options.headers = { - "X-Gdata-Key": "key=" + Server.cfg["ytv2devkey"] - }; - } - - urlRetrieve(https, options, function (status, data) { - if(status !== 200) { - callback("YTSearch HTTP " + status, 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(e, null); - } - }); - }, - - /* vimeo.com */ - vi: function (id, callback) { - var m = id.match(/([\w-]+)/); - if (m) { - id = m[1]; - } else { - callback("Invalid ID", null); - return; - } - 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) { + 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("Video not found", null); return; - } else if(status === 403) { - callback("Private video", null); - return; - } else if(status === 503) { - callback("API failure", null); - return; - } else if(status !== 200) { - callback("YTv2 HTTP " + status, 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 = e; - if(buffer.match(/not found/)) - err = "Video not found"; + 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(e, null); + } + }); + }, - callback(err, null); + /* youtube.com playlists */ + yp: function (id, callback, url) { + var sv = Server.getServer(); + var m = id.match(/([\w-]+)/); + if (m) { + id = m[1]; + } else { + callback("Invalid ID", null); + return; + } + 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 + }; + + if(sv.cfg["ytv2devkey"]) { + options.headers = { + "X-Gdata-Key": "key=" + sv.cfg["ytv2devkey"] + }; + } + + 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 === 503) { + callback("API failure", null); + return; + } else if(status !== 200) { + callback("YTPlaylist HTTP " + status, 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) { + } } - }); - }, - /* 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 m = id.match(/([\w-]+)/); - if (m) { - id = m[1]; - } else { - callback("Invalid ID", null); + 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(e, null); + } + + }); + }, + + /* youtube.com search */ + ytSearch: function (terms, callback) { + var sv = Server.getServer(); + 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 + }; + + if(sv.cfg["ytv2devkey"]) { + options.headers = { + "X-Gdata-Key": "key=" + sv.cfg["ytv2devkey"] + }; + } + + urlRetrieve(https, options, function (status, data) { + if(status !== 200) { + callback("YTSearch HTTP " + status, null); return; } - 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 === 404) { + 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(e, null); + } + }); + }, + + /* vimeo.com */ + vi: function (id, callback) { + var m = id.match(/([\w-]+)/); + if (m) { + id = m[1]; + } else { + callback("Invalid ID", null); + return; + } + 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 === 503) { + callback("API failure", null); + return; + } else if(status !== 200) { + callback("YTv2 HTTP " + status, 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 = e; + 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 m = id.match(/([\w-]+)/); + if (m) { + id = m[1]; + } else { + callback("Invalid ID", null); + return; + } + 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 === 404) { + callback("Video not found", null); + return; + } else if (status !== 200) { + callback("DM HTTP " + status, 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; - } else if (status !== 200) { - callback("DM HTTP " + status, null); - return; } + var media = new Media(id, title, seconds, "dm"); + callback(false, media); + } catch(e) { + callback(err, null); + } + }); + }, - 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 - /* 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"; - const SC_CLIENT = "2e0c82ab5a020f3a7509318146128abd"; + var m = id.match(/([\w-\/\.:]+)/); + if (m) { + id = m[1]; + } else { + callback("Invalid ID", null); + return; + } - var m = id.match(/([\w-\/\.:]+)/); - if (m) { - id = m[1]; - } else { - callback("Invalid ID", null); + 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 === 503) { + callback("API failure", null); + return; + } else if(status !== 302) { + callback("SC HTTP " + status, null); return; } - var options = { + var track = null; + try { + data = JSON.parse(data); + track = data.location; + } catch(e) { + callback(e, null); + return; + } + + var options2 = { host: "api.soundcloud.com", port: 443, - path: "/resolve.json?url=" + id + "&client_id=" + SC_CLIENT, + path: track, method: "GET", dataType: "jsonp", timeout: 1000 }; - urlRetrieve(https, options, function (status, data) { - if(status === 404) { - callback("Sound not found", null); - return; - } else if(status === 503) { - callback("API failure", null); - return; - } else if(status !== 302) { + // I want to get off async's wild ride + urlRetrieve(https, options2, function (status, data) { + if(status !== 200) { callback("SC HTTP " + status, null); return; } - var track = null; try { data = JSON.parse(data); - track = data.location; + // 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(e, 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("SC HTTP " + status, 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(e, null); - } - }); - - }); - }, - - /* livestream.com */ - li: function (id, callback) { - var m = id.match(/([\w-]+)/); - if (m) { - id = m[1]; - } else { - callback("Invalid ID", null); - return; - } - var title = "Livestream.com - " + id; - var media = new Media(id, title, "--:--", "li"); - callback(false, media); - }, - - /* twitch.tv */ - tw: function (id, callback) { - var m = id.match(/([\w-]+)/); - if (m) { - id = m[1]; - } else { - callback("Invalid ID", null); - return; - } - var title = "Twitch.tv - " + id; - var media = new Media(id, title, "--:--", "tw"); - callback(false, media); - }, - - /* justin.tv */ - jt: function (id, callback) { - var m = id.match(/([\w-]+)/); - if (m) { - id = m[1]; - } else { - callback("Invalid ID", null); - return; - } - var title = "Justin.tv - " + id; - var media = new Media(id, title, "--:--", "jt"); - callback(false, media); - }, - - /* ustream.tv */ - us: function (id, callback) { - // 2013-09-17 - // They couldn't fucking decide whether channels should - // be at http://www.ustream.tv/channel/foo or just - // http://www.ustream.tv/foo so they do both. - // [](/cleese) - var m = id.match(/([^\?&#]+)|(channel\/[^\?&#]+)/); - if (m) { - id = m[1]; - } else { - callback("Invalid ID", null); - return; - } - var options = { - host: "www.ustream.tv", - port: 80, - path: "/" + id, - method: "GET", - timeout: 1000 - }; - - urlRetrieve(http, options, function (status, data) { - if(status !== 200) { - callback("Ustream HTTP " + status, 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); - } else { - 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); - }, + /* livestream.com */ + li: function (id, callback) { + var m = id.match(/([\w-]+)/); + if (m) { + id = m[1]; + } else { + callback("Invalid ID", null); + return; + } + var title = "Livestream.com - " + id; + var media = new Media(id, title, "--:--", "li"); + callback(false, media); + }, - /* imgur.com albums */ - im: function (id, callback) { - var m = id.match(/([\w-]+)/); - if (m) { - id = m[1]; - } else { - callback("Invalid ID", null); + /* twitch.tv */ + tw: function (id, callback) { + var m = id.match(/([\w-]+)/); + if (m) { + id = m[1]; + } else { + callback("Invalid ID", null); + return; + } + var title = "Twitch.tv - " + id; + var media = new Media(id, title, "--:--", "tw"); + callback(false, media); + }, + + /* justin.tv */ + jt: function (id, callback) { + var m = id.match(/([\w-]+)/); + if (m) { + id = m[1]; + } else { + callback("Invalid ID", null); + return; + } + var title = "Justin.tv - " + id; + var media = new Media(id, title, "--:--", "jt"); + callback(false, media); + }, + + /* ustream.tv */ + us: function (id, callback) { + // 2013-09-17 + // They couldn't fucking decide whether channels should + // be at http://www.ustream.tv/channel/foo or just + // http://www.ustream.tv/foo so they do both. + // [](/cleese) + var m = id.match(/([^\?&#]+)|(channel\/[^\?&#]+)/); + if (m) { + id = m[1]; + } else { + callback("Invalid ID", null); + return; + } + var options = { + host: "www.ustream.tv", + port: 80, + path: "/" + id, + method: "GET", + timeout: 1000 + }; + + urlRetrieve(http, options, function (status, data) { + if(status !== 200) { + callback("Ustream HTTP " + status, null); return; } - var title = "Imgur Album - " + id; - var media = new Media(id, title, "--:--", "im"); - callback(false, media); - }, - /* custom embed */ - cu: function (id, callback) { - id = CustomEmbedFilter(id); - var media = new Media(id, "Custom Media", "--:--", "cu"); - 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); } else { - callback("Unknown media type '" + type + "'", null); + 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 m = id.match(/([\w-]+)/); + if (m) { + id = m[1]; + } else { + callback("Invalid ID", null); + return; } - }; -} + var title = "Imgur Album - " + id; + var media = new Media(id, title, "--:--", "im"); + callback(false, media); + }, + + /* custom embed */ + cu: function (id, callback) { + id = CustomEmbedFilter(id); + var media = new Media(id, "Custom Media", "--:--", "cu"); + callback(false, media); + } +}; + +module.exports = { + Getters: Getters, + getMedia: function (id, type, callback) { + if(type in this.Getters) { + this.Getters[type](id, callback); + } else { + callback("Unknown media type '" + type + "'", null); + } + } +}; diff --git a/lib/server.js b/lib/server.js index dadc16c8..fe681ede 100644 --- a/lib/server.js +++ b/lib/server.js @@ -9,6 +9,29 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +var singleton = null; + +module.exports = { + init: function (cfg) { + Logger.syslog.log("Starting CyTube v" + VERSION); + var chanlogpath = path.join(__dirname, "../chanlogs"); + fs.exists(chanlogpath, function (exists) { + exists || fs.mkdir(chanlogpath); + }); + + var chandumppath = path.join(__dirname, "../chandump"); + fs.exists(chandumppath, function (exists) { + exists || fs.mkdir(chandumppath); + }); + singleton = new Server(cfg); + return singleton; + }, + + getServer: function () { + return singleton; + } +}; + var path = require("path"); var fs = require("fs"); var http = require("http"); @@ -36,7 +59,6 @@ var Server = function (cfg) { self.ioSecure = null; self.ipCount = {}; self.db = null; - self.acp = null; self.api = null; self.announcement = null; self.httplog = null; @@ -46,8 +68,7 @@ var Server = function (cfg) { // database init ------------------------------------------------------ var Database = require("./database"); self.db = new Database(self.cfg); - // TODO self.db.init(); - self.actionlog = require("./actionlog")(self); + self.actionlog = require("./actionlog"); self.httplog = new Logger.Logger(path.join(__dirname, "../httpaccess.log")); // webserver init ----------------------------------------------------- @@ -139,15 +160,12 @@ var Server = function (cfg) { self.handleSocketConnection(sock); }); - // acp init ----------------------------------------------------------- - self.acp = require("./acp")(self); - // background tasks init ---------------------------------------------- require("./bgtask")(self); // media metadata retriever init -------------------------------------- // TODO make the constructor just accept cfg? - self.infogetter = require("./get-info")(self); + self.infogetter = require("./get-info"); }; Server.prototype.getHTTPIP = function (req) { @@ -190,7 +208,7 @@ Server.prototype.getChannel = function (name) { return this.channels[i]; } - var c = new Channel(name, this); + var c = new Channel(name); this.channels.push(c); return c; }; @@ -268,7 +286,7 @@ Server.prototype.handleSocketConnection = function (socket) { } Logger.syslog.log("Accepted socket from " + ip); - new User(socket, self); + new User(socket); }; Server.prototype.shutdown = function () { @@ -283,25 +301,3 @@ Server.prototype.shutdown = function () { process.exit(0); }; -var singleton = null; - -module.exports = { - init: function (cfg) { - Logger.syslog.log("Starting CyTube v" + VERSION); - var chanlogpath = path.join(__dirname, "../chanlogs"); - fs.exists(chanlogpath, function (exists) { - exists || fs.mkdir(chanlogpath); - }); - - var chandumppath = path.join(__dirname, "../chandump"); - fs.exists(chandumppath, function (exists) { - exists || fs.mkdir(chandumppath); - }); - singleton = new Server(cfg); - return singleton; - }, - - getServer: function () { - return singleton; - } -}; diff --git a/lib/user.js b/lib/user.js index 441304b3..e949e7e2 100644 --- a/lib/user.js +++ b/lib/user.js @@ -9,21 +9,24 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -var Rank = require("./rank.js"); var Channel = require("./channel.js").Channel; var Logger = require("./logger.js"); var $util = require("./utilities"); +var ActionLog = require("./actionlog"); +var Server = require("./server"); +var ACP = require("./acp"); +var InfoGetter = require("./get-info"); // Represents a client connected via socket.io -var User = function (socket, Server) { +var User = function (socket) { this.ip = socket._ip; - this.server = Server; + this.server = Server.getServer(); this.socket = socket; this.loggedIn = false; this.loggingIn = false; this.saverank = false; - this.rank = Rank.Anonymous; - this.global_rank = Rank.Anonymous; + this.rank = -1 + this.global_rank = -1; this.channel = null; this.name = ""; this.meta = { @@ -42,8 +45,8 @@ var User = function (socket, Server) { this.autoAFK(); this.initCallbacks(); - if (Server.announcement !== null) { - this.socket.emit("announcement", Server.announcement); + if (this.server.announcement !== null) { + this.socket.emit("announcement", this.server.announcement); } }; @@ -321,7 +324,7 @@ User.prototype.initCallbacks = function () { self.socket.on("searchMedia", function (data) { if (self.inChannel()) { if (data.source == "yt") { - var searchfn = self.server.infogetter.Getters.ytSearch; + var searchfn = InfoGetter.Getters.ytSearch; searchfn(data.query.split(" "), function (e, vids) { if (!e) { self.socket.emit("searchResults", { @@ -556,8 +559,8 @@ User.prototype.initCallbacks = function () { }); self.socket.on("acp-init", function () { - if (self.global_rank >= Rank.Siteadmin) - self.server.acp.init(self); + if (self.global_rank >= 255) + ACP.init(self); }); self.socket.on("borrow-rank", function (rank) { @@ -662,7 +665,7 @@ User.prototype.login = function (name, pw, session) { self.server.db.userLogin(name, pw, session, function (err, row) { if (err) { self.loggingIn = false; - self.server.actionlog.record(self.ip, name, "login-failure", + ActionLog.record(self.ip, name, "login-failure", err); self.socket.emit("login", { success: false, @@ -686,7 +689,7 @@ User.prototype.login = function (name, pw, session) { } // Record logins for administrator accounts if (self.global_rank >= 255) - self.server.actionlog.record(self.ip, name, "login-success"); + ActionLog.record(self.ip, name, "login-success"); self.loggedIn = true; self.loggingIn = false; self.socket.emit("login", {