Merge branch 'develop' into roster

This commit is contained in:
Lance Stout 2010-12-16 22:03:56 -05:00
commit adade2e5ec
17 changed files with 966 additions and 424 deletions

198
examples/disco_browser.py Executable file
View file

@ -0,0 +1,198 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import sys
import time
import logging
import getpass
from optparse import OptionParser
import sleekxmpp
# Python versions before 3.0 do not use UTF-8 encoding
# by default. To ensure that Unicode is handled properly
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
class Disco(sleekxmpp.ClientXMPP):
"""
A demonstration for using basic service discovery.
Send a disco#info and disco#items request to a JID/node combination,
and print out the results.
May also request only particular info categories such as just features,
or just items.
"""
def __init__(self, jid, password, target_jid, target_node='', get=''):
sleekxmpp.ClientXMPP.__init__(self, jid, password)
# Using service discovery requires the XEP-0030 plugin.
self.register_plugin('xep_0030')
self.get = get
self.target_jid = target_jid
self.target_node = target_node
# Values to control which disco entities are reported
self.info_types = ['', 'all', 'info', 'identities', 'features']
self.identity_types = ['', 'all', 'info', 'identities']
self.feature_types = ['', 'all', 'info', 'features']
self.items_types = ['', 'all', 'items']
# The session_start event will be triggered when
# the bot establishes its connection with the server
# and the XML streams are ready for use. We want to
# listen for this event so that we we can intialize
# our roster.
self.add_event_handler("session_start", self.start)
def start(self, event):
"""
Process the session_start event.
Typical actions for the session_start event are
requesting the roster and broadcasting an intial
presence stanza.
In this case, we send disco#info and disco#items
stanzas to the requested JID and print the results.
Arguments:
event -- An empty dictionary. The session_start
event does not provide any additional
data.
"""
self.get_roster()
self.send_presence()
if self.get in self.info_types:
# By using block=True, the result stanza will be
# returned. Execution will block until the reply is
# received. Non-blocking options would be to listen
# for the disco_info event, or passing a handler
# function using the callback parameter.
info = self['xep_0030'].get_info(jid=self.target_jid,
node=self.target_node,
block=True)
if self.get in self.items_types:
# The same applies from above. Listen for the
# disco_items event or pass a callback function
# if you need to process a non-blocking request.
items = self['xep_0030'].get_items(jid=self.target_jid,
node=self.target_node,
block=True)
else:
logging.error("Invalid disco request type.")
self.disconnect()
return
header = 'XMPP Service Discovery: %s' % self.target_jid
print(header)
print('-' * len(header))
if self.target_node != '':
print('Node: %s' % self.target_node)
print('-' * len(header))
if self.get in self.identity_types:
print('Identities:')
for identity in info['disco_info']['identities']:
print(' - ', identity)
if self.get in self.feature_types:
print('Features:')
for feature in info['disco_info']['features']:
print(' - %s' % feature)
if self.get in self.items_types:
print('Items:')
for item in items['disco_items']['items']:
print(' - %s' % str(item))
self.disconnect()
if __name__ == '__main__':
# Setup the command line arguments.
optp = OptionParser()
optp.version = '%%prog 0.1'
optp.usage = "Usage: %%prog [options] %s <jid> [<node>]" % \
'all|info|items|identities|features'
optp.add_option('-q','--quiet', help='set logging to ERROR',
action='store_const',
dest='loglevel',
const=logging.ERROR,
default=logging.ERROR)
optp.add_option('-d','--debug', help='set logging to DEBUG',
action='store_const',
dest='loglevel',
const=logging.DEBUG,
default=logging.ERROR)
optp.add_option('-v','--verbose', help='set logging to COMM',
action='store_const',
dest='loglevel',
const=5,
default=logging.ERROR)
# JID and password options.
optp.add_option("-j", "--jid", dest="jid",
help="JID to use")
optp.add_option("-p", "--password", dest="password",
help="password to use")
opts,args = optp.parse_args()
# Setup logging.
logging.basicConfig(level=opts.loglevel,
format='%(levelname)-8s %(message)s')
if len(args) < 2:
optp.print_help()
exit()
if len(args) == 2:
args = (args[0], args[1], '')
if opts.jid is None:
opts.jid = raw_input("Username: ")
if opts.password is None:
opts.password = getpass.getpass("Password: ")
# Setup the Disco browser.
xmpp = Disco(opts.jid, opts.password, args[1], args[2], args[0])
# If you are working with an OpenFire server, you may need
# to adjust the SSL version used:
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
# If you want to verify the SSL certificates offered by a server:
# xmpp.ca_certs = "path/to/ca/cert"
# Connect to the XMPP server and start processing XMPP stanzas.
if xmpp.connect():
# If you do not have the pydns library installed, you will need
# to manually specify the name of the server if it does not match
# the one in the JID. For example, to use Google Talk you would
# need to use:
#
# if xmpp.connect(('talk.google.com', 5222)):
# ...
xmpp.process(threaded=False)
else:
print("Unable to connect.")

View file

@ -118,6 +118,13 @@ if __name__ == '__main__':
xmpp.registerPlugin('xep_0060') # PubSub xmpp.registerPlugin('xep_0060') # PubSub
xmpp.registerPlugin('xep_0199') # XMPP Ping xmpp.registerPlugin('xep_0199') # XMPP Ping
# If you are working with an OpenFire server, you may need
# to adjust the SSL version used:
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
# If you want to verify the SSL certificates offered by a server:
# xmpp.ca_certs = "path/to/ca/cert"
# Connect to the XMPP server and start processing XMPP stanzas. # Connect to the XMPP server and start processing XMPP stanzas.
if xmpp.connect(): if xmpp.connect():
# If you do not have the pydns library installed, you will need # If you do not have the pydns library installed, you will need

View file

@ -38,13 +38,15 @@ CLASSIFIERS = [ 'Intended Audience :: Developers',
] ]
packages = [ 'sleekxmpp', packages = [ 'sleekxmpp',
'sleekxmpp/plugins',
'sleekxmpp/stanza', 'sleekxmpp/stanza',
'sleekxmpp/test', 'sleekxmpp/test',
'sleekxmpp/xmlstream', 'sleekxmpp/xmlstream',
'sleekxmpp/xmlstream/matcher', 'sleekxmpp/xmlstream/matcher',
'sleekxmpp/xmlstream/handler', 'sleekxmpp/xmlstream/handler',
'sleekxmpp/thirdparty', 'sleekxmpp/thirdparty',
'sleekxmpp/plugins',
'sleekxmpp/plugins/xep_0030',
'sleekxmpp/plugins/xep_0030/stanza'
] ]
if sys.version_info < (3, 0): if sys.version_info < (3, 0):

View file

