Mercurial > eldonilo > avatar
comparison 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 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:d5cfe54f11aa |
---|---|
1 #!/usr/bin/env node | |
2 | |
3 /* | |
4 * Copyright (C) 2011 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> | |
5 * | |
6 * This file is the source code of an XMPP avatar retriever. | |
7 * | |
8 * This program is free software: you can redistribute it and/or modify | |
9 * it under the terms of the GNU Affero General Public License as | |
10 * published by the Free Software Foundation, version 3 of the License. | |
11 * | |
12 * This program is distributed in the hope that it will be useful, | |
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 * GNU Affero General Public License for more details. | |
16 * | |
17 * You should have received a copy of the GNU Affero General Public License | |
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 */ | |
20 | |
21 'use strict'; | |
22 | |
23 var config = require('./configuration'); | |
24 | |
25 try { | |
26 var xmpp = require('node-xmpp'); | |
27 var conn = new xmpp.Component({ | |
28 jid: config.jid, | |
29 password: config.password, | |
30 host: 'localhost', | |
31 port: 5347 | |
32 }); | |
33 | |
34 conn.on('stanza', function (stanza) { | |
35 if (stanza.is('iq')) | |
36 onIq(stanza); | |
37 else | |
38 onError(stanza); | |
39 }); | |
40 | |
41 conn._uniqueId = 42; | |
42 conn.getUniqueId = function(suffix) { | |
43 return ++this._uniqueId + (suffix?(":"+suffix):""); | |
44 }; | |
45 | |
46 var Element = require('ltx').Element; | |
47 } catch (e) { | |
48 var xmpp = require('xmpp'); | |
49 var conn = new xmpp.Connection(); | |
50 | |
51 conn.log = function (_, m) { console.log(m); }; | |
52 | |
53 conn.connect(config.jid, config.password, function (status, condition) { | |
54 if(status == xmpp.Status.CONNECTED) { | |
55 conn.addHandler(onIq, null, 'iq', null, null, null); | |
56 conn.addHandler(onError, null, 'message', null, null, null); | |
57 conn.addHandler(onError, null, 'presence', null, null, null); | |
58 } else | |
59 conn.log(xmpp.LogLevel.DEBUG, "New connection status: " + status + (condition?(" ("+condition+")"):"")); | |
60 }); | |
61 | |
62 xmpp.StanzaBuilder.prototype.cnode = function (stanza) | |
63 { | |
64 var parent = this.last_node[this.last_node.length-1]; | |
65 parent.tags.push(stanza); | |
66 parent.children.push(stanza); | |
67 this.last_node.push(stanza); | |
68 return this; | |
69 }; | |
70 | |
71 var Element = xmpp.StanzaBuilder; | |
72 } | |
73 | |
74 var fs = require('fs'); | |
75 var http = require('http'); | |
76 | |
77 process.addListener('uncaughtException', function (err) { | |
78 console.log('\x1b[41;1mUncaught exception (' + err + '), this should never happen:\x1b[0m\n' + err.stack); | |
79 }); | |
80 | |
81 var extensions = { | |
82 png: 'image/png', | |
83 svg: 'image/svg+xml', | |
84 jpg: 'image/jpeg', | |
85 gif: 'image/gif' | |
86 } | |
87 | |
88 var jids = {}; | |
89 | |
90 var sent = {}; | |
91 | |
92 var makeError = function(response) { | |
93 response.attr.type = 'error'; | |
94 | |
95 var error = new Element('error', {type: 'cancel'}); | |
96 error.c('feature-not-implemented', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up(); | |
97 response.cnode(error); | |
98 | |
99 return response; | |
100 } | |
101 | |
102 function onIq(stanza) { | |
103 var type = stanza.getAttribute('type'); | |
104 var from = stanza.getAttribute('to'); | |
105 var to = stanza.getAttribute('from'); | |
106 var id = stanza.getAttribute('id'); | |
107 | |
108 var response; | |
109 if (id) | |
110 response = new Element('iq', {to: to, from: from, type: 'result', id: id}); | |
111 else | |
112 response = new Element('iq', {to: to, from: from, type: 'result'}); | |
113 | |
114 if (!sent[id]) { | |
115 conn.send(makeError(response)); | |
116 return; | |
117 } | |
118 | |
119 var res = sent[id]; | |
120 delete sent[id]; | |
121 | |
122 if (type == 'error') { | |
123 try { | |
124 var err = stanza.getChild('error').getChild().name; | |
125 } catch (e) { | |
126 var err = 'none'; | |
127 } | |
128 res.writeHead(500, {'Content-Type': 'text/plain'}); | |
129 res.end('Error during query of this user’s vCard: “'+err+'”.'); | |
130 return; | |
131 } | |
132 | |
133 var vCard = stanza.getChild('vCard', 'vcard-temp'); | |
134 if (!vCard) { | |
135 res.writeHead(500, {'Content-Type': 'text/plain'}); | |
136 res.end('Error: this user doesn’t have a vCard.'); | |
137 return; | |
138 } | |
139 | |
140 try { | |
141 var photo = vCard.getChild('PHOTO', 'vcard-temp'); | |
142 var type = photo.getChild('TYPE', 'vcard-temp').getText(); | |
143 var base64 = photo.getChild('BINVAL', 'vcard-temp').getText(); | |
144 | |
145 var ext; | |
146 for (var i in extensions) | |
147 if (type == extensions[i]) | |
148 ext = i; | |
149 | |
150 if (ext === undefined) { | |
151 console.log('Type MIME inconnu : '+type); | |
152 res.writeHead(500, {'Content-Type': 'text/plain'}); | |
153 res.end('Error: this user’s avatar is in an unknown format.'); | |
154 return; | |
155 } | |
156 | |
157 var binval = new Buffer(base64.replace(/\n/g, ''), 'base64'); | |
158 | |
159 fs.writeFile(config.directory+'/'+to+'.'+ext, binval, function() { | |
160 jids[to] = ext; | |
161 showImage(to, res); | |
162 }); | |
163 } catch (e) { | |
164 res.writeHead(500, {'Content-Type': 'text/plain'}); | |
165 res.end('Error: this user doesn’t have an avatar in his/her vCard.'); | |
166 } | |
167 } | |
168 | |
169 function onError(stanza) { | |
170 if (stanza.getAttribute('type') == 'error') | |
171 return; | |
172 | |
173 var from = stanza.getAttribute('to'); | |
174 var to = stanza.getAttribute('from'); | |
175 var id = stanza.getAttribute('id'); | |
176 | |
177 var response; | |
178 if (id) | |
179 response = new Element(stanza.name, {to: to, from: from, id: id}); | |
180 else | |
181 response = new Element(stanza.name, {to: to, from: from}); | |
182 | |
183 conn.send(makeError(response)); | |
184 } | |
185 | |
186 var getVCard = function(jid) { | |
187 var id = conn.getUniqueId(); | |
188 | |
189 var toSend = new Element('iq', {to: jid, from: config.jid, type: 'get', id: id}) | |
190 .c('vCard', {xmlns: 'vcard-temp'}); | |
191 | |
192 conn.send(toSend); | |
193 | |
194 return id; | |
195 } | |
196 | |
197 var showImage = function(jid, res) { | |
198 var extension = jids[jid]; | |
199 var file = config.directory+'/'+jid+'.'+extension; | |
200 res.writeHead(200, {'Content-Type': extensions[extension]}); | |
201 fs.readFile(file, function(err, data) { | |
202 res.end(data); | |
203 }); | |
204 fs.stat(file, function(err, stats) { | |
205 if (err) { | |
206 console.log('Error when stat on “'+file+'”.'); | |
207 return; | |
208 } | |
209 | |
210 var last = new Date(stats.mtime); | |
211 var now = new Date(); | |
212 | |
213 if (now - last > 24*60*60*1000) { | |
214 fs.unlink(file, function() { | |
215 delete jids[jid]; | |
216 var id = getVCard(jid); | |
217 sent[id] = res; | |
218 }); | |
219 } | |
220 }); | |
221 return; | |
222 } | |
223 | |
224 fs.readdir('data', function(err, files) { | |
225 if (err) | |
226 process.exit('1'); | |
227 | |
228 for (i in files) { | |
229 var tab = /(.*)\.([a-z]{3})/.exec(files[i]); | |
230 jids[tab[1]] = tab[2]; | |
231 } | |
232 }); | |
233 | |
234 http.createServer(function (req, res) { | |
235 console.log('Connection from '+req.client.remoteAddress+' ('+req.headers['user-agent']+') to get “'+req.url+'”.'); | |
236 | |
237 var easterEggs = { | |
238 source: { | |
239 re: /^\/avatar\/source\/code$/, | |
240 file: process.argv[1], | |
241 mime: 'application/ecmascript', | |
242 error: 'source code unavailable! oO' | |
243 }, | |
244 README: {}, | |
245 COPYING: {}, | |
246 }; | |
247 | |
248 req.setEncoding('utf-8'); | |
249 | |
250 for (var i in easterEggs) { | |
251 var ee = easterEggs[i]; | |
252 var file = ee.file || i; | |
253 var re = ee.re || new RegExp('^/avatar/'+file+'$'); | |
254 if (re.test(req.url)) { | |
255 fs.readFile(file, function(err, content) { | |
256 if (err) { | |
257 res.writeHead(500, {'Content-Type': 'text/plain'}); | |
258 res.end('Error: ' + (ee.error || file + ' unavailable.')); | |
259 return; | |
260 } | |
261 res.writeHead(200, {'Content-Type': ee.mime || 'text/plain'}); | |
262 res.end(content); | |
263 }); | |
264 return; | |
265 } | |
266 } | |
267 | |
268 var jid = unescape(req.url.replace(/^\/avatar\//, '')); | |
269 | |
270 if (jid === '') { | |
271 res.writeHead(200, {'Content-Type': 'application/xhtml+xml'}); | |
272 res.write('<?xml version="1.0" encoding="utf-8"?>\n'); | |
273 res.write('<!DOCTYPE html>\n'); | |
274 res.write('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n'); | |
275 res.write('\t<head>\n'); | |
276 res.write('\t\t<title>JavaScript XMPP Avatar Retriever</title>\n'); | |
277 res.write('\t</head>\n'); | |
278 res.write('\t<body>\n'); | |
279 res.write('\t\t<header><h1>JavaScript XMPP Avatar Retriever</h1></header>\n'); | |
280 res.write('\t\t<p>Put any JID and get its avatar. :)</p>\n'); | |
281 res.write('\t\t<form action="redirect" method="post">\n'); | |
282 res.write('\t\t\t<p>\n'); | |
283 res.write('\t\t\t\t<input name="jid" type="text" placeholder="you@yourserver.tld"/>\n'); | |
284 res.write('\t\t\t\t<input type="submit"/>\n'); | |
285 res.write('\t\t\t</p>\n'); | |
286 res.write('\t\t</form>\n'); | |
287 res.write('\t\t<footer><p>(<a href="README">README</a>, <a href="source/code">source code</a>)</p></footer>\n'); | |
288 res.write('\t</body>\n'); | |
289 res.end('</html>\n'); | |
290 return; | |
291 } | |
292 | |
293 if (jid === 'redirect') { | |
294 if (req.method !== 'POST') { | |
295 res.writeHead(404, {'Content-Type': 'text/plain'}); | |
296 res.end('Error: redirect unavailable.'); | |
297 return; | |
298 } | |
299 | |
300 req.on('data', function(content) { | |
301 console.log(content); | |
302 var jid = unescape(content.toString()).replace(/^jid=/, ''); | |
303 res.writeHead(301, {'Location': jid}); | |
304 res.end(); | |
305 }); | |
306 return; | |
307 } | |
308 | |
309 if (jid in jids) { | |
310 showImage(jid, res); | |
311 return; | |
312 } | |
313 | |
314 var id = getVCard(jid); | |
315 | |
316 sent[id] = res; | |
317 }).listen(8032); |