summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config.py10
-rw-r--r--src/contact.py2
-rw-r--r--src/core.py48
-rw-r--r--src/multiuserchat.py8
-rw-r--r--src/room.py6
-rw-r--r--src/roster.py4
-rw-r--r--src/tabs.py47
-rw-r--r--src/xhtml.py7
8 files changed, 83 insertions, 49 deletions
diff --git a/src/config.py b/src/config.py
index 4776e7d3..9cedcac7 100644
--- a/src/config.py
+++ b/src/config.py
@@ -134,18 +134,18 @@ class Config(RawConfigParser):
# and copy the default config in it
CONFIG_HOME = environ.get("XDG_CONFIG_HOME")
if not CONFIG_HOME:
- CONFIG_HOME = environ.get('HOME')+'/.config'
-CONFIG_PATH = CONFIG_HOME + '/poezio/'
+ CONFIG_HOME = path.join(environ.get('HOME'), '.config')
+CONFIG_PATH = path.join(CONFIG_HOME, 'poezio')
try:
makedirs(CONFIG_PATH)
except OSError:
pass
-if not path.isfile(CONFIG_PATH+'poezio.cfg'):
- copy2(path.join(path.dirname(__file__), '../data/default_config.cfg'), CONFIG_PATH+'poezio.cfg')
+if not path.isfile(path.join(CONFIG_PATH, 'poezio.cfg')):
+ copy2(path.join(path.dirname(__file__), '../data/default_config.cfg'), path.join(CONFIG_PATH, 'poezio.cfg'))
parser = OptionParser()
-parser.add_option("-f", "--file", dest="filename", default=CONFIG_PATH+'poezio.cfg',
+parser.add_option("-f", "--file", dest="filename", default=path.join(CONFIG_PATH, 'poezio.cfg'),
help="The config file you want to use", metavar="CONFIG_FILE")
parser.add_option("-d", "--debug", dest="debug",
help="The file where debug will be written", metavar="DEBUG_FILE")
diff --git a/src/contact.py b/src/contact.py
index 804a7068..ee0e1e80 100644
--- a/src/contact.py
+++ b/src/contact.py
@@ -166,5 +166,5 @@ class Contact(object):
def __repr__(self):
ret = '<Contact: %s' % self._jid
for resource in self._resources:
- ret += '\n\t\t%s'%resource
+ ret += '\n\t\t%s' % resource
return ret + ' />\n'
diff --git a/src/core.py b/src/core.py
index e096b65c..3f24fb1b 100644
--- a/src/core.py
+++ b/src/core.py
@@ -127,7 +127,7 @@ class Core(object):
'list': (self.command_list, _('Usage: /list\nList: get the list of public chatrooms on the specified server'), self.completion_list),
'message': (self.command_message, _('Usage: /message <jid> [optional message]\nMessage: Open a conversation with the specified JID (even if it is not in our roster), and send a message to it, if specified'), None),
'version': (self.command_version, _('Usage: /version <jid>\nVersion: get the software version of the given JID (usually its XMPP client and Operating System)'), None),
- 'connect': (self.command_reconnect, _('Usage: /connect\nConnect: disconnect from the remote server if you are currently connected and then connect to it again'), None),
+ 'reconnect': (self.command_reconnect, _('Usage: /connect\nConnect: disconnect from the remote server if you are currently connected and then connect to it again'), None),
'server_cycle': (self.command_server_cycle, _('Usage: /server_cycle [domain] [message]\nServer Cycle: disconnect and reconnects in all the rooms in domain.'), None),
}
@@ -550,6 +550,8 @@ class Core(object):
jid = message['from']
body = xhtml.get_body_from_message_stanza(message)
if not body:
+ if message['type'] == 'error':
+ self.information(self.get_error_message_from_error_stanza(message), 'Error')
return
conversation = self.get_tab_of_conversation_with_jid(jid, create=True)
if roster.get_contact_by_jid(jid.bare):
@@ -767,7 +769,8 @@ class Core(object):
def refresh_tab_win(self):
self.current_tab().tab_win.refresh()
- self.current_tab().input.refresh()
+ if self.current_tab().input:
+ self.current_tab().input.refresh()
self.doupdate()
def add_tab(self, new_tab, focus=False):
@@ -850,26 +853,34 @@ class Core(object):
self.current_tab().on_scroll_up()
self.refresh_window()
- def room_error(self, error, room_name):
+ def get_error_message_from_error_stanza(self, stanza):
"""
- Display the error on the room window
+ Takes a stanza of the form <message type='error'><error/></message>
+ and return a well formed string containing the error informations
"""
- room = self.get_room_by_name(room_name)
- msg = error['error']['type']
- condition = error['error']['condition']
- code = error['error']['code']
- body = error['error']['text']
+ msg = stanza['error']['type']
+ condition = stanza['error']['condition']
+ code = stanza['error']['code']
+ body = stanza['error']['text']
if not body:
if code in ERROR_AND_STATUS_CODES:
body = ERROR_AND_STATUS_CODES[code]
else:
body = condition or _('Unknown error')
if code:
- msg = _('Error: %(code)s - %(msg)s: %(body)s') % {'msg':msg, 'body':body, 'code':code}
- self.add_message_to_text_buffer(room, msg)
+ message = _('Error: %(code)s - %(msg)s: %(body)s') % {'msg':msg, 'body':body, 'code':code}
else:
- msg = _('Error: %(msg)s: %(body)s') % {'msg':msg, 'body':body}
- self.add_message_to_text_buffer(room, msg)
+ message = _('Error: %(msg)s: %(body)s') % {'msg':msg, 'body':body}
+ return message
+
+ def room_error(self, error, room_name):
+ """
+ Display the error on the room window
+ """
+ room = self.get_room_by_name(room_name)
+ error_message = self.get_error_message_from_error_stanza(error)
+ self.add_message_to_text_buffer(room, error_message)
+ code = error['error']['code']
if code == '401':
msg = _('To provide a password in order to join the room, type "/join / password" (replace "password" by the real password)')
self.add_message_to_text_buffer(room, msg)
@@ -1072,7 +1083,7 @@ class Core(object):
"""
/reconnect
"""
- self.disconnect(True)
+ self.disconnect(reconnect=True)
def command_list(self, arg):
"""
@@ -1385,7 +1396,7 @@ class Core(object):
popup_time = config.get('popup_time', 4) + (nb_lines - 1) * 2
self.pop_information_win_up(nb_lines, popup_time)
- def disconnect(self, msg=None):
+ def disconnect(self, msg=None, reconnect=False):
"""
Disconnect from remote server and correctly set the states of all
parts of the client (for example, set the MucTabs as not joined, etc)
@@ -1393,13 +1404,10 @@ class Core(object):
for tab in self.tabs:
if isinstance(tab, tabs.MucTab):
muc.leave_groupchat(self.xmpp, tab.get_room().name, tab.get_room().own_nick, msg)
+ roster.empty()
self.save_config()
# Ugly fix thanks to gmail servers
- try:
- sys.stderr = None
- self.xmpp.disconnect(False)
- except:
- pass
+ self.xmpp.disconnect(reconnect)
def command_quit(self, arg):
"""
diff --git a/src/multiuserchat.py b/src/multiuserchat.py
index 87443e07..dd936039 100644
--- a/src/multiuserchat.py
+++ b/src/multiuserchat.py
@@ -25,6 +25,8 @@ from xml.etree import cElementTree as ET
import logging
log = logging.getLogger(__name__)
+NS_MUC_ADMIN = 'http://jabber.org/protocol/muc#admin'
+
def send_private_message(xmpp, jid, line):
"""
Send a private message
@@ -77,10 +79,10 @@ def eject_user(xmpp, jid, nick, reason):
(try to) Eject an user from the room
"""
iq = xmpp.makeIqSet()
- query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
- item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'nick':nick, 'role':'none'})
+ query = ET.Element('{%s}query' % NS_MUC_ADMIN)
+ item = ET.Element('{%s}item' % NS_MUC_ADMIN, {'nick':nick, 'role':'none'})
if reason:
- reason_el = ET.Element('{http://jabber.org/protocol/muc#admin}reason')
+ reason_el = ET.Element('{%s}reason' % NS_MUC_ADMIN)
reason_el.text = reason
item.append(reason_el)
query.append(item)
diff --git a/src/room.py b/src/room.py
index a5a05845..45ebddbd 100644
--- a/src/room.py
+++ b/src/room.py
@@ -46,6 +46,9 @@ class Room(TextBuffer):
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, time, nickname):
@@ -77,6 +80,9 @@ class Room(TextBuffer):
return color
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
diff --git a/src/roster.py b/src/roster.py
index aed5f5a0..afe83c9e 100644
--- a/src/roster.py
+++ b/src/roster.py
@@ -45,6 +45,10 @@ class Roster(object):
except IOError:
return
+ def empty(self):
+ self._contacts = {}
+ self._roster_groups = []
+
def add_contact(self, contact, jid):
"""
Add a contact to the contact list
diff --git a/src/tabs.py b/src/tabs.py
index 12bf08be..801e0793 100644
--- a/src/tabs.py
+++ b/src/tabs.py
@@ -63,6 +63,8 @@ SHOW_NAME = {
'': _('available')
}
+NS_MUC_USER = 'http://jabber.org/protocol/muc#user'
+
class Tab(object):
number = 0
tab_core = None
@@ -119,8 +121,8 @@ class Tab(object):
return command[2](the_input)
else:
# complete the command's name
- words = ['/%s'%(name) for name in self.core.commands] +\
- ['/%s'% (name) for name in self.commands]
+ words = ['/%s'% (name) for name in self.core.commands] +\
+ ['/%s' % (name) for name in self.commands]
the_input.auto_completion(words, '')
return True
return False
@@ -267,6 +269,7 @@ class ChatTab(Tab):
self.commands['say'] = (self.command_say,
_("""Usage: /say <message>\nSay: Just send the message.
Useful if you want your message to begin with a '/'"""), None)
+ self.chat_state = None
def last_words_completion(self):
"""
@@ -306,6 +309,7 @@ class ChatTab(Tab):
msg = self.core.xmpp.make_message(self.get_name())
msg['type'] = self.message_type
msg['chat_state'] = state
+ self.chat_state = state
msg.send()
def send_composing_chat_state(self, empty_before, empty_after):
@@ -314,8 +318,13 @@ class ChatTab(Tab):
on the the current status of the input
"""
if config.get('send_chat_states', 'true') == 'true' and self.remote_wants_chatstates:
- if empty_after:
+ if self.chat_state == "composing" and not empty_after:
+ self.cancel_paused_delay()
+ self.set_paused_delay(True)
+ elif empty_after and not self.chat_state == 'active':
+ self.cancel_paused_delay()
self.send_chat_state("active")
+ elif empty_after:
self.cancel_paused_delay()
elif empty_before or (self.timed_event_paused is not None and not self.timed_event_paused()):
self.cancel_paused_delay()
@@ -767,12 +776,10 @@ class MucTab(ChatTab):
def handle_presence(self, presence):
from_nick = presence['from'].resource
from_room = presence['from'].bare
- code = presence.find('{jabber:client}status')
- status_codes = set([s.attrib['code'] for s in presence.findall('{http://jabber.org/protocol/muc#user}x/{http://jabber.org/protocol/muc#user}status')])
+ 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)
- msg = None
affiliation = presence['muc']['affiliation']
show = presence['show']
status = presence['status']
@@ -834,7 +841,7 @@ class MucTab(ChatTab):
room.add_message('\x194%(spec)s \x193%(nick)s \x195(\x194%(jid)s\x195) joined the room' % {'spec':theme.CHAR_JOIN, 'nick':from_nick, 'jid':jid.full})
def on_user_nick_change(self, room, presence, user, from_nick, from_room):
- new_nick = presence.find('{http://jabber.org/protocol/muc#user}x/{http://jabber.org/protocol/muc#user}item').attrib['nick']
+ new_nick = presence.find('{%s}x/{%s}item' % (NS_MUC_USER, NS_MUC_USER)).attrib['nick']
if user.nick == room.own_nick:
room.own_nick = new_nick
# also change our nick in all private discussion of this room
@@ -851,8 +858,8 @@ class MucTab(ChatTab):
When someone is banned from a muc
"""
room.users.remove(user)
- by = presence.find('{http://jabber.org/protocol/muc#user}x/{http://jabber.org/protocol/muc#user}item/{http://jabber.org/protocol/muc#user}actor')
- reason = presence.find('{http://jabber.org/protocol/muc#user}x/{http://jabber.org/protocol/muc#user}item/{http://jabber.org/protocol/muc#user}reason')
+ 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 == room.own_nick: # we are banned
room.disconnect()
@@ -874,8 +881,8 @@ class MucTab(ChatTab):
When someone is kicked from a muc
"""
room.users.remove(user)
- by = presence.find('{http://jabber.org/protocol/muc#user}x/{http://jabber.org/protocol/muc#user}item/{http://jabber.org/protocol/muc#user}actor')
- reason = presence.find('{http://jabber.org/protocol/muc#user}x/{http://jabber.org/protocol/muc#user}item/{http://jabber.org/protocol/muc#user}reason')
+ 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 == room.own_nick: # we are kicked
room.disconnect()
@@ -912,6 +919,7 @@ class MucTab(ChatTab):
if status:
leave_msg += ' (%s)' % status
room.add_message(leave_msg)
+ self.core.refresh_window()
self.core.on_user_left_private_conversation(from_room, from_nick, status)
def on_user_change_status(self, room, user, from_nick, from_room, affiliation, role, show, status):
@@ -931,9 +939,14 @@ class MucTab(ChatTab):
if show != user.show and show in SHOW_NAME:
msg += _('show: %s, ') % SHOW_NAME[show]
display_message = True
- if status and status != user.status:
- msg += _('status: %s, ') % status
+ if status != user.status:
+ # if the user sets his status to nothing
+ if not status:
+ msg += _('show: %s, ') % SHOW_NAME[show]
+ else:
+ msg += _('status: %s, ') % status
display_message = True
+
if not display_message:
return
msg = msg[:-2] # remove the last ", "
@@ -1084,9 +1097,9 @@ class PrivateTab(ChatTab):
The user left the associated MUC
"""
if not status_message:
- self.get_room().add_message(_('%(spec)s "[%(nick)s]" has left the room') % {'nick':from_nick.replace('"', '\\"'), 'spec':theme.CHAR_QUIT.replace('"', '\\"')})
+ self.get_room().add_message(_('\x191%(spec)s \x193%(nick)s\x195 has left the room') % {'nick':from_nick.replace('"', '\\"'), 'spec':theme.CHAR_QUIT.replace('"', '\\"')})
else:
- self.get_room().add_message(_('%(spec)s "[%(nick)s]" has left the room "(%(status)s)"') % {'nick':from_nick.replace('"', '\\"'), 'spec':theme.CHAR_QUIT, 'status': status_message.replace('"', '\\"')})
+ self.get_room().add_message(_('\x191%(spec)s \x193%(nick)s\x195 has left the room (%(status)s)"') % {'nick':from_nick.replace('"', '\\"'), 'spec':theme.CHAR_QUIT, 'status': status_message.replace('"', '\\"')})
class RosterInfoTab(Tab):
"""
@@ -1571,7 +1584,7 @@ class MucListTab(Tab):
self.name = server
self.upper_message = windows.Topic()
self.upper_message.set_message('Chatroom list on server %s (Loading)' % self.name)
- columns = ('node-part','name', 'users')
+ columns = ('node-part', 'name', 'users')
self.list_header = windows.ColumnHeaderWin(columns)
self.listview = windows.ListWin(columns)
self.tab_win = windows.GlobalInfoBar()
@@ -1764,6 +1777,7 @@ def diffmatch(search, string):
def jid_and_name_match(contact, txt):
"""
+ Match jid with text precisely
"""
if not txt:
return True
@@ -1776,7 +1790,6 @@ def jid_and_name_match_slow(contact, txt):
A function used to know if a contact in the roster should
be shown in the roster
"""
- ratio = 0.7
if not txt:
return True # Everything matches when search is empty
user = JID(contact.get_bare_jid()).user
diff --git a/src/xhtml.py b/src/xhtml.py
index d755229f..16972b65 100644
--- a/src/xhtml.py
+++ b/src/xhtml.py
@@ -28,11 +28,12 @@ import subprocess
from sleekxmpp.xmlstream import ET
from xml.etree.ElementTree import ElementTree
from sys import version_info
-from string import digits
from config import config
import logging
+digits = '0123456789' # never trust the modules
+
log = logging.getLogger(__name__)
shell_colors_re = re.compile(r'(\[(?:\d+;)*(?:\d+m))')
@@ -73,9 +74,9 @@ def convert_links_to_plaintext(text):
if child.tag == '{http://www.w3.org/1999/xhtml}a':
if child.attrib['href'] != child.text:
if child.text is None and 'title' in child.attrib:
- link_text = '\n%s (%s)'%(child.attrib['href'], child.attrib['title'])
+ link_text = '\n%s (%s)' % (child.attrib['href'], child.attrib['title'])
else:
- link_text = '\n%s (%s)'%(child.attrib['href'], child.text)
+ link_text = '\n%s (%s)' % (child.attrib['href'], child.text)
else:
link_text = child.text
if previous_child is not None: