comparison sat_frontends/quick_frontend/quick_chat.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents b5f8cb26ef6f
children 1370323e8f6c
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
41 41
42 # from datetime import datetime 42 # from datetime import datetime
43 43
44 try: 44 try:
45 # FIXME: to be removed when an acceptable solution is here 45 # FIXME: to be removed when an acceptable solution is here
46 unicode("") # XXX: unicode doesn't exist in pyjamas 46 str("") # XXX: unicode doesn't exist in pyjamas
47 except ( 47 except (
48 TypeError, 48 TypeError,
49 AttributeError, 49 AttributeError,
50 ): # Error raised is not the same depending on pyjsbuild options 50 ): # Error raised is not the same depending on pyjsbuild options
51 unicode = str 51 str = str
52 52
53 # FIXME: day_format need to be settable (i18n) 53 # FIXME: day_format need to be settable (i18n)
54 54
55 55
56 class Message(object): 56 class Message(object):
76 if self.parent.type == C.CHAT_GROUP 76 if self.parent.type == C.CHAT_GROUP
77 else (from_jid.bare == self.host.profiles[profile].whoami.bare) 77 else (from_jid.bare == self.host.profiles[profile].whoami.bare)
78 ) 78 )
79 # is user mentioned here ? 79 # is user mentioned here ?
80 if self.parent.type == C.CHAT_GROUP and not self.own_mess: 80 if self.parent.type == C.CHAT_GROUP and not self.own_mess:
81 for m in msg.itervalues(): 81 for m in msg.values():
82 if self.parent.nick.lower() in m.lower(): 82 if self.parent.nick.lower() in m.lower():
83 self._mention = True 83 self._mention = True
84 break 84 break
85 self.handleMe() 85 self.handleMe()
86 self.widgets = set() # widgets linked to this message 86 self.widgets = set() # widgets linked to this message
87 87
88 def __unicode__(self): 88 def __str__(self):
89 return u"Message<{mess_type}> [{time}]{nick}> {message}".format( 89 return "Message<{mess_type}> [{time}]{nick}> {message}".format(
90 mess_type=self.type, 90 mess_type=self.type,
91 time=self.time_text, 91 time=self.time_text,
92 nick=self.nick, 92 nick=self.nick,
93 message=self.main_message) 93 message=self.main_message)
94
95 def __str__(self):
96 return self.__unicode__().encode('utf-8', 'ignore')
97 94
98 @property 95 @property
99 def host(self): 96 def host(self):
100 return self.parent.host 97 return self.parent.host
101 98
124 try: 121 try:
125 self.selected_lang = "" 122 self.selected_lang = ""
126 return self.message[""] 123 return self.message[""]
127 except KeyError: 124 except KeyError:
128 try: 125 try:
129 lang, mess = self.message.iteritems().next() 126 lang, mess = next(iter(self.message.items()))
130 self.selected_lang = lang 127 self.selected_lang = lang
131 return mess 128 return mess
132 except StopIteration: 129 except StopIteration:
133 log.error(u"Can't find message for uid {}".format(self.uid)) 130 log.error("Can't find message for uid {}".format(self.uid))
134 return "" 131 return ""
135 132
136 @property 133 @property
137 def main_message_xhtml(self): 134 def main_message_xhtml(self):
138 """rich message""" 135 """rich message"""
139 xhtml = {k: v for k, v in self.extra.iteritems() if "html" in k} 136 xhtml = {k: v for k, v in self.extra.items() if "html" in k}
140 if xhtml: 137 if xhtml:
141 # FIXME: we only return first found value for now 138 # FIXME: we only return first found value for now
142 return next(xhtml.itervalues()) 139 return next(iter(xhtml.values()))
143 140
144 @property 141 @property
145 def time_text(self): 142 def time_text(self):
146 """Return timestamp in a nicely formatted way""" 143 """Return timestamp in a nicely formatted way"""
147 # if the message was sent before today, we print the full date 144 # if the message was sent before today, we print the full date
148 timestamp = time.localtime(self.timestamp) 145 timestamp = time.localtime(self.timestamp)
149 time_format = u"%c" if timestamp < self.parent.day_change else u"%H:%M" 146 time_format = "%c" if timestamp < self.parent.day_change else "%H:%M"
150 return time.strftime(time_format, timestamp).decode(getlocale()[1] or "utf-8") 147 return time.strftime(time_format, timestamp)
151 148
152 @property 149 @property
153 def avatar(self): 150 def avatar(self):
154 """avatar full path or None if no avatar is found""" 151 """avatar full path or None if no avatar is found"""
155 ret = self.host.getAvatar(self.from_jid, profile=self.profile) 152 ret = self.host.getAvatar(self.from_jid, profile=self.profile)
160 contact_list = self.host.contact_lists[self.profile] 157 contact_list = self.host.contact_lists[self.profile]
161 if self.type == C.MESS_TYPE_INFO and self.info_type in ROOM_USER_MOVED: 158 if self.type == C.MESS_TYPE_INFO and self.info_type in ROOM_USER_MOVED:
162 try: 159 try:
163 return self.extra["user_nick"] 160 return self.extra["user_nick"]
164 except KeyError: 161 except KeyError:
165 log.error(u"extra data is missing user nick for uid {}".format(self.uid)) 162 log.error("extra data is missing user nick for uid {}".format(self.uid))
166 return "" 163 return ""
167 # FIXME: converted getSpecials to list for pyjamas 164 # FIXME: converted getSpecials to list for pyjamas
168 if self.parent.type == C.CHAT_GROUP or entity in list( 165 if self.parent.type == C.CHAT_GROUP or entity in list(
169 contact_list.getSpecials(C.CONTACT_SPECIAL_GROUP) 166 contact_list.getSpecials(C.CONTACT_SPECIAL_GROUP)
170 ): 167 ):
195 if several messages (different languages) are presents, they all need to start with "/me " 192 if several messages (different languages) are presents, they all need to start with "/me "
196 """ 193 """
197 # TODO: XHTML-IM /me are not handled 194 # TODO: XHTML-IM /me are not handled
198 me = False 195 me = False
199 # we need to check /me for every message 196 # we need to check /me for every message
200 for m in self.message.itervalues(): 197 for m in self.message.values():
201 if m.startswith(u"/me "): 198 if m.startswith("/me "):
202 me = True 199 me = True
203 else: 200 else:
204 me = False 201 me = False
205 break 202 break
206 if me: 203 if me:
207 self.type = C.MESS_TYPE_INFO 204 self.type = C.MESS_TYPE_INFO
208 self.extra["info_type"] = "me" 205 self.extra["info_type"] = "me"
209 nick = self.nick 206 nick = self.nick
210 for lang, mess in self.message.iteritems(): 207 for lang, mess in self.message.items():
211 self.message[lang] = u"* " + nick + mess[3:] 208 self.message[lang] = "* " + nick + mess[3:]
212 209
213 210
214 class MessageWidget(object): 211 class MessageWidget(object):
215 """Base classe for widgets""" 212 """Base classe for widgets"""
216 # This class does nothing and is only used to have a common ancestor 213 # This class does nothing and is only used to have a common ancestor
243 return data 240 return data
244 241
245 @property 242 @property
246 def jid(self): 243 def jid(self):
247 """jid in the room""" 244 """jid in the room"""
248 return jid.JID(u"{}/{}".format(self.parent.target.bare, self.nick)) 245 return jid.JID("{}/{}".format(self.parent.target.bare, self.nick))
249 246
250 @property 247 @property
251 def real_jid(self): 248 def real_jid(self):
252 """real jid if known else None""" 249 """real jid if known else None"""
253 return self._entity 250 return self._entity
293 self._resync_lock = False 290 self._resync_lock = False
294 self.setLocked() 291 self.setLocked()
295 if type_ == C.CHAT_GROUP: 292 if type_ == C.CHAT_GROUP:
296 if target.resource: 293 if target.resource:
297 raise exceptions.InternalError( 294 raise exceptions.InternalError(
298 u"a group chat entity can't have a resource" 295 "a group chat entity can't have a resource"
299 ) 296 )
300 if nick is None: 297 if nick is None:
301 raise exceptions.InternalError(u"nick must not be None for group chat") 298 raise exceptions.InternalError("nick must not be None for group chat")
302 299
303 self.nick = nick 300 self.nick = nick
304 self.occupants = {} 301 self.occupants = {}
305 self.setOccupants(occupants) 302 self.setOccupants(occupants)
306 else: 303 else:
307 if occupants is not None or nick is not None: 304 if occupants is not None or nick is not None:
308 raise exceptions.InternalError( 305 raise exceptions.InternalError(
309 u"only group chat can have occupants or nick" 306 "only group chat can have occupants or nick"
310 ) 307 )
311 self.messages = OrderedDict() # key: uid, value: Message instance 308 self.messages = OrderedDict() # key: uid, value: Message instance
312 self.games = {} # key=game name (unicode), value=instance of quick_games.RoomGame 309 self.games = {} # key=game name (unicode), value=instance of quick_games.RoomGame
313 self.subject = subject 310 self.subject = subject
314 lt = time.localtime() 311 lt = time.localtime()
332 To be set when we are waiting for history/search 329 To be set when we are waiting for history/search
333 """ 330 """
334 # FIXME: we don't use getter/setter here because of pyjamas 331 # FIXME: we don't use getter/setter here because of pyjamas
335 # TODO: use proper getter/setter once we get rid of pyjamas 332 # TODO: use proper getter/setter once we get rid of pyjamas
336 if self._locked: 333 if self._locked:
337 log.warning(u"{wid} is already locked!".format(wid=self)) 334 log.warning("{wid} is already locked!".format(wid=self))
338 return 335 return
339 self._locked = True 336 self._locked = True
340 # messageNew signals are cached when locked 337 # messageNew signals are cached when locked
341 self._cache = OrderedDict() 338 self._cache = OrderedDict()
342 log.debug(u"{wid} is now locked".format(wid=self)) 339 log.debug("{wid} is now locked".format(wid=self))
343 340
344 def setUnlocked(self): 341 def setUnlocked(self):
345 if not self._locked: 342 if not self._locked:
346 log.debug(u"{wid} was already unlocked".format(wid=self)) 343 log.debug("{wid} was already unlocked".format(wid=self))
347 return 344 return
348 self._locked = False 345 self._locked = False
349 for uid, data in self._cache.iteritems(): 346 for uid, data in self._cache.items():
350 if uid not in self.messages: 347 if uid not in self.messages:
351 self.messageNew(*data) 348 self.messageNew(*data)
352 else: 349 else:
353 log.debug(u"discarding message already in history: {data}, ".format(data=data)) 350 log.debug("discarding message already in history: {data}, ".format(data=data))
354 del self._cache 351 del self._cache
355 log.debug(u"{wid} is now unlocked".format(wid=self)) 352 log.debug("{wid} is now unlocked".format(wid=self))
356 353
357 def postInit(self): 354 def postInit(self):
358 """Method to be called by frontend after widget is initialised 355 """Method to be called by frontend after widget is initialised
359 356
360 handle the display of history and subject 357 handle the display of history and subject
402 399
403 def resync(self): 400 def resync(self):
404 if self._resync_lock: 401 if self._resync_lock:
405 return 402 return
406 self._resync_lock = True 403 self._resync_lock = True
407 log.debug(u"resynchronising {self}".format(self=self)) 404 log.debug("resynchronising {self}".format(self=self))
408 for mess in reversed(self.messages.values()): 405 for mess in reversed(list(self.messages.values())):
409 if mess.type == C.MESS_TYPE_INFO: 406 if mess.type == C.MESS_TYPE_INFO:
410 continue 407 continue
411 last_message = mess 408 last_message = mess
412 break 409 break
413 else: 410 else:
415 self.historyPrint(callback=self._resyncComplete, profile=self.profile) 412 self.historyPrint(callback=self._resyncComplete, profile=self.profile)
416 return 413 return
417 if self.type == C.CHAT_GROUP: 414 if self.type == C.CHAT_GROUP:
418 self.occupantsClear() 415 self.occupantsClear()
419 self.host.bridge.mucOccupantsGet( 416 self.host.bridge.mucOccupantsGet(
420 unicode(self.target), self.profile, callback=self.updateOccupants, 417 str(self.target), self.profile, callback=self.updateOccupants,
421 errback=log.error) 418 errback=log.error)
422 self.historyPrint( 419 self.historyPrint(
423 size=C.HISTORY_LIMIT_NONE, 420 size=C.HISTORY_LIMIT_NONE,
424 filters={'timestamp_start': last_message.timestamp}, 421 filters={'timestamp_start': last_message.timestamp},
425 callback=self._resyncComplete, 422 callback=self._resyncComplete,
426 profile=self.profile) 423 profile=self.profile)
427 424
428 ## Widget management ## 425 ## Widget management ##
429 426
430 def __unicode__(self): 427 def __str__(self):
431 return u"Chat Widget [target: {}, type: {}, profile: {}]".format( 428 return "Chat Widget [target: {}, type: {}, profile: {}]".format(
432 self.target, self.type, self.profile 429 self.target, self.type, self.profile
433 ) 430 )
434 431
435 @staticmethod 432 @staticmethod
436 def getWidgetHash(target, profiles): 433 def getWidgetHash(target, profiles):
437 profile = list(profiles)[0] 434 profile = list(profiles)[0]
438 return profile + "\n" + unicode(target.bare) 435 return profile + "\n" + str(target.bare)
439 436
440 @staticmethod 437 @staticmethod
441 def getPrivateHash(target, profile): 438 def getPrivateHash(target, profile):
442 """Get unique hash for private conversations 439 """Get unique hash for private conversations
443 440
444 This method should be used with force_hash to get unique widget for private MUC conversations 441 This method should be used with force_hash to get unique widget for private MUC conversations
445 """ 442 """
446 return (unicode(profile), target) 443 return (str(profile), target)
447 444
448 def addTarget(self, target): 445 def addTarget(self, target):
449 super(QuickChat, self).addTarget(target) 446 super(QuickChat, self).addTarget(target)
450 if target.resource: 447 if target.resource:
451 self.current_target = ( 448 self.current_target = (
454 451
455 def recreateArgs(self, args, kwargs): 452 def recreateArgs(self, args, kwargs):
456 """copy important attribute for a new widget""" 453 """copy important attribute for a new widget"""
457 kwargs["type_"] = self.type 454 kwargs["type_"] = self.type
458 if self.type == C.CHAT_GROUP: 455 if self.type == C.CHAT_GROUP:
459 kwargs["occupants"] = {o.nick: o.data for o in self.occupants.itervalues()} 456 kwargs["occupants"] = {o.nick: o.data for o in self.occupants.values()}
460 kwargs["subject"] = self.subject 457 kwargs["subject"] = self.subject
461 try: 458 try:
462 kwargs["nick"] = self.nick 459 kwargs["nick"] = self.nick
463 except AttributeError: 460 except AttributeError:
464 pass 461 pass
490 ## occupants ## 487 ## occupants ##
491 488
492 def setOccupants(self, occupants): 489 def setOccupants(self, occupants):
493 """Set the whole list of occupants""" 490 """Set the whole list of occupants"""
494 assert len(self.occupants) == 0 491 assert len(self.occupants) == 0
495 for nick, data in occupants.iteritems(): 492 for nick, data in occupants.items():
496 # XXX: this log is disabled because it's really too verbose 493 # XXX: this log is disabled because it's really too verbose
497 # but kept commented as it may be useful for debugging 494 # but kept commented as it may be useful for debugging
498 # log.debug(u"adding occupant {nick} to {room}".format( 495 # log.debug(u"adding occupant {nick} to {room}".format(
499 # nick=nick, room=self.target)) 496 # nick=nick, room=self.target))
500 self.occupants[nick] = Occupant(self, data, self.profile) 497 self.occupants[nick] = Occupant(self, data, self.profile)
508 # FIXME: occupants with modified status are not handled 505 # FIXME: occupants with modified status are not handled
509 local_occupants = set(self.occupants) 506 local_occupants = set(self.occupants)
510 updated_occupants = set(occupants) 507 updated_occupants = set(occupants)
511 left_occupants = local_occupants - updated_occupants 508 left_occupants = local_occupants - updated_occupants
512 joined_occupants = updated_occupants - local_occupants 509 joined_occupants = updated_occupants - local_occupants
513 log.debug(u"updating occupants for {room}:\n" 510 log.debug("updating occupants for {room}:\n"
514 u"left: {left_occupants}\n" 511 "left: {left_occupants}\n"
515 u"joined: {joined_occupants}" 512 "joined: {joined_occupants}"
516 .format(room=self.target, 513 .format(room=self.target,
517 left_occupants=u", ".join(left_occupants), 514 left_occupants=", ".join(left_occupants),
518 joined_occupants=u", ".join(joined_occupants))) 515 joined_occupants=", ".join(joined_occupants)))
519 for nick in left_occupants: 516 for nick in left_occupants:
520 self.removeUser(occupants[nick]) 517 self.removeUser(occupants[nick])
521 for nick in joined_occupants: 518 for nick in joined_occupants:
522 self.addUser(occupants[nick]) 519 self.addUser(occupants[nick])
523 520
531 """Remove a user from the group list""" 528 """Remove a user from the group list"""
532 nick = occupant_data["nick"] 529 nick = occupant_data["nick"]
533 try: 530 try:
534 occupant = self.occupants.pop(nick) 531 occupant = self.occupants.pop(nick)
535 except KeyError: 532 except KeyError:
536 log.warning(u"Trying to remove an unknown occupant: {}".format(nick)) 533 log.warning("Trying to remove an unknown occupant: {}".format(nick))
537 else: 534 else:
538 return occupant 535 return occupant
539 536
540 def setUserNick(self, nick): 537 def setUserNick(self, nick):
541 """Set the nick of the user, usefull for e.g. change the color of the user""" 538 """Set the nick of the user, usefull for e.g. change the color of the user"""
600 @param profile (str): %(doc_profile)s 597 @param profile (str): %(doc_profile)s
601 """ 598 """
602 if filters is None: 599 if filters is None:
603 filters = {} 600 filters = {}
604 if size == 0: 601 if size == 0:
605 log.debug(u"Empty history requested, skipping") 602 log.debug("Empty history requested, skipping")
606 self._onHistoryPrinted() 603 self._onHistoryPrinted()
607 return 604 return
608 log_msg = _(u"now we print the history") 605 log_msg = _("now we print the history")
609 if size != C.HISTORY_LIMIT_DEFAULT: 606 if size != C.HISTORY_LIMIT_DEFAULT:
610 log_msg += _(u" ({} messages)".format(size)) 607 log_msg += _(" ({} messages)".format(size))
611 log.debug(log_msg) 608 log.debug(log_msg)
612 609
613 if self.type == C.CHAT_ONE2ONE: 610 if self.type == C.CHAT_ONE2ONE:
614 special = self.host.contact_lists[self.profile].getCache( 611 special = self.host.contact_lists[self.profile].getCache(
615 self.target, C.CONTACT_SPECIAL, create_if_not_found=True 612 self.target, C.CONTACT_SPECIAL, create_if_not_found=True
659 self._onHistoryPrinted() 656 self._onHistoryPrinted()
660 if callback is not None: 657 if callback is not None:
661 callback() 658 callback()
662 659
663 def _historyGetEb(err): 660 def _historyGetEb(err):
664 log.error(_(u"Can't get history: {}").format(err)) 661 log.error(_("Can't get history: {}").format(err))
665 self._onHistoryPrinted() 662 self._onHistoryPrinted()
666 if callback is not None: 663 if callback is not None:
667 callback() 664 callback()
668 665
669 self.host.bridge.historyGet( 666 self.host.bridge.historyGet(
670 unicode(self.host.profiles[profile].whoami.bare), 667 str(self.host.profiles[profile].whoami.bare),
671 unicode(target), 668 str(target),
672 size, 669 size,
673 True, 670 True,
674 {k: unicode(v) for k,v in filters.iteritems()}, 671 {k: str(v) for k,v in filters.items()},
675 profile, 672 profile,
676 callback=_historyGetCb, 673 callback=_historyGetCb,
677 errback=_historyGetEb, 674 errback=_historyGetEb,
678 ) 675 )
679 676
681 if session_data: 678 if session_data:
682 session_data = data_format.deserialise(session_data) 679 session_data = data_format.deserialise(session_data)
683 self.messageEncryptionStarted(session_data) 680 self.messageEncryptionStarted(session_data)
684 681
685 def messageEncryptionGetEb(self, failure_): 682 def messageEncryptionGetEb(self, failure_):
686 log.error(_(u"Can't get encryption state: {reason}").format(reason=failure_)) 683 log.error(_("Can't get encryption state: {reason}").format(reason=failure_))
687 684
688 def getEncryptionState(self): 685 def getEncryptionState(self):
689 """Retrieve encryption state with current target. 686 """Retrieve encryption state with current target.
690 687
691 Once state is retrieved, default messageEncryptionStarted will be called if 688 Once state is retrieved, default messageEncryptionStarted will be called if
692 suitable 689 suitable
693 """ 690 """
694 if self.type == C.CHAT_GROUP: 691 if self.type == C.CHAT_GROUP:
695 return 692 return
696 self.host.bridge.messageEncryptionGet(unicode(self.target.bare), self.profile, 693 self.host.bridge.messageEncryptionGet(str(self.target.bare), self.profile,
697 callback=self.messageEncryptionGetCb, 694 callback=self.messageEncryptionGetCb,
698 errback=self.messageEncryptionGetEb) 695 errback=self.messageEncryptionGetEb)
699 696
700 697
701 def messageNew(self, uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, 698 def messageNew(self, uid, timestamp, from_jid, to_jid, msg, subject, type_, extra,
713 profile, 710 profile,
714 ) 711 )
715 return 712 return
716 713
717 if not msg and not subject and type_ != C.MESS_TYPE_INFO: 714 if not msg and not subject and type_ != C.MESS_TYPE_INFO:
718 log.warning(u"Received an empty message for uid {}".format(uid)) 715 log.warning("Received an empty message for uid {}".format(uid))
719 return 716 return
720 717
721 if self.type == C.CHAT_GROUP: 718 if self.type == C.CHAT_GROUP:
722 if to_jid.resource and type_ != C.MESS_TYPE_GROUPCHAT: 719 if to_jid.resource and type_ != C.MESS_TYPE_GROUPCHAT:
723 # we have a private message, we forward it to a private conversation 720 # we have a private message, we forward it to a private conversation
732 info_type = extra["info_type"] 729 info_type = extra["info_type"]
733 except KeyError: 730 except KeyError:
734 pass 731 pass
735 else: 732 else:
736 user_data = { 733 user_data = {
737 k[5:]: v for k, v in extra.iteritems() if k.startswith("user_") 734 k[5:]: v for k, v in extra.items() if k.startswith("user_")
738 } 735 }
739 if info_type == ROOM_USER_JOINED: 736 if info_type == ROOM_USER_JOINED:
740 self.addUser(user_data) 737 self.addUser(user_data)
741 elif info_type == ROOM_USER_LEFT: 738 elif info_type == ROOM_USER_LEFT:
742 self.removeUser(user_data) 739 self.removeUser(user_data)
745 self, uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile 742 self, uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile
746 ) 743 )
747 self.messages[uid] = message 744 self.messages[uid] = message
748 745
749 if "received_timestamp" in extra: 746 if "received_timestamp" in extra:
750 log.warning(u"Delayed message received after history, this should not happen") 747 log.warning("Delayed message received after history, this should not happen")
751 self.createMessage(message) 748 self.createMessage(message)
752 749
753 def messageEncryptionStarted(self, session_data): 750 def messageEncryptionStarted(self, session_data):
754 self.encrypted = True 751 self.encrypted = True
755 log.debug(_(u"message encryption started with {target} using {encryption}").format( 752 log.debug(_("message encryption started with {target} using {encryption}").format(
756 target=self.target, encryption=session_data[u'name'])) 753 target=self.target, encryption=session_data['name']))
757 754
758 def messageEncryptionStopped(self, session_data): 755 def messageEncryptionStopped(self, session_data):
759 self.encrypted = False 756 self.encrypted = False
760 log.debug(_(u"message encryption stopped with {target} (was using {encryption})") 757 log.debug(_("message encryption stopped with {target} (was using {encryption})")
761 .format(target=self.target, encryption=session_data[u'name'])) 758 .format(target=self.target, encryption=session_data['name']))
762 759
763 def createMessage(self, message, append=False): 760 def createMessage(self, message, append=False):
764 """Must be implemented by frontend to create and show a new message widget 761 """Must be implemented by frontend to create and show a new message widget
765 762
766 This is only called on messageNew, not on history. 763 This is only called on messageNew, not on history.
808 count = wid.reentered_count 805 count = wid.reentered_count
809 except AttributeError: 806 except AttributeError:
810 count = wid.reentered_count = 1 807 count = wid.reentered_count = 1
811 nick = wid.mess_data.nick 808 nick = wid.mess_data.nick
812 if message.info_type == ROOM_USER_LEFT: 809 if message.info_type == ROOM_USER_LEFT:
813 wid.message = _(u"<= {nick} has left the room ({count})").format( 810 wid.message = _("<= {nick} has left the room ({count})").format(
814 nick=nick, count=count 811 nick=nick, count=count
815 ) 812 )
816 else: 813 else:
817 wid.message = _( 814 wid.message = _(
818 u"<=> {nick} re-entered the room ({count})" 815 "<=> {nick} re-entered the room ({count})"
819 ).format(nick=nick, count=count) 816 ).format(nick=nick, count=count)
820 wid.reentered_count += 1 817 wid.reentered_count += 1
821 return True 818 return True
822 return False 819 return False
823 820
843 """Change the subject of the room 840 """Change the subject of the room
844 841
845 This change the subject on the room itself (i.e. via XMPP), 842 This change the subject on the room itself (i.e. via XMPP),
846 while setSubject change the subject of this widget 843 while setSubject change the subject of this widget
847 """ 844 """
848 self.host.bridge.mucSubject(unicode(self.target), new_subject, self.profile) 845 self.host.bridge.mucSubject(str(self.target), new_subject, self.profile)
849 846
850 def addGamePanel(self, widget): 847 def addGamePanel(self, widget):
851 """Insert a game panel to this Chat dialog. 848 """Insert a game panel to this Chat dialog.
852 849
853 @param widget (Widget): the game panel 850 @param widget (Widget): the game panel
877 nick = from_jid.resource 874 nick = from_jid.resource
878 try: 875 try:
879 self.occupants[nick].state = state 876 self.occupants[nick].state = state
880 except KeyError: 877 except KeyError:
881 log.warning( 878 log.warning(
882 u"{nick} not found in {room}, ignoring new chat state".format( 879 "{nick} not found in {room}, ignoring new chat state".format(
883 nick=nick, room=self.target.bare 880 nick=nick, room=self.target.bare
884 ) 881 )
885 ) 882 )
886 883
887 def onMessageState(self, uid, status, profile): 884 def onMessageState(self, uid, status, profile):
900 except KeyError: 897 except KeyError:
901 # can happen for a message in history where the 898 # can happen for a message in history where the
902 # entity is not here anymore 899 # entity is not here anymore
903 pass 900 pass
904 901
905 for m in self.messages.values(): 902 for m in list(self.messages.values()):
906 if m.nick == entity.resource: 903 if m.nick == entity.resource:
907 for w in m.widgets: 904 for w in m.widgets:
908 w.update({"avatar": filename}) 905 w.update({"avatar": filename})
909 else: 906 else:
910 if ( 907 if (
911 entity.bare == self.target.bare 908 entity.bare == self.target.bare
912 or entity.bare == self.host.profiles[profile].whoami.bare 909 or entity.bare == self.host.profiles[profile].whoami.bare
913 ): 910 ):
914 log.info(u"avatar updated for {}".format(entity)) 911 log.info("avatar updated for {}".format(entity))
915 for m in self.messages.values(): 912 for m in list(self.messages.values()):
916 if m.from_jid.bare == entity.bare: 913 if m.from_jid.bare == entity.bare:
917 for w in m.widgets: 914 for w in m.widgets:
918 w.update({"avatar": filename}) 915 w.update({"avatar": filename})
919 916
920 917