summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml27
-rw-r--r--data/default_config.cfg4
-rw-r--r--data/doap.xml2
-rw-r--r--data/poezio_logo.svg2
-rw-r--r--doc/source/configuration.rst4
-rwxr-xr-xlaunch.sh12
-rw-r--r--plugins/reorder.py2
-rw-r--r--poezio/config.py12
-rw-r--r--poezio/contact.py16
-rw-r--r--poezio/core/commands.py2
-rw-r--r--poezio/core/core.py2
-rw-r--r--poezio/core/handlers.py9
-rw-r--r--poezio/fixes.py5
-rw-r--r--poezio/logger.py2
-rw-r--r--poezio/mam.py2
-rw-r--r--poezio/plugin.py2
-rw-r--r--poezio/plugin_e2ee.py18
-rw-r--r--poezio/roster.py4
-rw-r--r--poezio/size_manager.py12
-rw-r--r--poezio/tabs/basetabs.py8
-rw-r--r--poezio/tabs/conversationtab.py4
-rw-r--r--poezio/tabs/muctab.py2
-rw-r--r--poezio/tabs/privatetab.py6
-rw-r--r--poezio/tabs/rostertab.py4
-rwxr-xr-xpoezio/theming.py3
-rw-r--r--poezio/windows/image.py5
-rw-r--r--poezio/windows/roster_win.py2
27 files changed, 99 insertions, 74 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a908b86a..e8bd5415 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -79,7 +79,21 @@ pytest-3.9:
pytest-3.10:
stage: test
- image: python:3.10-rc
+ image: python:3.10
+ script:
+ - apt-get update && apt-get install -y libidn11-dev
+ - git clone https://lab.louiz.org/poezio/slixmpp.git
+ - pip3 install pytest pyasn1-modules cffi --upgrade
+ - cd slixmpp
+ - python3 setup.py install
+ - cd ..
+ - python3 setup.py install
+ - py.test -v test/
+
+pytest-3.11:
+ stage: test
+ image: python:3.11-rc
+ allow_failure: true
script:
- apt-get update && apt-get install -y libidn11-dev
- git clone https://lab.louiz.org/poezio/slixmpp.git
@@ -102,10 +116,19 @@ pylint-plugins:
- python3 setup.py install
- pylint -E plugins
-mypy:
+mypy-fixed:
stage: lint
image: python:3
script:
+ - pip3 install mypy==0.971 types-setuptools
+ - mypy --ignore-missing-imports ./poezio
+ - mypy --ignore-missing-imports ./plugins
+
+mypy-latest:
+ stage: lint
+ image: python:3
+ allow_failure: true
+ script:
- pip3 install mypy types-setuptools
- mypy --ignore-missing-imports ./poezio
- mypy --ignore-missing-imports ./plugins
diff --git a/data/default_config.cfg b/data/default_config.cfg
index d91ff36a..8e926c0e 100644
--- a/data/default_config.cfg
+++ b/data/default_config.cfg
@@ -84,8 +84,8 @@ certificate =
# keep the same for obvious reasons, so this is a good option if your server
# does this, rather than skipping all verifications.
# This is not affected by ignore_certificate
-# Poezio attempts to guess this value automatically. Set to override this
-# behaviour, to the empty string for example, or to another path.
+# Poezio attempts to guess this value automatically if empty. To override this
+# behaviour, set the value to another path.
#ca_cert_path =
# Auto-reconnects you when you get disconnected from the server
diff --git a/data/doap.xml b/data/doap.xml
index b9a42798..6a1330b7 100644
--- a/data/doap.xml
+++ b/data/doap.xml
@@ -20,7 +20,7 @@
<developer-forum rdf:resource="xmpp:poezio@muc.poez.io?join"/>
<support-forum rdf:resource="xmpp:poezio@muc.poez.io?join"/>
- <license rdf:resource="https://git.poez.io/poezio/plain/COPYING"/>
+ <license rdf:resource="https://lab.louiz.org/poezio/poezio/-/raw/main/COPYING"/>
<!-- See https://github.com/ewilderj/doap/issues/49 -->
<language>en</language>
diff --git a/data/poezio_logo.svg b/data/poezio_logo.svg
index 7848f8c6..30a93907 100644
--- a/data/poezio_logo.svg
+++ b/data/poezio_logo.svg
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<svg width="162.31mm" height="155.71mm" version="1.1" viewBox="0 0 162.31 155.71" xmlns="http://www.w3.org/2000/svg">
+<svg version="1.1" viewBox="0 -3.3 162.31 162.31" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="a" x1="-236.83" x2="-194.71" y1="320.77" y2="225.75" gradientTransform="matrix(3.7795 0 0 3.7795 -851.06 -1965.2)" gradientUnits="userSpaceOnUse">
<stop stop-color="#c6b8a3" offset="0"/>
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index fe4d06cd..c28f38fa 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -10,8 +10,8 @@ or use the :term:`/set` command to edit some of its values directly from poezio.
This file is also used to configure key bindings, but this is explained
in the :ref:`keys-page` documentation file.
-That file is read at each startup and the configuration is saved when poezio
-is closed.
+The configuration is read at each startup or when the `/reload` command is
+issued, and it is updated after every `/set` command.
This configuration file **requires** all global options to be in a section
named [Poezio]. Some other options can be in optional sections and will
diff --git a/launch.sh b/launch.sh
index 94283109..b9d59cb5 100755
--- a/launch.sh
+++ b/launch.sh
@@ -1,20 +1,20 @@
#!/bin/sh
-cd $(dirname "$(readlink -f "$0")")
+proj_dir=$(dirname "$(readlink -f "$0")")
if [ -z "$POEZIO_VENV" ]
then
POEZIO_VENV="poezio-venv"
fi
-if [ -e .git ]
+if [ -e "$proj_dir/.git" ]
then
- args=$(git show --format='%h %ci' | head -n1)
+ args=$(git -C "$proj_dir" show --format='%h %ci' | head -n1)
else
args="0.14-dev"
fi
-if [ -e "$POEZIO_VENV" ]
+if [ -e "$proj_dir/$POEZIO_VENV" ]
then
- PYTHON3="$POEZIO_VENV/bin/python3"
+ PYTHON3="$proj_dir/$POEZIO_VENV/bin/python3"
else
echo ""
echo "WARNING: Not using the up-to-date launch format"
@@ -25,5 +25,5 @@ else
fi
$PYTHON3 -c 'import sys;(print("Python 3.7 or newer is required") and exit(1)) if sys.version_info < (3, 7) else exit(0)' || exit 1
-exec "$PYTHON3" -m poezio --custom-version "$args" "$@"
+PYTHONPATH="$proj_dir:$PYTHONPATH" exec "$PYTHON3" -m poezio --custom-version "$args" "$@"
diff --git a/plugins/reorder.py b/plugins/reorder.py
index 5769d560..158b89bb 100644
--- a/plugins/reorder.py
+++ b/plugins/reorder.py
@@ -118,7 +118,7 @@ def parse_runtime_tablist(tablist):
i += 1
result = check_tab(tab)
# Don't serialize gap tabs as they're recreated automatically
- if result != 'empty' and isinstance(tab, TEXT_TO_TAB.values()):
+ if result != 'empty' and isinstance(tab, tuple(TEXT_TO_TAB.values())):
props.append((i, '%s:%s' % (result, tab.jid.full)))
return props
diff --git a/poezio/config.py b/poezio/config.py
index dbbed5ba..4eb43cad 100644
--- a/poezio/config.py
+++ b/poezio/config.py
@@ -30,16 +30,6 @@ ConfigDict = Dict[str, Dict[str, ConfigValue]]
USE_DEFAULT_SECTION = '__DEFAULT SECTION PLACEHOLDER__'
-CA_CERT_DEFAULT_PATHS = {
- '/etc/ssl/cert.pem',
- '/etc/ssl/certs/ca-certificates.crt',
- '/etc/ssl/certs/ca-bundle.crt',
- '/etc/pki/tls/certs/ca-bundle.crt',
- '/etc/ssl/certs/ca-certificates.crt',
- '/etc/ca-certificates/extracted/tls-ca-bundle.pem',
- '/etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt',
-}
-
DEFAULT_CONFIG: ConfigDict = {
'Poezio': {
'ack_message_receipts': True,
@@ -51,7 +41,7 @@ DEFAULT_CONFIG: ConfigDict = {
'autorejoin_delay': '5',
'autorejoin': False,
'beep_on': 'highlight private invite disconnect',
- 'ca_cert_path': ':'.join(CA_CERT_DEFAULT_PATHS),
+ 'ca_cert_path': '',
'certificate': '',
'certfile': '',
'ciphers': 'HIGH+kEDH:HIGH+kEECDH:HIGH:!PSK:!SRP:!3DES:!aNULL',
diff --git a/poezio/contact.py b/poezio/contact.py
index 09948086..90f34c7e 100644
--- a/poezio/contact.py
+++ b/poezio/contact.py
@@ -21,6 +21,7 @@ from typing import (
)
from slixmpp import InvalidJID, JID
+from slixmpp.roster import RosterItem
log = logging.getLogger(__name__)
@@ -74,7 +75,7 @@ class Contact:
to get the resource with the highest priority, etc
"""
- def __init__(self, item):
+ def __init__(self, item: RosterItem):
"""
item: a slixmpp RosterItem pointing to that contact
"""
@@ -96,7 +97,7 @@ class Contact:
return self.__item.jid
@property
- def name(self):
+ def name(self) -> str:
"""The name of the contact or an empty string."""
return self.__item['name'] or self._name or ''
@@ -106,26 +107,27 @@ class Contact:
self._name = value
@property
- def ask(self):
+ def ask(self) -> Optional[str]:
if self.__item['pending_out']:
return 'asked'
+ return None
@property
- def pending_in(self):
+ def pending_in(self) -> bool:
"""We received a subscribe stanza from this contact."""
return self.__item['pending_in']
@pending_in.setter
- def pending_in(self, value):
+ def pending_in(self, value: bool):
self.__item['pending_in'] = value
@property
- def pending_out(self):
+ def pending_out(self) -> bool:
"""We sent a subscribe stanza to this contact."""
return self.__item['pending_out']
@pending_out.setter
- def pending_out(self, value):
+ def pending_out(self, value: bool):
self.__item['pending_out'] = value
@property
diff --git a/poezio/core/commands.py b/poezio/core/commands.py
index f4662021..fe91ca67 100644
--- a/poezio/core/commands.py
+++ b/poezio/core/commands.py
@@ -984,7 +984,7 @@ class CommandCore:
bare = JID(jid).bare
except InvalidJID:
return self.core.information('Invalid JID for /impromptu: %s' % args[0], 'Error')
- jids.add(bare)
+ jids.add(JID(bare))
asyncio.create_task(self.core.impromptu(jids))
diff --git a/poezio/core/core.py b/poezio/core/core.py
index 7690e769..6582402d 100644
--- a/poezio/core/core.py
+++ b/poezio/core/core.py
@@ -276,7 +276,7 @@ class Core:
self.key_func.try_execute = self.try_execute
# Add handlers
- xmpp_event_handlers = [
+ xmpp_event_handlers: List[Tuple[str, Callable[..., Any]]] = [
('attention', self.handler.on_attention),
('carbon_received', self.handler.on_carbon_received),
('carbon_sent', self.handler.on_carbon_sent),
diff --git a/poezio/core/handlers.py b/poezio/core/handlers.py
index a75ecc52..e92e4aac 100644
--- a/poezio/core/handlers.py
+++ b/poezio/core/handlers.py
@@ -1180,25 +1180,26 @@ class HandlerCore:
We are sending a new stanza, write it in the xml buffer if needed.
"""
if self.core.xml_tab:
+ stanza_str = str(stanza)
if PYGMENTS:
- xhtml_text = highlight(str(stanza), LEXER, FORMATTER)
+ xhtml_text = highlight(stanza_str, LEXER, FORMATTER)
poezio_colored = xhtml.xhtml_to_poezio_colors(
xhtml_text, force=True).rstrip('\x19o').strip()
else:
- poezio_colored = str(stanza)
+ poezio_colored = stanza_str
self.core.xml_buffer.add_message(
XMLLog(txt=poezio_colored, incoming=False),
)
try:
if self.core.xml_tab.match_stanza(
- ElementBase(ET.fromstring(stanza))):
+ ElementBase(ET.fromstring(stanza_str))):
self.core.xml_tab.filtered_buffer.add_message(
XMLLog(txt=poezio_colored, incoming=False),
)
except:
# Most of the time what gets logged is whitespace pings. Skip.
# And also skip tab updates.
- if stanza.strip() == '':
+ if stanza_str.strip() == '':
return None
log.debug('', exc_info=True)
diff --git a/poezio/fixes.py b/poezio/fixes.py
index 7383154f..c2db4332 100644
--- a/poezio/fixes.py
+++ b/poezio/fixes.py
@@ -5,14 +5,15 @@ upstream.
TODO: Check that they are fixed and remove those hacks
"""
-from slixmpp import ClientXMPP, Message
+from slixmpp import Message
+from slixmpp.plugins.xep_0184 import XEP_0184
import logging
log = logging.getLogger(__name__)
-def _filter_add_receipt_request(self: ClientXMPP, stanza):
+def _filter_add_receipt_request(self: XEP_0184, stanza):
"""
Auto add receipt requests to outgoing messages, if:
diff --git a/poezio/logger.py b/poezio/logger.py
index 882a34df..29eaad32 100644
--- a/poezio/logger.py
+++ b/poezio/logger.py
@@ -184,7 +184,7 @@ class Logger:
self._check_and_create_log_dir(room)
log.debug('Log handle for %s re-created', room)
- def _check_and_create_log_dir(self, jid: str,
+ def _check_and_create_log_dir(self, jid: Union[str, JID],
open_fd: bool = True) -> Optional[IO[str]]:
"""
Check that the directory where we want to log the messages
diff --git a/poezio/mam.py b/poezio/mam.py
index 59544b16..7cb1d369 100644
--- a/poezio/mam.py
+++ b/poezio/mam.py
@@ -130,7 +130,7 @@ def _parse_message(msg: SMessage) -> Dict:
}
-def _ignore_private_message(stanza: SMessage, filter_jid: JID) -> bool:
+def _ignore_private_message(stanza: SMessage, filter_jid: Optional[JID]) -> bool:
"""Returns True if a MUC-PM should be ignored, as prosody returns
all PMs within the same room.
"""
diff --git a/poezio/plugin.py b/poezio/plugin.py
index 9101c6bb..f38e47e2 100644
--- a/poezio/plugin.py
+++ b/poezio/plugin.py
@@ -426,7 +426,7 @@ class BasePlugin(object, metaclass=SafetyMetaclass):
self.init()
@property
- def name(self):
+ def name(self) -> str:
"""
Get the name (module name) of the plugin.
"""
diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py
index 9b6a9fe4..49f7b067 100644
--- a/poezio/plugin_e2ee.py
+++ b/poezio/plugin_e2ee.py
@@ -234,7 +234,8 @@ class E2EEPlugin(BasePlugin):
for section in config.sections():
value = config.getstr('encryption', section=section)
if value and value == self.encryption_short_name:
- self._enabled_tabs[section] = self.encrypt
+ section_jid = JID(section)
+ self._enabled_tabs[section_jid] = self.encrypt
def cleanup(self):
ConversationTab.remove_information_element(self.encryption_short_name)
@@ -278,7 +279,7 @@ class E2EEPlugin(BasePlugin):
)
@staticmethod
- def format_fingerprint(fingerprint: str, theme: Theme) -> str:
+ def format_fingerprint(fingerprint: str, own: bool, theme: Theme) -> str:
return fingerprint
async def _show_fingerprints(self, jid: JID) -> None:
@@ -286,20 +287,21 @@ class E2EEPlugin(BasePlugin):
theme = get_theme()
fprs = await self.get_fingerprints(jid)
if len(fprs) == 1:
- fingerprint = self.format_fingerprint(fprs[0], theme)
+ fp, own = fprs[0]
+ fingerprint = self.format_fingerprint(fp, own, theme)
self.api.information(
f'Fingerprint for {jid}:\n{fingerprint}',
'Info',
)
elif fprs:
- fmt_fprs = map(lambda fp: self.format_fingerprint(fp, theme), fprs)
+ fmt_fprs = map(lambda fp: self.format_fingerprint(fp[0], fp[1], theme), fprs)
self.api.information(
'Fingerprints for %s:\n%s' % (jid, '\n\n'.join(fmt_fprs)),
'Info',
)
else:
self.api.information(
- 'No fingerprints to display',
+ f'{jid}: No fingerprints to display',
'Info',
)
@@ -308,6 +310,8 @@ class E2EEPlugin(BasePlugin):
tab = self.api.current_tab()
if not args and isinstance(tab, self.supported_tab_types):
jid = tab.jid
+ if isinstance(tab, MucTab):
+ jid = self.core.xmpp.boundjid.bare
elif not args and isinstance(tab, RosterInfoTab):
# Allow running the command without arguments in roster tab
jid = self.core.xmpp.boundjid.bare
@@ -489,7 +493,7 @@ class E2EEPlugin(BasePlugin):
await func(message, jid, tab, passthrough=True) # type: ignore
else:
# pylint: disable=unexpected-keyword-arg
- func(message, jid, tab)
+ func(message, jid, tab) # type: ignore
log.debug('Decrypted %s message: %r', self.encryption_name, message['body'])
return None
@@ -671,7 +675,7 @@ class E2EEPlugin(BasePlugin):
raise NotImplementedError
- async def get_fingerprints(self, jid: JID) -> List[str]:
+ async def get_fingerprints(self, jid: JID) -> List[Tuple[str, bool]]:
"""Show fingerprint(s) for this encryption method and JID.
To overload in plugins.
diff --git a/poezio/roster.py b/poezio/roster.py
index 20f5529d..a52ea23e 100644
--- a/poezio/roster.py
+++ b/poezio/roster.py
@@ -71,7 +71,7 @@ class Roster:
self.last_modified = datetime.now()
@property
- def needs_rebuild(self):
+ def needs_rebuild(self) -> bool:
return self.last_modified >= self.last_built
def __getitem__(self, key):
@@ -133,7 +133,7 @@ class Roster:
return False
@property
- def jid(self):
+ def jid(self) -> JID:
"""Our JID"""
return self.__node.jid
diff --git a/poezio/size_manager.py b/poezio/size_manager.py
index 3e80c357..c5312c9f 100644
--- a/poezio/size_manager.py
+++ b/poezio/size_manager.py
@@ -18,21 +18,25 @@ class SizeManager:
self._core = core
@property
- def tab_degrade_x(self):
+ def tab_degrade_x(self) -> bool:
+ if base_wins.TAB_WIN is None:
+ raise ValueError
_, x = base_wins.TAB_WIN.getmaxyx()
return x < THRESHOLD_WIDTH_DEGRADE
@property
- def tab_degrade_y(self):
+ def tab_degrade_y(self) -> bool:
+ if base_wins.TAB_WIN is None:
+ raise ValueError
y, x = base_wins.TAB_WIN.getmaxyx()
return y < THRESHOLD_HEIGHT_DEGRADE
@property
- def core_degrade_x(self):
+ def core_degrade_x(self) -> bool:
y, x = self._core.stdscr.getmaxyx()
return x < FULL_WIDTH_DEGRADE
@property
- def core_degrade_y(self):
+ def core_degrade_y(self) -> bool:
y, x = self._core.stdscr.getmaxyx()
return y < FULL_HEIGHT_DEGRADE
diff --git a/poezio/tabs/basetabs.py b/poezio/tabs/basetabs.py
index de88a6d1..793eae62 100644
--- a/poezio/tabs/basetabs.py
+++ b/poezio/tabs/basetabs.py
@@ -170,15 +170,15 @@ class Tab:
return 1
@property
- def info_win(self):
+ def info_win(self) -> windows.TextWin:
return self.core.information_win
@property
- def color(self):
+ def color(self) -> Union[Tuple[int, int], Tuple[int, int, 'str']]:
return STATE_COLORS[self._state]()
@property
- def vertical_color(self):
+ def vertical_color(self) -> Union[Tuple[int, int], Tuple[int, int, 'str']]:
return VERTICAL_STATE_COLORS[self._state]()
@property
@@ -492,7 +492,7 @@ class GapTab(Tab):
return 0
@property
- def name(self):
+ def name(self) -> str:
return ''
def refresh(self):
diff --git a/poezio/tabs/conversationtab.py b/poezio/tabs/conversationtab.py
index f6b5708b..de1f988a 100644
--- a/poezio/tabs/conversationtab.py
+++ b/poezio/tabs/conversationtab.py
@@ -83,8 +83,8 @@ class ConversationTab(OneToOneTab):
self.update_keys()
@property
- def general_jid(self):
- return self.jid.bare
+ def general_jid(self) -> JID:
+ return JID(self.jid.bare)
def get_info_header(self):
raise NotImplementedError
diff --git a/poezio/tabs/muctab.py b/poezio/tabs/muctab.py
index e2c27ab1..e2d546c9 100644
--- a/poezio/tabs/muctab.py
+++ b/poezio/tabs/muctab.py
@@ -556,7 +556,7 @@ class MucTab(ChatTab):
"""
replaced_id = message.message['replace']['id']
if replaced_id != '' and config.get_by_tabname(
- 'group_corrections', message.room_from):
+ 'group_corrections', JID(message.room_from)):
try:
delayed_date = message.date or datetime.now()
modify_hl = self.modify_message(
diff --git a/poezio/tabs/privatetab.py b/poezio/tabs/privatetab.py
index b6c06f2a..1909e3c1 100644
--- a/poezio/tabs/privatetab.py
+++ b/poezio/tabs/privatetab.py
@@ -85,14 +85,14 @@ class PrivateTab(OneToOneTab):
return super().remote_user_color()
@property
- def general_jid(self):
+ def general_jid(self) -> JID:
return self.jid
- def get_dest_jid(self):
+ def get_dest_jid(self) -> JID:
return self.jid
@property
- def nick(self):
+ def nick(self) -> str:
return self.get_nick()
def ack_message(self, msg_id: str, msg_jid: JID):
diff --git a/poezio/tabs/rostertab.py b/poezio/tabs/rostertab.py
index 66aff2b1..18334c20 100644
--- a/poezio/tabs/rostertab.py
+++ b/poezio/tabs/rostertab.py
@@ -14,7 +14,7 @@ import ssl
from functools import partial
from os import getenv, path
from pathlib import Path
-from typing import Dict, Callable
+from typing import Dict, Callable, Union
from slixmpp import JID, InvalidJID
from slixmpp.exceptions import IqError, IqTimeout
@@ -199,7 +199,7 @@ class RosterInfoTab(Tab):
completion=self.completion_cert_fetch)
@property
- def selected_row(self):
+ def selected_row(self) -> Union[Contact, Resource]:
return self.roster_win.get_selected_row()
@command_args_parser.ignored
diff --git a/poezio/theming.py b/poezio/theming.py
index 712a44ab..187d07c5 100755
--- a/poezio/theming.py
+++ b/poezio/theming.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python3
# Copyright 2010-2011 Florent Le Coz <louiz@louiz.org>
#
# This file is part of Poezio.
@@ -391,7 +392,7 @@ class Theme:
}
@property
- def ccg_palette(self):
+ def ccg_palette(self) -> Optional[Dict[float, int]]:
prepare_ccolor_palette(self)
return self.CCG_PALETTE
diff --git a/poezio/windows/image.py b/poezio/windows/image.py
index b721b859..2862d2d9 100644
--- a/poezio/windows/image.py
+++ b/poezio/windows/image.py
@@ -2,6 +2,8 @@
Defines a window which contains either an image or a border.
"""
+from __future__ import annotations
+
import curses
from io import BytesIO
@@ -9,9 +11,6 @@ try:
from PIL import Image
HAS_PIL = True
except ImportError:
- class Image: # type: ignore
- class Image:
- pass
HAS_PIL = False
try:
diff --git a/poezio/windows/roster_win.py b/poezio/windows/roster_win.py
index eed68429..dfdc9b9b 100644
--- a/poezio/windows/roster_win.py
+++ b/poezio/windows/roster_win.py
@@ -278,7 +278,7 @@ class RosterWin(Win):
elif contact.name and contact.name != contact.bare_jid:
display_name = '%s (%s)' % (contact.name, contact.bare_jid)
else:
- display_name = contact.bare_jid
+ display_name = str(contact.bare_jid)
display_name = self.truncate_name(display_name, added) + nb