Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial revision

  • Loading branch information...
commit 6249bd974aeb4672ec7f99db2e320d14cb014ec7 0 parents
@eschnou authored
2  .gitignore
@@ -0,0 +1,2 @@
+.project
+.settings/
21 LICENSE.txt
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2010 Laurent Eschenauer <laurent@eschenauer.be>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+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.
87 README.md
@@ -0,0 +1,87 @@
+node-ostatus
+============
+
+An implementation of the [OStatus](http://ostatus.org) protocol stack for node.js
+
+*** Ongoing development on the master branch until I reach a first stable 0.1 release ***
+
+Copyright (C) 2010 Laurent Eschenauer <laurent@eschenauer.be>
+
+Requirements
+------------
+
+- [node](http://nodejs.org/) v0.3+ is required, I'm developing against trunk.
+- [node-o3-xml](https://github.com/ajaxorg/node-o3-xml/)
+- [Mu](https://github.com/raycmorgan/Mu/tree/v2) the v2 branch
+
+Documentation
+-------------
+
+This is high on the todo list. For now, you'll have to look into the code to discover the API. You can also have a look at the command line tools to see some basic usages.
+
+Status
+------
+
+The following pieces of the protocol are implemented:
+- [webfinger](http://code.google.com/p/webfinger/):
+ * Lookup a user account and return the user meta in a JSON format
+ * Rendering of host/user meta based on a JSON input object
+- [pubsubhubbub](http://code.google.com/p/pubsubhubbub/):
+ * Subscribe/Unsubscribe to a topic on another hub
+ * Verify a subscription request from an other hub
+ * Distribute content to subscribers (with support for authenticated content distribution)
+- [hcard](http://microformats.org/wiki/hcard):
+ * Lookup and parse a hcard into a JSON object
+ * Render an hcard from a JSON object
+- [activitystreams](http://activitystrea.ms/):
+ * Fetch an atom feed with activitystream content and return a JSON representation of the stream. The JSON object is a valid activitystream JSON object.
+ * Render an atom feed from an array of activities in JSON
+
+What is missing:
+- [salmon](http://www.salmon-protocol.org/)
+
+Client
+------
+
+In the bin/ folder, you'll find a few simple command line clients for ostatus.
+
+### Status
+Display the last status update of someone:
+ ./status evan@identi.ca
+ 2010-12-14T19:29:07+00:00
+ RT @support Repetitive email notifications are fixed now. Our apologies for the inconvenience.
+
+### Profile
+Display the profile of someone
+ ./profile evan@identi.ca
+ photo: http://avatar.identi.ca/1-96-20100903013814.jpeg
+ nickname: evan
+ fn: Evan Prodromou
+ label: Montreal, Quebec, Canada
+ url: http://evan.status.net/
+ note: Montreal hacker and entrepreneur. Founder of identi.ca, lead developer of StatusNet, CEO of StatusNet Inc.
+
+License
+-------
+
+The MIT License
+
+Copyright (c) 2010 Laurent Eschenauer <laurent@eschenauer.be>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+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.
80 bin/profile.js
@@ -0,0 +1,80 @@
+#!/usr/local/bin/node
+
+/*
+ * node-ostatus - An implementation of OStatus for node.js
+ *
+ * Copyright (C) 2010 Laurent Eschenauer <laurent@eschenauer.be>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * 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 Ostatus = require('ostatus'),
+ Hcard = Ostatus.hcard,
+ Webfinger = Ostatus.webfinger;
+
+var _main = function(argv) {
+ // Parse the command line arguments
+ if (argv.length < 3) {
+ console.log("Usage: profile [identifier]");
+ process.exit(-1);
+ }
+
+ // Webfinger require acct: identifier, we add if required
+ var identifier = argv[2];
+ if (identifier.length<5 || identifier.substring(0,5) != "acct:") {
+ identifier = "acct:" + identifier;
+ }
+
+ // Wrap the request in a try.. catch to nicely die on errors
+ try {
+ Webfinger.lookup(identifier, function(err, result) {
+ if (err) _error(err);
+ var links = result.documentElement.getElementsByTagName("Link");
+ for (i=0;i<links.length;i++) {
+ var attributes = links[i].attributes;
+ var rel = attributes.getNamedItem("rel");
+ if (rel.nodeValue == "http://microformats.org/profile/hcard") {
+ var href = attributes.getNamedItem("href");
+ Hcard.lookup(href.nodeValue, _result);
+ }
+ }
+ });
+ } catch (error) {
+ _error(error);
+ }
+};
+
+var _error = function(error) {
+ console.log("Error: " + error.message);
+ process.exit(-1);
+};
+
+var _result = function(error, result) {
+ if (error) {
+ _error(error);
+ } else {
+ if (result != undefined) {
+ for(key in result) {
+ console.log(key + ": " + result[key]);
+ }
+ }
+ }
+};
+
+_main(process.argv);
82 bin/status.js
@@ -0,0 +1,82 @@
+#!/usr/local/bin/node
+
+/*
+ * node-ostatus - An implementation of OStatus for node.js
+ *
+ * Copyright (C) 2010 Laurent Eschenauer <laurent@eschenauer.be>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * 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 Ostatus = require('ostatus'),
+ Hcard = Ostatus.hcard,
+ Atom = Ostatus.atom,
+ Webfinger = Ostatus.webfinger;
+
+var _main = function(argv) {
+ // Parse the command line arguments
+ if (argv.length < 3) {
+ console.log("Usage: status [identifier]");
+ process.exit(-1);
+ }
+
+ // Webfinger require acct: identifier, we add if required
+ var identifier = argv[2];
+ if (identifier.length<5 || identifier.substring(0,5) != "acct:") {
+ identifier = "acct:" + identifier;
+ }
+
+ // Wrap the request in a try.. catch to nicely die on errors
+ try {
+ Webfinger.lookup(identifier, function(err, result) {
+ if (err) _error(err);
+ var links = result.documentElement.getElementsByTagName("Link");
+ for (i=0;i<links.length;i++) {
+ var attributes = links[i].attributes;
+ var rel = attributes.getNamedItem("rel");
+ if (rel.nodeValue == "http://schemas.google.com/g/2010#updates-from") {
+ var href = attributes.getNamedItem("href");
+ Atom.parseFeed(href.nodeValue, _result);
+ }
+ }
+ });
+ } catch (error) {
+ _error(error);
+ }
+};
+
+var _error = function(error) {
+ console.log("Error: " + error.message);
+ process.exit(-1);
+};
+
+var _result = function(error, result) {
+ if (error) {
+ _error(error);
+ } else {
+ if (result != undefined) {
+ var item = result[0];
+ var time = item['postedTime'];
+ var title = item['title'];
+ console.log(time + "\n" + title);
+ }
+ }
+};
+
+_main(process.argv);
72 bin/wfinger.js
@@ -0,0 +1,72 @@
+#!/usr/local/bin/node
+
+/*
+ * node-ostatus - An implementation of OStatus for node.js
+ *
+ * Copyright (C) 2010 Laurent Eschenauer <laurent@eschenauer.be>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * 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 Ostatus = require('ostatus'),
+ Hcard = Ostatus.hcard,
+ Webfinger = Ostatus.webfinger;
+
+var _main = function(argv) {
+ // Parse the command line arguments
+ if (argv.length < 3) {
+ console.log("Usage: finger [account]");
+ process.exit(-1);
+ }
+
+ // Wrap the request in a try.. catch to nicely die on errors
+ try {
+
+ // Webfinger require acct: reference, we add if required
+ var reference = argv[2];
+ if (reference.length<5 || reference.substring(0,5) != "acct:") {
+ reference = "acct:" + reference;
+ }
+ Webfinger.lookup(reference, _result);
+ } catch (error) {
+ _error(error);
+ }
+};
+
+var _error = function(error) {
+ console.log("Error: " + error.message);
+ process.exit(-1);
+};
+
+var _result = function(error, result) {
+ if (error) {
+ _error(error);
+ } else {
+ var links = result.documentElement.getElementsByTagName("Link");
+ for (i=0;i<links.length;i++) {
+ var attributes = links[i].attributes;
+ var aHref = attributes.getNamedItem("href");
+ var rel = attributes.getNamedItem("rel");
+ if (aHref != undefined && rel != undefined)
+ console.log(rel.nodeValue + ": " + aHref.nodeValue);
+ }
+ }
+};
+
+_main(process.argv);
25 index.js
@@ -0,0 +1,25 @@
+/*
+ * node-ostatus - An implementation of OStatus for node.js
+ *
+ * Copyright (C) 2010 Laurent Eschenauer <laurent@eschenauer.be>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * 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.
+*/
+
+module.exports = require('./lib/ostatus');
123 lib/ostatus/atom.js
@@ -0,0 +1,123 @@
+/*
+ * node-ostatus - An implementation of OStatus for node.js
+ *
+ * Copyright (C) 2010 Laurent Eschenauer <laurent@eschenauer.be>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * 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 Sys = require('sys'),
+ Url = require('url'),
+ Http = require('./http.js'),
+ Xml = require('o3-xml');
+ Path = require('path'),
+ Mu = require('mu');
+
+function render(updates, profile, callback) {
+ Mu.compile('updates.xml.mu', function (err, parsed) {
+ if (err) return callback(err);
+ var host = global.config.host;
+ var buffer = '';
+ var context = profile;
+ context.updates = updates;
+ context.host = host;
+ Mu.render(parsed,context)
+ .on('data', function (c) { buffer += c.toString(); })
+ .on('end', function () {callback(null, buffer);})
+ .on('error', function(err) {callback(err, buffer);});
+ });
+}
+
+function parseFeed(url, callback) {
+ console.log("Requesting atom feed at " + url);
+ Http.get(url, function(err, response, body) {
+ try {
+ var doc = Xml.parseFromString(body);
+ var childNodes = doc.documentElement.childNodes;
+ var entries = [];
+ for (var i=0; i<childNodes.length; i++) {
+ var name = childNodes[i].nodeName;
+ if (name == "entry") {
+ var entry = _readEntry(childNodes[i]);
+ if (entry != null) {
+ entries.push(entry);
+ }
+ }
+ }
+ callback(null, entries);
+ } catch (exception) {
+ callback(exception);
+ }
+ });
+}
+
+var _elements = {
+ "title": _parseTitle,
+ "verb" : _parseVerb,
+ "content": _parseContent,
+ "updated": _parseUpdated
+};
+
+function _readEntry(node) {
+ var childNodes = node.childNodes;
+ var entry = {};
+ for (var i=0; i<childNodes.length; i++) {
+ var name = childNodes[i].nodeName;
+ if (name in _elements) {
+ _elements[name](childNodes[i], entry);
+ }
+ }
+ return entry;
+}
+
+function _parseTitle(node, entry) {
+ if (node != null) {
+ entry["title"] = node.nodeValue;
+ }
+ return entry;
+}
+
+function _parseVerb(node, entry) {
+ if (node != null) {
+ var verb = node.nodeValue;
+ entry["verb"] = verb.substr(verb.lastIndexOf("/") + 1);
+ }
+ return entry;
+}
+
+function _parseContent(node, entry) {
+ if (node != null) {
+ var body = node.nodeValue;
+ entry["body"] = body;
+ }
+ return entry;
+}
+
+function _parseUpdated(node, entry) {
+ if (node != null) {
+ var updated = node.nodeValue;
+ // TODO Convert to W3CDTF
+ entry["postedTime"] = updated;
+ }
+ return entry;
+}
+
+
+exports.parseFeed = parseFeed;
+exports.render = render;
130 lib/ostatus/hcard.js
@@ -0,0 +1,130 @@
+/*
+ * node-ostatus - An implementation of OStatus for node.js
+ *
+ * Copyright (C) 2010 Laurent Eschenauer <laurent@eschenauer.be>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * 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 Sys = require('sys'),
+ Url = require('url'),
+ Http = require('./http.js'),
+ Xml = require('o3-xml'),
+ Path = require('path'),
+ Mu = require('mu');
+
+function lookup(profile, callback) {
+ console.log("Requesting hcard at " + profile);
+ Http.get(profile, function(err, response, body) {
+ if (response.statusCode == 200) {
+ var doc = Xml.parseFromString(body);
+ vcardNode = _findVcardNode(doc);
+ if (vcardNode != undefined) {
+ elements = _findVcardElements(vcardNode);
+ callback(null, elements);
+ }
+ } else {
+ callback(new Error("HCard lookup returned HTTP status " + response.statusCode));
+ }
+ });
+}
+
+function render(profile, callback) {
+ Mu.compile('hcard.html.mu', function (err, parsed) {
+ if (err) return callback(err);
+
+ var buffer = '';
+ Mu.render(parsed, profile)
+ .on('data', function (c) { buffer += c.toString(); })
+ .on('end', function () {
+ callback(null, buffer);
+ });
+ });
+}
+
+function _findVcardNode(doc) {
+ var nodes = doc.documentElement.selectNodes("//@class");
+ for (i=0; i<nodes.length; i++) {
+ var value = nodes[i].nodeValue;
+ if (value.search(/(^|\s)vcard(\s|$)/i)>=0) {
+ return nodes[i].parentNode;
+ }
+ }
+ return undefined;
+}
+
+function _findVcardElements(node) {
+ var nodes = node.selectNodes("//@class");
+ var vcard = {};
+ for (var i=0; i<nodes.length; i++) {
+ var value = nodes[i].nodeValue;
+ var element = _matchElement(value);
+ if (element != undefined) {
+ if (element == "photo") {
+ vcard["photo"] = nodes[i].parentNode.attributes.getNamedItem("src").nodeValue;
+ } else {
+ vcard[element] = nodes[i].parentNode.nodeValue;
+ }
+ }
+ }
+ return vcard;
+}
+
+function _matchElement(value) {
+ for (var i in elements) {
+ re = new RegExp("(^|\\s)" + elements[i] + "(\\s|$)", "i");
+ if (re.test(value)) {
+ return elements[i];
+ }
+ }
+ return undefined;
+}
+
+var elements = [
+"fn",
+"n",
+"adr",
+"agent",
+"bday",
+"category",
+"class",
+"email",
+"geo",
+"key",
+"label",
+"logo",
+"mailer",
+"nickname",
+"note",
+"org",
+"photo",
+"avatar",
+"rev",
+"role",
+"sort-string",
+"sound",
+"tel",
+"title",
+"tz",
+"uid",
+"url",
+];
+
+exports.lookup = lookup;
+exports.render = render;
103 lib/ostatus/http.js
@@ -0,0 +1,103 @@
+/*
+ * node-ostatus - An implementation of OStatus for node.js
+ *
+ * Copyright (C) 2010 Laurent Eschenauer <laurent@eschenauer.be>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * 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 Http = require('http'),
+ Url = require('url');
+
+function get(url, callback, headers) {
+ var url = Url.parse(url);
+ var host = url.hostname;
+ var path = url.pathname;
+ var headers = headers ? headers : {};
+ var secure = (url.protocol == "https:") ? true : false;
+ var port = secure ? 443 : 80;
+
+ headers["Host"] = host;
+
+ if (url.search != undefined) path += url.search;
+
+ var client = Http.createClient(port, host, secure);
+ var request = client.request('GET', path, headers);
+ var body = "";
+
+ request.on('response', function (response) {
+ response.setEncoding('utf8');
+ response.on('data', function (chunk) {
+ body += chunk;
+ });
+ response.on('end', function (chunk) {
+ callback(null, response, body);
+ });
+ });
+
+ request.end();
+}
+
+function post(url, reqBody, headers, callback) {
+ var url = Url.parse(url);
+ var host = url.hostname;
+ var path = url.pathname;
+ var body = body ? body : "";
+ var headers = headers ? headers : {};
+ var secure = (url.protocol == "https:") ? true : false;
+ var port = secure ? 443 : 80;
+
+ headers["Host"] = host;
+ headers["Content-Length"] = reqBody.length;
+
+ if (url.search != undefined) path += url.search;
+
+ var client = Http.createClient(port, host, secure);
+ var request = client.request('POST', path, headers);
+ var resBody = "";
+
+ request.on('response', function (response) {
+ response.setEncoding('utf8');
+ response.on('data', function (chunk) {
+ resBody += chunk;
+ });
+ response.on('end', function (chunk) {
+ callback(null, response, resBody);
+ });
+ });
+
+ request.write(reqBody);
+ request.end();
+}
+
+function response(res, message, code, type) {
+ var body = message;
+ var type = type ? type : 'text/html';
+ var code = code ? code : 200;
+ res.writeHead(code, {
+ 'Content-Type': type + ";charser=UTF-8",
+ 'Content-Length': body.length
+ });
+ if (body) res.write(body);
+ res.end();
+}
+
+exports.get = get;
+exports.post = post;
+exports.response = response;
29 lib/ostatus/index.js
@@ -0,0 +1,29 @@
+/*
+ * node-ostatus - An implementation of OStatus for node.js
+ *
+ * Copyright (C) 2010 Laurent Eschenauer <laurent@eschenauer.be>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * 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.
+*/
+
+exports.atom = require('./atom.js');
+exports.push = require('./push.js');
+exports.hcard = require('./hcard.js');
+exports.webfinger = require('./webfinger.js');
+
109 lib/ostatus/push.js
@@ -0,0 +1,109 @@
+/*
+ * node-ostatus - An implementation of OStatus for node.js
+ *
+ * Copyright (C) 2010 Laurent Eschenauer <laurent@eschenauer.be>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * 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 Sys = require('sys'),
+ Url = require('url'),
+ Crypto = require('crypto'),
+ Http = require('./http.js'),
+ Util = require('util'),
+ Qs = require('querystring');
+
+
+function verify(request, callback) {
+ var subscriber = request["hub.callback"];
+ var challenge = _secret(12);
+ var query = {
+ "hub.mode": request["hub.mode"],
+ "hub.topic": request["hub.topic"],
+ "hub.challenge": challenge,
+ "hub.lease_seconds": 60 * 60 // one hour lease for now
+ };
+
+ if (request["hub.verify_token"] != undefined) {
+ query["hub.verify_token"] = request["hub.verify_token"];
+ }
+
+ var url = subscriber + "?" + Qs.stringify(query);
+
+ Http.get(url, function(err, response, body) {
+ if (err) return callback(err);
+ if (body == challenge) {
+ callback(null, request["hub.topic"]);
+ } else {
+ callback(new Error("Challenge did not match, expecting " + challenge + " but received " + body));
+ }
+ });
+}
+
+function subscribe(url, config, callback) {
+ var data = Qs.stringify(config);
+ var headers = {"Content-Type": "application/x-www-form-urlencoded"};
+ console.log("Url: " + url);
+ console.log("Data: " + data);
+ Http.post(url, data, headers, function(err, response, body) {
+ if (err) return callback(err);
+ if (response.statusCode == 200) {
+ callback(null, "pending");
+ } else if (response.statusCode == 204) {
+ callback(null, "accepted");
+ } else {
+ callback(new Error("Push subscribe returned http status code " + response.statusCode));
+ }
+ });
+}
+
+function distribute(data, url, secret, callback) {
+ var headers = {"Content-Type": "application/atom-xml"};
+
+ if (secret != undefined) {
+ var hmac = Crypto.createHmac("sha1", secret);
+ var hash = hmac.update(data);
+ var digest = "sha1="+ hmac.digest(encoding="hex");
+ headers["X-Hub-Signature"] = digest;
+ console.log("Digest: " + digest);
+ }
+
+ Http.post(url, data, headers, function(err, response, body) {
+ if (err) return callback(err);
+ if (response.statusCode >= 200 && response.statusCode < 300) {
+ callback(null, body);
+ } else {
+ callback(new Error("Push distribute returned HTTP Status " + response.statusCode));
+ }
+ });
+}
+
+function _secret(size) {
+ var text = "";
+ var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+
+ for( var i=0; i < size; i++ )
+ text += possible.charAt(Math.floor(Math.random() * possible.length));
+
+ return text;
+}
+
+exports.verify = verify;
+exports.distribute = distribute;
+exports.subscribe = subscribe;
42 lib/ostatus/templates/hcard.html.mu
@@ -0,0 +1,42 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <titleStatus - HCard - {{username}}</title>
+ </head>
+ <body id="hcard">
+ <div id="wrapper">
+ <div id="i" class="entity_profile vcard author">
+ <h2>User profile</h2>
+ <dl class="entity_depiction">
+ <dt>Photo</dt>
+ <dd>
+ <img src="{{avatar}}" class="photo avatar" width="96" height="96" alt="{{username}}"/>
+ </dd>
+ </dl>
+ <dl class="entity_nickname">
+ <dt>Nickname</dt>
+ <dd>
+ <a href="{{profile}}" rel="me" class="nickname url uid">{{username}}</a>
+ </dd>
+ </dl>
+ <dl class="entity_fn">
+ <dt>Full name</dt>
+ <dd>
+ <span class="fn">{{fullname}}</span>
+ </dd>
+ </dl>
+ <dl class="entity_location">
+ <dt>Location</dt>
+ <dd class="label">{{location}}</dd>
+ </dl>
+ <dl class="entity_note">
+ <dt>Note</dt>
+ <dd class="note">{{note}}</dd>
+ </dl>
+ </div>
+ </div>
+ </body>
+</html>
+
+
8 lib/ostatus/templates/lrdd.xml.mu
@@ -0,0 +1,8 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>
+ <Subject>{{subject}}</Subject>
+ <Alias>{{alias}}</Alias>
+ {{#links}}
+ <Link {{#rel}}rel='{{rel}}'{{/rel}} {{#href}}href='{{href}}'{{/href}} {{#ref}}ref='{{ref}}'{{/ref}} {{#type}}type='{{type}}'{{/type}}/>
+ {{/links}}
+</XRD>
39 lib/ostatus/templates/updates.xml.mu
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:georss="http://www.georss.org/georss" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:media="http://purl.org/syndication/atommedia" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:statusnet="http://status.net/schema/api/1/">
+ <generator uri="http://ustatus.org" version="0.1"Status</generator>
+ <id>http://{{host}}/updates/{{username}}.atom</id>
+ <title>Latest updates from {{fullname}}</title>
+ <updated>2011-01-10T21:29:19+00:00</updated>
+ <link rel="alternate" href="http://{{host}}/users/{{username}}" type="text/html"/>
+ <link rel="hub" href="http://{{host}}/push/hub" />
+ <link rel="salmon" href="http://{{host}}/salmon/user/{{username}}" />
+ <link rel="self" href="http://{{host}}/updates/{{username}}.atom" type="application/atom+xml"/>
+ <author>
+ <name>{{fullname}}</name>
+ <uri>http://{{host}}/users/{{username}}</uri>
+ </author>
+ <activity:subject>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
+ <id>http://{{host}}/users/{{username}}</id>
+ <title>{{fullname}}</title>
+ <link rel="alternate" type="text/html" href="http://{{host}}/users/{{username}}"/>
+ <link rel="avatar" type="image/jpeg" media:width="73" media:height="73" href="{{avatar}}"/>
+ <poco:preferredUsername>{{username}}</poco:preferredUsername>
+ <poco:displayName>{{fullname}}</poco:displayName>
+ <poco:note>{{note}}</poco:note>
+ <poco:address>
+ <poco:formatted>{{location}}</poco:formatted>
+ </poco:address>
+ </activity:subject>
+{{#updates}}
+ <entry>
+ <id>{{id}}</id>
+ <updated>{{updated}}</updated>
+ <title>{{title}}</title>
+ <content type="html">{{content}}</content>
+ <activity:verb>http://activitystrea.ms/schema/1.0/{{verb}}</activity:verb>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/{{type}}</activity:object-type>
+ </entry>
+{{/updates}}
+</feed>
+
14 lib/ostatus/templates/user.html.mu
@@ -0,0 +1,14 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <title>Laurent (eschnou) - Identi.ca</title>
+ <link rel="alternate" href="http://{{host}}/updates/{{username}}.atom" type="application/atom+xml" title="Notice feed for {{username}} (Atom)"/>
+ </head>
+ <body>
+{{#updates}}
+ <h1>{{title}}</h1>
+ {{updated}}
+{{/updates}}
+ </body>
+</html>
+
9 lib/ostatus/templates/xrd.xml.mu
@@ -0,0 +1,9 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'
+ xmlns:hm='http://host-meta.net/xrd/1.0'>
+ <hm:Host xmlns='http://host-meta.net/xrd/1.0'>{{host}}</hm:Host>
+ <Link rel='lrdd'
+ template='http://{{host}}/webfinger/?q={uri}'>
+ <Title>Resource Descriptor</Title>
+ </Link>
+</XRD>
110 lib/ostatus/webfinger.js
@@ -0,0 +1,110 @@
+/*
+ * node-ostatus - An implementation of OStatus for node.js
+ *
+ * Copyright (C) 2010 Laurent Eschenauer <laurent@eschenauer.be>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * 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 Sys = require('sys')
+ ,Url = require('url')
+ ,Xml = require('o3-xml')
+ ,Path = require('path')
+ ,Mu = require('mu')
+ ,Http = require('./http.js');
+
+function xrd(host, callback) {
+ var context = {host: host};
+ Mu.compile('xrd.xml.mu', function (err, parsed) {
+ if (err) return callback(err);
+
+ var buffer = '';
+ Mu.render(parsed, context)
+ .on('data', function (c) { buffer += c.toString(); })
+ .on('end', function () {
+ callback(null, buffer);
+ });
+ });
+}
+
+function lrdd(subject, alias, links, callback) {
+ var context = {
+ subject: subject
+ ,alias: alias
+ ,links: links
+ };
+
+ Mu.compile('lrdd.xml.mu', function (err, parsed) {
+ if (err) return callback(err);
+ var buffer = '';
+ Mu.render(parsed, context)
+ .on('data', function (c) { buffer += c.toString(); })
+ .on('end', function () {
+ callback(null, buffer);
+ });
+ });
+}
+
+function lookup(identifier, callback) {
+ var url = Url.parse(identifier);
+ if (url.protocol != "acct:") {
+ callback(new Error("Protocol not supported"));
+ return;
+ }
+ _fetchHostMeta(url.hostname, function (err, template) {
+ if (err) return callback(err);
+ _fetchUserMeta(template, identifier, function (err, meta) {
+ callback(err,meta);
+ });
+ });
+}
+
+function _fetchHostMeta(host, callback) {
+ var url = "http://" + host + "/.well-known/host-meta";
+ console.log("Requesting host meta at " + url);
+ Http.get(url, function(err, response, content) {
+ if (err) return callback(err);
+ if (response.statusCode == 200) {
+ var doc = Xml.parseFromString(content);
+ var nodes = doc.documentElement.selectNodes("descendant-or-self::node()[@rel='lrdd']");
+ callback(null, nodes[0].getAttribute("template"));
+ } else {
+ callback(new Error("Fetching host meta returned HTTP Status " + response.statusCode));
+ }
+ });
+}
+
+function _fetchUserMeta(template, identifier, callback) {
+ var url = template.replace("{uri}", identifier);
+ console.log("Requesting user meta at " + url);
+ Http.get(url, function(err, response, content) {
+ if (err) return callback(err);
+ if (response.statusCode == 200) {
+ var links = [];
+ var doc = Xml.parseFromString(content);
+ callback(null, doc);
+ } else {
+ callback(new Error("Fetching user meta returned HTTP Status " + response.statusCode));
+ }
+ });
+}
+
+exports.lookup = lookup;
+exports.xrd = xrd;
+exports.lrdd = lrdd;
11 package.json
@@ -0,0 +1,11 @@
+{ "name" : "ostatus"
+, "description" : "An implementation of the OStatus protocol stack for nodejs."
+, "engines": { "node" : ">=0.3.8-pre < 4.0.0" }
+, "version": "v0.1.0"
+, "keywords": ["ostatus", "hcard", "pubsubhubbub", "atom", "activity", "salmon"]
+, "homepage": "http://github.com/eschnou/node-ostatus"
+, "author": "Laurent Eschenauer <laurent@eschenauer.be> (http://eschnou.com)"
+, "bin" : { "status" : "./bin/status.js", "profile": "./bin/profile.js", "wfinger": "./bin/wfinger.js"}
+, "main" : "./lib/ostatus"
+, "repository" : { "type": "git", "url": "https://github.com/eschnou/node-ostatus.git"}
+}
Please sign in to comment.
Something went wrong with that request. Please try again.