Mercurial > libervia-web
comparison src/browser/sat_browser/plugin_sec_otr.py @ 589:a5019e62c3e9 frontends_multi_profiles
browser side: big refactoring to base Libervia on QuickFrontend, first draft:
/!\ not finished, partially working and highly instable
- add collections module with an OrderedDict like class
- SatWebFrontend inherit from QuickApp
- general sat_frontends tools.jid module is used
- bridge/json methods have moved to json module
- UniBox is partially removed (should be totally removed before merge to trunk)
- Signals are now register with the generic registerSignal method (which is called mainly in QuickFrontend)
- the generic getOrCreateWidget method from QuickWidgetsManager is used instead of Libervia's specific methods
- all Widget are now based more or less directly on QuickWidget
- with the new QuickWidgetsManager.getWidgets method, it's no more necessary to check all widgets which are instance of a particular class
- ChatPanel and related moved to chat module
- MicroblogPanel and related moved to blog module
- global and overcomplicated send method has been disabled: each class should manage its own sending
- for consistency with other frontends, former ContactPanel has been renamed to ContactList and vice versa
- for the same reason, ChatPanel has been renamed to Chat
- for compatibility with QuickFrontend, a fake profile is used in several places, it is set to C.PROF_KEY_NONE (real profile is managed server side for obvious security reasons)
- changed default url for web panel to SàT website, and contact address to generic SàT contact address
- ContactList is based on QuickContactList, UI changes are done in update method
- bride call (now json module) have been greatly improved, in particular call can be done in the same way as for other frontends (bridge.method_name(arg1, arg2, ..., callback=cb, errback=eb). Blocking method must be called like async methods due to javascript architecture
- in bridge calls, a callback can now exists without errback
- hard reload on BridgeSignals remote error has been disabled, a better option should be implemented
- use of constants where that make sens, some style improvments
- avatars are temporarily disabled
- lot of code disabled, will be fixed or removed before merge
- various other changes, check diff for more details
server side: manage remote exception on getEntityData, removed getProfileJid call, added getWaitingConf, added getRoomsSubjects
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 24 Jan 2015 01:45:39 +0100 |
parents | 0a06cf833f5a |
children | 1c0d5a87c554 |
comparison
equal
deleted
inserted
replaced
585:bade589dbd5a | 589:a5019e62c3e9 |
---|---|
27 from sat.core.log import getLogger | 27 from sat.core.log import getLogger |
28 from sat.core import exceptions | 28 from sat.core import exceptions |
29 log = getLogger(__name__) | 29 log = getLogger(__name__) |
30 | 30 |
31 from constants import Const as C | 31 from constants import Const as C |
32 import jid | 32 from sat_frontends.tools import jid |
33 import otrjs_wrapper as otr | 33 import otrjs_wrapper as otr |
34 import dialog | 34 import dialog |
35 import panels | 35 import panels |
36 | 36 |
37 | 37 |
125 def __init__(self, host, account, other_jid): | 125 def __init__(self, host, account, other_jid): |
126 """ | 126 """ |
127 | 127 |
128 @param host (satWebFrontend) | 128 @param host (satWebFrontend) |
129 @param account (Account) | 129 @param account (Account) |
130 @param other_jid (JID): JID of the person your chat correspondent | 130 @param other_jid (jid.JID): JID of the person your chat correspondent |
131 """ | 131 """ |
132 super(Context, self).__init__(account, other_jid) | 132 super(Context, self).__init__(account, other_jid) |
133 self.host = host | 133 self.host = host |
134 | 134 |
135 def getPolicy(self, key): | 135 def getPolicy(self, key): |
155 if not encrypted: | 155 if not encrypted: |
156 log.warning("A plain-text message has been handled by otr.js") | 156 log.warning("A plain-text message has been handled by otr.js") |
157 log.debug("message received (was %s): %s" % ('encrypted' if encrypted else 'plain', msg)) | 157 log.debug("message received (was %s): %s" % ('encrypted' if encrypted else 'plain', msg)) |
158 if not encrypted: | 158 if not encrypted: |
159 if self.state == otr.context.STATE_ENCRYPTED: | 159 if self.state == otr.context.STATE_ENCRYPTED: |
160 log.warning(u"Received unencrypted message in an encrypted context (from %(jid)s)" % {'jid': self.peer.full()}) | 160 log.warning(u"Received unencrypted message in an encrypted context (from %(jid)s)" % {'jid': self.peer}) |
161 self.host.newMessageCb(self.peer, RECEIVE_PLAIN_IN_ENCRYPTED_CONTEXT, C.MESS_TYPE_INFO, self.host.whoami, {}) | 161 self.host.newMessageCb(self.peer, RECEIVE_PLAIN_IN_ENCRYPTED_CONTEXT, C.MESS_TYPE_INFO, self.host.whoami, {}) |
162 self.host.newMessageCb(self.peer, msg, "chat", self.host.whoami, {}) | 162 self.host.newMessageCb(self.peer, msg, "chat", self.host.whoami, {}) |
163 | 163 |
164 def sendMessageCb(self, msg, meta=None): | 164 def sendMessageCb(self, msg, meta=None): |
165 assert isinstance(self.peer, jid.JID) | 165 assert isinstance(self.peer, jid.JID) |
166 log.debug("message to send%s: %s" % ((' (attached meta data: %s)' % meta) if meta else '', msg)) | 166 log.debug("message to send%s: %s" % ((' (attached meta data: %s)' % meta) if meta else '', msg)) |
167 self.host.bridge.call('sendMessage', (None, self.host.sendError), self.peer.full(), msg, '', 'chat', {'send_only': 'true'}) | 167 self.host.bridge.call('sendMessage', (None, self.host.sendError), self.peer, msg, '', 'chat', {'send_only': 'true'}) |
168 | 168 |
169 def messageErrorCb(self, error): | 169 def messageErrorCb(self, error): |
170 log.error('error occured: %s' % error) | 170 log.error('error occured: %s' % error) |
171 | 171 |
172 def setStateCb(self, msg_state, status): | 172 def setStateCb(self, msg_state, status): |
173 if status == otr.context.STATUS_AKE_INIT: | 173 if status == otr.context.STATUS_AKE_INIT: |
174 return | 174 return |
175 | 175 |
176 other_jid_s = self.peer.full() | 176 other_jid_s = self.peer |
177 feedback = _(u"Error: the state of the conversation with %s is unknown!") | 177 feedback = _(u"Error: the state of the conversation with %s is unknown!") |
178 trust = self.getCurrentTrust() | 178 trust = self.getCurrentTrust() |
179 | 179 |
180 if status == otr.context.STATUS_SEND_QUERY: | 180 if status == otr.context.STATUS_SEND_QUERY: |
181 feedback = QUERY_ENCRYPTED if msg_state == otr.context.STATE_ENCRYPTED else QUERY_NOT_ENCRYPTED | 181 feedback = QUERY_ENCRYPTED if msg_state == otr.context.STATE_ENCRYPTED else QUERY_NOT_ENCRYPTED |
194 | 194 |
195 self.host.newMessageCb(self.peer, feedback.format(jid=other_jid_s), C.MESS_TYPE_INFO, self.host.whoami, {'header_info': OTR.getInfoText(msg_state, trust)}) | 195 self.host.newMessageCb(self.peer, feedback.format(jid=other_jid_s), C.MESS_TYPE_INFO, self.host.whoami, {'header_info': OTR.getInfoText(msg_state, trust)}) |
196 | 196 |
197 def setCurrentTrust(self, new_trust='', act='asked', type_='trust'): | 197 def setCurrentTrust(self, new_trust='', act='asked', type_='trust'): |
198 log.debug("setCurrentTrust: trust={trust}, act={act}, type={type}".format(type=type_, trust=new_trust, act=act)) | 198 log.debug("setCurrentTrust: trust={trust}, act={act}, type={type}".format(type=type_, trust=new_trust, act=act)) |
199 title = (AUTH_OTHER_TITLE if act == "asked" else AUTH_US_TITLE).format(jid=self.peer.full()) | 199 title = (AUTH_OTHER_TITLE if act == "asked" else AUTH_US_TITLE).format(jid=self.peer) |
200 old_trust = self.getCurrentTrust() | 200 old_trust = self.getCurrentTrust() |
201 if type_ == 'abort': | 201 if type_ == 'abort': |
202 msg = AUTH_ABORTED_TXT | 202 msg = AUTH_ABORTED_TXT |
203 elif new_trust: | 203 elif new_trust: |
204 if act == "asked": | 204 if act == "asked": |
234 | 234 |
235 def setTrust(confirm): | 235 def setTrust(confirm): |
236 self.setCurrentTrust('fingerprint' if confirm else '') | 236 self.setCurrentTrust('fingerprint' if confirm else '') |
237 | 237 |
238 text = (AUTH_INFO_TXT + "<i>" + AUTH_FINGERPRINT_TXT + "</i>" + AUTH_FINGERPRINT_VERIFY).format(you=self.host.whoami, your_fp=priv_key.fingerprint(), other=self.peer, other_fp=other_key.fingerprint(), eol=DIALOG_EOL) | 238 text = (AUTH_INFO_TXT + "<i>" + AUTH_FINGERPRINT_TXT + "</i>" + AUTH_FINGERPRINT_VERIFY).format(you=self.host.whoami, your_fp=priv_key.fingerprint(), other=self.peer, other_fp=other_key.fingerprint(), eol=DIALOG_EOL) |
239 title = AUTH_OTHER_TITLE.format(jid=self.peer.full()) | 239 title = AUTH_OTHER_TITLE.format(jid=self.peer) |
240 dialog.ConfirmDialog(setTrust, text, title, AddStyleName="maxWidthLimit").show() | 240 dialog.ConfirmDialog(setTrust, text, title, AddStyleName="maxWidthLimit").show() |
241 | 241 |
242 def smpAuthCb(self, type_, data, act=None): | 242 def smpAuthCb(self, type_, data, act=None): |
243 """OTR v3 authentication using the socialist millionaire protocol. | 243 """OTR v3 authentication using the socialist millionaire protocol. |
244 | 244 |
259 # fingerprints, we will reach this code... that's wrong, this method is for SMP! | 259 # fingerprints, we will reach this code... that's wrong, this method is for SMP! |
260 # There's probably a bug to fix in otr.js. Do it together with the issue that | 260 # There's probably a bug to fix in otr.js. Do it together with the issue that |
261 # make us need the dirty self.smpAuthAbort. | 261 # make us need the dirty self.smpAuthAbort. |
262 else: | 262 else: |
263 log.error("FIXME: unmanaged ambiguous 'act' value in Context.smpAuthCb!") | 263 log.error("FIXME: unmanaged ambiguous 'act' value in Context.smpAuthCb!") |
264 title = (AUTH_OTHER_TITLE if act == "asked" else AUTH_US_TITLE).format(jid=self.peer.full()) | 264 title = (AUTH_OTHER_TITLE if act == "asked" else AUTH_US_TITLE).format(jid=self.peer) |
265 if type_ == 'question': | 265 if type_ == 'question': |
266 if act == 'asked': | 266 if act == 'asked': |
267 def cb(question, answer=None): | 267 def cb(question, answer=None): |
268 if question is False or not answer: # dialog cancelled or the answer is empty | 268 if question is False or not answer: # dialog cancelled or the answer is empty |
269 return | 269 return |
295 | 295 |
296 | 296 |
297 class Account(otr.context.Account): | 297 class Account(otr.context.Account): |
298 | 298 |
299 def __init__(self, host): | 299 def __init__(self, host): |
300 log.debug(u"new account: %s" % host.whoami.full()) | 300 log.debug(u"new account: %s" % host.whoami) |
301 if not host.whoami.resource: | 301 if not host.whoami.resource: |
302 log.warning("Account created without resource") | 302 log.warning("Account created without resource") |
303 super(Account, self).__init__(host.whoami) | 303 super(Account, self).__init__(host.whoami) |
304 self.host = host | 304 self.host = host |
305 | 305 |
335 return self.contexts[other_jid] | 335 return self.contexts[other_jid] |
336 | 336 |
337 def getContextForUser(self, other_jid, start=True): | 337 def getContextForUser(self, other_jid, start=True): |
338 """Get the context for the given JID | 338 """Get the context for the given JID |
339 | 339 |
340 @param other_jid (JID): your correspondent | 340 @param other_jid (jid.JID): your correspondent |
341 @param start (bool): start non-existing context if True | 341 @param start (bool): start non-existing context if True |
342 @return: Context | 342 @return: Context |
343 """ | 343 """ |
344 log.debug(u"getContextForUser [%s]" % other_jid) | 344 log.debug(u"getContextForUser [%s]" % other_jid) |
345 if not other_jid.resource: | 345 if not other_jid.resource: |
346 log.error("getContextForUser called with a bare jid") | 346 log.error("getContextForUser called with a bare jid") |
347 running_sessions = [jid.bareJID() for jid in self.contexts.keys() if self.contexts[jid].state == otr.context.STATE_ENCRYPTED] | 347 running_sessions = [jid_.bareJID() for jid_ in self.contexts.keys() if self.contexts[jid_].state == otr.context.STATE_ENCRYPTED] |
348 if start or (other_jid in running_sessions): | 348 if start or (other_jid in running_sessions): |
349 users_ml = DIALOG_USERS_ML.format(subject=D_("OTR issue in Libervia: getContextForUser called with a bare jid in an encrypted context")) | 349 users_ml = DIALOG_USERS_ML.format(subject=D_("OTR issue in Libervia: getContextForUser called with a bare jid in an encrypted context")) |
350 text = RESOURCE_ISSUE.format(eol=DIALOG_EOL, jid=other_jid.full(), users_ml=users_ml) | 350 text = RESOURCE_ISSUE.format(eol=DIALOG_EOL, jid=other_jid, users_ml=users_ml) |
351 dialog.InfoDialog(RESOURCE_ISSUE_TITLE, text, AddStyleName="maxWidthLimit").show() | 351 dialog.InfoDialog(RESOURCE_ISSUE_TITLE, text, AddStyleName="maxWidthLimit").show() |
352 return None # never start an OTR session with a bare JID | 352 return None # never start an OTR session with a bare JID |
353 if start: | 353 if start: |
354 return self.startContext(other_jid) | 354 return self.startContext(other_jid) |
355 else: | 355 else: |
378 return OTR_MSG_STATES[state][1 if trust else 0] | 378 return OTR_MSG_STATES[state][1 if trust else 0] |
379 | 379 |
380 def infoTextCallback(self, other_jid, cb): | 380 def infoTextCallback(self, other_jid, cb): |
381 """Get the current info text for a conversation and run a callback. | 381 """Get the current info text for a conversation and run a callback. |
382 | 382 |
383 @param other_jid (JID): JID of the correspondant | 383 @param other_jid (jid.JID): JID of the correspondant |
384 @paam cb (callable): method to be called with the computed info text | 384 @paam cb (callable): method to be called with the computed info text |
385 """ | 385 """ |
386 def gotResource(other_jid): | 386 def gotResource(other_jid): |
387 otrctx = self.context_manager.getContextForUser(other_jid, start=False) | 387 otrctx = self.context_manager.getContextForUser(other_jid, start=False) |
388 if otrctx is None: | 388 if otrctx is None: |
412 | 412 |
413 def profileDisconnected(self): | 413 def profileDisconnected(self): |
414 for context in self.context_manager.contexts.values(): | 414 for context in self.context_manager.contexts.values(): |
415 context.disconnect() | 415 context.disconnect() |
416 | 416 |
417 def fixResource(self, jid, cb): | 417 def fixResource(self, jid_, cb): |
418 # FIXME: it's dirty, but libervia doesn't manage resources correctly now, refactoring is planed | 418 # FIXME: it's dirty, but libervia doesn't manage resources correctly now, refactoring is planed |
419 if jid.resource: | 419 if jid_.resource: |
420 self.last_resources[jid.bare] = jid.resource | 420 self.last_resources[jid_.bare] = jid_.resource |
421 cb(jid) | 421 cb(jid_) |
422 elif jid.bare in self.last_resources: | 422 elif jid_.bare in self.last_resources: |
423 jid.setResource(self.last_resources[jid.bare]) | 423 # FIXME: to be removed: must use new resource system |
424 cb(jid) | 424 # jid_.setResource(self.last_resources[jid_.bare]) |
425 cb(jid_) | |
425 else: | 426 else: |
426 def gotResource(resource): | 427 pass # FIXME: to be removed: must use new resource system |
427 if resource: | 428 # def gotResource(resource): |
428 jid.setResource(resource) | 429 # if resource: |
429 self.last_resources[jid.bare] = jid.resource | 430 # jid_.setResource(resource) |
430 cb(jid) | 431 # self.last_resources[jid_.bare] = jid_.resource |
431 self.host.bridge.call('getLastResource', gotResource, jid.full()) | 432 # cb(jid_) |
433 # | |
434 # self.host.bridge.call('getLastResource', gotResource, jid_) | |
432 | 435 |
433 def messageReceivedTrigger(self, from_jid, msg, msg_type, to_jid, extra): | 436 def messageReceivedTrigger(self, from_jid, msg, msg_type, to_jid, extra): |
434 if msg_type == C.MESS_TYPE_INFO: | 437 if msg_type == C.MESS_TYPE_INFO: |
435 return True | 438 return True |
436 | 439 |
439 return True | 442 return True |
440 | 443 |
441 def decrypt(context): | 444 def decrypt(context): |
442 context.receiveMessage(msg) | 445 context.receiveMessage(msg) |
443 | 446 |
444 def cb(jid): | 447 def cb(jid_): |
445 otrctx = self.context_manager.getContextForUser(jid, start=False) | 448 otrctx = self.context_manager.getContextForUser(jid_, start=False) |
446 if otrctx is None: | 449 if otrctx is None: |
447 def confirm(confirm): | 450 def confirm(confirm): |
448 if confirm: | 451 if confirm: |
449 self.host.getOrCreateLiberviaWidget(panels.ChatPanel, {'item': jid}) | 452 self.host.getOrCreateLiberviaWidget(panels.ChatPanel, {'item': jid_}) |
450 decrypt(self.context_manager.startContext(jid)) | 453 decrypt(self.context_manager.startContext(jid_)) |
451 else: | 454 else: |
452 # FIXME: plain text messages with whitespaces would be lost here when WHITESPACE_START_AKE is True | 455 # FIXME: plain text messages with whitespaces would be lost here when WHITESPACE_START_AKE is True |
453 pass | 456 pass |
454 key = self.context_manager.account.privkey | 457 key = self.context_manager.account.privkey |
455 msg = QUERY_RECEIVED + QUERY_SLOWDOWN + (QUERY_KEY if key else QUERY_NO_KEY) + QUERY_CONFIRM | 458 msg = QUERY_RECEIVED + QUERY_SLOWDOWN + (QUERY_KEY if key else QUERY_NO_KEY) + QUERY_CONFIRM |
456 dialog.ConfirmDialog(confirm, msg.format(jid=jid.full(), eol=DIALOG_EOL), QUERY_TITLE, AddStyleName="maxWidthLimit").show() | 459 dialog.ConfirmDialog(confirm, msg.format(jid=jid_, eol=DIALOG_EOL), QUERY_TITLE, AddStyleName="maxWidthLimit").show() |
457 else: # do not ask if the context exist | 460 else: # do not ask if the context exist |
458 decrypt(otrctx) | 461 decrypt(otrctx) |
459 | 462 |
460 other_jid = to_jid if from_jid.bare == self.host.whoami.bare else from_jid | 463 other_jid = to_jid if from_jid.bare == self.host.whoami.bare else from_jid |
461 self.fixResource(other_jid, cb) | 464 self.fixResource(other_jid, cb) |
462 return False # interrupt the main process | 465 return False # interrupt the main process |
463 | 466 |
464 def sendMessageTrigger(self, to_jid, msg, msg_type, extra): | 467 def sendMessageTrigger(self, to_jid, msg, msg_type, extra): |
465 def cb(jid): | 468 def cb(jid_): |
466 otrctx = self.context_manager.getContextForUser(jid, start=False) | 469 otrctx = self.context_manager.getContextForUser(jid_, start=False) |
467 if otrctx is not None and otrctx.state != otr.context.STATE_PLAINTEXT: | 470 if otrctx is not None and otrctx.state != otr.context.STATE_PLAINTEXT: |
468 if otrctx.state == otr.context.STATE_ENCRYPTED: | 471 if otrctx.state == otr.context.STATE_ENCRYPTED: |
469 log.debug(u"encrypting message") | 472 log.debug(u"encrypting message") |
470 otrctx.sendMessage(msg) | 473 otrctx.sendMessage(msg) |
471 self.host.newMessageCb(self.host.whoami, msg, msg_type, jid, extra) | 474 self.host.newMessageCb(self.host.whoami, msg, msg_type, jid_, extra) |
472 else: | 475 else: |
473 feedback = SEND_PLAIN_IN_FINISHED_CONTEXT | 476 feedback = SEND_PLAIN_IN_FINISHED_CONTEXT |
474 dialog.InfoDialog(FINISHED_CONTEXT_TITLE.format(jid=to_jid.full()), feedback, AddStyleName="maxWidthLimit").show() | 477 dialog.InfoDialog(FINISHED_CONTEXT_TITLE.format(jid=to_jid), feedback, AddStyleName="maxWidthLimit").show() |
475 else: | 478 else: |
476 log.debug(u"sending message unencrypted") | 479 log.debug(u"sending message unencrypted") |
477 self.host.bridge.call('sendMessage', (None, self.host.sendError), to_jid.full(), msg, '', msg_type, extra) | 480 self.host.bridge.call('sendMessage', (None, self.host.sendError), to_jid, msg, '', msg_type, extra) |
478 | 481 |
479 if msg_type == 'groupchat': | 482 if msg_type == 'groupchat': |
480 return True | 483 return True |
481 self.fixResource(to_jid, cb) | 484 self.fixResource(to_jid, cb) |
482 return False # interrupt the main process | 485 return False # interrupt the main process |
487 return True | 490 return True |
488 | 491 |
489 def endSession(self, other_jid, profile, finish=False): | 492 def endSession(self, other_jid, profile, finish=False): |
490 """Finish or disconnect an OTR session | 493 """Finish or disconnect an OTR session |
491 | 494 |
492 @param other_jid (JID): str | 495 @param other_jid (jid.JID): str |
493 @param finish: if True, finish the session but do not disconnect it | 496 @param finish: if True, finish the session but do not disconnect it |
494 @return: True if the session has been finished or disconnected, False if there was nothing to do | 497 @return: True if the session has been finished or disconnected, False if there was nothing to do |
495 """ | 498 """ |
496 def cb(other_jid): | 499 def cb(other_jid): |
497 def not_available(): | 500 def not_available(): |
498 if not finish: | 501 if not finish: |
499 self.host.newMessageCb(other_jid, END_PLAIN.format(jid=other_jid.full()), C.MESS_TYPE_INFO, self.host.whoami, {}) | 502 self.host.newMessageCb(other_jid, END_PLAIN.format(jid=other_jid), C.MESS_TYPE_INFO, self.host.whoami, {}) |
500 | 503 |
501 priv_key = self.context_manager.account.privkey | 504 priv_key = self.context_manager.account.privkey |
502 if priv_key is None: | 505 if priv_key is None: |
503 not_available() | 506 not_available() |
504 return | 507 return |
524 def query(other_jid): | 527 def query(other_jid): |
525 otrctx = self.context_manager.getContextForUser(other_jid) | 528 otrctx = self.context_manager.getContextForUser(other_jid) |
526 if otrctx: | 529 if otrctx: |
527 otrctx.sendQueryMessage() | 530 otrctx.sendQueryMessage() |
528 | 531 |
529 def cb(jid): | 532 def cb(jid_): |
530 key = self.context_manager.account.privkey | 533 key = self.context_manager.account.privkey |
531 if key is None: | 534 if key is None: |
532 def confirm(confirm): | 535 def confirm(confirm): |
533 if confirm: | 536 if confirm: |
534 query(jid) | 537 query(jid_) |
535 msg = QUERY_SEND + QUERY_SLOWDOWN + QUERY_NO_KEY + QUERY_CONFIRM | 538 msg = QUERY_SEND + QUERY_SLOWDOWN + QUERY_NO_KEY + QUERY_CONFIRM |
536 dialog.ConfirmDialog(confirm, msg.format(jid=jid.full(), eol=DIALOG_EOL), QUERY_TITLE, AddStyleName="maxWidthLimit").show() | 539 dialog.ConfirmDialog(confirm, msg.format(jid=jid_, eol=DIALOG_EOL), QUERY_TITLE, AddStyleName="maxWidthLimit").show() |
537 else: # on query reception we ask always, if we initiate we just ask the first time | 540 else: # on query reception we ask always, if we initiate we just ask the first time |
538 query(jid) | 541 query(jid_) |
539 | 542 |
540 try: | 543 try: |
541 other_jid = menu_data['jid'] | 544 other_jid = menu_data['jid'] |
542 if other_jid.bare not in self.host.contact_panel.connected: | 545 if other_jid.bare not in self.host.contact_panel.connected: |
543 dialog.InfoDialog(ACTION_NA_TITLE, ACTION_NA, AddStyleName="maxWidthLimit").show() | 546 dialog.InfoDialog(ACTION_NA_TITLE, ACTION_NA, AddStyleName="maxWidthLimit").show() |