comparison avatar.js @ 20:e4916f1763e5

Deactivable debug; fix a stupid bug in gravatar; add a better default image and make it configurable.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Sat, 16 Jul 2011 13:54:47 +0200
parents 9ff8f951da99
children 46c42ec52680
comparison
equal deleted inserted replaced
19:9ff8f951da99 20:e4916f1763e5
39 39
40 process.addListener('uncaughtException', function (err) { 40 process.addListener('uncaughtException', function (err) {
41 console.log('Uncaught exception (' + err + '), this should never happen:\n' + err.stack); 41 console.log('Uncaught exception (' + err + '), this should never happen:\n' + err.stack);
42 }); 42 });
43 43
44 (function() { 44 if (config.debug)
45 var send = conn.send; 45 (function() {
46 conn.send = function(s) { 46 var send = conn.send;
47 util.log('Sent: ' + s + ''); 47 conn.send = function(s) {
48 send.call(conn, s); 48 console.log('Sent: ' + s + '');
49 }; 49 send.call(conn, s);
50 })(); 50 };
51 })();
51 52
52 conn.on('online', function () { 53 conn.on('online', function () {
53 console.log('Connected'); 54 util.log('Connected');
54 }); 55 });
55 56
56 conn.on('stanza', function (stanza) { 57 conn.on('stanza', function (stanza) {
57 util.log('Recv: ' + stanza + ''); 58 if (config.debug)
59 console.log('Recv: ' + stanza + '');
60
58 if (stanza.is('iq')) 61 if (stanza.is('iq'))
59 onIq(stanza); 62 onIq(stanza);
60 else 63 else
61 onError(stanza); 64 onError(stanza);
62 }); 65 });
71 var jids = {}; 74 var jids = {};
72 75
73 var sent = {}; 76 var sent = {};
74 77
75 var svgError = function(res, message) { 78 var svgError = function(res, message) {
79 util.log('No avatar at all, display a default image with the error in the title.');
80
76 res.writeHead(200, {'Content-Type': 'image/svg+xml'}); 81 res.writeHead(200, {'Content-Type': 'image/svg+xml'});
77 res.write('<?xml version="1.0" encoding="UTF-8"?>\n'); 82 res.write('<?xml version="1.0" encoding="UTF-8"?>\n');
78 res.write('<svg xmlns="http://www.w3.org/2000/svg" viewBox="-32 -36 64 64">\n'); 83 res.write('<svg xmlns="http://www.w3.org/2000/svg"');
79 res.write('\t<title>'+message+'</title>\n'); 84
80 res.write('\t<rect x="-32" y="-36" width="64" height="64" fill="white"/>\n'); 85 if (config.defaultImage)
81 res.write('\t<text font-family="sans-serif" font-weight="bold" text-anchor="middle">Error</text>\n'); 86 res.write(' xmlns:xlink="http://www.w3.org/1999/xlink"');
87
88 res.write(' viewBox="0 0 64 64">\n');
89 res.write('\t<title>' + message + '</title>\n');
90
91 if (config.defaultImage)
92 res.write('\t<image width="64" height="64" xlink:href="' + config.defaultImage + '"/>\n');
93 else {
94 res.write('\t<rect width="64" height="64" fill="silver"/>\n');
95 res.write('\t<circle cx="32" cy="26" r="14" fill="white"/>\n');
96 res.write('\t<ellipse cx="32" cy="64" rx="24" ry="26" fill="white"/>\n');
97 }
98
82 res.end('</svg>\n'); 99 res.end('</svg>\n');
83 } 100 }
84 101
85 var makeError = function(response) { 102 var makeError = function(response) {
86 response.attrs.type = 'error'; 103 response.attrs.type = 'error';
90 107
91 return response; 108 return response;
92 } 109 }
93 110
94 if (config.useGravatar) 111 if (config.useGravatar)
95 var noAvatar = function(res, from, message) { 112 var noAvatar = function(res, to, message) {
96 var options = { 113 var options = {
97 host: 'gravatar.com', 114 host: 'gravatar.com',
98 port: 80, 115 port: 80,
99 path: '/avatar/' + hash('md5').update(from).digest('hex') + '?d=404', 116 path: '/avatar/' + hash('md5').update(to).digest('hex') + '?d=404',
100 method: 'GET' 117 method: 'GET'
101 }; 118 };
119
120 util.log('No XMPP avatar, falling back to Gravatar for ' + to + '.');
102 121
103 var r = http.request(options, function(r) { 122 var r = http.request(options, function(r) {
104 if (r.statusCode != 200) 123 if (r.statusCode != 200)
105 return svgError(res, message + ' Additionaly, no gravatar available.'); 124 return svgError(res, message + ' Additionaly, no gravatar available.');
106 125
119 }); 138 });
120 139
121 return r.end(); 140 return r.end();
122 }; 141 };
123 else 142 else
124 var noAvatar = function(res, _, message) { 143 var noAvatar = function(res, to, message) {
144 util.log('No XMPP avatar for ' + to + '.');
125 return svgError(res, message); 145 return svgError(res, message);
126 }; 146 };
127 147
128 function onIq(stanza) { 148 function onIq(stanza) {
129 var type = stanza.getAttribute('type'); 149 var type = stanza.getAttribute('type');
146 var err = stanza.getChild('error').getChild().name; 166 var err = stanza.getChild('error').getChild().name;
147 } catch (e) { 167 } catch (e) {
148 var err = 'none'; 168 var err = 'none';
149 } 169 }
150 170
151 return noAvatar(res, from, 'Error during query of this user’s vCard: “'+err+'”.'); 171 return noAvatar(res, to, 'Error during query of this user’s vCard: “'+err+'”.');
152 } 172 }
153 173
154 var vCard = stanza.getChild('vCard', 'vcard-temp'); 174 var vCard = stanza.getChild('vCard', 'vcard-temp');
155 if (!vCard) 175 if (!vCard)
156 return noAvatar(res, from, 'Error: this user doesn’t have a vCard.'); 176 return noAvatar(res, to, 'Error: this user doesn’t have a vCard.');
157 177
158 try { 178 try {
159 var photo = vCard.getChild('PHOTO', 'vcard-temp'); 179 var photo = vCard.getChild('PHOTO', 'vcard-temp');
160 var base64 = photo.getChild('BINVAL', 'vcard-temp').getText(); 180 var base64 = photo.getChild('BINVAL', 'vcard-temp').getText();
161 181
163 var type = photo.getChild('TYPE', 'vcard-temp').getText(); 183 var type = photo.getChild('TYPE', 'vcard-temp').getText();
164 } catch (e) { 184 } catch (e) {
165 if (config.guessType) 185 if (config.guessType)
166 type = 'image/png'; // FIXME: use magic. 186 type = 'image/png'; // FIXME: use magic.
167 else 187 else
168 return noAvatar(res, from, 'Error: this user’s vCard doesn’t specify the MIME type of its avatar.'); 188 return noAvatar(res, to, 'Error: this user’s vCard doesn’t specify the MIME type of its avatar.');
169 } 189 }
170 190
171 var ext; 191 var ext;
172 for (var i in config.extensions) 192 for (var i in config.extensions)
173 if (type == config.extensions[i]) 193 if (type == config.extensions[i])
174 ext = i; 194 ext = i;
175 195
176 // Here we don’t try to guess the extension even if the option is set. 196 // Here we don’t try to guess the extension even if the option is set.
177 if (ext === undefined) { 197 if (ext === undefined) {
178 console.log('Unknown MIME type: '+type); 198 console.log('Unknown MIME type: '+type);
179 return noAvatar(res, from, 'Error: this user’s avatar is in an unknown format.'); 199 return noAvatar(res, to, 'Error: this user’s avatar is in an unknown format.');
180 } 200 }
181 201
182 var binval = new Buffer(base64.replace(/\n/g, ''), 'base64'); 202 var binval = new Buffer(base64.replace(/\n/g, ''), 'base64');
183 203
184 fs.writeFile(config.directory+'/'+to+'.'+ext, binval, function() { 204 fs.writeFile(config.directory+'/'+to+'.'+ext, binval, function() {
185 jids[to] = ext; 205 jids[to] = ext;
186 showImage(to, res); 206 showImage(to, res);
187 }); 207 });
188 } catch (e) { 208 } catch (e) {
189 return noAvatar(res, from, 'Error: this user doesn’t have an avatar in his/her vCard.'); 209 return noAvatar(res, to, 'Error: this user doesn’t have an avatar in his/her vCard.');
190 } 210 }
191 } 211 }
192 212
193 function onError(stanza) { 213 function onError(stanza) {
194 if (stanza.getAttribute('type') == 'error') 214 if (stanza.getAttribute('type') == 'error')
248 jids[tab[1]] = tab[2]; 268 jids[tab[1]] = tab[2];
249 } 269 }
250 }); 270 });
251 271
252 http.createServer(function (req, res) { 272 http.createServer(function (req, res) {
253 console.log('Connection from ' + (req.headers['x-forwarded-for'] || req.client.remoteAddress) + ' (' + req.headers['user-agent'] + ') to ' + req.method.toLocaleLowerCase() + ' “' + req.url + '”.'); 273 util.log('Connection from ' + (req.headers['x-forwarded-for'] || req.client.remoteAddress) + ' (' + req.headers['user-agent'] + ') to ' + req.method.toLocaleLowerCase() + ' “' + req.url + '”.');
254 274
255 var easterEggs = { 275 var easterEggs = {
256 source: { 276 source: {
257 re: new RegExp(config.webRoot + 'source/code$'), 277 re: new RegExp('^' + config.webRoot + 'source/code$'),
258 file: process.argv[1], 278 file: process.argv[1],
259 mime: 'application/ecmascript', 279 mime: 'application/ecmascript',
260 error: 'source code unavailable! oO' 280 error: 'source code unavailable! oO'
261 }, 281 },
262 README: {}, 282 README: {},
271 req.setEncoding('utf-8'); 291 req.setEncoding('utf-8');
272 292
273 for (var i in easterEggs) { 293 for (var i in easterEggs) {
274 var ee = easterEggs[i]; 294 var ee = easterEggs[i];
275 var file = ee.file || i; 295 var file = ee.file || i;
276 var re = ee.re || new RegExp(config.webRoot + file + '$'); 296 var re = ee.re || new RegExp('^' + config.webRoot + file + '$');
277 if (re.test(req.url)) { 297 if (re.test(req.url)) {
278 fs.readFile(file, function(err, content) { 298 fs.readFile(file, function(err, content) {
279 if (err) 299 if (err)
280 return noAvatar(res, from, 'Error: ' + (ee.error || file + ' unavailable.')); 300 return noAvatar(res, to, 'Error: ' + (ee.error || file + ' unavailable.'));
281 301
282 res.writeHead(200, {'Content-Type': ee.mime || 'text/plain'}); 302 res.writeHead(200, {'Content-Type': ee.mime || 'text/plain'});
283 res.end(content); 303 res.end(content);
284 }); 304 });
285 return; 305 return;
286 } 306 }
287 } 307 }
288 308
289 var jid = unescape(req.url.replace(new RegExp(config.webRoot), '')); 309 var jid = unescape(req.url.replace(new RegExp('^' + config.webRoot), ''));
290 310
291 if (jid === 'redirect') { 311 if (jid === 'redirect') {
292 if (req.method !== 'POST') { 312 if (req.method !== 'POST') {
293 res.writeHead(404, {'Content-Type': 'text/plain'}); 313 res.writeHead(404, {'Content-Type': 'text/plain'});
294 res.end('Error: redirect unavailable.'); 314 res.end('Error: redirect unavailable.');