Mercurial > eldonilo > avatar
diff avatar.js @ 0:d5cfe54f11aa
Initial commit.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Wed, 02 Mar 2011 17:33:28 +0100 |
parents | |
children | 0c19fadfc12d |
line wrap: on
line diff
new file mode 100755 --- /dev/null +++ b/avatar.js @@ -0,0 +1,317 @@ +#!/usr/bin/env node + +/* + * Copyright (C) 2011 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> + * + * This file is the source code of an XMPP avatar retriever. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +'use strict'; + +var config = require('./configuration'); + +try { + var xmpp = require('node-xmpp'); + var conn = new xmpp.Component({ + jid: config.jid, + password: config.password, + host: 'localhost', + port: 5347 + }); + + conn.on('stanza', function (stanza) { + if (stanza.is('iq')) + onIq(stanza); + else + onError(stanza); + }); + + conn._uniqueId = 42; + conn.getUniqueId = function(suffix) { + return ++this._uniqueId + (suffix?(":"+suffix):""); + }; + + var Element = require('ltx').Element; +} catch (e) { + var xmpp = require('xmpp'); + var conn = new xmpp.Connection(); + + conn.log = function (_, m) { console.log(m); }; + + conn.connect(config.jid, config.password, function (status, condition) { + if(status == xmpp.Status.CONNECTED) { + conn.addHandler(onIq, null, 'iq', null, null, null); + conn.addHandler(onError, null, 'message', null, null, null); + conn.addHandler(onError, null, 'presence', null, null, null); + } else + conn.log(xmpp.LogLevel.DEBUG, "New connection status: " + status + (condition?(" ("+condition+")"):"")); + }); + + xmpp.StanzaBuilder.prototype.cnode = function (stanza) + { + var parent = this.last_node[this.last_node.length-1]; + parent.tags.push(stanza); + parent.children.push(stanza); + this.last_node.push(stanza); + return this; + }; + + var Element = xmpp.StanzaBuilder; +} + +var fs = require('fs'); +var http = require('http'); + +process.addListener('uncaughtException', function (err) { + console.log('\x1b[41;1mUncaught exception (' + err + '), this should never happen:\x1b[0m\n' + err.stack); +}); + +var extensions = { + png: 'image/png', + svg: 'image/svg+xml', + jpg: 'image/jpeg', + gif: 'image/gif' +} + +var jids = {}; + +var sent = {}; + +var makeError = function(response) { + response.attr.type = 'error'; + + var error = new Element('error', {type: 'cancel'}); + error.c('feature-not-implemented', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up(); + response.cnode(error); + + return response; +} + +function onIq(stanza) { + var type = stanza.getAttribute('type'); + var from = stanza.getAttribute('to'); + var to = stanza.getAttribute('from'); + var id = stanza.getAttribute('id'); + + var response; + if (id) + response = new Element('iq', {to: to, from: from, type: 'result', id: id}); + else + response = new Element('iq', {to: to, from: from, type: 'result'}); + + if (!sent[id]) { + conn.send(makeError(response)); + return; + } + + var res = sent[id]; + delete sent[id]; + + if (type == 'error') { + try { + var err = stanza.getChild('error').getChild().name; + } catch (e) { + var err = 'none'; + } + res.writeHead(500, {'Content-Type': 'text/plain'}); + res.end('Error during query of this user’s vCard: “'+err+'”.'); + return; + } + + var vCard = stanza.getChild('vCard', 'vcard-temp'); + if (!vCard) { + res.writeHead(500, {'Content-Type': 'text/plain'}); + res.end('Error: this user doesn’t have a vCard.'); + return; + } + + try { + var photo = vCard.getChild('PHOTO', 'vcard-temp'); + var type = photo.getChild('TYPE', 'vcard-temp').getText(); + var base64 = photo.getChild('BINVAL', 'vcard-temp').getText(); + + var ext; + for (var i in extensions) + if (type == extensions[i]) + ext = i; + + if (ext === undefined) { + console.log('Type MIME inconnu : '+type); + res.writeHead(500, {'Content-Type': 'text/plain'}); + res.end('Error: this user’s avatar is in an unknown format.'); + return; + } + + var binval = new Buffer(base64.replace(/\n/g, ''), 'base64'); + + fs.writeFile(config.directory+'/'+to+'.'+ext, binval, function() { + jids[to] = ext; + showImage(to, res); + }); + } catch (e) { + res.writeHead(500, {'Content-Type': 'text/plain'}); + res.end('Error: this user doesn’t have an avatar in his/her vCard.'); + } +} + +function onError(stanza) { + if (stanza.getAttribute('type') == 'error') + return; + + var from = stanza.getAttribute('to'); + var to = stanza.getAttribute('from'); + var id = stanza.getAttribute('id'); + + var response; + if (id) + response = new Element(stanza.name, {to: to, from: from, id: id}); + else + response = new Element(stanza.name, {to: to, from: from}); + + conn.send(makeError(response)); +} + +var getVCard = function(jid) { + var id = conn.getUniqueId(); + + var toSend = new Element('iq', {to: jid, from: config.jid, type: 'get', id: id}) + .c('vCard', {xmlns: 'vcard-temp'}); + + conn.send(toSend); + + return id; +} + +var showImage = function(jid, res) { + var extension = jids[jid]; + var file = config.directory+'/'+jid+'.'+extension; + res.writeHead(200, {'Content-Type': extensions[extension]}); + fs.readFile(file, function(err, data) { + res.end(data); + }); + fs.stat(file, function(err, stats) { + if (err) { + console.log('Error when stat on “'+file+'”.'); + return; + } + + var last = new Date(stats.mtime); + var now = new Date(); + + if (now - last > 24*60*60*1000) { + fs.unlink(file, function() { + delete jids[jid]; + var id = getVCard(jid); + sent[id] = res; + }); + } + }); + return; +} + +fs.readdir('data', function(err, files) { + if (err) + process.exit('1'); + + for (i in files) { + var tab = /(.*)\.([a-z]{3})/.exec(files[i]); + jids[tab[1]] = tab[2]; + } +}); + +http.createServer(function (req, res) { + console.log('Connection from '+req.client.remoteAddress+' ('+req.headers['user-agent']+') to get “'+req.url+'”.'); + + var easterEggs = { + source: { + re: /^\/avatar\/source\/code$/, + file: process.argv[1], + mime: 'application/ecmascript', + error: 'source code unavailable! oO' + }, + README: {}, + COPYING: {}, + }; + + req.setEncoding('utf-8'); + + for (var i in easterEggs) { + var ee = easterEggs[i]; + var file = ee.file || i; + var re = ee.re || new RegExp('^/avatar/'+file+'$'); + if (re.test(req.url)) { + fs.readFile(file, function(err, content) { + if (err) { + res.writeHead(500, {'Content-Type': 'text/plain'}); + res.end('Error: ' + (ee.error || file + ' unavailable.')); + return; + } + res.writeHead(200, {'Content-Type': ee.mime || 'text/plain'}); + res.end(content); + }); + return; + } + } + + var jid = unescape(req.url.replace(/^\/avatar\//, '')); + + if (jid === '') { + res.writeHead(200, {'Content-Type': 'application/xhtml+xml'}); + res.write('<?xml version="1.0" encoding="utf-8"?>\n'); + res.write('<!DOCTYPE html>\n'); + res.write('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n'); + res.write('\t<head>\n'); + res.write('\t\t<title>JavaScript XMPP Avatar Retriever</title>\n'); + res.write('\t</head>\n'); + res.write('\t<body>\n'); + res.write('\t\t<header><h1>JavaScript XMPP Avatar Retriever</h1></header>\n'); + res.write('\t\t<p>Put any JID and get its avatar. :)</p>\n'); + res.write('\t\t<form action="redirect" method="post">\n'); + res.write('\t\t\t<p>\n'); + res.write('\t\t\t\t<input name="jid" type="text" placeholder="you@yourserver.tld"/>\n'); + res.write('\t\t\t\t<input type="submit"/>\n'); + res.write('\t\t\t</p>\n'); + res.write('\t\t</form>\n'); + res.write('\t\t<footer><p>(<a href="README">README</a>, <a href="source/code">source code</a>)</p></footer>\n'); + res.write('\t</body>\n'); + res.end('</html>\n'); + return; + } + + if (jid === 'redirect') { + if (req.method !== 'POST') { + res.writeHead(404, {'Content-Type': 'text/plain'}); + res.end('Error: redirect unavailable.'); + return; + } + + req.on('data', function(content) { + console.log(content); + var jid = unescape(content.toString()).replace(/^jid=/, ''); + res.writeHead(301, {'Location': jid}); + res.end(); + }); + return; + } + + if (jid in jids) { + showImage(jid, res); + return; + } + + var id = getVCard(jid); + + sent[id] = res; +}).listen(8032);