@ -271,7 +271,7 @@ class BaseXMPP(XMLStream):
"""Create a Presence stanza associated with this stream.""" """Create a Presence stanza associated with this stream."""
return Presence(self, *args, **kwargs) return Presence(self, *args, **kwargs)
def make_iq(self, id=0, ifrom=None): def make_iq(self, id=0, ifrom=None, ito=None, type=None, query=None):
""" """
Create a new Iq stanza with a given Id and from JID. Create a new Iq stanza with a given Id and from JID.
@ -279,11 +279,19 @@ class BaseXMPP(XMLStream):
id -- An ideally unique ID value for this stanza thread. id -- An ideally unique ID value for this stanza thread.
Defaults to 0. Defaults to 0.
ifrom -- The from JID to use for this stanza. ifrom -- The from JID to use for this stanza.
ito -- The destination JID for this stanza.
type -- The Iq's type, one of: get, set, result, or error.
query -- Optional namespace for adding a query element.
""" """
return self.Iq()._set_stanza_values({'id': str(id), iq = self.Iq()
'from': ifrom}) iq['id'] = str(id)
iq['to'] = ito
iq['from'] = ifrom
iq['type'] = itype
iq['query'] = query
return iq
def make_iq_get(self, queryxmlns=None): def make_iq_get(self, queryxmlns=None, ito=None, ifrom=None, iq=None):
""" """
Create an Iq stanza of type 'get'. Create an Iq stanza of type 'get'.
@ -291,21 +299,45 @@ class BaseXMPP(XMLStream):
Arguments: Arguments:
queryxmlns -- The namespace of the query to use. queryxmlns -- The namespace of the query to use.
ito -- The destination JID for this stanza.
ifrom -- The from JID to use for this stanza.
iq -- Optionally use an existing stanza instead
of generating a new one.
""" """
return self.Iq()._set_stanza_values({'type': 'get', if not iq:
'query': queryxmlns}) iq = self.Iq()
iq['type'] = 'get'
iq['query'] = queryxmlns
if ito:
iq['to'] = ito
if ifrom:
iq['from'] = ifrom
return iq
def make_iq_result(self, id): def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None):
""" """
Create an Iq stanza of type 'result' with the given ID value. Create an Iq stanza of type 'result' with the given ID value.
Arguments: Arguments:
id -- An ideally unique ID value. May use self.new_id(). id -- An ideally unique ID value. May use self.new_id().
ito -- The destination JID for this stanza.
ifrom -- The from JID to use for this stanza.
iq -- Optionally use an existing stanza instead
of generating a new one.
""" """
return self.Iq()._set_stanza_values({'id': id, if not iq:
'type': 'result'}) iq = self.Iq()
if id is None:
id = self.new_id()
iq['id'] = id
iq['type'] = 'result'
if ito:
iq['to'] = ito
if ifrom:
iq['from'] = ifrom
return iq
def make_iq_set(self, sub=None): def make_iq_set(self, sub=None, ito=None, ifrom=None, iq=None):
""" """
Create an Iq stanza of type 'set'. Create an Iq stanza of type 'set'.
@ -313,15 +345,26 @@ class BaseXMPP(XMLStream):
stanza's payload. stanza's payload.
Arguments: Arguments:
sub -- A stanza or XML object to use as the Iq's payload. sub -- A stanza or XML object to use as the Iq's payload.
ito -- The destination JID for this stanza.
ifrom -- The from JID to use for this stanza.
iq -- Optionally use an existing stanza instead
of generating a new one.
""" """
iq = self.Iq()._set_stanza_values({'type': 'set'}) if not iq:
iq = self.Iq()
iq['type'] = 'set'
if sub != None: if sub != None:
iq.append(sub) iq.append(sub)
if ito:
iq['to'] = ito
if ifrom:
iq['from'] = ifrom
return iq return iq
def make_iq_error(self, id, type='cancel', def make_iq_error(self, id, type='cancel',
condition='feature-not-implemented', text=None): condition='feature-not-implemented',
text=None, ito=None, ifrom=None, iq=None):
""" """
Create an Iq stanza of type 'error'. Create an Iq stanza of type 'error'.
@ -332,14 +375,24 @@ class BaseXMPP(XMLStream):
condition -- The error condition. condition -- The error condition.
Defaults to 'feature-not-implemented'. Defaults to 'feature-not-implemented'.
text -- A message describing the cause of the error. text -- A message describing the cause of the error.
ito -- The destination JID for this stanza.
ifrom -- The from JID to use for this stanza.
iq -- Optionally use an existing stanza instead
of generating a new one.
""" """
iq = self.Iq()._set_stanza_values({'id': id}) if not iq:
iq['error']._set_stanza_values({'type': type, iq = self.Iq()
'condition': condition, iq['id'] = id
'text': text}) iq['error']['type'] = type
iq['error']['condition'] = condition
iq['error']['text'] = text
if ito:
iq['to'] = ito
if ifrom:
iq['from'] = ifrom
return iq return iq
def make_iq_query(self, iq=None, xmlns=''): def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None):
""" """
Create or modify an Iq stanza to use the given Create or modify an Iq stanza to use the given
query namespace. query namespace.
@ -348,10 +401,16 @@ class BaseXMPP(XMLStream):
iq -- Optional Iq stanza to modify. A new iq -- Optional Iq stanza to modify. A new
stanza is created otherwise. stanza is created otherwise.
xmlns -- The query's namespace. xmlns -- The query's namespace.
ito -- The destination JID for this stanza.
ifrom -- The from JID to use for this stanza.
""" """
if not iq: if not iq:
iq = self.Iq() iq = self.Iq()
iq['query'] = xmlns iq['query'] = xmlns
if ito:
iq['to'] = ito
if ifrom:
iq['from'] = ifrom
return iq return iq
def make_query_roster(self, iq=None): def make_query_roster(self, iq=None):

View file

@ -5,6 +5,6 @@
See the file LICENSE for copying permission. See the file LICENSE for copying permission.
""" """
__all__ = ['xep_0004', 'xep_0012', 'xep_0030', 'xep_0033', 'xep_0045', __all__ = ['xep_0004', 'xep_0009', 'xep_0012', 'xep_0030', 'xep_0033',
'xep_0050', 'xep_0085', 'xep_0092', 'xep_0199', 'gmail_notify', 'xep_0045', 'xep_0050', 'xep_0060', 'xep_0085', 'xep_0086',
'xep_0060', 'xep_0202'] 'xep_0092', 'xep_0128', 'xep_0199', 'xep_0202', 'gmail_notify']

View file

@ -143,7 +143,7 @@ class gmail_notify(base.base_plugin):
log.info('Gmail: Searching for emails matching: "%s"' % query) log.info('Gmail: Searching for emails matching: "%s"' % query)
iq = self.xmpp.Iq() iq = self.xmpp.Iq()
iq['type'] = 'get' iq['type'] = 'get'
iq['to'] = self.xmpp.jid iq['to'] = self.xmpp.boundjid.bare
iq['gmail']['q'] = query iq['gmail']['q'] = query
iq['gmail']['newer-than-time'] = newer iq['gmail']['newer-than-time'] = newer
return iq.send() return iq.send()

View file

@ -1,7 +1,6 @@
from . import base from . import base
import logging import logging
from xml.etree import cElementTree as ET from xml.etree import cElementTree as ET
import types
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -43,7 +42,7 @@ class jobs(base.base_plugin):
iq['psstate']['item'] = jobid iq['psstate']['item'] = jobid
iq['psstate']['payload'] = state iq['psstate']['payload'] = state
result = iq.send() result = iq.send()
if result is None or type(result) == types.BooleanType or result['type'] != 'result': if result is None or type(result) == bool or result['type'] != 'result':
log.error("Unable to change %s:%s to %s" % (node, jobid, state)) log.error("Unable to change %s:%s to %s" % (node, jobid, state))
return False return False
return True return True

View file

@ -13,7 +13,6 @@ from .. xmlstream.handler.callback import Callback
from .. xmlstream.matcher.xpath import MatchXPath from .. xmlstream.matcher.xpath import MatchXPath
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from .. stanza.message import Message from .. stanza.message import Message
import types
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -203,7 +202,7 @@ class Form(ElementBase):
def merge(self, other): def merge(self, other):
new = copy.copy(self) new = copy.copy(self)
if type(other) == types.DictType: if type(other) == dict:
new.setValues(other) new.setValues(other)
return new return new
nfields = new.getFields(use_dict=True) nfields = new.getFields(use_dict=True)

