Mercurial > libervia-backend
comparison sat/plugins/plugin_xep_0054.py @ 2624:56f94936df1e
code style reformatting using black
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 27 Jun 2018 20:14:46 +0200 |
parents | 395a3d1c2888 |
children | 378188abe941 |
comparison
equal
deleted
inserted
replaced
2623:49533de4540b | 2624:56f94936df1e |
---|---|
19 # along with this program. If not, see <http://www.gnu.org/licenses/>. | 19 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
20 | 20 |
21 from sat.core.i18n import _ | 21 from sat.core.i18n import _ |
22 from sat.core.constants import Const as C | 22 from sat.core.constants import Const as C |
23 from sat.core.log import getLogger | 23 from sat.core.log import getLogger |
24 | |
24 log = getLogger(__name__) | 25 log = getLogger(__name__) |
25 from twisted.internet import threads, defer | 26 from twisted.internet import threads, defer |
26 from twisted.words.protocols.jabber import jid, error | 27 from twisted.words.protocols.jabber import jid, error |
27 from twisted.words.xish import domish | 28 from twisted.words.xish import domish |
28 from twisted.python.failure import Failure | 29 from twisted.python.failure import Failure |
34 from base64 import b64decode, b64encode | 35 from base64 import b64decode, b64encode |
35 from hashlib import sha1 | 36 from hashlib import sha1 |
36 from sat.core import exceptions | 37 from sat.core import exceptions |
37 from sat.memory import persistent | 38 from sat.memory import persistent |
38 import mimetypes | 39 import mimetypes |
40 | |
39 try: | 41 try: |
40 from PIL import Image | 42 from PIL import Image |
41 except: | 43 except: |
42 raise exceptions.MissingModule(u"Missing module pillow, please download/install it from https://python-pillow.github.io") | 44 raise exceptions.MissingModule( |
45 u"Missing module pillow, please download/install it from https://python-pillow.github.io" | |
46 ) | |
43 from cStringIO import StringIO | 47 from cStringIO import StringIO |
44 | 48 |
45 try: | 49 try: |
46 from twisted.words.protocols.xmlstream import XMPPHandler | 50 from twisted.words.protocols.xmlstream import XMPPHandler |
47 except ImportError: | 51 except ImportError: |
48 from wokkel.subprotocols import XMPPHandler | 52 from wokkel.subprotocols import XMPPHandler |
49 | 53 |
50 AVATAR_PATH = "avatars" | 54 AVATAR_PATH = "avatars" |
51 AVATAR_DIM = (64, 64) # FIXME: dim are not adapted to modern resolutions ! | 55 AVATAR_DIM = (64, 64) # FIXME: dim are not adapted to modern resolutions ! |
52 | 56 |
53 IQ_GET = '/iq[@type="get"]' | 57 IQ_GET = '/iq[@type="get"]' |
54 NS_VCARD = 'vcard-temp' | 58 NS_VCARD = "vcard-temp" |
55 VCARD_REQUEST = IQ_GET + '/vCard[@xmlns="' + NS_VCARD + '"]' # TODO: manage requests | 59 VCARD_REQUEST = IQ_GET + '/vCard[@xmlns="' + NS_VCARD + '"]' # TODO: manage requests |
56 | 60 |
57 PRESENCE = '/presence' | 61 PRESENCE = "/presence" |
58 NS_VCARD_UPDATE = 'vcard-temp:x:update' | 62 NS_VCARD_UPDATE = "vcard-temp:x:update" |
59 VCARD_UPDATE = PRESENCE + '/x[@xmlns="' + NS_VCARD_UPDATE + '"]' | 63 VCARD_UPDATE = PRESENCE + '/x[@xmlns="' + NS_VCARD_UPDATE + '"]' |
60 | 64 |
61 CACHED_DATA = {'avatar', 'nick'} | 65 CACHED_DATA = {"avatar", "nick"} |
62 MAX_AGE = 60 * 60 * 24 * 365 | 66 MAX_AGE = 60 * 60 * 24 * 365 |
63 | 67 |
64 PLUGIN_INFO = { | 68 PLUGIN_INFO = { |
65 C.PI_NAME: "XEP 0054 Plugin", | 69 C.PI_NAME: "XEP 0054 Plugin", |
66 C.PI_IMPORT_NAME: "XEP-0054", | 70 C.PI_IMPORT_NAME: "XEP-0054", |
68 C.PI_PROTOCOLS: ["XEP-0054", "XEP-0153"], | 72 C.PI_PROTOCOLS: ["XEP-0054", "XEP-0153"], |
69 C.PI_DEPENDENCIES: [], | 73 C.PI_DEPENDENCIES: [], |
70 C.PI_RECOMMENDATIONS: ["XEP-0045"], | 74 C.PI_RECOMMENDATIONS: ["XEP-0045"], |
71 C.PI_MAIN: "XEP_0054", | 75 C.PI_MAIN: "XEP_0054", |
72 C.PI_HANDLER: "yes", | 76 C.PI_HANDLER: "yes", |
73 C.PI_DESCRIPTION: _("""Implementation of vcard-temp""") | 77 C.PI_DESCRIPTION: _("""Implementation of vcard-temp"""), |
74 } | 78 } |
75 | 79 |
76 | 80 |
77 class XEP_0054(object): | 81 class XEP_0054(object): |
78 #TODO: - check that nickname is ok | 82 # TODO: - check that nickname is ok |
79 # - refactor the code/better use of Wokkel | 83 # - refactor the code/better use of Wokkel |
80 # - get missing values | 84 # - get missing values |
81 | 85 |
82 def __init__(self, host): | 86 def __init__(self, host): |
83 log.info(_(u"Plugin XEP_0054 initialization")) | 87 log.info(_(u"Plugin XEP_0054 initialization")) |
84 self.host = host | 88 self.host = host |
85 host.bridge.addMethod(u"avatarGet", u".plugin", in_sign=u'sbbs', out_sign=u's', method=self._getAvatar, async=True) | 89 host.bridge.addMethod( |
86 host.bridge.addMethod(u"avatarSet", u".plugin", in_sign=u'ss', out_sign=u'', method=self._setAvatar, async=True) | 90 u"avatarGet", |
91 u".plugin", | |
92 in_sign=u"sbbs", | |
93 out_sign=u"s", | |
94 method=self._getAvatar, | |
95 async=True, | |
96 ) | |
97 host.bridge.addMethod( | |
98 u"avatarSet", | |
99 u".plugin", | |
100 in_sign=u"ss", | |
101 out_sign=u"", | |
102 method=self._setAvatar, | |
103 async=True, | |
104 ) | |
87 host.trigger.add(u"presence_available", self.presenceAvailableTrigger) | 105 host.trigger.add(u"presence_available", self.presenceAvailableTrigger) |
88 host.memory.setSignalOnUpdate(u"avatar") | 106 host.memory.setSignalOnUpdate(u"avatar") |
89 host.memory.setSignalOnUpdate(u"nick") | 107 host.memory.setSignalOnUpdate(u"nick") |
90 | 108 |
91 def getHandler(self, client): | 109 def getHandler(self, client): |
96 | 114 |
97 @param entity_jid(jid.JID): full or bare jid of the entity check | 115 @param entity_jid(jid.JID): full or bare jid of the entity check |
98 @return (bool): True if the bare jid of the entity is a room jid | 116 @return (bool): True if the bare jid of the entity is a room jid |
99 """ | 117 """ |
100 try: | 118 try: |
101 muc_plg = self.host.plugins['XEP-0045'] | 119 muc_plg = self.host.plugins["XEP-0045"] |
102 except KeyError: | 120 except KeyError: |
103 return False | 121 return False |
104 | 122 |
105 try: | 123 try: |
106 muc_plg.checkRoomJoined(client, entity_jid.userhostJID()) | 124 muc_plg.checkRoomJoined(client, entity_jid.userhostJID()) |
121 return jid_ | 139 return jid_ |
122 | 140 |
123 def presenceAvailableTrigger(self, presence_elt, client): | 141 def presenceAvailableTrigger(self, presence_elt, client): |
124 if client.jid.userhost() in client._cache_0054: | 142 if client.jid.userhost() in client._cache_0054: |
125 try: | 143 try: |
126 avatar_hash = client._cache_0054[client.jid.userhost()]['avatar'] | 144 avatar_hash = client._cache_0054[client.jid.userhost()]["avatar"] |
127 except KeyError: | 145 except KeyError: |
128 log.info(u"No avatar in cache for {}".format(client.jid.userhost())) | 146 log.info(u"No avatar in cache for {}".format(client.jid.userhost())) |
129 return True | 147 return True |
130 x_elt = domish.Element((NS_VCARD_UPDATE, 'x')) | 148 x_elt = domish.Element((NS_VCARD_UPDATE, "x")) |
131 x_elt.addElement('photo', content=avatar_hash) | 149 x_elt.addElement("photo", content=avatar_hash) |
132 presence_elt.addChild(x_elt) | 150 presence_elt.addChild(x_elt) |
133 return True | 151 return True |
134 | 152 |
135 @defer.inlineCallbacks | 153 @defer.inlineCallbacks |
136 def profileConnecting(self, client): | 154 def profileConnecting(self, client): |
137 client._cache_0054 = persistent.PersistentBinaryDict(NS_VCARD, client.profile) | 155 client._cache_0054 = persistent.PersistentBinaryDict(NS_VCARD, client.profile) |
138 yield client._cache_0054.load() | 156 yield client._cache_0054.load() |
139 self._fillCachedValues(client.profile) | 157 self._fillCachedValues(client.profile) |
140 | 158 |
141 def _fillCachedValues(self, profile): | 159 def _fillCachedValues(self, profile): |
142 #FIXME: this may need to be reworked | 160 # FIXME: this may need to be reworked |
143 # the current naive approach keeps a map between all jids | 161 # the current naive approach keeps a map between all jids |
144 # in persistent cache, then put avatar hashs in memory. | 162 # in persistent cache, then put avatar hashs in memory. |
145 # Hashes should be shared between profiles (or not ? what | 163 # Hashes should be shared between profiles (or not ? what |
146 # if the avatar is different depending on who is requesting it | 164 # if the avatar is different depending on who is requesting it |
147 # this is not possible with vcard-tmp, but it is with XEP-0084). | 165 # this is not possible with vcard-tmp, but it is with XEP-0084). |
151 jid_ = jid.JID(jid_s) | 169 jid_ = jid.JID(jid_s) |
152 for name in CACHED_DATA: | 170 for name in CACHED_DATA: |
153 try: | 171 try: |
154 value = data[name] | 172 value = data[name] |
155 if value is None: | 173 if value is None: |
156 log.error(u"{name} value for {jid_} is None, ignoring".format(name=name, jid_=jid_)) | 174 log.error( |
175 u"{name} value for {jid_} is None, ignoring".format( | |
176 name=name, jid_=jid_ | |
177 ) | |
178 ) | |
157 continue | 179 continue |
158 self.host.memory.updateEntityData(jid_, name, data[name], silent=True, profile_key=profile) | 180 self.host.memory.updateEntityData( |
181 jid_, name, data[name], silent=True, profile_key=profile | |
182 ) | |
159 except KeyError: | 183 except KeyError: |
160 pass | 184 pass |
161 | 185 |
162 def updateCache(self, client, jid_, name, value): | 186 def updateCache(self, client, jid_, name, value): |
163 """update cache value | 187 """update cache value |
182 except KeyError: | 206 except KeyError: |
183 pass | 207 pass |
184 else: | 208 else: |
185 client._cache_0054.force(jid_s) | 209 client._cache_0054.force(jid_s) |
186 else: | 210 else: |
187 self.host.memory.updateEntityData(jid_, name, value, profile_key=client.profile) | 211 self.host.memory.updateEntityData( |
212 jid_, name, value, profile_key=client.profile | |
213 ) | |
188 if name in CACHED_DATA: | 214 if name in CACHED_DATA: |
189 client._cache_0054.setdefault(jid_s, {})[name] = value | 215 client._cache_0054.setdefault(jid_s, {})[name] = value |
190 client._cache_0054.force(jid_s) | 216 client._cache_0054.force(jid_s) |
191 | 217 |
192 def getCache(self, client, entity_jid, name): | 218 def getCache(self, client, entity_jid, name): |
204 | 230 |
205 def savePhoto(self, client, photo_elt, entity_jid): | 231 def savePhoto(self, client, photo_elt, entity_jid): |
206 """Parse a <PHOTO> photo_elt and save the picture""" | 232 """Parse a <PHOTO> photo_elt and save the picture""" |
207 # XXX: this method is launched in a separate thread | 233 # XXX: this method is launched in a separate thread |
208 try: | 234 try: |
209 mime_type = unicode(photo_elt.elements(NS_VCARD, 'TYPE').next()) | 235 mime_type = unicode(photo_elt.elements(NS_VCARD, "TYPE").next()) |
210 except StopIteration: | 236 except StopIteration: |
211 log.warning(u"no MIME type found, assuming image/png") | 237 log.warning(u"no MIME type found, assuming image/png") |
212 mime_type = u"image/png" | 238 mime_type = u"image/png" |
213 else: | 239 else: |
214 if not mime_type: | 240 if not mime_type: |
218 if mime_type == "image/x-png": | 244 if mime_type == "image/x-png": |
219 # XXX: this old MIME type is still used by some clients | 245 # XXX: this old MIME type is still used by some clients |
220 mime_type = "image/png" | 246 mime_type = "image/png" |
221 else: | 247 else: |
222 # TODO: handle other image formats (svg?) | 248 # TODO: handle other image formats (svg?) |
223 log.warning(u"following avatar image format is not handled: {type} [{jid}]".format( | 249 log.warning( |
224 type=mime_type, jid=entity_jid.full())) | 250 u"following avatar image format is not handled: {type} [{jid}]".format( |
251 type=mime_type, jid=entity_jid.full() | |
252 ) | |
253 ) | |
225 raise Failure(exceptions.DataError()) | 254 raise Failure(exceptions.DataError()) |
226 | 255 |
227 ext = mimetypes.guess_extension(mime_type, strict=False) | 256 ext = mimetypes.guess_extension(mime_type, strict=False) |
228 assert ext is not None | 257 assert ext is not None |
229 if ext == u'.jpe': | 258 if ext == u".jpe": |
230 ext = u'.jpg' | 259 ext = u".jpg" |
231 log.debug(u'photo of type {type} with extension {ext} found [{jid}]'.format( | 260 log.debug( |
232 type=mime_type, ext=ext, jid=entity_jid.full())) | 261 u"photo of type {type} with extension {ext} found [{jid}]".format( |
233 try: | 262 type=mime_type, ext=ext, jid=entity_jid.full() |
234 buf = str(photo_elt.elements(NS_VCARD, 'BINVAL').next()) | 263 ) |
264 ) | |
265 try: | |
266 buf = str(photo_elt.elements(NS_VCARD, "BINVAL").next()) | |
235 except StopIteration: | 267 except StopIteration: |
236 log.warning(u"BINVAL element not found") | 268 log.warning(u"BINVAL element not found") |
237 raise Failure(exceptions.NotFound()) | 269 raise Failure(exceptions.NotFound()) |
238 if not buf: | 270 if not buf: |
239 log.warning(u"empty avatar for {jid}".format(jid=entity_jid.full())) | 271 log.warning(u"empty avatar for {jid}".format(jid=entity_jid.full())) |
240 raise Failure(exceptions.NotFound()) | 272 raise Failure(exceptions.NotFound()) |
241 log.debug(_(u'Decoding binary')) | 273 log.debug(_(u"Decoding binary")) |
242 decoded = b64decode(buf) | 274 decoded = b64decode(buf) |
243 del buf | 275 del buf |
244 image_hash = sha1(decoded).hexdigest() | 276 image_hash = sha1(decoded).hexdigest() |
245 with client.cache.cacheData( | 277 with client.cache.cacheData( |
246 PLUGIN_INFO['import_name'], | 278 PLUGIN_INFO["import_name"], |
247 image_hash, | 279 image_hash, |
248 mime_type, | 280 mime_type, |
249 # we keep in cache for 1 year | 281 # we keep in cache for 1 year |
250 MAX_AGE | 282 MAX_AGE, |
251 ) as f: | 283 ) as f: |
252 f.write(decoded) | 284 f.write(decoded) |
253 return image_hash | 285 return image_hash |
254 | 286 |
255 @defer.inlineCallbacks | 287 @defer.inlineCallbacks |
256 def vCard2Dict(self, client, vcard, entity_jid): | 288 def vCard2Dict(self, client, vcard, entity_jid): |
257 """Convert a VCard to a dict, and save binaries""" | 289 """Convert a VCard to a dict, and save binaries""" |
258 log.debug((u"parsing vcard")) | 290 log.debug((u"parsing vcard")) |
259 vcard_dict = {} | 291 vcard_dict = {} |
260 | 292 |
261 for elem in vcard.elements(): | 293 for elem in vcard.elements(): |
262 if elem.name == 'FN': | 294 if elem.name == "FN": |
263 vcard_dict['fullname'] = unicode(elem) | 295 vcard_dict["fullname"] = unicode(elem) |
264 elif elem.name == 'NICKNAME': | 296 elif elem.name == "NICKNAME": |
265 vcard_dict['nick'] = unicode(elem) | 297 vcard_dict["nick"] = unicode(elem) |
266 self.updateCache(client, entity_jid, 'nick', vcard_dict['nick']) | 298 self.updateCache(client, entity_jid, "nick", vcard_dict["nick"]) |
267 elif elem.name == 'URL': | 299 elif elem.name == "URL": |
268 vcard_dict['website'] = unicode(elem) | 300 vcard_dict["website"] = unicode(elem) |
269 elif elem.name == 'EMAIL': | 301 elif elem.name == "EMAIL": |
270 vcard_dict['email'] = unicode(elem) | 302 vcard_dict["email"] = unicode(elem) |
271 elif elem.name == 'BDAY': | 303 elif elem.name == "BDAY": |
272 vcard_dict['birthday'] = unicode(elem) | 304 vcard_dict["birthday"] = unicode(elem) |
273 elif elem.name == 'PHOTO': | 305 elif elem.name == "PHOTO": |
274 # TODO: handle EXTVAL | 306 # TODO: handle EXTVAL |
275 try: | 307 try: |
276 avatar_hash = yield threads.deferToThread( | 308 avatar_hash = yield threads.deferToThread( |
277 self.savePhoto, client, elem, entity_jid) | 309 self.savePhoto, client, elem, entity_jid |
310 ) | |
278 except (exceptions.DataError, exceptions.NotFound) as e: | 311 except (exceptions.DataError, exceptions.NotFound) as e: |
279 avatar_hash = '' | 312 avatar_hash = "" |
280 vcard_dict['avatar'] = avatar_hash | 313 vcard_dict["avatar"] = avatar_hash |
281 except Exception as e: | 314 except Exception as e: |
282 log.error(u"avatar saving error: {}".format(e)) | 315 log.error(u"avatar saving error: {}".format(e)) |
283 avatar_hash = None | 316 avatar_hash = None |
284 else: | 317 else: |
285 vcard_dict['avatar'] = avatar_hash | 318 vcard_dict["avatar"] = avatar_hash |
286 self.updateCache(client, entity_jid, 'avatar', avatar_hash) | 319 self.updateCache(client, entity_jid, "avatar", avatar_hash) |
287 else: | 320 else: |
288 log.debug(u'FIXME: [{}] VCard tag is not managed yet'.format(elem.name)) | 321 log.debug(u"FIXME: [{}] VCard tag is not managed yet".format(elem.name)) |
289 | 322 |
290 # if a data in cache doesn't exist anymore, we need to delete it | 323 # if a data in cache doesn't exist anymore, we need to delete it |
291 # so we check CACHED_DATA no gotten (i.e. not in vcard_dict keys) | 324 # so we check CACHED_DATA no gotten (i.e. not in vcard_dict keys) |
292 # and we reset them | 325 # and we reset them |
293 for datum in CACHED_DATA.difference(vcard_dict.keys()): | 326 for datum in CACHED_DATA.difference(vcard_dict.keys()): |
294 log.debug(u"reseting vcard datum [{datum}] for {entity}".format(datum=datum, entity=entity_jid.full())) | 327 log.debug( |
328 u"reseting vcard datum [{datum}] for {entity}".format( | |
329 datum=datum, entity=entity_jid.full() | |
330 ) | |
331 ) | |
295 self.updateCache(client, entity_jid, datum, None) | 332 self.updateCache(client, entity_jid, datum, None) |
296 | 333 |
297 defer.returnValue(vcard_dict) | 334 defer.returnValue(vcard_dict) |
298 | 335 |
299 def _vCardCb(self, vcard_elt, to_jid, client): | 336 def _vCardCb(self, vcard_elt, to_jid, client): |
307 d = self.vCard2Dict(client, vcard_elt, from_jid) | 344 d = self.vCard2Dict(client, vcard_elt, from_jid) |
308 return d | 345 return d |
309 | 346 |
310 def _vCardEb(self, failure_, to_jid, client): | 347 def _vCardEb(self, failure_, to_jid, client): |
311 """Called when something is wrong with registration""" | 348 """Called when something is wrong with registration""" |
312 log.warning(u"Can't get vCard for {jid}: {failure}".format(jid=to_jid.full, failure=failure_)) | 349 log.warning( |
350 u"Can't get vCard for {jid}: {failure}".format( | |
351 jid=to_jid.full, failure=failure_ | |
352 ) | |
353 ) | |
313 self.updateCache(client, to_jid, "avatar", None) | 354 self.updateCache(client, to_jid, "avatar", None) |
314 | 355 |
315 def _getVcardElt(self, iq_elt): | 356 def _getVcardElt(self, iq_elt): |
316 return iq_elt.elements(NS_VCARD, "vCard").next() | 357 return iq_elt.elements(NS_VCARD, "vCard").next() |
317 | 358 |
318 def getCardRaw(self, client, entity_jid): | 359 def getCardRaw(self, client, entity_jid): |
319 """get raw vCard XML | 360 """get raw vCard XML |
320 | 361 |
321 params are as in [getCard] | 362 params are as in [getCard] |
322 """ | 363 """ |
323 entity_jid = self.getBareOrFull(client, entity_jid) | 364 entity_jid = self.getBareOrFull(client, entity_jid) |
324 log.debug(u"Asking for {}'s VCard".format(entity_jid.full())) | 365 log.debug(u"Asking for {}'s VCard".format(entity_jid.full())) |
325 reg_request = client.IQ('get') | 366 reg_request = client.IQ("get") |
326 reg_request["from"] = client.jid.full() | 367 reg_request["from"] = client.jid.full() |
327 reg_request["to"] = entity_jid.full() | 368 reg_request["to"] = entity_jid.full() |
328 reg_request.addElement('vCard', NS_VCARD) | 369 reg_request.addElement("vCard", NS_VCARD) |
329 d = reg_request.send(entity_jid.full()) | 370 d = reg_request.send(entity_jid.full()) |
330 d.addCallback(self._getVcardElt) | 371 d.addCallback(self._getVcardElt) |
331 return d | 372 return d |
332 | 373 |
333 def getCard(self, client, entity_jid): | 374 def getCard(self, client, entity_jid): |
335 | 376 |
336 @param entity_jid(jid.JID): jid from which we want the VCard | 377 @param entity_jid(jid.JID): jid from which we want the VCard |
337 @result: id to retrieve the profile | 378 @result: id to retrieve the profile |
338 """ | 379 """ |
339 d = self.getCardRaw(client, entity_jid) | 380 d = self.getCardRaw(client, entity_jid) |
340 d.addCallbacks(self._vCardCb, self._vCardEb, callbackArgs=[entity_jid, client], errbackArgs=[entity_jid, client]) | 381 d.addCallbacks( |
382 self._vCardCb, | |
383 self._vCardEb, | |
384 callbackArgs=[entity_jid, client], | |
385 errbackArgs=[entity_jid, client], | |
386 ) | |
341 return d | 387 return d |
342 | 388 |
343 def _getCardCb(self, dummy, client, entity): | 389 def _getCardCb(self, dummy, client, entity): |
344 try: | 390 try: |
345 return client._cache_0054[entity.full()]['avatar'] | 391 return client._cache_0054[entity.full()]["avatar"] |
346 except KeyError: | 392 except KeyError: |
347 raise Failure(exceptions.NotFound()) | 393 raise Failure(exceptions.NotFound()) |
348 | 394 |
349 def _getAvatar(self, entity, cache_only, hash_only, profile): | 395 def _getAvatar(self, entity, cache_only, hash_only, profile): |
350 client = self.host.getClient(profile) | 396 client = self.host.getClient(profile) |
351 d = self.getAvatar(client, jid.JID(entity), cache_only, hash_only) | 397 d = self.getAvatar(client, jid.JID(entity), cache_only, hash_only) |
352 d.addErrback(lambda dummy: '') | 398 d.addErrback(lambda dummy: "") |
353 | 399 |
354 return d | 400 return d |
355 | 401 |
356 def getAvatar(self, client, entity, cache_only=True, hash_only=False): | 402 def getAvatar(self, client, entity, cache_only=True, hash_only=False): |
357 """get avatar full path or hash | 403 """get avatar full path or hash |
368 entity = self.getBareOrFull(client, entity) | 414 entity = self.getBareOrFull(client, entity) |
369 full_path = None | 415 full_path = None |
370 | 416 |
371 try: | 417 try: |
372 # we first check if we have avatar in cache | 418 # we first check if we have avatar in cache |
373 avatar_hash = client._cache_0054[entity.full()]['avatar'] | 419 avatar_hash = client._cache_0054[entity.full()]["avatar"] |
374 if avatar_hash: | 420 if avatar_hash: |
375 # avatar is known and exists | 421 # avatar is known and exists |
376 full_path = client.cache.getFilePath(avatar_hash) | 422 full_path = client.cache.getFilePath(avatar_hash) |
377 if full_path is None: | 423 if full_path is None: |
378 # cache file is not available (probably expired) | 424 # cache file is not available (probably expired) |
379 raise KeyError | 425 raise KeyError |
380 else: | 426 else: |
381 # avatar has already been checked but it is not set | 427 # avatar has already been checked but it is not set |
382 full_path = u'' | 428 full_path = u"" |
383 except KeyError: | 429 except KeyError: |
384 # avatar is not in cache | 430 # avatar is not in cache |
385 if cache_only: | 431 if cache_only: |
386 return defer.fail(Failure(exceptions.NotFound())) | 432 return defer.fail(Failure(exceptions.NotFound())) |
387 # we request vCard to get avatar | 433 # we request vCard to get avatar |
404 """get nick from cache, or check vCard | 450 """get nick from cache, or check vCard |
405 | 451 |
406 @param entity(jid.JID): entity to get nick from | 452 @param entity(jid.JID): entity to get nick from |
407 @return(unicode, None): nick or None if not found | 453 @return(unicode, None): nick or None if not found |
408 """ | 454 """ |
409 nick = self.getCache(client, entity, u'nick') | 455 nick = self.getCache(client, entity, u"nick") |
410 if nick is not None: | 456 if nick is not None: |
411 defer.returnValue(nick) | 457 defer.returnValue(nick) |
412 yield self.getCard(client, entity) | 458 yield self.getCard(client, entity) |
413 defer.returnValue(self.getCache(client, entity, u'nick')) | 459 defer.returnValue(self.getCache(client, entity, u"nick")) |
414 | 460 |
415 @defer.inlineCallbacks | 461 @defer.inlineCallbacks |
416 def setNick(self, client, nick): | 462 def setNick(self, client, nick): |
417 """update our vCard and set a nickname | 463 """update our vCard and set a nickname |
418 | 464 |
420 """ | 466 """ |
421 jid_ = client.jid.userhostJID() | 467 jid_ = client.jid.userhostJID() |
422 try: | 468 try: |
423 vcard_elt = yield self.getCardRaw(client, jid_) | 469 vcard_elt = yield self.getCardRaw(client, jid_) |
424 except error.StanzaError as e: | 470 except error.StanzaError as e: |
425 if e.condition == 'item-not-found': | 471 if e.condition == "item-not-found": |
426 vcard_elt = domish.Element((NS_VCARD, 'vCard')) | 472 vcard_elt = domish.Element((NS_VCARD, "vCard")) |
427 else: | 473 else: |
428 raise e | 474 raise e |
429 try: | 475 try: |
430 nickname_elt = next(vcard_elt.elements(NS_VCARD, u'NICKNAME')) | 476 nickname_elt = next(vcard_elt.elements(NS_VCARD, u"NICKNAME")) |
431 except StopIteration: | 477 except StopIteration: |
432 pass | 478 pass |
433 else: | 479 else: |
434 vcard_elt.children.remove(nickname_elt) | 480 vcard_elt.children.remove(nickname_elt) |
435 | 481 |
436 nickname_elt = vcard_elt.addElement((NS_VCARD, u'NICKNAME'), content=nick) | 482 nickname_elt = vcard_elt.addElement((NS_VCARD, u"NICKNAME"), content=nick) |
437 iq_elt = client.IQ() | 483 iq_elt = client.IQ() |
438 vcard_elt = iq_elt.addChild(vcard_elt) | 484 vcard_elt = iq_elt.addChild(vcard_elt) |
439 yield iq_elt.send() | 485 yield iq_elt.send() |
440 self.updateCache(client, jid_, u'nick', unicode(nick)) | 486 self.updateCache(client, jid_, u"nick", unicode(nick)) |
441 | 487 |
442 def _buildSetAvatar(self, client, vcard_elt, file_path): | 488 def _buildSetAvatar(self, client, vcard_elt, file_path): |
443 # XXX: this method is executed in a separate thread | 489 # XXX: this method is executed in a separate thread |
444 try: | 490 try: |
445 img = Image.open(file_path) | 491 img = Image.open(file_path) |
458 else: | 504 else: |
459 left += offset | 505 left += offset |
460 right -= offset | 506 right -= offset |
461 img = img.crop((left, upper, right, lower)) | 507 img = img.crop((left, upper, right, lower)) |
462 img_buf = StringIO() | 508 img_buf = StringIO() |
463 img.save(img_buf, 'PNG') | 509 img.save(img_buf, "PNG") |
464 | 510 |
465 photo_elt = vcard_elt.addElement('PHOTO') | 511 photo_elt = vcard_elt.addElement("PHOTO") |
466 photo_elt.addElement('TYPE', content='image/png') | 512 photo_elt.addElement("TYPE", content="image/png") |
467 photo_elt.addElement('BINVAL', content=b64encode(img_buf.getvalue())) | 513 photo_elt.addElement("BINVAL", content=b64encode(img_buf.getvalue())) |
468 image_hash = sha1(img_buf.getvalue()).hexdigest() | 514 image_hash = sha1(img_buf.getvalue()).hexdigest() |
469 with client.cache.cacheData( | 515 with client.cache.cacheData( |
470 PLUGIN_INFO['import_name'], | 516 PLUGIN_INFO["import_name"], image_hash, "image/png", MAX_AGE |
471 image_hash, | 517 ) as f: |
472 "image/png", | |
473 MAX_AGE | |
474 ) as f: | |
475 f.write(img_buf.getvalue()) | 518 f.write(img_buf.getvalue()) |
476 return image_hash | 519 return image_hash |
477 | 520 |
478 def _setAvatar(self, file_path, profile_key=C.PROF_KEY_NONE): | 521 def _setAvatar(self, file_path, profile_key=C.PROF_KEY_NONE): |
479 client = self.host.getClient(profile_key) | 522 client = self.host.getClient(profile_key) |
487 """ | 530 """ |
488 try: | 531 try: |
489 # we first check if a vcard already exists, to keep data | 532 # we first check if a vcard already exists, to keep data |
490 vcard_elt = yield self.getCardRaw(client, client.jid.userhostJID()) | 533 vcard_elt = yield self.getCardRaw(client, client.jid.userhostJID()) |
491 except error.StanzaError as e: | 534 except error.StanzaError as e: |
492 if e.condition == 'item-not-found': | 535 if e.condition == "item-not-found": |
493 vcard_elt = domish.Element((NS_VCARD, 'vCard')) | 536 vcard_elt = domish.Element((NS_VCARD, "vCard")) |
494 else: | 537 else: |
495 raise e | 538 raise e |
496 else: | 539 else: |
497 # the vcard exists, we need to remove PHOTO element as we'll make a new one | 540 # the vcard exists, we need to remove PHOTO element as we'll make a new one |
498 try: | 541 try: |
499 photo_elt = next(vcard_elt.elements(NS_VCARD, u'PHOTO')) | 542 photo_elt = next(vcard_elt.elements(NS_VCARD, u"PHOTO")) |
500 except StopIteration: | 543 except StopIteration: |
501 pass | 544 pass |
502 else: | 545 else: |
503 vcard_elt.children.remove(photo_elt) | 546 vcard_elt.children.remove(photo_elt) |
504 | 547 |
505 iq_elt = client.IQ() | 548 iq_elt = client.IQ() |
506 iq_elt.addChild(vcard_elt) | 549 iq_elt.addChild(vcard_elt) |
507 image_hash = yield threads.deferToThread(self._buildSetAvatar, client, vcard_elt, file_path) | 550 image_hash = yield threads.deferToThread( |
551 self._buildSetAvatar, client, vcard_elt, file_path | |
552 ) | |
508 # image is now at the right size/format | 553 # image is now at the right size/format |
509 | 554 |
510 self.updateCache(client, client.jid.userhostJID(), 'avatar', image_hash) | 555 self.updateCache(client, client.jid.userhostJID(), "avatar", image_hash) |
511 yield iq_elt.send() | 556 yield iq_elt.send() |
512 client.presence.available() # FIXME: should send the current presence, not always "available" ! | 557 client.presence.available() # FIXME: should send the current presence, not always "available" ! |
513 | 558 |
514 | 559 |
515 class XEP_0054_handler(XMPPHandler): | 560 class XEP_0054_handler(XMPPHandler): |
516 implements(iwokkel.IDisco) | 561 implements(iwokkel.IDisco) |
517 | 562 |
520 self.host = plugin_parent.host | 565 self.host = plugin_parent.host |
521 | 566 |
522 def connectionInitialized(self): | 567 def connectionInitialized(self): |
523 self.xmlstream.addObserver(VCARD_UPDATE, self.update) | 568 self.xmlstream.addObserver(VCARD_UPDATE, self.update) |
524 | 569 |
525 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): | 570 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): |
526 return [disco.DiscoFeature(NS_VCARD)] | 571 return [disco.DiscoFeature(NS_VCARD)] |
527 | 572 |
528 def getDiscoItems(self, requestor, target, nodeIdentifier=''): | 573 def getDiscoItems(self, requestor, target, nodeIdentifier=""): |
529 return [] | 574 return [] |
530 | 575 |
531 def _checkAvatarHash(self, dummy, client, entity, given_hash): | 576 def _checkAvatarHash(self, dummy, client, entity, given_hash): |
532 """check that hash in cash (i.e. computed hash) is the same as given one""" | 577 """check that hash in cash (i.e. computed hash) is the same as given one""" |
533 # XXX: if they differ, the avater will be requested on each connection | 578 # XXX: if they differ, the avater will be requested on each connection |
534 # TODO: try to avoid re-requesting avatar in this case | 579 # TODO: try to avoid re-requesting avatar in this case |
535 computed_hash = self.plugin_parent.getCache(client, entity, 'avatar') | 580 computed_hash = self.plugin_parent.getCache(client, entity, "avatar") |
536 if computed_hash != given_hash: | 581 if computed_hash != given_hash: |
537 log.warning(u"computed hash differs from given hash for {entity}:\n" | 582 log.warning( |
583 u"computed hash differs from given hash for {entity}:\n" | |
538 "computed: {computed}\ngiven: {given}".format( | 584 "computed: {computed}\ngiven: {given}".format( |
539 entity=entity, computed=computed_hash, given=given_hash)) | 585 entity=entity, computed=computed_hash, given=given_hash |
586 ) | |
587 ) | |
540 | 588 |
541 def update(self, presence): | 589 def update(self, presence): |
542 """Called on <presence/> stanza with vcard data | 590 """Called on <presence/> stanza with vcard data |
543 | 591 |
544 Check for avatar information, and get VCard if needed | 592 Check for avatar information, and get VCard if needed |
545 @param presend(domish.Element): <presence/> stanza | 593 @param presend(domish.Element): <presence/> stanza |
546 """ | 594 """ |
547 client = self.parent | 595 client = self.parent |
548 entity_jid = self.plugin_parent.getBareOrFull(client, jid.JID(presence['from'])) | 596 entity_jid = self.plugin_parent.getBareOrFull(client, jid.JID(presence["from"])) |
549 #FIXME: wokkel's data_form should be used here | 597 # FIXME: wokkel's data_form should be used here |
550 try: | 598 try: |
551 x_elt = presence.elements(NS_VCARD_UPDATE, 'x').next() | 599 x_elt = presence.elements(NS_VCARD_UPDATE, "x").next() |
552 except StopIteration: | 600 except StopIteration: |
553 return | 601 return |
554 | 602 |
555 try: | 603 try: |
556 photo_elt = x_elt.elements(NS_VCARD_UPDATE, 'photo').next() | 604 photo_elt = x_elt.elements(NS_VCARD_UPDATE, "photo").next() |
557 except StopIteration: | 605 except StopIteration: |
558 return | 606 return |
559 | 607 |
560 hash_ = unicode(photo_elt).strip() | 608 hash_ = unicode(photo_elt).strip() |
561 if hash_ == C.HASH_SHA1_EMPTY: | 609 if hash_ == C.HASH_SHA1_EMPTY: |
562 hash_ = u'' | 610 hash_ = u"" |
563 old_avatar = self.plugin_parent.getCache(client, entity_jid, 'avatar') | 611 old_avatar = self.plugin_parent.getCache(client, entity_jid, "avatar") |
564 | 612 |
565 if old_avatar == hash_: | 613 if old_avatar == hash_: |
566 # no change, we can return... | 614 # no change, we can return... |
567 if hash_: | 615 if hash_: |
568 # ...but we double check that avatar is in cache | 616 # ...but we double check that avatar is in cache |
569 file_path = client.cache.getFilePath(hash_) | 617 file_path = client.cache.getFilePath(hash_) |
570 if file_path is None: | 618 if file_path is None: |
571 log.error(u"Avatar for [{}] should be in cache but it is not! We get it".format(entity_jid.full())) | 619 log.error( |
620 u"Avatar for [{}] should be in cache but it is not! We get it".format( | |
621 entity_jid.full() | |
622 ) | |
623 ) | |
572 self.plugin_parent.getCard(client, entity_jid) | 624 self.plugin_parent.getCard(client, entity_jid) |
573 else: | 625 else: |
574 log.debug(u"avatar for {} already in cache".format(entity_jid.full())) | 626 log.debug(u"avatar for {} already in cache".format(entity_jid.full())) |
575 return | 627 return |
576 | 628 |
577 if not hash_: | 629 if not hash_: |
578 # the avatar has been removed | 630 # the avatar has been removed |
579 # XXX: we use empty string instead of None to indicate that we took avatar | 631 # XXX: we use empty string instead of None to indicate that we took avatar |
580 # but it is empty on purpose | 632 # but it is empty on purpose |
581 self.plugin_parent.updateCache(client, entity_jid, 'avatar', '') | 633 self.plugin_parent.updateCache(client, entity_jid, "avatar", "") |
582 return | 634 return |
583 | 635 |
584 file_path = client.cache.getFilePath(hash_) | 636 file_path = client.cache.getFilePath(hash_) |
585 if file_path is not None: | 637 if file_path is not None: |
586 log.debug(u"New avatar found for [{}], it's already in cache, we use it".format(entity_jid.full())) | 638 log.debug( |
587 self.plugin_parent.updateCache(client, entity_jid, 'avatar', hash_) | 639 u"New avatar found for [{}], it's already in cache, we use it".format( |
640 entity_jid.full() | |
641 ) | |
642 ) | |
643 self.plugin_parent.updateCache(client, entity_jid, "avatar", hash_) | |
588 else: | 644 else: |
589 log.debug(u'New avatar found for [{}], requesting vcard'.format(entity_jid.full())) | 645 log.debug( |
646 u"New avatar found for [{}], requesting vcard".format(entity_jid.full()) | |
647 ) | |
590 d = self.plugin_parent.getCard(client, entity_jid) | 648 d = self.plugin_parent.getCard(client, entity_jid) |
591 d.addCallback(self._checkAvatarHash, client, entity_jid, hash_) | 649 d.addCallback(self._checkAvatarHash, client, entity_jid, hash_) |