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 on_mood_event(self, message): """ Called when a pep notification for an user mood is received. """ contact = roster[message['from'].bare] if not contact: return roster.modified() item = message['pubsub_event']['items']['item'] old_mood = contact.mood if item.xml.find('{http://jabber.org/protocol/mood}mood'): mood = item['mood']['value'] if mood: mood = pep.MOODS.get(mood, mood) text = item['mood']['text'] if text: mood = '%s (%s)' % (mood, text) contact.mood = mood else: contact.mood = '' else: contact.mood = '' if contact.mood: logger.log_roster_change(contact.bare_jid, 'has now the mood: %s' % contact.mood) if old_mood != contact.mood and config.get_by_tabname('display_mood_notifications', contact.bare_jid): if contact.mood: self.information('Mood from '+ contact.bare_jid + ': ' + contact.mood, 'Mood') else: self.information(contact.bare_jid + ' stopped having his/her mood.', 'Mood')
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 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') 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 on_tune_event(self, message): """ Called when a pep notification for an user tune is received """ contact = roster[message['from'].bare] if not contact: return roster.modified() item = message['pubsub_event']['items']['item'] old_tune = contact.tune if item.xml.find('{http://jabber.org/protocol/tune}tune'): item = item['tune'] contact.tune = { 'artist': item['artist'], 'length': item['length'], 'rating': item['rating'], 'source': item['source'], 'title': item['title'], 'track': item['track'], 'uri': item['uri'] } else: contact.tune = {} if contact.tune: logger.log_roster_change(message['from'].bare, 'is now listening to %s' % common.format_tune_string(contact.tune)) if old_tune != contact.tune and config.get_by_tabname('display_tune_notifications', contact.bare_jid): if contact.tune: self.information( 'Tune from '+ message['from'].bare + ': ' + common.format_tune_string(contact.tune), 'Tune') else: self.information(contact.bare_jid + ' stopped listening to music.', 'Tune')
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 on_gaming_event(self, message): """ Called when a pep notification for user gaming is received """ contact = roster[message['from'].bare] if not contact: return item = message['pubsub_event']['items']['item'] old_gaming = contact.gaming if item.xml.find('{urn:xmpp:gaming:0}gaming'): item = item['gaming'] # only name and server_address are used for now contact.gaming = { 'character_name': item['character_name'], 'character_profile': item['character_profile'], 'name': item['name'], 'level': item['level'], 'uri': item['uri'], 'server_name': item['server_name'], 'server_address': item['server_address'], } else: contact.gaming = {} if contact.gaming: logger.log_roster_change(contact.bare_jid, 'is playing %s' % (common.format_gaming_string(contact.gaming))) if old_gaming != contact.gaming and config.get_by_tabname('display_gaming_notifications', contact.bare_jid): if contact.gaming: self.information('%s is playing %s' % (contact.bare_jid, common.format_gaming_string(contact.gaming)), 'Gaming') else: self.information(contact.bare_jid + ' stopped playing.', 'Gaming')
def hl(tab): 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 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 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 hl(tab): 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 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 try_modify(): replaced_id = message['replace']['id'] if replaced_id and config.get_by_tabname('group_corrections', conv_jid.bare): try: conversation.modify_message(body, replaced_id, message['id'], jid=jid, nickname=remote_nick) return True except CorrectionError: log.debug('Unable to correct a message', exc_info=True) return False
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.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('inactive') self.check_scrolled()
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.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('inactive') self.check_scrolled()
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.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): 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 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 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, autojoin=True, nick=nick, password=passwd, method="local") self.append(b)
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 get_local(): """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, autojoin=True, nick=nick, password=passwd, method='local') if not get_by_jid(b.jid): bookmarks.append(b)
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) and (not self.input.get_text() or not self.input.get_text().startswith('//'))): if resource: self.send_chat_state('inactive') self.check_scrolled()
def on_activity_event(self, message): """ Called when a pep notification for an user activity is received. """ contact = roster[message['from'].bare] if not contact: return roster.modified() item = message['pubsub_event']['items']['item'] old_activity = contact.activity if item.xml.find('{http://jabber.org/protocol/activity}activity'): try: activity = item['activity']['value'] except ValueError: return if activity[0]: general = pep.ACTIVITIES.get(activity[0]) s = general['category'] if activity[1]: s = s + '/' + general.get(activity[1], 'other') text = item['activity']['text'] if text: s = '%s (%s)' % (s, text) contact.activity = s else: contact.activity = '' else: contact.activity = '' if contact.activity: logger.log_roster_change(contact.bare_jid, 'has now the activity %s' % contact.activity) if old_activity != contact.activity and config.get_by_tabname('display_activity_notifications', contact.bare_jid): if contact.activity: self.information('Activity from '+ contact.bare_jid + ': ' + contact.activity, 'Activity') else: self.information(contact.bare_jid + ' stopped doing his/her activity.', 'Activity')
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.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): 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 command_join(self, args): """ /join [room][/nick] [password] """ password = None if len(args) == 0: tab = self.current_tab() if not isinstance(tab, (tabs.MucTab, tabs.PrivateTab)): return room = safeJID(tab.name).bare nick = tab.own_nick else: if args[0].startswith('@'): # we try to join a server directly server_root = True info = safeJID(args[0][1:]) else: info = safeJID(args[0]) server_root = False if info == '' and len(args[0]) > 1 and args[0][0] == '/': nick = args[0][1:] elif info.resource == '': nick = self.own_nick else: nick = info.resource if info.bare == '': # happens with /join /nickname, which is OK tab = self.current_tab() if not isinstance(tab, tabs.MucTab): return room = tab.name if nick == '': nick = tab.own_nick else: room = info.bare # no server is provided, like "/join hello": # use the server of the current room if available # check if the current room's name has a server if room.find('@') == -1 and not server_root: if isinstance(self.current_tab(), tabs.MucTab) and\ self.current_tab().name.find('@') != -1: domain = safeJID(self.current_tab().name).domain room += '@%s' % domain else: room = args[0] room = room.lower() if room in self.pending_invites: del self.pending_invites[room] tab = self.get_tab_by_name(room, tabs.MucTab) if tab is not None and tab.joined: # if we are already in the room self.focus_tab_named(tab.name) if tab.own_nick == nick: self.information('/join: Nothing to do.', 'Info') else: tab.own_nick = nick tab.command_cycle('') return if room.startswith('@'): room = room[1:] if len(args) == 2: # a password is provided password = args[1] if password is None: # try to use a saved password password = config.get_by_tabname('password', room, fallback=False) if tab is not None: if password: tab.password = password tab.join() else: tab = self.open_new_room(room, nick, password=password) tab.join() if tab.joined: self.enable_private_tabs(room) tab.state = "normal" if tab == self.current_tab(): tab.refresh() self.doupdate()
def on_groupchat_message(self, message): """ Triggered whenever a message is received from a multi-user chat room. """ if message['subject']: return room_from = message['from'].bare if message['type'] == 'error': # Check if it's an error return self.room_error(message, room_from) tab = self.get_tab_by_name(room_from, tabs.MucTab) if not tab: self.information(_("message received for a non-existing room: %s") % (room_from)) muc.leave_groupchat(self.xmpp, room_from, self.own_nick, msg='') return nick_from = message['mucnick'] user = tab.get_user_by_name(nick_from) if user and user in tab.ignores: return self.events.trigger('muc_msg', message, tab) use_xhtml = config.get('enable_xhtml_im') tmp_dir = config.get('tmp_image_dir') or path.join(CACHE_DIR, 'images') extract_images = config.get('extract_inline_images') body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml, tmp_dir=tmp_dir, extract_images=extract_images) if not body: return old_state = tab.state delayed, date = common.find_delayed_tag(message) replaced_id = message['replace']['id'] replaced = False if replaced_id is not '' and config.get_by_tabname('group_corrections', message['from'].bare): try: if tab.modify_message(body, replaced_id, message['id'], time=date, nickname=nick_from, user=user): self.events.trigger('highlight', message, tab) replaced = True except CorrectionError: log.debug('Unable to correct a message', exc_info=True) if not replaced and tab.add_message(body, date, nick_from, history=delayed, identifier=message['id'], jid=message['from'], typ=1): self.events.trigger('highlight', message, tab) if message['from'].resource == tab.own_nick: tab.last_sent_message = message if tab is self.current_tab(): tab.text_win.refresh() tab.info_header.refresh(tab, tab.text_win) tab.input.refresh() self.doupdate() elif tab.state != old_state: self.refresh_tab_win() current = self.current_tab() if hasattr(current, 'input') and current.input: current.input.refresh() self.doupdate() if 'message' in config.get('beep_on').split(): if (not config.get_by_tabname('disable_beep', room_from) and self.own_nick != message['from'].resource): curses.beep()
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 7 > len(tup) > 10: # skip log.debug('format? %s', tup) continue time = [int(i) for index, i in enumerate(tup) if index < 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
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 on_normal_message(self, message): """ When receiving "normal" messages (not a private message from a muc participant) """ if message['type'] == 'error': return self.information(self.get_error_message(message, deprecated=True), 'Error') elif message['type'] == 'headline' and message['body']: return self.information('%s says: %s' % (message['from'], message['body']), 'Headline') use_xhtml = config.get('enable_xhtml_im') tmp_dir = config.get('tmp_image_dir') or path.join(CACHE_DIR, 'images') extract_images = config.get('extract_inline_images') body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml, tmp_dir=tmp_dir, extract_images=extract_images) if not body: return remote_nick = '' # normal message, we are the recipient if message['to'].bare == self.xmpp.boundjid.bare: conv_jid = message['from'] jid = conv_jid color = get_theme().COLOR_REMOTE_USER # check for a name if conv_jid.bare in roster: remote_nick = roster[conv_jid.bare].name # check for a received nick if not remote_nick and config.get('enable_user_nick'): if message.xml.find('{http://jabber.org/protocol/nick}nick') is not None: remote_nick = message['nick']['nick'] if not remote_nick: remote_nick = conv_jid.user if not remote_nick: remote_nick = conv_jid.full own = False # we wrote the message (happens with carbons) elif message['from'].bare == self.xmpp.boundjid.bare: conv_jid = message['to'] jid = self.xmpp.boundjid color = get_theme().COLOR_OWN_NICK remote_nick = self.own_nick own = True # we are not part of that message, drop it else: return conversation = self.get_conversation_by_jid(conv_jid, create=True) if isinstance(conversation, tabs.DynamicConversationTab) and conv_jid.resource: conversation.lock(conv_jid.resource) if not own and not conversation.nick: conversation.nick = remote_nick elif not own: # keep a fixed nick during the whole conversation remote_nick = conversation.nick self.events.trigger('conversation_msg', message, conversation) if not message['body']: return body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml, tmp_dir=tmp_dir, extract_images=extract_images) delayed, date = common.find_delayed_tag(message) def try_modify(): replaced_id = message['replace']['id'] if replaced_id and config.get_by_tabname('group_corrections', conv_jid.bare): try: conversation.modify_message(body, replaced_id, message['id'], jid=jid, nickname=remote_nick) return True except CorrectionError: log.debug('Unable to correct a message', exc_info=True) return False if not try_modify(): conversation.add_message(body, date, nickname=remote_nick, nick_color=color, history=delayed, identifier=message['id'], jid=jid, typ=1) if conversation.remote_wants_chatstates is None and not delayed: if message['chat_state']: conversation.remote_wants_chatstates = True else: conversation.remote_wants_chatstates = False if 'private' in config.get('beep_on').split(): if not config.get_by_tabname('disable_beep', conv_jid.bare): curses.beep() if self.current_tab() is not conversation: conversation.state = 'private' self.refresh_tab_win() else: self.refresh_window()
def on_groupchat_private_message(self, message): """ We received a Private Message (from someone in a Muc) """ jid = message['from'] nick_from = jid.resource if not nick_from: return self.on_groupchat_message(message) room_from = jid.bare use_xhtml = config.get('enable_xhtml_im') tmp_dir = config.get('tmp_image_dir') or path.join(CACHE_DIR, 'images') extract_images = config.get('extract_inline_images') body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml, tmp_dir=tmp_dir, extract_images=extract_images) tab = self.get_tab_by_name(jid.full, tabs.PrivateTab) # get the tab with the private conversation ignore = config.get_by_tabname('ignore_private', room_from) if not tab: # It's the first message we receive: create the tab if body and not ignore: tab = self.open_private_window(room_from, nick_from, False) if ignore: self.events.trigger('ignored_private', message, tab) msg = config.get_by_tabname('private_auto_response', room_from) if msg and body: self.xmpp.send_message(mto=jid.full, mbody=msg, mtype='chat') return self.events.trigger('private_msg', message, tab) body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml, tmp_dir=tmp_dir, extract_images=extract_images) if not body or not tab: return replaced_id = message['replace']['id'] replaced = False user = tab.parent_muc.get_user_by_name(nick_from) if replaced_id is not '' and config.get_by_tabname('group_corrections', room_from): try: tab.modify_message(body, replaced_id, message['id'], user=user, jid=message['from'], nickname=nick_from) replaced = True except CorrectionError: log.debug('Unable to correct a message', exc_info=True) if not replaced: tab.add_message(body, time=None, nickname=nick_from, forced_user=user, identifier=message['id'], jid=message['from'], typ=1) if tab.remote_wants_chatstates is None: if message['chat_state']: tab.remote_wants_chatstates = True else: tab.remote_wants_chatstates = False if 'private' in config.get('beep_on').split(): if not config.get_by_tabname('disable_beep', jid.full): curses.beep() if tab is self.current_tab(): self.refresh_window() else: tab.state = 'private' self.refresh_tab_win()
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: 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 7 > len(tup) > 10: # skip log.debug('format? %s', tup) continue time = [int(i) for index, i in enumerate(tup) if index < 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
def command_join(self, args, histo_length=None): """ /join [room][/nick] [password] """ password = None if len(args) == 0: tab = self.current_tab() if not isinstance(tab, (tabs.MucTab, tabs.PrivateTab)): return room = safeJID(tab.name).bare nick = tab.own_nick else: if args[0].startswith('@'): # we try to join a server directly server_root = True info = safeJID(args[0][1:]) else: info = safeJID(args[0]) server_root = False if info == '' and len(args[0]) > 1 and args[0][0] == '/': nick = args[0][1:] elif info.resource == '': nick = self.own_nick else: nick = info.resource if info.bare == '': # happens with /join /nickname, which is OK tab = self.current_tab() if not isinstance(tab, tabs.MucTab): return room = tab.name if nick == '': nick = tab.own_nick else: room = info.bare # no server is provided, like "/join hello": # use the server of the current room if available # check if the current room's name has a server if room.find('@') == -1 and not server_root: if isinstance(self.current_tab(), tabs.MucTab) and\ self.current_tab().name.find('@') != -1: domain = safeJID(self.current_tab().name).domain room += '@%s' % domain else: room = args[0] room = room.lower() if room in self.pending_invites: del self.pending_invites[room] tab = self.get_tab_by_name(room, tabs.MucTab) if len(args) == 2: # a password is provided password = args[1] if tab and tab.joined: # if we are already in the room self.focus_tab_named(tab.name) if tab.own_nick == nick: self.information('/join: Nothing to do.', 'Info') else: tab.own_nick = nick tab.command_cycle('') return if room.startswith('@'): room = room[1:] current_status = self.get_status() if not histo_length: histo_length = config.get('muc_history_length') if histo_length == -1: histo_length = None if histo_length is not None: histo_length = str(histo_length) if password is None: # try to use a saved password password = config.get_by_tabname('password', room, fallback=False) if tab and not tab.joined: if tab.last_connection: if tab.last_connection is not None: delta = datetime.now() - tab.last_connection seconds = delta.seconds + delta.days * 24 * 3600 else: seconds = 0 seconds = int(seconds) else: seconds = 0 muc.join_groupchat(self, room, nick, password, histo_length, current_status.message, current_status.show, seconds=seconds) if not tab: self.open_new_room(room, nick) muc.join_groupchat(self, room, nick, password, histo_length, current_status.message, current_status.show) else: tab.own_nick = nick tab.users = [] if tab and tab.joined: self.enable_private_tabs(room) tab.state = "normal" if tab == self.current_tab(): tab.refresh() self.doupdate()
def command_join(self, args, histo_length=None): """ /join [room][/nick] [password] """ password = None if len(args) == 0: tab = self.current_tab() if not isinstance(tab, (tabs.MucTab, tabs.PrivateTab)): return room = safeJID(tab.name).bare nick = tab.own_nick else: if args[0].startswith('@'): # we try to join a server directly server_root = True info = safeJID(args[0][1:]) else: info = safeJID(args[0]) server_root = False if info == '' and len(args[0]) > 1 and args[0][0] == '/': nick = args[0][1:] elif info.resource == '': nick = self.own_nick else: nick = info.resource if info.bare == '': # happens with /join /nickname, which is OK tab = self.current_tab() if not isinstance(tab, tabs.MucTab): return room = tab.name if nick == '': nick = tab.own_nick else: room = info.bare # no server is provided, like "/join hello": # use the server of the current room if available # check if the current room's name has a server if room.find('@') == -1 and not server_root: if isinstance(self.current_tab(), tabs.MucTab) and\ self.current_tab().name.find('@') != -1: domain = safeJID(self.current_tab().name).domain room += '@%s' % domain else: room = args[0] room = room.lower() if room in self.pending_invites: del self.pending_invites[room] tab = self.get_tab_by_name(room, tabs.MucTab) if len(args) == 2: # a password is provided password = args[1] if tab and tab.joined: # if we are already in the room self.focus_tab_named(tab.name) if tab.own_nick == nick: self.information('/join: Nothing to do.', 'Info') else: tab.own_nick = nick tab.command_cycle('') return if room.startswith('@'): room = room[1:] current_status = self.get_status() if not histo_length: histo_length = config.get('muc_history_length') if histo_length == -1: histo_length = None if histo_length is not None: histo_length = str(histo_length) if password is None: # try to use a saved password password = config.get_by_tabname('password', room, fallback=False) if tab and not tab.joined: if tab.last_connection: if tab.last_connection is not None: delta = datetime.now() - tab.last_connection seconds = delta.seconds + delta.days * 24 * 3600 else: seconds = 0 seconds = int(seconds) else: seconds = 0 if password: tab.password = password muc.join_groupchat(self, room, nick, password, histo_length, current_status.message, current_status.show, seconds=seconds) if not tab: self.open_new_room(room, nick, password=password) muc.join_groupchat(self, room, nick, password, histo_length, current_status.message, current_status.show) else: tab.own_nick = nick tab.users = [] if tab and tab.joined: self.enable_private_tabs(room) tab.state = "normal" if tab == self.current_tab(): tab.refresh() self.doupdate()