Switch to Sleekxmpp. fixed #1768
This commit is contained in:
parent
d2fef9112d
commit
e84b23d1ad
8 changed files with 410 additions and 795 deletions
|
@ -47,8 +47,6 @@ import errno
|
|||
import time
|
||||
import traceback
|
||||
|
||||
import xmpp
|
||||
|
||||
ROOM_STATE_NONE = 11
|
||||
ROOM_STATE_CURRENT = 10
|
||||
ROOM_STATE_PRIVATE = 15
|
||||
|
@ -120,20 +118,11 @@ def is_in_path(command, return_abs_path=False):
|
|||
pass
|
||||
return False
|
||||
|
||||
def get_stripped_jid(jid):
|
||||
"""
|
||||
Return the stripped JID (bare representation)
|
||||
nick@server/resource -> nick@server
|
||||
"""
|
||||
if isinstance(jid, basestring):
|
||||
jid = xmpp.JID(jid)
|
||||
return jid.getStripped()
|
||||
|
||||
def is_jid(jid):
|
||||
"""
|
||||
Return True if this is a valid JID
|
||||
"""
|
||||
if xmpp.JID(jid).getNode() != '':
|
||||
if jid.find('@') != -1:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -141,35 +130,34 @@ def jid_get_node(jid):
|
|||
"""
|
||||
nick@server/resource -> nick
|
||||
"""
|
||||
if isinstance(jid, basestring):
|
||||
jid = xmpp.JID(jid)
|
||||
return jid.getNode()
|
||||
return jid.split('@', 1)[0]
|
||||
|
||||
def jid_get_domain(jid):
|
||||
"""
|
||||
nick@server/resource -> server
|
||||
"""
|
||||
if isinstance(jid, basestring):
|
||||
jid = xmpp.JID(jid)
|
||||
return jid.getDomain()
|
||||
return jid.split('@',1)[-1].split('/', 1)[0]
|
||||
|
||||
def jid_get_resource(jid):
|
||||
def jid_get_resource(fulljid):
|
||||
"""
|
||||
nick@server/resource -> resource
|
||||
"""
|
||||
if isinstance(jid, basestring):
|
||||
jid = xmpp.JID(jid)
|
||||
return jid.getResource()
|
||||
if '/' in fulljid:
|
||||
return fulljid.split('/', 1)[-1]
|
||||
else:
|
||||
return ''
|
||||
|
||||
def jid_get_bare(fulljid):
|
||||
"""
|
||||
nick@server/resource -> nick@server
|
||||
"""
|
||||
return '%s@%s' % (jid_get_domain(fulljid), jid_get_node(fulljid))
|
||||
|
||||
def is_jid_the_same(a, b):
|
||||
"""
|
||||
Compare two bare jids
|
||||
"""
|
||||
if isinstance(a, basestring):
|
||||
a = xmpp.JID(a)
|
||||
if isinstance(b, basestring):
|
||||
b = xmpp.JID(b)
|
||||
return a.bareMatch(b)
|
||||
return jid_get_bare(a) == jid_get_bare(a)
|
||||
|
||||
DISTRO_INFO = {
|
||||
'Arch Linux': '/etc/arch-release',
|
||||
|
|
|
@ -24,205 +24,27 @@ from gettext import (bindtextdomain, textdomain, bind_textdomain_codeset,
|
|||
gettext as _)
|
||||
|
||||
import sys
|
||||
import threading
|
||||
import sleekxmpp
|
||||
|
||||
import xmpp
|
||||
from config import config
|
||||
from logging import logger
|
||||
from logger import logger
|
||||
from handler import Handler
|
||||
from common import jid_get_node, jid_get_domain, is_jid_the_same
|
||||
|
||||
class Connection(threading.Thread):
|
||||
import logging
|
||||
|
||||
class Connection(sleekxmpp.ClientXMPP):
|
||||
"""
|
||||
Receives everything from Jabber and emits the
|
||||
appropriate signals
|
||||
"""
|
||||
def __init__(self, server, resource):
|
||||
threading.Thread.__init__(self)
|
||||
self.handler = Handler()
|
||||
self.daemon = True # exit the program when this thread exits
|
||||
if config.get('jid', '') == '':
|
||||
self.server = server
|
||||
else:
|
||||
self.server = jid_get_domain(config.get('jid', ''))
|
||||
self.resource = resource
|
||||
self.online = 0 # 1:connected, 2:auth confirmed
|
||||
self.jid = '' # we don't know our jid yet (anon account)
|
||||
self.port = config.get('port', 5222)
|
||||
self.client = xmpp.Client(self.server, debug=[])
|
||||
def __init__(self):
|
||||
sleekxmpp.ClientXMPP.__init__(self, None, None, ssl=True,
|
||||
resource=config.get('resource', 'poezio'))
|
||||
self.registerPlugin('xep_0045')
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
run in a thread
|
||||
connect to server
|
||||
"""
|
||||
if not self.connect_to_server(self.server, self.port):
|
||||
self.handler.emit('error', msg='Could not connect to server')
|
||||
sys.exit(-1)
|
||||
if not self.authenticate(config.get('jid', '') == ''):
|
||||
self.handler.emit('error', msg='Could not authenticate to server')
|
||||
sys.exit(-1)
|
||||
# TODO, become invisible before sendInitPresence
|
||||
self.client.sendInitPresence(requestRoster=0)
|
||||
self.register_handlers()
|
||||
|
||||
self.online = 1 # 2 when confirmation of our auth is received
|
||||
while 1:
|
||||
self.process()
|
||||
|
||||
def connect_to_server(self, server, port):
|
||||
"""
|
||||
Connect to the server
|
||||
"""
|
||||
if config.get('use_proxy','false') == 'true':
|
||||
return self.client.connect((server, port),
|
||||
{'host': config.get("proxy_server", ""),
|
||||
'port': config.get("proxy_port", 1080),
|
||||
'user': config.get("proxy_user", ""),
|
||||
'password': config.get("proxy_password",
|
||||
"")
|
||||
})
|
||||
else:
|
||||
return self.client.connect((server, port))
|
||||
|
||||
def authenticate(self, anon=True):
|
||||
"""
|
||||
Authenticate to the server
|
||||
"""
|
||||
if anon:
|
||||
try:
|
||||
self.client.auth(None, "", self.resource)
|
||||
return True
|
||||
except TypeError:
|
||||
self.handler.emit('error', msg=_('Error: Could not authenticate. Please make sure the server you chose (%s) supports anonymous authentication' % (config.get('server', ''))))
|
||||
return False
|
||||
else:
|
||||
password = config.get('password', '')
|
||||
jid = config.get('jid', '')
|
||||
auth = self.client.auth(jid_get_node(jid), password, "salut")
|
||||
return True
|
||||
|
||||
def register_handlers(self):
|
||||
"""
|
||||
registers handlers from xmpppy signals
|
||||
"""
|
||||
self.client.RegisterHandler('iq', self.on_get_time, typ='get',
|
||||
ns="urn:xmpp:time")
|
||||
self.client.RegisterHandler('iq', self.on_get_vcard)
|
||||
self.client.RegisterHandler('iq', self.on_get_version, typ='get',
|
||||
ns=xmpp.NS_VERSION)
|
||||
self.client.RegisterHandler('presence', self.handler_presence)
|
||||
self.client.RegisterHandler('message', self.handler_message)
|
||||
|
||||
def error_message(self, stanza):
|
||||
"""
|
||||
handles the error messages
|
||||
"""
|
||||
from_ = stanza.getFrom()
|
||||
if not from_:
|
||||
room_name = ''
|
||||
else:
|
||||
room_name = from_.getStripped()
|
||||
self.handler.emit('error-message', room=room_name,
|
||||
error=stanza.getTag('error'),
|
||||
msg=stanza.getError())
|
||||
raise xmpp.protocol.NodeProcessed
|
||||
|
||||
def handler_presence(self, connection, presence):
|
||||
"""
|
||||
check if it's a normal or a muc presence
|
||||
"""
|
||||
is_muc = False
|
||||
tags = presence.getTags('x')
|
||||
for tag in tags:
|
||||
if tag.getAttr('xmlns') == 'http://jabber.org/protocol/muc#user':
|
||||
is_muc = True
|
||||
if is_muc:
|
||||
self.handler_muc_presence(connection, presence)
|
||||
else:
|
||||
self.handler_normal_presence(connection, presence)
|
||||
|
||||
def handler_normal_presence(self, connection, presence):
|
||||
"""
|
||||
handles the non-MUC presences
|
||||
"""
|
||||
fro = presence.getFrom()
|
||||
toj = presence.getAttr('to')
|
||||
if presence.getType() == 'error':
|
||||
self.error_message(presence)
|
||||
return
|
||||
if not toj or fro == toj: # own presence
|
||||
self.online = 2
|
||||
self.jid = toj
|
||||
self.handler.emit('on-connected', jid=fro)
|
||||
|
||||
def handler_muc_presence(self, connection, presence):
|
||||
"""
|
||||
handles the presence messages
|
||||
"""
|
||||
if not connection:
|
||||
return
|
||||
self.handler.emit('room-presence', stanza=presence)
|
||||
raise xmpp.protocol.NodeProcessed
|
||||
|
||||
def handler_delayed_message(self, connection, message):
|
||||
"""
|
||||
handles the delayed messages
|
||||
These are received when we join a muc and we are sent the
|
||||
recent history
|
||||
"""
|
||||
if not connection:
|
||||
return
|
||||
self.handler.emit('room-delayed-message', stanza=message)
|
||||
raise xmpp.protocol.NodeProcessed
|
||||
|
||||
def handler_message(self, connection, message):
|
||||
"""
|
||||
handles the common messages
|
||||
"""
|
||||
if not connection:
|
||||
return
|
||||
if message.getType() == 'error':
|
||||
self.error_message(message)
|
||||
return
|
||||
if message.getType() == 'groupchat':
|
||||
self.handler.emit('room-message', stanza=message)
|
||||
else:
|
||||
self.handler.emit('private-message', stanza=message)
|
||||
|
||||
raise xmpp.protocol.NodeProcessed
|
||||
|
||||
def process(self, timeout=10):
|
||||
"""
|
||||
Main connection loop
|
||||
It just waits for something to process (something is received
|
||||
or something has to be sent)
|
||||
"""
|
||||
if self.online:
|
||||
self.client.Process(timeout)
|
||||
else:
|
||||
logger.warning('disconnecting...')
|
||||
sys.exit()
|
||||
|
||||
def on_get_version(self, connection, iq):
|
||||
"""
|
||||
Handles the iq requesting our software version
|
||||
"""
|
||||
if not connection:
|
||||
return
|
||||
self.handler.emit('send-version', iq_obj=iq)
|
||||
|
||||
def on_get_time(self, connection, iq):
|
||||
"""
|
||||
handles the iq requesting our time
|
||||
"""
|
||||
if not connection:
|
||||
return
|
||||
self.handler.emit('send-time', iq_obj=iq)
|
||||
|
||||
def on_get_vcard(self, connection, iq):
|
||||
"""
|
||||
we received a vcard
|
||||
"""
|
||||
from common import debug
|
||||
debug('\n====\n%s\n\n' % iq)
|
||||
def start(self):
|
||||
# TODO, try multiple servers
|
||||
if self.connect((config.get('server', 'anon.louiz.org'),
|
||||
config.get('port', 5222))):
|
||||
self.process(threaded=True)
|
||||
|
|
522
src/gui.py
522
src/gui.py
|
@ -33,6 +33,7 @@ from datetime import datetime
|
|||
import common
|
||||
import theme
|
||||
|
||||
import multiuserchat as muc
|
||||
from handler import Handler
|
||||
from config import config
|
||||
from window import Window
|
||||
|
@ -40,7 +41,7 @@ from user import User
|
|||
from room import Room
|
||||
from message import Message
|
||||
from keyboard import read_char
|
||||
from common import is_jid_the_same, jid_get_domain, is_jid
|
||||
from common import is_jid_the_same, jid_get_domain, jid_get_resource, is_jid
|
||||
|
||||
# http://xmpp.org/extensions/xep-0045.html#errorstatus
|
||||
ERROR_AND_STATUS_CODES = {
|
||||
|
@ -61,15 +62,14 @@ class Gui(object):
|
|||
"""
|
||||
User interface using ncurses
|
||||
"""
|
||||
def __init__(self, stdscr=None, muc=None):
|
||||
self.init_curses(stdscr)
|
||||
self.stdscr = stdscr
|
||||
self.window = Window(stdscr)
|
||||
def __init__(self, xmpp):
|
||||
self.stdscr = curses.initscr()
|
||||
self.init_curses(self.stdscr)
|
||||
self.xmpp = xmpp
|
||||
self.window = Window(self.stdscr)
|
||||
self.rooms = [Room('Info', '', self.window)]
|
||||
self.ignores = {}
|
||||
|
||||
self.muc = muc
|
||||
|
||||
self.commands = {
|
||||
'help': (self.command_help, u'\_o< KOIN KOIN KOIN'),
|
||||
'join': (self.command_join, _("Usage: /join [room_name][@server][/nick] [password]\nJoin: Join the specified room. You can specify a nickname after a slash (/). If no nickname is specified, you will use the default_nick in the configuration file. You can omit the room name: you will then join the room you\'re looking at (useful if you were kicked). You can also provide a room_name without specifying a server, the server of the room you're currently in will be used. You can also provide a password to join the room.\nExamples:\n/join room@server.tld\n/join room@server.tld/John\n/join room2\n/join /me_again\n/join\n/join room@server.tld/my_nick password\n/join / password")),
|
||||
|
@ -136,14 +136,230 @@ class Gui(object):
|
|||
'M-b': self.window.input.jump_word_left
|
||||
}
|
||||
|
||||
self.handler = Handler()
|
||||
self.handler.connect('on-connected', self.on_connected)
|
||||
self.handler.connect('join-room', self.join_room)
|
||||
self.handler.connect('room-presence', self.room_presence)
|
||||
self.handler.connect('room-message', self.room_message)
|
||||
self.handler.connect('private-message', self.private_message)
|
||||
self.handler.connect('error-message', self.room_error)
|
||||
self.handler.connect('error', self.information)
|
||||
# Add handlers
|
||||
self.xmpp.add_event_handler("session_start", self.on_connected)
|
||||
self.xmpp.add_event_handler("groupchat_presence", self.on_groupchat_presence)
|
||||
self.xmpp.add_event_handler("groupchat_message", self.on_groupchat_message)
|
||||
self.xmpp.add_event_handler("message", self.on_message)
|
||||
# self.handler = Handler()
|
||||
# self.handler.connect('on-connected', self.on_connected)
|
||||
# self.handler.connect('join-room', self.join_room)
|
||||
# self.handler.connect('room-presence', self.room_presence)
|
||||
# self.handler.connect('room-message', self.room_message)
|
||||
# self.handler.connect('private-message', self.private_message)
|
||||
# self.handler.connect('error-message', self.room_error)
|
||||
# self.handler.connect('error', self.information)
|
||||
|
||||
def on_connected(self, event):
|
||||
"""
|
||||
Called when we are connected and authenticated
|
||||
"""
|
||||
self.information(_("Welcome on Poezio \o/!"))
|
||||
self.information(_("Your JID is %s") % self.xmpp.fulljid)
|
||||
|
||||
rooms = config.get('rooms', '')
|
||||
if rooms == '' or not isinstance(rooms, str):
|
||||
return
|
||||
rooms = rooms.split(':')
|
||||
for room in rooms:
|
||||
args = room.split('/')
|
||||
if args[0] == '':
|
||||
return
|
||||
roomname = args[0]
|
||||
if len(args) == 2:
|
||||
nick = args[1]
|
||||
else:
|
||||
default = os.environ.get('USER') if os.environ.get('USER') else 'poezio'
|
||||
nick = config.get('default_nick', '')
|
||||
if nick == '':
|
||||
nick = default
|
||||
self.open_new_room(roomname, nick)
|
||||
muc.join_groupchat(self.xmpp, roomname, nick)
|
||||
# Todo: SEND VCARD
|
||||
return
|
||||
if config.get('jid', '') == '': # Don't send the vcard if we're not anonymous
|
||||
self.vcard_sender.start() # because the user ALREADY has one on the server
|
||||
|
||||
def on_groupchat_presence(self, presence):
|
||||
"""
|
||||
Triggered whenever a presence stanza is received from a user in a multi-user chat room.
|
||||
Display the presence on the room window and update the
|
||||
presence information of the concerned user
|
||||
"""
|
||||
from_nick = presence['from'].resource
|
||||
from_room = presence['from'].bare
|
||||
room = self.get_room_by_name(from_room)
|
||||
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')])
|
||||
# Check if it's not an error presence.
|
||||
if presence['type'] == 'error':
|
||||
return self.room_error(presence, from_room)
|
||||
if not room:
|
||||
return
|
||||
else:
|
||||
msg = None
|
||||
affiliation = presence['muc']['affiliation']
|
||||
show = presence['muc']['type']
|
||||
status = presence['status']
|
||||
role = presence['muc']['role']
|
||||
jid = presence['muc']['jid']
|
||||
typ = presence['type']
|
||||
if not room.joined: # user in the room BEFORE us.
|
||||
# ignore redondant presence message, see bug #1509
|
||||
if from_nick not in [user.nick for user in room.users]:
|
||||
new_user = User(from_nick, affiliation, show, status, role)
|
||||
room.users.append(new_user)
|
||||
if from_nick.encode('utf-8') == room.own_nick:
|
||||
room.joined = True
|
||||
new_user.color = theme.COLOR_OWN_NICK
|
||||
self.add_message_to_room(room, _("Your nickname is %s") % (from_nick))
|
||||
if '170' in status_codes:
|
||||
self.add_message_to_room(room, 'Warning: this room is publicly logged')
|
||||
else:
|
||||
change_nick = '303' in status_codes
|
||||
kick = '307' in status_codes and typ == 'unavailable'
|
||||
user = room.get_user_by_name(from_nick)
|
||||
# New user
|
||||
if not user:
|
||||
room.users.append(User(from_nick, affiliation,
|
||||
show, status, role))
|
||||
hide_exit_join = config.get('hide_exit_join', -1)
|
||||
if hide_exit_join != 0:
|
||||
if not jid.full:
|
||||
self.add_message_to_room(room, _("%(spec)s [%(nick)s] joined the room") % {'nick':from_nick, 'spec':theme.CHAR_JOIN}, colorized=True)
|
||||
else:
|
||||
self.add_message_to_room(room, _("%(spec)s [%(nick)s] (%(jid)s) joined the room") % {'spec':theme.CHAR_JOIN, 'nick':from_nick, 'jid':jid.full}, colorized=True)
|
||||
# nick change
|
||||
elif change_nick:
|
||||
new_nick = presence.find('{http://jabber.org/protocol/muc#user}x/{http://jabber.org/protocol/muc#user}item').attrib['nick']
|
||||
if user.nick == room.own_nick:
|
||||
room.own_nick = new_nick
|
||||
# also change our nick in all private discussion of this room
|
||||
for _room in self.rooms:
|
||||
if _room.jid is not None and is_jid_the_same(_room.jid, room.name):
|
||||
_room.own_nick = new_nick
|
||||
user.change_nick(new_nick)
|
||||
self.add_message_to_room(room, _('[%(old)s] is now known as [%(new)s]') % {'old':from_nick, 'new':new_nick}, colorized=True)
|
||||
# rename the private tabs if needed
|
||||
private_room = self.get_room_by_name('%s/%s' % (from_room, from_nick))
|
||||
if private_room:
|
||||
self.add_message_to_room(private_room, _('[%(old_nick)s] is now known as [%(new_nick)s]') % {'old_nick':from_nick, 'new_nick':new_nick}, colorized=True)
|
||||
new_jid = private_room.name.split('/')[0]+'/'+new_nick
|
||||
private_room.jid = private_room.name = new_jid
|
||||
|
||||
# kick
|
||||
elif kick:
|
||||
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 = by.attrib['jid'] if by else ''
|
||||
reason = reason.text# if reason else ''
|
||||
if from_nick == room.own_nick: # we are kicked
|
||||
room.disconnect()
|
||||
if by:
|
||||
kick_msg = _("%(spec) [You] have been kicked by [%(by)s].") % {'spec': theme.CHAR_KICK, 'by':by}
|
||||
else:
|
||||
kick_msg = _("%(spec)s [You] have been kicked.") % {'spec':theme.CHAR_KICK}
|
||||
# try to auto-rejoin
|
||||
if config.get('autorejoin', 'false') == 'true':
|
||||
muc.join_groupchat(self.xmpp, room.name, room.own_nick)
|
||||
else:
|
||||
if by:
|
||||
kick_msg = _("%(spec)s [%(nick)s] has been kicked by %(by)s.") % {'spec':theme.CHAR_KICK, 'nick':from_nick, 'by':by}
|
||||
else:
|
||||
kick_msg = _("%(spec)s [%(nick)s] has been kicked") % {'spec':theme.CHAR_KICK, 'nick':from_nick}
|
||||
if reason:
|
||||
kick_msg += _(' Reason: %(reason)s') % {'reason': reason}
|
||||
self.add_message_to_room(room, kick_msg, colorized=True)
|
||||
|
||||
# user quit
|
||||
elif typ == 'unavailable':
|
||||
room.users.remove(user)
|
||||
hide_exit_join = config.get('hide_exit_join', -1) if config.get('hide_exit_join', -1) >= -1 else -1
|
||||
if hide_exit_join == -1 or user.has_talked_since(hide_exit_join):
|
||||
if not jid.full:
|
||||
leave_msg = _('%(spec)s [%(nick)s] has left the room') % {'nick':from_nick, 'spec':theme.CHAR_QUIT}
|
||||
else:
|
||||
leave_msg = _('%(spec)s [%(nick)s] (%(jid)s) has left the room') % {'spec':theme.CHAR_QUIT, 'nick':from_nick, 'jid':jid.full}
|
||||
if status:
|
||||
leave_msg += ' (%s)' % status
|
||||
self.add_message_to_room(room, leave_msg, colorized=True)
|
||||
private_room = self.get_room_by_name('%s/%s' % (from_room, from_nick))
|
||||
if private_room:
|
||||
if not status:
|
||||
self.add_message_to_room(private_room, _('%(spec)s [%(nick)s] has left the room') % {'nick':from_nick, 'spec':theme.CHAR_QUIT}, colorized=True)
|
||||
else:
|
||||
self.add_message_to_room(private_room, _('%(spec)s [%(nick)s] has left the room (%(status)s)') % {'nick':from_nick, 'spec':theme.CHAR_QUIT, 'status': status}, colorized=True)
|
||||
# status change
|
||||
else:
|
||||
# build the message
|
||||
msg = _('%s changed his/her status: ')% from_nick
|
||||
if affiliation != user.affiliation:
|
||||
msg += _('affiliation: %s,') % affiliation
|
||||
if role != user.role:
|
||||
msg += _('role: %s,') % role
|
||||
if show != user.show:
|
||||
msg += _('show: %s,') % show
|
||||
if status != user.status:
|
||||
msg += _('status: %s,') % status
|
||||
msg = msg[:-1] # remove the last ","
|
||||
hide_status_change = config.get('hide_status_change', -1) if config.get('hide_status_change', -1) >= -1 else -1
|
||||
if (hide_status_change == -1 or \
|
||||
user.has_talked_since(hide_status_change) or\
|
||||
user.nick == room.own_nick)\
|
||||
and\
|
||||
(affiliation != user.affiliation or\
|
||||
role != user.role or\
|
||||
show != user.show or\
|
||||
status != user.status):
|
||||
# display the message in the room
|
||||
self.add_message_to_room(room, msg)
|
||||
private_room = self.get_room_by_name(from_room)
|
||||
if private_room: # display the message in private
|
||||
self.add_message_to_room(private_room, msg)
|
||||
# finally, effectively change the user status
|
||||
user.update(affiliation, show, status, role)
|
||||
if room == self.current_room():
|
||||
self.window.user_win.refresh(room.users)
|
||||
self.window.input.refresh()
|
||||
doupdate()
|
||||
|
||||
def on_message(self, message):
|
||||
"""
|
||||
When receiving private message from a muc OR a normal message
|
||||
(from one of our contacts)
|
||||
"""
|
||||
if message['type'] == 'groupchat':
|
||||
return None
|
||||
# Differentiate both type of messages, and call the appropriate handler.
|
||||
jid_from = message['from']
|
||||
for room in self.rooms:
|
||||
if room.jid is None and room.name == jid_from.bare: # check all the MUC we are in
|
||||
return self.on_groupchat_private_message(message)
|
||||
return self.on_normal_message(message)
|
||||
|
||||
def on_groupchat_private_message(self, message):
|
||||
"""
|
||||
We received a Private Message (from someone in a Muc)
|
||||
"""
|
||||
jid = message['from']
|
||||
nick_from = jid.user
|
||||
room_from = jid.server
|
||||
room = self.get_room_by_name(jid.full) # get the tab with the private conversation
|
||||
if not room: # It's the first message we receive: create the tab
|
||||
room = self.open_private_window(room_from, nick_from.encode('utf-8'), False)
|
||||
if not room:
|
||||
return
|
||||
body = message['body']
|
||||
self.add_message_to_room(room, body, None, nick_from)
|
||||
self.window.input.refresh()
|
||||
doupdate()
|
||||
|
||||
def on_normal_message(self, message):
|
||||
"""
|
||||
When receiving "normal" messages (from someone in our roster)
|
||||
"""
|
||||
return
|
||||
|
||||
def resize_window(self):
|
||||
"""
|
||||
|
@ -152,14 +368,14 @@ class Gui(object):
|
|||
self.window.resize(self.stdscr)
|
||||
self.window.refresh(self.rooms)
|
||||
|
||||
def main_loop(self, stdscr):
|
||||
def main_loop(self):
|
||||
"""
|
||||
main loop waiting for the user to press a key
|
||||
"""
|
||||
self.refresh_window()
|
||||
while True:
|
||||
doupdate()
|
||||
char=read_char(stdscr)
|
||||
char=read_char(self.stdscr)
|
||||
try: # if this is not a valide utf-8 char, discard it
|
||||
char.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
|
@ -205,17 +421,11 @@ class Gui(object):
|
|||
Reset terminal capabilities to what they were before ncurses
|
||||
init
|
||||
"""
|
||||
# TODO remove me?
|
||||
curses.echo()
|
||||
curses.nocbreak()
|
||||
curses.endwin()
|
||||
|
||||
def on_connected(self, jid):
|
||||
"""
|
||||
We are connected when authentification confirmation is received
|
||||
"""
|
||||
self.information(_("Welcome on Poezio \o/!"))
|
||||
self.information(_("Your JID is %s") % jid)
|
||||
|
||||
def refresh_window(self):
|
||||
"""
|
||||
Refresh everything
|
||||
|
@ -223,9 +433,9 @@ class Gui(object):
|
|||
self.current_room().set_color_state(theme.COLOR_TAB_CURRENT)
|
||||
self.window.refresh(self.rooms)
|
||||
|
||||
def join_room(self, room, nick):
|
||||
def open_new_room(self, room, nick, focus=True):
|
||||
"""
|
||||
join the specified room (muc), using the specified nick
|
||||
Open a new Tab containing a Muc room, using the specified nick
|
||||
"""
|
||||
r = Room(room, nick, self.window)
|
||||
self.current_room().set_color_state(theme.COLOR_TAB_NORMAL)
|
||||
|
@ -236,7 +446,8 @@ class Gui(object):
|
|||
if ro.nb == 0:
|
||||
self.rooms.insert(self.rooms.index(ro), r)
|
||||
break
|
||||
self.command_win("%s" % r.nb)
|
||||
if focus:
|
||||
self.command_win("%s" % r.nb)
|
||||
self.refresh_window()
|
||||
|
||||
def completion(self):
|
||||
|
@ -322,27 +533,28 @@ class Gui(object):
|
|||
self.current_room().scroll_up(self.window.text_win.height-1)
|
||||
self.refresh_window()
|
||||
|
||||
def room_error(self, room, error, msg):
|
||||
def room_error(self, error, room_name):
|
||||
"""
|
||||
Display the error on the room window
|
||||
"""
|
||||
if not error:
|
||||
return
|
||||
room = self.get_room_by_name(room)
|
||||
room = self.get_room_by_name(room_name)
|
||||
if not room:
|
||||
room = self.get_room_by_name('Info')
|
||||
code = error.getAttr('code')
|
||||
typ = error.getAttr('type')
|
||||
if error.getTag('text'):
|
||||
body = error.getTag('text').getData()
|
||||
else: # No description of the error is provided in the stanza
|
||||
# If it's a standard error, use our own messages
|
||||
msg = error['error']['type']
|
||||
condition = error['error']['condition']
|
||||
code = error['error']['code']
|
||||
body = error['error']['text']
|
||||
if not body:
|
||||
if code in ERROR_AND_STATUS_CODES.keys():
|
||||
body = ERROR_AND_STATUS_CODES[code]
|
||||
else:
|
||||
body = _('Unknown error')
|
||||
self.add_message_to_room(room, _('Error: %(code)s-%(msg)s: %(body)s' %
|
||||
{'msg':msg, 'code':code, 'body':body}))
|
||||
body = condition or _('Unknown error')
|
||||
if code:
|
||||
self.add_message_to_room(room, _('Error: %(code)s - %(msg)s: %(body)s' %
|
||||
{'msg':msg, 'body':body, 'code':code}))
|
||||
else:
|
||||
self.add_message_to_room(room, _('Error: %(msg)s: %(body)s' %
|
||||
{'msg':msg, 'body':body}))
|
||||
if code == '401':
|
||||
self.add_message_to_room(room, _('To provide a password in order to join the room, type "/join / password" (replace "password" by the real password)'))
|
||||
if code == '409':
|
||||
|
@ -352,23 +564,6 @@ class Gui(object):
|
|||
self.add_message_to_room(room, _('You can join the room with an other nick, by typing "/join /other_nick"'))
|
||||
self.refresh_window()
|
||||
|
||||
def private_message(self, stanza):
|
||||
"""
|
||||
When a private message is received
|
||||
"""
|
||||
jid = stanza.getFrom()
|
||||
nick_from = stanza.getFrom().getResource()
|
||||
room_from = stanza.getFrom().getStripped()
|
||||
room = self.get_room_by_name(jid) # get the tab with the private conversation
|
||||
if not room: # It's the first message we receive: create the tab
|
||||
room = self.open_private_window(room_from, nick_from.encode('utf-8'), False)
|
||||
if not room:
|
||||
return
|
||||
body = stanza.getBody()
|
||||
self.add_message_to_room(room, body, None, nick_from)
|
||||
self.window.input.refresh()
|
||||
doupdate()
|
||||
|
||||
def open_private_window(self, room_name, user_nick, focus=True):
|
||||
complete_jid = room_name.decode('utf-8')+'/'+user_nick
|
||||
for room in self.rooms: # if the room exists, focus it and return
|
||||
|
@ -396,40 +591,41 @@ class Gui(object):
|
|||
self.refresh_window()
|
||||
return r
|
||||
|
||||
def room_message(self, stanza, date=None):
|
||||
def on_groupchat_message(self, message):
|
||||
"""
|
||||
Display the message on the room window
|
||||
Triggered whenever a message is received from a multi-user chat room.
|
||||
"""
|
||||
delay_tag = stanza.getTag('delay', namespace='urn:xmpp:delay')
|
||||
if delay_tag:
|
||||
# FIXME: not receiving subjects? :/
|
||||
delay_tag = message.find('{urn:xmpp:delay}delay')
|
||||
if delay_tag is not None:
|
||||
delayed = True
|
||||
date = common.datetime_tuple(delay_tag.getAttr('stamp'))
|
||||
date = common.datetime_tuple(delay_tag.attrib['stamp'])
|
||||
else:
|
||||
# We support the OLD and deprecated XEP: http://xmpp.org/extensions/xep-0091.html
|
||||
# But it sucks, please, Jabber servers, don't do this :(
|
||||
delay_tag = stanza.getTag('x', namespace='jabber:x:delay')
|
||||
if delay_tag:
|
||||
delay_tag = message.find('{jabber:x:delay}x')
|
||||
if delay_tag is not None:
|
||||
delayed = True
|
||||
date = common.datetime_tuple(delay_tag.getAttr('stamp'))
|
||||
date = common.datetime_tuple(delay_tag.attrib['stamp'])
|
||||
else:
|
||||
delayed = False
|
||||
if stanza.getType() != 'groupchat':
|
||||
return # ignore all messages not comming from a MUC
|
||||
nick_from = stanza.getFrom().getResource()
|
||||
room_from = stanza.getFrom().getStripped()
|
||||
date = None
|
||||
nick_from = message['from'].resource
|
||||
room_from = message.getMucroom()
|
||||
room = self.get_room_by_name(room_from)
|
||||
if (self.ignores.has_key(room_from)) and (nick_from in self.ignores[room_from]):
|
||||
return
|
||||
room = self.get_room_by_name(room_from)
|
||||
if not room:
|
||||
self.information(_("message received for a non-existing room: %s") % (room_from))
|
||||
if not room:
|
||||
self.information(_("message received for a non-existing room: %s") % (room_from))
|
||||
return
|
||||
body = stanza.getBody()
|
||||
subject = stanza.getSubject()
|
||||
body = message['body']#stanza.getBody()
|
||||
subject = message['subject']#stanza.getSubject()
|
||||
if subject:
|
||||
if nick_from:
|
||||
self.add_message_to_room(room, _("%(nick)s changed the subject to: %(subject)s") % {'nick':nick_from, 'subject':subject}, date)
|
||||
self.add_message_to_room(room, _("%(nick)s changed the subject to: %(subject)s") % {'nick':nick_from, 'subject':subject}, time=date)
|
||||
else:
|
||||
self.add_message_to_room(room, _("The subject is: %(subject)s") % {'subject':subject}, date)
|
||||
self.add_message_to_room(room, _("The subject is: %(subject)s") % {'subject':subject}, time=date)
|
||||
room.topic = subject.encode('utf-8').replace('\n', '|')
|
||||
if room == self.current_room():
|
||||
self.window.topic_win.refresh(room.topic)
|
||||
|
@ -442,139 +638,6 @@ class Gui(object):
|
|||
self.refresh_window()
|
||||
doupdate()
|
||||
|
||||
def room_presence(self, stanza):
|
||||
"""
|
||||
Display the presence on the room window and update the
|
||||
presence information of the concerned user
|
||||
"""
|
||||
from_nick = stanza.getFrom().getResource()
|
||||
from_room = stanza.getFrom().getStripped()
|
||||
room = self.get_room_by_name(from_room)
|
||||
if not room:
|
||||
return
|
||||
else:
|
||||
msg = None
|
||||
affiliation = stanza.getAffiliation()
|
||||
show = stanza.getShow()
|
||||
status = stanza.getStatus()
|
||||
role = stanza.getRole()
|
||||
jid = stanza.getJid()
|
||||
if not room.joined: # user in the room BEFORE us.
|
||||
# ignore redondant presence message, see bug #1509
|
||||
if from_nick not in [user.nick for user in room.users]:
|
||||
new_user = User(from_nick, affiliation, show, status, role)
|
||||
room.users.append(new_user)
|
||||
if from_nick.encode('utf-8') == room.own_nick:
|
||||
room.joined = True
|
||||
self.add_message_to_room(room, _("Your nickname is %s") % (from_nick))
|
||||
# Check for a 170 status code
|
||||
for xtag in stanza.getTags('x'):
|
||||
for child in xtag.getTags('status'):
|
||||
if child.getAttr('code') == '170':
|
||||
self.add_message_to_room(room, 'Warning: this room is publicly logged')
|
||||
new_user.color = theme.COLOR_OWN_NICK
|
||||
else:
|
||||
change_nick = stanza.getStatusCode() == '303'
|
||||
kick = stanza.getStatusCode() == '307'
|
||||
user = room.get_user_by_name(from_nick)
|
||||
# New user
|
||||
if not user:
|
||||
room.users.append(User(from_nick, affiliation,
|
||||
show, status, role))
|
||||
hide_exit_join = config.get('hide_exit_join', -1)
|
||||
if hide_exit_join != 0:
|
||||
if not jid:
|
||||
self.add_message_to_room(room, _("%(spec)s [%(nick)s] joined the room") % {'nick':from_nick, 'spec':theme.CHAR_JOIN}, colorized=True)
|
||||
else:
|
||||
self.add_message_to_room(room, _("%(spec)s [%(nick)s] (%(jid)s) joined the room") % {'spec':theme.CHAR_JOIN, 'nick':from_nick, 'jid':jid}, colorized=True)
|
||||
# nick change
|
||||
elif change_nick:
|
||||
if user.nick == room.own_nick:
|
||||
room.own_nick = stanza.getNick().encode('utf-8')
|
||||
# also change our nick in all private discussion of this room
|
||||
for _room in self.rooms:
|
||||
if _room.jid is not None and is_jid_the_same(_room.jid, room.name):
|
||||
_room.own_nick = stanza.getNick()
|
||||
user.change_nick(stanza.getNick())
|
||||
self.add_message_to_room(room, _('[%(old)s] is now known as [%(new)s]') % {'old':from_nick, 'new':stanza.getNick()}, colorized=True)
|
||||
# rename the private tabs if needed
|
||||
private_room = self.get_room_by_name(stanza.getFrom())
|
||||
if private_room:
|
||||
self.add_message_to_room(private_room, _('[%(old_nick)s] is now known as [%(new_nick)s]') % {'old_nick':from_nick, 'new_nick':stanza.getNick()}, colorized=True)
|
||||
new_jid = private_room.name.split('/')[0]+'/'+stanza.getNick()
|
||||
private_room.jid = new_jid
|
||||
private_room.name = new_jid
|
||||
|
||||
# kick
|
||||
elif kick:
|
||||
room.users.remove(user)
|
||||
try:
|
||||
reason = stanza.getReason()
|
||||
except:
|
||||
reason = ''
|
||||
try:
|
||||
by = stanza.getActor()
|
||||
except:
|
||||
by = None
|
||||
if from_nick == room.own_nick: # we are kicked
|
||||
room.disconnect()
|
||||
if by:
|
||||
self.add_message_to_room(room, _("%(spec) [You] have been kicked by [%(by)s]. Reason: {%(reason)s}") % {'spec': theme.CHAR_KICK, 'by':by, 'reason':reason}, colorized=True)
|
||||
else:
|
||||
self.add_message_to_room(room, _("%(spec)s [You] have been kicked. Reason: %(reason)s") % {'reason':reason, 'spec':theme.CHAR_KICK}, colorized=True)
|
||||
# try to auto-rejoin
|
||||
if config.get('autorejoin', 'false') == 'true':
|
||||
self.muc.join_room(room.name, room.own_nick)
|
||||
else:
|
||||
if by:
|
||||
self.add_message_to_room(room, _("%(spec)s [%(nick)s] has been kicked by %(by)s. Reason: %(reason)s") % {'spec':theme.CHAR_KICK, 'nick':from_nick, 'by':by, 'reason':reason}, colorized=True)
|
||||
else:
|
||||
self.add_message_to_room(room, _("%(spec)s [%(nick)s] has been kicked. Reason: %(reason)s") % {'nick':from_nick, 'reason':reason, 'spec':theme.CHAR_KICK}, colorized=True)
|
||||
# user quit
|
||||
elif status == 'offline' or role == 'none':
|
||||
room.users.remove(user)
|
||||
hide_exit_join = config.get('hide_exit_join', -1) if config.get('hide_exit_join', -1) >= -1 else -1
|
||||
if hide_exit_join == -1 or user.has_talked_since(hide_exit_join):
|
||||
if not jid:
|
||||
self.add_message_to_room(room, _('%(spec)s [%(nick)s] has left the room') % {'nick':from_nick, 'spec':theme.CHAR_QUIT}, colorized=True)
|
||||
else:
|
||||
self.add_message_to_room(room, _('%(spec)s [%(nick)s] (%(jid)s) has left the room') % {'spec':theme.CHAR_QUIT, 'nick':from_nick, 'jid':jid}, colorized=True)
|
||||
private_room = self.get_room_by_name(stanza.getFrom())
|
||||
if private_room:
|
||||
self.add_message_to_room(private_room, _('%(spec)s [%(nick)s] has left the room') % {'nick':from_nick, 'spec':theme.CHAR_KICK}, colorized=True)
|
||||
# status change
|
||||
else:
|
||||
# build the message
|
||||
msg = _('%s changed his/her status: ')% from_nick
|
||||
if affiliation != user.affiliation:
|
||||
msg += _('affiliation: %s,') % affiliation
|
||||
if role != user.role:
|
||||
msg += _('role: %s,') % role
|
||||
if show != user.show:
|
||||
msg += _('show: %s,') % show
|
||||
if status != user.status:
|
||||
msg += _('status: %s,') % status
|
||||
msg = msg[:-1] # remove the last ","
|
||||
hide_status_change = config.get('hide_status_change', -1) if config.get('hide_status_change', -1) >= -1 else -1
|
||||
if (hide_status_change == -1 or \
|
||||
user.has_talked_since(hide_status_change) or\
|
||||
user.nick == room.own_nick)\
|
||||
and\
|
||||
(affiliation != user.affiliation or\
|
||||
role != user.role or\
|
||||
show != user.show or\
|
||||
status != user.status):
|
||||
# display the message in the room
|
||||
self.add_message_to_room(room, msg)
|
||||
private_room = self.get_room_by_name(stanza.getFrom())
|
||||
if private_room: # display the message in private
|
||||
self.add_message_to_room(private_room, msg)
|
||||
# finally, effectively change the user status
|
||||
user.update(affiliation, show, status, role)
|
||||
if room == self.current_room():
|
||||
self.window.user_win.refresh(room.users)
|
||||
self.window.input.refresh()
|
||||
doupdate()
|
||||
|
||||
def add_message_to_room(self, room, txt, time=None, nickname=None, colorized=False):
|
||||
"""
|
||||
|
@ -611,10 +674,10 @@ class Gui(object):
|
|||
self.add_message_to_room(self.current_room(), _("Error: unknown command (%s)") % (command))
|
||||
elif self.current_room().name != 'Info':
|
||||
if self.current_room().jid is not None:
|
||||
self.muc.send_private_message(self.current_room().name, line)
|
||||
muc.send_private_message(self.xmpp, self.current_room().name, line)
|
||||
self.add_message_to_room(self.current_room(), line.decode('utf-8'), None, self.current_room().own_nick.decode('utf-8'))
|
||||
else:
|
||||
self.muc.send_message(self.current_room().name, line)
|
||||
muc.send_groupchat_message(self.xmpp, self.current_room().name, line)
|
||||
self.window.input.refresh()
|
||||
doupdate()
|
||||
|
||||
|
@ -640,6 +703,8 @@ class Gui(object):
|
|||
"""
|
||||
/whois <nickname>
|
||||
"""
|
||||
# TODO
|
||||
return
|
||||
args = arg.split()
|
||||
room = self.current_room()
|
||||
if len(args) != 1:
|
||||
|
@ -701,7 +766,9 @@ class Gui(object):
|
|||
if self.current_room().name == 'Info' or not self.current_room().joined:
|
||||
return
|
||||
roomname = self.current_room().name
|
||||
self.muc.eject_user(roomname, 'kick', nick, reason)
|
||||
res = muc.eject_user(self.xmpp, roomname, nick, reason)
|
||||
if res['type'] == 'error':
|
||||
self.room_error(res, roomname)
|
||||
|
||||
def command_say(self, arg):
|
||||
"""
|
||||
|
@ -710,10 +777,10 @@ class Gui(object):
|
|||
line = arg
|
||||
if self.current_room().name != 'Info':
|
||||
if self.current_room().jid is not None:
|
||||
self.muc.send_private_message(self.current_room().name, line)
|
||||
muc.send_private_message(self.xmpp, self.current_room().name, line)
|
||||
self.add_message_to_room(self.current_room(), line.decode('utf-8'), None, self.current_room().own_nick)
|
||||
else:
|
||||
self.muc.send_message(self.current_room().name, line)
|
||||
muc.send_groupchat_message(self.xmpp, self.current_room().name, line)
|
||||
self.window.input.refresh()
|
||||
doupdate()
|
||||
|
||||
|
@ -751,7 +818,7 @@ class Gui(object):
|
|||
# use the server of the current room if available
|
||||
# check if the current room's name has a server
|
||||
if is_jid(self.current_room().name):
|
||||
room += '@%s' % jid_get_domain(self.current_room().name.encode('utf-8'))
|
||||
room += '@%s' % jid_get_domain(self.current_room().name)
|
||||
else: # no server could be found, print a message and return
|
||||
self.add_message_to_room(self.current_room(), _("You didn't specify a server for the room you want to join"))
|
||||
return
|
||||
|
@ -759,14 +826,14 @@ class Gui(object):
|
|||
if len(args) == 2: # a password is provided
|
||||
password = args[1]
|
||||
if r and r.joined: # if we are already in the room
|
||||
self.add_message_to_room(self.current_room(), _("already in room [%s]") % room)
|
||||
self.command_win('%s' % (r.nb))
|
||||
return
|
||||
self.muc.join_room(room, nick, password)
|
||||
room = room.lower()
|
||||
self.xmpp.plugin['xep_0045'].joinMUC(room, nick, password)
|
||||
if not r: # if the room window exists, we don't recreate it.
|
||||
self.join_room(room, nick)
|
||||
self.open_new_room(room, nick)
|
||||
else:
|
||||
r.own_nick = nick
|
||||
# r.own_nick = nick
|
||||
r.users = []
|
||||
|
||||
def command_bookmark(self, arg):
|
||||
|
@ -852,7 +919,7 @@ class Gui(object):
|
|||
msg = None
|
||||
for room in self.rooms:
|
||||
if room.joined:
|
||||
self.muc.change_show(room.name, room.own_nick, show, msg)
|
||||
muc.change_show(self.xmpp, room.name, room.own_nick, show, msg)
|
||||
|
||||
def command_ignore(self, arg):
|
||||
"""
|
||||
|
@ -926,7 +993,7 @@ class Gui(object):
|
|||
else:
|
||||
msg = None
|
||||
if room.joined:
|
||||
self.muc.quit_room(room.name, room.own_nick, msg)
|
||||
muc.leave_groupchat(self.xmpp, room.name, room.own_nick, arg)
|
||||
self.rooms.remove(self.current_room())
|
||||
self.refresh_window()
|
||||
|
||||
|
@ -955,7 +1022,7 @@ class Gui(object):
|
|||
r = self.open_private_window(room.name, user.nick.decode('utf-8'))
|
||||
if r and len(args) > 1:
|
||||
msg = arg[len(nick)+1:]
|
||||
self.muc.send_private_message(r.name, msg)
|
||||
muc.send_private_message(r.name, msg)
|
||||
self.add_message_to_room(r, msg.decode('utf-8'), None, r.own_nick)
|
||||
|
||||
def command_topic(self, arg):
|
||||
|
@ -970,7 +1037,7 @@ class Gui(object):
|
|||
subject = ' '.join(args)
|
||||
if not room.joined or room.name == "Info":
|
||||
return
|
||||
self.muc.change_subject(room.name, subject)
|
||||
muc.change_subject(self.xmpp, room.name, subject)
|
||||
|
||||
def command_link(self, arg):
|
||||
"""
|
||||
|
@ -1029,7 +1096,7 @@ class Gui(object):
|
|||
room = self.current_room()
|
||||
if not room.joined or room.name == "Info":
|
||||
return
|
||||
self.muc.change_nick(room.name, nick)
|
||||
muc.change_nick(self.xmpp, room.name, nick)
|
||||
|
||||
def information(self, msg):
|
||||
"""
|
||||
|
@ -1047,8 +1114,9 @@ class Gui(object):
|
|||
msg = arg
|
||||
else:
|
||||
msg = None
|
||||
if msg:
|
||||
self.muc.disconnect(self.rooms, msg)
|
||||
sleep(0.2) # :(
|
||||
self.reset_curses()
|
||||
for room in self.rooms:
|
||||
if not room.jid and room.name != 'Info':
|
||||
muc.leave_groupchat(self.xmpp, room.name, room.own_nick, msg)
|
||||
self.xmpp.disconnect()
|
||||
self.reset_curses()
|
||||
sys.exit()
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
from singleton import Singleton
|
||||
|
||||
#Todo, it's not a singleton. Oh, also, remove-me
|
||||
|
||||
class Handler(Singleton):
|
||||
"""
|
||||
This class is the global handler for the software's signals.
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# Copyright 2009, 2010 Erwan Briand
|
||||
# Copyright 2010, Florent Le Coz <louizatakk@fedoraproject.org>
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
|
@ -13,331 +12,92 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Implementation of the XEP-0045: Multi-User Chat.
|
||||
"""
|
||||
Implementation of the XEP-0045: Multi-User Chat.
|
||||
Add some facilities that are not available on the XEP_0045
|
||||
sleek plugin
|
||||
"""
|
||||
|
||||
from xmpp import NS_MUC_ADMIN, NS_MUC
|
||||
from xmpp.protocol import Presence, Iq, Message, JID
|
||||
import xmpp
|
||||
import common
|
||||
import threading
|
||||
import os
|
||||
import sleekxmpp
|
||||
|
||||
from time import (altzone, gmtime, localtime, strftime, timezone)
|
||||
from xml.etree import cElementTree as ET
|
||||
|
||||
from handler import Handler
|
||||
from config import config
|
||||
|
||||
from common import get_stripped_jid
|
||||
from common import is_jid
|
||||
from common import debug
|
||||
|
||||
class VcardSender(threading.Thread):
|
||||
def send_private_message(xmpp, jid, line):
|
||||
"""
|
||||
avatar sending is really slow (don't know why...)
|
||||
use a thread to send it...
|
||||
Send a private message
|
||||
"""
|
||||
def __init__(self, connection):
|
||||
threading.Thread.__init__(self)
|
||||
self.connection = connection
|
||||
self.handler = Handler()
|
||||
msg = xmpp.makeMessage(jid)
|
||||
msg['to'] = jid
|
||||
msg['type'] = 'chat'
|
||||
msg['body'] = line
|
||||
msg.send()
|
||||
|
||||
def run(self):
|
||||
self.send_vcard()
|
||||
def send_groupchat_message(xmpp, jid, line):
|
||||
"""
|
||||
Send a message to the groupchat
|
||||
"""
|
||||
msg = xmpp.makeMessage(jid)
|
||||
msg['type'] = 'groupchat'
|
||||
msg['body'] = line
|
||||
msg.send()
|
||||
|
||||
def send_vcard(self):
|
||||
"""
|
||||
Method stolen from Gajim (thanks)
|
||||
## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
|
||||
## Junglecow J <junglecow AT gmail.com>
|
||||
## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org>
|
||||
## Travis Shirk <travis AT pobox.com>
|
||||
## Nikos Kouremenos <kourem AT gmail.com>
|
||||
## Copyright (C) 2006-2008 Yann Leboulanger <asterix AT lagaule.org>
|
||||
## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
|
||||
## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
|
||||
## Jean-Marie Traissard <jim AT lapin.org>
|
||||
## Stephan Erb <steve-e AT h3c.de>
|
||||
## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
|
||||
(one of these people coded this method, probably)
|
||||
"""
|
||||
if not self.connection:
|
||||
return
|
||||
vcard = {
|
||||
"FN":config.get('full_name', ''),
|
||||
"URL":config.get('website', ''),
|
||||
"EMAIL":{
|
||||
"USERID":config.get('email', '')
|
||||
},
|
||||
"DESC":config.get('comment', 'A proud Poezio user')
|
||||
}
|
||||
photo_file_path = config.get('photo', '../data/poezio_80.png')
|
||||
(image, mime_type, sha1) = common.get_base64_from_file(photo_file_path)
|
||||
if image:
|
||||
vcard['PHOTO'] = {"TYPE":mime_type,"BINVAL":image}
|
||||
iq = xmpp.Iq(typ = 'set')
|
||||
iq2 = iq.addChild('vCard', namespace=xmpp.NS_VCARD)
|
||||
for i in vcard:
|
||||
if i == 'jid':
|
||||
continue
|
||||
if isinstance(vcard[i], dict):
|
||||
iq3 = iq2.addChild(i)
|
||||
for j in vcard[i]:
|
||||
iq3.addChild(j).setData(vcard[i][j])
|
||||
elif isinstance(vcard[i], list):
|
||||
for j in vcard[i]:
|
||||
iq3 = iq2.addChild(i)
|
||||
for k in j:
|
||||
iq3.addChild(k).setData(j[k])
|
||||
else:
|
||||
iq2.addChild(i).setData(vcard[i])
|
||||
self.connection.send(iq)
|
||||
iq = xmpp.Iq(typ = 'set')
|
||||
iq2 = iq.addChild('vCard', namespace=xmpp.NS_VCARD_UPDATE)
|
||||
iq2.addChild('PHOTO').setData(sha1)
|
||||
self.connection.send(iq)
|
||||
def change_show(xmpp, jid, own_nick, show, status):
|
||||
"""
|
||||
Change our 'Show'
|
||||
"""
|
||||
pres = xmpp.makePresence(pto='%s/%s' % (jid, own_nick),
|
||||
pfrom=xmpp.fulljid)
|
||||
if show: # if show is None, don't put a <show /> tag. It means "online"
|
||||
pres['type'] = show
|
||||
if status:
|
||||
pres['status'] = status
|
||||
debug('Change presence: %s\n' % (pres))
|
||||
pres.send()
|
||||
|
||||
class MultiUserChat(object):
|
||||
def __init__(self, connection):
|
||||
self.connection = connection
|
||||
self.vcard_sender = VcardSender(self.connection)
|
||||
def change_subject(xmpp, jid, subject):
|
||||
"""
|
||||
Change the room subject
|
||||
"""
|
||||
msg = xmpp.makeMessage(jid)
|
||||
msg['type'] = 'groupchat'
|
||||
msg['subject'] = subject
|
||||
msg['from'] = xmpp.jid
|
||||
msg.send()
|
||||
|
||||
self.rooms = []
|
||||
self.rn = {}
|
||||
def change_nick(xmpp, jid, nick):
|
||||
"""
|
||||
Change our own nick in a room
|
||||
"""
|
||||
xmpp.makePresence(pto='%s/%s' % (jid, nick),
|
||||
pfrom=xmpp.jid).send()
|
||||
|
||||
self.own_jid = None
|
||||
def join_groupchat(xmpp, jid, nick, password=None):
|
||||
"""
|
||||
Join the groupchat
|
||||
"""
|
||||
xmpp.plugin['xep_0045'].joinMUC(jid, nick, password)
|
||||
|
||||
self.handler = Handler()
|
||||
self.handler.connect('join-room', self.join_room)
|
||||
self.handler.connect('on-connected', self.on_connected)
|
||||
self.handler.connect('send-version', self.send_version)
|
||||
self.handler.connect('send-time', self.send_time)
|
||||
def leave_groupchat(xmpp, jid, own_nick, msg):
|
||||
"""
|
||||
Leave the groupchat
|
||||
"""
|
||||
xmpp.plugin['xep_0045'].leaveMUC(jid, own_nick, msg)
|
||||
|
||||
def on_connected(self, jid):
|
||||
self.own_jid = jid
|
||||
rooms = config.get('rooms', '')
|
||||
if rooms == '' or type(rooms) != str:
|
||||
return
|
||||
else:
|
||||
rooms = rooms.split(':')
|
||||
for room in rooms:
|
||||
args = room.split('/')
|
||||
if args[0] == '':
|
||||
return
|
||||
roomname = args[0]
|
||||
if len(args) == 2:
|
||||
nick = args[1]
|
||||
else:
|
||||
default = os.environ.get('USER') if os.environ.get('USER') else 'poezio'
|
||||
nick = config.get('default_nick', '')
|
||||
if nick == '':
|
||||
nick = default
|
||||
self.handler.emit('join-room', room=roomname, nick=nick)
|
||||
if config.get('jid', '') == '': # Don't send the vcard if we're not anonymous
|
||||
self.vcard_sender.start() # because the user ALREADY has one on the server
|
||||
|
||||
def send_message(self, room, message):
|
||||
mes = Message(to=room)
|
||||
mes.setBody(message)
|
||||
mes.setType('groupchat')
|
||||
self.connection.send(mes)
|
||||
|
||||
def send_private_message(self, user_jid, message):
|
||||
mes = Message(to=user_jid)
|
||||
mes.setBody(message)
|
||||
mes.setType('chat')
|
||||
self.connection.send(mes)
|
||||
|
||||
def request_vcard(self, room_name, nickname):
|
||||
"""
|
||||
Request the vCard of an user, over a MUC or not
|
||||
"""
|
||||
request = Iq(typ='get', to='%s/%s'% (room_name, nickname))
|
||||
vcard_tag = request.addChild(name='vCard', namespace='vcard-temp')
|
||||
self.connection.send(request)
|
||||
|
||||
def join_room(self, room, nick, password=None):
|
||||
"""Join a new room"""
|
||||
pres = Presence(to='%s/%s' % (room, nick))
|
||||
pres.setFrom('%s'%self.own_jid)
|
||||
x_tag = pres.addChild(name='x', namespace=NS_MUC)
|
||||
if password:
|
||||
passwd = x_tag.addChild(name='password')
|
||||
passwd.setData(password)
|
||||
muc_history_length = config.get('muc_history_length', -1)
|
||||
if muc_history_length >= 0:
|
||||
history_tag = x_tag.addChild(name='history')
|
||||
if muc_history_length == 0:
|
||||
history_tag.setAttr('maxchars', 0)
|
||||
else:
|
||||
history_tag.setAttr('maxstanzas', muc_history_length)
|
||||
self.connection.send(pres)
|
||||
|
||||
def quit_room(self, room, nick, msg=None):
|
||||
"""Quit a room"""
|
||||
if room is None and nick is None:
|
||||
self.on_disconnect()
|
||||
return
|
||||
|
||||
pres = Presence(to='%s/%s' % (room, nick), typ='unavailable')
|
||||
if msg:
|
||||
pres.setStatus(msg)
|
||||
self.connection.send(pres)
|
||||
|
||||
def disconnect(self, rooms, msg):
|
||||
"""
|
||||
"""
|
||||
for room in rooms:
|
||||
if room.jid is None and room.joined:
|
||||
pres = Presence(to='%s' % room.name,
|
||||
typ='unavailable')
|
||||
pres.setStatus(msg)
|
||||
self.connection.send(pres)
|
||||
|
||||
def on_disconnect(self):
|
||||
"""Called at disconnection"""
|
||||
for room in self.rooms:
|
||||
pres = Presence(to='%s/%s' % (room, self.rn[room]),
|
||||
typ='unavailable')
|
||||
self.connection.send(pres)
|
||||
|
||||
def on_iq(self, iq):
|
||||
"""Receive a MUC iq notification"""
|
||||
from_ = iq.getFrom().__str__()
|
||||
|
||||
if get_stripped_jid(from_) in self.rooms:
|
||||
children = iq.getChildren()
|
||||
for child in children:
|
||||
if child.getName() == 'error':
|
||||
code = int(child.getAttr('code'))
|
||||
msg = None
|
||||
|
||||
echildren = child.getChildren()
|
||||
for echild in echildren:
|
||||
if echild.getName() == 'text':
|
||||
msg = echild.getData()
|
||||
|
||||
self.handler.emit('on-muc-error',
|
||||
room=from_,
|
||||
code=code,
|
||||
msg=msg)
|
||||
|
||||
def on_presence(self, presence):
|
||||
"""Receive a MUC presence notification"""
|
||||
from_ = presence.getFrom().__str__()
|
||||
if get_stripped_jid(from_) in self.rooms:
|
||||
self.handler.emit('on-muc-presence-changed',
|
||||
jid=from_.encode('utf-8'),
|
||||
priority=presence.getPriority(),
|
||||
show=presence.getShow(),
|
||||
status=presence.getStatus(),
|
||||
stanza=presence
|
||||
)
|
||||
|
||||
def on_message(self, message):
|
||||
"""Receive a MUC message notification"""
|
||||
from_ = message.getFrom().__str__().encode('utf-8')
|
||||
|
||||
if get_stripped_jid(from_) in self.rooms:
|
||||
body_ = message.getBody()
|
||||
type_ = message.getType()
|
||||
subj_ = message.getSubject()
|
||||
self.handler.emit('on-muc-message-received',
|
||||
jid=from_, msg=body_, subject=subj_,
|
||||
typ=type_, stanza=message)
|
||||
|
||||
def eject_user(self, room, action, nick, reason):
|
||||
"""Eject an user from a room"""
|
||||
iq = Iq(typ='set', to=room)
|
||||
query = iq.addChild('query', namespace=NS_MUC_ADMIN)
|
||||
item = query.addChild('item')
|
||||
|
||||
if action == 'kick':
|
||||
item.setAttr('role', 'none')
|
||||
if is_jid(nick):
|
||||
item.setAttr('jid', nick)
|
||||
else:
|
||||
item.setAttr('nick', nick)
|
||||
elif action == 'ban':
|
||||
item.setAttr('affiliation', 'outcast')
|
||||
item.setAttr('jid', nick)
|
||||
|
||||
if reason is not None:
|
||||
rson = item.addChild('reason')
|
||||
rson.setData(reason)
|
||||
|
||||
self.connection.send(iq)
|
||||
|
||||
def change_role(self, room, nick, role):
|
||||
"""Change the role of an user"""
|
||||
iq = Iq(typ='set', to=room)
|
||||
query = iq.addChild('query', namespace=NS_MUC_ADMIN)
|
||||
item = query.addChild('item')
|
||||
item.setAttr('nick', nick)
|
||||
item.setAttr('role', role)
|
||||
|
||||
self.connection.send(iq)
|
||||
|
||||
def change_aff(self, room, jid, aff):
|
||||
"""Change the affiliation of an user"""
|
||||
iq = Iq(typ='set', to=room)
|
||||
query = iq.addChild('query', namespace=NS_MUC_ADMIN)
|
||||
item = query.addChild('item')
|
||||
item.setAttr('jid', jid)
|
||||
item.setAttr('affiliation', aff)
|
||||
|
||||
self.connection.send(iq)
|
||||
|
||||
def change_subject(self, room, subject):
|
||||
"""Change the subject of a room"""
|
||||
message = Message(typ='groupchat', to=room)
|
||||
subj = message.addChild('subject')
|
||||
subj.setData(subject)
|
||||
|
||||
self.connection.send(message)
|
||||
|
||||
def change_nick(self, room, nick):
|
||||
"""Change the nickname"""
|
||||
pres = Presence(to='%s/%s' % (room, nick))
|
||||
self.connection.send(pres)
|
||||
|
||||
def change_show(self, room, nick, show, status):
|
||||
pres = Presence(to='%s/%s' % (room, nick))
|
||||
if show: # if show is None, don't put a <show /> tag. It means "online"
|
||||
pres.setShow(show)
|
||||
if status:
|
||||
pres.setStatus(status)
|
||||
self.connection.send(pres)
|
||||
|
||||
def send_version(self, iq_obj):
|
||||
"""
|
||||
from gajim and modified
|
||||
"""
|
||||
iq_obj = iq_obj.buildReply('result')
|
||||
qp = iq_obj.getTag('query')
|
||||
if config.get('send_poezio_info', 'true') == 'true':
|
||||
qp.setTagData('name', 'Poezio')
|
||||
qp.setTagData('version', '0.6.3 dev')
|
||||
else:
|
||||
qp.setTagData('name', 'Unknown')
|
||||
qp.setTagData('version', 'Unknown')
|
||||
if config.get('send_os_info', 'true') == 'true':
|
||||
qp.setTagData('os', common.get_os_info())
|
||||
else:
|
||||
qp.setTagData('os', 'Unknown')
|
||||
self.connection.send(iq_obj)
|
||||
raise xmpp.protocol.NodeProcessed
|
||||
|
||||
def send_time(self, iq_obj):
|
||||
"""
|
||||
from gajim
|
||||
"""
|
||||
iq_obj = iq_obj.buildReply('result')
|
||||
qp = iq_obj.setTag('time',
|
||||
namespace="urn:xmpp:time")
|
||||
if config.get('send_time', 'true') == 'true':
|
||||
qp.setTagData('utc', strftime('%Y-%m-%dT%H:%M:%SZ', gmtime()))
|
||||
isdst = localtime().tm_isdst
|
||||
zone = -(timezone, altzone)[isdst] / 60
|
||||
tzo = (zone / 60, abs(zone % 60))
|
||||
qp.setTagData('tzo', '%+03d:%02d' % (tzo))
|
||||
self.connection.send(iq_obj)
|
||||
raise xmpp.protocol.NodeProcessed
|
||||
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'})
|
||||
if reason:
|
||||
reason_el = ET.Element('{http://jabber.org/protocol/muc#admin}reason')
|
||||
reason_el.text = reason
|
||||
item.append(reason_el)
|
||||
query.append(item)
|
||||
iq.append(query)
|
||||
iq['to'] = jid
|
||||
return iq.send()
|
||||
|
|
|
@ -25,26 +25,6 @@ import threading
|
|||
import sys
|
||||
import traceback
|
||||
|
||||
def installThreadExcepthook():
|
||||
"""
|
||||
Workaround for sys.excepthook thread bug
|
||||
See http://bugs.python.org/issue1230540
|
||||
Python, you made me sad :(
|
||||
"""
|
||||
init_old = threading.Thread.__init__
|
||||
def init(self, *args, **kwargs):
|
||||
init_old(self, *args, **kwargs)
|
||||
run_old = self.run
|
||||
def run_with_except_hook(*args, **kw):
|
||||
try:
|
||||
run_old(*args, **kw)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
sys.excepthook(*sys.exc_info())
|
||||
self.run = run_with_except_hook
|
||||
threading.Thread.__init__ = init
|
||||
|
||||
class MyStdErr(object):
|
||||
def __init__(self, fd):
|
||||
"""
|
||||
|
@ -59,6 +39,7 @@ class MyStdErr(object):
|
|||
Restaure the good ol' sys.stderr, because we need
|
||||
it in order to print the tracebacks
|
||||
"""
|
||||
sys.stderr.close()
|
||||
sys.stderr = self.old_stderr
|
||||
|
||||
my_stderr = MyStdErr(open('/dev/null', 'a'))
|
||||
|
@ -77,30 +58,24 @@ def exception_handler(type_, value, trace):
|
|||
|
||||
sys.excepthook = exception_handler
|
||||
|
||||
import sys
|
||||
import curses
|
||||
import signal
|
||||
|
||||
from connection import Connection
|
||||
from multiuserchat import MultiUserChat
|
||||
from config import config
|
||||
from gui import Gui
|
||||
from curses import initscr
|
||||
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN) # ignore ctrl-c
|
||||
|
||||
def main():
|
||||
"""
|
||||
main function
|
||||
The main function consist of the Connection initialization
|
||||
then the gui (ncurses) init, connection handlers and then the
|
||||
connection is "started"
|
||||
"""
|
||||
resource = config.get('resource', 'poezio')
|
||||
server = config.get('server', 'anon.louiz.org:jeproteste.info')
|
||||
connection = Connection(server, resource)
|
||||
connection.start()
|
||||
stdscr = initscr()
|
||||
gui = Gui(stdscr, MultiUserChat(connection.client))
|
||||
gui.main_loop(stdscr)
|
||||
xmpp = Connection() # Connection init
|
||||
gui = Gui(xmpp) # Gui init.
|
||||
xmpp.start() # Connect to remote server
|
||||
gui.main_loop() # Refresh the screen, wait for user events etc
|
||||
|
||||
if __name__ == '__main__':
|
||||
installThreadExcepthook()
|
||||
main()
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
from datetime import datetime
|
||||
from random import randrange
|
||||
from config import config
|
||||
from logging import logger
|
||||
from logger import logger
|
||||
from message import Message
|
||||
|
||||
import common
|
||||
|
|
Loading…
Reference in a new issue