Ejemplo n.º 1
0
    def __init__(self, jid='', default_ns='jabber:client'):
        XMLStream.__init__(self)

        self.default_ns = default_ns
        self.stream_ns = 'http://etherx.jabber.org/streams'
        self.namespace_map[self.stream_ns] = 'stream'

        #: An identifier for the stream as given by the server.
        self.stream_id = None

        #: The JabberID (JID) used by this connection.
        self.boundjid = JID(jid)
        self._expected_server_name = self.boundjid.host

        #: A dictionary mapping plugin names to plugins.
        self.plugin = PluginManager(self)

        #: Configuration options for whitelisted plugins.
        #: If a plugin is registered without any configuration,
        #: and there is an entry here, it will be used.
        self.plugin_config = {}

        #: A list of plugins that will be loaded if
        #: :meth:`register_plugins` is called.
        self.plugin_whitelist = []

        #: The main roster object. This roster supports multiple
        #: owner JIDs, as in the case for components. For clients
        #: which only have a single JID, see :attr:`client_roster`.
        self.roster = roster.Roster(self)
        self.roster.add(self.boundjid.bare)

        #: The single roster for the bound JID. This is the
        #: equivalent of::
        #:
        #:     self.roster[self.boundjid.bare]
        self.client_roster = self.roster[self.boundjid.bare]

        #: The distinction between clients and components can be
        #: important, primarily for choosing how to handle the
        #: ``'to'`` and ``'from'`` JIDs of stanzas.
        self.is_component = False

        #: The API registry is a way to process callbacks based on
        #: JID+node combinations. Each callback in the registry is
        #: marked with:
        #:
        #:   - An API name, e.g. xep_0030
        #:   - The name of an action, e.g. get_info
        #:   - The JID that will be affected
        #:   - The node that will be affected
        #:
        #: API handlers with no JID or node will act as global handlers,
        #: while those with a JID and no node will service all nodes
        #: for a JID, and handlers with both a JID and node will be
        #: used only for that specific combination. The handler that
        #: provides the most specificity will be used.
        self.api = APIRegistry(self)

        #: Flag indicating that the initial presence broadcast has
        #: been sent. Until this happens, some servers may not
        #: behave as expected when sending stanzas.
        self.sentpresence = False

        #: A reference to :mod:`sleekxmpp.stanza` to make accessing
        #: stanza classes easier.
        self.stanza = sleekxmpp.stanza

        self.register_handler(
            Callback('IM',
                     MatchXPath('{%s}message/{%s}body' % (self.default_ns,
                                                          self.default_ns)),
                     self._handle_message))
        self.register_handler(
            Callback('Presence',
                     MatchXPath("{%s}presence" % self.default_ns),
                     self._handle_presence))
        self.register_handler(
            Callback('Stream Error',
                     MatchXPath("{%s}error" % self.stream_ns),
                     self._handle_stream_error))

        self.add_event_handler('disconnected',
                               self._handle_disconnected)
        self.add_event_handler('presence_available',
                               self._handle_available)
        self.add_event_handler('presence_dnd',
                               self._handle_available)
        self.add_event_handler('presence_xa',
                               self._handle_available)
        self.add_event_handler('presence_chat',
                               self._handle_available)
        self.add_event_handler('presence_away',
                               self._handle_available)
        self.add_event_handler('presence_unavailable',
                               self._handle_unavailable)
        self.add_event_handler('presence_subscribe',
                               self._handle_subscribe)
        self.add_event_handler('presence_subscribed',
                               self._handle_subscribed)
        self.add_event_handler('presence_unsubscribe',
                               self._handle_unsubscribe)
        self.add_event_handler('presence_unsubscribed',
                               self._handle_unsubscribed)
        self.add_event_handler('roster_subscription_request',
                               self._handle_new_subscription)

        # Set up the XML stream with XMPP's root stanzas.
        self.register_stanza(Message)
        self.register_stanza(Iq)
        self.register_stanza(Presence)
        self.register_stanza(StreamError)

        # Initialize a few default stanza plugins.
        register_stanza_plugin(Iq, Roster)
        register_stanza_plugin(Message, Nick)
        register_stanza_plugin(Message, HTMLIM)