View file

@ -26,30 +26,66 @@ class xep_0030(base_plugin):
""" """
XEP-0030: Service Discovery XEP-0030: Service Discovery
Service discovery in XMPP allows entities to discover information about
other agents in the network, such as the feature sets supported by a
client, or signposts to other, related entities.
Also see <http://www.xmpp.org/extensions/xep-0030.html>.
The XEP-0030 plugin works using a hierarchy of dynamic
node handlers, ranging from global handlers to specific
JID+node handlers. The default set of handlers operate
in a static manner, storing disco information in memory.
However, custom handlers may use any available backend
storage mechanism desired, such as SQLite or Redis.
Node handler hierarchy:
JID | Node | Level
---------------------
None | None | Global
Given | None | All nodes for the JID
None | Given | Node on self.xmpp.boundjid
Given | Given | A single node
Stream Handlers: Stream Handlers:
Disco Info -- Disco Info -- Any Iq stanze that includes a query with the
Disco Items -- namespace http://jabber.org/protocol/disco#info.
Disco Items -- Any Iq stanze that includes a query with the
namespace http://jabber.org/protocol/disco#items.
Events: Events:
disco_info -- disco_info -- Received a disco#info Iq query result.
disco_items -- disco_items -- Received a disco#items Iq query result.
disco_info_query -- disco_info_query -- Received a disco#info Iq query request.
disco_items_query -- disco_items_query -- Received a disco#items Iq query request.
Attributes:
stanza -- A reference to the module containing the stanza classes
provided by this plugin.
static -- Object containing the default set of static node handlers.
xmpp -- The main SleekXMPP object.
Methods: Methods:
set_node_handler -- set_node_handler -- Assign a handler to a JID/node combination.
del_node_handler -- del_node_handler -- Remove a handler from a JID/node combination.
add_identity -- get_info -- Retrieve disco#info data, locally or remote.
get_items -- Retrieve disco#items data, locally or remote.
set_identities --
set_features --
set_items --
del_items --
del_identity -- del_identity --
add_feature --
del_feature -- del_feature --
add_item --
del_item -- del_item --
get_info -- add_identity --
get_items -- add_feature --
add_item --
""" """
def plugin_init(self): def plugin_init(self):
"""
Start the XEP-0030 plugin.
"""
self.xep = '0030' self.xep = '0030'
self.description = 'Service Discovery' self.description = 'Service Discovery'
self.stanza = sleekxmpp.plugins.xep_0030.stanza self.stanza = sleekxmpp.plugins.xep_0030.stanza
@ -70,42 +106,89 @@ class xep_0030(base_plugin):
self.static = StaticDisco(self.xmpp) self.static = StaticDisco(self.xmpp)
self._disco_ops = ['get_info', 'set_identities', 'set_features', self._disco_ops = ['get_info', 'set_identities', 'set_features',
'del_info', 'get_items', 'set_items', 'del_items', 'get_items', 'set_items', 'del_items',
'add_identity', 'del_identity', 'add_feature', 'add_identity', 'del_identity', 'add_feature',
'del_feature', 'add_item', 'del_item'] 'del_feature', 'add_item', 'del_item']
self.handlers = {} self._handlers = {}
for op in self._disco_ops: for op in self._disco_ops:
self.handlers[op] = {'global': getattr(self.static, op), self._handlers[op] = {'global': getattr(self.static, op),
'jid': {}, 'jid': {},
'node': {}} 'node': {}}
def set_node_handler(self, htype, jid=None, node=None, handler=None): def set_node_handler(self, htype, jid=None, node=None, handler=None):
""" """
Add a node handler for the given hierarchy level and
handler type.
Node handlers are ordered in a hierarchy where the
most specific handler is executed. Thus, a fallback,
global handler can be used for the majority of cases
with a few node specific handler that override the
global behavior.
Node handler hierarchy:
JID | Node | Level
---------------------
None | None | Global
Given | None | All nodes for the JID
None | Given | Node on self.xmpp.boundjid
Given | Given | A single node
Handler types:
get_info
get_items
set_identities
set_features
set_items
del_items
del_identity
del_feature
del_item
add_identity
add_feature
add_item
Arguments: Arguments:
htype htype -- The operation provided by the handler.
jid jid -- The JID the handler applies to. May be narrowed
node further if a node is given.
handler node -- The particular node the handler is for. If no JID
is given, then the self.xmpp.boundjid.full is
assumed.
handler -- The handler function to use.
""" """
if htype not in self._disco_ops: if htype not in self._disco_ops:
return return
if jid is None and node is None: if jid is None and node is None:
self.handlers[htype]['global'] = handler self._handlers[htype]['global'] = handler
elif node is None: elif node is None:
self.handlers[htype]['jid'][jid] = handler self._handlers[htype]['jid'][jid] = handler
elif jid is None: elif jid is None:
jid = self.xmpp.boundjid.full jid = self.xmpp.boundjid.full
self.handlers[htype]['node'][(jid, node)] = handler self._handlers[htype]['node'][(jid, node)] = handler
else: else:
self.handlers[htype]['node'][(jid, node)] = handler self._handlers[htype]['node'][(jid, node)] = handler
def del_node_handler(self, htype, jid, node): def del_node_handler(self, htype, jid, node):
""" """
Remove a handler type for a JID and node combination.
The next handler in the hierarchy will be used if one
exists. If removing the global handler, make sure that
other handlers exist to process existing nodes.
Node handler hierarchy:
JID | Node | Level
---------------------
None | None | Global
Given | None | All nodes for the JID
None | Given | Node on self.xmpp.boundjid
Given | Given | A single node
Arguments: Arguments:
htype htype -- The type of handler to remove.
jid jid -- The JID from which to remove the handler.
node node -- The node from which to remove the handler.
""" """
self.set_node_handler(htype, jid, node, None) self.set_node_handler(htype, jid, node, None)
@ -132,14 +215,28 @@ class xep_0030(base_plugin):
def get_info(self, jid=None, node=None, local=False, **kwargs): def get_info(self, jid=None, node=None, local=False, **kwargs):
""" """
Retrieve the disco#info results from a given JID/node combination.
Info may be retrieved from both local resources and remote agents;
the local parameter indicates if the information should be gathered
by executing the local node handlers, or if a disco#info stanza
must be generated and sent.
Arguments: Arguments:
jid -- jid -- Request info from this JID.
node -- node -- The particular node to query.
local -- local -- If true, then the query is for a JID/node
dfrom -- combination handled by this Sleek instance and
block -- no stanzas need to be sent.
timeout -- Otherwise, a disco stanza must be sent to the
callback -- remove JID to retrieve the info.
dfrom -- Specifiy the sender's JID.
block -- If true, block and wait for the stanzas' reply.
timeout -- The time in seconds to block while waiting for
a reply. If None, then wait indefinitely.
callback -- Optional callback to execute when a reply is
received instead of blocking and waiting for
the reply.
""" """
if local or jid is None: if local or jid is None:
log.debug("Looking up local disco#info data " + \ log.debug("Looking up local disco#info data " + \
@ -158,14 +255,28 @@ class xep_0030(base_plugin):
def get_items(self, jid=None, node=None, local=False, **kwargs): def get_items(self, jid=None, node=None, local=False, **kwargs):
""" """
Retrieve the disco#items results from a given JID/node combination.
Items may be retrieved from both local resources and remote agents;
the local parameter indicates if the items should be gathered by
executing the local node handlers, or if a disco#items stanza must
be generated and sent.
Arguments: Arguments:
jid -- jid -- Request info from this JID.
node -- node -- The particular node to query.
local -- local -- If true, then the query is for a JID/node
dfrom -- combination handled by this Sleek instance and
block -- no stanzas need to be sent.
timeout -- Otherwise, a disco stanza must be sent to the
callback -- remove JID to retrieve the items.
dfrom -- Specifiy the sender's JID.
block -- If true, block and wait for the stanzas' reply.
timeout -- The time in seconds to block while waiting for
a reply. If None, then wait indefinitely.
callback -- Optional callback to execute when a reply is
received instead of blocking and waiting for
the reply.
""" """
if local or jid is None: if local or jid is None:
return self._run_node_handler('get_items', jid, node, kwargs) return self._run_node_handler('get_items', jid, node, kwargs)
@ -179,37 +290,169 @@ class xep_0030(base_plugin):
block=kwargs.get('block', None), block=kwargs.get('block', None),
callback=kwargs.get('callback', None)) callback=kwargs.get('callback', None))
def set_info(self, jid=None, node=None, **kwargs):
self._run_node_handler('set_info', jid, node, kwargs)
def del_info(self, jid=None, node=None, **kwargs):
self._run_node_handler('del_info', jid, node, kwargs)
def set_items(self, jid=None, node=None, **kwargs): def set_items(self, jid=None, node=None, **kwargs):
"""
Set or replace all items for the specified JID/node combination.
The given items must be in a list or set where each item is a
tuple of the form: (jid, node, name).
Arguments:
jid -- The JID to modify.
node -- Optional node to modify.
items -- A series of items in tuple format.
"""
self._run_node_handler('set_items', jid, node, kwargs) self._run_node_handler('set_items', jid, node, kwargs)
def del_items(self, jid=None, node=None, **kwargs): def del_items(self, jid=None, node=None, **kwargs):
"""
Remove all items from the given JID/node combination.
Arguments:
jid -- The JID to modify.
node -- Optional node to modify.
"""
self._run_node_handler('del_items', jid, node, kwargs) self._run_node_handler('del_items', jid, node, kwargs)
def add_identity(self, jid=None, node=None, **kwargs):
self._run_node_handler('add_identity', jid, node, kwargs)
def add_feature(self, jid=None, node=None, **kwargs):
self._run_node_handler('add_feature', jid, node, kwargs)
def del_identity(self, jid=None, node=None, **kwargs):
self._run_node_handler('del_identity', jid, node, kwargs)
def del_feature(self, jid=None, node=None, **kwargs):
self._run_node_handler('del_feature', jid, node, kwargs)
def add_item(self, jid=None, node=None, **kwargs): def add_item(self, jid=None, node=None, **kwargs):
"""
Add a new item element to the given JID/node combination.
Each item is required to have a JID, but may also specify
a node value to reference non-addressable entities.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
ijid -- The JID for the item.
inode -- Optional node for the item.
name -- Optional name for the item.
"""
self._run_node_handler('add_item', jid, node, kwargs) self._run_node_handler('add_item', jid, node, kwargs)
def del_item(self, jid=None, node=None, **kwargs): def del_item(self, jid=None, node=None, **kwargs):
"""
Remove a single item from the given JID/node combination.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
ijid -- The item's JID.
inode -- The item's node.
"""
self._run_node_handler('del_item', jid, node, kwargs) self._run_node_handler('del_item', jid, node, kwargs)
def _run_node_handler(self, htype, jid, node, data=None): def add_identity(self, jid=None, node=None, **kwargs):
"""
Add a new identity to the given JID/node combination.
Each identity must be unique in terms of all four identity
components: category, type, name, and language.
Multiple, identical category/type pairs are allowed only
if the xml:lang values are different. Likewise, multiple
category/type/xml:lang pairs are allowed so long as the
names are different. A category and type is always required.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
category -- The identity's category.
itype -- The identity's type.
name -- Optional name for the identity.
lang -- Optional two-letter language code.
"""
self._run_node_handler('add_identity', jid, node, kwargs)
def add_feature(self, jid=None, node=None, **kwargs):
"""
Add a feature to a JID/node combination.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
feature -- The namespace of the supported feature.
"""
self._run_node_handler('add_feature', jid, node, kwargs)
def del_identity(self, jid=None, node=None, **kwargs):
"""
Remove an identity from the given JID/node combination.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
category -- The identity's category.
itype -- The identity's type value.
name -- Optional, human readable name for the identity.
lang -- Optional, the identity's xml:lang value.
"""
self._run_node_handler('del_identity', jid, node, kwargs)
def del_feature(self, jid=None, node=None, **kwargs):
"""
Remove a feature from a given JID/node combination.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
feature -- The feature's namespace.
"""
self._run_node_handler('del_feature', jid, node, kwargs)
def set_identities(self, jid=None, node=None, **kwargs):
"""
Add or replace all identities for the given JID/node combination.
The identities must be in a set where each identity is a tuple
of the form: (category, type, lang, name)
Arguments:
jid -- The JID to modify.
node -- The node to modify.
identities -- A set of identities in tuple form.
lang -- Optional, xml:lang value.
"""
self._run_node_handler('set_identities', jid, node, kwargs)
def del_identities(self, jid=None, node=None, **kwargs):
"""
Remove all identities for a JID/node combination.
If a language is specified, only identities using that
language will be removed.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
lang -- Optional. If given, only remove identities
using this xml:lang value.
"""
self._run_node_handler('del_identities', jid, node, kwargs)
def set_features(self, jid=None, node=None, **kwargs):
"""
Add or replace the set of supported features
for a JID/node combination.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
features -- The new set of supported features.
"""
self._run_node_handler('set_features', jid, node, kwargs)
def del_features(self, jid=None, node=None, **kwargs):
"""
Remove all features from a JID/node combination.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
"""
self._run_node_handler('del_features', jid, node, kwargs)
def _run_node_handler(self, htype, jid, node, data={}):
""" """
Execute the most specific node handler for the given Execute the most specific node handler for the given
JID/node combination. JID/node combination.
@ -218,19 +461,19 @@ class xep_0030(base_plugin):
htype -- The handler type to execute. htype -- The handler type to execute.
jid -- The JID requested. jid -- The JID requested.
node -- The node requested. node -- The node requested.
dat -- Optional, custom data to pass to the handler. data -- Optional, custom data to pass to the handler.
""" """
if jid is None: if jid is None:
jid = self.xmpp.boundjid.full jid = self.xmpp.boundjid.full
if node is None: if node is None:
node = '' node = ''
if self.handlers[htype]['node'].get((jid, node), False): if self._handlers[htype]['node'].get((jid, node), False):
return self.handlers[htype]['node'][(jid, node)](jid, node, data) return self._handlers[htype]['node'][(jid, node)](jid, node, data)
elif self.handlers[htype]['jid'].get(jid, False): elif self._handlers[htype]['jid'].get(jid, False):
return self.handlers[htype]['jid'][jid](jid, node, data) return self._handlers[htype]['jid'][jid](jid, node, data)
elif self.handlers[htype]['global']: elif self._handlers[htype]['global']:
return self.handlers[htype]['global'](jid, node, data) return self._handlers[htype]['global'](jid, node, data)
else: else:
return None return None
@ -311,4 +554,3 @@ class xep_0030(base_plugin):
"Using default disco#info feature.") "Using default disco#info feature.")
info.add_feature(info.namespace) info.add_feature(info.namespace)
return info return info

