summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/register_account.py8
-rwxr-xr-xsetup.py7
-rw-r--r--sleekxmpp/basexmpp.py1
-rw-r--r--sleekxmpp/exceptions.py2
-rw-r--r--sleekxmpp/features/feature_bind/bind.py8
-rw-r--r--sleekxmpp/features/feature_mechanisms/mechanisms.py6
-rw-r--r--sleekxmpp/plugins/__init__.py5
-rw-r--r--sleekxmpp/plugins/gmail_notify.py149
-rw-r--r--sleekxmpp/plugins/google/__init__.py47
-rw-r--r--sleekxmpp/plugins/google/auth/__init__.py10
-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)37
-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/old_0004.py421
-rw-r--r--sleekxmpp/plugins/old_0009.py277
-rw-r--r--sleekxmpp/plugins/xep_0004/stanza/field.py5
-rw-r--r--sleekxmpp/plugins/xep_0004/stanza/form.py12
-rw-r--r--sleekxmpp/plugins/xep_0020/__init__.py (renamed from sleekxmpp/plugins/gmail/__init__.py)7
-rw-r--r--sleekxmpp/plugins/xep_0020/feature_negotiation.py36
-rw-r--r--sleekxmpp/plugins/xep_0020/stanza.py17
-rw-r--r--sleekxmpp/plugins/xep_0045.py23
-rw-r--r--sleekxmpp/plugins/xep_0050/adhoc.py124
-rw-r--r--sleekxmpp/plugins/xep_0060/pubsub.py2
-rw-r--r--sleekxmpp/plugins/xep_0060/stanza/pubsub.py40
-rw-r--r--sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py3
-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_0077/register.py21
-rw-r--r--sleekxmpp/plugins/xep_0153/vcard_avatar.py13
-rw-r--r--sleekxmpp/plugins/xep_0199/ping.py2
-rw-r--r--sleekxmpp/stanza/htmlim.py71
-rw-r--r--sleekxmpp/stanza/iq.py22
-rw-r--r--sleekxmpp/xmlstream/stanzabase.py52
-rw-r--r--sleekxmpp/xmlstream/tostring.py35
-rw-r--r--tests/test_stanza_message.py4
-rw-r--r--tests/test_stanza_xep_0060.py14
-rw-r--r--tox.ini2
48 files changed, 767 insertions, 1009 deletions
diff --git a/examples/register_account.py b/examples/register_account.py
index bd9b1160..422e5602 100644
--- a/examples/register_account.py
+++ b/examples/register_account.py
@@ -51,7 +51,7 @@ class RegisterBot(sleekxmpp.ClientXMPP):
# The register event provides an Iq result stanza with
# a registration form from the server. This may include
- # the basic registration fields, a data form, an
+ # the basic registration fields, a data form, an
# out-of-band URL, or any combination. For more advanced
# cases, you will need to examine the fields provided
# and respond accordingly. SleekXMPP provides plugins
@@ -104,7 +104,7 @@ class RegisterBot(sleekxmpp.ClientXMPP):
resp.send(now=True)
logging.info("Account created for %s!" % self.boundjid)
except IqError as e:
- logging.error("Could not register account: %s" %
+ logging.error("Could not register account: %s" %
e.iq['error']['text'])
self.disconnect()
except IqTimeout:
@@ -153,6 +153,10 @@ if __name__ == '__main__':
xmpp.register_plugin('xep_0066') # Out-of-band Data
xmpp.register_plugin('xep_0077') # In-band Registration
+ # Some servers don't advertise support for inband registration, even
+ # though they allow it. If this applies to your server, use:
+ xmpp['xep_0077'].force_registration = True
+
# If you are working with an OpenFire server, you may need
# to adjust the SSL version used:
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
diff --git a/setup.py b/setup.py
index e9c51641..f48de173 100755
--- a/setup.py
+++ b/setup.py
@@ -62,6 +62,7 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0012',
'sleekxmpp/plugins/xep_0013',
'sleekxmpp/plugins/xep_0016',
+ 'sleekxmpp/plugins/xep_0020',
'sleekxmpp/plugins/xep_0027',
'sleekxmpp/plugins/xep_0030',
'sleekxmpp/plugins/xep_0030/stanza',
@@ -76,6 +77,7 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0060/stanza',
'sleekxmpp/plugins/xep_0065',
'sleekxmpp/plugins/xep_0066',
+ 'sleekxmpp/plugins/xep_0071',
'sleekxmpp/plugins/xep_0077',
'sleekxmpp/plugins/xep_0078',
'sleekxmpp/plugins/xep_0080',
@@ -111,6 +113,11 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0297',
'sleekxmpp/plugins/xep_0308',
'sleekxmpp/plugins/xep_0313',
+ 'sleekxmpp/plugins/google',
+ 'sleekxmpp/plugins/google/gmail',
+ 'sleekxmpp/plugins/google/auth',
+ 'sleekxmpp/plugins/google/settings',
+ 'sleekxmpp/plugins/google/nosave',
'sleekxmpp/features',
'sleekxmpp/features/feature_mechanisms',
'sleekxmpp/features/feature_mechanisms/stanza',
diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py
index c3ff5ba3..a54e4bb6 100644
--- a/sleekxmpp/basexmpp.py
+++ b/sleekxmpp/basexmpp.py
@@ -201,7 +201,6 @@ class BaseXMPP(XMLStream):
# 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.
diff --git a/sleekxmpp/exceptions.py b/sleekxmpp/exceptions.py
index 8036532d..8a2aa75c 100644
--- a/sleekxmpp/exceptions.py
+++ b/sleekxmpp/exceptions.py
@@ -42,7 +42,7 @@ class XMPPError(Exception):
Defaults to ``True``.
"""
- def __init__(self, condition='undefined-condition', text=None,
+ def __init__(self, condition='undefined-condition', text='',
etype='cancel', extension=None, extension_ns=None,
extension_args=None, clear=True):
if extension_args is None:
diff --git a/sleekxmpp/features/feature_bind/bind.py b/sleekxmpp/features/feature_bind/bind.py
index 0f97952d..d2adc27b 100644
--- a/sleekxmpp/features/feature_bind/bind.py
+++ b/sleekxmpp/features/feature_bind/bind.py
@@ -41,12 +41,12 @@ class FeatureBind(BasePlugin):
Arguments:
features -- The stream features stanza.
"""
- log.debug("Requesting resource: %s", self.xmpp.boundjid.resource)
+ log.debug("Requesting resource: %s", self.xmpp.requested_jid.resource)
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq.enable('bind')
- if self.xmpp.boundjid.resource:
- iq['bind']['resource'] = self.xmpp.boundjid.resource
+ if self.xmpp.requested_jid.resource:
+ iq['bind']['resource'] = self.xmpp.requested_jid.resource
response = iq.send(now=True)
self.xmpp.boundjid = JID(response['bind']['jid'], cache_lock=True)
@@ -56,7 +56,7 @@ class FeatureBind(BasePlugin):
self.xmpp.features.add('bind')
- log.info("Node set to: %s", self.xmpp.boundjid.full)
+ log.info("JID set to: %s", self.xmpp.boundjid.full)
if 'session' not in features['features']:
log.debug("Established Session")
diff --git a/sleekxmpp/features/feature_mechanisms/mechanisms.py b/sleekxmpp/features/feature_mechanisms/mechanisms.py
index 555d8fad..81b997eb 100644
--- a/sleekxmpp/features/feature_mechanisms/mechanisms.py
+++ b/sleekxmpp/features/feature_mechanisms/mechanisms.py
@@ -173,6 +173,9 @@ class FeatureMechanisms(BasePlugin):
self.xmpp.event("no_auth", direct=True)
self.attempted_mechs = set()
return self.xmpp.disconnect()
+ except StringPrepError:
+ log.exception("A credential value did not pass SASLprep.")
+ self.xmpp.disconnect()
resp = stanza.Auth(self.xmpp)
resp['mechanism'] = self.mech.name
@@ -189,9 +192,6 @@ class FeatureMechanisms(BasePlugin):
"A security breach is possible.")
self.attempted_mechs.add(self.mech.name)
self.xmpp.disconnect()
- except StringPrepError:
- log.exception("A credential value did not pass SASLprep.")
- self.xmpp.disconnect()
else:
resp.send(now=True)
diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py
index 61b5ff9c..e3e01a5e 100644
--- a/sleekxmpp/plugins/__init__.py
+++ b/sleekxmpp/plugins/__init__.py
@@ -11,15 +11,13 @@ 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
'xep_0012', # Last Activity
'xep_0013', # Flexible Offline Message Retrieval
'xep_0016', # Privacy Lists
+ 'xep_0020', # Feature Negotiation
'xep_0027', # Current Jabber OpenPGP Usage
'xep_0030', # Service Discovery
'xep_0033', # Extended Stanza Addresses
@@ -33,6 +31,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_0079', # Advanced Message Processing
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/google/auth/__init__.py b/sleekxmpp/plugins/google/auth/__init__.py
new file mode 100644
index 00000000..5a8feb0d
--- /dev/null
+++ b/sleekxmpp/plugins/google/auth/__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.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 9cc67f04..509a95fd 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__)
@@ -52,7 +52,7 @@ class Gmail(BasePlugin):
self.xmpp.remove_handler('Gmail New Mail')
def _handle_new_mail(self, iq):
- log.info("Gmail: New email!")
+ log.info('Gmail: New email!')
iq.reply().send()
self.xmpp.event('gmail_notification')
@@ -60,19 +60,26 @@ class Gmail(BasePlugin):
last_time = self._last_result_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=check_callback)
+ if not block:
+ callback = lambda iq: self._update_last_results(iq, callback)
+
+ resp = self.search(newer_time=last_time,
+ newer_tid=last_tid,
+ block=block,
+ timeout=timeout,
+ callback=callback)
+
+ if block:
+ self._update_last_results(resp)
+ return resp
+
+ def _update_last_results(self, iq, callback=None):
+ self._last_result_time = data['gmail_messages']['result_time']
+ threads = data['gmail_messages']['threads']
+ if threads:
+ self._last_result_tid = threads[0]['tid']
+ if callback:
+ callback(iq)
def search(self, query=None, newer_time=None, newer_tid=None, block=True,
timeout=None, callback=None):
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/old_0004.py b/sleekxmpp/plugins/old_0004.py
deleted file mode 100644
index 7f086866..00000000
--- a/sleekxmpp/plugins/old_0004.py
+++ /dev/null
@@ -1,421 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-from . import base
-import logging
-from xml.etree import cElementTree as ET
-import copy
-import logging
-#TODO support item groups and results
-
-
-log = logging.getLogger(__name__)
-
-
-class old_0004(base.base_plugin):
-
- def plugin_init(self):
- self.xep = '0004'
- self.description = '*Deprecated Data Forms'
- self.xmpp.add_handler("<message><x xmlns='jabber:x:data' /></message>", self.handler_message_xform, name='Old Message Form')
-
- def post_init(self):
- base.base_plugin.post_init(self)
- self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
- log.warning("This implementation of XEP-0004 is deprecated.")
-
- def handler_message_xform(self, xml):
- object = self.handle_form(xml)
- self.xmpp.event("message_form", object)
-
- def handler_presence_xform(self, xml):
- object = self.handle_form(xml)
- self.xmpp.event("presence_form", object)
-
- def handle_form(self, xml):
- xmlform = xml.find('{jabber:x:data}x')
- object = self.buildForm(xmlform)
- self.xmpp.event("message_xform", object)
- return object
-
- def buildForm(self, xml):
- form = Form(ftype=xml.attrib['type'])
- form.fromXML(xml)
- return form
-
- def makeForm(self, ftype='form', title='', instructions=''):
- return Form(self.xmpp, ftype, title, instructions)
-
-class FieldContainer(object):
- def __init__(self, stanza = 'form'):
- self.fields = []
- self.field = {}
- self.stanza = stanza
-
- def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None):
- self.field[var] = FormField(var, ftype, label, desc, required, value)
- self.fields.append(self.field[var])
- return self.field[var]
-
- def buildField(self, xml):
- self.field[xml.get('var', '__unnamed__')] = FormField(xml.get('var', '__unnamed__'), xml.get('type', 'text-single'))
- self.fields.append(self.field[xml.get('var', '__unnamed__')])
- self.field[xml.get('var', '__unnamed__')].buildField(xml)
-
- def buildContainer(self, xml):
- self.stanza = xml.tag
- for field in xml.findall('{jabber:x:data}field'):
- self.buildField(field)
-
- def getXML(self, ftype):
- container = ET.Element(self.stanza)
- for field in self.fields:
- container.append(field.getXML(ftype))
- return container
-
-class Form(FieldContainer):
- types = ('form', 'submit', 'cancel', 'result')
- def __init__(self, xmpp=None, ftype='form', title='', instructions=''):
- if not ftype in self.types:
- raise ValueError("Invalid Form Type")
- FieldContainer.__init__(self)
- self.xmpp = xmpp
- self.type = ftype
- self.title = title
- self.instructions = instructions
- self.reported = []
- self.items = []
-
- def merge(self, form2):
- form1 = Form(ftype=self.type)
- form1.fromXML(self.getXML(self.type))
- for field in form2.fields:
- if not field.var in form1.field:
- form1.addField(field.var, field.type, field.label, field.desc, field.required, field.value)
- else:
- form1.field[field.var].value = field.value
- for option, label in field.options:
- if (option, label) not in form1.field[field.var].options:
- form1.fields[field.var].addOption(option, label)
- return form1
-
- def copy(self):
- newform = Form(ftype=self.type)
- newform.fromXML(self.getXML(self.type))
- return newform
-
- def update(self, form):
- values = form.getValues()
- for var in values:
- if var in self.fields:
- self.fields[var].setValue(self.fields[var])
-
- def getValues(self):
- result = {}
- for field in self.fields:
- value = field.value
- if len(value) == 1:
- value = value[0]
- result[field.var] = value
- return result
-
- def setValues(self, values={}):
- for field in values:
- if field in self.field:
- if isinstance(values[field], list) or isinstance(values[field], tuple):
- for value in values[field]:
- self.field[field].setValue(value)
- else:
- self.field[field].setValue(values[field])
-
- def fromXML(self, xml):
- self.buildForm(xml)
-
- def addItem(self):
- newitem = FieldContainer('item')
- self.items.append(newitem)
- return newitem
-
- def buildItem(self, xml):
- newitem = self.addItem()
- newitem.buildContainer(xml)
-
- def addReported(self):
- reported = FieldContainer('reported')
- self.reported.append(reported)
- return reported
-
- def buildReported(self, xml):
- reported = self.addReported()
- reported.buildContainer(xml)
-
- def setTitle(self, title):
- self.title = title
-
- def setInstructions(self, instructions):
- self.instructions = instructions
-
- def setType(self, ftype):
- self.type = ftype
-
- def getXMLMessage(self, to):
- msg = self.xmpp.makeMessage(to)
- msg.append(self.getXML())
- return msg
-
- def buildForm(self, xml):
- self.type = xml.get('type', 'form')
- if xml.find('{jabber:x:data}title') is not None:
- self.setTitle(xml.find('{jabber:x:data}title').text)
- if xml.find('{jabber:x:data}instructions') is not None:
- self.setInstructions(xml.find('{jabber:x:data}instructions').text)
- for field in xml.findall('{jabber:x:data}field'):
- self.buildField(field)
- for reported in xml.findall('{jabber:x:data}reported'):
- self.buildReported(reported)
- for item in xml.findall('{jabber:x:data}item'):
- self.buildItem(item)
-
- #def getXML(self, tostring = False):
- def getXML(self, ftype=None):
- if ftype:
- self.type = ftype
- form = ET.Element('{jabber:x:data}x')
- form.attrib['type'] = self.type
- if self.title and self.type in ('form', 'result'):
- title = ET.Element('{jabber:x:data}title')
- title.text = self.title
- form.append(title)
- if self.instructions and self.type == 'form':
- instructions = ET.Element('{jabber:x:data}instructions')
- instructions.text = self.instructions
- form.append(instructions)
- for field in self.fields:
- form.append(field.getXML(self.type))
- for reported in self.reported:
- form.append(reported.getXML('{jabber:x:data}reported'))
- for item in self.items:
- form.append(item.getXML(self.type))
- #if tostring:
- # form = self.xmpp.tostring(form)
- return form
-
- def getXHTML(self):
- form = ET.Element('{http://www.w3.org/1999/xhtml}form')
- if self.title:
- title = ET.Element('h2')
- title.text = self.title
- form.append(title)
- if self.instructions:
- instructions = ET.Element('p')
- instructions.text = self.instructions
- form.append(instructions)
- for field in self.fields:
- form.append(field.getXHTML())
- for field in self.reported:
- form.append(field.getXHTML())
- for field in self.items:
- form.append(field.getXHTML())
- return form
-
-
- def makeSubmit(self):
- self.setType('submit')
-
-class FormField(object):
- types = ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private', 'text-single')
- listtypes = ('jid-multi', 'jid-single', 'list-multi', 'list-single')
- lbtypes = ('fixed', 'text-multi')
- def __init__(self, var, ftype='text-single', label='', desc='', required=False, value=None):
- if not ftype in self.types:
- raise ValueError("Invalid Field Type")
- self.type = ftype
- self.var = var
- self.label = label
- self.desc = desc
- self.options = []
- self.required = False
- self.value = []
- if self.type in self.listtypes:
- self.islist = True
- else:
- self.islist = False
- if self.type in self.lbtypes:
- self.islinebreak = True
- else:
- self.islinebreak = False
- if value:
- self.setValue(value)
-
- def addOption(self, value, label):
- if self.islist:
- self.options.append((value, label))
- else:
- raise ValueError("Cannot add options to non-list type field.")
-
- def setTrue(self):
- if self.type == 'boolean':
- self.value = [True]
-
- def setFalse(self):
- if self.type == 'boolean':
- self.value = [False]
-
- def require(self):
- self.required = True
-
- def setDescription(self, desc):
- self.desc = desc
-
- def setValue(self, value):
- if self.type == 'boolean':
- if value in ('1', 1, True, 'true', 'True', 'yes'):
- value = True
- else:
- value = False
- if self.islinebreak and value is not None:
- self.value += value.split('\n')
- else:
- if len(self.value) and (not self.islist or self.type == 'list-single'):
- self.value = [value]
- else:
- self.value.append(value)
-
- def delValue(self, value):
- if type(self.value) == type([]):
- try:
- idx = self.value.index(value)
- if idx != -1:
- self.value.pop(idx)
- except ValueError:
- pass
- else:
- self.value = ''
-
- def setAnswer(self, value):
- self.setValue(value)
-
- def buildField(self, xml):
- self.type = xml.get('type', 'text-single')
- self.label = xml.get('label', '')
- for option in xml.findall('{jabber:x:data}option'):
- self.addOption(option.find('{jabber:x:data}value').text, option.get('label', ''))
- for value in xml.findall('{jabber:x:data}value'):
- self.setValue(value.text)
- if xml.find('{jabber:x:data}required') is not None:
- self.require()
- if xml.find('{jabber:x:data}desc') is not None:
- self.setDescription(xml.find('{jabber:x:data}desc').text)
-
- def getXML(self, ftype):
- field = ET.Element('{jabber:x:data}field')
- if ftype != 'result':
- field.attrib['type'] = self.type
- if self.type != 'fixed':
- if self.var:
- field.attrib['var'] = self.var
- if self.label:
- field.attrib['label'] = self.label
- if ftype == 'form':
- for option in self.options:
- optionxml = ET.Element('{jabber:x:data}option')
- optionxml.attrib['label'] = option[1]
- optionval = ET.Element('{jabber:x:data}value')
- optionval.text = option[0]
- optionxml.append(optionval)
- field.append(optionxml)
- if self.required:
- required = ET.Element('{jabber:x:data}required')
- field.append(required)
- if self.desc:
- desc = ET.Element('{jabber:x:data}desc')
- desc.text = self.desc
- field.append(desc)
- for value in self.value:
- valuexml = ET.Element('{jabber:x:data}value')
- if value is True or value is False:
- if value:
- valuexml.text = '1'
- else:
- valuexml.text = '0'
- else:
- valuexml.text = value
- field.append(valuexml)
- return field
-
- def getXHTML(self):
- field = ET.Element('div', {'class': 'xmpp-xforms-%s' % self.type})
- if self.label:
- label = ET.Element('p')
- label.text = "%s: " % self.label
- else:
- label = ET.Element('p')
- label.text = "%s: " % self.var
- field.append(label)
- if self.type == 'boolean':
- formf = ET.Element('input', {'type': 'checkbox', 'name': self.var})
- if len(self.value) and self.value[0] in (True, 'true', '1'):
- formf.attrib['checked'] = 'checked'
- elif self.type == 'fixed':
- formf = ET.Element('p')
- try:
- formf.text = ', '.join(self.value)
- except:
- pass
- field.append(formf)
- formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
- try:
- formf.text = ', '.join(self.value)
- except:
- pass
- elif self.type == 'hidden':
- formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
- try:
- formf.text = ', '.join(self.value)
- except:
- pass
- elif self.type in ('jid-multi', 'list-multi'):
- formf = ET.Element('select', {'name': self.var})
- for option in self.options:
- optf = ET.Element('option', {'value': option[0], 'multiple': 'multiple'})
- optf.text = option[1]
- if option[1] in self.value:
- optf.attrib['selected'] = 'selected'
- formf.append(option)
- elif self.type in ('jid-single', 'text-single'):
- formf = ET.Element('input', {'type': 'text', 'name': self.var})
- try:
- formf.attrib['value'] = ', '.join(self.value)
- except:
- pass
- elif self.type == 'list-single':
- formf = ET.Element('select', {'name': self.var})
- for option in self.options:
- optf = ET.Element('option', {'value': option[0]})
- optf.text = option[1]
- if not optf.text:
- optf.text = option[0]
- if option[1] in self.value:
- optf.attrib['selected'] = 'selected'
- formf.append(optf)
- elif self.type == 'text-multi':
- formf = ET.Element('textarea', {'name': self.var})
- try:
- formf.text = ', '.join(self.value)
- except:
- pass
- if not formf.text:
- formf.text = ' '
- elif self.type == 'text-private':
- formf = ET.Element('input', {'type': 'password', 'name': self.var})
- try:
- formf.attrib['value'] = ', '.join(self.value)
- except:
- pass
- label.append(formf)
- return field
-
diff --git a/sleekxmpp/plugins/old_0009.py b/sleekxmpp/plugins/old_0009.py
deleted file mode 100644
index 625b03fb..00000000
--- a/sleekxmpp/plugins/old_0009.py
+++ /dev/null
@@ -1,277 +0,0 @@
-"""
-XEP-0009 XMPP Remote Procedure Calls
-"""
-from __future__ import with_statement
-from . import base
-import logging
-from xml.etree import cElementTree as ET
-import copy
-import time
-import base64
-
-def py2xml(*args):
- params = ET.Element("params")
- for x in args:
- param = ET.Element("param")
- param.append(_py2xml(x))
- params.append(param) #<params><param>...
- return params
-
-def _py2xml(*args):
- for x in args:
- val = ET.Element("value")
- if type(x) is int:
- i4 = ET.Element("i4")
- i4.text = str(x)
- val.append(i4)
- if type(x) is bool:
- boolean = ET.Element("boolean")
- boolean.text = str(int(x))
- val.append(boolean)
- elif type(x) is str:
- string = ET.Element("string")
- string.text = x
- val.append(string)
- elif type(x) is float:
- double = ET.Element("double")
- double.text = str(x)
- val.append(double)
- elif type(x) is rpcbase64:
- b64 = ET.Element("Base64")
- b64.text = x.encoded()
- val.append(b64)
- elif type(x) is rpctime:
- iso = ET.Element("dateTime.iso8601")
- iso.text = str(x)
- val.append(iso)
- elif type(x) is list:
- array = ET.Element("array")
- data = ET.Element("data")
- for y in x:
- data.append(_py2xml(y))
- array.append(data)
- val.append(array)
- elif type(x) is dict:
- struct = ET.Element("struct")
- for y in x.keys():
- member = ET.Element("member")
- name = ET.Element("name")
- name.text = y
- member.append(name)
- member.append(_py2xml(x[y]))
- struct.append(member)
- val.append(struct)
- return val
-
-def xml2py(params):
- vals = []
- for param in params.findall('param'):
- vals.append(_xml2py(param.find('value')))
- return vals
-
-def _xml2py(value):
- if value.find('i4') is not None:
- return int(value.find('i4').text)
- if value.find('int') is not None:
- return int(value.find('int').text)
- if value.find('boolean') is not None:
- return bool(value.find('boolean').text)
- if value.find('string') is not None:
- return value.find('string').text
- if value.find('double') is not None:
- return float(value.find('double').text)
- if value.find('Base64') is not None:
- return rpcbase64(value.find('Base64').text)
- if value.find('dateTime.iso8601') is not None:
- return rpctime(value.find('dateTime.iso8601'))
- if value.find('struct') is not None:
- struct = {}
- for member in value.find('struct').findall('member'):
- struct[member.find('name').text] = _xml2py(member.find('value'))
- return struct
- if value.find('array') is not None:
- array = []
- for val in value.find('array').find('data').findall('value'):
- array.append(_xml2py(val))
- return array
- raise ValueError()
-
-class rpcbase64(object):
- def __init__(self, data):
- #base 64 encoded string
- self.data = data
-
- def decode(self):
- return base64.decodestring(data)
-
- def __str__(self):
- return self.decode()
-
- def encoded(self):
- return self.data
-
-class rpctime(object):
- def __init__(self,data=None):
- #assume string data is in iso format YYYYMMDDTHH:MM:SS
- if type(data) is str:
- self.timestamp = time.strptime(data,"%Y%m%dT%H:%M:%S")
- elif type(data) is time.struct_time:
- self.timestamp = data
- elif data is None:
- self.timestamp = time.gmtime()
- else:
- raise ValueError()
-
- def iso8601(self):
- #return a iso8601 string
- return time.strftime("%Y%m%dT%H:%M:%S",self.timestamp)
-
- def __str__(self):
- return self.iso8601()
-
-class JabberRPCEntry(object):
- def __init__(self,call):
- self.call = call
- self.result = None
- self.error = None
- self.allow = {} #{'<jid>':['<resource1>',...],...}
- self.deny = {}
-
- def check_acl(self, jid, resource):
- #Check for deny
- if jid in self.deny.keys():
- if self.deny[jid] == None or resource in self.deny[jid]:
- return False
- #Check for allow
- if allow == None:
- return True
- if jid in self.allow.keys():
- if self.allow[jid] == None or resource in self.allow[jid]:
- return True
- return False
-
- def acl_allow(self, jid, resource):
- if jid == None:
- self.allow = None
- elif resource == None:
- self.allow[jid] = None
- elif jid in self.allow.keys():
- self.allow[jid].append(resource)
- else:
- self.allow[jid] = [resource]
-
- def acl_deny(self, jid, resource):
- if jid == None:
- self.deny = None
- elif resource == None:
- self.deny[jid] = None
- elif jid in self.deny.keys():
- self.deny[jid].append(resource)
- else:
- self.deny[jid] = [resource]
-
- def call_method(self, args):
- ret = self.call(*args)
-
-class xep_0009(base.base_plugin):
-
- def plugin_init(self):
- self.xep = '0009'
- self.description = 'Jabber-RPC'
- self.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>",
- self._callMethod, name='Jabber RPC Call')
- self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>",
- self._callResult, name='Jabber RPC Result')
- self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>",
- self._callError, name='Jabber RPC Error')
- self.entries = {}
- self.activeCalls = []
-
- def post_init(self):
- base.base_plugin.post_init(self)
- self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:rpc')
- self.xmpp.plugin['xep_0030'].add_identity('automatition','rpc')
-
- def register_call(self, method, name=None):
- #@returns an string that can be used in acl commands.
- with self.lock:
- if name is None:
- self.entries[method.__name__] = JabberRPCEntry(method)
- return method.__name__
- else:
- self.entries[name] = JabberRPCEntry(method)
- return name
-
- def acl_allow(self, entry, jid=None, resource=None):
- #allow the method entry to be called by the given jid and resource.
- #if jid is None it will allow any jid/resource.
- #if resource is None it will allow any resource belonging to the jid.
- with self.lock:
- if self.entries[entry]:
- self.entries[entry].acl_allow(jid,resource)
- else:
- raise ValueError()
-
- def acl_deny(self, entry, jid=None, resource=None):
- #Note: by default all requests are denied unless allowed with acl_allow.
- #If you deny an entry it will not be allowed regardless of acl_allow
- with self.lock:
- if self.entries[entry]:
- self.entries[entry].acl_deny(jid,resource)
- else:
- raise ValueError()
-
- def unregister_call(self, entry):
- #removes the registered call
- with self.lock:
- if self.entries[entry]:
- del self.entries[entry]
- else:
- raise ValueError()
-
- def makeMethodCallQuery(self,pmethod,params):
- query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
- methodCall = ET.Element('methodCall')
- methodName = ET.Element('methodName')
- methodName.text = pmethod
- methodCall.append(methodName)
- methodCall.append(params)
- query.append(methodCall)
- return query
-
- def makeIqMethodCall(self,pto,pmethod,params):
- iq = self.xmpp.makeIqSet()
- iq.set('to',pto)
- iq.append(self.makeMethodCallQuery(pmethod,params))
- return iq
-
- def makeIqMethodResponse(self,pto,pid,params):
- iq = self.xmpp.makeIqResult(pid)
- iq.set('to',pto)
- query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
- methodResponse = ET.Element('methodResponse')
- methodResponse.append(params)
- query.append(methodResponse)
- return iq
-
- def makeIqMethodError(self,pto,id,pmethod,params,condition):
- iq = self.xmpp.makeIqError(id)
- iq.set('to',pto)
- iq.append(self.makeMethodCallQuery(pmethod,params))
- iq.append(self.xmpp['xep_0086'].makeError(condition))
- return iq
-
-
-
- def call_remote(self, pto, pmethod, *args):
- #calls a remote method. Returns the id of the Iq.
- pass
-
- def _callMethod(self,xml):
- pass
-
- def _callResult(self,xml):
- pass
-
- def _callError(self,xml):
- pass
diff --git a/sleekxmpp/plugins/xep_0004/stanza/field.py b/sleekxmpp/plugins/xep_0004/stanza/field.py
index 1e175966..51f85995 100644
--- a/sleekxmpp/plugins/xep_0004/stanza/field.py
+++ b/sleekxmpp/plugins/xep_0004/stanza/field.py
@@ -41,10 +41,11 @@ class FormField(ElementBase):
self._type = value
def add_option(self, label='', value=''):
- if self._type in self.option_types:
- opt = FieldOption(parent=self)
+ if self._type is None or self._type in self.option_types:
+ opt = FieldOption()
opt['label'] = label
opt['value'] = value
+ self.append(opt)
else:
raise ValueError("Cannot add options to " + \
"a %s field." % self['type'])
diff --git a/sleekxmpp/plugins/xep_0004/stanza/form.py b/sleekxmpp/plugins/xep_0004/stanza/form.py
index 721ecc35..bbd8540f 100644
--- a/sleekxmpp/plugins/xep_0004/stanza/form.py
+++ b/sleekxmpp/plugins/xep_0004/stanza/form.py
@@ -65,7 +65,7 @@ class Form(ElementBase):
if kwtype is None:
kwtype = ftype
- field = FormField(parent=self)
+ field = FormField()
field['var'] = var
field['type'] = kwtype
field['value'] = value
@@ -77,6 +77,7 @@ class Form(ElementBase):
field['options'] = options
else:
del field['type']
+ self.append(field)
return field
def getXML(self, type='submit'):
@@ -144,10 +145,9 @@ class Form(ElementBase):
def get_fields(self, use_dict=False):
fields = OrderedDict()
- fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
- for fieldXML in fieldsXML:
- field = FormField(xml=fieldXML)
- fields[field['var']] = field
+ for stanza in self['substanzas']:
+ if isinstance(stanza, FormField):
+ fields[stanza['var']] = stanza
return fields
def get_instructions(self):
@@ -221,6 +221,8 @@ class Form(ElementBase):
def set_values(self, values):
fields = self['fields']
for field in values:
+ if field not in fields:
+ fields[field] = self.add_field(var=field)
fields[field]['value'] = values[field]
def merge(self, other):
diff --git a/sleekxmpp/plugins/gmail/__init__.py b/sleekxmpp/plugins/xep_0020/__init__.py
index a87c78bb..c6aafe97 100644
--- a/sleekxmpp/plugins/gmail/__init__.py
+++ b/sleekxmpp/plugins/xep_0020/__init__.py
@@ -8,8 +8,9 @@
from sleekxmpp.plugins.base import register_plugin
-from sleekxmpp.plugins.gmail import stanza
-from sleekxmpp.plugins.gmail.notifications import Gmail
+from sleekxmpp.plugins.xep_0020 import stanza
+from sleekxmpp.plugins.xep_0020.stanza import FeatureNegotiation
+from sleekxmpp.plugins.xep_0020.feature_negotiation import XEP_0020
-register_plugin(Gmail)
+register_plugin(XEP_0020)
diff --git a/sleekxmpp/plugins/xep_0020/feature_negotiation.py b/sleekxmpp/plugins/xep_0020/feature_negotiation.py
new file mode 100644
index 00000000..7cb82cd5
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0020/feature_negotiation.py
@@ -0,0 +1,36 @@
+"""
+ 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 import Iq, Message
+from sleekxmpp.plugins import BasePlugin
+from sleekxmpp.xmlstream.handler import Callback
+from sleekxmpp.xmlstream.matcher import StanzaPath
+from sleekxmpp.xmlstream import register_stanza_plugin, JID
+from sleekxmpp.plugins.xep_0020 import stanza, FeatureNegotiation
+from sleekxmpp.plugins.xep_0004 import Form
+
+
+log = logging.getLogger(__name__)
+
+
+class XEP_0020(BasePlugin):
+
+ name = 'xep_0020'
+ description = 'XEP-0020: Feature Negotiation'
+ dependencies = set(['xep_0004', 'xep_0030'])
+ stanza = stanza
+
+ def plugin_init(self):
+ self.xmpp['xep_0030'].add_feature(FeatureNegotiation.namespace)
+
+ register_stanza_plugin(FeatureNegotiation, Form)
+
+ register_stanza_plugin(Iq, FeatureNegotiation)
+ register_stanza_plugin(Message, FeatureNegotiation)
diff --git a/sleekxmpp/plugins/xep_0020/stanza.py b/sleekxmpp/plugins/xep_0020/stanza.py
new file mode 100644
index 00000000..13e4056e
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0020/stanza.py
@@ -0,0 +1,17 @@
+"""
+ 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
+
+
+class FeatureNegotiation(ElementBase):
+
+ name = 'feature'
+ namespace = 'http://jabber.org/protocol/feature-neg'
+ plugin_attrib = 'feature_neg'
+ interfaces = set()
diff --git a/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py
index e074656b..1d5f3c83 100644
--- a/sleekxmpp/plugins/xep_0045.py
+++ b/sleekxmpp/plugins/xep_0045.py
@@ -198,30 +198,9 @@ class XEP_0045(BasePlugin):
if entry is not None and entry['jid'].full == jid:
return nick
- def getRoomForm(self, room, ifrom=None):
- iq = self.xmpp.makeIqGet()
- iq['to'] = room
- if ifrom is not None:
- iq['from'] = ifrom
- query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
- iq.append(query)
- # For now, swallow errors to preserve existing API
- try:
- result = iq.send()
- except IqError:
- return False
- except IqTimeout:
- return False
- xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
- if xform is None: return False
- form = self.xmpp.plugin['old_0004'].buildForm(xform)
- return form
-
def configureRoom(self, room, form=None, ifrom=None):
if form is None:
- form = self.getRoomForm(room, ifrom=ifrom)
- #form = self.xmpp.plugin['old_0004'].makeForm(ftype='submit')
- #form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig')
+ form = self.getRoomConfig(room, ifrom=ifrom)
iq = self.xmpp.makeIqSet()
iq['to'] = room
if ifrom is not None:
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_0060/pubsub.py b/sleekxmpp/plugins/xep_0060/pubsub.py
index 952cad85..bec5f565 100644
--- a/sleekxmpp/plugins/xep_0060/pubsub.py
+++ b/sleekxmpp/plugins/xep_0060/pubsub.py
@@ -423,7 +423,7 @@ class XEP_0060(BasePlugin):
callback=None, timeout=None):
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
iq['pubsub_owner']['configure']['node'] = node
- iq['pubsub_owner']['configure']['form'].values = config.values
+ iq['pubsub_owner']['configure'].append(config)
return iq.send(block=block, callback=callback, timeout=timeout)
def publish(self, jid, node, id=None, payload=None, options=None,
diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub.py
index b2fe3010..c1907a13 100644
--- a/sleekxmpp/plugins/xep_0060/stanza/pubsub.py
+++ b/sleekxmpp/plugins/xep_0060/stanza/pubsub.py
@@ -74,7 +74,12 @@ class Item(ElementBase):
def set_payload(self, value):
del self['payload']
- self.append(value)
+ if isinstance(value, ElementBase):
+ if value.tag_name() in self.plugin_tag_map:
+ self.init_plugin(value.plugin_attrib, existing_xml=value.xml)
+ self.xml.append(value.xml)
+ else:
+ self.xml.append(value)
def get_payload(self):
childs = list(self.xml)
@@ -243,39 +248,6 @@ class PublishOptions(ElementBase):
self.parent().xml.remove(self.xml)
-class PubsubState(ElementBase):
- """This is an experimental pubsub extension."""
- namespace = 'http://jabber.org/protocol/psstate'
- name = 'state'
- plugin_attrib = 'psstate'
- interfaces = set(('node', 'item', 'payload'))
-
- def set_payload(self, value):
- self.xml.append(value)
-
- def get_payload(self):
- childs = list(self.xml)
- if len(childs) > 0:
- return childs[0]
-
- def del_payload(self):
- for child in self.xml:
- self.xml.remove(child)
-
-
-class PubsubStateEvent(ElementBase):
- """This is an experimental pubsub extension."""
- namespace = 'http://jabber.org/protocol/psstate#event'
- name = 'event'
- plugin_attrib = 'psstate_event'
- intefaces = set(tuple())
-
-
-register_stanza_plugin(Iq, PubsubState)
-register_stanza_plugin(Message, PubsubStateEvent)
-register_stanza_plugin(PubsubStateEvent, PubsubState)
-
-
register_stanza_plugin(Iq, Pubsub)
register_stanza_plugin(Pubsub, Affiliations)
register_stanza_plugin(Pubsub, Configure)
diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py
index 4a35db9d..c10ac762 100644
--- a/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py
+++ b/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py
@@ -34,7 +34,8 @@ class DefaultConfig(ElementBase):
return self['form']
def set_config(self, value):
- self['form'].values = value.values
+ del self['from']
+ self.append(value)
return self
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_0077/register.py b/sleekxmpp/plugins/xep_0077/register.py
index d4da21a5..ee07548b 100644
--- a/sleekxmpp/plugins/xep_0077/register.py
+++ b/sleekxmpp/plugins/xep_0077/register.py
@@ -7,6 +7,7 @@
"""
import logging
+import ssl
from sleekxmpp.stanza import StreamFeatures, Iq
from sleekxmpp.xmlstream import register_stanza_plugin, JID
@@ -29,6 +30,7 @@ class XEP_0077(BasePlugin):
stanza = stanza
default_config = {
'create_account': True,
+ 'force_registration': False,
'order': 50
}
@@ -45,10 +47,29 @@ class XEP_0077(BasePlugin):
register_stanza_plugin(Register, self.xmpp['xep_0004'].stanza.Form)
register_stanza_plugin(Register, self.xmpp['xep_0066'].stanza.OOB)
+ self.xmpp.add_event_handler('connected', self._force_registration)
+
def plugin_end(self):
if not self.xmpp.is_component:
self.xmpp.unregister_feature('register', self.order)
+ def _force_registration(self, event):
+ if self.force_registration:
+ self.xmpp.add_filter('in', self._force_stream_feature)
+
+ def _force_stream_feature(self, stanza):
+ if isinstance(stanza, StreamFeatures):
+ if self.xmpp.use_tls or self.xmpp.use_ssl:
+ if 'starttls' not in self.xmpp.features:
+ return stanza
+ elif not isinstance(self.xmpp.socket, ssl.SSLSocket):
+ return stanza
+ if 'mechanisms' not in self.xmpp.features:
+ log.debug('Forced adding in-band registration stream feature')
+ stanza.enable('register')
+ self.xmpp.del_filter('in', self._force_stream_feature)
+ return stanza
+
def _handle_register_feature(self, features):
if 'mechanisms' in self.xmpp.features:
# We have already logged in with an account
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)
diff --git a/sleekxmpp/stanza/htmlim.py b/sleekxmpp/stanza/htmlim.py
index d21a74e1..c43178f2 100644
--- a/sleekxmpp/stanza/htmlim.py
+++ b/sleekxmpp/stanza/htmlim.py
@@ -7,78 +7,13 @@
"""
from sleekxmpp.stanza import Message
-from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
-
-
-class HTMLIM(ElementBase):
-
- """
- XEP-0071: XHTML-IM defines a method for embedding XHTML content
- within a <message> stanza so that lightweight markup can be used
- to format the message contents and to create links.
-
- Only a subset of XHTML is recommended for use with XHTML-IM.
- See the full spec at 'http://xmpp.org/extensions/xep-0071.html'
- for more information.
-
- Example stanza:
- <message to="user@example.com">
- <body>Non-html message content.</body>
- <html xmlns="http://jabber.org/protocol/xhtml-im">
- <body xmlns="http://www.w3.org/1999/xhtml">
- <p><b>HTML!</b></p>
- </body>
- </html>
- </message>
-
- Stanza Interface:
- body -- The contents of the HTML body tag.
-
- Methods:
- setup -- Overrides ElementBase.setup.
- get_body -- Return the HTML body contents.
- set_body -- Set the HTML body contents.
- del_body -- Remove the HTML body contents.
- """
-
- namespace = 'http://jabber.org/protocol/xhtml-im'
- name = 'html'
- interfaces = set(('body',))
- plugin_attrib = name
-
- def set_body(self, html):
- """
- Set the contents of the HTML body.
-
- Arguments:
- html -- Either a string or XML object. If the top level
- element is not <body> with a namespace of
- 'http://www.w3.org/1999/xhtml', it will be wrapped.
- """
- if isinstance(html, str):
- html = ET.XML(html)
- if html.tag != '{http://www.w3.org/1999/xhtml}body':
- body = ET.Element('{http://www.w3.org/1999/xhtml}body')
- body.append(html)
- self.xml.append(body)
- else:
- self.xml.append(html)
-
- def get_body(self):
- """Return the contents of the HTML body."""
- html = self.xml.find('{http://www.w3.org/1999/xhtml}body')
- if html is None:
- return ''
- return html
-
- def del_body(self):
- """Remove the HTML body contents."""
- if self.parent is not None:
- self.parent().xml.remove(self.xml)
+from sleekxmpp.xmlstream import register_stanza_plugin
+from sleekxmpp.plugins.xep_0071 import XHTML_IM as HTMLIM
register_stanza_plugin(Message, HTMLIM)
+
# To comply with PEP8, method names now use underscores.
# Deprecated method names are re-mapped for backwards compatibility.
HTMLIM.setBody = HTMLIM.set_body
diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py
index 71c0444d..ba945e08 100644
--- a/sleekxmpp/stanza/iq.py
+++ b/sleekxmpp/stanza/iq.py
@@ -115,9 +115,13 @@ class Iq(RootStanza):
"""
query = self.xml.find("{%s}query" % value)
if query is None and value:
- self.clear()
- query = ET.Element("{%s}query" % value)
- self.xml.append(query)
+ plugin = self.plugin_tag_map.get('{%s}query' % value, None)
+ if plugin:
+ self.enable(plugin.plugin_attrib)
+ else:
+ self.clear()
+ query = ET.Element("{%s}query" % value)
+ self.xml.append(query)
return self
def get_query(self):
@@ -182,8 +186,8 @@ class Iq(RootStanza):
the stanza immediately. Used during stream
initialization. Defaults to False.
timeout_callback -- Optional reference to a stream handler function.
- Will be executed when the timeout expires before a
- response has been received with the originally-sent IQ
+ Will be executed when the timeout expires before a
+ response has been received with the originally-sent IQ
stanza. Only called if there is a callback parameter
(and therefore are in async mode).
"""
@@ -194,10 +198,10 @@ class Iq(RootStanza):
if timeout_callback:
self.callback = callback
self.timeout_callback = timeout_callback
- self.stream.schedule('IqTimeout_%s' % self['id'],
- timeout,
- self._fire_timeout,
- repeat=False)
+ self.stream.schedule('IqTimeout_%s' % self['id'],
+ timeout,
+ self._fire_timeout,
+ repeat=False)
handler = Callback(handler_name,
MatcherId(self['id']),
self._handle_result,
diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py
index 120373db..97107098 100644
--- a/sleekxmpp/xmlstream/stanzabase.py
+++ b/sleekxmpp/xmlstream/stanzabase.py
@@ -3,7 +3,7 @@
sleekxmpp.xmlstream.stanzabase
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- This module implements a wrapper layer for XML objects
+ module implements a wrapper layer for XML objects
that allows them to be treated like dictionaries.
Part of SleekXMPP: The Sleek XMPP Library
@@ -141,7 +141,7 @@ def multifactory(stanza, plugin_attrib):
parent.loaded_plugins.remove(plugin_attrib)
try:
parent.xml.remove(self.xml)
- except:
+ except ValueError:
pass
else:
for stanza in list(res):
@@ -596,31 +596,39 @@ class ElementBase(object):
iterable_interfaces = [p.plugin_attrib for \
p in self.plugin_iterables]
+ if 'lang' in values:
+ self['lang'] = values['lang']
+
+ if 'substanzas' in values:
+ # Remove existing substanzas
+ for stanza in self.iterables:
+ try:
+ self.xml.remove(stanza.xml)
+ except ValueError:
+ pass
+ self.iterables = []
+
+ # Add new substanzas
+ for subdict in values['substanzas']:
+ if '__childtag__' in subdict:
+ for subclass in self.plugin_iterables:
+ child_tag = "{%s}%s" % (subclass.namespace,
+ subclass.name)
+ if subdict['__childtag__'] == child_tag:
+ sub = subclass(parent=self)
+ sub.values = subdict
+ self.iterables.append(sub)
+
for interface, value in values.items():
full_interface = interface
interface_lang = ('%s|' % interface).split('|')
interface = interface_lang[0]
lang = interface_lang[1] or self.get_lang()
- if interface == 'substanzas':
- # Remove existing substanzas
- for stanza in self.iterables:
- self.xml.remove(stanza.xml)
- self.iterables = []
-
- # Add new substanzas
- for subdict in value:
- if '__childtag__' in subdict:
- for subclass in self.plugin_iterables:
- child_tag = "{%s}%s" % (subclass.namespace,
- subclass.name)
- if subdict['__childtag__'] == child_tag:
- sub = subclass(parent=self)
- sub.values = subdict
- self.iterables.append(sub)
- break
- elif interface == 'lang':
- self[interface] = value
+ if interface == 'lang':
+ continue
+ elif interface == 'substanzas':
+ continue
elif interface in self.interfaces:
self[full_interface] = value
elif interface in self.plugin_attrib_map:
@@ -866,7 +874,7 @@ class ElementBase(object):
self.loaded_plugins.remove(attrib)
try:
self.xml.remove(plugin.xml)
- except:
+ except ValueError:
pass
return self
diff --git a/sleekxmpp/xmlstream/tostring.py b/sleekxmpp/xmlstream/tostring.py
index 08d7ad02..c49abd3e 100644
--- a/sleekxmpp/xmlstream/tostring.py
+++ b/sleekxmpp/xmlstream/tostring.py
@@ -24,8 +24,8 @@ if sys.version_info < (3, 0):
XML_NS = 'http://www.w3.org/XML/1998/namespace'
-def tostring(xml=None, xmlns='', stream=None,
- outbuffer='', top_level=False, open_only=False):
+def tostring(xml=None, xmlns='', stream=None, outbuffer='',
+ top_level=False, open_only=False, namespaces=None):
"""Serialize an XML object to a Unicode string.
If an outer xmlns is provided using ``xmlns``, then the current element's
@@ -41,7 +41,8 @@ def tostring(xml=None, xmlns='', stream=None,
during recursive calls.
:param bool top_level: Indicates that the element is the outermost
element.
-
+ :param set namespaces: Track which namespaces are in active use so
+ that new ones can be declared when needed.
:type xml: :py:class:`~xml.etree.ElementTree.Element`
:type stream: :class:`~sleekxmpp.xmlstream.xmlstream.XMLStream`
@@ -63,6 +64,7 @@ def tostring(xml=None, xmlns='', stream=None,
default_ns = ''
stream_ns = ''
use_cdata = False
+
if stream:
default_ns = stream.default_ns
stream_ns = stream.stream_ns
@@ -82,6 +84,7 @@ def tostring(xml=None, xmlns='', stream=None,
output.append(namespace)
# Output escaped attribute values.
+ new_namespaces = set()
for attrib, value in xml.attrib.items():
value = escape(value, use_cdata)
if '}' not in attrib:
@@ -89,14 +92,20 @@ def tostring(xml=None, xmlns='', stream=None,
else:
attrib_ns = attrib.split('}')[0][1:]
attrib = attrib.split('}')[1]
- if stream and attrib_ns in stream.namespace_map:
+ if attrib_ns == XML_NS:
+ output.append(' xml:%s="%s"' % (attrib, value))
+ elif stream and attrib_ns in stream.namespace_map:
mapped_ns = stream.namespace_map[attrib_ns]
if mapped_ns:
- output.append(' %s:%s="%s"' % (mapped_ns,
- attrib,
- value))
- elif attrib_ns == XML_NS:
- output.append(' xml:%s="%s"' % (attrib, value))
+ if namespaces is None:
+ namespaces = set()
+ if attrib_ns not in namespaces:
+ namespaces.add(attrib_ns)
+ new_namespaces.add(attrib_ns)
+ output.append(' xmlns:%s="%s"' % (
+ mapped_ns, attrib_ns))
+ output.append(' %s:%s="%s"' % (
+ mapped_ns, attrib, value))
if open_only:
# Only output the opening tag, regardless of content.
@@ -110,7 +119,8 @@ def tostring(xml=None, xmlns='', stream=None,
output.append(escape(xml.text, use_cdata))
if len(xml):
for child in xml:
- output.append(tostring(child, tag_xmlns, stream))
+ output.append(tostring(child, tag_xmlns, stream,
+ namespaces=namespaces))
output.append("</%s>" % tag_name)
elif xml.text:
# If we only have text content.
@@ -121,6 +131,11 @@ def tostring(xml=None, xmlns='', stream=None,
if xml.tail:
# If there is additional text after the element.
output.append(escape(xml.tail, use_cdata))
+ for ns in new_namespaces:
+ # Remove namespaces introduced in this context. This is necessary
+ # because the namespaces object continues to be shared with other
+ # contexts.
+ namespaces.remove(ns)
return ''.join(output)
diff --git a/tests/test_stanza_message.py b/tests/test_stanza_message.py
index e55971df..3ed965b6 100644
--- a/tests/test_stanza_message.py
+++ b/tests/test_stanza_message.py
@@ -30,9 +30,7 @@ class TestMessageStanzas(SleekTest):
msg['to'] = "fritzy@netflint.net/sleekxmpp"
msg['body'] = "this is the plaintext message"
msg['type'] = 'chat'
- p = ET.Element('{http://www.w3.org/1999/xhtml}p')
- p.text = "This is the htmlim message"
- msg['html']['body'] = p
+ msg['html']['body'] = '<p>This is the htmlim message</p>'
self.check(msg, """
<message to="fritzy@netflint.net/sleekxmpp" type="chat">
<body>this is the plaintext message</body>
diff --git a/tests/test_stanza_xep_0060.py b/tests/test_stanza_xep_0060.py
index 16a7cb37..3898d0ab 100644
--- a/tests/test_stanza_xep_0060.py
+++ b/tests/test_stanza_xep_0060.py
@@ -129,20 +129,6 @@ class TestPubsubStanzas(SleekTest):
</pubsub>
</iq>""")
- def testState(self):
- "Testing iq/psstate stanzas"
- iq = self.Iq()
- iq['psstate']['node']= 'mynode'
- iq['psstate']['item']= 'myitem'
- pl = ET.Element('{http://andyet.net/protocol/pubsubqueue}claimed')
- iq['psstate']['payload'] = pl
- self.check(iq, """
- <iq id="0">
- <state xmlns="http://jabber.org/protocol/psstate" node="mynode" item="myitem">
- <claimed xmlns="http://andyet.net/protocol/pubsubqueue" />
- </state>
- </iq>""")
-
def testDefault(self):
"Testing iq/pubsub_owner/default stanzas"
iq = self.Iq()
diff --git a/tox.ini b/tox.ini
index 8576f4eb..91617941 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py26,py27,py31,py32
+envlist = py26,py27,py31,py32,py33
[testenv]
deps = nose
commands = nosetests --where=tests --exclude=live -i sleektest.py