Ejemplo n.º 2
0
class BaseXMPP(XMLStream):

    """
    The BaseXMPP class adapts the generic XMLStream class for use
    with XMPP. It also provides a plugin mechanism to easily extend
    and add support for new XMPP features.

    :param default_ns: Ensure that the correct default XML namespace
                       is used during initialization.
    """

    def __init__(self, jid='', default_ns='jabber:client'):
        XMLStream.__init__(self)

        self.default_ns = default_ns
        self.stream_ns = 'http://etherx.jabber.org/streams'
        self.namespace_map[self.stream_ns] = 'stream'

        #: An identifier for the stream as given by the server.
        self.stream_id = None

        #: The JabberID (JID) used by this connection.
        self.boundjid = JID(jid)
        self._expected_server_name = self.boundjid.host

        #: A dictionary mapping plugin names to plugins.
        self.plugin = PluginManager(self)

        #: Configuration options for whitelisted plugins.
        #: If a plugin is registered without any configuration,
        #: and there is an entry here, it will be used.
        self.plugin_config = {}

        #: A list of plugins that will be loaded if
        #: :meth:`register_plugins` is called.
        self.plugin_whitelist = []

        #: The main roster object. This roster supports multiple
        #: owner JIDs, as in the case for components. For clients
        #: which only have a single JID, see :attr:`client_roster`.
        self.roster = roster.Roster(self)
        self.roster.add(self.boundjid.bare)

        #: The single roster for the bound JID. This is the
        #: equivalent of::
        #:
        #:     self.roster[self.boundjid.bare]
        self.client_roster = self.roster[self.boundjid.bare]

        #: The distinction between clients and components can be
        #: important, primarily for choosing how to handle the
        #: ``'to'`` and ``'from'`` JIDs of stanzas.
        self.is_component = False

        #: The API registry is a way to process callbacks based on
        #: JID+node combinations. Each callback in the registry is
        #: marked with:
        #:
        #:   - An API name, e.g. xep_0030
        #:   - The name of an action, e.g. get_info
        #:   - The JID that will be affected
        #:   - The node that will be affected
        #:
        #: API handlers with no JID or node will act as global handlers,
        #: while those with a JID and no node will service all nodes
        #: for a JID, and handlers with both a JID and node will be
        #: used only for that specific combination. The handler that
        #: provides the most specificity will be used.
        self.api = APIRegistry(self)

        #: Flag indicating that the initial presence broadcast has
        #: been sent. Until this happens, some servers may not
        #: behave as expected when sending stanzas.
        self.sentpresence = False

        #: A reference to :mod:`sleekxmpp.stanza` to make accessing
        #: stanza classes easier.
        self.stanza = sleekxmpp.stanza

        self.register_handler(
            Callback('IM',
                     MatchXPath('{%s}message/{%s}body' % (self.default_ns,
                                                          self.default_ns)),
                     self._handle_message))
        self.register_handler(
            Callback('Presence',
                     MatchXPath("{%s}presence" % self.default_ns),
                     self._handle_presence))
        self.register_handler(
            Callback('Stream Error',
                     MatchXPath("{%s}error" % self.stream_ns),
                     self._handle_stream_error))

        self.add_event_handler('disconnected',
                               self._handle_disconnected)
        self.add_event_handler('presence_available',
                               self._handle_available)
        self.add_event_handler('presence_dnd',
                               self._handle_available)
        self.add_event_handler('presence_xa',
                               self._handle_available)
        self.add_event_handler('presence_chat',
                               self._handle_available)
        self.add_event_handler('presence_away',
                               self._handle_available)
        self.add_event_handler('presence_unavailable',
                               self._handle_unavailable)
        self.add_event_handler('presence_subscribe',
                               self._handle_subscribe)
        self.add_event_handler('presence_subscribed',
                               self._handle_subscribed)
        self.add_event_handler('presence_unsubscribe',
                               self._handle_unsubscribe)
        self.add_event_handler('presence_unsubscribed',
                               self._handle_unsubscribed)
        self.add_event_handler('roster_subscription_request',
                               self._handle_new_subscription)

        # Set up the XML stream with XMPP's root stanzas.
        self.register_stanza(Message)
        self.register_stanza(Iq)
        self.register_stanza(Presence)
        self.register_stanza(StreamError)

        # Initialize a few default stanza plugins.
        register_stanza_plugin(Iq, Roster)
        register_stanza_plugin(Message, Nick)
        register_stanza_plugin(Message, HTMLIM)

    def start_stream_handler(self, xml):
        """Save the stream ID once the streams have been established.

        :param xml: The incoming stream's root element.
        """
        self.stream_id = xml.get('id', '')
        self.stream_version = xml.get('version', '')
        self.peer_default_lang = xml.get('{%s}lang' % XML_NS, None)

    def process(self, *args, **kwargs):
        """Initialize plugins and begin processing the XML stream.

        The number of threads used for processing stream events is determined
        by :data:`HANDLER_THREADS`.

        :param bool block: If ``False``, then event dispatcher will run
                    in a separate thread, allowing for the stream to be
                    used in the background for another application.
                    Otherwise, ``process(block=True)`` blocks the current
                    thread. Defaults to ``False``.
        :param bool threaded: **DEPRECATED**
                    If ``True``, then event dispatcher will run
                    in a separate thread, allowing for the stream to be
                    used in the background for another application.
                    Defaults to ``True``. This does **not** mean that no
                    threads are used at all if ``threaded=False``.

        Regardless of these threading options, these threads will
        always exist:

        - The event queue processor
        - The send queue processor
        - The scheduler
        """
        if 'xep_0115' in self.plugin:
            name = 'xep_0115'
            if not hasattr(self.plugin[name], 'post_inited'):
                if hasattr(self.plugin[name], 'post_init'):
                    self.plugin[name].post_init()
                self.plugin[name].post_inited = True

        for name in self.plugin:
            if not hasattr(self.plugin[name], 'post_inited'):
                if hasattr(self.plugin[name], 'post_init'):
                    self.plugin[name].post_init()
                self.plugin[name].post_inited = True
        return XMLStream.process(self, *args, **kwargs)

    def register_plugin(self, plugin, pconfig={}, module=None):
        """Register and configure  a plugin for use in this stream.

        :param plugin: The name of the plugin class. Plugin names must
                       be unique.
        :param pconfig: A dictionary of configuration data for the plugin.
                        Defaults to an empty dictionary.
        :param module: Optional refence to the module containing the plugin
                       class if using custom plugins.
        """

        # Use the global plugin config cache, if applicable
        if not pconfig:
            pconfig = self.plugin_config.get(plugin, {})

        if not self.plugin.registered(plugin):
            load_plugin(plugin, module)
        self.plugin.enable(plugin, pconfig)

    def register_plugins(self):
        """Register and initialize all built-in plugins.

        Optionally, the list of plugins loaded may be limited to those
        contained in :attr:`plugin_whitelist`.

        Plugin configurations stored in :attr:`plugin_config` will be used.
        """
        if self.plugin_whitelist:
            plugin_list = self.plugin_whitelist
        else:
            plugin_list = plugins.__all__

        for plugin in plugin_list:
            if plugin in plugins.__all__:
                self.register_plugin(plugin)
            else:
                raise NameError("Plugin %s not in plugins.__all__." % plugin)

    def __getitem__(self, key):
        """Return a plugin given its name, if it has been registered."""
        if key in self.plugin:
            return self.plugin[key]
        else:
            log.warning("Plugin '%s' is not loaded.", key)
            return False

    def get(self, key, default):
        """Return a plugin given its name, if it has been registered."""
        return self.plugin.get(key, default)

    def Message(self, *args, **kwargs):
        """Create a Message stanza associated with this stream."""
        msg = Message(self, *args, **kwargs)
        msg['lang'] = self.default_lang
        return msg

    def Iq(self, *args, **kwargs):
        """Create an Iq stanza associated with this stream."""
        return Iq(self, *args, **kwargs)

    def Presence(self, *args, **kwargs):
        """Create a Presence stanza associated with this stream."""
        pres = Presence(self, *args, **kwargs)
        pres['lang'] = self.default_lang
        return pres

    def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None):
        """Create a new Iq stanza with a given Id and from JID.

        :param id: An ideally unique ID value for this stanza thread.
                   Defaults to 0.
        :param ifrom: The from :class:`~sleekxmpp.xmlstream.jid.JID`
                      to use for this stanza.
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
                    for this stanza.
        :param itype: The :class:`~sleekxmpp.stanza.iq.Iq`'s type,
                      one of: ``'get'``, ``'set'``, ``'result'``,
                      or ``'error'``.
        :param iquery: Optional namespace for adding a query element.
        """
        iq = self.Iq()
        iq['id'] = str(id)
        iq['to'] = ito
        iq['from'] = ifrom
        iq['type'] = itype
        iq['query'] = iquery
        return iq

    def make_iq_get(self, queryxmlns=None, ito=None, ifrom=None, iq=None):
        """Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'get'``.

        Optionally, a query element may be added.

        :param queryxmlns: The namespace of the query to use.
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
                    for this stanza.
        :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
                      to use for this stanza.
        :param iq: Optionally use an existing stanza instead
                   of generating a new one.
        """
        if not iq:
            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=None, ito=None, ifrom=None, iq=None):
        """
        Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type
        ``'result'`` with the given ID value.

        :param id: An ideally unique ID value. May use :meth:`new_id()`.
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
                    for this stanza.
        :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
                      to use for this stanza.
        :param iq: Optionally use an existing stanza instead
                   of generating a new one.
        """
        if not iq:
            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, ito=None, ifrom=None, iq=None):
        """
        Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'set'``.

        Optionally, a substanza may be given to use as the
        stanza's payload.

        :param sub: Either an
                    :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
                    stanza object or an
                    :class:`~xml.etree.ElementTree.Element` XML object
                    to use as the :class:`~sleekxmpp.stanza.iq.Iq`'s payload.
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
                    for this stanza.
        :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
                      to use for this stanza.
        :param iq: Optionally use an existing stanza instead
                   of generating a new one.
        """
        if not iq:
            iq = self.Iq()
        iq['type'] = 'set'
        if sub != None:
            iq.append(sub)
        if ito:
            iq['to'] = ito
        if ifrom:
            iq['from'] = ifrom
        return iq

    def make_iq_error(self, id, type='cancel',
                      condition='feature-not-implemented',
                      text=None, ito=None, ifrom=None, iq=None):
        """
        Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'error'``.

        :param id: An ideally unique ID value. May use :meth:`new_id()`.
        :param type: The type of the error, such as ``'cancel'`` or
                     ``'modify'``. Defaults to ``'cancel'``.
        :param condition: The error condition. Defaults to
                          ``'feature-not-implemented'``.
        :param text: A message describing the cause of the error.
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
                    for this stanza.
        :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
                      to use for this stanza.
        :param iq: Optionally use an existing stanza instead
                   of generating a new one.
        """
        if not iq:
            iq = self.Iq()
        iq['id'] = id
        iq['error']['type'] = type
        iq['error']['condition'] = condition
        iq['error']['text'] = text
        if ito:
            iq['to'] = ito
        if ifrom:
            iq['from'] = ifrom
        return iq

    def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None):
        """
        Create or modify an :class:`~sleekxmpp.stanza.iq.Iq` stanza
        to use the given query namespace.

        :param iq: Optionally use an existing stanza instead
                   of generating a new one.
        :param xmlns: The query's namespace.
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
                    for this stanza.
        :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
                      to use for this stanza.
        """
        if not iq:
            iq = self.Iq()
        iq['query'] = xmlns
        if ito:
            iq['to'] = ito
        if ifrom:
            iq['from'] = ifrom
        return iq

    def make_query_roster(self, iq=None):
        """Create a roster query element.

        :param iq: Optionally use an existing stanza instead
                   of generating a new one.
        """
        if iq:
            iq['query'] = 'jabber:iq:roster'
        return ET.Element("{jabber:iq:roster}query")

    def make_message(self, mto, mbody=None, msubject=None, mtype=None,
                     mhtml=None, mfrom=None, mnick=None):
        """
        Create and initialize a new
        :class:`~sleekxmpp.stanza.message.Message` stanza.

        :param mto: The recipient of the message.
        :param mbody: The main contents of the message.
        :param msubject: Optional subject for the message.
        :param mtype: The message's type, such as ``'chat'`` or
                      ``'groupchat'``.
        :param mhtml: Optional HTML body content in the form of a string.
        :param mfrom: The sender of the message. if sending from a client,
                      be aware that some servers require that the full JID
                      of the sender be used.
        :param mnick: Optional nickname of the sender.
        """
        message = self.Message(sto=mto, stype=mtype, sfrom=mfrom)
        message['body'] = mbody
        message['subject'] = msubject
        if mnick is not None:
            message['nick'] = mnick
        if mhtml is not None:
            message['html']['body'] = mhtml
        return message

    def make_presence(self, pshow=None, pstatus=None, ppriority=None,
                      pto=None, ptype=None, pfrom=None, pnick=None):
        """
        Create and initialize a new
        :class:`~sleekxmpp.stanza.presence.Presence` stanza.

        :param pshow: The presence's show value.
        :param pstatus: The presence's status message.
        :param ppriority: This connection's priority.
        :param pto: The recipient of a directed presence.
        :param ptype: The type of presence, such as ``'subscribe'``.
        :param pfrom: The sender of the presence.
        :param pnick: Optional nickname of the presence's sender.
        """
        presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto)
        if pshow is not None:
            presence['type'] = pshow
        if pfrom is None and self.is_component:
            presence['from'] = self.boundjid.full
        presence['priority'] = ppriority
        presence['status'] = pstatus
        presence['nick'] = pnick
        return presence

    def send_message(self, mto, mbody, msubject=None, mtype=None,
                     mhtml=None, mfrom=None, mnick=None):
        """
        Create, initialize, and send a new
        :class:`~sleekxmpp.stanza.message.Message` stanza.

        :param mto: The recipient of the message.
        :param mbody: The main contents of the message.
        :param msubject: Optional subject for the message.
        :param mtype: The message's type, such as ``'chat'`` or
                      ``'groupchat'``.
        :param mhtml: Optional HTML body content in the form of a string.
        :param mfrom: The sender of the message. if sending from a client,
                      be aware that some servers require that the full JID
                      of the sender be used.
        :param mnick: Optional nickname of the sender.
        """
        self.make_message(mto, mbody, msubject, mtype,
                          mhtml, mfrom, mnick).send()

    def send_presence(self, pshow=None, pstatus=None, ppriority=None,
                      pto=None, pfrom=None, ptype=None, pnick=None):
        """
        Create, initialize, and send a new
        :class:`~sleekxmpp.stanza.presence.Presence` stanza.

        :param pshow: The presence's show value.
        :param pstatus: The presence's status message.
        :param ppriority: This connection's priority.
        :param pto: The recipient of a directed presence.
        :param ptype: The type of presence, such as ``'subscribe'``.
        :param pfrom: The sender of the presence.
        :param pnick: Optional nickname of the presence's sender.
        """
        self.make_presence(pshow, pstatus, ppriority, pto,
                           ptype, pfrom, pnick).send()

    def send_presence_subscription(self, pto, pfrom=None,
                                   ptype='subscribe', pnick=None):
        """
        Create, initialize, and send a new
        :class:`~sleekxmpp.stanza.presence.Presence` stanza of
        type ``'subscribe'``.

        :param pto: The recipient of a directed presence.
        :param pfrom: The sender of the presence.
        :param ptype: The type of presence, such as ``'subscribe'``.
        :param pnick: Optional nickname of the presence's sender.
        """
        self.make_presence(ptype=ptype,
                           pfrom=pfrom,
                           pto=JID(pto).bare,
                           pnick=pnick).send()

    @property
    def jid(self):
        """Attribute accessor for bare jid"""
        log.warning("jid property deprecated. Use boundjid.bare")
        return self.boundjid.bare

    @jid.setter
    def jid(self, value):
        log.warning("jid property deprecated. Use boundjid.bare")
        self.boundjid.bare = value

    @property
    def fulljid(self):
        """Attribute accessor for full jid"""
        log.warning("fulljid property deprecated. Use boundjid.full")
        return self.boundjid.full

    @fulljid.setter
    def fulljid(self, value):
        log.warning("fulljid property deprecated. Use boundjid.full")
        self.boundjid.full = value

    @property
    def resource(self):
        """Attribute accessor for jid resource"""
        log.warning("resource property deprecated. Use boundjid.resource")
        return self.boundjid.resource

    @resource.setter
    def resource(self, value):
        log.warning("fulljid property deprecated. Use boundjid.full")
        self.boundjid.resource = value

    @property
    def username(self):
        """Attribute accessor for jid usernode"""
        log.warning("username property deprecated. Use boundjid.user")
        return self.boundjid.user

    @username.setter
    def username(self, value):
        log.warning("username property deprecated. Use boundjid.user")
        self.boundjid.user = value

    @property
    def server(self):
        """Attribute accessor for jid host"""
        log.warning("server property deprecated. Use boundjid.host")
        return self.boundjid.server

    @server.setter
    def server(self, value):
        log.warning("server property deprecated. Use boundjid.host")
        self.boundjid.server = value

    @property
    def auto_authorize(self):
        """Auto accept or deny subscription requests.

        If ``True``, auto accept subscription requests.
        If ``False``, auto deny subscription requests.
        If ``None``, don't automatically respond.
        """
        return self.roster.auto_authorize

    @auto_authorize.setter
    def auto_authorize(self, value):
        self.roster.auto_authorize = value

    @property
    def auto_subscribe(self):
        """Auto send requests for mutual subscriptions.

        If ``True``, auto send mutual subscription requests.
        """
        return self.roster.auto_subscribe

    @auto_subscribe.setter
    def auto_subscribe(self, value):
        self.roster.auto_subscribe = value

    def set_jid(self, jid):
        """Rip a JID apart and claim it as our own."""
        log.debug("setting jid to %s", jid)
        self.boundjid.full = jid

    def getjidresource(self, fulljid):
        if '/' in fulljid:
            return fulljid.split('/', 1)[-1]
        else:
            return ''

    def getjidbare(self, fulljid):
        return fulljid.split('/', 1)[0]

    def _handle_disconnected(self, event):
        """When disconnected, reset the roster"""
        self.roster.reset()

    def _handle_stream_error(self, error):
        self.event('stream_error', error)

    def _handle_message(self, msg):
        """Process incoming message stanzas."""
        if not self.is_component and not msg['to'].bare:
            msg['to'] = self.boundjid
        self.event('message', msg)

    def _handle_available(self, presence):
        pto = presence['to'].bare
        pfrom = presence['from'].bare
        self.roster[pto][pfrom].handle_available(presence)

    def _handle_unavailable(self, presence):
        pto = presence['to'].bare
        pfrom = presence['from'].bare
        self.roster[pto][pfrom].handle_unavailable(presence)

    def _handle_new_subscription(self, stanza):
        """Attempt to automatically handle subscription requests.

        Subscriptions will be approved if the request is from
        a whitelisted JID, of :attr:`auto_authorize` is True. They
        will be rejected if :attr:`auto_authorize` is False. Setting
        :attr:`auto_authorize` to ``None`` will disable automatic
        subscription handling (except for whitelisted JIDs).

        If a subscription is accepted, a request for a mutual
        subscription will be sent if :attr:`auto_subscribe` is ``True``.
        """
        roster = self.roster[stanza['to'].bare]
        item = self.roster[stanza['to'].bare][stanza['from'].bare]
        if item['whitelisted']:
            item.authorize()
        elif roster.auto_authorize:
            item.authorize()
            if roster.auto_subscribe:
                item.subscribe()
        elif roster.auto_authorize == False:
            item.unauthorize()

    def _handle_removed_subscription(self, presence):
        pto = presence['to'].bare
        pfrom = presence['from'].bare
        self.roster[pto][pfrom].unauthorize()

    def _handle_subscribe(self, presence):
        pto = presence['to'].bare
        pfrom = presence['from'].bare
        self.roster[pto][pfrom].handle_subscribe(presence)

    def _handle_subscribed(self, presence):
        pto = presence['to'].bare
        pfrom = presence['from'].bare
        self.roster[pto][pfrom].handle_subscribed(presence)

    def _handle_unsubscribe(self, presence):
        pto = presence['to'].bare
        pfrom = presence['from'].bare
        self.roster[pto][pfrom].handle_unsubscribe(presence)

    def _handle_unsubscribed(self, presence):
        pto = presence['to'].bare
        pfrom = presence['from'].bare
        self.roster[pto][pfrom].handle_unsubscribed(presence)

    def _handle_presence(self, presence):
        """Process incoming presence stanzas.

        Update the roster with presence information.
        """
        if not self.is_component and not presence['to'].bare:
            presence['to'] = self.boundjid

        self.event('presence', presence)
        self.event('presence_%s' % presence['type'], presence)

        # Check for changes in subscription state.
        if presence['type'] in ('subscribe', 'subscribed',
                                'unsubscribe', 'unsubscribed'):
            self.event('changed_subscription', presence)
            return
        elif not presence['type'] in ('available', 'unavailable') and \
             not presence['type'] in presence.showtypes:
            return

    def exception(self, exception):
        """Process any uncaught exceptions, notably
        :class:`~sleekxmpp.exceptions.IqError` and
        :class:`~sleekxmpp.exceptions.IqTimeout` exceptions.

        :param exception: An unhandled :class:`Exception` object.
        """
        if isinstance(exception, IqError):
            iq = exception.iq
            log.error('%s: %s', iq['error']['condition'],
                                iq['error']['text'])
            log.warning('You should catch IqError exceptions')
        elif isinstance(exception, IqTimeout):
            iq = exception.iq
            log.error('Request timed out: %s', iq)
            log.warning('You should catch IqTimeout exceptions')
        elif isinstance(exception, SyntaxError):
            # Hide stream parsing errors that occur when the
            # stream is disconnected (they've been handled, we
            # don't need to make a mess in the logs).
            pass
        else:
            log.exception(exception)
