Mercurial > eldonilo > blog
diff server.js @ 0:f62b5c395a48
Initial commit.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Sat, 04 Jun 2011 05:02:47 +0200 |
parents | |
children | 82905edac9d8 |
line wrap: on
line diff
new file mode 100755 --- /dev/null +++ b/server.js @@ -0,0 +1,382 @@ +#!/usr/bin/env node + +'use strict'; + +var config = require('./configuration'); + +var util = require('util'); +var http = require('http'); +var fs = require('fs'); +var xmpp = require('node-xmpp'); +var Element = xmpp.Element; +var JID = require('./jid').JID; +var ns = require('./ns').ns; +var forms = require('./forms'); +require('./date'); + +var received = {}; + +var cl = new xmpp.Client(config); + +(function() { + var send = cl.send; + cl.send = function(s) { + util.log('Sent: [1;32m' + s + '[0m'); + send.call(cl, s); + } +})(); + +cl.on('online', function() { + util.log('Connected.'); + cl.send(new Element('presence')); +}); + +var getUniqId = (function() { + var id = 0; + return function() { + return ++id; + } +})(); + +var getNodeInfo = function(jid) { + var iq = new Element('iq', {to: jid.bare, type: 'get', id: getUniqId()}) + .c('query', {xmlns: ns.info, node: jid.resource}) + .up(); + + cl.send(iq); +}; + +var getNodeItems = function(jid) { + var iq = new Element('iq', {to: jid.bare, type: 'get', id: getUniqId()}) + .c('pubsub', {xmlns: ns.ps}) + .c('items', {node: jid.resource}) + .up() + .up(); + + cl.send(iq); +}; + +var makeError = function(response) { + response.attrs.type = 'error'; + + response.c('error', {type: 'cancel'}) + .c('feature-not-implemented', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}); + + return cl.send(response); +}; + +var handleInfo = function(query, from) { + var x = query.getChild('x', ns.data); + if (!x) + return; + + var jid = new JID; + jid.bare = from; + jid.resource = query.attrs.node; + + var form = forms.parse(x, true).fields; + received[jid].form = form; + generatePage(jid); +}; + +var handlePubSub = function(pubsub, from) { + var items = pubsub.getChild('items'); + if (!items) + return; + + var itemsNS = items.getNS(); + if (itemsNS !== ns.ps && itemsNS !== ns.pse) + return; + + var jid = new JID; + jid.bare = from; + jid.resource = items.attrs.node; + + items = items.getChildren('item', itemsNS); + if (!items) + return; + + if (!received[jid]) + received[jid] = {data: null, form: null, wait: {}}; + + var data = received[jid].data || {}; + + for (var i in items) { + var item = items[i]; + var id = item.attrs.id; + + var payload = item.children[0]; + delete payload.parent; + data[id] = payload; + } + + received[jid].data = data; + generatePage(jid); +}; + +cl.on('stanza', function(stanza) { + util.log('Recv: [1;34m' + stanza + '[0m'); + if (stanza.is('iq', ns.j)) { + var type = stanza.attrs.type; + if (type === 'error') + return; + + var result = new Element('iq', {to: stanza.attrs.from, from: stanza.attrs.to, type: 'result'}); + if (type === 'get' || type === 'set') + return makeError(result); + + var payload = stanza.getChild('query', ns.info); + if (payload) + return handleInfo(payload, stanza.attrs.from); + + payload = stanza.getChild('pubsub', ns.ps); + if (payload) + return handlePubSub(payload, stanza.attrs.from); + + } else if (stanza.is('message')) { + var type = stanza.attrs.type; + if (type === 'error') + return; + + if (type !== 'headline') + return; + + var payload = stanza.getChild('event', ns.pse); + if (payload) + return handlePubSub(payload, stanza.attrs.from); + } +}); + +var parseAtom = function(atom, id) { + var article = new Element('article', {'e:id': id, 'e:date': '2011-06-02T10:59:39Z'}); + + var avatar = article.c('aside').c('img') + article.up(); + + try { + var title = atom.getChild('title', ns.atom).getText(); + if (title) + article.c('h2').t(title).up(); + } catch (e) { } + + var footer = article.c('footer'); + article.up(); + + var author = atom.getChild('author', ns.atom); + if (author) { + footer.t('By '); + var name = author.getChild('name', ns.atom).getText(); + + try{ + var uri = author.getChild('uri', ns.atom).getText(); + footer.c('cite').c('a', {href: uri}).t(name).up(); + avatar.attrs.src = '/avatar/' + uri.substring(5); + } catch (e) { + footer.c('cite').t(name); + } + + try { + var email = author.getChild('email', ns.atom).getText(); + footer.t(', (').c('a', {href: email}).t('email').up().t(')'); + } catch (e) { } + footer.up(); + } + + var published = atom.getChild('published', ns.atom).getText(); + if (published) { + if (author) + footer.t(', '); + footer.c('time', {datetime: published}).t((new Date).set8601(published).getRelative()).up(); + } + + try { + var summary = atom.getChild('summary', ns.atom).getText(); + if (summary) + article.c('p').t(summary).up(); + } catch (e) { } + + try { + var links = atom.getChildren('link'); + for (var i in links) { + var link = links[i]; + + if (link.attrs.rel !== 'replies') + continue; + + if (link.attrs.title !== 'comments') + continue; + + var href = new JID; + href.uri = link.attrs.href; + + article.c('a', {href: '?jid=' + href.bare + ';node=' + href.query.node/* + ';comments=' + params.jid + '/' + params.node*/}).t('Comments !'); + break; + } + } catch (e) { } + + return article; +}; + +var generatePage = function(jid) { + var r = received[jid.full]; + var s = r.wait; + var form = r.form; + var data = r.data; + + if (!form || !data) + return; + + for (var i in s) { + r = s[i]; + delete s[i]; + makePage(r.res, jid.full, form, data, r.noscript); + } +}; + +var makePage = function(res, jid, form, data, noscript) { + var body = '</div>'; + + for (var id in data) { + var item = data[id]; + var article = parseAtom(item, id); + body = article + body; + } + body = '<div e:jid="' + jid + '">' + body; + + home(res, form['pubsub#title'], form['pubsub#description'], body, 'Node created the <time>' + form['pubsub#creation_date'] + '</time> by <cite>' + form['pubsub#creator'] + '</cite> with <a href="http://linkmauve.fr/dev/eldonilo/blog">Eldonilo blog</a>.', noscript); +}; + +var servePage = function(url, res) { + util.log(url.href); + + var query = require('querystring').parse(url.query, ';'); + + var page = new JID(config.defaultNode); + if (query.jid) + page.bare = query.jid; + if (query.node) + page.resource = query.node; + + if (query.type === 'atom' && config.atomRoot) { + res.writeHead(301, {'Location': config.atomRoot + page.resource}); + return res.end(); + } + + res.writeHead(200, {'Content-Type': 'application/xhtml+xml'}); + + var noscript = false; + if (query.no === 'server') + return fs.readFile('index.xhtml', function(err, data) { + res.end(data); + }); + else if (query.no === 'client') + noscript = true; + + var jid = page.full; + if (!received[jid]) { + getNodeInfo(page); + getNodeItems(page); + received[jid] = {data: null, form: null, wait: {}}; + received[jid].wait[getUniqId()] = {res: res, noscript: noscript}; + } else { + received[jid].wait[getUniqId()] = {res: res, noscript: noscript}; + generatePage(page); + } +} + +var home = function(res, title, desc, body, footer, noscript) { + res.writeHead(200, {'Content-Type': 'application/xhtml+xml'}); + res.write('<?xml version="1.0" encoding="utf-8"?>\n'); + res.write('<?xml-stylesheet type="text/css" href="theme.css" media="screen"?>\n'); + res.write('<!DOCTYPE html>\n'); + res.write('<html xmlns="http://www.w3.org/1999/xhtml">\n'); + res.write(' <head>\n'); + res.write(' <title>' + (title? title: 'Eldonilo blog') + '</title>\n'); + + if (!noscript) { + res.write('\n'); + res.write(' <script type="application/ecmascript" src="configuration.js" defer=""/>\n'); + res.write(' <script type="application/ecmascript" src="xml2json.js" defer=""/>\n'); + res.write(' <script type="application/ecmascript" src="strophe.js" defer=""/>\n'); + res.write(' <script type="application/ecmascript" src="strophe.pubsub.js" defer=""/>\n'); + res.write(' <script type="application/ecmascript" src="date.js" defer=""/>\n'); + res.write(' <script type="application/ecmascript" src="ns.js" defer=""/>\n'); + res.write(' <script type="application/ecmascript" src="jid.js" defer=""/>\n'); + res.write(' <script type="application/ecmascript" src="atom.js" defer=""/>\n'); + res.write(' <script type="application/ecmascript" src="nothing.js" defer=""/>\n'); + } + + if (config.atomRoot) { + res.write('\n'); + res.write(' <link rel="alternate" type="application/atom+xml" title="Atom feed" href="?type=atom"/>\n'); + } + + res.write(' </head>\n'); + res.write('\n'); + res.write(' <body>\n'); + res.write(' <header>\n'); + res.write(' <h1>' + (title? title: 'Eldonilo blog') + '</h1>\n'); + res.write(' <p>' + (desc? desc: 'Displaying your nodes.') + '</p>\n'); + res.write(' </header>\n'); + res.write('\n'); + res.write(' <nav>\n'); + res.write(' <ul>\n'); + res.write(' <li><a href="?no=client">Without client-side</a></li>\n'); + res.write(' <li><a href="?no=server">Without server-side</a></li>\n'); + res.write(' <li><a href="?">Hybrid mode</a></li>\n'); + res.write(' </ul>\n'); + res.write(' </nav>\n'); + res.write('\n'); + res.write(' <hr/>\n'); + res.write('\n'); + + if (!body) + res.write(' <section id="messages"/>\n'); + else { + res.write(' <section id="messages" xmlns:e="' + ns.e + '">\n'); + res.write(body); + res.write(' </section>\n'); + } + + res.write('\n'); + res.write(' <section hidden="">\n'); + res.write(' <h2>logs</h2>\n'); + res.write(' <div id="log"></div>\n'); + res.write(' </section>\n'); + res.write('\n'); + + if (!footer) + res.write(' <footer/>\n'); + else { + res.write(' <footer>\n'); + res.write(footer); + res.write(' </footer>\n'); + } + + if (!noscript) { + res.write('\n'); + res.write(' <script type="application/ecmascript" src="blog.js"/>\n'); + } + + res.write(' </body>\n'); + res.end('</html>\n'); +}; + +http.createServer(function(req, res) { + var re = new RegExp('^' + config.webRoot); + req.url = req.url.replace(re, ''); + var ext = req.url.substring(req.url.lastIndexOf('.')+1); + var url = require('url').parse(req.url); + + if (url.pathname === '') + return servePage(url, res); + + fs.readFile(req.url, function(err, data) { + if (err) + return servePage(url, res); + + res.writeHead(200, {'Content-Type': config.types[ext] || 'application/octet-stream'}); + + res.end(data); + }); +}).listen(config.webPort, config.webHost);