basexmpp.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831
  1. # -*- coding: utf-8 -*-
  2. """
  3. sleekxmpp.basexmpp
  4. ~~~~~~~~~~~~~~~~~~
  5. This module provides the common XMPP functionality
  6. for both clients and components.
  7. Part of SleekXMPP: The Sleek XMPP Library
  8. :copyright: (c) 2011 Nathanael C. Fritz
  9. :license: MIT, see LICENSE for more details
  10. """
  11. from __future__ import with_statement, unicode_literals
  12. import sys
  13. import logging
  14. import threading
  15. from sleekxmpp import plugins, roster, stanza
  16. from sleekxmpp.api import APIRegistry
  17. from sleekxmpp.exceptions import IqError, IqTimeout
  18. from sleekxmpp.stanza import Message, Presence, Iq, StreamError
  19. from sleekxmpp.stanza.roster import Roster
  20. from sleekxmpp.stanza.nick import Nick
  21. from sleekxmpp.xmlstream import XMLStream, JID
  22. from sleekxmpp.xmlstream import ET, register_stanza_plugin
  23. from sleekxmpp.xmlstream.matcher import MatchXPath
  24. from sleekxmpp.xmlstream.handler import Callback
  25. from sleekxmpp.xmlstream.stanzabase import XML_NS
  26. from sleekxmpp.plugins import PluginManager, load_plugin
  27. log = logging.getLogger(__name__)
  28. # In order to make sure that Unicode is handled properly
  29. # in Python 2.x, reset the default encoding.
  30. if sys.version_info < (3, 0):
  31. from sleekxmpp.util.misc_ops import setdefaultencoding
  32. setdefaultencoding('utf8')
  33. class BaseXMPP(XMLStream):
  34. """
  35. The BaseXMPP class adapts the generic XMLStream class for use
  36. with XMPP. It also provides a plugin mechanism to easily extend
  37. and add support for new XMPP features.
  38. :param default_ns: Ensure that the correct default XML namespace
  39. is used during initialization.
  40. """
  41. def __init__(self, jid='', default_ns='jabber:client', **kwargs):
  42. XMLStream.__init__(self, **kwargs)
  43. self.default_ns = default_ns
  44. self.stream_ns = 'http://etherx.jabber.org/streams'
  45. self.namespace_map[self.stream_ns] = 'stream'
  46. #: An identifier for the stream as given by the server.
  47. self.stream_id = None
  48. #: The JabberID (JID) requested for this connection.
  49. self.requested_jid = JID(jid, cache_lock=True)
  50. #: The JabberID (JID) used by this connection,
  51. #: as set after session binding. This may even be a
  52. #: different bare JID than what was requested.
  53. self.boundjid = JID(jid, cache_lock=True)
  54. self._expected_server_name = self.boundjid.host
  55. self._redirect_attempts = 0
  56. #: The maximum number of consecutive see-other-host
  57. #: redirections that will be followed before quitting.
  58. self.max_redirects = 5
  59. self.session_bind_event = threading.Event()
  60. #: A dictionary mapping plugin names to plugins.
  61. self.plugin = PluginManager(self)
  62. #: Configuration options for whitelisted plugins.
  63. #: If a plugin is registered without any configuration,
  64. #: and there is an entry here, it will be used.
  65. self.plugin_config = {}
  66. #: A list of plugins that will be loaded if
  67. #: :meth:`register_plugins` is called.
  68. self.plugin_whitelist = []
  69. #: The main roster object. This roster supports multiple
  70. #: owner JIDs, as in the case for components. For clients
  71. #: which only have a single JID, see :attr:`client_roster`.
  72. self.roster = roster.Roster(self)
  73. self.roster.add(self.boundjid)
  74. #: The single roster for the bound JID. This is the
  75. #: equivalent of::
  76. #:
  77. #: self.roster[self.boundjid.bare]
  78. self.client_roster = self.roster[self.boundjid]
  79. #: The distinction between clients and components can be
  80. #: important, primarily for choosing how to handle the
  81. #: ``'to'`` and ``'from'`` JIDs of stanzas.
  82. self.is_component = False
  83. #: Messages may optionally be tagged with ID values. Setting
  84. #: :attr:`use_message_ids` to `True` will assign all outgoing
  85. #: messages an ID. Some plugin features require enabling
  86. #: this option.
  87. self.use_message_ids = False
  88. #: Presence updates may optionally be tagged with ID values.
  89. #: Setting :attr:`use_message_ids` to `True` will assign all
  90. #: outgoing messages an ID.
  91. self.use_presence_ids = False
  92. #: The API registry is a way to process callbacks based on
  93. #: JID+node combinations. Each callback in the registry is
  94. #: marked with:
  95. #:
  96. #: - An API name, e.g. xep_0030
  97. #: - The name of an action, e.g. get_info
  98. #: - The JID that will be affected
  99. #: - The node that will be affected
  100. #:
  101. #: API handlers with no JID or node will act as global handlers,
  102. #: while those with a JID and no node will service all nodes
  103. #: for a JID, and handlers with both a JID and node will be
  104. #: used only for that specific combination. The handler that
  105. #: provides the most specificity will be used.
  106. self.api = APIRegistry(self)
  107. #: Flag indicating that the initial presence broadcast has
  108. #: been sent. Until this happens, some servers may not
  109. #: behave as expected when sending stanzas.
  110. self.sentpresence = False
  111. #: A reference to :mod:`sleekxmpp.stanza` to make accessing
  112. #: stanza classes easier.
  113. self.stanza = stanza
  114. self.register_handler(
  115. Callback('IM',
  116. MatchXPath('{%s}message/{%s}body' % (self.default_ns,
  117. self.default_ns)),
  118. self._handle_message))
  119. self.register_handler(
  120. Callback('Presence',
  121. MatchXPath("{%s}presence" % self.default_ns),
  122. self._handle_presence))
  123. self.register_handler(
  124. Callback('Stream Error',
  125. MatchXPath("{%s}error" % self.stream_ns),
  126. self._handle_stream_error))
  127. self.add_event_handler('session_start',
  128. self._handle_session_start)
  129. self.add_event_handler('disconnected',
  130. self._handle_disconnected)
  131. self.add_event_handler('presence_available',
  132. self._handle_available)
  133. self.add_event_handler('presence_dnd',
  134. self._handle_available)
  135. self.add_event_handler('presence_xa',
  136. self._handle_available)
  137. self.add_event_handler('presence_chat',
  138. self._handle_available)
  139. self.add_event_handler('presence_away',
  140. self._handle_available)
  141. self.add_event_handler('presence_unavailable',
  142. self._handle_unavailable)
  143. self.add_event_handler('presence_subscribe',
  144. self._handle_subscribe)
  145. self.add_event_handler('presence_subscribed',
  146. self._handle_subscribed)
  147. self.add_event_handler('presence_unsubscribe',
  148. self._handle_unsubscribe)
  149. self.add_event_handler('presence_unsubscribed',
  150. self._handle_unsubscribed)
  151. self.add_event_handler('roster_subscription_request',
  152. self._handle_new_subscription)
  153. # Set up the XML stream with XMPP's root stanzas.
  154. self.register_stanza(Message)
  155. self.register_stanza(Iq)
  156. self.register_stanza(Presence)
  157. self.register_stanza(StreamError)
  158. # Initialize a few default stanza plugins.
  159. register_stanza_plugin(Iq, Roster)
  160. register_stanza_plugin(Message, Nick)
  161. def start_stream_handler(self, xml):
  162. """Save the stream ID once the streams have been established.
  163. :param xml: The incoming stream's root element.
  164. """
  165. self.stream_id = xml.get('id', '')
  166. self.stream_version = xml.get('version', '')
  167. self.peer_default_lang = xml.get('{%s}lang' % XML_NS, None)
  168. if not self.is_component and not self.stream_version:
  169. log.warning('Legacy XMPP 0.9 protocol detected.')
  170. self.event('legacy_protocol')
  171. def process(self, *args, **kwargs):
  172. """Initialize plugins and begin processing the XML stream.
  173. The number of threads used for processing stream events is determined
  174. by :data:`HANDLER_THREADS`.
  175. :param bool block: If ``False``, then event dispatcher will run
  176. in a separate thread, allowing for the stream to be
  177. used in the background for another application.
  178. Otherwise, ``process(block=True)`` blocks the current
  179. thread. Defaults to ``False``.
  180. :param bool threaded: **DEPRECATED**
  181. If ``True``, then event dispatcher will run
  182. in a separate thread, allowing for the stream to be
  183. used in the background for another application.
  184. Defaults to ``True``. This does **not** mean that no
  185. threads are used at all if ``threaded=False``.
  186. Regardless of these threading options, these threads will
  187. always exist:
  188. - The event queue processor
  189. - The send queue processor
  190. - The scheduler
  191. """
  192. for name in self.plugin:
  193. if not hasattr(self.plugin[name], 'post_inited'):
  194. if hasattr(self.plugin[name], 'post_init'):
  195. self.plugin[name].post_init()
  196. self.plugin[name].post_inited = True
  197. return XMLStream.process(self, *args, **kwargs)
  198. def register_plugin(self, plugin, pconfig=None, module=None):
  199. """Register and configure a plugin for use in this stream.
  200. :param plugin: The name of the plugin class. Plugin names must
  201. be unique.
  202. :param pconfig: A dictionary of configuration data for the plugin.
  203. Defaults to an empty dictionary.
  204. :param module: Optional refence to the module containing the plugin
  205. class if using custom plugins.
  206. """
  207. # Use the global plugin config cache, if applicable
  208. if not pconfig:
  209. pconfig = self.plugin_config.get(plugin, {})
  210. if not self.plugin.registered(plugin):
  211. load_plugin(plugin, module)
  212. self.plugin.enable(plugin, pconfig)
  213. def register_plugins(self):
  214. """Register and initialize all built-in plugins.
  215. Optionally, the list of plugins loaded may be limited to those
  216. contained in :attr:`plugin_whitelist`.
  217. Plugin configurations stored in :attr:`plugin_config` will be used.
  218. """
  219. if self.plugin_whitelist:
  220. plugin_list = self.plugin_whitelist
  221. else:
  222. plugin_list = plugins.__all__
  223. for plugin in plugin_list:
  224. if plugin in plugins.__all__:
  225. self.register_plugin(plugin)
  226. else:
  227. raise NameError("Plugin %s not in plugins.__all__." % plugin)
  228. def __getitem__(self, key):
  229. """Return a plugin given its name, if it has been registered."""
  230. if key in self.plugin:
  231. return self.plugin[key]
  232. else:
  233. log.warning("Plugin '%s' is not loaded.", key)
  234. return False
  235. def get(self, key, default):
  236. """Return a plugin given its name, if it has been registered."""
  237. return self.plugin.get(key, default)
  238. def Message(self, *args, **kwargs):
  239. """Create a Message stanza associated with this stream."""
  240. msg = Message(self, *args, **kwargs)
  241. msg['lang'] = self.default_lang
  242. return msg
  243. def Iq(self, *args, **kwargs):
  244. """Create an Iq stanza associated with this stream."""
  245. return Iq(self, *args, **kwargs)
  246. def Presence(self, *args, **kwargs):
  247. """Create a Presence stanza associated with this stream."""
  248. pres = Presence(self, *args, **kwargs)
  249. pres['lang'] = self.default_lang
  250. return pres
  251. def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None):
  252. """Create a new Iq stanza with a given Id and from JID.
  253. :param id: An ideally unique ID value for this stanza thread.
  254. Defaults to 0.
  255. :param ifrom: The from :class:`~sleekxmpp.xmlstream.jid.JID`
  256. to use for this stanza.
  257. :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
  258. for this stanza.
  259. :param itype: The :class:`~sleekxmpp.stanza.iq.Iq`'s type,
  260. one of: ``'get'``, ``'set'``, ``'result'``,
  261. or ``'error'``.
  262. :param iquery: Optional namespace for adding a query element.
  263. """
  264. iq = self.Iq()
  265. iq['id'] = str(id)
  266. iq['to'] = ito
  267. iq['from'] = ifrom
  268. iq['type'] = itype
  269. iq['query'] = iquery
  270. return iq
  271. def make_iq_get(self, queryxmlns=None, ito=None, ifrom=None, iq=None):
  272. """Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'get'``.
  273. Optionally, a query element may be added.
  274. :param queryxmlns: The namespace of the query to use.
  275. :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
  276. for this stanza.
  277. :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
  278. to use for this stanza.
  279. :param iq: Optionally use an existing stanza instead
  280. of generating a new one.
  281. """
  282. if not iq:
  283. iq = self.Iq()
  284. iq['type'] = 'get'
  285. iq['query'] = queryxmlns
  286. if ito:
  287. iq['to'] = ito
  288. if ifrom:
  289. iq['from'] = ifrom
  290. return iq
  291. def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None):
  292. """
  293. Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type
  294. ``'result'`` with the given ID value.
  295. :param id: An ideally unique ID value. May use :meth:`new_id()`.
  296. :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
  297. for this stanza.
  298. :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
  299. to use for this stanza.
  300. :param iq: Optionally use an existing stanza instead
  301. of generating a new one.
  302. """
  303. if not iq:
  304. iq = self.Iq()
  305. if id is None:
  306. id = self.new_id()
  307. iq['id'] = id
  308. iq['type'] = 'result'
  309. if ito:
  310. iq['to'] = ito
  311. if ifrom:
  312. iq['from'] = ifrom
  313. return iq
  314. def make_iq_set(self, sub=None, ito=None, ifrom=None, iq=None):
  315. """
  316. Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'set'``.
  317. Optionally, a substanza may be given to use as the
  318. stanza's payload.
  319. :param sub: Either an
  320. :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
  321. stanza object or an
  322. :class:`~xml.etree.ElementTree.Element` XML object
  323. to use as the :class:`~sleekxmpp.stanza.iq.Iq`'s payload.
  324. :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
  325. for this stanza.
  326. :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
  327. to use for this stanza.
  328. :param iq: Optionally use an existing stanza instead
  329. of generating a new one.
  330. """
  331. if not iq:
  332. iq = self.Iq()
  333. iq['type'] = 'set'
  334. if sub != None:
  335. iq.append(sub)
  336. if ito:
  337. iq['to'] = ito
  338. if ifrom:
  339. iq['from'] = ifrom
  340. return iq
  341. def make_iq_error(self, id, type='cancel',
  342. condition='feature-not-implemented',
  343. text=None, ito=None, ifrom=None, iq=None):
  344. """
  345. Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'error'``.
  346. :param id: An ideally unique ID value. May use :meth:`new_id()`.
  347. :param type: The type of the error, such as ``'cancel'`` or
  348. ``'modify'``. Defaults to ``'cancel'``.
  349. :param condition: The error condition. Defaults to
  350. ``'feature-not-implemented'``.
  351. :param text: A message describing the cause of the error.
  352. :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
  353. for this stanza.
  354. :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
  355. to use for this stanza.
  356. :param iq: Optionally use an existing stanza instead
  357. of generating a new one.
  358. """
  359. if not iq:
  360. iq = self.Iq()
  361. iq['id'] = id
  362. iq['error']['type'] = type
  363. iq['error']['condition'] = condition
  364. iq['error']['text'] = text
  365. if ito:
  366. iq['to'] = ito
  367. if ifrom:
  368. iq['from'] = ifrom
  369. return iq
  370. def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None):
  371. """
  372. Create or modify an :class:`~sleekxmpp.stanza.iq.Iq` stanza
  373. to use the given query namespace.
  374. :param iq: Optionally use an existing stanza instead
  375. of generating a new one.
  376. :param xmlns: The query's namespace.
  377. :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
  378. for this stanza.
  379. :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
  380. to use for this stanza.
  381. """
  382. if not iq:
  383. iq = self.Iq()
  384. iq['query'] = xmlns
  385. if ito:
  386. iq['to'] = ito
  387. if ifrom:
  388. iq['from'] = ifrom
  389. return iq
  390. def make_query_roster(self, iq=None):
  391. """Create a roster query element.
  392. :param iq: Optionally use an existing stanza instead
  393. of generating a new one.
  394. """
  395. if iq:
  396. iq['query'] = 'jabber:iq:roster'
  397. return ET.Element("{jabber:iq:roster}query")
  398. def make_message(self, mto, mbody=None, msubject=None, mtype=None,
  399. mhtml=None, mfrom=None, mnick=None):
  400. """
  401. Create and initialize a new
  402. :class:`~sleekxmpp.stanza.message.Message` stanza.
  403. :param mto: The recipient of the message.
  404. :param mbody: The main contents of the message.
  405. :param msubject: Optional subject for the message.
  406. :param mtype: The message's type, such as ``'chat'`` or
  407. ``'groupchat'``.
  408. :param mhtml: Optional HTML body content in the form of a string.
  409. :param mfrom: The sender of the message. if sending from a client,
  410. be aware that some servers require that the full JID
  411. of the sender be used.
  412. :param mnick: Optional nickname of the sender.
  413. """
  414. message = self.Message(sto=mto, stype=mtype, sfrom=mfrom)
  415. message['body'] = mbody
  416. message['subject'] = msubject
  417. if mnick is not None:
  418. message['nick'] = mnick
  419. if mhtml is not None:
  420. message['html']['body'] = mhtml
  421. return message
  422. def make_presence(self, pshow=None, pstatus=None, ppriority=None,
  423. pto=None, ptype=None, pfrom=None, pnick=None):
  424. """
  425. Create and initialize a new
  426. :class:`~sleekxmpp.stanza.presence.Presence` stanza.
  427. :param pshow: The presence's show value.
  428. :param pstatus: The presence's status message.
  429. :param ppriority: This connection's priority.
  430. :param pto: The recipient of a directed presence.
  431. :param ptype: The type of presence, such as ``'subscribe'``.
  432. :param pfrom: The sender of the presence.
  433. :param pnick: Optional nickname of the presence's sender.
  434. """
  435. presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto)
  436. if pshow is not None:
  437. presence['type'] = pshow
  438. if pfrom is None and self.is_component:
  439. presence['from'] = self.boundjid.full
  440. presence['priority'] = ppriority
  441. presence['status'] = pstatus
  442. presence['nick'] = pnick
  443. return presence
  444. def send_message(self, mto, mbody, msubject=None, mtype=None,
  445. mhtml=None, mfrom=None, mnick=None):
  446. """
  447. Create, initialize, and send a new
  448. :class:`~sleekxmpp.stanza.message.Message` stanza.
  449. :param mto: The recipient of the message.
  450. :param mbody: The main contents of the message.
  451. :param msubject: Optional subject for the message.
  452. :param mtype: The message's type, such as ``'chat'`` or
  453. ``'groupchat'``.
  454. :param mhtml: Optional HTML body content in the form of a string.
  455. :param mfrom: The sender of the message. if sending from a client,
  456. be aware that some servers require that the full JID
  457. of the sender be used.
  458. :param mnick: Optional nickname of the sender.
  459. """
  460. self.make_message(mto, mbody, msubject, mtype,
  461. mhtml, mfrom, mnick).send()
  462. def send_presence(self, pshow=None, pstatus=None, ppriority=None,
  463. pto=None, pfrom=None, ptype=None, pnick=None):
  464. """
  465. Create, initialize, and send a new
  466. :class:`~sleekxmpp.stanza.presence.Presence` stanza.
  467. :param pshow: The presence's show value.
  468. :param pstatus: The presence's status message.
  469. :param ppriority: This connection's priority.
  470. :param pto: The recipient of a directed presence.
  471. :param ptype: The type of presence, such as ``'subscribe'``.
  472. :param pfrom: The sender of the presence.
  473. :param pnick: Optional nickname of the presence's sender.
  474. """
  475. self.make_presence(pshow, pstatus, ppriority, pto,
  476. ptype, pfrom, pnick).send()
  477. def send_presence_subscription(self, pto, pfrom=None,
  478. ptype='subscribe', pnick=None):
  479. """
  480. Create, initialize, and send a new
  481. :class:`~sleekxmpp.stanza.presence.Presence` stanza of
  482. type ``'subscribe'``.
  483. :param pto: The recipient of a directed presence.
  484. :param pfrom: The sender of the presence.
  485. :param ptype: The type of presence, such as ``'subscribe'``.
  486. :param pnick: Optional nickname of the presence's sender.
  487. """
  488. self.make_presence(ptype=ptype,
  489. pfrom=pfrom,
  490. pto=JID(pto).bare,
  491. pnick=pnick).send()
  492. @property
  493. def jid(self):
  494. """Attribute accessor for bare jid"""
  495. log.warning("jid property deprecated. Use boundjid.bare")
  496. return self.boundjid.bare
  497. @jid.setter
  498. def jid(self, value):
  499. log.warning("jid property deprecated. Use boundjid.bare")
  500. self.boundjid.bare = value
  501. @property
  502. def fulljid(self):
  503. """Attribute accessor for full jid"""
  504. log.warning("fulljid property deprecated. Use boundjid.full")
  505. return self.boundjid.full
  506. @fulljid.setter
  507. def fulljid(self, value):
  508. log.warning("fulljid property deprecated. Use boundjid.full")
  509. self.boundjid.full = value
  510. @property
  511. def resource(self):
  512. """Attribute accessor for jid resource"""
  513. log.warning("resource property deprecated. Use boundjid.resource")
  514. return self.boundjid.resource
  515. @resource.setter
  516. def resource(self, value):
  517. log.warning("fulljid property deprecated. Use boundjid.resource")
  518. self.boundjid.resource = value
  519. @property
  520. def username(self):
  521. """Attribute accessor for jid usernode"""
  522. log.warning("username property deprecated. Use boundjid.user")
  523. return self.boundjid.user
  524. @username.setter
  525. def username(self, value):
  526. log.warning("username property deprecated. Use boundjid.user")
  527. self.boundjid.user = value
  528. @property
  529. def server(self):
  530. """Attribute accessor for jid host"""
  531. log.warning("server property deprecated. Use boundjid.host")
  532. return self.boundjid.server
  533. @server.setter
  534. def server(self, value):
  535. log.warning("server property deprecated. Use boundjid.host")
  536. self.boundjid.server = value
  537. @property
  538. def auto_authorize(self):
  539. """Auto accept or deny subscription requests.
  540. If ``True``, auto accept subscription requests.
  541. If ``False``, auto deny subscription requests.
  542. If ``None``, don't automatically respond.
  543. """
  544. return self.roster.auto_authorize
  545. @auto_authorize.setter
  546. def auto_authorize(self, value):
  547. self.roster.auto_authorize = value
  548. @property
  549. def auto_subscribe(self):
  550. """Auto send requests for mutual subscriptions.
  551. If ``True``, auto send mutual subscription requests.
  552. """
  553. return self.roster.auto_subscribe
  554. @auto_subscribe.setter
  555. def auto_subscribe(self, value):
  556. self.roster.auto_subscribe = value
  557. def set_jid(self, jid):
  558. """Rip a JID apart and claim it as our own."""
  559. log.debug("setting jid to %s", jid)
  560. self.boundjid = JID(jid, cache_lock=True)
  561. def getjidresource(self, fulljid):
  562. if '/' in fulljid:
  563. return fulljid.split('/', 1)[-1]
  564. else:
  565. return ''
  566. def getjidbare(self, fulljid):
  567. return fulljid.split('/', 1)[0]
  568. def _handle_session_start(self, event):
  569. """Reset redirection attempt count."""
  570. self._redirect_attempts = 0
  571. def _handle_disconnected(self, event):
  572. """When disconnected, reset the roster"""
  573. self.roster.reset()
  574. self.session_bind_event.clear()
  575. def _handle_stream_error(self, error):
  576. self.event('stream_error', error)
  577. if error['condition'] == 'see-other-host':
  578. other_host = error['see_other_host']
  579. if not other_host:
  580. log.warning("No other host specified.")
  581. return
  582. if self._redirect_attempts > self.max_redirects:
  583. log.error("Exceeded maximum number of redirection attempts.")
  584. return
  585. self._redirect_attempts += 1
  586. host = other_host
  587. port = 5222
  588. if '[' in other_host and ']' in other_host:
  589. host = other_host.split(']')[0][1:]
  590. elif ':' in other_host:
  591. host = other_host.split(':')[0]
  592. port_sec = other_host.split(']')[-1]
  593. if ':' in port_sec:
  594. port = int(port_sec.split(':')[1])
  595. self.address = (host, port)
  596. self.default_domain = host
  597. self.dns_records = None
  598. self.reconnect_delay = None
  599. self.reconnect()
  600. def _handle_message(self, msg):
  601. """Process incoming message stanzas."""
  602. if not self.is_component and not msg['to'].bare:
  603. msg['to'] = self.boundjid
  604. self.event('message', msg)
  605. def _handle_available(self, pres):
  606. self.roster[pres['to']][pres['from']].handle_available(pres)
  607. def _handle_unavailable(self, pres):
  608. self.roster[pres['to']][pres['from']].handle_unavailable(pres)
  609. def _handle_new_subscription(self, pres):
  610. """Attempt to automatically handle subscription requests.
  611. Subscriptions will be approved if the request is from
  612. a whitelisted JID, of :attr:`auto_authorize` is True. They
  613. will be rejected if :attr:`auto_authorize` is False. Setting
  614. :attr:`auto_authorize` to ``None`` will disable automatic
  615. subscription handling (except for whitelisted JIDs).
  616. If a subscription is accepted, a request for a mutual
  617. subscription will be sent if :attr:`auto_subscribe` is ``True``.
  618. """
  619. roster = self.roster[pres['to']]
  620. item = self.roster[pres['to']][pres['from']]
  621. if item['whitelisted']:
  622. item.authorize()
  623. if roster.auto_subscribe:
  624. item.subscribe()
  625. elif roster.auto_authorize:
  626. item.authorize()
  627. if roster.auto_subscribe:
  628. item.subscribe()
  629. elif roster.auto_authorize == False:
  630. item.unauthorize()
  631. def _handle_removed_subscription(self, pres):
  632. self.roster[pres['to']][pres['from']].handle_unauthorize(pres)
  633. def _handle_subscribe(self, pres):
  634. self.roster[pres['to']][pres['from']].handle_subscribe(pres)
  635. def _handle_subscribed(self, pres):
  636. self.roster[pres['to']][pres['from']].handle_subscribed(pres)
  637. def _handle_unsubscribe(self, pres):
  638. self.roster[pres['to']][pres['from']].handle_unsubscribe(pres)
  639. def _handle_unsubscribed(self, pres):
  640. self.roster[pres['to']][pres['from']].handle_unsubscribed(pres)
  641. def _handle_presence(self, presence):
  642. """Process incoming presence stanzas.
  643. Update the roster with presence information.
  644. """
  645. if not self.is_component and not presence['to'].bare:
  646. presence['to'] = self.boundjid
  647. self.event('presence', presence)
  648. self.event('presence_%s' % presence['type'], presence)
  649. # Check for changes in subscription state.
  650. if presence['type'] in ('subscribe', 'subscribed',
  651. 'unsubscribe', 'unsubscribed'):
  652. self.event('changed_subscription', presence)
  653. return
  654. elif not presence['type'] in ('available', 'unavailable') and \
  655. not presence['type'] in presence.showtypes:
  656. return
  657. def exception(self, exception):
  658. """Process any uncaught exceptions, notably
  659. :class:`~sleekxmpp.exceptions.IqError` and
  660. :class:`~sleekxmpp.exceptions.IqTimeout` exceptions.
  661. :param exception: An unhandled :class:`Exception` object.
  662. """
  663. if isinstance(exception, IqError):
  664. iq = exception.iq
  665. log.error('%s: %s', iq['error']['condition'],
  666. iq['error']['text'])
  667. log.warning('You should catch IqError exceptions')
  668. elif isinstance(exception, IqTimeout):
  669. iq = exception.iq
  670. log.error('Request timed out: %s', iq)
  671. log.warning('You should catch IqTimeout exceptions')
  672. elif isinstance(exception, SyntaxError):
  673. # Hide stream parsing errors that occur when the
  674. # stream is disconnected (they've been handled, we
  675. # don't need to make a mess in the logs).
  676. pass
  677. else:
  678. log.exception(exception)
  679. # Restore the old, lowercased name for backwards compatibility.
  680. basexmpp = BaseXMPP
  681. # To comply with PEP8, method names now use underscores.
  682. # Deprecated method names are re-mapped for backwards compatibility.
  683. BaseXMPP.registerPlugin = BaseXMPP.register_plugin
  684. BaseXMPP.makeIq = BaseXMPP.make_iq
  685. BaseXMPP.makeIqGet = BaseXMPP.make_iq_get
  686. BaseXMPP.makeIqResult = BaseXMPP.make_iq_result
  687. BaseXMPP.makeIqSet = BaseXMPP.make_iq_set
  688. BaseXMPP.makeIqError = BaseXMPP.make_iq_error
  689. BaseXMPP.makeIqQuery = BaseXMPP.make_iq_query
  690. BaseXMPP.makeQueryRoster = BaseXMPP.make_query_roster
  691. BaseXMPP.makeMessage = BaseXMPP.make_message
  692. BaseXMPP.makePresence = BaseXMPP.make_presence
  693. BaseXMPP.sendMessage = BaseXMPP.send_message
  694. BaseXMPP.sendPresence = BaseXMPP.send_presence
  695. BaseXMPP.sendPresenceSubscription = BaseXMPP.send_presence_subscription