View file

@ -12,8 +12,6 @@ from sleekxmpp.xmlstream import ElementBase, ET
class DiscoItems(ElementBase): class DiscoItems(ElementBase):
""" """
Example disco#items stanzas: Example disco#items stanzas:
<iq type="get"> <iq type="get">
<query xmlns="http://jabber.org/protocol/disco#items" /> <query xmlns="http://jabber.org/protocol/disco#items" />

View file

@ -35,6 +35,11 @@ class StaticDisco(object):
def __init__(self, xmpp): def __init__(self, xmpp):
""" """
Create a static disco interface. Sets of disco#info and
disco#items are maintained for every given JID and node
combination. These stanzas are used to store disco
information in memory without any additional processing.
Arguments: Arguments:
xmpp -- The main SleekXMPP object. xmpp -- The main SleekXMPP object.
""" """
@ -52,7 +57,7 @@ class StaticDisco(object):
self.nodes[(jid, node)]['info']['node'] = node self.nodes[(jid, node)]['info']['node'] = node
self.nodes[(jid, node)]['items']['node'] = node self.nodes[(jid, node)]['items']['node'] = node
def get_info(self, jid, node, data=None): def get_info(self, jid, node, data):
if (jid, node) not in self.nodes: if (jid, node) not in self.nodes:
if not node: if not node:
return DiscoInfo() return DiscoInfo()
@ -61,11 +66,11 @@ class StaticDisco(object):
else: else:
return self.nodes[(jid, node)]['info'] return self.nodes[(jid, node)]['info']
def del_info(self, jid, node, data=None): def del_info(self, jid, node, data):
if (jid, node) in self.nodes: if (jid, node) in self.nodes:
self.nodes[(jid, node)]['info'] = DiscoInfo() self.nodes[(jid, node)]['info'] = DiscoInfo()
def get_items(self, jid, node, data=None): def get_items(self, jid, node, data):
if (jid, node) not in self.nodes: if (jid, node) not in self.nodes:
if not node: if not node:
return DiscoInfo() return DiscoInfo()
@ -74,14 +79,16 @@ class StaticDisco(object):
else: else:
return self.nodes[(jid, node)]['items'] return self.nodes[(jid, node)]['items']
def set_items(self, jid, node, data=None): def set_items(self, jid, node, data):
pass items = data.get('items', set())
self.add_node(jid, node)
self.nodes[(jid, node)]['items']['items'] = items
def del_items(self, jid, node, data=None): def del_items(self, jid, node, data):
if (jid, node) in self.nodes: if (jid, node) in self.nodes:
self.nodes[(jid, node)]['items'] = DiscoItems() self.nodes[(jid, node)]['items'] = DiscoItems()
def add_identity(self, jid, node, data={}): def add_identity(self, jid, node, data):
self.add_node(jid, node) self.add_node(jid, node)
self.nodes[(jid, node)]['info'].add_identity( self.nodes[(jid, node)]['info'].add_identity(
data.get('category', ''), data.get('category', ''),
@ -89,10 +96,12 @@ class StaticDisco(object):
data.get('name', None), data.get('name', None),
data.get('lang', None)) data.get('lang', None))
def set_identities(self, jid, node, data=None): def set_identities(self, jid, node, data):
pass identities = data.get('identities', set())
self.add_node(jid, node)
self.nodes[(jid, node)]['info']['identities'] = identities
def del_identity(self, jid, node, data=None): def del_identity(self, jid, node, data):
if (jid, node) not in self.nodes: if (jid, node) not in self.nodes:
return return
self.nodes[(jid, node)]['info'].del_identity( self.nodes[(jid, node)]['info'].del_identity(
@ -101,27 +110,29 @@ class StaticDisco(object):
data.get('name', None), data.get('name', None),
data.get('lang', None)) data.get('lang', None))
def add_feature(self, jid, node, data):
def add_feature(self, jid, node, data=None):
self.add_node(jid, node) self.add_node(jid, node)
self.nodes[(jid, node)]['info'].add_feature(data.get('feature', '')) self.nodes[(jid, node)]['info'].add_feature(data.get('feature', ''))
def set_features(self, jid, node, data=None): def set_features(self, jid, node, data):
pass features = data.get('features', set())
self.add_node(jid, node)
self.nodes[(jid, node)]['info']['features'] = features
def del_feature(self, jid, node, data=None): def del_feature(self, jid, node, data):
if (jid, node) not in self.nodes: if (jid, node) not in self.nodes:
return return
self.nodes[(jid, node)]['info'].del_feature(data.get('feature', '')) self.nodes[(jid, node)]['info'].del_feature(data.get('feature', ''))
def add_item(self, jid, node, data=None): def add_item(self, jid, node, data):
self.add_node(jid, node) self.add_node(jid, node)
self.nodes[(jid, node)]['items'].add_item( self.nodes[(jid, node)]['items'].add_item(
data.get('ijid', ''), data.get('ijid', ''),
node=data.get('inode', None), node=data.get('inode', None),
name=data.get('name', None)) name=data.get('name', None))
def del_item(self, jid, node, data=None): def del_item(self, jid, node, data):
if (jid, node) in self.nodes: if (jid, node) in self.nodes:
self.nodes[(jid, node)]['items'].del_item(**data) self.nodes[(jid, node)]['items'].del_item(
data.get('ijid', ''),
node=data.get('inode', None))