Ejemplo n.º 3
0
    def __init__(self, jid='', default_ns='jabber:client'):
        XMLStream.__init__(self)

        self.default_ns = default_ns
        self.stream_ns = 'http://etherx.jabber.org/streams'
        self.namespace_map[self.stream_ns] = 'stream'

        #: An identifier for the stream as given by the server.
        self.stream_id = None

        #: The JabberID (JID) requested for this connection.
        self.requested_jid = JID(jid, cache_lock=True)

        #: The JabberID (JID) used by this connection,
        #: as set after session binding. This may even be a
        #: different bare JID than what was requested.
        self.boundjid = JID(jid, cache_lock=True)

        self._expected_server_name = self.boundjid.host
        self._redirect_attempts = 0

        #: The maximum number of consecutive see-other-host
        #: redirections that will be followed before quitting.
        self.max_redirects = 5

        self.session_bind_event = threading.Event()

        #: A dictionary mapping plugin names to plugins.
        self.plugin = PluginManager(self)

        #: Configuration options for whitelisted plugins.
        #: If a plugin is registered without any configuration,
        #: and there is an entry here, it will be used.
        self.plugin_config = {}

        #: A list of plugins that will be loaded if
        #: :meth:`register_plugins` is called.
        self.plugin_whitelist = []

        #: The main roster object. This roster supports multiple
        #: owner JIDs, as in the case for components. For clients
        #: which only have a single JID, see :attr:`client_roster`.
        self.roster = roster.Roster(self)
        self.roster.add(self.boundjid)

        #: The single roster for the bound JID. This is the
        #: equivalent of::
        #:
        #:     self.roster[self.boundjid.bare]
        self.client_roster = self.roster[self.boundjid]

        #: The distinction between clients and components can be
        #: important, primarily for choosing how to handle the
        #: ``'to'`` and ``'from'`` JIDs of stanzas.
        self.is_component = False

        #: Messages may optionally be tagged with ID values. Setting
        #: :attr:`use_message_ids` to `True` will assign all outgoing
        #: messages an ID. Some plugin features require enabling
        #: this option.
        self.use_message_ids = False

        #: Presence updates may optionally be tagged with ID values.
        #: Setting :attr:`use_message_ids` to `True` will assign all
        #: outgoing messages an ID.
        self.use_presence_ids = False

        #: The API registry is a way to process callbacks based on
        #: JID+node combinations. Each callback in the registry is
        #: marked with:
        #:
        #:   - An API name, e.g. xep_0030
        #:   - The name of an action, e.g. get_info
        #:   - The JID that will be affected
        #:   - The node that will be affected
        #:
        #: API handlers with no JID or node will act as global handlers,
        #: while those with a JID and no node will service all nodes
        #: for a JID, and handlers with both a JID and node will be
        #: used only for that specific combination. The handler that
        #: provides the most specificity will be used.
        self.api = APIRegistry(self)

        #: Flag indicating that the initial presence broadcast has
        #: been sent. Until this happens, some servers may not
        #: behave as expected when sending stanzas.
        self.sentpresence = False

        #: A reference to :mod:`sleekxmpp.stanza` to make accessing
        #: stanza classes easier.
        self.stanza = stanza

        self.register_handler(
            Callback('IM',
                     MatchXPath('{%s}message/{%s}body' % (self.default_ns,
                                                          self.default_ns)),
                     self._handle_message))
        self.register_handler(
            Callback('Presence',
                     MatchXPath("{%s}presence" % self.default_ns),
                     self._handle_presence))

        self.register_handler(
            Callback('Stream Error',
                     MatchXPath("{%s}error" % self.stream_ns),
                     self._handle_stream_error))

        self.add_event_handler('session_start',
                               self._handle_session_start)
        self.add_event_handler('disconnected',
                               self._handle_disconnected)
        self.add_event_handler('presence_available',
                               self._handle_available)
        self.add_event_handler('presence_dnd',
                               self._handle_available)
        self.add_event_handler('presence_xa',
                               self._handle_available)
        self.add_event_handler('presence_chat',
                               self._handle_available)
        self.add_event_handler('presence_away',
                               self._handle_available)
        self.add_event_handler('presence_unavailable',
                               self._handle_unavailable)
        self.add_event_handler('presence_subscribe',
                               self._handle_subscribe)
        self.add_event_handler('presence_subscribed',
                               self._handle_subscribed)
        self.add_event_handler('presence_unsubscribe',
                               self._handle_unsubscribe)
        self.add_event_handler('presence_unsubscribed',
                               self._handle_unsubscribed)
        self.add_event_handler('roster_subscription_request',
                               self._handle_new_subscription)

        # Set up the XML stream with XMPP's root stanzas.
        self.register_stanza(Message)
        self.register_stanza(Iq)
        self.register_stanza(Presence)
        self.register_stanza(StreamError)

        # Initialize a few default stanza plugins.
        register_stanza_plugin(Iq, Roster)
        register_stanza_plugin(Message, Nick)
