summaryrefslogtreecommitdiff
path: root/src/tabs/muctab.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/tabs/muctab.py')
-rw-r--r--src/tabs/muctab.py1214
1 files changed, 1214 insertions, 0 deletions
diff --git a/src/tabs/muctab.py b/src/tabs/muctab.py
new file mode 100644
index 00000000..9bc2f88e
--- /dev/null
+++ b/src/tabs/muctab.py
@@ -0,0 +1,1214 @@
+from gettext import gettext as _
+
+import logging
+log = logging.getLogger(__name__)
+
+import curses
+import os
+import random
+from datetime import datetime, timedelta
+
+from . import ChatTab, Tab
+
+import common
+import fixes
+import multiuserchat as muc
+import timed_events
+import windows
+import xhtml
+from common import safeJID
+from config import config
+from decorators import refresh_wrapper
+from logger import logger
+from roster import roster
+from theming import get_theme, dump_tuple
+from user import User
+
+
+SHOW_NAME = {
+ 'dnd': _('busy'),
+ 'away': _('away'),
+ 'xa': _('not available'),
+ 'chat': _('chatty'),
+ '': _('available')
+ }
+
+NS_MUC_USER = 'http://jabber.org/protocol/muc#user'
+
+
+class MucTab(ChatTab):
+ """
+ The tab containing a multi-user-chat room.
+ It contains an userlist, an input, a topic, an information and a chat zone
+ """
+ message_type = 'groupchat'
+ plugin_commands = {}
+ plugin_keys = {}
+ def __init__(self, jid, nick):
+ self.joined = False
+ ChatTab.__init__(self, jid)
+ if self.joined == False:
+ self._state = 'disconnected'
+ self.own_nick = nick
+ self.name = jid
+ self.users = []
+ self.privates = [] # private conversations
+ self.topic = ''
+ self.remote_wants_chatstates = True
+ # We send active, composing and paused states to the MUC because
+ # the chatstate may or may not be filtered by the MUC,
+ # that’s not our problem.
+ self.topic_win = windows.Topic()
+ self.text_win = windows.TextWin()
+ self._text_buffer.add_window(self.text_win)
+ self.v_separator = windows.VerticalSeparator()
+ self.user_win = windows.UserList()
+ self.info_header = windows.MucInfoWin()
+ self.input = windows.MessageInput()
+ self.ignores = [] # set of Users
+ # keys
+ self.key_func['^I'] = self.completion
+ self.key_func['M-u'] = self.scroll_user_list_down
+ self.key_func['M-y'] = self.scroll_user_list_up
+ self.key_func['M-n'] = self.go_to_next_hl
+ self.key_func['M-p'] = self.go_to_prev_hl
+ # commands
+ self.register_command('ignore', self.command_ignore,
+ usage=_('<nickname>'),
+ desc=_('Ignore a specified nickname.'),
+ shortdesc=_('Ignore someone'),
+ completion=self.completion_ignore)
+ self.register_command('unignore', self.command_unignore,
+ usage=_('<nickname>'),
+ desc=_('Remove the specified nickname from the ignore list.'),
+ shortdesc=_('Unignore someone.'),
+ completion=self.completion_unignore)
+ self.register_command('kick', self.command_kick,
+ usage=_('<nick> [reason]'),
+ desc=_('Kick the user with the specified nickname. You also can give an optional reason.'),
+ shortdesc=_('Kick someone.'),
+ completion=self.completion_quoted)
+ self.register_command('ban', self.command_ban,
+ usage=_('<nick> [reason]'),
+ desc=_('Ban the user with the specified nickname. You also can give an optional reason.'),
+ shortdesc='Ban someone',
+ completion=self.completion_quoted)
+ self.register_command('role', self.command_role,
+ usage=_('<nick> <role> [reason]'),
+ desc=_('Set the role of an user. Roles can be: none, visitor, participant, moderator. You also can give an optional reason.'),
+ shortdesc=_('Set the role of an user.'),
+ completion=self.completion_role)
+ self.register_command('affiliation', self.command_affiliation,
+ usage=_('<nick or jid> <affiliation>'),
+ desc=_('Set the affiliation of an user. Affiliations can be: outcast, none, member, admin, owner.'),
+ shortdesc=_('Set the affiliation of an user.'),
+ completion=self.completion_affiliation)
+ self.register_command('topic', self.command_topic,
+ usage=_('<subject>'),
+ desc=_('Change the subject of the room.'),
+ shortdesc=_('Change the subject.'),
+ completion=self.completion_topic)
+ self.register_command('query', self.command_query,
+ usage=_('<nick> [message]'),
+ desc=_('Query: Open a private conversation with <nick>. This nick has to be present in the room you\'re currently in. If you specified a message after the nickname, it will immediately be sent to this user.'),
+ shortdesc=_('Query an user.'),
+ completion=self.completion_quoted)
+ self.register_command('part', self.command_part,
+ usage=_('[message]'),
+ desc=_('Disconnect from a room. You can specify an optional message.'),
+ shortdesc=_('Leave the room.'))
+ self.register_command('close', self.command_close,
+ usage=_('[message]'),
+ desc=_('Disconnect from a room and close the tab. You can specify an optional message if you are still connected.'),
+ shortdesc=_('Close the tab.'))
+ self.register_command('nick', self.command_nick,
+ usage=_('<nickname>'),
+ desc=_('Change your nickname in the current room.'),
+ shortdesc=_('Change your nickname.'),
+ completion=self.completion_nick)
+ self.register_command('recolor', self.command_recolor,
+ desc=_('Re-assign a color to all participants of the current room, based on the last time they talked. Use this if the participants currently talking have too many identical colors.'),
+ shortdesc=_('Change the nicks colors.'),
+ completion=self.completion_recolor)
+ self.register_command('cycle', self.command_cycle,
+ usage=_('[message]'),
+ desc=_('Leave the current room and rejoin it immediately.'),
+ shortdesc=_('Leave and re-join the room.'))
+ self.register_command('info', self.command_info,
+ usage=_('<nickname>'),
+ desc=_('Display some information about the user in the MUC: its/his/her role, affiliation, status and status message.'),
+ shortdesc=_('Show an user\'s infos.'),
+ completion=self.completion_info)
+ self.register_command('configure', self.command_configure,
+ desc=_('Configure the current room, through a form.'),
+ shortdesc=_('Configure the room.'))
+ self.register_command('version', self.command_version,
+ usage=_('<jid or nick>'),
+ desc=_('Get the software version of the given JID or nick in room (usually its XMPP client and Operating System).'),
+ shortdesc=_('Get the software version of a jid.'),
+ completion=self.completion_version)
+ self.register_command('names', self.command_names,
+ desc=_('Get the list of the users in the room, and the list of the people assuming the different roles.'),
+ shortdesc=_('List the users.'))
+ self.register_command('invite', self.command_invite,
+ desc=_('Invite a contact to this room'),
+ usage=_('<jid> [reason]'),
+ shortdesc=_('Invite a contact to this room'),
+ completion=self.completion_invite)
+
+ if self.core.xmpp.boundjid.server == "gmail.com": #gmail sucks
+ del self.commands["nick"]
+
+ self.resize()
+ self.update_commands()
+ self.update_keys()
+
+ @property
+ def general_jid(self):
+ return self.get_name()
+
+ @property
+ def is_muc(self):
+ return True
+
+ @property
+ def last_connection(self):
+ last_message = self._text_buffer.last_message
+ if last_message:
+ return last_message.time
+ return None
+
+ @refresh_wrapper.always
+ def go_to_next_hl(self):
+ """
+ Go to the next HL in the room, or the last
+ """
+ self.text_win.next_highlight()
+
+ @refresh_wrapper.always
+ def go_to_prev_hl(self):
+ """
+ Go to the previous HL in the room, or the first
+ """
+ self.text_win.previous_highlight()
+
+ def completion_version(self, the_input):
+ """Completion for /version"""
+ compare_users = lambda x: x.last_talked
+ userlist = [user.nick for user in sorted(self.users, key=compare_users, reverse=True)\
+ if user.nick != self.own_nick]
+ return the_input.auto_completion(userlist, quotify=False)
+
+ def completion_info(self, the_input):
+ """Completion for /info"""
+ compare_users = lambda x: x.last_talked
+ userlist = [user.nick for user in sorted(self.users, key=compare_users, reverse=True)]
+ return the_input.auto_completion(userlist, quotify=False)
+
+ def completion_nick(self, the_input):
+ """Completion for /nick"""
+ nicks = [os.environ.get('USER'), config.get('default_nick', ''), self.core.get_bookmark_nickname(self.get_name())]
+ nicks = [i for i in nicks if i]
+ return the_input.auto_completion(nicks, '', quotify=False)
+
+ def completion_recolor(self, the_input):
+ if the_input.get_argument_position() == 1:
+ return the_input.new_completion(['random'], 1, '', quotify=False)
+ return True
+
+ def completion_ignore(self, the_input):
+ """Completion for /ignore"""
+ userlist = [user.nick for user in self.users]
+ if self.own_nick in userlist:
+ userlist.remove(self.own_nick)
+ userlist.sort()
+ return the_input.auto_completion(userlist, quotify=False)
+
+ def completion_role(self, the_input):
+ """Completion for /role"""
+ n = the_input.get_argument_position(quoted=True)
+ if n == 1:
+ userlist = [user.nick for user in self.users]
+ if self.own_nick in userlist:
+ userlist.remove(self.own_nick)
+ return the_input.new_completion(userlist, 1, '', quotify=True)
+ elif n == 2:
+ possible_roles = ['none', 'visitor', 'participant', 'moderator']
+ return the_input.new_completion(possible_roles, 2, '', quotify=True)
+
+ def completion_affiliation(self, the_input):
+ """Completion for /affiliation"""
+ n = the_input.get_argument_position(quoted=True)
+ if n == 1:
+ userlist = [user.nick for user in self.users]
+ if self.own_nick in userlist:
+ userlist.remove(self.own_nick)
+ jidlist = [user.jid.bare for user in self.users]
+ if self.core.xmpp.boundjid.bare in jidlist:
+ jidlist.remove(self.core.xmpp.boundjid.bare)
+ userlist.extend(jidlist)
+ return the_input.new_completion(userlist, 1, '', quotify=True)
+ elif n == 2:
+ possible_affiliations = ['none', 'member', 'admin', 'owner', 'outcast']
+ return the_input.new_completion(possible_affiliations, 2, '', quotify=True)
+
+ def command_invite(self, args):
+ """/invite <jid> [reason]"""
+ args = common.shell_split(args)
+ if len(args) == 1:
+ jid, reason = args[0], ''
+ elif len(args) == 2:
+ jid, reason = args
+ else:
+ return self.core.command_help('invite')
+ self.core.command_invite('%s %s "%s"' % (jid, self.name, reason))
+
+ def completion_invite(self, the_input):
+ """Completion for /invite"""
+ n = the_input.get_argument_position(quoted=True)
+ if n == 1:
+ return the_input.new_completion(roster.jids(), 1, quotify=True)
+
+ def scroll_user_list_up(self):
+ self.user_win.scroll_up()
+ self.user_win.refresh(self.users)
+ self.input.refresh()
+
+ def scroll_user_list_down(self):
+ self.user_win.scroll_down()
+ self.user_win.refresh(self.users)
+ self.input.refresh()
+
+ def command_info(self, arg):
+ """
+ /info <nick>
+ """
+ if not arg:
+ return self.core.command_help('info')
+ user = self.get_user_by_name(arg)
+ if not user:
+ return self.core.information("Unknown user: %s" % arg)
+ theme = get_theme()
+ info = '\x19%s}%s\x19o%s: show: \x19%s}%s\x19o, affiliation: \x19%s}%s\x19o, role: \x19%s}%s\x19o%s' % (
+ dump_tuple(user.color),
+ arg,
+ (' (\x19%s}%s\x19o)' % (dump_tuple(theme.COLOR_MUC_JID), user.jid)) if user.jid != '' else '',
+ dump_tuple(theme.color_show(user.show)),
+ user.show or 'Available',
+ dump_tuple(theme.color_role(user.role)),
+ user.affiliation or 'None',
+ dump_tuple(theme.color_role(user.role)),
+ user.role or 'None',
+ '\n%s' % user.status if user.status else '')
+ self.core.information(info, 'Info')
+
+ def command_configure(self, arg):
+ form = fixes.get_room_form(self.core.xmpp, self.get_name())
+ if not form:
+ self.core.information('Could not retrieve the configuration form', 'Error')
+ return
+ self.core.open_new_form(form, self.cancel_config, self.send_config)
+
+ def cancel_config(self, form):
+ """
+ The user do not want to send his/her config, send an iq cancel
+ """
+ self.core.xmpp.plugin['xep_0045'].cancelConfig(self.get_name())
+ self.core.close_tab()
+
+ def send_config(self, form):
+ """
+ The user sends his/her config to the server
+ """
+ self.core.xmpp.plugin['xep_0045'].configureRoom(self.get_name(), form)
+ self.core.close_tab()
+
+ def command_cycle(self, arg):
+ """/cycle [reason]"""
+ if self.joined:
+ muc.leave_groupchat(self.core.xmpp, self.get_name(), self.own_nick, arg)
+ self.disconnect()
+ self.core.disable_private_tabs(self.name)
+ self.core.command_join('"/%s"' % self.own_nick)
+ self.user_win.pos = 0
+
+ def command_recolor(self, arg):
+ """
+ /recolor [random]
+ Re-assign color to the participants of the room
+ """
+ arg = arg.strip()
+ compare_users = lambda x: x.last_talked
+ users = list(self.users)
+ sorted_users = sorted(users, key=compare_users, reverse=True)
+ # search our own user, to remove it from the list
+ for user in sorted_users:
+ if user.nick == self.own_nick:
+ sorted_users.remove(user)
+ user.color = get_theme().COLOR_OWN_NICK
+ colors = list(get_theme().LIST_COLOR_NICKNAMES)
+ if arg and arg == 'random':
+ random.shuffle(colors)
+ for i, user in enumerate(sorted_users):
+ user.color = colors[i % len(colors)]
+ self.text_win.rebuild_everything(self._text_buffer)
+ self.user_win.refresh(self.users)
+ self.text_win.refresh()
+ self.input.refresh()
+
+ def command_version(self, arg):
+ """
+ /version <jid or nick>
+ """
+ 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 not arg:
+ return self.core.command_help('version')
+ if arg in [user.nick for user in self.users]:
+ jid = safeJID(self.name).bare
+ jid = safeJID(jid + '/' + arg)
+ else:
+ jid = safeJID(arg)
+ fixes.get_version(self.core.xmpp, jid, callback=callback)
+
+ def command_nick(self, arg):
+ """
+ /nick <nickname>
+ """
+ if not arg:
+ return self.core.command_help('nick')
+ nick = arg
+ if not self.joined:
+ return self.core.information('/nick only works in joined rooms', 'Info')
+ current_status = self.core.get_status()
+ if not safeJID(self.get_name() + '/' + nick):
+ return self.core.information('Invalid nick', 'Info')
+ muc.change_nick(self.core, self.name, nick, current_status.message, current_status.show)
+
+ def command_part(self, arg):
+ """
+ /part [msg]
+ """
+ arg = arg.strip()
+ msg = None
+ if self.joined:
+ self.disconnect()
+ muc.leave_groupchat(self.core.xmpp, self.name, self.own_nick, arg)
+ if arg:
+ msg = _("\x195}You left the chatroom (\x19o%s\x195})\x193}" % arg)
+ else:
+ msg =_("\x195}You left the chatroom\x193}")
+ self.add_message(msg, typ=2)
+ if self == self.core.current_tab():
+ self.refresh()
+ self.core.doupdate()
+ else:
+ msg =_("\x195}You left the chatroom\x193}")
+ self.core.disable_private_tabs(self.name, reason=msg)
+
+ def command_close(self, arg):
+ """
+ /close [msg]
+ """
+ self.command_part(arg)
+ self.core.close_tab()
+
+ def command_query(self, arg):
+ """
+ /query <nick> [message]
+ """
+ args = common.shell_split(arg)
+ if len(args) < 1:
+ return
+ nick = args[0]
+ r = None
+ for user in self.users:
+ if user.nick == nick:
+ r = self.core.open_private_window(self.name, user.nick)
+ if r and len(args) > 1:
+ msg = args[1]
+ self.core.current_tab().command_say(xhtml.convert_simple_to_full_colors(msg))
+ if not r:
+ self.core.information(_("Cannot find user: %s" % nick), 'Error')
+
+ def command_topic(self, arg):
+ """
+ /topic [new topic]
+ """
+ if not arg.strip():
+ self._text_buffer.add_message(_("\x19%s}The subject of the room is: %s") %
+ (dump_tuple(get_theme().COLOR_INFORMATION_TEXT), self.topic))
+ self.refresh()
+ return
+ subject = arg
+ muc.change_subject(self.core.xmpp, self.name, subject)
+
+ def command_names(self, arg=None):
+ """
+ /names
+ """
+ if not self.joined:
+ return
+ color_visitor = dump_tuple(get_theme().COLOR_USER_VISITOR)
+ color_other = dump_tuple(get_theme().COLOR_USER_NONE)
+ color_moderator = dump_tuple(get_theme().COLOR_USER_MODERATOR)
+ color_participant = dump_tuple(get_theme().COLOR_USER_PARTICIPANT)
+ visitors, moderators, participants, others = [], [], [], []
+ aff = {
+ 'owner': get_theme().CHAR_AFFILIATION_OWNER,
+ 'admin': get_theme().CHAR_AFFILIATION_ADMIN,
+ 'member': get_theme().CHAR_AFFILIATION_MEMBER,
+ 'none': get_theme().CHAR_AFFILIATION_NONE,
+ }
+
+ users = self.users[:]
+ users.sort(key=lambda x: x.nick.lower())
+ for user in users:
+ color = aff.get(user.affiliation, get_theme().CHAR_AFFILIATION_NONE)
+ if user.role == 'visitor':
+ visitors.append((user, color))
+ elif user.role == 'participant':
+ participants.append((user, color))
+ elif user.role == 'moderator':
+ moderators.append((user, color))
+ else:
+ others.append((user, color))
+
+ buff = ['Users: %s \n' % len(self.users)]
+ for moderator in moderators:
+ buff.append('\x19%s}%s\x19o\x19%s}%s\x19o' % (color_moderator,
+ moderator[1], dump_tuple(moderator[0].color), moderator[0].nick))
+ for participant in participants:
+ buff.append('\x19%s}%s\x19o\x19%s}%s\x19o' % (color_participant,
+ participant[1], dump_tuple(participant[0].color), participant[0].nick))
+ for visitor in visitors:
+ buff.append('\x19%s}%s\x19o\x19%s}%s\x19o' % (color_visitor,
+ visitor[1], dump_tuple(visitor[0].color), visitor[0].nick))
+ for other in others:
+ buff.append('\x19%s}%s\x19o\x19%s}%s\x19o' % (color_other,
+ other[1], dump_tuple(other[0].color), other[0].nick))
+ buff.append('\n')
+ message = ' '.join(buff)
+
+ self._text_buffer.add_message(message)
+ self.text_win.refresh()
+ self.input.refresh()
+
+ def completion_topic(self, the_input):
+ if the_input.get_argument_position() == 1:
+ return the_input.auto_completion([self.topic], '', quotify=False)
+
+ def completion_quoted(self, the_input):
+ """Nick completion, but with quotes"""
+ if the_input.get_argument_position(quoted=True) == 1:
+ compare_users = lambda x: x.last_talked
+ word_list = [user.nick for user in sorted(self.users, key=compare_users, reverse=True)\
+ if user.nick != self.own_nick]
+ return the_input.new_completion(word_list, 1, quotify=True)
+
+ def command_kick(self, arg):
+ """
+ /kick <nick> [reason]
+ """
+ args = common.shell_split(arg)
+ if not args:
+ self.core.command_help('kick')
+ else:
+ if len(args) > 1:
+ msg = ' "%s"' % args[1]
+ else:
+ msg = ''
+ self.command_role('"'+args[0]+ '" none'+msg)
+
+ def command_ban(self, arg):
+ """
+ /ban <nick> [reason]
+ """
+ def callback(iq):
+ if iq['type'] == 'error':
+ self.core.room_error(iq, self.get_name())
+ args = common.shell_split(arg)
+ if not args:
+ return self.core.command_help('ban')
+ if len(args) > 1:
+ msg = args[1]
+ else:
+ msg = ''
+ nick = args[0]
+
+ if nick in [user.nick for user in self.users]:
+ res = muc.set_user_affiliation(self.core.xmpp, self.get_name(), 'outcast', nick=nick, callback=callback, reason=msg)
+ else:
+ res = muc.set_user_affiliation(self.core.xmpp, self.get_name(), 'outcast', jid=safeJID(nick), callback=callback, reason=msg)
+ if not res:
+ self.core.information('Could not ban user', 'Error')
+
+ def command_role(self, arg):
+ """
+ /role <nick> <role> [reason]
+ Changes the role of an user
+ roles can be: none, visitor, participant, moderator
+ """
+ def callback(iq):
+ if iq['type'] == 'error':
+ self.core.room_error(iq, self.get_name())
+ args = common.shell_split(arg)
+ if len(args) < 2:
+ self.core.command_help('role')
+ return
+ nick, role = args[0],args[1]
+ if len(args) > 2:
+ reason = ' '.join(args[2:])
+ else:
+ reason = ''
+ if not self.joined or \
+ not role in ('none', 'visitor', 'participant', 'moderator'):
+ return
+ if not safeJID(self.get_name() + '/' + nick):
+ return self.core('Invalid nick', 'Info')
+ muc.set_user_role(self.core.xmpp, self.get_name(), nick, reason, role, callback=callback)
+
+ def command_affiliation(self, arg):
+ """
+ /affiliation <nick> <role>
+ Changes the affiliation of an user
+ affiliations can be: outcast, none, member, admin, owner
+ """
+ def callback(iq):
+ if iq['type'] == 'error':
+ self.core.room_error(iq, self.get_name())
+ args = common.shell_split(arg)
+ if len(args) < 2:
+ self.core.command_help('affiliation')
+ return
+ nick, affiliation = args[0], args[1].lower()
+ if not self.joined:
+ return
+ if affiliation not in ('outcast', 'none', 'member', 'admin', 'owner'):
+ self.core.command_help('affiliation')
+ return
+ if nick in [user.nick for user in self.users]:
+ res = muc.set_user_affiliation(self.core.xmpp, self.get_name(), affiliation, nick=nick, callback=callback)
+ else:
+ res = muc.set_user_affiliation(self.core.xmpp, self.get_name(), affiliation, jid=safeJID(nick), callback=callback)
+ if not res:
+ self.core.information('Could not set affiliation', 'Error')
+
+ def command_say(self, line, correct=False):
+ """
+ /say <message>
+ Or normal input + enter
+ """
+ needed = 'inactive' if self.inactive else 'active'
+ msg = self.core.xmpp.make_message(self.get_name())
+ msg['type'] = 'groupchat'
+ 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('muc_say', msg, self)
+ if not msg['body']:
+ self.cancel_paused_delay()
+ self.text_win.refresh()
+ self.input.refresh()
+ return
+ 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:
+ msg['chat_state'] = needed
+ if correct:
+ msg['replace']['id'] = self.last_sent_message['id']
+ self.cancel_paused_delay()
+ self.core.events.trigger('muc_say_after', msg, self)
+ if not msg['body']:
+ self.cancel_paused_delay()
+ self.text_win.refresh()
+ self.input.refresh()
+ return
+ self.last_sent_message = msg
+ msg.send()
+ self.chat_state = needed
+
+ def command_xhtml(self, arg):
+ message = self.generate_xhtml_message(arg)
+ if message:
+ message['type'] = 'groupchat'
+ message.send()
+
+ def command_ignore(self, arg):
+ """
+ /ignore <nick>
+ """
+ if not arg:
+ self.core.command_help('ignore')
+ return
+ nick = arg
+ user = self.get_user_by_name(nick)
+ if not user:
+ self.core.information(_('%s is not in the room') % nick)
+ elif user in self.ignores:
+ self.core.information(_('%s is already ignored') % nick)
+ else:
+ self.ignores.append(user)
+ self.core.information(_("%s is now ignored") % nick, 'info')
+
+ def command_unignore(self, arg):
+ """
+ /unignore <nick>
+ """
+ if not arg:
+ self.core.command_help('unignore')
+ return
+ nick = arg
+ user = self.get_user_by_name(nick)
+ if not user:
+ self.core.information(_('%s is not in the room') % nick)
+ elif user not in self.ignores:
+ self.core.information(_('%s is not ignored') % nick)
+ else:
+ self.ignores.remove(user)
+ self.core.information(_('%s is now unignored') % nick)
+
+ def completion_unignore(self, the_input):
+ if the_input.get_argument_position() == 1:
+ return the_input.new_completion([user.nick for user in self.ignores], 1, '', quotify=False)
+
+ def resize(self):
+ """
+ Resize the whole window. i.e. all its sub-windows
+ """
+ if not self.visible:
+ return
+ self.need_resize = False
+ if config.get("hide_user_list", "false") == "true":
+ text_width = self.width
+ else:
+ text_width = (self.width//10)*9
+ self.user_win.resize(self.height-3-self.core.information_win_size - Tab.tab_win_height(), self.width-(self.width//10)*9-1, 1, (self.width//10)*9+1)
+ self.topic_win.resize(1, self.width, 0, 0)
+ self.v_separator.resize(self.height-2 - Tab.tab_win_height(), 1, 1, 9*(self.width//10))
+ self.text_win.resize(self.height-3-self.core.information_win_size - Tab.tab_win_height(), text_width, 1, 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.topic_win.refresh(self.get_single_line_topic())
+ self.text_win.refresh()
+ if config.get("hide_user_list", "false") == "false":
+ self.v_separator.refresh()
+ self.user_win.refresh(self.users)
+ self.info_header.refresh(self, self.text_win)
+ self.refresh_tab_win()
+ self.info_win.refresh()
+ self.input.refresh()
+
+ 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)
+ 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)
+ return False
+
+ 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.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 = '' if config.get('add_space_after_completion', 'true') == 'false' else ' '
+ 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 get_name(self):
+ return self.name
+
+ def get_nick(self):
+ if config.getl('show_muc_jid', 'true') == 'false':
+ return safeJID(self.name).user
+ return self.name
+
+ def get_text_window(self):
+ return self.text_win
+
+ def on_lose_focus(self):
+ if self.joined:
+ self.state = 'normal'
+ else:
+ self.state = 'disconnected'
+ self.text_win.remove_line_separator()
+ self.text_win.add_line_separator(self._text_buffer)
+ if config.get_by_tabname('send_chat_states', 'true', self.general_jid, True) == 'true' and not self.input.get_text():
+ self.send_chat_state('inactive')
+ self.check_scrolled()
+
+ def on_gain_focus(self):
+ self.state = 'current'
+ if self.text_win.built_lines and self.text_win.built_lines[-1] is None and config.getl('show_useless_separator', 'false') != 'true':
+ self.text_win.remove_line_separator()
+ curses.curs_set(1)
+ if self.joined and config.get_by_tabname('send_chat_states', 'true', self.general_jid, True) == 'true' and not self.input.get_text():
+ self.send_chat_state('active')
+
+ def on_info_win_size_changed(self):
+ if self.core.information_win_size >= self.height-3:
+ return
+ if config.get("hide_user_list", "false") == "true":
+ text_width = self.width
+ else:
+ text_width = (self.width//10)*9
+ self.user_win.resize(self.height-3-self.core.information_win_size - Tab.tab_win_height(), self.width-(self.width//10)*9-1, 1, (self.width//10)*9+1)
+ self.text_win.resize(self.height-3-self.core.information_win_size - Tab.tab_win_height(), text_width, 1, 0)
+ self.info_header.resize(1, self.width, self.height-2-self.core.information_win_size - Tab.tab_win_height(), 0)
+
+ def handle_presence(self, presence):
+ from_nick = presence['from'].resource
+ from_room = presence['from'].bare
+ status_codes = set([s.attrib['code'] for s in presence.findall('{%s}x/{%s}status' % (NS_MUC_USER, NS_MUC_USER))])
+ # Check if it's not an error presence.
+ if presence['type'] == 'error':
+ return self.core.room_error(presence, from_room)
+ affiliation = presence['muc']['affiliation']
+ show = presence['show']
+ status = presence['status']
+ role = presence['muc']['role']
+ jid = presence['muc']['jid']
+ typ = presence['type']
+ if not self.joined: # user in the room BEFORE us.
+ # ignore redondant presence message, see bug #1509
+ if from_nick not in [user.nick for user in self.users] and typ != "unavailable":
+ new_user = User(from_nick, affiliation, show, status, role, jid)
+ self.users.append(new_user)
+ self.core.events.trigger('muc_join', presence, self)
+ if '110' in status_codes or self.own_nick == from_nick:
+ # second part of the condition is a workaround for old
+ # ejabberd or every gateway in the world that just do
+ # not send a 110 status code with the presence
+ self.own_nick = from_nick
+ self.joined = True
+ if self.get_name() in self.core.initial_joins:
+ self.core.initial_joins.remove(self.get_name())
+ self._state = 'normal'
+ elif self != self.core.current_tab():
+ self._state = 'joined'
+ if self.core.current_tab() == self and self.core.status.show not in ('xa', 'away'):
+ self.send_chat_state('active')
+ new_user.color = get_theme().COLOR_OWN_NICK
+ self.add_message(_("\x19%(info_col)s}Your nickname is \x19%(nick_col)s}%(nick)s") % {
+ 'nick': from_nick,
+ 'nick_col': dump_tuple(new_user.color),
+ 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)},
+ typ=2)
+ if '201' in status_codes:
+ self.add_message('\x19%(info_col)s}Info: The room has been created' % {
+ 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)},
+ typ=2)
+ if '170' in status_codes:
+ self.add_message('\x191}Warning: \x19%(info_col)s}this room is publicly logged' % {
+ 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)},
+ typ=2)
+ if '100' in status_codes:
+ self.add_message('\x191}Warning: \x19%(info_col)s}This room is not anonymous.' % {
+ 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)},
+ typ=2)
+ if self.core.current_tab() is not self:
+ self.refresh_tab_win()
+ self.core.current_tab().input.refresh()
+ self.core.doupdate()
+ self.core.enable_private_tabs(self.get_name())
+ else:
+ change_nick = '303' in status_codes
+ kick = '307' in status_codes and typ == 'unavailable'
+ ban = '301' in status_codes and typ == 'unavailable'
+ shutdown = '332' in status_codes and typ == 'unavailable'
+ non_member = '322' in status_codes and typ == 'unavailable'
+ user = self.get_user_by_name(from_nick)
+ # New user
+ if not user:
+ self.core.events.trigger('muc_join', presence, self)
+ self.on_user_join(from_nick, affiliation, show, status, role, jid)
+ # nick change
+ elif change_nick:
+ self.core.events.trigger('muc_nickchange', presence, self)
+ self.on_user_nick_change(presence, user, from_nick, from_room)
+ elif ban:
+ self.core.events.trigger('muc_ban', presence, self)
+ self.core.on_user_left_private_conversation(from_room, from_nick, status)
+ self.on_user_banned(presence, user, from_nick)
+ # kick
+ elif kick:
+ self.core.events.trigger('muc_kick', presence, self)
+ self.core.on_user_left_private_conversation(from_room, from_nick, status)
+ self.on_user_kicked(presence, user, from_nick)
+ elif shutdown:
+ self.core.events.trigger('muc_shutdown', presence, self)
+ self.on_muc_shutdown()
+ elif non_member:
+ self.core.events.trigger('muc_shutdown', presence, self)
+ self.on_non_member_kicked()
+ # user quit
+ elif typ == 'unavailable':
+ self.on_user_leave_groupchat(user, jid, status, from_nick, from_room)
+ # status change
+ else:
+ self.on_user_change_status(user, from_nick, from_room, affiliation, role, show, status)
+ if self.core.current_tab() is self:
+ self.text_win.refresh()
+ self.user_win.refresh(self.users)
+ self.info_header.refresh(self, self.text_win)
+ self.input.refresh()
+ self.core.doupdate()
+
+ def on_non_member_kicked(self):
+ """We have been kicked because the MUC is members-only"""
+ self.add_message('\x19%(info_col)s}%You have been kicked because you are not a member and the room is now members-only.' % {
+ 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)},
+ typ=2)
+ self.disconnect()
+
+ def on_muc_shutdown(self):
+ """We have been kicked because the MUC service is shutting down"""
+ self.add_message('\x19%(info_col)s}%You have been kicked because the MUC service is shutting down.' % {
+ 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)},
+ typ=2)
+ self.disconnect()
+
+ def on_user_join(self, from_nick, affiliation, show, status, role, jid):
+ """
+ When a new user joins the groupchat
+ """
+ user = User(from_nick, affiliation,
+ show, status, role, jid)
+ self.users.append(user)
+ hide_exit_join = config.get_by_tabname('hide_exit_join', -1, self.general_jid, True)
+ if hide_exit_join != 0:
+ color = dump_tuple(user.color) if config.get_by_tabname('display_user_color_in_join_part', '', self.general_jid, True) == 'true' else 3
+ if not jid.full:
+ msg = '\x194}%(spec)s \x19%(color)s}%(nick)s\x19%(info_col)s} joined the room' % {
+ 'nick':from_nick, 'color':color, 'spec':get_theme().CHAR_JOIN,
+ 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}
+ else:
+ msg = '\x194}%(spec)s \x19%(color)s}%(nick)s \x19%(info_col)s}(\x19%(jid_color)s}%(jid)s\x19%(info_col)s}) joined the room' % {
+ 'spec':get_theme().CHAR_JOIN, 'nick':from_nick, 'color':color, 'jid':jid.full,
+ 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT),
+ 'jid_color': dump_tuple(get_theme().COLOR_MUC_JID)}
+ self.add_message(msg, typ=2)
+ self.core.on_user_rejoined_private_conversation(self.name, from_nick)
+
+ def on_user_nick_change(self, presence, user, from_nick, from_room):
+ new_nick = presence.find('{%s}x/{%s}item' % (NS_MUC_USER, NS_MUC_USER)).attrib['nick']
+ if user.nick == self.own_nick:
+ self.own_nick = new_nick
+ # also change our nick in all private discussions of this room
+ self.core.on_muc_own_nickchange(self)
+ user.change_nick(new_nick)
+ color = dump_tuple(user.color) if config.get_by_tabname('display_user_color_in_join_part', '', self.general_jid, True) == 'true' else 3
+ self.add_message('\x19%(color)s}%(old)s\x19%(info_col)s} is now known as \x19%(color)s}%(new)s' % {
+ 'old':from_nick, 'new':new_nick, 'color':color,
+ 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)},
+ typ=2)
+ # rename the private tabs if needed
+ self.core.rename_private_tabs(self.name, from_nick, new_nick)
+
+ def on_user_banned(self, presence, user, from_nick):
+ """
+ When someone is banned from a muc
+ """
+ self.users.remove(user)
+ by = presence.find('{%s}x/{%s}item/{%s}actor' % (NS_MUC_USER, NS_MUC_USER, NS_MUC_USER))
+ reason = presence.find('{%s}x/{%s}item/{%s}reason' % (NS_MUC_USER, NS_MUC_USER, NS_MUC_USER))
+ by = by.attrib['jid'] if by is not None else None
+ if from_nick == self.own_nick: # we are banned
+ if by:
+ kick_msg = _('\x191}%(spec)s \x193}You\x19%(info_col)s} have been banned by \x194}%(by)s') % {
+ 'spec': get_theme().CHAR_KICK, 'by':by,
+ 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}
+ else:
+ kick_msg = _('\x191}%(spec)s \x193}You\x19%(info_col)s} have been banned.') % {
+ 'spec': get_theme().CHAR_KICK,
+ 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}
+ self.core.disable_private_tabs(self.name, reason=kick_msg)
+ self.disconnect()
+ self.refresh_tab_win()
+ self.core.current_tab().input.refresh()
+ self.core.doupdate()
+ if config.get_by_tabname('autorejoin', 'false', self.general_jid, True) == 'true':
+ delay = config.get_by_tabname('autorejoin_delay', "5", self.general_jid, True)
+ delay = common.parse_str_to_secs(delay)
+ if delay <= 0:
+ muc.join_groupchat(self.core, self.name, self.own_nick)
+ else:
+ self.core.add_timed_event(timed_events.DelayedEvent(
+ delay,
+ muc.join_groupchat,
+ self.core,
+ self.name,
+ self.own_nick))
+
+ else:
+ color = dump_tuple(user.color) if config.get_by_tabname('display_user_color_in_join_part', '', self.general_jid, True) == 'true' else 3
+ if by:
+ kick_msg = _('\x191}%(spec)s \x19%(color)s}%(nick)s\x19%(info_col)s} has been banned by \x194}%(by)s') % {
+ 'spec':get_theme().CHAR_KICK, 'nick':from_nick, 'color':color, 'by':by,
+ 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}
+ else:
+ kick_msg = _('\x191}%(spec)s \x19%(color)s}%(nick)s\x19%(info_col)s} has been banned') % {
+ 'spec':get_theme().CHAR_KICK, 'nick':from_nick, 'color':color,
+ 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}
+ if reason is not None and reason.text:
+ kick_msg += _('\x19%(info_col)s} Reason: \x196}%(reason)s\x19%(info_col)s}') % {
+ 'reason': reason.text, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}
+ self.add_message(kick_msg, typ=2)
+
+ def on_user_kicked(self, presence, user, from_nick):
+ """
+ When someone is kicked from a muc
+ """
+ self.users.remove(user)
+ actor_elem = presence.find('{%s}x/{%s}item/{%s}actor' % (NS_MUC_USER, NS_MUC_USER, NS_MUC_USER))
+ reason = presence.find('{%s}x/{%s}item/{%s}reason' % (NS_MUC_USER, NS_MUC_USER, NS_MUC_USER))
+ by = None
+ if actor_elem is not None:
+ by = actor_elem.get('nick') or actor_elem.get('jid')
+ if from_nick == self.own_nick: # we are kicked
+ if by:
+ kick_msg = _('\x191}%(spec)s \x193}You\x19%(info_col)s} have been kicked by \x193}%(by)s') % {
+ 'spec': get_theme().CHAR_KICK, 'by':by,
+ 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}
+ else:
+ kick_msg = _('\x191}%(spec)s \x193}You\x19%(info_col)s} have been kicked.') % {'spec':get_theme().CHAR_KICK, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}
+ self.core.disable_private_tabs(self.name, reason=kick_msg)
+ self.disconnect()
+ self.refresh_tab_win()
+ self.core.current_tab().input.refresh()
+ self.core.doupdate()
+ # try to auto-rejoin
+ if config.get_by_tabname('autorejoin', 'false', self.general_jid, True) == 'true':
+ delay = config.get_by_tabname('autorejoin_delay', "5", self.general_jid, True)
+ delay = common.parse_str_to_secs(delay)
+ if delay <= 0:
+ muc.join_groupchat(self.core, self.name, self.own_nick)
+ else:
+ self.core.add_timed_event(timed_events.DelayedEvent(
+ delay,
+ muc.join_groupchat,
+ self.core,
+ self.name,
+ self.own_nick))
+ else:
+ color = dump_tuple(user.color) if config.get_by_tabname('display_user_color_in_join_part', '', self.general_jid, True) == 'true' else 3
+ if by:
+ kick_msg = _('\x191}%(spec)s \x19%(color)s}%(nick)s\x19%(info_col)s} has been kicked by \x193}%(by)s') % {'spec': get_theme().CHAR_KICK, 'nick':from_nick, 'color':color, 'by':by, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}
+ else:
+ kick_msg = _('\x191}%(spec)s \x19%(color)s}%(nick)s\x19%(info_col)s} has been kicked') % {'spec': get_theme().CHAR_KICK, 'nick':from_nick, 'color':color, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}
+ if reason is not None and reason.text:
+ kick_msg += _('\x19%(info_col)s} Reason: \x196}%(reason)s') % {'reason': reason.text, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}
+ self.add_message(kick_msg, typ=2)
+
+ def on_user_leave_groupchat(self, user, jid, status, from_nick, from_room):
+ """
+ When an user leaves a groupchat
+ """
+ self.users.remove(user)
+ if self.own_nick == user.nick:
+ # We are now out of the room. Happens with some buggy (? not sure) servers
+ self.disconnect()
+ self.core.disable_private_tabs(from_room)
+ self.refresh_tab_win()
+ self.core.current_tab().input.refresh()
+ self.core.doupdate()
+ hide_exit_join = config.get_by_tabname('hide_exit_join', -1, self.general_jid, True) if config.get_by_tabname('hide_exit_join', -1, self.general_jid, True) >= -1 else -1
+ if hide_exit_join == -1 or user.has_talked_since(hide_exit_join):
+ color = dump_tuple(user.color) if config.get_by_tabname('display_user_color_in_join_part', '', self.general_jid, True) == 'true' else 3
+ if not jid.full:
+ leave_msg = _('\x191}%(spec)s \x19%(color)s}%(nick)s\x19%(info_col)s} has left the room') % {'nick':from_nick, 'color':color, 'spec':get_theme().CHAR_QUIT, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}
+ else:
+ leave_msg = _('\x191}%(spec)s \x19%(color)s}%(nick)s\x19%(info_col)s} (\x194}%(jid)s\x19%(info_col)s}) has left the room') % {'spec':get_theme().CHAR_QUIT, 'nick':from_nick, 'color':color, 'jid':jid.full, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}
+ if status:
+ leave_msg += ' (%s)' % status
+ self.add_message(leave_msg, typ=2)
+ self.core.on_user_left_private_conversation(from_room, from_nick, status)
+
+ def on_user_change_status(self, user, from_nick, from_room, affiliation, role, show, status):
+ """
+ When an user changes her status
+ """
+ # build the message
+ display_message = False # flag to know if something significant enough
+ # to be displayed has changed
+ color = dump_tuple(user.color) if config.get_by_tabname('display_user_color_in_join_part', '', self.general_jid, True) == 'true' else 3
+ if from_nick == self.own_nick:
+ msg = _('\x193}You\x19%(info_col)s} changed: ') % {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}
+ else:
+ msg = _('\x19%(color)s}%(nick)s\x19%(info_col)s} changed: ') % {'nick': from_nick, 'color': color, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}
+ if show not in SHOW_NAME:
+ self.core.information(_("%s from room %s sent an invalid show: %s") %\
+ (from_nick, from_room, show), "Warning")
+ if affiliation != user.affiliation:
+ msg += _('affiliation: %s, ') % affiliation
+ display_message = True
+ if role != user.role:
+ msg += _('role: %s, ') % role
+ display_message = True
+ if show != user.show and show in SHOW_NAME:
+ msg += _('show: %s, ') % SHOW_NAME[show]
+ display_message = True
+ if status != user.status:
+ # if the user sets his status to nothing
+ if status:
+ msg += _('status: %s, ') % status
+ display_message = True
+ elif show in SHOW_NAME and show == user.show:
+ msg += _('show: %s, ') % SHOW_NAME[show]
+ display_message = True
+ if not display_message:
+ return
+ msg = msg[:-2] # remove the last ", "
+ hide_status_change = config.get_by_tabname('hide_status_change', -1, self.general_jid, True)
+ if hide_status_change < -1:
+ hide_status_change = -1
+ if ((hide_status_change == -1 or \
+ user.has_talked_since(hide_status_change) or\
+ user.nick == self.own_nick)\
+ and\
+ (affiliation != user.affiliation or\
+ role != user.role or\
+ show != user.show or\
+ status != user.status))\
+ or\
+ (affiliation != user.affiliation or\
+ role != user.role):
+ # display the message in the room
+ self._text_buffer.add_message(msg)
+ self.core.on_user_changed_status_in_private('%s/%s' % (from_room, from_nick), msg)
+ # finally, effectively change the user status
+ user.update(affiliation, show, status, role)
+
+ def disconnect(self):
+ """
+ Set the state of the room as not joined, so
+ we can know if we can join it, send messages to it, etc
+ """
+ self.users = []
+ if self is not self.core.current_tab():
+ self.state = 'disconnected'
+ self.joined = False
+
+ def get_single_line_topic(self):
+ """
+ Return the topic as a single-line string (for the window header)
+ """
+ return self.topic.replace('\n', '|')
+
+ def log_message(self, txt, nickname, time=None, typ=1):
+ """
+ Log the messages in the archives, if it needs
+ to be
+ """
+ if time is None and self.joined: # don't log the history messages
+ if not logger.log_message(self.name, nickname, txt, typ=typ):
+ self.core.information(_('Unable to write in the log file'), 'Error')
+
+ def do_highlight(self, txt, time, nickname):
+ """
+ Set the tab color and returns the nick color
+ """
+ highlighted = False
+ if not time and nickname and nickname != self.own_nick and self.joined:
+ if self.own_nick.lower() in txt.lower():
+ if self.state != 'current':
+ self.state = 'highlight'
+ highlighted = True
+ else:
+ highlight_words = config.get_by_tabname('highlight_on', '', self.general_jid, True).split(':')
+ for word in highlight_words:
+ if word and word.lower() in txt.lower():
+ if self.state != 'current':
+ self.state = 'highlight'
+ highlighted = True
+ break
+ if highlighted:
+ beep_on = config.get('beep_on', 'highlight private').split()
+ if 'highlight' in beep_on and 'message' not in beep_on:
+ if config.get_by_tabname('disable_beep', 'false', self.name, False).lower() != 'true':
+ curses.beep()
+ return highlighted
+
+ def get_user_by_name(self, nick):
+ """
+ Gets the user associated with the given nick, or None if not found
+ """
+ for user in self.users:
+ if user.nick == nick:
+ return user
+ return None
+
+ def add_message(self, txt, time=None, nickname=None, **kwargs):
+ """
+ Note that user can be None even if nickname is not None. It happens
+ when we receive an history message said by someone who is not
+ in the room anymore
+ Return True if the message highlighted us. False otherwise.
+ """
+ self.log_message(txt, nickname, time=time, typ=kwargs.get('typ', 1))
+ args = {key: value for key, value in kwargs.items() if key not in ('typ', 'forced_user')}
+ user = self.get_user_by_name(nickname) if nickname is not None else None
+ if user:
+ user.set_last_talked(datetime.now())
+ args['user'] = user
+ if not user and kwargs.get('forced_user'):
+ args['user'] = kwargs['forced_user']
+ if not time and nickname and\
+ nickname != self.own_nick and\
+ self.state != 'current':
+ if self.state != 'highlight' and\
+ config.get_by_tabname('notify_messages', 'true', self.get_name()) == 'true':
+ self.state = 'message'
+ if (not nickname or time) and not txt.startswith('/me '):
+ txt = '\x19%(info_col)s}%(txt)s' % {'txt':txt, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}
+ elif not kwargs.get('highlight'): # TODO
+ args['highlight'] = self.do_highlight(txt, time, nickname)
+ time = time or datetime.now()
+ self._text_buffer.add_message(txt, time, nickname, **args)
+ return args.get('highlight', False)
+
+ def modify_message(self, txt, old_id, new_id, time=None, nickname=None, user=None, jid=None):
+ self.log_message(txt, nickname, time=time, typ=1)
+ highlight = self.do_highlight(txt, time, nickname)
+ message = self._text_buffer.modify_message(txt, old_id, new_id, highlight=highlight, time=time, user=user, jid=jid)
+ if message:
+ self.text_win.modify_message(old_id, message)
+ self.core.refresh_window()
+ return highlight
+ return False
+
+ def matching_names(self):
+ return [(1, safeJID(self.get_name()).user), (3, self.get_name())]
+
+