libervia-backend/src/plugins/plugin_xep_0045.py
2011-10-08 20:26:36 +02:00

221 lines
9.8 KiB
Python
Executable file

#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
SAT plugin for managing xep-0045
Copyright (C) 2009, 2010, 2011 Jérôme Poisson (goffi@goffi.org)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from logging import debug, info, warning, error
from twisted.words.xish import domish
from twisted.internet import protocol, defer, threads, reactor
from twisted.words.protocols.jabber import client, jid, xmlstream
from twisted.words.protocols.jabber import error as jab_error
from twisted.words.protocols.jabber.xmlstream import IQ
import os.path
import uuid
from zope.interface import implements
from wokkel import disco, iwokkel, muc
from base64 import b64decode
from hashlib import sha1
from time import sleep
try:
from twisted.words.protocols.xmlstream import XMPPHandler
except ImportError:
from wokkel.subprotocols import XMPPHandler
PLUGIN_INFO = {
"name": "XEP 0045 Plugin",
"import_name": "XEP-0045",
"type": "XEP",
"protocols": ["XEP-0045"],
"dependencies": [],
"main": "XEP_0045",
"handler": "yes",
"description": _("""Implementation of Multi-User Chat""")
}
class XEP_0045():
def __init__(self, host):
info(_("Plugin XEP_0045 initialization"))
self.host = host
self.clients={}
host.bridge.addMethod("joinMUC", ".plugin", in_sign='ssa{ss}s', out_sign='', method=self._join)
host.bridge.addMethod("getRoomsJoined", ".plugin", in_sign='s', out_sign='a(sass)', method=self.getRoomsJoined)
host.bridge.addMethod("getRoomsSubjectss", ".plugin", in_sign='s', out_sign='a(ss)', method=self.getRoomsSubjectss)
host.bridge.addMethod("getUniqueRoomName", ".plugin", in_sign='s', out_sign='s', method=self.getUniqueName)
host.bridge.addSignal("roomJoined", ".plugin", signature='sasss') #args: room_jid, room_nicks, user_nick, profile
host.bridge.addSignal("roomUserJoined", ".plugin", signature='ssa{ss}s') #args: room_jid, user_nick, user_data, profile
host.bridge.addSignal("roomUserLeft", ".plugin", signature='ssa{ss}s') #args: room_jid, user_nick, user_data, profile
host.bridge.addSignal("roomNewSubject", ".plugin", signature='sss') #args: room_jid, subject, profile
def __check_profile(self, profile):
"""check if profile is used and connected
if profile known but disconnected, remove it from known profiles
@param profile: profile to check
@return: True if the profile is known and connected, else False"""
if not profile or not self.clients.has_key(profile) or not self.host.isConnected(profile):
error (_('Unknown or disconnected profile (%s)') % profile)
if self.clients.has_key(profile):
del self.clients[profile]
return False
return True
def __room_joined(self, room, profile):
"""Called when the user is in the requested room"""
def _sendBridgeSignal(ignore=None):
self.host.bridge.roomJoined(room.roomJID.userhost(), [user.nick for user in room.roster.values()], room.nick, profile)
room_jid_s = room.roomJID.userhost()
self.clients[profile].joined_rooms[room_jid_s] = room
if room.status == '201':
#FIXME: the current behaviour is to create an instant room
#and send the signal only when the room is unlocked
#a proper configuration management should be done
self.clients[profile].configure(room_jid_s).addCallbacks(_sendBridgeSignal, lambda x: error(_('Error while configuring the room')))
else:
_sendBridgeSignal()
return room
def __err_joining_room(self, failure, profile):
"""Called when something is going wrong when joining the room"""
mess = _("Error when joining the room")
error (mess)
self.host.bridge.newAlert(mess, _("Group chat error"), "ERROR", profile)
def getRoomsJoined(self, profile_key='@DEFAULT@'):
"""Return room where user is"""
profile = self.host.memory.getProfileName(profile_key)
result = []
if not self.__check_profile(profile):
return result
for room in self.clients[profile].joined_rooms.values():
result.append((room.roomJID.userhost(), [user.nick for user in room.roster.values()], room.nick))
return result
def getRoomNick(self, room_jid, profile_key='@DEFAULT@'):
"""return nick used in room by user
@param room_jid: unicode room id
@profile_key: profile
@return: nick or empty string in case of error"""
profile = self.host.memory.getProfileName(profile_key)
if not self.__check_profile(profile) or not self.clients[profile].joined_rooms.has_key(room_jid):
return ''
return self.clients[profile].joined_rooms[room_jid].nick
def getRoomsSubjectss(self, profile_key='@DEFAULT@'):
"""Return received subjects of rooms"""
profile = self.host.memory.getProfileName(profile_key)
if not self.__check_profile(profile):
return []
return self.clients[profile].rec_subjects.values()
def getUniqueName(self, profile_key='@DEFAULT@'):
"""Return unique name for room, avoiding collision"""
#TODO: we should use #RFC-0045 10.1.4 when available here
#TODO: we should be able to select the MUC service here
return uuid.uuid1()
def join(self, room_jid, nick, options, profile_key='@DEFAULT@'):
def _errDeferred(exc_obj = Exception, txt='Error while joining room'):
d = defer.Deferred()
d.errback(exc_obj(txt))
profile = self.host.memory.getProfileName(profile_key)
if not self.__check_profile(profile):
return _errDeferred()
if self.clients[profile].joined_rooms.has_key(room_jid.userhost()):
warning(_('%(profile)s is already in room %(room_jid)s') % {'profile':profile, 'room_jid':room_jid.userhost()})
return _errDeferred()
info (_("[%(profile)s] is joining room %(room)s with nick %(nick)s") % {'profile':profile,'room':room_jid.userhost(), 'nick':nick})
history_options = options["history"] == "True" if options.has_key("history") else None
password = options["password"] if options.has_key("password") else None
return self.clients[profile].join(room_jid, nick, history_options, password).addCallbacks(self.__room_joined, self.__err_joining_room, callbackKeywords={'profile':profile}, errbackKeywords={'profile':profile})
def _join(self, room_jid_s, nick, options={}, profile_key='@DEFAULT@'):
"""join method used by bridge: use the _join method, but doesn't return any deferred"""
profile = self.host.memory.getProfileName(profile_key)
if not self.__check_profile(profile):
return
try:
room_jid = jid.JID(room_jid_s)
except:
mess = _("Invalid room jid: %s") % room_jid_s
warning(mess)
self.host.bridge.newAlert(mess, _("Group chat error"), "ERROR", profile)
return
d = self.join(room_jid, nick, options, profile)
d.addErrback(lambda x: warning(_('Error while joining room'))) #TODO: error management + signal in bridge
def getHandler(self, profile):
self.clients[profile] = SatMUCClient(self)
return self.clients[profile]
class SatMUCClient (muc.MUCClient):
#implements(iwokkel.IDisco)
def __init__(self, plugin_parent):
self.plugin_parent = plugin_parent
self.host = plugin_parent.host
muc.MUCClient.__init__(self)
self.joined_rooms = {}
self.rec_subjects = {}
print "init SatMUCClient OK"
def receivedGroupChat(self, room, user, body):
debug('receivedGroupChat: room=%s user=%s body=%s', room, user, body)
def userJoinedRoom(self, room, user):
debug (_("user %(nick)s has joined room (%(room_id)s)") % {'nick':user.nick, 'room_id':room.occupantJID.userhost()})
if not self.host.trigger.point("MUC user joined", room, user, self.parent.profile):
return
user_data={'entity':user.entity.full() if user.entity else '', 'affiliation':user.affiliation, 'role':user.role}
self.host.bridge.roomUserJoined(room.roomJID.userhost(), user.nick, user_data, self.parent.profile)
def userLeftRoom(self, room, user):
debug (_("user %(nick)s left room (%(room_id)s)") % {'nick':user.nick, 'room_id':room.occupantJID.userhost()})
user_data={'entity':user.entity.full() if user.entity else '', 'affiliation':user.affiliation, 'role':user.role}
self.host.bridge.roomUserLeft(room.roomJID.userhost(), user.nick, user_data, self.parent.profile)
def userUpdatedStatus(self, room, user, show, status):
print("FIXME: MUC status not managed yet")
#FIXME: gof
def receivedSubject(self, room, user, subject):
debug (_("New subject for room (%(room_id)s): %(subject)s") % {'room_id':room.roomJID.full(),'subject':subject})
self.rec_subjects[room.roomJID.userhost()] = (room.roomJID.userhost(), subject)
self.host.bridge.roomNewSubject(room.roomJID.userhost(), subject, self.parent.profile)
#def connectionInitialized(self):
#pass
#def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
#return [disco.DiscoFeature(NS_VCARD)]
#def getDiscoItems(self, requestor, target, nodeIdentifier=''):
#return []