Ejemplo n.º 4
0
class BaseXMPP(XMLStream):

    """
    The BaseXMPP class adapts the generic XMLStream class for use
    with XMPP. It also provides a plugin mechanism to easily extend
    and add support for new XMPP features.

    :param default_ns: Ensure that the correct default XML namespace
                       is used during initialization.
    """

    def __init__(self, jid='', default_ns='jabber:client'):
        XMLStream.__init__(self)

        self.default_ns = default_ns
        self.stream_ns = 'http://etherx.jabber.org/streams'
        self.namespace_map[self.stream_ns] = 'stream'

        #: An identifier for the stream as given by the server.
        self.stream_id = None

        #: The JabberID (JID) requested for this connection.
        self.requested_jid = JID(jid, cache_lock=True)

        #: The JabberID (JID) used by this connection,
        #: as set after session binding. This may even be a
        #: different bare JID than what was requested.
        self.boundjid = JID(jid, cache_lock=True)

        self._expected_server_name = self.boundjid.host
        self._redirect_attempts = 0

        #: The maximum number of consecutive see-other-host
        #: redirections that will be followed before quitting.
        self.max_redirects = 5

        self.session_bind_event = threading.Event()

        #: A dictionary mapping plugin names to plugins.
        self.plugin = PluginManager(self)

        #: Configuration options for whitelisted plugins.
        #: If a plugin is registered without any configuration,
        #: and there is an entry here, it will be used.
        self.plugin_config = {}

        #: A list of plugins that will be loaded if
        #: :meth:`register_plugins` is called.
        self.plugin_whitelist = []

        #: The main roster object. This roster supports multiple
        #: owner JIDs, as in the case for components. For clients
        #: which only have a single JID, see :attr:`client_roster`.
        self.roster = roster.Roster(self)
        self.roster.add(self.boundjid)

        #: The single roster for the bound JID. This is the
        #: equivalent of::
        #:
        #:     self.roster[self.boundjid.bare]
        self.client_roster = self.roster[self.boundjid]

        #: The distinction between clients and components can be
        #: important, primarily for choosing how to handle the
        #: ``'to'`` and ``'from'`` JIDs of stanzas.
        self.is_component = False

        #: Messages may optionally be tagged with ID values. Setting
        #: :attr:`use_message_ids` to `True` will assign all outgoing
        #: messages an ID. Some plugin features require enabling
        #: this option.
        self.use_message_ids = False

        #: Presence updates may optionally be tagged with ID values.
        #: Setting :attr:`use_message_ids` to `True` will assign all
        #: outgoing messages an ID.
        self.use_presence_ids = False

        #: The API registry is a way to process callbacks based on
        #: JID+node combinations. Each callback in the registry is
        #: marked with:
        #:
        #:   - An API name, e.g. xep_0030
        #:   - The name of an action, e.g. get_info
        #:   - The JID that will be affected
        #:   - The node that will be affected
        #:
        #: API handlers with no JID or node will act as global handlers,
        #: while those with a JID and no node will service all nodes
        #: for a JID, and handlers with both a JID and node will be
        #: used only for that specific combination. The handler that
        #: provides the most specificity will be used.
        self.api = APIRegistry(self)

        #: Flag indicating that the initial presence broadcast has
        #: been sent. Until this happens, some servers may not
        #: behave as expected when sending stanzas.
        self.sentpresence = False

        #: A reference to :mod:`sleekxmpp.stanza` to make accessing
        #: stanza classes easier.
        self.stanza = stanza

        self.register_handler(
            Callback('IM',
                     MatchXPath('{%s}message/{%s}body' % (self.default_ns,
                                                          self.default_ns)),
                     self._handle_message))
        self.register_handler(
            Callback('Presence',
                     MatchXPath("{%s}presence" % self.default_ns),
                     self._handle_presence))

        self.register_handler(
            Callback('Stream Error',
                     MatchXPath("{%s}error" % self.stream_ns),
                     self._handle_stream_error))

        self.add_event_handler('session_start',
                               self._handle_session_start)
        self.add_event_handler('disconnected',
                               self._handle_disconnected)
        self.add_event_handler('presence_available',
                               self._handle_available)
        self.add_event_handler('presence_dnd',
                               self._handle_available)
        self.add_event_handler('presence_xa',
                               self._handle_available)
        self.add_event_handler('presence_chat',
                               self._handle_available)
        self.add_event_handler('presence_away',
                               self._handle_available)
        self.add_event_handler('presence_unavailable',
                               self._handle_unavailable)
        self.add_event_handler('presence_subscribe',
                               self._handle_subscribe)
        self.add_event_handler('presence_subscribed',
                               self._handle_subscribed)
        self.add_event_handler('presence_unsubscribe',
                               self._handle_unsubscribe)
        self.add_event_handler('presence_unsubscribed',
                               self._handle_unsubscribed)
        self.add_event_handler('roster_subscription_request',
                               self._handle_new_subscription)

        # Set up the XML stream with XMPP's root stanzas.
        self.register_stanza(Message)
        self.register_stanza(Iq)
        self.register_stanza(Presence)
        self.register_stanza(StreamError)

        # Initialize a few default stanza plugins.
        register_stanza_plugin(Iq, Roster)
        register_stanza_plugin(Message, Nick)

    def start_stream_handler(self, xml):
        """Save the stream ID once the streams have been established.

        :param xml: The incoming stream's root element.
        """
        self.stream_id = xml.get('id', '')
        self.stream_version = xml.get('version', '')
        self.peer_default_lang = xml.get('{%s}lang' % XML_NS, None)

        if not self.is_component and not self.stream_version:
            log.warning('Legacy XMPP 0.9 protocol detected.')
            self.event('legacy_protocol')

    def process(self, *args, **kwargs):
        """Initialize plugins and begin processing the XML stream.

        The number of threads used for processing stream events is determined
        by :data:`HANDLER_THREADS`.

        :param bool block: If ``False``, then event dispatcher will run
                    in a separate thread, allowing for the stream to be
                    used in the background for another application.
                    Otherwise, ``process(block=True)`` blocks the current
                    thread. Defaults to ``False``.
        :param bool threaded: **DEPRECATED**
                    If ``True``, then event dispatcher will run
                    in a separate thread, allowing for the stream to be
                    used in the background for another application.
                    Defaults to ``True``. This does **not** mean that no
                    threads are used at all if ``threaded=False``.

        Regardless of these threading options, these threads will
        always exist:

        - The event queue processor
        - The send queue processor
        - The scheduler
        """
        # print "((((process called)))"
        for name in self.plugin:
            if not hasattr(self.plugin[name], 'post_inited'):
                if hasattr(self.plugin[name], 'post_init'):
                    self.plugin[name].post_init()
                self.plugin[name].post_inited = True
        # print "function process before return"
        return XMLStream.process(self, *args, **kwargs)

    def register_plugin(self, plugin, pconfig={}, module=None):
        """Register and configure  a plugin for use in this stream.

        :param plugin: The name of the plugin class. Plugin names must
                       be unique.
        :param pconfig: A dictionary of configuration data for the plugin.
                        Defaults to an empty dictionary.
        :param module: Optional refence to the module containing the plugin
                       class if using custom plugins.
        """

        # Use the global plugin config cache, if applicable
        if not pconfig:
            pconfig = self.plugin_config.get(plugin, {})

        if not self.plugin.registered(plugin):
            load_plugin(plugin, module)
        self.plugin.enable(plugin, pconfig)

    def register_plugins(self):
        """Register and initialize all built-in plugins.

        Optionally, the list of plugins loaded may be limited to those
        contained in :attr:`plugin_whitelist`.

        Plugin configurations stored in :attr:`plugin_config` will be used.
        """
        if self.plugin_whitelist:
            plugin_list = self.plugin_whitelist
        else:
            plugin_list = plugins.__all__

        for plugin in plugin_list:
            if plugin in plugins.__all__:
                self.register_plugin(plugin)
            else:
                raise NameError("Plugin %s not in plugins.__all__." % plugin)

    def __getitem__(self, key):
        """Return a plugin given its name, if it has been registered."""
        if key in self.plugin:
            return self.plugin[key]
        else:
            log.warning("Plugin '%s' is not loaded.", key)
            return False

    def get(self, key, default):
        """Return a plugin given its name, if it has been registered."""
        return self.plugin.get(key, default)

    def Message(self, *args, **kwargs):
        """Create a Message stanza associated with this stream."""
        msg = Message(self, *args, **kwargs)
        msg['lang'] = self.default_lang
        return msg

    def Iq(self, *args, **kwargs):
        """Create an Iq stanza associated with this stream."""
        return Iq(self, *args, **kwargs)

    def Presence(self, *args, **kwargs):
        """Create a Presence stanza associated with this stream."""
        pres = Presence(self, *args, **kwargs)
        pres['lang'] = self.default_lang
        return pres

    def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None):
        """Create a new Iq stanza with a given Id and from JID.

        :param id: An ideally unique ID value for this stanza thread.
                   Defaults to 0.
        :param ifrom: The from :class:`~sleekxmpp.xmlstream.jid.JID`
                      to use for this stanza.
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
                    for this stanza.
        :param itype: The :class:`~sleekxmpp.stanza.iq.Iq`'s type,
                      one of: ``'get'``, ``'set'``, ``'result'``,
                      or ``'error'``.
        :param iquery: Optional namespace for adding a query element.
        """
        iq = self.Iq()
        iq['id'] = str(id)
        iq['to'] = ito
        iq['from'] = ifrom
        iq['type'] = itype
        iq['query'] = iquery
        return iq

    def make_iq_get(self, queryxmlns=None, ito=None, ifrom=None, iq=None):
        """Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'get'``.

        Optionally, a query element may be added.

        :param queryxmlns: The namespace of the query to use.
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
                    for this stanza.
        :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
                      to use for this stanza.
        :param iq: Optionally use an existing stanza instead
                   of generating a new one.
        """
        if not iq:
            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=None, ito=None, ifrom=None, iq=None):
        """
        Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type
        ``'result'`` with the given ID value.

        :param id: An ideally unique ID value. May use :meth:`new_id()`.
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
                    for this stanza.
        :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
                      to use for this stanza.
        :param iq: Optionally use an existing stanza instead
                   of generating a new one.
        """
        if not iq:
            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, ito=None, ifrom=None, iq=None):
        """
        Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'set'``.

        Optionally, a substanza may be given to use as the
        stanza's payload.

        :param sub: Either an
                    :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
                    stanza object or an
                    :class:`~xml.etree.ElementTree.Element` XML object
                    to use as the :class:`~sleekxmpp.stanza.iq.Iq`'s payload.
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
                    for this stanza.
        :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
                      to use for this stanza.
        :param iq: Optionally use an existing stanza instead
                   of generating a new one.
        """
        if not iq:
            iq = self.Iq()
        iq['type'] = 'set'
        if sub != None:
            iq.append(sub)
        if ito:
            iq['to'] = ito
        if ifrom:
            iq['from'] = ifrom
        return iq

    def make_iq_error(self, id, type='cancel',
                      condition='feature-not-implemented',
                      text=None, ito=None, ifrom=None, iq=None):
        """
        Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'error'``.

        :param id: An ideally unique ID value. May use :meth:`new_id()`.
        :param type: The type of the error, such as ``'cancel'`` or
                     ``'modify'``. Defaults to ``'cancel'``.
        :param condition: The error condition. Defaults to
                          ``'feature-not-implemented'``.
        :param text: A message describing the cause of the error.
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
                    for this stanza.
        :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
                      to use for this stanza.
        :param iq: Optionally use an existing stanza instead
                   of generating a new one.
        """
        if not iq:
            iq = self.Iq()
        iq['id'] = id
        iq['error']['type'] = type
        iq['error']['condition'] = condition
        iq['error']['text'] = text
        if ito:
            iq['to'] = ito
        if ifrom:
            iq['from'] = ifrom
        return iq

    def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None):
        """
        Create or modify an :class:`~sleekxmpp.stanza.iq.Iq` stanza
        to use the given query namespace.

        :param iq: Optionally use an existing stanza instead
                   of generating a new one.
        :param xmlns: The query's namespace.
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
                    for this stanza.
        :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
                      to use for this stanza.
        """
        if not iq:
            iq = self.Iq()
        iq['query'] = xmlns
        if ito:
            iq['to'] = ito
        if ifrom:
            iq['from'] = ifrom
        return iq

    def make_query_roster(self, iq=None):
        """Create a roster query element.

        :param iq: Optionally use an existing stanza instead
                   of generating a new one.
        """
        if iq:
            iq['query'] = 'jabber:iq:roster'
        return ET.Element("{jabber:iq:roster}query")

    def make_message(self, mto, mbody=None, msubject=None, mtype=None,
                     mhtml=None, mfrom=None, mnick=None):
        """
        Create and initialize a new
        :class:`~sleekxmpp.stanza.message.Message` stanza.

        :param mto: The recipient of the message.
        :param mbody: The main contents of the message.
        :param msubject: Optional subject for the message.
        :param mtype: The message's type, such as ``'chat'`` or
                      ``'groupchat'``.
        :param mhtml: Optional HTML body content in the form of a string.
        :param mfrom: The sender of the message. if sending from a client,
                      be aware that some servers require that the full JID
                      of the sender be used.
        :param mnick: Optional nickname of the sender.
        """
        message = self.Message(sto=mto, stype=mtype, sfrom=mfrom)
        message['body'] = mbody
        message['subject'] = msubject
        if mnick is not None:
            message['nick'] = mnick
        if mhtml is not None:
            message['html']['body'] = mhtml
        return message

    def make_presence(self, pshow=None, pstatus=None, ppriority=None,
                      pto=None, ptype=None, pfrom=None, pnick=None):
        """
        Create and initialize a new
        :class:`~sleekxmpp.stanza.presence.Presence` stanza.

        :param pshow: The presence's show value.
        :param pstatus: The presence's status message.
        :param ppriority: This connection's priority.
        :param pto: The recipient of a directed presence.
        :param ptype: The type of presence, such as ``'subscribe'``.
        :param pfrom: The sender of the presence.
        :param pnick: Optional nickname of the presence's sender.
        """
        presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto)
        if pshow is not None:
            presence['type'] = pshow
        if pfrom is None and self.is_component:
            presence['from'] = self.boundjid.full
        presence['priority'] = ppriority
        presence['status'] = pstatus
        presence['nick'] = pnick
        return presence

    def send_message(self, mto, mbody, msubject=None, mtype=None,
                     mhtml=None, mfrom=None, mnick=None):
        """
        Create, initialize, and send a new
        :class:`~sleekxmpp.stanza.message.Message` stanza.

        :param mto: The recipient of the message.
        :param mbody: The main contents of the message.
        :param msubject: Optional subject for the message.
        :param mtype: The message's type, such as ``'chat'`` or
                      ``'groupchat'``.
        :param mhtml: Optional HTML body content in the form of a string.
        :param mfrom: The sender of the message. if sending from a client,
                      be aware that some servers require that the full JID
                      of the sender be used.
        :param mnick: Optional nickname of the sender.
        """
        self.make_message(mto, mbody, msubject, mtype,
                          mhtml, mfrom, mnick).send()

    def send_presence(self, pshow=None, pstatus=None, ppriority=None,
                      pto=None, pfrom=None, ptype=None, pnick=None):
        """
        Create, initialize, and send a new
        :class:`~sleekxmpp.stanza.presence.Presence` stanza.

        :param pshow: The presence's show value.
        :param pstatus: The presence's status message.
        :param ppriority: This connection's priority.
        :param pto: The recipient of a directed presence.
        :param ptype: The type of presence, such as ``'subscribe'``.
        :param pfrom: The sender of the presence.
        :param pnick: Optional nickname of the presence's sender.
        """
        self.make_presence(pshow, pstatus, ppriority, pto,
                           ptype, pfrom, pnick).send()

    def send_presence_subscription(self, pto, pfrom=None,
                                   ptype='subscribe', pnick=None):
        """
        Create, initialize, and send a new
        :class:`~sleekxmpp.stanza.presence.Presence` stanza of
        type ``'subscribe'``.

        :param pto: The recipient of a directed presence.
        :param pfrom: The sender of the presence.
        :param ptype: The type of presence, such as ``'subscribe'``.
        :param pnick: Optional nickname of the presence's sender.
        """
        self.make_presence(ptype=ptype,
                           pfrom=pfrom,
                           pto=JID(pto).bare,
                           pnick=pnick).send()

    @property
    def jid(self):
        """Attribute accessor for bare jid"""
        log.warning("jid property deprecated. Use boundjid.bare")
        return self.boundjid.bare

    @jid.setter
    def jid(self, value):
        log.warning("jid property deprecated. Use boundjid.bare")
        self.boundjid.bare = value

    @property
    def fulljid(self):
        """Attribute accessor for full jid"""
        log.warning("fulljid property deprecated. Use boundjid.full")
        return self.boundjid.full

    @fulljid.setter
    def fulljid(self, value):
        log.warning("fulljid property deprecated. Use boundjid.full")
        self.boundjid.full = value

    @property
    def resource(self):
        """Attribute accessor for jid resource"""
        log.warning("resource property deprecated. Use boundjid.resource")
        return self.boundjid.resource

    @resource.setter
    def resource(self, value):
        log.warning("fulljid property deprecated. Use boundjid.resource")
        self.boundjid.resource = value

    @property
    def username(self):
        """Attribute accessor for jid usernode"""
        log.warning("username property deprecated. Use boundjid.user")
        return self.boundjid.user

    @username.setter
    def username(self, value):
        log.warning("username property deprecated. Use boundjid.user")
        self.boundjid.user = value

    @property
    def server(self):
        """Attribute accessor for jid host"""
        log.warning("server property deprecated. Use boundjid.host")
        return self.boundjid.server

    @server.setter
    def server(self, value):
        log.warning("server property deprecated. Use boundjid.host")
        self.boundjid.server = value

    @property
    def auto_authorize(self):
        """Auto accept or deny subscription requests.

        If ``True``, auto accept subscription requests.
        If ``False``, auto deny subscription requests.
        If ``None``, don't automatically respond.
        """
        return self.roster.auto_authorize

    @auto_authorize.setter
    def auto_authorize(self, value):
        self.roster.auto_authorize = value

    @property
    def auto_subscribe(self):
        """Auto send requests for mutual subscriptions.

        If ``True``, auto send mutual subscription requests.
        """
        return self.roster.auto_subscribe

    @auto_subscribe.setter
    def auto_subscribe(self, value):
        self.roster.auto_subscribe = value

    def set_jid(self, jid):
        """Rip a JID apart and claim it as our own."""
        log.debug("setting jid to %s", jid)
        self.boundjid = JID(jid, cache_lock=True)

    def getjidresource(self, fulljid):
        if '/' in fulljid:
            return fulljid.split('/', 1)[-1]
        else:
            return ''

    def getjidbare(self, fulljid):
        return fulljid.split('/', 1)[0]

    def _handle_session_start(self, event):
        """Reset redirection attempt count."""
        self._redirect_attempts = 0

    def _handle_disconnected(self, event):
        """When disconnected, reset the roster"""
        self.roster.reset()
        self.session_bind_event.clear()

    def _handle_stream_error(self, error):
        self.event('stream_error', error)

        if error['condition'] == 'see-other-host':
            other_host = error['see_other_host']
            if not other_host:
                log.warning("No other host specified.")
                return

            if self._redirect_attempts > self.max_redirects:
                log.error("Exceeded maximum number of redirection attempts.")
                return

            self._redirect_attempts += 1

            host = other_host
            port = 5222

            if '[' in other_host and ']' in other_host:
                host = other_host.split(']')[0][1:]
            elif ':' in other_host:
                host = other_host.split(':')[0]

            port_sec = other_host.split(']')[-1]
            if ':' in port_sec:
                port = int(port_sec.split(':')[1])

            self.address = (host, port)
            self.default_domain = host
            self.dns_records = None
            self.reconnect_delay = None
            self.reconnect()

    def _handle_message(self, msg):
        """Process incoming message stanzas."""
        if not self.is_component and not msg['to'].bare:
            msg['to'] = self.boundjid
        self.event('message', msg)

    def _handle_available(self, pres):
        self.roster[pres['to']][pres['from']].handle_available(pres)

    def _handle_unavailable(self, pres):
        self.roster[pres['to']][pres['from']].handle_unavailable(pres)

    def _handle_new_subscription(self, pres):
        """Attempt to automatically handle subscription requests.

        Subscriptions will be approved if the request is from
        a whitelisted JID, of :attr:`auto_authorize` is True. They
        will be rejected if :attr:`auto_authorize` is False. Setting
        :attr:`auto_authorize` to ``None`` will disable automatic
        subscription handling (except for whitelisted JIDs).

        If a subscription is accepted, a request for a mutual
        subscription will be sent if :attr:`auto_subscribe` is ``True``.
        """
        roster = self.roster[pres['to']]
        item = self.roster[pres['to']][pres['from']]
        if item['whitelisted']:
            item.authorize()
            if roster.auto_subscribe:
                item.subscribe()
        elif roster.auto_authorize:
            item.authorize()
            if roster.auto_subscribe:
                item.subscribe()
        elif roster.auto_authorize == False:
            item.unauthorize()

    def _handle_removed_subscription(self, pres):
        self.roster[pres['to']][pres['from']].handle_unauthorize(pres)

    def _handle_subscribe(self, pres):
        self.roster[pres['to']][pres['from']].handle_subscribe(pres)

    def _handle_subscribed(self, pres):
        self.roster[pres['to']][pres['from']].handle_subscribed(pres)

    def _handle_unsubscribe(self, pres):
        self.roster[pres['to']][pres['from']].handle_unsubscribe(pres)

    def _handle_unsubscribed(self, pres):
        self.roster[pres['to']][pres['from']].handle_unsubscribed(pres)

    def _handle_presence(self, presence):
        """Process incoming presence stanzas.

        Update the roster with presence information.
        """
        if not self.is_component and not presence['to'].bare:
            presence['to'] = self.boundjid

        self.event('presence', presence)
        self.event('presence_%s' % presence['type'], presence)

        # Check for changes in subscription state.
        if presence['type'] in ('subscribe', 'subscribed',
                                'unsubscribe', 'unsubscribed'):
            self.event('changed_subscription', presence)
            return
        elif not presence['type'] in ('available', 'unavailable') and \
             not presence['type'] in presence.showtypes:
            return

    def exception(self, exception):
        """Process any uncaught exceptions, notably
        :class:`~sleekxmpp.exceptions.IqError` and
        :class:`~sleekxmpp.exceptions.IqTimeout` exceptions.

        :param exception: An unhandled :class:`Exception` object.
        """
        if isinstance(exception, IqError):
            iq = exception.iq
            log.error('%s: %s', iq['error']['condition'],
                                iq['error']['text'])
            log.warning('You should catch IqError exceptions')
        elif isinstance(exception, IqTimeout):
            iq = exception.iq
            log.error('Request timed out: %s', iq)
            log.warning('You should catch IqTimeout exceptions')
        elif isinstance(exception, SyntaxError):
            # Hide stream parsing errors that occur when the
            # stream is disconnected (they've been handled, we
            # don't need to make a mess in the logs).
            pass
        else:
            log.exception(exception)
