from gettext import gettext as _ import logging log = logging.getLogger(__name__) import curses from . import ChatTab, MucTab, Tab import common import fixes import windows import xhtml from common import safeJID from config import config from decorators import refresh_wrapper from logger import logger from theming import get_theme, dump_tuple class PrivateTab(ChatTab): """ The tab containg a private conversation (someone from a MUC) """ message_type = 'chat' plugin_commands = {} additional_informations = {} plugin_keys = {} def __init__(self, name, nick): ChatTab.__init__(self, name) self.own_nick = nick self.name = name self.text_win = windows.TextWin() self._text_buffer.add_window(self.text_win) self.info_header = windows.PrivateInfoWin() self.input = windows.MessageInput() self.check_attention() # keys self.key_func['^I'] = self.completion # commands self.register_command('info', self.command_info, desc=_('Display some information about the user in the MUC: its/his/her role, affiliation, status and status message.'), shortdesc=_('Info about the user.')) self.register_command('unquery', self.command_unquery, shortdesc=_('Close the tab.')) self.register_command('close', self.command_unquery, shortdesc=_('Close the tab.')) self.register_command('version', self.command_version, desc=_('Get the software version of the current interlocutor (usually its XMPP client and Operating System).'), shortdesc=_('Get the software version of a jid.')) self.resize() self.parent_muc = self.core.get_tab_by_name(safeJID(name).bare, MucTab) self.on = True self.update_commands() self.update_keys() @property def general_jid(self): return self.get_name() @property def nick(self): return self.get_nick() @staticmethod def add_information_element(plugin_name, callback): """ Lets a plugin add its own information to the PrivateInfoWin """ PrivateTab.additional_informations[plugin_name] = callback @staticmethod def remove_information_element(plugin_name): del PrivateTab.additional_informations[plugin_name] def load_logs(self, log_nb): logs = logger.get_logs(safeJID(self.get_name()).full.replace('/', '\\'), log_nb) def log_message(self, txt, nickname, time=None, typ=1): """ Log the messages in the archives. """ if not logger.log_message(self.name, nickname, txt, date=time, typ=typ): self.core.information(_('Unable to write in the log file'), 'Error') def on_close(self): self.parent_muc.privates.remove(self) def completion(self): """ Called when Tab is pressed, complete the nickname in the input """ if self.complete_commands(self.input): return # If we are not completing a command or a command's argument, complete a nick compare_users = lambda x: x.last_talked word_list = [user.nick for user in sorted(self.parent_muc.users, key=compare_users, reverse=True)\ if user.nick != self.own_nick] after = config.get('after_completion', ',')+" " input_pos = self.input.pos if ' ' not in self.input.get_text()[:input_pos] or (self.input.last_completion and\ self.input.get_text()[:input_pos] == self.input.last_completion + after): add_after = after else: add_after = '' self.input.auto_completion(word_list, add_after, quotify=False) empty_after = self.input.get_text() == '' or (self.input.get_text().startswith('/') and not self.input.get_text().startswith('//')) self.send_composing_chat_state(empty_after) def command_say(self, line, attention=False, correct=False): if not self.on: return msg = self.core.xmpp.make_message(self.get_name()) msg['type'] = 'chat' msg['body'] = line # trigger the event BEFORE looking for colors. # This lets a plugin insert \x19xxx} colors, that will # be converted in xhtml. self.core.events.trigger('private_say', msg, self) if not msg['body']: self.cancel_paused_delay() self.text_win.refresh() self.input.refresh() return user = self.parent_muc.get_user_by_name(self.own_nick) replaced = False if correct or msg['replace']['id']: msg['replace']['id'] = self.last_sent_message['id'] if config.get_by_tabname('group_corrections', 'true', self.get_name()).lower() != 'false': try: self.modify_message(msg['body'], self.last_sent_message['id'], msg['id'], user=user, jid=self.core.xmpp.boundjid, nickname=self.own_nick) replaced = True except: log.error('Unable to correct a message', exc_info=True) else: del msg['replace'] if msg['body'].find('\x19') != -1: msg.enable('html') msg['html']['body'] = xhtml.poezio_colors_to_html(msg['body']) msg['body'] = xhtml.clean_text(msg['body']) if config.get_by_tabname('send_chat_states', 'true', self.general_jid, True) == 'true' and self.remote_wants_chatstates is not False: needed = 'inactive' if self.inactive else 'active' msg['chat_state'] = needed if attention and self.remote_supports_attention: msg['attention'] = True self.core.events.trigger('private_say_after', msg, self) if not msg['body']: self.cancel_paused_delay() self.text_win.refresh() self.input.refresh() return if not replaced: self.add_message(msg['body'], nickname=self.core.own_nick or self.own_nick, forced_user=user, nick_color=get_theme().COLOR_OWN_NICK, identifier=msg['id'], jid=self.core.xmpp.boundjid, typ=1) self.last_sent_message = msg msg.send() self.cancel_paused_delay() self.text_win.refresh() self.input.refresh() def command_attention(self, message=''): if message is not '': self.command_say(message, attention=True) else: msg = self.core.xmpp.make_message(self.get_name()) msg['type'] = 'chat' msg['attention'] = True msg.send() def check_attention(self): self.core.xmpp.plugin['xep_0030'].get_info(jid=self.get_name(), block=False, timeout=5, callback=self.on_attention_checked) def on_attention_checked(self, iq): if 'urn:xmpp:attention:0' in iq['disco_info'].get_features(): self.core.information('Attention is supported', 'Info') self.remote_supports_attention = True self.commands['attention'] = (self.command_attention, _('Usage: /attention [message]\nAttention: Require the attention of the contact. Can also send a message along with the attention.'), None) else: self.remote_supports_attention = False def command_unquery(self, arg): """ /unquery """ self.core.close_tab() def command_version(self, arg): """ /version """ def callback(res): if not res: return self.core.information('Could not get the software version from %s' % (jid,), 'Warning') version = '%s is running %s version %s on %s' % (jid, res.get('name') or _('an unknown software'), res.get('version') or _('unknown'), res.get('os') or _('an unknown platform')) self.core.information(version, 'Info') if arg: return self.core.command_version(arg) jid = safeJID(self.name) fixes.get_version(self.core.xmpp, jid, callback=callback) def command_info(self, arg): """ /info """ if arg: self.parent_muc.command_info(arg) else: user = safeJID(self.name).resource self.parent_muc.command_info(user) def resize(self): if self.core.information_win_size >= self.height-3 or not self.visible: return self.need_resize = False self.text_win.resize(self.height-2-self.core.information_win_size - Tab.tab_win_height(), self.width, 0, 0) self.text_win.rebuild_everything(self._text_buffer) self.info_header.resize(1, self.width, self.height-2-self.core.information_win_size - Tab.tab_win_height(), 0) self.input.resize(1, self.width, self.height-1, 0) def refresh(self): if self.need_resize: self.resize() log.debug(' TAB Refresh: %s',self.__class__.__name__) self.text_win.refresh() self.info_header.refresh(self.name, self.text_win, self.chatstate, PrivateTab.additional_informations) self.info_win.refresh() self.refresh_tab_win() self.input.refresh() def refresh_info_header(self): self.info_header.refresh(self.name, self.text_win, self.chatstate, PrivateTab.additional_informations) self.input.refresh() def get_name(self): return self.name def get_nick(self): return safeJID(self.name).resource def on_input(self, key, raw): if not raw and key in self.key_func: self.key_func[key]() return False self.input.do_command(key, raw=raw) if not self.on: return False empty_after = self.input.get_text() == '' or (self.input.get_text().startswith('/') and not self.input.get_text().startswith('//')) tab = self.core.get_tab_by_name(safeJID(self.name).bare, MucTab) if tab and tab.joined: self.send_composing_chat_state(empty_after) return False def on_lose_focus(self): self.state = 'normal' self.text_win.remove_line_separator() self.text_win.add_line_separator(self._text_buffer) tab = self.core.get_tab_by_name(safeJID(self.name).bare, MucTab) if tab and tab.joined and config.get_by_tabname( 'send_chat_states', 'true', self.general_jid, True) == 'true'\ and not self.input.get_text() and self.on: self.send_chat_state('inactive') self.check_scrolled() def on_gain_focus(self): self.state = 'current' curses.curs_set(1) tab = self.core.get_tab_by_name(safeJID(self.name).bare, MucTab) if tab and tab.joined and config.get_by_tabname( 'send_chat_states', 'true', self.general_jid, True) == 'true'\ and not self.input.get_text() and self.on: self.send_chat_state('active') def on_info_win_size_changed(self): if self.core.information_win_size >= self.height-3: return self.text_win.resize(self.height-2-self.core.information_win_size - Tab.tab_win_height(), self.width, 0, 0) self.info_header.resize(1, self.width, self.height-2-self.core.information_win_size - Tab.tab_win_height(), 0) def get_text_window(self): return self.text_win def rename_user(self, old_nick, new_nick): """ The user changed her nick in the corresponding muc: update the tab’s name and display a message. """ self.add_message('\x193}%(old)s\x19%(info_col)s} is now known as \x193}%(new)s' % {'old':old_nick, 'new':new_nick, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) new_jid = safeJID(self.name).bare+'/'+new_nick self.name = new_jid @refresh_wrapper.conditional def user_left(self, status_message, from_nick): """ The user left the associated MUC """ self.deactivate() if not status_message: self.add_message(_('\x191}%(spec)s \x193}%(nick)s\x19%(info_col)s} has left the room') % {'nick':from_nick, 'spec':get_theme().CHAR_QUIT, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) else: self.add_message(_('\x191}%(spec)s \x193}%(nick)s\x19%(info_col)s} has left the room (%(status)s)"') % {'nick':from_nick, 'spec':get_theme().CHAR_QUIT, 'status': status_message, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) return self.core.current_tab() is self @refresh_wrapper.conditional def user_rejoined(self, nick): """ The user (or at least someone with the same nick) came back in the MUC """ self.activate() tab = self.core.get_tab_by_name(safeJID(self.name).bare, MucTab) color = 3 if tab and config.get_by_tabname('display_user_color_in_join_part', '', self.general_jid, True): user = tab.get_user_by_name(nick) if user: color = dump_tuple(user.color) self.add_message('\x194}%(spec)s \x19%(color)s}%(nick)s\x19%(info_col)s} joined the room' % {'nick':nick, 'color': color, 'spec':get_theme().CHAR_JOIN, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) return self.core.current_tab() is self def activate(self, reason=None): self.on = True if reason: self.add_message(txt=reason, typ=2) def deactivate(self, reason=None): self.on = False if reason: self.add_message(txt=reason, typ=2) def matching_names(self): return [(3, safeJID(self.get_name()).resource), (4, self.get_name())]