View file

@ -20,325 +20,333 @@ log = logging.getLogger(__name__)
class MUCPresence(ElementBase): class MUCPresence(ElementBase):
name = 'x' name = 'x'
namespace = 'http://jabber.org/protocol/muc#user' namespace = 'http://jabber.org/protocol/muc#user'
plugin_attrib = 'muc' plugin_attrib = 'muc'
interfaces = set(('affiliation', 'role', 'jid', 'nick', 'room')) interfaces = set(('affiliation', 'role', 'jid', 'nick', 'room'))
affiliations = set(('', )) affiliations = set(('', ))
roles = set(('', )) roles = set(('', ))
def getXMLItem(self): def getXMLItem(self):
item = self.xml.find('{http://jabber.org/protocol/muc#user}item') item = self.xml.find('{http://jabber.org/protocol/muc#user}item')
if item is None: if item is None:
item = ET.Element('{http://jabber.org/protocol/muc#user}item') item = ET.Element('{http://jabber.org/protocol/muc#user}item')
self.xml.append(item) self.xml.append(item)
return item return item
def getAffiliation(self): def getAffiliation(self):
#TODO if no affilation, set it to the default and return default #TODO if no affilation, set it to the default and return default
item = self.getXMLItem() item = self.getXMLItem()
return item.get('affiliation', '') return item.get('affiliation', '')
def setAffiliation(self, value): def setAffiliation(self, value):
item = self.getXMLItem() item = self.getXMLItem()
#TODO check for valid affiliation #TODO check for valid affiliation
item.attrib['affiliation'] = value item.attrib['affiliation'] = value
return self return self
def delAffiliation(self): def delAffiliation(self):
item = self.getXMLItem() item = self.getXMLItem()
#TODO set default affiliation #TODO set default affiliation
if 'affiliation' in item.attrib: del item.attrib['affiliation'] if 'affiliation' in item.attrib: del item.attrib['affiliation']
return self return self
def getJid(self): def getJid(self):
item = self.getXMLItem() item = self.getXMLItem()
return JID(item.get('jid', '')) return JID(item.get('jid', ''))
def setJid(self, value): def setJid(self, value):
item = self.getXMLItem() item = self.getXMLItem()
if not isinstance(value, str): if not isinstance(value, str):
value = str(value) value = str(value)
item.attrib['jid'] = value item.attrib['jid'] = value
return self return self
def delJid(self): def delJid(self):
item = self.getXMLItem() item = self.getXMLItem()
if 'jid' in item.attrib: del item.attrib['jid'] if 'jid' in item.attrib: del item.attrib['jid']
return self return self
def getRole(self): def getRole(self):
item = self.getXMLItem() item = self.getXMLItem()
#TODO get default role, set default role if none #TODO get default role, set default role if none
return item.get('role', '') return item.get('role', '')
def setRole(self, value): def setRole(self, value):
item = self.getXMLItem() item = self.getXMLItem()
#TODO check for valid role #TODO check for valid role
item.attrib['role'] = value item.attrib['role'] = value
return self return self
def delRole(self): def delRole(self):
item = self.getXMLItem() item = self.getXMLItem()
#TODO set default role #TODO set default role
if 'role' in item.attrib: del item.attrib['role'] if 'role' in item.attrib: del item.attrib['role']
return self return self
def getNick(self): def getNick(self):
return self.parent()['from'].resource return self.parent()['from'].resource
def getRoom(self): def getRoom(self):
return self.parent()['from'].bare return self.parent()['from'].bare
def setNick(self, value): def setNick(self, value):
log.warning("Cannot set nick through mucpresence plugin.") log.warning("Cannot set nick through mucpresence plugin.")
return self return self
def setRoom(self, value): def setRoom(self, value):
log.warning("Cannot set room through mucpresence plugin.") log.warning("Cannot set room through mucpresence plugin.")
return self return self
def delNick(self): def delNick(self):
log.warning("Cannot delete nick through mucpresence plugin.") log.warning("Cannot delete nick through mucpresence plugin.")
return self return self
def delRoom(self): def delRoom(self):
log.warning("Cannot delete room through mucpresence plugin.") log.warning("Cannot delete room through mucpresence plugin.")
return self return self
class xep_0045(base.base_plugin): class xep_0045(base.base_plugin):
""" """
Impliments XEP-0045 Multi User Chat Implements XEP-0045 Multi User Chat
""" """
def plugin_init(self): def plugin_init(self):
self.rooms = {} self.rooms = {}
self.ourNicks = {} self.ourNicks = {}
self.xep = '0045' self.xep = '0045'
self.description = 'Multi User Chat' self.description = 'Multi User Chat'
# load MUC support in presence stanzas # load MUC support in presence stanzas
registerStanzaPlugin(Presence, MUCPresence) registerStanzaPlugin(Presence, MUCPresence)
self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence)) 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('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)) self.xmpp.registerHandler(Callback('MUCSubject', MatchXMLMask("<message xmlns='%s' type='groupchat'><subject/></message>" % self.xmpp.default_ns), self.handle_groupchat_subject))
self.xmpp.registerHandler(Callback('MUCInvite', MatchXPath("{%s}message/{http://jabber.org/protocol/muc#user}x/invite" % self.xmpp.default_ns), self.handle_groupchat_invite))
def handle_groupchat_presence(self, pr): def handle_groupchat_invite(self, inv):
""" Handle a presence in a muc. """ Handle an invite into a muc.
""" """
got_offline = False logging.debug("MUC invite to %s from %s: %s" % (inv['from'], inv["from"], inv))
got_online = False if inv['from'] not in self.rooms.keys():
if pr['muc']['room'] not in self.rooms.keys(): self.xmpp.event("groupchat_invite", inv)
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): def handle_groupchat_presence(self, pr):
""" Handle a message event in a muc. """ Handle a presence in a muc.
""" """
self.xmpp.event('groupchat_message', msg) got_offline = False
self.xmpp.event("muc::%s::message" % msg['from'].bare, msg) 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_subject(self, msg): def handle_groupchat_message(self, msg):
""" Handle a message coming from a muc indicating """ Handle a message event in a muc.
a change of subject (or announcing it when joining the room) """
""" self.xmpp.event('groupchat_message', msg)
self.xmpp.event('groupchat_subject', msg) self.xmpp.event("muc::%s::message" % msg['from'].bare, msg)
def jidInRoom(self, room, jid): def handle_groupchat_subject(self, msg):
for nick in self.rooms[room]: """ Handle a message coming from a muc indicating
entry = self.rooms[room][nick] a change of subject (or announcing it when joining the room)
if entry is not None and entry['jid'].full == jid: """
return True self.xmpp.event('groupchat_subject', msg)
return False
def getNick(self, room, jid): def jidInRoom(self, room, jid):
for nick in self.rooms[room]: for nick in self.rooms[room]:
entry = self.rooms[room][nick] entry = self.rooms[room][nick]
if entry is not None and entry['jid'].full == jid: if entry is not None and entry['jid'].full == jid:
return nick return True
return False
def getRoomForm(self, room, ifrom=None): def getNick(self, room, jid):
iq = self.xmpp.makeIqGet() for nick in self.rooms[room]:
iq['to'] = room entry = self.rooms[room][nick]
if ifrom is not None: if entry is not None and entry['jid'].full == jid:
iq['from'] = ifrom return nick
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): def getRoomForm(self, room, ifrom=None):
if form is None: iq = self.xmpp.makeIqGet()
form = self.getRoomForm(room, ifrom=ifrom) iq['to'] = room
#form = self.xmpp.plugin['old_0004'].makeForm(ftype='submit') if ifrom is not None:
#form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig') iq['from'] = ifrom
iq = self.xmpp.makeIqSet() query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
iq['to'] = room iq.append(query)
if ifrom is not None: result = iq.send()
iq['from'] = ifrom if result['type'] == 'error':
query = ET.Element('{http://jabber.org/protocol/muc#owner}query') return False
form = form.getXML('submit') xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
query.append(form) if xform is None: return False
iq.append(query) form = self.xmpp.plugin['old_0004'].buildForm(xform)
result = iq.send() return form
if result['type'] == 'error':
return False
return True
def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None): def configureRoom(self, room, form=None, ifrom=None):
""" Join the specified room, requesting 'maxhistory' lines of history. if form is None:
""" form = self.getRoomForm(room, ifrom=ifrom)
stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow) #form = self.xmpp.plugin['old_0004'].makeForm(ftype='submit')
x = ET.Element('{http://jabber.org/protocol/muc}x') #form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig')
if password: iq = self.xmpp.makeIqSet()
passelement = ET.Element('password') iq['to'] = room
passelement.text = password if ifrom is not None:
x.append(passelement) iq['from'] = ifrom
if maxhistory: query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
history = ET.Element('history') form = form.getXML('submit')
if maxhistory == "0": query.append(form)
history.attrib['maxchars'] = maxhistory iq.append(query)
else: result = iq.send()
history.attrib['maxstanzas'] = maxhistory if result['type'] == 'error':
x.append(history) return False
stanza.append(x) return True
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): def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None):
iq = self.xmpp.makeIqSet() """ Join the specified room, requesting 'maxhistory' lines of history.
if ifrom is not None: """
iq['from'] = ifrom stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow)
iq['to'] = room x = ET.Element('{http://jabber.org/protocol/muc}x')
query = ET.Element('{http://jabber.org/protocol/muc#owner}query') if password:
destroy = ET.Element('destroy') passelement = ET.Element('password')
if altroom: passelement.text = password
destroy.attrib['jid'] = altroom x.append(passelement)
xreason = ET.Element('reason') if maxhistory:
xreason.text = reason history = ET.Element('history')
destroy.append(xreason) if maxhistory == "0":
query.append(destroy) history.attrib['maxchars'] = maxhistory
iq.append(query) else:
r = iq.send() history.attrib['maxstanzas'] = maxhistory
if r is False or r['type'] == 'error': x.append(history)
return False stanza.append(x)
return True 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 setAffiliation(self, room, jid=None, nick=None, affiliation='member'): def destroy(self, room, reason='', altroom = '', ifrom=None):
""" Change room affiliation.""" iq = self.xmpp.makeIqSet()
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'): if ifrom is not None:
raise TypeError iq['from'] = ifrom
query = ET.Element('{http://jabber.org/protocol/muc#admin}query') iq['to'] = room
if nick is not None: query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
item = ET.Element('item', {'affiliation':affiliation, 'nick':nick}) destroy = ET.Element('destroy')
else: if altroom:
item = ET.Element('item', {'affiliation':affiliation, 'jid':jid}) destroy.attrib['jid'] = altroom
query.append(item) xreason = ET.Element('reason')
iq = self.xmpp.makeIqSet(query) xreason.text = reason
iq['to'] = room destroy.append(xreason)
result = iq.send() query.append(destroy)
if result is False or result['type'] != 'result': iq.append(query)
raise ValueError r = iq.send()
return True if r is False or r['type'] == 'error':
return False
return True
def invite(self, room, jid, reason=''): def setAffiliation(self, room, jid=None, nick=None, affiliation='member'):
""" Invite a jid to a room.""" """ Change room affiliation."""
msg = self.xmpp.makeMessage(room) if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
msg['from'] = self.xmpp.jid raise TypeError
x = ET.Element('{http://jabber.org/protocol/muc#user}x') query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
invite = ET.Element('{http://jabber.org/protocol/muc#user}invite', {'to': jid}) if nick is not None:
if reason: item = ET.Element('item', {'affiliation':affiliation, 'nick':nick})
rxml = ET.Element('reason') else:
rxml.text = reason item = ET.Element('item', {'affiliation':affiliation, 'jid':jid})
invite.append(rxml) query.append(item)
x.append(invite) iq = self.xmpp.makeIqSet(query)
msg.append(x) iq['to'] = room
self.xmpp.send(msg) result = iq.send()
if result is False or result['type'] != 'result':
raise ValueError
return True
def leaveMUC(self, room, nick, msg=''): def invite(self, room, jid, reason='', mfrom=''):
""" Leave the specified room. """ Invite a jid to a room."""
""" msg = self.xmpp.makeMessage(room)
if msg: msg['from'] = mfrom
self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick), pstatus=msg) x = ET.Element('{http://jabber.org/protocol/muc#user}x')
else: invite = ET.Element('{http://jabber.org/protocol/muc#user}invite', {'to': jid})
self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick)) if reason:
del self.rooms[room] rxml = ET.Element('reason')
rxml.text = reason
invite.append(rxml)
x.append(invite)
msg.append(x)
self.xmpp.send(msg)
def getRoomConfig(self, room): def leaveMUC(self, room, nick, msg=''):
iq = self.xmpp.makeIqGet('http://jabber.org/protocol/muc#owner') """ Leave the specified room.
iq['to'] = room """
iq['from'] = self.xmpp.jid if msg:
result = iq.send() self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick), pstatus=msg)
if result is None or result['type'] != 'result': else:
raise ValueError self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick))
form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') del self.rooms[room]
if form is None:
raise ValueError
return self.xmpp.plugin['xep_0004'].buildForm(form)
def cancelConfig(self, room): def getRoomConfig(self, room, ifrom=''):
query = ET.Element('{http://jabber.org/protocol/muc#owner}query') iq = self.xmpp.makeIqGet('http://jabber.org/protocol/muc#owner')
x = ET.Element('{jabber:x:data}x', type='cancel') iq['to'] = room
query.append(x) iq['from'] = ifrom
iq = self.xmpp.makeIqSet(query) result = iq.send()
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 setRoomConfig(self, room, config): def cancelConfig(self, room):
query = ET.Element('{http://jabber.org/protocol/muc#owner}query') query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
x = config.getXML('submit') x = ET.Element('{jabber:x:data}x', type='cancel')
query.append(x) query.append(x)
iq = self.xmpp.makeIqSet(query) iq = self.xmpp.makeIqSet(query)
iq['to'] = room iq.send()
iq['from'] = self.xmpp.jid
iq.send()
def getJoinedRooms(self): def setRoomConfig(self, room, config, ifrom=''):
return self.rooms.keys() 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'] = ifrom
iq.send()
def getOurJidInRoom(self, roomJid): def getJoinedRooms(self):
""" Return the jid we're using in a room. return self.rooms.keys()
"""
return "%s/%s" % (roomJid, self.ourNicks[roomJid])
def getJidProperty(self, room, nick, jidProperty): def getOurJidInRoom(self, roomJid):
""" Get the property of a nick in a room, such as its 'jid' or 'affiliation' """ Return the jid we're using in a room.
If not found, return None. """
""" return "%s/%s" % (roomJid, self.ourNicks[roomJid])
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): def getJidProperty(self, room, nick, jidProperty):
""" Get the list of nicks in a room. """ Get the property of a nick in a room, such as its 'jid' or 'affiliation'
""" If not found, return None.
if room not in self.rooms.keys(): """
return None if room in self.rooms and nick in self.rooms[room] and jidProperty in self.rooms[room][nick]:
return self.rooms[room].keys() 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()

