summaryrefslogtreecommitdiff
path: root/sleekxmpp/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'sleekxmpp/plugins')
-rw-r--r--sleekxmpp/plugins/__init__.py4
-rw-r--r--sleekxmpp/plugins/gmail_notify.py149
-rw-r--r--sleekxmpp/plugins/google/__init__.py47
-rw-r--r--sleekxmpp/plugins/google/auth/__init__.py (renamed from sleekxmpp/plugins/gmail/__init__.py)9
-rw-r--r--sleekxmpp/plugins/google/auth/auth.py52
-rw-r--r--sleekxmpp/plugins/google/auth/stanza.py49
-rw-r--r--sleekxmpp/plugins/google/gmail/__init__.py10
-rw-r--r--sleekxmpp/plugins/google/gmail/notifications.py (renamed from sleekxmpp/plugins/gmail/notifications.py)24
-rw-r--r--sleekxmpp/plugins/google/gmail/stanza.py (renamed from sleekxmpp/plugins/gmail/stanza.py)0
-rw-r--r--sleekxmpp/plugins/google/nosave/__init__.py10
-rw-r--r--sleekxmpp/plugins/google/nosave/nosave.py (renamed from sleekxmpp/plugins/google_nosave/nosave.py)2
-rw-r--r--sleekxmpp/plugins/google/nosave/stanza.py (renamed from sleekxmpp/plugins/google_nosave/stanza.py)0
-rw-r--r--sleekxmpp/plugins/google/settings/__init__.py10
-rw-r--r--sleekxmpp/plugins/google/settings/settings.py (renamed from sleekxmpp/plugins/google_settings/settings.py)5
-rw-r--r--sleekxmpp/plugins/google/settings/stanza.py (renamed from sleekxmpp/plugins/google_settings/stanza.py)0
-rw-r--r--sleekxmpp/plugins/google_nosave/__init__.py15
-rw-r--r--sleekxmpp/plugins/google_settings/__init__.py15
-rw-r--r--sleekxmpp/plugins/xep_0050/adhoc.py124
-rw-r--r--sleekxmpp/plugins/xep_0071/__init__.py15
-rw-r--r--sleekxmpp/plugins/xep_0071/stanza.py80
-rw-r--r--sleekxmpp/plugins/xep_0071/xhtml_im.py30
-rw-r--r--sleekxmpp/plugins/xep_0153/vcard_avatar.py13
-rw-r--r--sleekxmpp/plugins/xep_0199/ping.py2
23 files changed, 562 insertions, 103 deletions
diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py
index c2d89c46..68fff5ef 100644
--- a/sleekxmpp/plugins/__init__.py
+++ b/sleekxmpp/plugins/__init__.py
@@ -11,9 +11,6 @@ from sleekxmpp.plugins.base import register_plugin, load_plugin
__all__ = [
- # Non-standard
- 'gmail', # Gmail searching and notifications
-
# XEPS
'xep_0004', # Data Forms
'xep_0009', # Jabber-RPC
@@ -33,6 +30,7 @@ __all__ = [
'xep_0060', # Pubsub (Client)
'xep_0065', # SOCKS5 Bytestreams
'xep_0066', # Out of Band Data
+ 'xep_0071', # XHTML-IM
'xep_0077', # In-Band Registration
# 'xep_0078', # Non-SASL auth. Don't automatically load
'xep_0080', # User Location
diff --git a/sleekxmpp/plugins/gmail_notify.py b/sleekxmpp/plugins/gmail_notify.py
new file mode 100644
index 00000000..fc97a2ab
--- /dev/null
+++ b/sleekxmpp/plugins/gmail_notify.py
@@ -0,0 +1,149 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+import logging
+from . import base
+from .. xmlstream.handler.callback import Callback
+from .. xmlstream.matcher.xpath import MatchXPath
+from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
+from .. stanza.iq import Iq
+
+
+log = logging.getLogger(__name__)
+
+
+class GmailQuery(ElementBase):
+ namespace = 'google:mail:notify'
+ name = 'query'
+ plugin_attrib = 'gmail'
+ interfaces = set(('newer-than-time', 'newer-than-tid', 'q', 'search'))
+
+ def getSearch(self):
+ return self['q']
+
+ def setSearch(self, search):
+ self['q'] = search
+
+ def delSearch(self):
+ del self['q']
+
+
+class MailBox(ElementBase):
+ namespace = 'google:mail:notify'
+ name = 'mailbox'
+ plugin_attrib = 'mailbox'
+ interfaces = set(('result-time', 'total-matched', 'total-estimate',
+ 'url', 'threads', 'matched', 'estimate'))
+
+ def getThreads(self):
+ threads = []
+ for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace,
+ MailThread.name)):
+ threads.append(MailThread(xml=threadXML, parent=None))
+ return threads
+
+ def getMatched(self):
+ return self['total-matched']
+
+ def getEstimate(self):
+ return self['total-estimate'] == '1'
+
+
+class MailThread(ElementBase):
+ namespace = 'google:mail:notify'
+ name = 'mail-thread-info'
+ plugin_attrib = 'thread'
+ interfaces = set(('tid', 'participation', 'messages', 'date',
+ 'senders', 'url', 'labels', 'subject', 'snippet'))
+ sub_interfaces = set(('labels', 'subject', 'snippet'))
+
+ def getSenders(self):
+ senders = []
+ sendersXML = self.xml.find('{%s}senders' % self.namespace)
+ if sendersXML is not None:
+ for senderXML in sendersXML.findall('{%s}sender' % self.namespace):
+ senders.append(MailSender(xml=senderXML, parent=None))
+ return senders
+
+
+class MailSender(ElementBase):
+ namespace = 'google:mail:notify'
+ name = 'sender'
+ plugin_attrib = 'sender'
+ interfaces = set(('address', 'name', 'originator', 'unread'))
+
+ def getOriginator(self):
+ return self.xml.attrib.get('originator', '0') == '1'
+
+ def getUnread(self):
+ return self.xml.attrib.get('unread', '0') == '1'
+
+
+class NewMail(ElementBase):
+ namespace = 'google:mail:notify'
+ name = 'new-mail'
+ plugin_attrib = 'new-mail'
+
+
+class gmail_notify(base.base_plugin):
+ """
+ Google Talk: Gmail Notifications
+ """
+
+ def plugin_init(self):
+ self.description = 'Google Talk: Gmail Notifications'
+
+ self.xmpp.registerHandler(
+ Callback('Gmail Result',
+ MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns,
+ MailBox.namespace,
+ MailBox.name)),
+ self.handle_gmail))
+
+ self.xmpp.registerHandler(
+ Callback('Gmail New Mail',
+ MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns,
+ NewMail.namespace,
+ NewMail.name)),
+ self.handle_new_mail))
+
+ registerStanzaPlugin(Iq, GmailQuery)
+ registerStanzaPlugin(Iq, MailBox)
+ registerStanzaPlugin(Iq, NewMail)
+
+ self.last_result_time = None
+
+ def handle_gmail(self, iq):
+ mailbox = iq['mailbox']
+ approx = ' approximately' if mailbox['estimated'] else ''
+ log.info('Gmail: Received%s %s emails', approx, mailbox['total-matched'])
+ self.last_result_time = mailbox['result-time']
+ self.xmpp.event('gmail_messages', iq)
+
+ def handle_new_mail(self, iq):
+ log.info("Gmail: New emails received!")
+ self.xmpp.event('gmail_notify')
+ self.checkEmail()
+
+ def getEmail(self, query=None):
+ return self.search(query)
+
+ def checkEmail(self):
+ return self.search(newer=self.last_result_time)
+
+ def search(self, query=None, newer=None):
+ if query is None:
+ log.info("Gmail: Checking for new emails")
+ else:
+ log.info('Gmail: Searching for emails matching: "%s"', query)
+ iq = self.xmpp.Iq()
+ iq['type'] = 'get'
+ iq['to'] = self.xmpp.boundjid.bare
+ iq['gmail']['q'] = query
+ iq['gmail']['newer-than-time'] = newer
+ return iq.send()
diff --git a/sleekxmpp/plugins/google/__init__.py b/sleekxmpp/plugins/google/__init__.py
new file mode 100644
index 00000000..bd7ca123
--- /dev/null
+++ b/sleekxmpp/plugins/google/__init__.py
@@ -0,0 +1,47 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins.base import register_plugin, BasePlugin
+
+from sleekxmpp.plugins.google.gmail import Gmail
+from sleekxmpp.plugins.google.auth import GoogleAuth
+from sleekxmpp.plugins.google.settings import GoogleSettings
+from sleekxmpp.plugins.google.nosave import GoogleNoSave
+
+
+class Google(BasePlugin):
+
+ """
+ Google: Custom GTalk Features
+
+ Also see: <https://developers.google.com/talk/jep_extensions/extensions>
+ """
+
+ name = 'google'
+ description = 'Google: Custom GTalk Features'
+ dependencies = set([
+ 'gmail',
+ 'google_settings',
+ 'google_nosave',
+ 'google_auth'
+ ])
+
+ def __getitem__(self, attr):
+ if attr in ('settings', 'nosave', 'auth'):
+ return self.xmpp['google_%s' % attr]
+ elif attr == 'gmail':
+ return self.xmpp['gmail']
+ else:
+ raise KeyError(attr)
+
+
+register_plugin(Gmail)
+register_plugin(GoogleAuth)
+register_plugin(GoogleSettings)
+register_plugin(GoogleNoSave)
+register_plugin(Google)
diff --git a/sleekxmpp/plugins/gmail/__init__.py b/sleekxmpp/plugins/google/auth/__init__.py
index a87c78bb..5a8feb0d 100644
--- a/sleekxmpp/plugins/gmail/__init__.py
+++ b/sleekxmpp/plugins/google/auth/__init__.py
@@ -6,10 +6,5 @@
See the file LICENSE for copying permission.
"""
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.gmail import stanza
-from sleekxmpp.plugins.gmail.notifications import Gmail
-
-
-register_plugin(Gmail)
+from sleekxmpp.plugins.google.auth import stanza
+from sleekxmpp.plugins.google.auth.auth import GoogleAuth
diff --git a/sleekxmpp/plugins/google/auth/auth.py b/sleekxmpp/plugins/google/auth/auth.py
new file mode 100644
index 00000000..042bd404
--- /dev/null
+++ b/sleekxmpp/plugins/google/auth/auth.py
@@ -0,0 +1,52 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+import logging
+
+from sleekxmpp.xmlstream import register_stanza_plugin
+from sleekxmpp.plugins import BasePlugin
+from sleekxmpp.plugins.google.auth import stanza
+
+
+log = logging.getLogger(__name__)
+
+
+class GoogleAuth(BasePlugin):
+
+ """
+ Google: Auth Extensions (JID Domain Discovery, OAuth2)
+
+ Also see:
+ <https://developers.google.com/talk/jep_extensions/jid_domain_change>
+ <https://developers.google.com/talk/jep_extensions/oauth>
+ """
+
+ name = 'google_auth'
+ description = 'Google: Auth Extensions (JID Domain Discovery, OAuth2)'
+ dependencies = set(['feature_mechanisms'])
+ stanza = stanza
+
+ def plugin_init(self):
+ self.xmpp.namespace_map['http://www.google.com/talk/protocol/auth'] = 'ga'
+
+ register_stanza_plugin(self.xmpp['feature_mechanisms'].stanza.Auth,
+ stanza.GoogleAuth)
+
+ self.xmpp.add_filter('out', self._auth)
+
+ def plugin_end(self):
+ self.xmpp.del_filter('out', self._auth)
+
+ def _auth(self, stanza):
+ if isinstance(stanza, self.xmpp['feature_mechanisms'].stanza.Auth):
+ stanza.stream = self.xmpp
+ stanza['google']['client_uses_full_bind_result'] = True
+ if stanza['mechanism'] == 'X-OAUTH2':
+ stanza['google']['service'] = 'oauth2'
+ print(stanza)
+ return stanza
diff --git a/sleekxmpp/plugins/google/auth/stanza.py b/sleekxmpp/plugins/google/auth/stanza.py
new file mode 100644
index 00000000..49c5cba7
--- /dev/null
+++ b/sleekxmpp/plugins/google/auth/stanza.py
@@ -0,0 +1,49 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.xmlstream import ElementBase, ET
+
+
+class GoogleAuth(ElementBase):
+ name = 'auth'
+ namespace = 'http://www.google.com/talk/protocol/auth'
+ plugin_attrib = 'google'
+ interfaces = set(['client_uses_full_bind_result', 'service'])
+
+ discovery_attr= '{%s}client-uses-full-bind-result' % namespace
+ service_attr= '{%s}service' % namespace
+
+ def setup(self, xml):
+ """Don't create XML for the plugin."""
+ self.xml = ET.Element('')
+ print('setting up google extension')
+
+ def get_client_uses_full_bind_result(self):
+ return self.parent()._get_attr(self.disovery_attr) == 'true'
+
+ def set_client_uses_full_bind_result(self, value):
+ print('>>>', value)
+ if value in (True, 'true'):
+ self.parent()._set_attr(self.discovery_attr, 'true')
+ else:
+ self.parent()._del_attr(self.discovery_attr)
+
+ def del_client_uses_full_bind_result(self):
+ self.parent()._del_attr(self.discovery_attr)
+
+ def get_service(self):
+ return self.parent()._get_attr(self.service_attr, '')
+
+ def set_service(self, value):
+ if value:
+ self.parent()._set_attr(self.service_attr, value)
+ else:
+ self.parent()._del_attr(self.service_attr)
+
+ def del_service(self):
+ self.parent()._del_attr(self.service_attr)
diff --git a/sleekxmpp/plugins/google/gmail/__init__.py b/sleekxmpp/plugins/google/gmail/__init__.py
new file mode 100644
index 00000000..a92e363b
--- /dev/null
+++ b/sleekxmpp/plugins/google/gmail/__init__.py
@@ -0,0 +1,10 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins.google.gmail import stanza
+from sleekxmpp.plugins.google.gmail.notifications import Gmail
diff --git a/sleekxmpp/plugins/gmail/notifications.py b/sleekxmpp/plugins/google/gmail/notifications.py
index dbc68162..7226fa1f 100644
--- a/sleekxmpp/plugins/gmail/notifications.py
+++ b/sleekxmpp/plugins/google/gmail/notifications.py
@@ -13,7 +13,7 @@ from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import MatchXPath
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.gmail import stanza
+from sleekxmpp.plugins.google.gmail import stanza
log = logging.getLogger(__name__)
@@ -46,6 +46,7 @@ class Gmail(BasePlugin):
self._handle_new_mail))
self._last_result_time = None
+ self._last_result_tid = None
def plugin_end(self):
self.xmpp.remove_handler('Gmail New Mail')
@@ -57,13 +58,23 @@ class Gmail(BasePlugin):
def check(self, block=True, timeout=None, callback=None):
last_time = self._last_result_time
- self._last_result_time = str(int(time.time() * 1000))
- return self.search(newer=last_time,
+ last_tid = self._last_result_tid
+
+ def check_callback(data):
+ self._last_result_time = data["gmail_messages"]["result_time"]
+ if data["gmail_messages"]["threads"]:
+ self._last_result_tid = \
+ data["gmail_messages"]["threads"][0]["tid"]
+ if callback:
+ callback(data)
+
+ return self.search(newer_time=last_time,
+ newer_tid=last_tid,
block=block,
timeout=timeout,
- callback=callback)
+ callback=check_callback)
- def search(self, query=None, newer=None, block=True,
+ def search(self, query=None, newer_time=None, newer_tid=None, block=True,
timeout=None, callback=None):
if not query:
log.info('Gmail: Checking for new email')
@@ -73,5 +84,6 @@ class Gmail(BasePlugin):
iq['type'] = 'get'
iq['to'] = self.xmpp.boundjid.bare
iq['gmail']['search'] = query
- iq['gmail']['newer_than_time'] = newer
+ iq['gmail']['newer_than_time'] = newer_time
+ iq['gmail']['newer_than_tid'] = newer_tid
return iq.send(block=block, timeout=timeout, callback=callback)
diff --git a/sleekxmpp/plugins/gmail/stanza.py b/sleekxmpp/plugins/google/gmail/stanza.py
index e7e308e1..e7e308e1 100644
--- a/sleekxmpp/plugins/gmail/stanza.py
+++ b/sleekxmpp/plugins/google/gmail/stanza.py
diff --git a/sleekxmpp/plugins/google/nosave/__init__.py b/sleekxmpp/plugins/google/nosave/__init__.py
new file mode 100644
index 00000000..57847af5
--- /dev/null
+++ b/sleekxmpp/plugins/google/nosave/__init__.py
@@ -0,0 +1,10 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins.google.nosave import stanza
+from sleekxmpp.plugins.google.nosave.nosave import GoogleNoSave
diff --git a/sleekxmpp/plugins/google_nosave/nosave.py b/sleekxmpp/plugins/google/nosave/nosave.py
index 1d3b36db..d6bef615 100644
--- a/sleekxmpp/plugins/google_nosave/nosave.py
+++ b/sleekxmpp/plugins/google/nosave/nosave.py
@@ -13,7 +13,7 @@ from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.google_nosave import stanza
+from sleekxmpp.plugins.google.nosave import stanza
log = logging.getLogger(__name__)
diff --git a/sleekxmpp/plugins/google_nosave/stanza.py b/sleekxmpp/plugins/google/nosave/stanza.py
index d8701322..d8701322 100644
--- a/sleekxmpp/plugins/google_nosave/stanza.py
+++ b/sleekxmpp/plugins/google/nosave/stanza.py
diff --git a/sleekxmpp/plugins/google/settings/__init__.py b/sleekxmpp/plugins/google/settings/__init__.py
new file mode 100644
index 00000000..c3a0471d
--- /dev/null
+++ b/sleekxmpp/plugins/google/settings/__init__.py
@@ -0,0 +1,10 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins.google.settings import stanza
+from sleekxmpp.plugins.google.settings.settings import GoogleSettings
diff --git a/sleekxmpp/plugins/google_settings/settings.py b/sleekxmpp/plugins/google/settings/settings.py
index 6bd209c7..7122ff56 100644
--- a/sleekxmpp/plugins/google_settings/settings.py
+++ b/sleekxmpp/plugins/google/settings/settings.py
@@ -13,10 +13,7 @@ from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.google_settings import stanza
-
-
-log = logging.getLogger(__name__)
+from sleekxmpp.plugins.google.settings import stanza
class GoogleSettings(BasePlugin):
diff --git a/sleekxmpp/plugins/google_settings/stanza.py b/sleekxmpp/plugins/google/settings/stanza.py
index d8161770..d8161770 100644
--- a/sleekxmpp/plugins/google_settings/stanza.py
+++ b/sleekxmpp/plugins/google/settings/stanza.py
diff --git a/sleekxmpp/plugins/google_nosave/__init__.py b/sleekxmpp/plugins/google_nosave/__init__.py
deleted file mode 100644
index eba50a35..00000000
--- a/sleekxmpp/plugins/google_nosave/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.google_nosave import stanza
-from sleekxmpp.plugins.google_nosave.nosave import GoogleNoSave
-
-
-register_plugin(GoogleNoSave)
diff --git a/sleekxmpp/plugins/google_settings/__init__.py b/sleekxmpp/plugins/google_settings/__init__.py
deleted file mode 100644
index ef4b2342..00000000
--- a/sleekxmpp/plugins/google_settings/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.google_settings import stanza
-from sleekxmpp.plugins.google_settings.settings import GoogleSettings
-
-
-register_plugin(GoogleSettings)
diff --git a/sleekxmpp/plugins/xep_0050/adhoc.py b/sleekxmpp/plugins/xep_0050/adhoc.py
index 90256228..e5594c3f 100644
--- a/sleekxmpp/plugins/xep_0050/adhoc.py
+++ b/sleekxmpp/plugins/xep_0050/adhoc.py
@@ -267,20 +267,50 @@ class XEP_0050(BasePlugin):
iq -- The command continuation request.
"""
sessionid = iq['command']['sessionid']
- session = self.sessions[sessionid]
+ session = self.sessions.get(sessionid)
- handler = session['next']
- interfaces = session['interfaces']
- results = []
- for stanza in iq['command']['substanzas']:
- if stanza.plugin_attrib in interfaces:
- results.append(stanza)
- if len(results) == 1:
- results = results[0]
+ if session:
+ handler = session['next']
+ interfaces = session['interfaces']
+ results = []
+ for stanza in iq['command']['substanzas']:
+ if stanza.plugin_attrib in interfaces:
+ results.append(stanza)
+ if len(results) == 1:
+ results = results[0]
- session = handler(results, session)
+ session = handler(results, session)
- self._process_command_response(iq, session)
+ self._process_command_response(iq, session)
+ else:
+ raise XMPPError('item-not-found')
+
+ def _handle_command_prev(self, iq):
+ """
+ Process a request for the prev step in the workflow
+ for a command with multiple steps.
+
+ Arguments:
+ iq -- The command continuation request.
+ """
+ sessionid = iq['command']['sessionid']
+ session = self.sessions.get(sessionid)
+
+ if session:
+ handler = session['prev']
+ interfaces = session['interfaces']
+ results = []
+ for stanza in iq['command']['substanzas']:
+ if stanza.plugin_attrib in interfaces:
+ results.append(stanza)
+ if len(results) == 1:
+ results = results[0]
+
+ session = handler(results, session)
+
+ self._process_command_response(iq, session)
+ else:
+ raise XMPPError('item-not-found')
def _process_command_response(self, iq, session):
"""
@@ -348,23 +378,23 @@ class XEP_0050(BasePlugin):
"""
node = iq['command']['node']
sessionid = iq['command']['sessionid']
- session = self.sessions[sessionid]
- handler = session['cancel']
- if handler:
- handler(iq, session)
+ session = self.sessions.get(sessionid)
- try:
+ if session:
+ handler = session['cancel']
+ if handler:
+ handler(iq, session)
del self.sessions[sessionid]
- except:
- pass
+ iq.reply()
+ iq['command']['node'] = node
+ iq['command']['sessionid'] = sessionid
+ iq['command']['status'] = 'canceled'
+ iq['command']['notes'] = session['notes']
+ iq.send()
+ else:
+ raise XMPPError('item-not-found')
- iq.reply()
- iq['command']['node'] = node
- iq['command']['sessionid'] = sessionid
- iq['command']['status'] = 'canceled'
- iq['command']['notes'] = session['notes']
- iq.send()
def _handle_command_complete(self, iq):
"""
@@ -378,28 +408,32 @@ class XEP_0050(BasePlugin):
"""
node = iq['command']['node']
sessionid = iq['command']['sessionid']
- session = self.sessions[sessionid]
- handler = session['next']
- interfaces = session['interfaces']
- results = []
- for stanza in iq['command']['substanzas']:
- if stanza.plugin_attrib in interfaces:
- results.append(stanza)
- if len(results) == 1:
- results = results[0]
+ session = self.sessions.get(sessionid)
- if handler:
- handler(results, session)
+ if session:
+ handler = session['next']
+ interfaces = session['interfaces']
+ results = []
+ for stanza in iq['command']['substanzas']:
+ if stanza.plugin_attrib in interfaces:
+ results.append(stanza)
+ if len(results) == 1:
+ results = results[0]
- iq.reply()
- iq['command']['node'] = node
- iq['command']['sessionid'] = sessionid
- iq['command']['actions'] = []
- iq['command']['status'] = 'completed'
- iq['command']['notes'] = session['notes']
- iq.send()
+ if handler:
+ handler(results, session)
+
+ del self.sessions[sessionid]
- del self.sessions[sessionid]
+ iq.reply()
+ iq['command']['node'] = node
+ iq['command']['sessionid'] = sessionid
+ iq['command']['actions'] = []
+ iq['command']['status'] = 'completed'
+ iq['command']['notes'] = session['notes']
+ iq.send()
+ else:
+ raise XMPPError('item-not-found')
# =================================================================
# Client side (command user) API
@@ -537,7 +571,7 @@ class XEP_0050(BasePlugin):
else:
iq.send(block=False, callback=self._handle_command_result)
- def continue_command(self, session):
+ def continue_command(self, session, direction='next'):
"""
Execute the next action of the command.
@@ -551,7 +585,7 @@ class XEP_0050(BasePlugin):
self.send_command(session['jid'],
session['node'],
ifrom=session.get('from', None),
- action='next',
+ action=direction,
payload=session.get('payload', None),
sessionid=session['id'],
flow=True,
diff --git a/sleekxmpp/plugins/xep_0071/__init__.py b/sleekxmpp/plugins/xep_0071/__init__.py
new file mode 100644
index 00000000..c21e9265
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0071/__init__.py
@@ -0,0 +1,15 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permissio
+"""
+
+from sleekxmpp.plugins.base import register_plugin
+
+from sleekxmpp.plugins.xep_0071.stanza import XHTML_IM
+from sleekxmpp.plugins.xep_0071.xhtml_im import XEP_0071
+
+
+register_plugin(XEP_0071)
diff --git a/sleekxmpp/plugins/xep_0071/stanza.py b/sleekxmpp/plugins/xep_0071/stanza.py
new file mode 100644
index 00000000..ce91c552
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0071/stanza.py
@@ -0,0 +1,80 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.stanza import Message
+from sleekxmpp.thirdparty import OrderedDict
+from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin, tostring
+
+
+XHTML_NS = 'http://www.w3.org/1999/xhtml'
+
+
+class XHTML_IM(ElementBase):
+
+ namespace = 'http://jabber.org/protocol/xhtml-im'
+ name = 'html'
+ interfaces = set(['body'])
+ lang_interfaces = set(['body'])
+ plugin_attrib = name
+
+ def set_body(self, content, lang=None):
+ if lang is None:
+ lang = self.get_lang()
+ self.del_body(lang)
+ if lang == '*':
+ for sublang, subcontent in content.items():
+ self.set_body(subcontent, sublang)
+ else:
+ if isinstance(content, type(ET.Element('test'))):
+ content = ET.tostring(content)
+ else:
+ content = str(content)
+ header = '<body xmlns="%s"' % XHTML_NS
+ if lang:
+ header = '%s xml:lang="%s"' % (header, lang)
+ content = '%s>%s</body>' % (header, content)
+ xhtml = ET.fromstring(content)
+ self.xml.append(xhtml)
+
+ def get_body(self, lang=None):
+ """Return the contents of the HTML body."""
+ if lang is None:
+ lang = self.get_lang()
+
+ bodies = self.xml.findall('{%s}body' % XHTML_NS)
+
+ if lang == '*':
+ result = OrderedDict()
+ for body in bodies:
+ body_lang = body.attrib.get('{%s}lang' % self.xml_ns, '')
+ body_result = []
+ body_result.append(body.text if body.text else '')
+ for child in body:
+ body_result.append(tostring(child, xmlns=XHTML_NS))
+ body_result.append(body.tail if body.tail else '')
+ result[body_lang] = ''.join(body_result)
+ return result
+ else:
+ for body in bodies:
+ if body.attrib.get('{%s}lang' % self.xml_ns, self.get_lang()) == lang:
+ result = []
+ result.append(body.text if body.text else '')
+ for child in body:
+ result.append(tostring(child, xmlns=XHTML_NS))
+ result.append(body.tail if body.tail else '')
+ return ''.join(result)
+ return ''
+
+ def del_body(self, lang=None):
+ if lang is None:
+ lang = self.get_lang()
+ bodies = self.xml.findall('{%s}body' % XHTML_NS)
+ for body in bodies:
+ if body.attrib.get('{%s}lang' % self.xml_ns, self.get_lang()) == lang:
+ self.xml.remove(body)
+ return
diff --git a/sleekxmpp/plugins/xep_0071/xhtml_im.py b/sleekxmpp/plugins/xep_0071/xhtml_im.py
new file mode 100644
index 00000000..096a00aa
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0071/xhtml_im.py
@@ -0,0 +1,30 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+
+from sleekxmpp.stanza import Message
+from sleekxmpp.plugins import BasePlugin
+from sleekxmpp.xmlstream import register_stanza_plugin
+from sleekxmpp.plugins.xep_0071 import stanza, XHTML_IM
+
+
+class XEP_0071(BasePlugin):
+
+ name = 'xep_0071'
+ description = 'XEP-0071: XHTML-IM'
+ dependencies = set(['xep_0030'])
+ stanza = stanza
+
+ def plugin_init(self):
+ register_stanza_plugin(Message, XHTML_IM)
+
+ def session_bind(self, jid):
+ self.xmpp['xep_0030'].add_feature(feature=XHTML_IM.namespace)
+
+ def plugin_end(self):
+ self.xmpp['xep_0030'].del_feature(feature=XHTML_IM.namespace)
diff --git a/sleekxmpp/plugins/xep_0153/vcard_avatar.py b/sleekxmpp/plugins/xep_0153/vcard_avatar.py
index 874897cb..271ac995 100644
--- a/sleekxmpp/plugins/xep_0153/vcard_avatar.py
+++ b/sleekxmpp/plugins/xep_0153/vcard_avatar.py
@@ -10,12 +10,9 @@ import hashlib
import logging
import threading
-from sleekxmpp import JID
from sleekxmpp.stanza import Presence
from sleekxmpp.exceptions import XMPPError
from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.plugins.base import BasePlugin
from sleekxmpp.plugins.xep_0153 import stanza, VCardTempUpdate
@@ -86,11 +83,10 @@ class XEP_0153(BasePlugin):
else:
new_hash = hashlib.sha1(data).hexdigest()
self.api['set_hash'](self.xmpp.boundjid, args=new_hash)
+ self._allow_advertising.set()
except XMPPError:
log.debug('Could not retrieve vCard for %s' % self.xmpp.boundjid.bare)
- self._allow_advertising.set()
-
def _end(self, event):
self._allow_advertising.clear()
@@ -128,6 +124,11 @@ class XEP_0153(BasePlugin):
log.debug('Could not retrieve vCard for %s' % jid)
def _recv_presence(self, pres):
+ if pres['muc']['affiliation']:
+ # Don't process vCard avatars for MUC occupants
+ # since they all share the same bare JID.
+ return
+
if not pres.match('presence/vcard_temp_update'):
self.api['set_hash'](pres['from'], args=None)
return
@@ -135,7 +136,7 @@ class XEP_0153(BasePlugin):
data = pres['vcard_temp_update']['photo']
if data is None:
return
- elif data == '' or data != self.api['get_hash'](pres['to']):
+ elif data == '' or data != self.api['get_hash'](pres['from']):
ifrom = pres['to'] if self.xmpp.is_component else None
self.api['reset_hash'](pres['from'], ifrom=ifrom)
self.xmpp.event('vcard_avatar_update', pres)
diff --git a/sleekxmpp/plugins/xep_0199/ping.py b/sleekxmpp/plugins/xep_0199/ping.py
index e095a551..b024880e 100644
--- a/sleekxmpp/plugins/xep_0199/ping.py
+++ b/sleekxmpp/plugins/xep_0199/ping.py
@@ -103,7 +103,7 @@ class XEP_0199(BasePlugin):
def disable_keepalive(self, event=None):
self.xmpp.scheduler.remove('Ping keepalive')
- def _keepalive(self, event):
+ def _keepalive(self, event=None):
log.debug("Keepalive ping...")
try:
rtt = self.ping(self.xmpp.boundjid.host, self.timeout)