Ejemplo n.º 5
0
    def __init__(self, jid='', default_ns='jabber:client'):
        XMLStream.__init__(self)

        self.default_ns = default_ns
        self.stream_ns = 'http://etherx.jabber.org/streams'
        self.namespace_map[self.stream_ns] = 'stream'

        #: An identifier for the stream as given by the server.
        self.stream_id = None

        #: The JabberID (JID) used by this connection.
        self.boundjid = JID(jid)

        #: A dictionary mapping plugin names to plugins.
        self.plugin = PluginManager(self)

        #: Configuration options for whitelisted plugins.
        #: If a plugin is registered without any configuration,
        #: and there is an entry here, it will be used.
        self.plugin_config = {}

        #: A list of plugins that will be loaded if
        #: :meth:`register_plugins` is called.
        self.plugin_whitelist = []

        #: The main roster object. This roster supports multiple
        #: owner JIDs, as in the case for components. For clients
        #: which only have a single JID, see :attr:`client_roster`.
        self.roster = roster.Roster(self)
        self.roster.add(self.boundjid.bare)

        #: The single roster for the bound JID. This is the
        #: equivalent of::
        #:
        #:     self.roster[self.boundjid.bare]
        self.client_roster = self.roster[self.boundjid.bare]

        #: The distinction between clients and components can be
        #: important, primarily for choosing how to handle the
        #: ``'to'`` and ``'from'`` JIDs of stanzas.
        self.is_component = False

        #: Flag indicating that the initial presence broadcast has
        #: been sent. Until this happens, some servers may not
        #: behave as expected when sending stanzas.
        self.sentpresence = False

        #: A reference to :mod:`sleekxmpp.stanza` to make accessing
        #: stanza classes easier.
        self.stanza = sleekxmpp.stanza

        self.register_handler(
            Callback(
                'IM',
                MatchXPath('{%s}message/{%s}body' %
                           (self.default_ns, self.default_ns)),
                self._handle_message))
        self.register_handler(
            Callback('Presence', MatchXPath("{%s}presence" % self.default_ns),
                     self._handle_presence))
        self.register_handler(
            Callback('Stream Error', MatchXPath("{%s}error" % self.stream_ns),
                     self._handle_stream_error))

        self.add_event_handler('disconnected', self._handle_disconnected)
        self.add_event_handler('presence_available', self._handle_available)
        self.add_event_handler('presence_dnd', self._handle_available)
        self.add_event_handler('presence_xa', self._handle_available)
        self.add_event_handler('presence_chat', self._handle_available)
        self.add_event_handler('presence_away', self._handle_available)
        self.add_event_handler('presence_unavailable',
                               self._handle_unavailable)
        self.add_event_handler('presence_subscribe', self._handle_subscribe)
        self.add_event_handler('presence_subscribed', self._handle_subscribed)
        self.add_event_handler('presence_unsubscribe',
                               self._handle_unsubscribe)
        self.add_event_handler('presence_unsubscribed',
                               self._handle_unsubscribed)
        self.add_event_handler('roster_subscription_request',
                               self._handle_new_subscription)

        # Set up the XML stream with XMPP's root stanzas.
        self.register_stanza(Message)
        self.register_stanza(Iq)
        self.register_stanza(Presence)
        self.register_stanza(StreamError)

        # Initialize a few default stanza plugins.
        register_stanza_plugin(Iq, Roster)
        register_stanza_plugin(Message, Nick)
        register_stanza_plugin(Message, HTMLIM)