View file

@ -110,7 +110,7 @@ class xep_0050(base.base_plugin):
if not id: if not id:
id = self.xmpp.getNewId() id = self.xmpp.getNewId()
iq = self.xmpp.makeIqResult(id) iq = self.xmpp.makeIqResult(id)
iq.attrib['from'] = self.xmpp.fulljid iq.attrib['from'] = self.xmpp.boundjid.full
iq.attrib['to'] = to iq.attrib['to'] = to
command = ET.Element('{http://jabber.org/protocol/commands}command') command = ET.Element('{http://jabber.org/protocol/commands}command')
command.attrib['node'] = node command.attrib['node'] = node

View file

@ -51,7 +51,7 @@ class xep_0060(base.base_plugin):
pubsub.append(configure) pubsub.append(configure)
iq = self.xmpp.makeIqSet(pubsub) iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.fulljid iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id'] id = iq['id']
result = iq.send() result = iq.send()
if result is False or result is None or result['type'] == 'error': return False if result is False or result is None or result['type'] == 'error': return False
@ -63,15 +63,15 @@ class xep_0060(base.base_plugin):
subscribe.attrib['node'] = node subscribe.attrib['node'] = node
if subscribee is None: if subscribee is None:
if bare: if bare:
subscribe.attrib['jid'] = self.xmpp.jid subscribe.attrib['jid'] = self.xmpp.boundjid.bare
else: else:
subscribe.attrib['jid'] = self.xmpp.fulljid subscribe.attrib['jid'] = self.xmpp.boundjid.full
else: else:
subscribe.attrib['jid'] = subscribee subscribe.attrib['jid'] = subscribee
pubsub.append(subscribe) pubsub.append(subscribe)
iq = self.xmpp.makeIqSet(pubsub) iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.fulljid iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id'] id = iq['id']
result = iq.send() result = iq.send()
if result is False or result is None or result['type'] == 'error': return False if result is False or result is None or result['type'] == 'error': return False
@ -83,15 +83,15 @@ class xep_0060(base.base_plugin):
unsubscribe.attrib['node'] = node unsubscribe.attrib['node'] = node
if subscribee is None: if subscribee is None:
if bare: if bare:
unsubscribe.attrib['jid'] = self.xmpp.jid unsubscribe.attrib['jid'] = self.xmpp.boundjid.bare
else: else:
unsubscribe.attrib['jid'] = self.xmpp.fulljid unsubscribe.attrib['jid'] = self.xmpp.boundjid.full
else: else:
unsubscribe.attrib['jid'] = subscribee unsubscribe.attrib['jid'] = subscribee
pubsub.append(unsubscribe) pubsub.append(unsubscribe)
iq = self.xmpp.makeIqSet(pubsub) iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.fulljid iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id'] id = iq['id']
result = iq.send() result = iq.send()
if result is False or result is None or result['type'] == 'error': return False if result is False or result is None or result['type'] == 'error': return False
@ -109,7 +109,7 @@ class xep_0060(base.base_plugin):
iq = self.xmpp.makeIqGet() iq = self.xmpp.makeIqGet()
iq.append(pubsub) iq.append(pubsub)
iq.attrib['to'] = jid iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.fulljid iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id'] id = iq['id']
#self.xmpp.add_handler("<iq id='%s'/>" % id, self.handlerCreateNodeResponse) #self.xmpp.add_handler("<iq id='%s'/>" % id, self.handlerCreateNodeResponse)
result = iq.send() result = iq.send()
@ -133,7 +133,7 @@ class xep_0060(base.base_plugin):
iq = self.xmpp.makeIqGet() iq = self.xmpp.makeIqGet()
iq.append(pubsub) iq.append(pubsub)
iq.attrib['to'] = jid iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.fulljid iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id'] id = iq['id']
result = iq.send() result = iq.send()
if result is None or result == False or result['type'] == 'error': if result is None or result == False or result['type'] == 'error':
@ -156,7 +156,7 @@ class xep_0060(base.base_plugin):
iq = self.xmpp.makeIqGet() iq = self.xmpp.makeIqGet()
iq.append(pubsub) iq.append(pubsub)
iq.attrib['to'] = jid iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.fulljid iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id'] id = iq['id']
result = iq.send() result = iq.send()
if result is None or result == False or result['type'] == 'error': if result is None or result == False or result['type'] == 'error':
@ -179,7 +179,7 @@ class xep_0060(base.base_plugin):
pubsub.append(delete) pubsub.append(delete)
iq.append(pubsub) iq.append(pubsub)
iq.attrib['to'] = jid iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.fulljid iq.attrib['from'] = self.xmpp.boundjid.full
result = iq.send() result = iq.send()
if result is not None and result is not False and result['type'] != 'error': if result is not None and result is not False and result['type'] != 'error':
return True return True
@ -196,7 +196,7 @@ class xep_0060(base.base_plugin):
pubsub.append(configure) pubsub.append(configure)
iq = self.xmpp.makeIqSet(pubsub) iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.fulljid iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id'] id = iq['id']
result = iq.send() result = iq.send()
if result is None or result['type'] == 'error': if result is None or result['type'] == 'error':
@ -217,7 +217,7 @@ class xep_0060(base.base_plugin):
pubsub.append(publish) pubsub.append(publish)
iq = self.xmpp.makeIqSet(pubsub) iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.fulljid iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id'] id = iq['id']
result = iq.send() result = iq.send()
if result is None or result is False or result['type'] == 'error': return False if result is None or result is False or result['type'] == 'error': return False
@ -236,7 +236,7 @@ class xep_0060(base.base_plugin):
pubsub.append(retract) pubsub.append(retract)
iq = self.xmpp.makeIqSet(pubsub) iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.fulljid iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id'] id = iq['id']
result = iq.send() result = iq.send()
if result is None or result is False or result['type'] == 'error': return False if result is None or result is False or result['type'] == 'error': return False
@ -287,7 +287,7 @@ class xep_0060(base.base_plugin):
pubsub.append(affs) pubsub.append(affs)
iq = self.xmpp.makeIqSet(pubsub) iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = ps_jid iq.attrib['to'] = ps_jid
iq.attrib['from'] = self.xmpp.fulljid iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id'] id = iq['id']
result = iq.send() result = iq.send()
if result is None or result is False or result['type'] == 'error': if result is None or result is False or result['type'] == 'error':

