diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/index.js b/index.js new file mode 100644 index 0000000..8b53cc7 --- /dev/null +++ b/index.js @@ -0,0 +1,170 @@ +var request = require("request"), + http = require("http"), + urllib = require("url"), + Stream = require("stream").Stream, + utillib = require("util"); + +function PubSubHubbub(options){ + Stream.call(this); + + options = options || {}; + + this.port = options.port || 8921; + this.callbackServer = options.callbackServer || "http://pubsub.node.ee"; + this.callbackPath = options.callbackPath || "/hubbub"; + this.token = options.token || "FjMMzUBRFmDWCCyqBRMk"; + this.maxRSSSize = options.maxRSSSize || 3 * 1024 * 1024; + this.gid = options.gid; + this.uid = options.uid; + + this.server = http.createServer(this.serverHandler.bind(this)); + this.server.on("error", this.onServerError.bind(this)); + this.server.on("listening", this.onServerListening.bind(this)); + + this.server.listen(this.port); +} +utillib.inherits(PubSubHubbub, Stream); + +PubSubHubbub.prototype.serverHandler = function(req, res){ + if(req.url != this.callbackPath){ + res.writeHead(404, {"Content-Type": "text/html"}); + res.end("404 Not Found

404 Not Found

"); + return; + } + + if(req.method == "GET"){ + return this.serverGETHandler(req, res); + } + + if(req.method == "POST"){ + return this.serverPOSTHandler(req, res); + } + + res.writeHead(500, {"Content-Type": "text/html"}); + res.end("500 Internal Server Error

500 Internal Server Error

"); +} + +PubSubHubbub.prototype.onServerError = function(error){ + if(error.syscall == "listen"){ + process.stderr.write("Failed to start listening on port " + config.port + " ("+error.code+")\n"); + process.exit(1); + } + + console.log("HTTP Server error"); + console.log(error); +} + +PubSubHubbub.prototype.onServerListening = function(){ + if(typeof this.gid != "undefined"){ + process.setgid(this.gid); + } + + if(typeof this.uid != "undefined"){ + process.setuid(this.uid); + } + + console.log("Server listening on port " + this.port + " as " + process.getuid() + ":" + process.getgid()); +} + +PubSubHubbub.prototype.setSubscription = function(mode, topic, hub, callback){ + var form = { + "hub.mode": mode || "subscribe", + "hub.verify": "sync", + "hub.callback": config.callbackServer + config.callbackPath, + "hub.topic": topic, + "hub.verify_token": config.token + }, + postParams = { + url: hub, + form: form, + encoding: "utf-8" + }; + + request.post(postParams, this.pubsubResponse.bind(this, topic, callback)); +} + +PubSubHubbub.prototype.pubsubResponse = function(topic, callback, error, response, body){ + if(error){ + return callback(error); + } + + if(response.statusCode >= 300){ + return callback(new Error("Invalid response status "+response.statusCode)); + } + + return callback(null, topic); +} + +PubSubHubbub.prototype.serverGETHandler = function(req, res){ + var params = urllib.parse(request.url, true, true); + + if(params.query['hub.verify_token'] != this.token){ + res.writeHead(500, {"Content-Type": "text/html"}); + res.end("500 Internal Server Error

500 Internal Server Error

"); + return; + } + + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end(params.query['hub.challenge']); + + var data = { + lease: Number(params.query["hub.lease_seconds"] || 0) + Math.round(Date.now()/1000), + topic: params.query["hub.topic"] + }; + + this.emit(params.query["hub.mode"] || "subscribe", data); +} + +PubSubHubbub.prototype.serverPOSTHandler = function(req, res){ + var body = new Buffer(0), data; + + req.on("data", (function(chunk){ + if(body.length < this.maxRSSSize){ + if(body.length + chunk.length < this.maxRSSSize){ + data = new Buffer(body.length + chunk.length); + if(body.length){ + body.copy(data); + } + chunk.copy(data, body.length); + }else{ + data = new Buffer(this.maxRSSSize); + if(body.length){ + body.copy(data); + } + chunk.copy(data, body.length, 0, this.maxRSSSize - body.length); + } + body = data; + data = null; + } + chunk = null; + }).bind(this)); + + req.on("end", function(){ + res.writeHead(204, {'Content-Type': 'text/plain; charset=utf-8'}); + res.end(); + + this.parseFeed(body); + }); + +} + +PubSubHubbub.prototype.parseFeed(xml){ + var feed = new NodePie(xml); + + try{ + feed.init(); + }catch(E){ + this.emit("error", E); + return; + } + + notifier.emit("feed", feed); +} + +var pubsub = new PubSubHubbub(); +pubsub.on("subscribe", console.log.bind(console, "SUBSCRIBE")); +pubsub.on("unsubscribe", console.log.bind(console, "UNSUBSCRIBE")); +pubsub.on("error", console.log.bind(console, "ERROR")); +pubsub.on("feed", console.log.bind(console, "FEED")); + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..6aac2c0 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "pubsubhubbub", + "version": "0.1.0", + "description": "PubSubHubbub subscriber", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Andris Reinman", + "license": "MIT", + "dependencies": { + "request": "*", + "nodepie": "*" + } +}