Mercurial > eldonilo > blog
comparison 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 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:f62b5c395a48 |
---|---|
1 #!/usr/bin/env node | |
2 | |
3 'use strict'; | |
4 | |
5 var config = require('./configuration'); | |
6 | |
7 var util = require('util'); | |
8 var http = require('http'); | |
9 var fs = require('fs'); | |
10 var xmpp = require('node-xmpp'); | |
11 var Element = xmpp.Element; | |
12 var JID = require('./jid').JID; | |
13 var ns = require('./ns').ns; | |
14 var forms = require('./forms'); | |
15 require('./date'); | |
16 | |
17 var received = {}; | |
18 | |
19 var cl = new xmpp.Client(config); | |
20 | |
21 (function() { | |
22 var send = cl.send; | |
23 cl.send = function(s) { | |
24 util.log('Sent: [1;32m' + s + '[0m'); | |
25 send.call(cl, s); | |
26 } | |
27 })(); | |
28 | |
29 cl.on('online', function() { | |
30 util.log('Connected.'); | |
31 cl.send(new Element('presence')); | |
32 }); | |
33 | |
34 var getUniqId = (function() { | |
35 var id = 0; | |
36 return function() { | |
37 return ++id; | |
38 } | |
39 })(); | |
40 | |
41 var getNodeInfo = function(jid) { | |
42 var iq = new Element('iq', {to: jid.bare, type: 'get', id: getUniqId()}) | |
43 .c('query', {xmlns: ns.info, node: jid.resource}) | |
44 .up(); | |
45 | |
46 cl.send(iq); | |
47 }; | |
48 | |
49 var getNodeItems = function(jid) { | |
50 var iq = new Element('iq', {to: jid.bare, type: 'get', id: getUniqId()}) | |
51 .c('pubsub', {xmlns: ns.ps}) | |
52 .c('items', {node: jid.resource}) | |
53 .up() | |
54 .up(); | |
55 | |
56 cl.send(iq); | |
57 }; | |
58 | |
59 var makeError = function(response) { | |
60 response.attrs.type = 'error'; | |
61 | |
62 response.c('error', {type: 'cancel'}) | |
63 .c('feature-not-implemented', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}); | |
64 | |
65 return cl.send(response); | |
66 }; | |
67 | |
68 var handleInfo = function(query, from) { | |
69 var x = query.getChild('x', ns.data); | |
70 if (!x) | |
71 return; | |
72 | |
73 var jid = new JID; | |
74 jid.bare = from; | |
75 jid.resource = query.attrs.node; | |
76 | |
77 var form = forms.parse(x, true).fields; | |
78 received[jid].form = form; | |
79 generatePage(jid); | |
80 }; | |
81 | |
82 var handlePubSub = function(pubsub, from) { | |
83 var items = pubsub.getChild('items'); | |
84 if (!items) | |
85 return; | |
86 | |
87 var itemsNS = items.getNS(); | |
88 if (itemsNS !== ns.ps && itemsNS !== ns.pse) | |
89 return; | |
90 | |
91 var jid = new JID; | |
92 jid.bare = from; | |
93 jid.resource = items.attrs.node; | |
94 | |
95 items = items.getChildren('item', itemsNS); | |
96 if (!items) | |
97 return; | |
98 | |
99 if (!received[jid]) | |
100 received[jid] = {data: null, form: null, wait: {}}; | |
101 | |
102 var data = received[jid].data || {}; | |
103 | |
104 for (var i in items) { | |
105 var item = items[i]; | |
106 var id = item.attrs.id; | |
107 | |
108 var payload = item.children[0]; | |
109 delete payload.parent; | |
110 data[id] = payload; | |
111 } | |
112 | |
113 received[jid].data = data; | |
114 generatePage(jid); | |
115 }; | |
116 | |
117 cl.on('stanza', function(stanza) { | |
118 util.log('Recv: [1;34m' + stanza + '[0m'); | |
119 if (stanza.is('iq', ns.j)) { | |
120 var type = stanza.attrs.type; | |
121 if (type === 'error') | |
122 return; | |
123 | |
124 var result = new Element('iq', {to: stanza.attrs.from, from: stanza.attrs.to, type: 'result'}); | |
125 if (type === 'get' || type === 'set') | |
126 return makeError(result); | |
127 | |
128 var payload = stanza.getChild('query', ns.info); | |
129 if (payload) | |
130 return handleInfo(payload, stanza.attrs.from); | |
131 | |
132 payload = stanza.getChild('pubsub', ns.ps); | |
133 if (payload) | |
134 return handlePubSub(payload, stanza.attrs.from); | |
135 | |
136 } else if (stanza.is('message')) { | |
137 var type = stanza.attrs.type; | |
138 if (type === 'error') | |
139 return; | |
140 | |
141 if (type !== 'headline') | |
142 return; | |
143 | |
144 var payload = stanza.getChild('event', ns.pse); | |
145 if (payload) | |
146 return handlePubSub(payload, stanza.attrs.from); | |
147 } | |
148 }); | |
149 | |
150 var parseAtom = function(atom, id) { | |
151 var article = new Element('article', {'e:id': id, 'e:date': '2011-06-02T10:59:39Z'}); | |
152 | |
153 var avatar = article.c('aside').c('img') | |
154 article.up(); | |
155 | |
156 try { | |
157 var title = atom.getChild('title', ns.atom).getText(); | |
158 if (title) | |
159 article.c('h2').t(title).up(); | |
160 } catch (e) { } | |
161 | |
162 var footer = article.c('footer'); | |
163 article.up(); | |
164 | |
165 var author = atom.getChild('author', ns.atom); | |
166 if (author) { | |
167 footer.t('By '); | |
168 var name = author.getChild('name', ns.atom).getText(); | |
169 | |
170 try{ | |
171 var uri = author.getChild('uri', ns.atom).getText(); | |
172 footer.c('cite').c('a', {href: uri}).t(name).up(); | |
173 avatar.attrs.src = '/avatar/' + uri.substring(5); | |
174 } catch (e) { | |
175 footer.c('cite').t(name); | |
176 } | |
177 | |
178 try { | |
179 var email = author.getChild('email', ns.atom).getText(); | |
180 footer.t(', (').c('a', {href: email}).t('email').up().t(')'); | |
181 } catch (e) { } | |
182 footer.up(); | |
183 } | |
184 | |
185 var published = atom.getChild('published', ns.atom).getText(); | |
186 if (published) { | |
187 if (author) | |
188 footer.t(', '); | |
189 footer.c('time', {datetime: published}).t((new Date).set8601(published).getRelative()).up(); | |
190 } | |
191 | |
192 try { | |
193 var summary = atom.getChild('summary', ns.atom).getText(); | |
194 if (summary) | |
195 article.c('p').t(summary).up(); | |
196 } catch (e) { } | |
197 | |
198 try { | |
199 var links = atom.getChildren('link'); | |
200 for (var i in links) { | |
201 var link = links[i]; | |
202 | |
203 if (link.attrs.rel !== 'replies') | |
204 continue; | |
205 | |
206 if (link.attrs.title !== 'comments') | |
207 continue; | |
208 | |
209 var href = new JID; | |
210 href.uri = link.attrs.href; | |
211 | |
212 article.c('a', {href: '?jid=' + href.bare + ';node=' + href.query.node/* + ';comments=' + params.jid + '/' + params.node*/}).t('Comments !'); | |
213 break; | |
214 } | |
215 } catch (e) { } | |
216 | |
217 return article; | |
218 }; | |
219 | |
220 var generatePage = function(jid) { | |
221 var r = received[jid.full]; | |
222 var s = r.wait; | |
223 var form = r.form; | |
224 var data = r.data; | |
225 | |
226 if (!form || !data) | |
227 return; | |
228 | |
229 for (var i in s) { | |
230 r = s[i]; | |
231 delete s[i]; | |
232 makePage(r.res, jid.full, form, data, r.noscript); | |
233 } | |
234 }; | |
235 | |
236 var makePage = function(res, jid, form, data, noscript) { | |
237 var body = '</div>'; | |
238 | |
239 for (var id in data) { | |
240 var item = data[id]; | |
241 var article = parseAtom(item, id); | |
242 body = article + body; | |
243 } | |
244 body = '<div e:jid="' + jid + '">' + body; | |
245 | |
246 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); | |
247 }; | |
248 | |
249 var servePage = function(url, res) { | |
250 util.log(url.href); | |
251 | |
252 var query = require('querystring').parse(url.query, ';'); | |
253 | |
254 var page = new JID(config.defaultNode); | |
255 if (query.jid) | |
256 page.bare = query.jid; | |
257 if (query.node) | |
258 page.resource = query.node; | |
259 | |
260 if (query.type === 'atom' && config.atomRoot) { | |
261 res.writeHead(301, {'Location': config.atomRoot + page.resource}); | |
262 return res.end(); | |
263 } | |
264 | |
265 res.writeHead(200, {'Content-Type': 'application/xhtml+xml'}); | |
266 | |
267 var noscript = false; | |
268 if (query.no === 'server') | |
269 return fs.readFile('index.xhtml', function(err, data) { | |
270 res.end(data); | |
271 }); | |
272 else if (query.no === 'client') | |
273 noscript = true; | |
274 | |
275 var jid = page.full; | |
276 if (!received[jid]) { | |
277 getNodeInfo(page); | |
278 getNodeItems(page); | |
279 received[jid] = {data: null, form: null, wait: {}}; | |
280 received[jid].wait[getUniqId()] = {res: res, noscript: noscript}; | |
281 } else { | |
282 received[jid].wait[getUniqId()] = {res: res, noscript: noscript}; | |
283 generatePage(page); | |
284 } | |
285 } | |
286 | |
287 var home = function(res, title, desc, body, footer, noscript) { | |
288 res.writeHead(200, {'Content-Type': 'application/xhtml+xml'}); | |
289 res.write('<?xml version="1.0" encoding="utf-8"?>\n'); | |
290 res.write('<?xml-stylesheet type="text/css" href="theme.css" media="screen"?>\n'); | |
291 res.write('<!DOCTYPE html>\n'); | |
292 res.write('<html xmlns="http://www.w3.org/1999/xhtml">\n'); | |
293 res.write(' <head>\n'); | |
294 res.write(' <title>' + (title? title: 'Eldonilo blog') + '</title>\n'); | |
295 | |
296 if (!noscript) { | |
297 res.write('\n'); | |
298 res.write(' <script type="application/ecmascript" src="configuration.js" defer=""/>\n'); | |
299 res.write(' <script type="application/ecmascript" src="xml2json.js" defer=""/>\n'); | |
300 res.write(' <script type="application/ecmascript" src="strophe.js" defer=""/>\n'); | |
301 res.write(' <script type="application/ecmascript" src="strophe.pubsub.js" defer=""/>\n'); | |
302 res.write(' <script type="application/ecmascript" src="date.js" defer=""/>\n'); | |
303 res.write(' <script type="application/ecmascript" src="ns.js" defer=""/>\n'); | |
304 res.write(' <script type="application/ecmascript" src="jid.js" defer=""/>\n'); | |
305 res.write(' <script type="application/ecmascript" src="atom.js" defer=""/>\n'); | |
306 res.write(' <script type="application/ecmascript" src="nothing.js" defer=""/>\n'); | |
307 } | |
308 | |
309 if (config.atomRoot) { | |
310 res.write('\n'); | |
311 res.write(' <link rel="alternate" type="application/atom+xml" title="Atom feed" href="?type=atom"/>\n'); | |
312 } | |
313 | |
314 res.write(' </head>\n'); | |
315 res.write('\n'); | |
316 res.write(' <body>\n'); | |
317 res.write(' <header>\n'); | |
318 res.write(' <h1>' + (title? title: 'Eldonilo blog') + '</h1>\n'); | |
319 res.write(' <p>' + (desc? desc: 'Displaying your nodes.') + '</p>\n'); | |
320 res.write(' </header>\n'); | |
321 res.write('\n'); | |
322 res.write(' <nav>\n'); | |
323 res.write(' <ul>\n'); | |
324 res.write(' <li><a href="?no=client">Without client-side</a></li>\n'); | |
325 res.write(' <li><a href="?no=server">Without server-side</a></li>\n'); | |
326 res.write(' <li><a href="?">Hybrid mode</a></li>\n'); | |
327 res.write(' </ul>\n'); | |
328 res.write(' </nav>\n'); | |
329 res.write('\n'); | |
330 res.write(' <hr/>\n'); | |
331 res.write('\n'); | |
332 | |
333 if (!body) | |
334 res.write(' <section id="messages"/>\n'); | |
335 else { | |
336 res.write(' <section id="messages" xmlns:e="' + ns.e + '">\n'); | |
337 res.write(body); | |
338 res.write(' </section>\n'); | |
339 } | |
340 | |
341 res.write('\n'); | |
342 res.write(' <section hidden="">\n'); | |
343 res.write(' <h2>logs</h2>\n'); | |
344 res.write(' <div id="log"></div>\n'); | |
345 res.write(' </section>\n'); | |
346 res.write('\n'); | |
347 | |
348 if (!footer) | |
349 res.write(' <footer/>\n'); | |
350 else { | |
351 res.write(' <footer>\n'); | |
352 res.write(footer); | |
353 res.write(' </footer>\n'); | |
354 } | |
355 | |
356 if (!noscript) { | |
357 res.write('\n'); | |
358 res.write(' <script type="application/ecmascript" src="blog.js"/>\n'); | |
359 } | |
360 | |
361 res.write(' </body>\n'); | |
362 res.end('</html>\n'); | |
363 }; | |
364 | |
365 http.createServer(function(req, res) { | |
366 var re = new RegExp('^' + config.webRoot); | |
367 req.url = req.url.replace(re, ''); | |
368 var ext = req.url.substring(req.url.lastIndexOf('.')+1); | |
369 var url = require('url').parse(req.url); | |
370 | |
371 if (url.pathname === '') | |
372 return servePage(url, res); | |
373 | |
374 fs.readFile(req.url, function(err, data) { | |
375 if (err) | |
376 return servePage(url, res); | |
377 | |
378 res.writeHead(200, {'Content-Type': config.types[ext] || 'application/octet-stream'}); | |
379 | |
380 res.end(data); | |
381 }); | |
382 }).listen(config.webPort, config.webHost); |