Ejemplo n.º 6
0
    def __init__(self, jid="", default_ns="jabber:client"):
        XMLStream.__init__(self)

        self.default_ns = default_ns
        self.stream_ns = "http://etherx.jabber.org/streams"
        self.namespace_map[self.stream_ns] = "stream"

        #: An identifier for the stream as given by the server.
        self.stream_id = None

        #: The JabberID (JID) requested for this connection.
        self.requested_jid = JID(jid, cache_lock=True)

        #: The JabberID (JID) used by this connection,
        #: as set after session binding. This may even be a
        #: different bare JID than what was requested.
        self.boundjid = JID(jid, cache_lock=True)

        self._expected_server_name = self.boundjid.host
        self._redirect_attempts = 0

        #: The maximum number of consecutive see-other-host
        #: redirections that will be followed before quitting.
        self.max_redirects = 5

        self.session_bind_event = threading.Event()

        #: A dictionary mapping plugin names to plugins.
        self.plugin = PluginManager(self)

        #: Configuration options for whitelisted plugins.
        #: If a plugin is registered without any configuration,
        #: and there is an entry here, it will be used.
        self.plugin_config = {}

        #: A list of plugins that will be loaded if
        #: :meth:`register_plugins` is called.
        self.plugin_whitelist = []

        #: The main roster object. This roster supports multiple
        #: owner JIDs, as in the case for components. For clients
        #: which only have a single JID, see :attr:`client_roster`.
        self.roster = roster.Roster(self)
        self.roster.add(self.boundjid)

        #: The single roster for the bound JID. This is the
        #: equivalent of::
        #:
        #:     self.roster[self.boundjid.bare]
        self.client_roster = self.roster[self.boundjid]

        #: The distinction between clients and components can be
        #: important, primarily for choosing how to handle the
        #: ``'to'`` and ``'from'`` JIDs of stanzas.
        self.is_component = False

        #: Messages may optionally be tagged with ID values. Setting
        #: :attr:`use_message_ids` to `True` will assign all outgoing
        #: messages an ID. Some plugin features require enabling
        #: this option.
        self.use_message_ids = False

        #: Presence updates may optionally be tagged with ID values.
        #: Setting :attr:`use_message_ids` to `True` will assign all
        #: outgoing messages an ID.
        self.use_presence_ids = False

        #: The API registry is a way to process callbacks based on
        #: JID+node combinations. Each callback in the registry is
        #: marked with:
        #:
        #:   - An API name, e.g. xep_0030
        #:   - The name of an action, e.g. get_info
        #:   - The JID that will be affected
        #:   - The node that will be affected
        #:
        #: API handlers with no JID or node will act as global handlers,
        #: while those with a JID and no node will service all nodes
        #: for a JID, and handlers with both a JID and node will be
        #: used only for that specific combination. The handler that
        #: provides the most specificity will be used.
        self.api = APIRegistry(self)

        #: Flag indicating that the initial presence broadcast has
        #: been sent. Until this happens, some servers may not
        #: behave as expected when sending stanzas.
        self.sentpresence = False

        #: A reference to :mod:`sleekxmpp.stanza` to make accessing
        #: stanza classes easier.
        self.stanza = sleekxmpp.stanza

        self.register_handler(
            Callback(
                "IM", MatchXPath("{%s}message/{%s}body" % (self.default_ns, self.default_ns)), self._handle_message
            )
        )
        self.register_handler(Callback("Presence", MatchXPath("{%s}presence" % self.default_ns), self._handle_presence))

        self.register_handler(
            Callback("Stream Error", MatchXPath("{%s}error" % self.stream_ns), self._handle_stream_error)
        )

        self.add_event_handler("session_start", self._handle_session_start)
        self.add_event_handler("disconnected", self._handle_disconnected)
        self.add_event_handler("presence_available", self._handle_available)
        self.add_event_handler("presence_dnd", self._handle_available)
        self.add_event_handler("presence_xa", self._handle_available)
        self.add_event_handler("presence_chat", self._handle_available)
        self.add_event_handler("presence_away", self._handle_available)
        self.add_event_handler("presence_unavailable", self._handle_unavailable)
        self.add_event_handler("presence_subscribe", self._handle_subscribe)
        self.add_event_handler("presence_subscribed", self._handle_subscribed)
        self.add_event_handler("presence_unsubscribe", self._handle_unsubscribe)
        self.add_event_handler("presence_unsubscribed", self._handle_unsubscribed)
        self.add_event_handler("roster_subscription_request", self._handle_new_subscription)

        # Set up the XML stream with XMPP's root stanzas.
        self.register_stanza(Message)
        self.register_stanza(Iq)
        self.register_stanza(Presence)
        self.register_stanza(StreamError)

        # Initialize a few default stanza plugins.
        register_stanza_plugin(Iq, Roster)
        register_stanza_plugin(Message, Nick)
        register_stanza_plugin(Message, HTMLIM)