b73a859031
Use this event to get notified of the subject changes (or to get the subject of the room when joining one)
344 lines
10 KiB
Python
344 lines
10 KiB
Python
"""
|
|
SleekXMPP: The Sleek XMPP Library
|
|
Copyright (C) 2010 Nathanael C. Fritz
|
|
This file is part of SleekXMPP.
|
|
|
|
See the file LICENSE for copying permission.
|
|
"""
|
|
from __future__ import with_statement
|
|
from . import base
|
|
import logging
|
|
from xml.etree import cElementTree as ET
|
|
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, JID
|
|
from .. stanza.presence import Presence
|
|
from .. xmlstream.handler.callback import Callback
|
|
from .. xmlstream.matcher.xpath import MatchXPath
|
|
from .. xmlstream.matcher.xmlmask import MatchXMLMask
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class MUCPresence(ElementBase):
|
|
name = 'x'
|
|
namespace = 'http://jabber.org/protocol/muc#user'
|
|
plugin_attrib = 'muc'
|
|
interfaces = set(('affiliation', 'role', 'jid', 'nick', 'room'))
|
|
affiliations = set(('', ))
|
|
roles = set(('', ))
|
|
|
|
def getXMLItem(self):
|
|
item = self.xml.find('{http://jabber.org/protocol/muc#user}item')
|
|
if item is None:
|
|
item = ET.Element('{http://jabber.org/protocol/muc#user}item')
|
|
self.xml.append(item)
|
|
return item
|
|
|
|
def getAffiliation(self):
|
|
#TODO if no affilation, set it to the default and return default
|
|
item = self.getXMLItem()
|
|
return item.get('affiliation', '')
|
|
|
|
def setAffiliation(self, value):
|
|
item = self.getXMLItem()
|
|
#TODO check for valid affiliation
|
|
item.attrib['affiliation'] = value
|
|
return self
|
|
|
|
def delAffiliation(self):
|
|
item = self.getXMLItem()
|
|
#TODO set default affiliation
|
|
if 'affiliation' in item.attrib: del item.attrib['affiliation']
|
|
return self
|
|
|
|
def getJid(self):
|
|
item = self.getXMLItem()
|
|
return JID(item.get('jid', ''))
|
|
|
|
def setJid(self, value):
|
|
item = self.getXMLItem()
|
|
if not isinstance(value, str):
|
|
value = str(value)
|
|
item.attrib['jid'] = value
|
|
return self
|
|
|
|
def delJid(self):
|
|
item = self.getXMLItem()
|
|
if 'jid' in item.attrib: del item.attrib['jid']
|
|
return self
|
|
|
|
def getRole(self):
|
|
item = self.getXMLItem()
|
|
#TODO get default role, set default role if none
|
|
return item.get('role', '')
|
|
|
|
def setRole(self, value):
|
|
item = self.getXMLItem()
|
|
#TODO check for valid role
|
|
item.attrib['role'] = value
|
|
return self
|
|
|
|
def delRole(self):
|
|
item = self.getXMLItem()
|
|
#TODO set default role
|
|
if 'role' in item.attrib: del item.attrib['role']
|
|
return self
|
|
|
|
def getNick(self):
|
|
return self.parent()['from'].resource
|
|
|
|
def getRoom(self):
|
|
return self.parent()['from'].bare
|
|
|
|
def setNick(self, value):
|
|
log.warning("Cannot set nick through mucpresence plugin.")
|
|
return self
|
|
|
|
def setRoom(self, value):
|
|
log.warning("Cannot set room through mucpresence plugin.")
|
|
return self
|
|
|
|
def delNick(self):
|
|
log.warning("Cannot delete nick through mucpresence plugin.")
|
|
return self
|
|
|
|
def delRoom(self):
|
|
log.warning("Cannot delete room through mucpresence plugin.")
|
|
return self
|
|
|
|
class xep_0045(base.base_plugin):
|
|
"""
|
|
Impliments XEP-0045 Multi User Chat
|
|
"""
|
|
|
|
def plugin_init(self):
|
|
self.rooms = {}
|
|
self.ourNicks = {}
|
|
self.xep = '0045'
|
|
self.description = 'Multi User Chat'
|
|
# load MUC support in presence stanzas
|
|
registerStanzaPlugin(Presence, MUCPresence)
|
|
self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence))
|
|
self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message))
|
|
self.xmpp.registerHandler(Callback('MUCSubject', MatchXMLMask("<message xmlns='%s' type='groupchat'><subject/></message>" % self.xmpp.default_ns), self.handle_groupchat_subject))
|
|
|
|
def handle_groupchat_presence(self, pr):
|
|
""" Handle a presence in a muc.
|
|
"""
|
|
got_offline = False
|
|
got_online = False
|
|
if pr['muc']['room'] not in self.rooms.keys():
|
|
return
|
|
entry = pr['muc'].getStanzaValues()
|
|
entry['show'] = pr['show']
|
|
entry['status'] = pr['status']
|
|
if pr['type'] == 'unavailable':
|
|
if entry['nick'] in self.rooms[entry['room']]:
|
|
del self.rooms[entry['room']][entry['nick']]
|
|
got_offline = True
|
|
else:
|
|
if entry['nick'] not in self.rooms[entry['room']]:
|
|
got_online = True
|
|
self.rooms[entry['room']][entry['nick']] = entry
|
|
log.debug("MUC presence from %s/%s : %s" % (entry['room'],entry['nick'], entry))
|
|
self.xmpp.event("groupchat_presence", pr)
|
|
self.xmpp.event("muc::%s::presence" % entry['room'], pr)
|
|
if got_offline:
|
|
self.xmpp.event("muc::%s::got_offline" % entry['room'], pr)
|
|
if got_online:
|
|
self.xmpp.event("muc::%s::got_online" % entry['room'], pr)
|
|
|
|
def handle_groupchat_message(self, msg):
|
|
""" Handle a message event in a muc.
|
|
"""
|
|
self.xmpp.event('groupchat_message', msg)
|
|
self.xmpp.event("muc::%s::message" % msg['from'].bare, msg)
|
|
|
|
def handle_groupchat_subject(self, msg):
|
|
""" Handle a message coming from a muc indicating
|
|
a change of subject (or announcing it when joining the room)
|
|
"""
|
|
self.xmpp.event('groupchat_subject', msg)
|
|
|
|
def jidInRoom(self, room, jid):
|
|
for nick in self.rooms[room]:
|
|
entry = self.rooms[room][nick]
|
|
if entry is not None and entry['jid'].full == jid:
|
|
return True
|
|
return False
|
|
|
|
def getNick(self, room, jid):
|
|
for nick in self.rooms[room]:
|
|
entry = self.rooms[room][nick]
|
|
if entry is not None and entry['jid'].full == jid:
|
|
return nick
|
|
|
|
def getRoomForm(self, room, ifrom=None):
|
|
iq = self.xmpp.makeIqGet()
|
|
iq['to'] = room
|
|
if ifrom is not None:
|
|
iq['from'] = ifrom
|
|
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
|
|
iq.append(query)
|
|
result = iq.send()
|
|
if result['type'] == 'error':
|
|
return False
|
|
xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
|
|
if xform is None: return False
|
|
form = self.xmpp.plugin['old_0004'].buildForm(xform)
|
|
return form
|
|
|
|
def configureRoom(self, room, form=None, ifrom=None):
|
|
if form is None:
|
|
form = self.getRoomForm(room, ifrom=ifrom)
|
|
#form = self.xmpp.plugin['old_0004'].makeForm(ftype='submit')
|
|
#form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig')
|
|
iq = self.xmpp.makeIqSet()
|
|
iq['to'] = room
|
|
if ifrom is not None:
|
|
iq['from'] = ifrom
|
|
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
|
|
form = form.getXML('submit')
|
|
query.append(form)
|
|
iq.append(query)
|
|
result = iq.send()
|
|
if result['type'] == 'error':
|
|
return False
|
|
return True
|
|
|
|
def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None):
|
|
""" Join the specified room, requesting 'maxhistory' lines of history.
|
|
"""
|
|
stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow)
|
|
x = ET.Element('{http://jabber.org/protocol/muc}x')
|
|
if password:
|
|
passelement = ET.Element('password')
|
|
passelement.text = password
|
|
x.append(passelement)
|
|
if maxhistory:
|
|
history = ET.Element('history')
|
|
if maxhistory == "0":
|
|
history.attrib['maxchars'] = maxhistory
|
|
else:
|
|
history.attrib['maxstanzas'] = maxhistory
|
|
x.append(history)
|
|
stanza.append(x)
|
|
if not wait:
|
|
self.xmpp.send(stanza)
|
|
else:
|
|
#wait for our own room presence back
|
|
expect = ET.Element("{%s}presence" % self.xmpp.default_ns, {'from':"%s/%s" % (room, nick)})
|
|
self.xmpp.send(stanza, expect)
|
|
self.rooms[room] = {}
|
|
self.ourNicks[room] = nick
|
|
|
|
def destroy(self, room, reason='', altroom = '', ifrom=None):
|
|
iq = self.xmpp.makeIqSet()
|
|
if ifrom is not None:
|
|
iq['from'] = ifrom
|
|
iq['to'] = room
|
|
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
|
|
destroy = ET.Element('destroy')
|
|
if altroom:
|
|
destroy.attrib['jid'] = altroom
|
|
xreason = ET.Element('reason')
|
|
xreason.text = reason
|
|
destroy.append(xreason)
|
|
query.append(destroy)
|
|
iq.append(query)
|
|
r = iq.send()
|
|
if r is False or r['type'] == 'error':
|
|
return False
|
|
return True
|
|
|
|
def setAffiliation(self, room, jid=None, nick=None, affiliation='member'):
|
|
""" Change room affiliation."""
|
|
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
|
|
raise TypeError
|
|
query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
|
|
if nick is not None:
|
|
item = ET.Element('item', {'affiliation':affiliation, 'nick':nick})
|
|
else:
|
|
item = ET.Element('item', {'affiliation':affiliation, 'jid':jid})
|
|
query.append(item)
|
|
iq = self.xmpp.makeIqSet(query)
|
|
iq['to'] = room
|
|
result = iq.send()
|
|
if result is False or result['type'] != 'result':
|
|
raise ValueError
|
|
return True
|
|
|
|
def invite(self, room, jid, reason=''):
|
|
""" Invite a jid to a room."""
|
|
msg = self.xmpp.makeMessage(room)
|
|
msg['from'] = self.xmpp.jid
|
|
x = ET.Element('{http://jabber.org/protocol/muc#user}x')
|
|
invite = ET.Element('{http://jabber.org/protocol/muc#user}invite', {'to': jid})
|
|
if reason:
|
|
rxml = ET.Element('reason')
|
|
rxml.text = reason
|
|
invite.append(rxml)
|
|
x.append(invite)
|
|
msg.append(x)
|
|
self.xmpp.send(msg)
|
|
|
|
def leaveMUC(self, room, nick, msg=''):
|
|
""" Leave the specified room.
|
|
"""
|
|
if msg:
|
|
self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick), pstatus=msg)
|
|
else:
|
|
self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick))
|
|
del self.rooms[room]
|
|
|
|
def getRoomConfig(self, room):
|
|
iq = self.xmpp.makeIqGet('http://jabber.org/protocol/muc#owner')
|
|
iq['to'] = room
|
|
iq['from'] = self.xmpp.jid
|
|
result = iq.send()
|
|
if result is None or result['type'] != 'result':
|
|
raise ValueError
|
|
form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
|
|
if form is None:
|
|
raise ValueError
|
|
return self.xmpp.plugin['xep_0004'].buildForm(form)
|
|
|
|
def cancelConfig(self, room):
|
|
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
|
|
x = ET.Element('{jabber:x:data}x', type='cancel')
|
|
query.append(x)
|
|
iq = self.xmpp.makeIqSet(query)
|
|
iq.send()
|
|
|
|
def setRoomConfig(self, room, config):
|
|
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
|
|
x = config.getXML('submit')
|
|
query.append(x)
|
|
iq = self.xmpp.makeIqSet(query)
|
|
iq['to'] = room
|
|
iq['from'] = self.xmpp.jid
|
|
iq.send()
|
|
|
|
def getJoinedRooms(self):
|
|
return self.rooms.keys()
|
|
|
|
def getOurJidInRoom(self, roomJid):
|
|
""" Return the jid we're using in a room.
|
|
"""
|
|
return "%s/%s" % (roomJid, self.ourNicks[roomJid])
|
|
|
|
def getJidProperty(self, room, nick, jidProperty):
|
|
""" Get the property of a nick in a room, such as its 'jid' or 'affiliation'
|
|
If not found, return None.
|
|
"""
|
|
if room in self.rooms and nick in self.rooms[room] and jidProperty in self.rooms[room][nick]:
|
|
return self.rooms[room][nick][jidProperty]
|
|
else:
|
|
return None
|
|
|
|
def getRoster(self, room):
|
|
""" Get the list of nicks in a room.
|
|
"""
|
|
if room not in self.rooms.keys():
|
|
return None
|
|
return self.rooms[room].keys()
|