comparison plugins/plugin_xep_0054.py @ 42:874de3020e1c

Initial VCard (XEP-0054) support + misc fixes - new xep-0054 plugin, avatar are cached, new getProfile bridge method - gateways plugin (XEP-0100): new __private__ key in resulting data, used to keep target jid
author Goffi <goffi@goffi.org>
date Mon, 21 Dec 2009 13:22:11 +1100
parents
children 8a438a6ff587
comparison
equal deleted inserted replaced
41:d24629c631fc 42:874de3020e1c
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 """
5 SAT plugin for managing xep-0054
6 Copyright (C) 2009 Jérôme Poisson (goffi@goffi.org)
7
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 """
21
22 from logging import debug, info, error
23 from twisted.words.xish import domish
24 from twisted.internet import protocol, defer, threads, reactor
25 from twisted.words.protocols.jabber import client, jid, xmlstream
26 from twisted.words.protocols.jabber import error as jab_error
27 from twisted.words.protocols.jabber.xmlstream import IQ
28 import os.path
29 import pdb
30
31 from zope.interface import implements
32
33 from wokkel import disco, iwokkel
34
35 from base64 import b64decode
36 from hashlib import sha1
37 from time import sleep
38
39 AVATAR_PATH = "/avatars"
40
41 IQ_GET = '/iq[@type="get"]'
42 NS_VCARD = 'vcard-temp'
43 VCARD_REQUEST = IQ_GET + '/si[@xmlns="' + NS_VCARD + '"]' #TODO: manage requests
44
45 PLUGIN_INFO = {
46 "name": "XEP 0054 Plugin",
47 "import_name": "XEP_0054",
48 "type": "XEP",
49 "dependencies": [],
50 "main": "XEP_0054",
51 "description": """Implementation of vcard-temp"""
52 }
53
54 class XEP_0054():
55 implements(iwokkel.IDisco)
56
57 def __init__(self, host):
58 info("Plugin XEP_0054 initialization")
59 self.host = host
60 self.avatar_path = os.path.expanduser(self.host.get_const('local_dir') + AVATAR_PATH)
61 if not os.path.exists(self.avatar_path):
62 os.makedirs(self.avatar_path)
63 host.bridge.addMethod("getProfile", ".communication", in_sign='s', out_sign='s', method=self.getProfile)
64
65 def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
66 return [disco.DiscoFeature(NS_VCARD)]
67
68 def getDiscoItems(self, requestor, target, nodeIdentifier=''):
69 return []
70
71 def save_photo(self, photo_xml):
72 """Parse a <PHOTO> elem and save the picture"""
73 print "save_photo result"
74 for elem in photo_xml.elements():
75 if elem.name == 'TYPE':
76 info('Photo of type [%s] found' % str(elem))
77 if elem.name == 'BINVAL':
78 debug('Decoding binary')
79 decoded = b64decode(str(elem))
80 hash = sha1(decoded).hexdigest()
81 filename = self.avatar_path+'/'+hash
82 if not os.path.exists(filename):
83 with open(filename,'wb') as file:
84 file.write(decoded)
85 debug("file saved to %s" % hash)
86 else:
87 debug("file [%s] already in cache" % hash)
88 return hash
89
90 @defer.deferredGenerator
91 def vCard2Dict(self, vcard):
92 """Convert a VCard to a dict, and save binaries"""
93 debug ("parsing vcard")
94 dictionary = {}
95 d = defer.Deferred()
96
97 for elem in vcard.elements():
98 if elem.name == 'FN':
99 dictionary['fullname'] = unicode(elem)
100 elif elem.name == 'NICKNAME':
101 dictionary['nick'] = unicode(elem)
102 elif elem.name == 'URL':
103 dictionary['website'] = unicode(elem)
104 elif elem.name == 'EMAIL':
105 dictionary['email'] = unicode(elem)
106 elif elem.name == 'BDAY':
107 dictionary['birthday'] = unicode(elem)
108 elif elem.name == 'PHOTO':
109 debug('photo deferred')
110 d2 = defer.waitForDeferred(
111 threads.deferToThread(self.save_photo, elem))
112 yield d2
113 dictionary["photo"] = d2.getResult()
114 else:
115 info ('FIXME: [%s] VCard tag is not managed yet' % elem.name)
116
117 yield dictionary
118
119 def vcard_ok(self, answer):
120 """Called after the first get IQ"""
121 debug ("VCard found")
122
123 if answer.firstChildElement().name == "vCard":
124 d = self.vCard2Dict(answer.firstChildElement())
125 d.addCallback(lambda data: self.host.bridge.actionResult("RESULT", answer['id'], data))
126 else:
127 error ("FIXME: vCard not found as first child element")
128 self.host.bridge.actionResult("SUPPRESS", answer['id'], {}) #FIXME: maybe an error message would be best
129
130 def vcard_err(self, failure):
131 """Called when something is wrong with registration"""
132 error ("Can't find VCard of %s" % failure.value.stanza['from'])
133 self.host.bridge.actionResult("SUPPRESS", failure.value.stanza['id'], {}) #FIXME: maybe an error message would be best
134
135 def getProfile(self, target):
136 """Ask server for VCard
137 @param target: jid from which we want the VCard
138 @result: id to retrieve the profile"""
139 to_jid = jid.JID(target)
140 debug("Asking for %s's VCard" % to_jid.full())
141 reg_request=IQ(self.host.xmlstream,'get')
142 reg_request["from"]=self.host.me.full()
143 reg_request["to"] = to_jid.full()
144 query=reg_request.addElement('vCard', NS_VCARD)
145 reg_request.send(to_jid.full()).addCallbacks(self.vcard_ok, self.vcard_err)
146 return reg_request["id"]
147