def command_say(self, line, attention=False, correct=False): msg = self.core.xmpp.make_message(self.get_dest_jid()) msg['type'] = 'chat' msg['body'] = line if not self.nick_sent: msg['nick'] = self.core.own_nick self.nick_sent = True # trigger the event BEFORE looking for colors. # and before displaying the message in the window # This lets a plugin insert \x19xxx} colors, that will # be converted in xhtml. self.core.events.trigger('conversation_say', msg, self) if not msg['body']: self.cancel_paused_delay() self.text_win.refresh() self.input.refresh() return replaced = False if correct or msg['replace']['id']: msg['replace']['id'] = self.last_sent_message['id'] if config.get_by_tabname('group_corrections', self.name): try: self.modify_message(msg['body'], self.last_sent_message['id'], msg['id'], jid=self.core.xmpp.boundjid, nickname=self.core.own_nick) replaced = True except CorrectionError: 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', self.general_jid) 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('conversation_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, nick_color=get_theme().COLOR_OWN_NICK, identifier=msg['id'], jid=self.core.xmpp.boundjid, typ=1) self.last_sent_message = msg if self.remote_supports_receipts: msg._add_receipt = True msg.send() self.cancel_paused_delay() self.text_win.refresh() self.input.refresh()
def command_say(self, line, attention=False, correct=False): msg = self.core.xmpp.make_message(self.get_dest_jid()) msg['type'] = 'chat' msg['body'] = line if not self.nick_sent: msg['nick'] = self.core.own_nick self.nick_sent = True # trigger the event BEFORE looking for colors. # and before displaying the message in the window # This lets a plugin insert \x19xxx} colors, that will # be converted in xhtml. self.core.events.trigger('conversation_say', msg, self) if not msg['body']: self.cancel_paused_delay() self.text_win.refresh() self.input.refresh() return replaced = False if correct or msg['replace']['id']: msg['replace']['id'] = self.last_sent_message['id'] if config.get_by_tabname('group_corrections', self.name): try: self.modify_message(msg['body'], self.last_sent_message['id'], msg['id'], jid=self.core.xmpp.boundjid, nickname=self.core.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', self.general_jid) 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('conversation_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, nick_color=get_theme().COLOR_OWN_NICK, identifier=msg['id'], jid=self.core.xmpp.boundjid, typ=1) self.last_sent_message = msg if self.remote_supports_receipts: msg._add_receipt = True msg.send() self.cancel_paused_delay() self.text_win.refresh() self.input.refresh()
def get_local(self): """Add the locally stored bookmarks to the list.""" rooms = config.get('rooms') if not rooms: return rooms = rooms.split(':') for room in rooms: try: jid = JID(room) except InvalidJID: continue if jid.bare == '': continue if jid.resource != '': nick = jid.resource else: nick = None passwd = config.get_by_tabname( 'password', jid.bare, fallback=False) or None b = Bookmark(jid.bare, jid.user, autojoin=True, nick=nick, password=passwd, method='local') self.append(b)
def _check_and_create_log_dir(self, room, open_fd=True): """ Check that the directory where we want to log the messages exists. if not, create it """ if not config.get_by_tabname('use_log', room): return try: makedirs(log_dir) except OSError as e: if e.errno != 17: # file exists log.error('Unable to create the log dir', exc_info=True) except: log.error('Unable to create the log dir', exc_info=True) return if not open_fd: return try: fd = open(os.path.join(log_dir, room), 'a') self._fds[room] = fd return fd except IOError: log.error('Unable to open the log file (%s)', os.path.join(log_dir, room), exc_info=True)
def _check_and_create_log_dir(self, room, open_fd=True): """ Check that the directory where we want to log the messages exists. if not, create it """ if not config.get_by_tabname('use_log', room): return try: makedirs(log_dir) except OSError as e: if e.errno != 17: # file exists log.error('Unable to create the log dir', exc_info=True) except: log.error('Unable to create the log dir', exc_info=True) return if not open_fd: return try: fd = open(os.path.join(log_dir, room), 'a', encoding='utf-8') self._fds[room] = fd return fd except IOError: log.error('Unable to open the log file (%s)', os.path.join(log_dir, room), exc_info=True)
def log_roster_change(self, jid, message): """ Log a roster change """ if not config.get_by_tabname('use_log', jid): return True self._check_and_create_log_dir('', open_fd=False) if not self._roster_logfile: try: self._roster_logfile = open(os.path.join( log_dir, 'roster.log'), 'a', encoding='utf-8') except IOError: log.error('Unable to create the log file (%s)', os.path.join(log_dir, 'roster.log'), exc_info=True) return False try: str_time = common.get_utc_time().strftime('%Y%m%dT%H:%M:%SZ') message = clean_text(message) lines = message.split('\n') first_line = lines.pop(0) nb_lines = str(len(lines)).zfill(3) self._roster_logfile.write('MI %s %s %s %s\n' % (str_time, nb_lines, jid, first_line)) for line in lines: self._roster_logfile.write(' %s\n' % line) self._roster_logfile.flush() except: log.error('Unable to write in the log file (%s)', os.path.join(log_dir, 'roster.log'), exc_info=True) return False return True
def log_message(self, jid, nick, msg, date=None, typ=1): """ log the message in the appropriate jid's file type: 0 = Don’t log 1 = Message 2 = Status/whatever """ if not config.get_by_tabname('use_log', jid): return True logged_msg = build_log_message(nick, msg, date=date, typ=typ) if not logged_msg: return True if jid in self._fds.keys(): fd = self._fds[jid] else: fd = self._check_and_create_log_dir(jid) if not fd: return True try: fd.write(logged_msg) except OSError: log.error('Unable to write in the log file (%s)', os.path.join(log_dir, jid), exc_info=True) return False else: try: fd.flush() # TODO do something better here? except OSError: log.error('Unable to flush the log file (%s)', os.path.join(log_dir, jid), exc_info=True) return False return True
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', self.general_jid,) and not self.input.get_text() and self.on: self.send_chat_state('active')
def log_roster_change(self, jid: str, message: str) -> bool: """ Log a roster change """ if not config.get_by_tabname('use_log', jid): return True self._check_and_create_log_dir('', open_fd=False) filename = log_dir / 'roster.log' if not self._roster_logfile: try: self._roster_logfile = filename.open('a', encoding='utf-8') except IOError: log.error( 'Unable to create the log file (%s)', filename, exc_info=True) return False try: str_time = common.get_utc_time().strftime('%Y%m%dT%H:%M:%SZ') message = clean_text(message) lines = message.split('\n') first_line = lines.pop(0) nb_lines = str(len(lines)).zfill(3) self._roster_logfile.write( 'MI %s %s %s %s\n' % (str_time, nb_lines, jid, first_line)) for line in lines: self._roster_logfile.write(' %s\n' % line) self._roster_logfile.flush() except: log.error( 'Unable to write in the log file (%s)', filename, exc_info=True) return False return True
def _check_and_create_log_dir(self, room: str, open_fd: bool = True) -> Optional[IO[Any]]: """ Check that the directory where we want to log the messages exists. if not, create it """ if not config.get_by_tabname('use_log', room): return None try: log_dir.mkdir(parents=True, exist_ok=True) except OSError as e: log.error('Unable to create the log dir', exc_info=True) except: log.error('Unable to create the log dir', exc_info=True) return None if not open_fd: return None filename = log_dir / room try: fd = filename.open('a', encoding='utf-8') self._fds[room] = fd return fd except IOError: log.error( 'Unable to open the log file (%s)', filename, exc_info=True) return None
def update_status(self, status): old_status = self.__status if not (old_status.show != status.show or old_status.message != status.message): return self.__status = status hide_status_change = config.get_by_tabname('hide_status_change', self.jid.bare) now = datetime.now() dff = now - self.last_remote_message if hide_status_change > -1 and dff.total_seconds() > hide_status_change: return info_c = dump_tuple(get_theme().COLOR_INFORMATION_TEXT) nick = self.get_nick() remote = self.remote_user_color() msg = '\x19%(color)s}%(nick)s\x19%(info)s} changed: ' msg %= {'color': remote, 'nick': nick, 'info': info_c} if status.message != old_status.message and status.message: msg += 'status: %s, ' % status.message if status.show in SHOW_NAME: msg += 'show: %s, ' % SHOW_NAME[status.show] self.add_message( InfoMessage(txt=msg[:-2]), typ=2, )
async def retrieve_messages(tab: tabs.ChatTab, results: AsyncIterable[SMessage], amount: int = 100) -> List[BaseMessage]: """Run the MAM query and put messages in order""" text_buffer = tab._text_buffer msg_count = 0 msgs = [] to_add = [] deterministic = config.get_by_tabname( 'deterministic_nick_colors', tab.jid.bare ) try: async for rsm in results: for msg in rsm['mam']['results']: if msg['mam_result']['forwarded']['stanza'] \ .xml.find('{%s}%s' % ('jabber:client', 'body')) is not None: args = _parse_message(msg) msgs.append(make_line(tab, deterministic=deterministic, **args)) for msg in reversed(msgs): to_add.append(msg) msg_count += 1 if msg_count == amount: to_add.reverse() return to_add msgs = [] to_add.reverse() return to_add except (IqError, IqTimeout) as exc: log.debug('Unable to complete MAM query: %s', exc, exc_info=True) raise MAMQueryException('Query interrupted')
def user_rejoined(self, nick): """ The user (or at least someone with the same nick) came back in the MUC """ self.activate() self.check_features() tab = self.parent_muc theme = get_theme() color = dump_tuple(theme.COLOR_REMOTE_USER) if tab and config.get_by_tabname('display_user_color_in_join_part', self.general_jid): user = tab.get_user_by_name(nick) if user: color = dump_tuple(user.color) self.add_message( '\x19%(join_col)s}%(spec)s \x19%(color)s}%(nick)s\x19' '%(info_col)s} joined the room' % { 'nick': nick, 'color': color, 'spec': theme.CHAR_JOIN, 'join_col': dump_tuple(theme.COLOR_JOIN_CHAR), 'info_col': dump_tuple(theme.COLOR_INFORMATION_TEXT) }, typ=2) return self.core.tabs.current_tab is self
def user_left(self, status_message, user): """ The user left the associated MUC """ self.deactivate() if config.get_by_tabname('display_user_color_in_join_part', self.general_jid): color = dump_tuple(user.color) else: color = dump_tuple(get_theme().COLOR_REMOTE_USER) if not status_message: self.add_message('\x19%(quit_col)s}%(spec)s \x19%(nick_col)s}' '%(nick)s\x19%(info_col)s} has left the room' % { 'nick': user.nick, 'spec': get_theme().CHAR_QUIT, 'nick_col': color, 'quit_col': dump_tuple(get_theme().COLOR_QUIT_CHAR), 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) else: self.add_message('\x19%(quit_col)s}%(spec)s \x19%(nick_col)s}' '%(nick)s\x19%(info_col)s} has left the room' ' (%(status)s)' % { 'status': status_message, 'nick': user.nick, 'spec': get_theme().CHAR_QUIT, 'nick_col': color, 'quit_col': dump_tuple(get_theme().COLOR_QUIT_CHAR), 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) return self.core.current_tab() is self
def log_message(self, jid, nick, msg, date=None, typ=1): """ log the message in the appropriate jid's file type: 0 = Don’t log 1 = Message 2 = Status/whatever """ if not typ: return True jid = str(jid).replace('/', '\\') if not config.get_by_tabname('use_log', jid): return True if jid in self._fds.keys(): fd = self._fds[jid] else: fd = self._check_and_create_log_dir(jid) if not fd: return True try: msg = clean_text(msg) if date is None: str_time = common.get_utc_time().strftime('%Y%m%dT%H:%M:%SZ') else: str_time = common.get_utc_time(date).strftime( '%Y%m%dT%H:%M:%SZ') if typ == 1: prefix = 'MR' else: prefix = 'MI' lines = msg.split('\n') first_line = lines.pop(0) nb_lines = str(len(lines)).zfill(3) if nick: nick = '<' + nick + '>' fd.write(' '.join((prefix, str_time, nb_lines, nick, ' ' + first_line, '\n'))) else: fd.write(' '.join( (prefix, str_time, nb_lines, first_line, '\n'))) for line in lines: fd.write(' %s\n' % line) except: log.error('Unable to write in the log file (%s)', os.path.join(log_dir, jid), exc_info=True) return False else: try: fd.flush() # TODO do something better here? except: log.error('Unable to flush the log file (%s)', os.path.join(log_dir, jid), exc_info=True) return False return True
def get_logs(self, jid, nb=10): """ Get the nb last messages from the log history for the given jid. Note that a message may be more than one line in these files, so this function is a little bit more complicated than “read the last nb lines”. """ if config.get_by_tabname('load_log', jid) <= 0: return if not config.get_by_tabname('use_log', jid): return if nb <= 0: return self._check_and_create_log_dir(jid, open_fd=False) try: fd = open(os.path.join(log_dir, jid), 'rb') except FileNotFoundError: log.info('Non-existing log file (%s)', os.path.join(log_dir, jid), exc_info=True) return except OSError: log.error('Unable to open the log file (%s)', os.path.join(log_dir, jid), exc_info=True) return if not fd: return # read the needed data from the file, we just search nb messages by # searching "\nM" nb times from the end of the file. We use mmap to # do that efficiently, instead of seek()s and read()s which are costly. with fd: try: lines = get_lines_from_fd(fd, nb=nb) except Exception: # file probably empty log.error('Unable to mmap the log file for (%s)', os.path.join(log_dir, jid), exc_info=True) return return parse_log_lines(lines)
def get_logs(self, jid: str, nb: int = 10) -> Optional[List[Dict[str, Any]]]: """ Get the nb last messages from the log history for the given jid. Note that a message may be more than one line in these files, so this function is a little bit more complicated than “read the last nb lines”. """ if config.get_by_tabname('load_log', jid) <= 0: return None if not config.get_by_tabname('use_log', jid): return None if nb <= 0: return None self._check_and_create_log_dir(jid, open_fd=False) filename = log_dir / jid try: fd = filename.open('rb') except FileNotFoundError: log.info('Non-existing log file (%s)', filename, exc_info=True) return None except OSError: log.error( 'Unable to open the log file (%s)', filename, exc_info=True) return None if not fd: return None # read the needed data from the file, we just search nb messages by # searching "\nM" nb times from the end of the file. We use mmap to # do that efficiently, instead of seek()s and read()s which are costly. with fd: try: lines = get_lines_from_fd(fd, nb=nb) except Exception: # file probably empty log.error( 'Unable to mmap the log file for (%s)', filename, exc_info=True) return None return parse_log_lines(lines)
def on_gain_focus(self): self.state = 'current' curses.curs_set(1) tab = self.core.tabs.by_name_and_class(safeJID(self.name).bare, MucTab) if tab and tab.joined and config.get_by_tabname( 'send_chat_states', self.general_jid, ) and not self.input.get_text() and self.on: self.send_chat_state('active')
def log_message(self, jid, nick, msg, date=None, typ=1): """ log the message in the appropriate jid's file type: 0 = Don’t log 1 = Message 2 = Status/whatever """ if not typ: return True jid = str(jid).replace('/', '\\') if not config.get_by_tabname('use_log', jid): return True if jid in self._fds.keys(): fd = self._fds[jid] else: fd = self._check_and_create_log_dir(jid) if not fd: return True try: msg = clean_text(msg) if date is None: str_time = common.get_utc_time().strftime('%Y%m%dT%H:%M:%SZ') else: str_time = common.get_utc_time(date).strftime('%Y%m%dT%H:%M:%SZ') if typ == 1: prefix = 'MR' else: prefix = 'MI' lines = msg.split('\n') first_line = lines.pop(0) nb_lines = str(len(lines)).zfill(3) if nick: nick = '<' + nick + '>' fd.write(' '.join((prefix, str_time, nb_lines, nick, ' '+first_line, '\n'))) else: fd.write(' '.join((prefix, str_time, nb_lines, first_line, '\n'))) for line in lines: fd.write(' %s\n' % line) except: log.error('Unable to write in the log file (%s)', os.path.join(log_dir, jid), exc_info=True) return False else: try: fd.flush() # TODO do something better here? except: log.error('Unable to flush the log file (%s)', os.path.join(log_dir, jid), exc_info=True) return False return True
def hl(tab): """ Make a tab beep and change its status. """ if tab.state != 'current': tab.state = 'private' conv_jid = safeJID(tab.name) if 'private' in config.get('beep_on', 'highlight private').split(): if not config.get_by_tabname('disable_beep', conv_jid.bare, default=False): curses.beep()
def add_line( tab, text_buffer: TextBuffer, text: str, time: datetime, nick: str, top: bool, ) -> None: """Adds a textual entry in the TextBuffer""" # Convert to local timezone time = time.replace(tzinfo=timezone.utc).astimezone(tz=None) time = time.replace(tzinfo=None) deterministic = config.get_by_tabname('deterministic_nick_colors', tab.jid.bare) if isinstance(tab, tabs.MucTab): nick = nick.split('/')[1] user = tab.get_user_by_name(nick) if deterministic: if user: color = user.color else: theme = get_theme() if theme.ccg_palette: fg_color = colors.ccg_text_to_color( theme.ccg_palette, nick) color = fg_color, -1 else: mod = len(theme.LIST_COLOR_NICKNAMES) nick_pos = int(md5(nick.encode('utf-8')).hexdigest(), 16) % mod color = theme.LIST_COLOR_NICKNAMES[nick_pos] else: color = random.choice(list(xhtml.colors)) color = xhtml.colors.get(color) color = (color, -1) else: nick = nick.split('/')[0] color = get_theme().COLOR_OWN_NICK text_buffer.add_message( txt=text, time=time, nickname=nick, nick_color=color, history=True, user=None, highlight=False, top=top, identifier=None, str_time=None, jid=None, )
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 on_lose_focus(self): if self.input.text: self.state = 'nonempty' else: self.state = 'normal' self.text_win.remove_line_separator() self.text_win.add_line_separator(self._text_buffer) tab = self.core.tabs.by_name_and_class(safeJID(self.name).bare, MucTab) if tab and tab.joined and config.get_by_tabname( 'send_chat_states', self.general_jid) and self.on: self.send_chat_state('inactive') self.check_scrolled()
def send_chat_state(self, state, always_send=False): """ Send an empty chatstate message """ if self.check_send_chat_state(): 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): 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): 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 join(self, args): """ /join [room][/nick] [password] """ if len(args) == 0: room, nick = self._empty_join() else: room, nick = self._parse_join_jid(args[0]) if not room and not nick: return # nothing was parsed room = room.lower() if nick == '': nick = self.core.own_nick # a password is provided if len(args) == 2: password = args[1] else: password = config.get_by_tabname('password', room, fallback=False) if room in self.core.pending_invites: del self.core.pending_invites[room] tab = self.core.get_tab_by_name(room, tabs.MucTab) # New tab if tab is None: tab = self.core.open_new_room(room, nick, password=password) tab.join() else: self.core.focus_tab_named(tab.name) if tab.own_nick == nick and tab.joined: self.core.information('/join: Nothing to do.', 'Info') else: tab.command_part('') tab.own_nick = nick tab.password = password tab.join() if config.get('bookmark_on_join'): method = 'remote' if config.get( 'use_remote_bookmarks') else 'local' self._add_bookmark('%s/%s' % (room, nick), True, password, method) if tab == self.core.current_tab(): tab.refresh() self.core.doupdate()
def join(self, args): """ /join [room][/nick] [password] """ if len(args) == 0: room, nick = self._empty_join() else: room, nick = self._parse_join_jid(args[0]) if not room and not nick: return # nothing was parsed room = room.lower() if nick == '': nick = self.core.own_nick # a password is provided if len(args) == 2: password = args[1] else: password = config.get_by_tabname('password', room, fallback=False) if room in self.core.pending_invites: del self.core.pending_invites[room] tab = self.core.tabs.by_name_and_class(room, tabs.MucTab) # New tab if tab is None: tab = self.core.open_new_room(room, nick, password=password) tab.join() else: self.core.focus_tab(tab) if tab.own_nick == nick and tab.joined: self.core.information('/join: Nothing to do.', 'Info') else: tab.command_part('') tab.own_nick = nick tab.password = password tab.join() if config.get('bookmark_on_join'): method = 'remote' if config.get( 'use_remote_bookmarks') else 'local' self._add_bookmark('%s/%s' % (room, nick), True, password, method) if tab == self.core.tabs.current_tab: tab.refresh() self.core.doupdate()
def log_message(self, jid: str, nick: str, msg: str, date: Optional[datetime] = None, typ: int = 1) -> bool: """ log the message in the appropriate jid's file type: 0 = Don’t log 1 = Message 2 = Status/whatever """ if not config.get_by_tabname('use_log', jid): return True logged_msg = build_log_message(nick, msg, date=date, typ=typ) if not logged_msg: return True jid = str(jid).replace('/', '\\') if jid in self._fds.keys(): fd = self._fds[jid] else: option_fd = self._check_and_create_log_dir(jid) if option_fd is None: return True fd = option_fd filename = log_dir / jid try: fd.write(logged_msg) except OSError: log.error( 'Unable to write in the log file (%s)', filename, exc_info=True) return False else: try: fd.flush() # TODO do something better here? except OSError: log.error( 'Unable to flush the log file (%s)', filename, exc_info=True) return False return True
def on_gain_focus(self): contact = roster[self.get_dest_jid()] jid = safeJID(self.get_dest_jid()) if contact: if jid.resource: resource = contact[jid.full] else: resource = contact.get_highest_priority_resource() else: resource = None self.state = 'current' curses.curs_set(1) if (config.get_by_tabname('send_chat_states', self.general_jid) and (not self.input.get_text() or not self.input.get_text().startswith('//'))): if resource: self.send_chat_state('active')
def command_say(self, line: str, attention: bool = False, correct: bool = False) -> None: if not self.on: return our_jid = JID(self.jid.bare) our_jid.resource = self.own_nick msg = self.core.xmpp.make_message( mto=self.jid.full, mfrom=our_jid, ) msg['type'] = 'chat' msg['body'] = line x = ET.Element('{%s}x' % NS_MUC_USER) msg.append(x) # 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']: return if correct or msg['replace']['id']: msg['replace']['id'] = self.last_sent_message['id'] 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', self.general_jid): needed = 'inactive' if self.inactive else 'active' msg['chat_state'] = needed if attention: msg['attention'] = True self.core.events.trigger('private_say_after', msg, self) if not msg['body']: return self.set_last_sent_message(msg, correct=correct) self.core.handler.on_groupchat_private_message(msg, sent=True) msg._add_receipt = True msg.send() self.cancel_paused_delay()
def join(self, args): """ /join [room][/nick] [password] """ if len(args) == 0: room, nick = self._empty_join() else: room, nick = self._parse_join_jid(args[0]) if not room and not nick: return # nothing was parsed room = room.lower() if nick == '': nick = self.core.own_nick # a password is provided if len(args) == 2: password = args[1] else: password = config.get_by_tabname('password', room, fallback=False) if room in self.core.pending_invites: del self.core.pending_invites[room] tab = self.core.get_tab_by_name(room, tabs.MucTab) # New tab if tab is None: tab = self.core.open_new_room(room, nick, password=password) tab.join() else: self.core.focus_tab_named(tab.name) if tab.own_nick == nick and tab.joined: self.core.information('/join: Nothing to do.', 'Info') else: tab.command_part('') tab.own_nick = nick tab.password = password tab.join() if tab == self.core.current_tab(): tab.refresh() self.core.doupdate()
def on_lose_focus(self): contact = roster[self.get_dest_jid()] jid = safeJID(self.get_dest_jid()) if contact: if jid.resource: resource = contact[jid.full] else: resource = contact.get_highest_priority_resource() else: resource = None if self.input.text: self.state = 'nonempty' else: self.state = 'normal' self.text_win.remove_line_separator() self.text_win.add_line_separator(self._text_buffer) if config.get_by_tabname('send_chat_states', self.general_jid): if resource: self.send_chat_state('inactive') self.check_scrolled()
def send_chat_state(self, state: str, always_send: bool = False) -> None: """ Send an empty chatstate message """ from poezio.tabs import PrivateTab if self.check_send_chat_state(): 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): msg = self.core.xmpp.make_message(self.get_dest_jid()) msg['type'] = self.message_type msg['chat_state'] = state self.chat_state = state msg['no-store'] = True if isinstance(self, PrivateTab): x = ET.Element('{%s}x' % NS_MUC_USER) msg.append(x) msg.send()
def update_status(self, status): old_status = self.__status if not (old_status.show != status.show or old_status.message != status.message): return self.__status = status hide_status_change = config.get_by_tabname('hide_status_change', safeJID(self.name).bare) now = datetime.now() dff = now - self.last_remote_message if hide_status_change > -1 and dff.total_seconds() > hide_status_change: return info_c = dump_tuple(get_theme().COLOR_INFORMATION_TEXT) nick = self.get_nick() remote = self.remote_user_color() msg = '\x19%(color)s}%(nick)s\x19%(info)s} changed: ' msg %= {'color': remote, 'nick': nick, 'info': info_c} if status.message != old_status.message and status.message: msg += 'status: %s, ' % status.message if status.show in SHOW_NAME: msg += 'show: %s, ' % SHOW_NAME[status.show] self.add_message(msg[:-2], typ=2)
def user_left(self, status_message, user): """ The user left the associated MUC """ self.deactivate() theme = get_theme() if config.get_by_tabname('display_user_color_in_join_part', self.general_jid): color = dump_tuple(user.color) else: color = dump_tuple(theme.COLOR_REMOTE_USER) if not status_message: self.add_message( '\x19%(quit_col)s}%(spec)s \x19%(nick_col)s}' '%(nick)s\x19%(info_col)s} has left the room' % { 'nick': user.nick, 'spec': theme.CHAR_QUIT, 'nick_col': color, 'quit_col': dump_tuple(theme.COLOR_QUIT_CHAR), 'info_col': dump_tuple(theme.COLOR_INFORMATION_TEXT) }, typ=2) else: self.add_message( '\x19%(quit_col)s}%(spec)s \x19%(nick_col)s}' '%(nick)s\x19%(info_col)s} has left the room' ' (%(status)s)' % { 'status': status_message, 'nick': user.nick, 'spec': theme.CHAR_QUIT, 'nick_col': color, 'quit_col': dump_tuple(theme.COLOR_QUIT_CHAR), 'info_col': dump_tuple(theme.COLOR_INFORMATION_TEXT) }, typ=2) return self.core.tabs.current_tab is self
def command_say(self, line, attention=False, correct=False): msg = self.core.xmpp.make_message(mto=self.get_dest_jid(), mfrom=self.core.xmpp.boundjid) msg['type'] = 'chat' msg['body'] = line if not self.nick_sent: msg['nick'] = self.core.own_nick self.nick_sent = True # trigger the event BEFORE looking for colors. # and before displaying the message in the window # This lets a plugin insert \x19xxx} colors, that will # be converted in xhtml. self.core.events.trigger('conversation_say', msg, self) if not msg['body']: return replaced = False if correct or msg['replace']['id']: msg['replace']['id'] = self.last_sent_message['id'] 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', self.general_jid): needed = 'inactive' if self.inactive else 'active' msg['chat_state'] = needed if attention: msg['attention'] = True self.core.events.trigger('conversation_say_after', msg, self) if not msg['body']: return self.last_sent_message = msg self.core.handler.on_normal_message(msg) msg._add_receipt = True msg.send() self.cancel_paused_delay()
def get_local(self): """Add the locally stored bookmarks to the list.""" rooms = config.get('rooms') if not rooms: return rooms = rooms.split(':') for room in rooms: jid = safeJID(room) if jid.bare == '': continue if jid.resource != '': nick = jid.resource else: nick = None passwd = config.get_by_tabname( 'password', jid.bare, fallback=False) or None b = Bookmark( jid.bare, jid.user, autojoin=True, nick=nick, password=passwd, method='local') self.append(b)
def get_logs(self, jid, nb=10): """ Get the nb last messages from the log history for the given jid. Note that a message may be more than one line in these files, so this function is a little bit more complicated than “read the last nb lines”. """ if config.get_by_tabname('load_log', jid) <= 0: return if not config.get_by_tabname('use_log', jid): return if nb <= 0: return self._check_and_create_log_dir(jid, open_fd=False) try: fd = open(os.path.join(log_dir, jid), 'rb') except FileNotFoundError: log.info('Non-existing log file (%s)', os.path.join(log_dir, jid), exc_info=True) return except OSError: log.error('Unable to open the log file (%s)', os.path.join(log_dir, jid), exc_info=True) return if not fd: return # read the needed data from the file, we just search nb messages by # searching "\nM" nb times from the end of the file. We use mmap to # do that efficiently, instead of seek()s and read()s which are costly. with fd: try: m = mmap.mmap(fd.fileno(), 0, prot=mmap.PROT_READ) except Exception: # file probably empty log.error('Unable to mmap the log file for (%s)', os.path.join(log_dir, jid), exc_info=True) return pos = m.rfind(b"\nM") # start of messages begin with MI or MR, # after a \n # number of message found so far count = 0 while pos != -1 and count < nb - 1: count += 1 pos = m.rfind(b"\nM", 0, pos) if pos == -1: # If we don't have enough lines in the file pos = 1 # 1, because we do -1 just on the next line # to get 0 (start of the file) lines = m[pos - 1:].decode(errors='replace').splitlines() messages = [] color = '\x19%s}' % dump_tuple(get_theme().COLOR_LOG_MSG) # now convert that data into actual Message objects idx = 0 while idx < len(lines): if lines[idx].startswith(' '): # should not happen ; skip idx += 1 log.debug('fail?') continue log_item = parse_message_line(lines[idx]) idx += 1 if not isinstance(log_item, LogItem): log.debug('wrong log format? %s', log_item) continue message = { 'lines': [], 'history': True, 'time': common.get_local_time(log_item.time) } size = log_item.nb_lines if isinstance(log_item, LogInfo): message['lines'].append(color + log_item.text) elif isinstance(log_item, LogMessage): message['nickname'] = log_item.nick message['lines'].append(color + log_item.text) while size != 0 and idx < len(lines): message['lines'].append(lines[idx][1:]) size -= 1 idx += 1 message['txt'] = '\n'.join(message['lines']) del message['lines'] messages.append(message) return messages
def command_say(self, line, attention=False, correct=False): if not self.on: return echo_message = JID(self.name).resource != self.own_nick msg = self.core.xmpp.make_message(self.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', self.name) and echo_message): 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', self.general_jid): needed = 'inactive' if self.inactive else 'active' msg['chat_state'] = needed if 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 and echo_message: self.add_message( msg['body'], nickname=self.own_nick or self.core.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._add_receipt = True msg.send() self.cancel_paused_delay() self.text_win.refresh() self.input.refresh()
def on_close(self): Tab.on_close(self) if config.get_by_tabname('send_chat_states', self.general_jid): self.send_chat_state('gone')
def get_logs(self, jid, nb=10): """ Get the nb last messages from the log history for the given jid. Note that a message may be more than one line in these files, so this function is a little bit more complicated than “read the last nb lines”. """ if config.get_by_tabname('load_log', jid) <= 0: return if not config.get_by_tabname('use_log', jid): return if nb <= 0: return self._check_and_create_log_dir(jid, open_fd=False) try: fd = open(os.path.join(log_dir, jid), 'rb') except FileNotFoundError: log.info('Non-existing log file (%s)', os.path.join(log_dir, jid), exc_info=True) return except OSError: log.error('Unable to open the log file (%s)', os.path.join(log_dir, jid), exc_info=True) return if not fd: return # read the needed data from the file, we just search nb messages by # searching "\nM" nb times from the end of the file. We use mmap to # do that efficiently, instead of seek()s and read()s which are costly. with fd: try: m = mmap.mmap(fd.fileno(), 0, prot=mmap.PROT_READ) except Exception: # file probably empty log.error('Unable to mmap the log file for (%s)', os.path.join(log_dir, jid), exc_info=True) return pos = m.rfind(b"\nM") # start of messages begin with MI or MR, # after a \n # number of message found so far count = 0 while pos != -1 and count < nb-1: count += 1 pos = m.rfind(b"\nM", 0, pos) if pos == -1: # If we don't have enough lines in the file pos = 1 # 1, because we do -1 just on the next line # to get 0 (start of the file) lines = m[pos-1:].decode(errors='replace').splitlines() messages = [] color = '\x19%s}' % dump_tuple(get_theme().COLOR_LOG_MSG) # now convert that data into actual Message objects idx = 0 while idx < len(lines): if lines[idx].startswith(' '): # should not happen ; skip idx += 1 log.debug('fail?') continue tup = _parse_message_line(lines[idx]) idx += 1 if not tup or len(tup) not in (8, 9): # skip log.debug('format? %s', tup) continue time = [int(i) for i in tup[:6]] message = {'lines': [], 'history': True, 'time': common.get_local_time(datetime(*time))} size = int(tup[6]) if len(tup) == 8: #info line message['lines'].append(color + tup[7]) else: # message line message['nickname'] = tup[7] message['lines'].append(color + tup[8]) while size != 0 and idx < len(lines): message['lines'].append(lines[idx][1:]) size -= 1 idx += 1 message['txt'] = '\n'.join(message['lines']) del message['lines'] messages.append(message) return messages