class ChatTab(Tab): """ A tab containing a chat of any type. Just use this class instead of Tab if the tab needs a recent-words completion Also, ^M is already bound to on_enter And also, add the /say command """ plugin_commands = {} plugin_keys = {} def __init__(self, jid=''): Tab.__init__(self) self.name = jid self.text_win = None self._text_buffer = TextBuffer() self.chatstate = None # can be "active", "composing", "paused", "gone", "inactive" # We keep a reference of the event that will set our chatstate to "paused", so that # we can delete it or change it if we need to self.timed_event_paused = None # Keeps the last sent message to complete it easily in completion_correct, and to replace it. self.last_sent_message = None self.key_func['M-v'] = self.move_separator self.key_func['M-h'] = self.scroll_separator self.key_func['M-/'] = self.last_words_completion self.key_func['^M'] = self.on_enter self.register_command('say', self.command_say, usage=_('<message>'), shortdesc=_('Send the message.')) self.register_command('xhtml', self.command_xhtml, usage=_('<custom xhtml>'), shortdesc=_('Send custom XHTML.')) self.register_command('clear', self.command_clear, shortdesc=_('Clear the current buffer.')) self.register_command('correct', self.command_correct, desc=_('Fix the last message with whatever you want.'), shortdesc=_('Correct the last message.'), completion=self.completion_correct) self.chat_state = None self.update_commands() self.update_keys() # Get the logs log_nb = config.get('load_log') logs = self.load_logs(log_nb) if logs: for message in logs: self._text_buffer.add_message(**message) @property def is_muc(self): return False def load_logs(self, log_nb): logs = logger.get_logs(safeJID(self.name).bare, log_nb) return logs def log_message(self, txt, nickname, time=None, typ=1): """ Log the messages in the archives. """ name = safeJID(self.name).bare if not logger.log_message(name, nickname, txt, date=time, typ=typ): self.core.information(_('Unable to write in the log file'), 'Error') def add_message(self, txt, time=None, nickname=None, forced_user=None, nick_color=None, identifier=None, jid=None, history=None, typ=1, highlight=False): self.log_message(txt, nickname, time=time, typ=typ) self._text_buffer.add_message(txt, time=time, nickname=nickname, highlight=highlight, nick_color=nick_color, history=history, user=forced_user, identifier=identifier, jid=jid) def modify_message(self, txt, old_id, new_id, user=None, jid=None, nickname=None): self.log_message(txt, nickname, typ=1) message = self._text_buffer.modify_message(txt, old_id, new_id, time=time, user=user, jid=jid) if message: self.text_win.modify_message(old_id, message) self.core.refresh_window() return True return False def last_words_completion(self): """ Complete the input with words recently said """ # build the list of the recent words char_we_dont_want = string.punctuation+' ’„“”…«»' words = list() for msg in self._text_buffer.messages[:-40:-1]: if not msg: continue txt = xhtml.clean_text(msg.txt) for char in char_we_dont_want: txt = txt.replace(char, ' ') for word in txt.split(): if len(word) >= 4 and word not in words: words.append(word) words.extend([word for word in config.get('words').split(':') if word]) self.input.auto_completion(words, ' ', quotify=False) def on_enter(self): txt = self.input.key_enter() if txt: if not self.execute_command(txt): if txt.startswith('//'): txt = txt[1:] self.command_say(xhtml.convert_simple_to_full_colors(txt)) self.cancel_paused_delay() @command_args_parser.raw def command_xhtml(self, xhtml): """" /xhtml <custom xhtml> """ message = self.generate_xhtml_message(xhtml) if message: message.send() def generate_xhtml_message(self, arg): if not arg: return try: body = xhtml.clean_text(xhtml.xhtml_to_poezio_colors(arg)) ET.fromstring(arg) except: self.core.information('Could not send custom xhtml', 'Error') log.error('/xhtml: Unable to send custom xhtml', exc_info=True) return msg = self.core.xmpp.make_message(self.get_dest_jid()) msg['body'] = body msg.enable('html') msg['html']['body'] = arg return msg def get_dest_jid(self): return self.name @refresh_wrapper.always def command_clear(self, ignored): """ /clear """ self._text_buffer.messages = [] self.text_win.rebuild_everything(self._text_buffer) def send_chat_state(self, state, always_send=False): """ Send an empty chatstate message """ if not self.is_muc or self.joined: if state in ('active', 'inactive', 'gone') and self.inactive and not always_send: return if (config.get_by_tabname('send_chat_states', self.general_jid) and self.remote_wants_chatstates is not False): msg = self.core.xmpp.make_message(self.get_dest_jid()) msg['type'] = self.message_type msg['chat_state'] = state self.chat_state = state msg.send() return True def send_composing_chat_state(self, empty_after): """ Send the "active" or "composing" chatstate, depending on the the current status of the input """ name = self.general_jid if (config.get_by_tabname('send_chat_states', name) and self.remote_wants_chatstates): needed = 'inactive' if self.inactive else 'active' self.cancel_paused_delay() if not empty_after: if self.chat_state != "composing": self.send_chat_state("composing") self.set_paused_delay(True) elif empty_after and self.chat_state != needed: self.send_chat_state(needed, True) def set_paused_delay(self, composing): """ we create a timed event that will put us to paused in a few seconds """ if not config.get_by_tabname('send_chat_states', self.general_jid): return # First, cancel the delay if it already exists, before rescheduling # it at a new date self.cancel_paused_delay() new_event = timed_events.DelayedEvent(4, self.send_chat_state, 'paused') self.core.add_timed_event(new_event) self.timed_event_paused = new_event def cancel_paused_delay(self): """ Remove that event from the list and set it to None. Called for example when the input is emptied, or when the message is sent """ if self.timed_event_paused is not None: self.core.remove_timed_event(self.timed_event_paused) self.timed_event_paused = None @command_args_parser.raw def command_correct(self, line): """ /correct <fixed message> """ if not line: self.core.command_help('correct') return if not self.last_sent_message: self.core.information(_('There is no message to correct.')) return self.command_say(line, correct=True) def completion_correct(self, the_input): if self.last_sent_message and the_input.get_argument_position() == 1: return the_input.auto_completion([self.last_sent_message['body']], '', quotify=False) @property def inactive(self): """Whether we should send inactive or active as a chatstate""" return self.core.status.show in ('xa', 'away') or\ (hasattr(self, 'directed_presence') and not self.directed_presence) def move_separator(self): self.text_win.remove_line_separator() self.text_win.add_line_separator(self._text_buffer) self.text_win.refresh() self.input.refresh() def get_conversation_messages(self): return self._text_buffer.messages def check_scrolled(self): if self.text_win.pos != 0: self.state = 'scrolled' @command_args_parser.raw def command_say(self, line, correct=False): pass def on_line_up(self): return self.text_win.scroll_up(1) def on_line_down(self): return self.text_win.scroll_down(1) def on_scroll_up(self): return self.text_win.scroll_up(self.text_win.height-1) def on_scroll_down(self): return self.text_win.scroll_down(self.text_win.height-1) def on_half_scroll_up(self): return self.text_win.scroll_up((self.text_win.height-1) // 2) def on_half_scroll_down(self): return self.text_win.scroll_down((self.text_win.height-1) // 2) @refresh_wrapper.always def scroll_separator(self): self.text_win.scroll_to_separator()
class ChatTab(Tab): """ A tab containing a chat of any type. Just use this class instead of Tab if the tab needs a recent-words completion Also, ^M is already bound to on_enter And also, add the /say command """ plugin_commands = {} plugin_keys = {} def __init__(self, jid=''): Tab.__init__(self) self.name = jid self.text_win = None self._text_buffer = TextBuffer() self.chatstate = None # can be "active", "composing", "paused", "gone", "inactive" # We keep a reference of the event that will set our chatstate to "paused", so that # we can delete it or change it if we need to self.timed_event_paused = None # Keeps the last sent message to complete it easily in completion_correct, and to replace it. self.last_sent_message = None self.key_func['M-v'] = self.move_separator self.key_func['M-h'] = self.scroll_separator self.key_func['M-/'] = self.last_words_completion self.key_func['^M'] = self.on_enter self.register_command('say', self.command_say, usage='<message>', shortdesc='Send the message.') self.register_command('xhtml', self.command_xhtml, usage='<custom xhtml>', shortdesc='Send custom XHTML.') self.register_command('clear', self.command_clear, shortdesc='Clear the current buffer.') self.register_command( 'correct', self.command_correct, desc='Fix the last message with whatever you want.', shortdesc='Correct the last message.', completion=self.completion_correct) self.chat_state = None self.update_commands() self.update_keys() # Get the logs log_nb = config.get('load_log') logs = self.load_logs(log_nb) if logs: for message in logs: self._text_buffer.add_message(**message) @property def is_muc(self): return False def load_logs(self, log_nb): logs = logger.get_logs(safeJID(self.name).bare, log_nb) return logs def log_message(self, txt, nickname, time=None, typ=1): """ Log the messages in the archives. """ name = safeJID(self.name).bare if not logger.log_message(name, nickname, txt, date=time, typ=typ): self.core.information('Unable to write in the log file', 'Error') def add_message(self, txt, time=None, nickname=None, forced_user=None, nick_color=None, identifier=None, jid=None, history=None, typ=1, highlight=False): self.log_message(txt, nickname, time=time, typ=typ) self._text_buffer.add_message(txt, time=time, nickname=nickname, highlight=highlight, nick_color=nick_color, history=history, user=forced_user, identifier=identifier, jid=jid) def modify_message(self, txt, old_id, new_id, user=None, jid=None, nickname=None): self.log_message(txt, nickname, typ=1) message = self._text_buffer.modify_message(txt, old_id, new_id, time=time, user=user, jid=jid) if message: self.text_win.modify_message(old_id, message) self.core.refresh_window() return True return False def last_words_completion(self): """ Complete the input with words recently said """ # build the list of the recent words char_we_dont_want = string.punctuation + ' ’„“”…«»' words = list() for msg in self._text_buffer.messages[:-40:-1]: if not msg: continue txt = xhtml.clean_text(msg.txt) for char in char_we_dont_want: txt = txt.replace(char, ' ') for word in txt.split(): if len(word) >= 4 and word not in words: words.append(word) words.extend([word for word in config.get('words').split(':') if word]) self.input.auto_completion(words, ' ', quotify=False) def on_enter(self): txt = self.input.key_enter() if txt: if not self.execute_command(txt): if txt.startswith('//'): txt = txt[1:] self.command_say(xhtml.convert_simple_to_full_colors(txt)) self.cancel_paused_delay() @command_args_parser.raw def command_xhtml(self, xhtml): """" /xhtml <custom xhtml> """ message = self.generate_xhtml_message(xhtml) if message: message.send() def generate_xhtml_message(self, arg): if not arg: return try: body = xhtml.clean_text(xhtml.xhtml_to_poezio_colors(arg)) ET.fromstring(arg) except: self.core.information('Could not send custom xhtml', 'Error') log.error('/xhtml: Unable to send custom xhtml', exc_info=True) return msg = self.core.xmpp.make_message(self.get_dest_jid()) msg['body'] = body msg.enable('html') msg['html']['body'] = arg return msg def get_dest_jid(self): return self.name @refresh_wrapper.always def command_clear(self, ignored): """ /clear """ self._text_buffer.messages = [] self.text_win.rebuild_everything(self._text_buffer) def send_chat_state(self, state, always_send=False): """ Send an empty chatstate message """ if not self.is_muc or self.joined: if state in ('active', 'inactive', 'gone') and self.inactive and not always_send: return if (config.get_by_tabname('send_chat_states', self.general_jid) and self.remote_wants_chatstates is not False): msg = self.core.xmpp.make_message(self.get_dest_jid()) msg['type'] = self.message_type msg['chat_state'] = state self.chat_state = state msg.send() return True def send_composing_chat_state(self, empty_after): """ Send the "active" or "composing" chatstate, depending on the the current status of the input """ name = self.general_jid if (config.get_by_tabname('send_chat_states', name) and self.remote_wants_chatstates): needed = 'inactive' if self.inactive else 'active' self.cancel_paused_delay() if not empty_after: if self.chat_state != "composing": self.send_chat_state("composing") self.set_paused_delay(True) elif empty_after and self.chat_state != needed: self.send_chat_state(needed, True) def set_paused_delay(self, composing): """ we create a timed event that will put us to paused in a few seconds """ if not config.get_by_tabname('send_chat_states', self.general_jid): return # First, cancel the delay if it already exists, before rescheduling # it at a new date self.cancel_paused_delay() new_event = timed_events.DelayedEvent(4, self.send_chat_state, 'paused') self.core.add_timed_event(new_event) self.timed_event_paused = new_event def cancel_paused_delay(self): """ Remove that event from the list and set it to None. Called for example when the input is emptied, or when the message is sent """ if self.timed_event_paused is not None: self.core.remove_timed_event(self.timed_event_paused) self.timed_event_paused = None @command_args_parser.raw def command_correct(self, line): """ /correct <fixed message> """ if not line: self.core.command_help('correct') return if not self.last_sent_message: self.core.information('There is no message to correct.') return self.command_say(line, correct=True) def completion_correct(self, the_input): if self.last_sent_message and the_input.get_argument_position() == 1: return the_input.auto_completion([self.last_sent_message['body']], '', quotify=False) @property def inactive(self): """Whether we should send inactive or active as a chatstate""" return self.core.status.show in ('xa', 'away') or\ (hasattr(self, 'directed_presence') and not self.directed_presence) def move_separator(self): self.text_win.remove_line_separator() self.text_win.add_line_separator(self._text_buffer) self.text_win.refresh() self.input.refresh() def get_conversation_messages(self): return self._text_buffer.messages def check_scrolled(self): if self.text_win.pos != 0: self.state = 'scrolled' @command_args_parser.raw def command_say(self, line, correct=False): pass def on_line_up(self): return self.text_win.scroll_up(1) def on_line_down(self): return self.text_win.scroll_down(1) def on_scroll_up(self): return self.text_win.scroll_up(self.text_win.height - 1) def on_scroll_down(self): return self.text_win.scroll_down(self.text_win.height - 1) def on_half_scroll_up(self): return self.text_win.scroll_up((self.text_win.height - 1) // 2) def on_half_scroll_down(self): return self.text_win.scroll_down((self.text_win.height - 1) // 2) @refresh_wrapper.always def scroll_separator(self): self.text_win.scroll_to_separator()