View file

@ -42,7 +42,7 @@ class xep_0092(base.base_plugin):
query = ET.Element('{jabber:iq:version}query') query = ET.Element('{jabber:iq:version}query')
iq.append(query) iq.append(query)
iq.attrib['to'] = jid iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.fulljid iq.attrib['from'] = self.xmpp.boundjid.full
id = iq.get('id') id = iq.get('id')
result = iq.send() result = iq.send()
if result and result is not None and result.get('type', 'error') != 'error': if result and result is not None and result.get('type', 'error') != 'error':

View file

@ -94,6 +94,8 @@ class XMLStream(object):
ssl_support -- Indicates if a SSL library is available for use. ssl_support -- Indicates if a SSL library is available for use.
ssl_version -- The version of the SSL protocol to use. ssl_version -- The version of the SSL protocol to use.
Defaults to ssl.PROTOCOL_TLSv1. Defaults to ssl.PROTOCOL_TLSv1.
ca_certs -- File path to a CA certificate to verify the
server's identity.
state -- A state machine for managing the stream's state -- A state machine for managing the stream's
connection state. connection state.
stream_footer -- The start tag and any attributes for the stream's stream_footer -- The start tag and any attributes for the stream's
@ -163,6 +165,7 @@ class XMLStream(object):
self.ssl_support = SSL_SUPPORT self.ssl_support = SSL_SUPPORT
self.ssl_version = ssl.PROTOCOL_TLSv1 self.ssl_version = ssl.PROTOCOL_TLSv1
self.ca_certs = None
self.response_timeout = RESPONSE_TIMEOUT self.response_timeout = RESPONSE_TIMEOUT
@ -283,7 +286,15 @@ class XMLStream(object):
self.socket.settimeout(None) self.socket.settimeout(None)
if self.use_ssl and self.ssl_support: if self.use_ssl and self.ssl_support:
log.debug("Socket Wrapped for SSL") log.debug("Socket Wrapped for SSL")
ssl_socket = ssl.wrap_socket(self.socket) if self.ca_certs is None:
cert_policy = ssl.CERT_NONE
else:
cert_policy = ssl.CERT_REQUIRED
ssl_socket = ssl.wrap_socket(self.socket,
ca_certs=self.ca_certs,
certs_reqs=cert_policy)
if hasattr(self.socket, 'socket'): if hasattr(self.socket, 'socket'):
# We are using a testing socket, so preserve the top # We are using a testing socket, so preserve the top
# layer of wrapping. # layer of wrapping.
@ -387,9 +398,17 @@ class XMLStream(object):
if self.ssl_support: if self.ssl_support:
log.info("Negotiating TLS") log.info("Negotiating TLS")
log.info("Using SSL version: %s" % str(self.ssl_version)) log.info("Using SSL version: %s" % str(self.ssl_version))
if self.ca_certs is None:
cert_policy = ssl.CERT_NONE
else:
cert_policy = ssl.CERT_REQUIRED
ssl_socket = ssl.wrap_socket(self.socket, ssl_socket = ssl.wrap_socket(self.socket,
ssl_version=self.ssl_version, ssl_version=self.ssl_version,
do_handshake_on_connect=False) do_handshake_on_connect=False,
ca_certs=self.ca_certs,
cert_reqs=cert_policy)
if hasattr(self.socket, 'socket'): if hasattr(self.socket, 'socket'):
# We are using a testing socket, so preserve the top # We are using a testing socket, so preserve the top
# layer of wrapping. # layer of wrapping.