Switch to Sleekxmpp. fixed #1768

parent d2fef911
......@@ -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 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 __init__(self):
sleekxmpp.ClientXMPP.__init__(self, None, None, ssl=True,
resource=config.get('resource', 'poezio'))
self.registerPlugin('xep_0045')
def start(self):
# TODO, try multiple servers
if self.connect((config.get('server', 'anon.louiz.org'),
config.get('port', 5222))):
self.process(threaded=True)
This diff is collapsed.
......@@ -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.
......
# 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_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)
class MultiUserChat(object):
def __init__(self, connection):
self.connection = connection
self.vcard_sender = VcardSender(self.connection)
self.rooms = []
self.rn = {}
self.own_jid = None
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 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)
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()
self.connection.send(message)
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