Merge branch 'master' into develop
This commit is contained in:
commit
bad405bea9
@ -62,16 +62,18 @@ class PingTest(sleekxmpp.ClientXMPP):
|
|||||||
"""
|
"""
|
||||||
self.send_presence()
|
self.send_presence()
|
||||||
self.get_roster()
|
self.get_roster()
|
||||||
result = self['xep_0199'].send_ping(self.pingjid,
|
|
||||||
timeout=10,
|
try:
|
||||||
errorfalse=True)
|
rtt = self['xep_0199'].ping(self.pingjid,
|
||||||
logging.info("Pinging...")
|
timeout=10)
|
||||||
if result is False:
|
logging.info("Success! RTT: %s", rtt)
|
||||||
logging.info("Couldn't ping.")
|
except IqError as e:
|
||||||
self.disconnect()
|
logging.info("Error pinging %s: %s",
|
||||||
sys.exit(1)
|
self.pingjid,
|
||||||
else:
|
e.iq['error']['condition'])
|
||||||
logging.info("Success! RTT: %s", str(result))
|
except IqTimeout:
|
||||||
|
logging.info("No response from %s", self.pingjid)
|
||||||
|
finally:
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ from sleekxmpp.plugins.base import register_plugin, load_plugin
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# Non-standard
|
# Non-standard
|
||||||
'gmail_notify', # Gmail searching and notifications
|
'gmail', # Gmail searching and notifications
|
||||||
|
|
||||||
# XEPS
|
# XEPS
|
||||||
'xep_0004', # Data Forms
|
'xep_0004', # Data Forms
|
||||||
|
15
sleekxmpp/plugins/gmail/__init__.py
Normal file
15
sleekxmpp/plugins/gmail/__init__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
"""
|
||||||
|
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.gmail import stanza
|
||||||
|
from sleekxmpp.plugins.gmail.notifications import Gmail
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(Gmail)
|
77
sleekxmpp/plugins/gmail/notifications.py
Normal file
77
sleekxmpp/plugins/gmail/notifications.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
"""
|
||||||
|
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.stanza import Iq
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Gmail(BasePlugin):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Google: Gmail Notifications
|
||||||
|
|
||||||
|
Also see <https://developers.google.com/talk/jep_extensions/gmail>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = 'gmail'
|
||||||
|
description = 'Google: Gmail Notifications'
|
||||||
|
dependencies = set()
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Iq, stanza.GmailQuery)
|
||||||
|
register_stanza_plugin(Iq, stanza.MailBox)
|
||||||
|
register_stanza_plugin(Iq, stanza.NewMail)
|
||||||
|
|
||||||
|
self.xmpp.register_handler(
|
||||||
|
Callback('Gmail New Mail',
|
||||||
|
MatchXPath('{%s}iq/{%s}%s' % (
|
||||||
|
self.xmpp.default_ns,
|
||||||
|
stanza.NewMail.namespace,
|
||||||
|
stanza.NewMail.name)),
|
||||||
|
self._handle_new_mail))
|
||||||
|
|
||||||
|
self._last_result_time = None
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.xmpp.remove_handler('Gmail New Mail')
|
||||||
|
|
||||||
|
def _handle_new_mail(self, iq):
|
||||||
|
log.info("Gmail: New email!")
|
||||||
|
iq.reply().send()
|
||||||
|
self.xmpp.event('gmail_notification')
|
||||||
|
|
||||||
|
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,
|
||||||
|
block=block,
|
||||||
|
timeout=timeout,
|
||||||
|
callback=callback)
|
||||||
|
|
||||||
|
def search(self, query=None, newer=None, block=True,
|
||||||
|
timeout=None, callback=None):
|
||||||
|
if not query:
|
||||||
|
log.info('Gmail: Checking for new email')
|
||||||
|
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']['search'] = query
|
||||||
|
iq['gmail']['newer_than_time'] = newer
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
101
sleekxmpp/plugins/gmail/stanza.py
Normal file
101
sleekxmpp/plugins/gmail/stanza.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
"""
|
||||||
|
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, register_stanza_plugin
|
||||||
|
|
||||||
|
|
||||||
|
class GmailQuery(ElementBase):
|
||||||
|
namespace = 'google:mail:notify'
|
||||||
|
name = 'query'
|
||||||
|
plugin_attrib = 'gmail'
|
||||||
|
interfaces = set(['newer_than_time', 'newer_than_tid', 'search'])
|
||||||
|
|
||||||
|
def get_search(self):
|
||||||
|
return self._get_attr('q', '')
|
||||||
|
|
||||||
|
def set_search(self, search):
|
||||||
|
self._set_attr('q', search)
|
||||||
|
|
||||||
|
def del_search(self):
|
||||||
|
self._del_attr('q')
|
||||||
|
|
||||||
|
def get_newer_than_time(self):
|
||||||
|
return self._get_attr('newer-than-time', '')
|
||||||
|
|
||||||
|
def set_newer_than_time(self, value):
|
||||||
|
self._set_attr('newer-than-time', value)
|
||||||
|
|
||||||
|
def del_newer_than_time(self):
|
||||||
|
self._del_attr('newer-than-time')
|
||||||
|
|
||||||
|
def get_newer_than_tid(self):
|
||||||
|
return self._get_attr('newer-than-tid', '')
|
||||||
|
|
||||||
|
def set_newer_than_tid(self, value):
|
||||||
|
self._set_attr('newer-than-tid', value)
|
||||||
|
|
||||||
|
def del_newer_than_tid(self):
|
||||||
|
self._del_attr('newer-than-tid')
|
||||||
|
|
||||||
|
|
||||||
|
class MailBox(ElementBase):
|
||||||
|
namespace = 'google:mail:notify'
|
||||||
|
name = 'mailbox'
|
||||||
|
plugin_attrib = 'gmail_messages'
|
||||||
|
interfaces = set(['result_time', 'url', 'matched', 'estimate'])
|
||||||
|
|
||||||
|
def get_matched(self):
|
||||||
|
return self._get_attr('total-matched', '')
|
||||||
|
|
||||||
|
def get_estimate(self):
|
||||||
|
return self._get_attr('total-estimate', '') == '1'
|
||||||
|
|
||||||
|
def get_result_time(self):
|
||||||
|
return self._get_attr('result-time', '')
|
||||||
|
|
||||||
|
|
||||||
|
class MailThread(ElementBase):
|
||||||
|
namespace = 'google:mail:notify'
|
||||||
|
name = 'mail-thread-info'
|
||||||
|
plugin_attrib = 'thread'
|
||||||
|
plugin_multi_attrib = 'threads'
|
||||||
|
interfaces = set(['tid', 'participation', 'messages', 'date',
|
||||||
|
'senders', 'url', 'labels', 'subject', 'snippet'])
|
||||||
|
sub_interfaces = set(['labels', 'subject', 'snippet'])
|
||||||
|
|
||||||
|
def get_senders(self):
|
||||||
|
result = []
|
||||||
|
senders = self.xml.findall('{%s}senders/{%s}sender' % (
|
||||||
|
self.namespace, self.namespace))
|
||||||
|
|
||||||
|
for sender in senders:
|
||||||
|
result.append(MailSender(xml=sender))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class MailSender(ElementBase):
|
||||||
|
namespace = 'google:mail:notify'
|
||||||
|
name = 'sender'
|
||||||
|
plugin_attrib = name
|
||||||
|
interfaces = set(['address', 'name', 'originator', 'unread'])
|
||||||
|
|
||||||
|
def get_originator(self):
|
||||||
|
return self.xml.attrib.get('originator', '0') == '1'
|
||||||
|
|
||||||
|
def get_unread(self):
|
||||||
|
return self.xml.attrib.get('unread', '0') == '1'
|
||||||
|
|
||||||
|
|
||||||
|
class NewMail(ElementBase):
|
||||||
|
namespace = 'google:mail:notify'
|
||||||
|
name = 'new-mail'
|
||||||
|
plugin_attrib = 'gmail_notification'
|
||||||
|
|
||||||
|
|
||||||
|
register_stanza_plugin(MailBox, MailThread, iterable=True)
|
@ -1,149 +0,0 @@
|
|||||||
"""
|
|
||||||
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()
|
|
15
sleekxmpp/plugins/google_nosave/__init__.py
Normal file
15
sleekxmpp/plugins/google_nosave/__init__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
"""
|
||||||
|
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)
|
83
sleekxmpp/plugins/google_nosave/nosave.py
Normal file
83
sleekxmpp/plugins/google_nosave/nosave.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
"""
|
||||||
|
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.stanza import Iq, Message
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class GoogleNoSave(BasePlugin):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Google: Off the Record Chats
|
||||||
|
|
||||||
|
NOTE: This is NOT an encryption method.
|
||||||
|
|
||||||
|
Also see <https://developers.google.com/talk/jep_extensions/otr>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = 'google_nosave'
|
||||||
|
description = 'Google: Off the Record Chats'
|
||||||
|
dependencies = set(['google_settings'])
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Message, stanza.NoSave)
|
||||||
|
register_stanza_plugin(Iq, stanza.NoSaveQuery)
|
||||||
|
|
||||||
|
self.xmpp.register_handler(
|
||||||
|
Callback('Google Nosave',
|
||||||
|
StanzaPath('iq@type=set/google_nosave'),
|
||||||
|
self._handle_nosave_change))
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.xmpp.remove_handler('Google Nosave')
|
||||||
|
|
||||||
|
def enable(self, jid=None, block=True, timeout=None, callback=None):
|
||||||
|
if jid is None:
|
||||||
|
self.xmpp['google_settings'].update({'archiving_enabled': False},
|
||||||
|
block=block, timeout=timeout, callback=callback)
|
||||||
|
else:
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['google_nosave']['item']['jid'] = jid
|
||||||
|
iq['google_nosave']['item']['value'] = True
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def disable(self, jid=None, block=True, timeout=None, callback=None):
|
||||||
|
if jid is None:
|
||||||
|
self.xmpp['google_settings'].update({'archiving_enabled': True},
|
||||||
|
block=block, timeout=timeout, callback=callback)
|
||||||
|
else:
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['google_nosave']['item']['jid'] = jid
|
||||||
|
iq['google_nosave']['item']['value'] = False
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def get(self, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'get'
|
||||||
|
iq.enable('google_nosave')
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def _handle_nosave_change(self, iq):
|
||||||
|
reply = self.xmpp.Iq()
|
||||||
|
reply['type'] = 'result'
|
||||||
|
reply['id'] = iq['id']
|
||||||
|
reply['to'] = iq['from']
|
||||||
|
reply.send()
|
||||||
|
self.xmpp.event('google_nosave_change', iq)
|
59
sleekxmpp/plugins/google_nosave/stanza.py
Normal file
59
sleekxmpp/plugins/google_nosave/stanza.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
"""
|
||||||
|
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.jid import JID
|
||||||
|
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
|
||||||
|
|
||||||
|
|
||||||
|
class NoSave(ElementBase):
|
||||||
|
name = 'x'
|
||||||
|
namespace = 'google:nosave'
|
||||||
|
plugin_attrib = 'google_nosave'
|
||||||
|
interfaces = set(['value'])
|
||||||
|
|
||||||
|
def get_value(self):
|
||||||
|
return self._get_attr('value', '') == 'enabled'
|
||||||
|
|
||||||
|
def set_value(self, value):
|
||||||
|
self._set_attr('value', 'enabled' if value else 'disabled')
|
||||||
|
|
||||||
|
|
||||||
|
class NoSaveQuery(ElementBase):
|
||||||
|
name = 'query'
|
||||||
|
namespace = 'google:nosave'
|
||||||
|
plugin_attrib = 'google_nosave'
|
||||||
|
interfaces = set()
|
||||||
|
|
||||||
|
|
||||||
|
class Item(ElementBase):
|
||||||
|
name = 'item'
|
||||||
|
namespace = 'google:nosave'
|
||||||
|
plugin_attrib = 'item'
|
||||||
|
plugin_multi_attrib = 'items'
|
||||||
|
interfaces = set(['jid', 'source', 'value'])
|
||||||
|
|
||||||
|
def get_value(self):
|
||||||
|
return self._get_attr('value', '') == 'enabled'
|
||||||
|
|
||||||
|
def set_value(self, value):
|
||||||
|
self._set_attr('value', 'enabled' if value else 'disabled')
|
||||||
|
|
||||||
|
def get_jid(self):
|
||||||
|
return JID(self._get_attr('jid', ''))
|
||||||
|
|
||||||
|
def set_jid(self, value):
|
||||||
|
self._set_attr('jid', str(value))
|
||||||
|
|
||||||
|
def get_source(self):
|
||||||
|
return JID(self._get_attr('source', ''))
|
||||||
|
|
||||||
|
def set_source(self):
|
||||||
|
self._set_attr('source', str(value))
|
||||||
|
|
||||||
|
|
||||||
|
register_stanza_plugin(NoSaveQuery, Item)
|
15
sleekxmpp/plugins/google_settings/__init__.py
Normal file
15
sleekxmpp/plugins/google_settings/__init__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
"""
|
||||||
|
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)
|
68
sleekxmpp/plugins/google_settings/settings.py
Normal file
68
sleekxmpp/plugins/google_settings/settings.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
"""
|
||||||
|
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.stanza import Iq
|
||||||
|
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__)
|
||||||
|
|
||||||
|
|
||||||
|
class GoogleSettings(BasePlugin):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Google: Gmail Notifications
|
||||||
|
|
||||||
|
Also see <https://developers.google.com/talk/jep_extensions/usersettings>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = 'google_settings'
|
||||||
|
description = 'Google: User Settings'
|
||||||
|
dependencies = set()
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Iq, stanza.UserSettings)
|
||||||
|
|
||||||
|
self.xmpp.register_handler(
|
||||||
|
Callback('Google Settings',
|
||||||
|
StanzaPath('iq@type=set/google_settings'),
|
||||||
|
self._handle_settings_change))
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.xmpp.remove_handler('Google Settings')
|
||||||
|
|
||||||
|
def get(self, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'get'
|
||||||
|
iq.enable('google_settings')
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def update(self, settings, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq.enable('google_settings')
|
||||||
|
|
||||||
|
for setting, value in settings.items():
|
||||||
|
iq['google_settings'][setting] = value
|
||||||
|
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def _handle_settings_change(self, iq):
|
||||||
|
reply = self.xmpp.Iq()
|
||||||
|
reply['type'] = 'result'
|
||||||
|
reply['id'] = iq['id']
|
||||||
|
reply['to'] = iq['from']
|
||||||
|
reply.send()
|
||||||
|
self.xmpp.event('google_settings_change', iq)
|
110
sleekxmpp/plugins/google_settings/stanza.py
Normal file
110
sleekxmpp/plugins/google_settings/stanza.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
"""
|
||||||
|
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 ET, ElementBase, register_stanza_plugin
|
||||||
|
|
||||||
|
|
||||||
|
class UserSettings(ElementBase):
|
||||||
|
name = 'usersetting'
|
||||||
|
namespace = 'google:setting'
|
||||||
|
plugin_attrib = 'google_settings'
|
||||||
|
interfaces = set(['auto_accept_suggestions',
|
||||||
|
'mail_notifications',
|
||||||
|
'archiving_enabled',
|
||||||
|
'gmail',
|
||||||
|
'email_verified',
|
||||||
|
'domain_privacy_notice',
|
||||||
|
'display_name'])
|
||||||
|
|
||||||
|
def _get_setting(self, setting):
|
||||||
|
xml = self.xml.find('{%s}%s' % (self.namespace, setting))
|
||||||
|
if xml is not None:
|
||||||
|
return xml.attrib.get('value', '') == 'true'
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _set_setting(self, setting, value):
|
||||||
|
self._del_setting(setting)
|
||||||
|
if value in (True, False):
|
||||||
|
xml = ET.Element('{%s}%s' % (self.namespace, setting))
|
||||||
|
xml.attrib['value'] = 'true' if value else 'false'
|
||||||
|
self.xml.append(xml)
|
||||||
|
|
||||||
|
def _del_setting(self, setting):
|
||||||
|
xml = self.xml.find('{%s}%s' % (self.namespace, setting))
|
||||||
|
if xml is not None:
|
||||||
|
self.xml.remove(xml)
|
||||||
|
|
||||||
|
def get_display_name(self):
|
||||||
|
xml = self.xml.find('{%s}%s' % (self.namespace, 'displayname'))
|
||||||
|
if xml is not None:
|
||||||
|
return xml.attrib.get('value', '')
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def set_display_name(self, value):
|
||||||
|
self._del_setting(setting)
|
||||||
|
if value:
|
||||||
|
xml = ET.Element('{%s}%s' % (self.namespace, 'displayname'))
|
||||||
|
xml.attrib['value'] = value
|
||||||
|
self.xml.append(xml)
|
||||||
|
|
||||||
|
def del_display_name(self):
|
||||||
|
self._del_setting('displayname')
|
||||||
|
|
||||||
|
def get_auto_accept_suggestions(self):
|
||||||
|
return self._get_setting('autoacceptsuggestions')
|
||||||
|
|
||||||
|
def get_mail_notifications(self):
|
||||||
|
return self._get_setting('mailnotifications')
|
||||||
|
|
||||||
|
def get_archiving_enabled(self):
|
||||||
|
return self._get_setting('archivingenabled')
|
||||||
|
|
||||||
|
def get_gmail(self):
|
||||||
|
return self._get_setting('gmail')
|
||||||
|
|
||||||
|
def get_email_verified(self):
|
||||||
|
return self._get_setting('emailverified')
|
||||||
|
|
||||||
|
def get_domain_privacy_notice(self):
|
||||||
|
return self._get_setting('domainprivacynotice')
|
||||||
|
|
||||||
|
def set_auto_accept_suggestions(self, value):
|
||||||
|
self._set_setting('autoacceptsuggestions', value)
|
||||||
|
|
||||||
|
def set_mail_notifications(self, value):
|
||||||
|
self._set_setting('mailnotifications', value)
|
||||||
|
|
||||||
|
def set_archiving_enabled(self, value):
|
||||||
|
self._set_setting('archivingenabled', value)
|
||||||
|
|
||||||
|
def set_gmail(self, value):
|
||||||
|
self._set_setting('gmail', value)
|
||||||
|
|
||||||
|
def set_email_verified(self, value):
|
||||||
|
self._set_setting('emailverified', value)
|
||||||
|
|
||||||
|
def set_domain_privacy_notice(self, value):
|
||||||
|
self._set_setting('domainprivacynotice', value)
|
||||||
|
|
||||||
|
def del_auto_accept_suggestions(self):
|
||||||
|
self._del_setting('autoacceptsuggestions')
|
||||||
|
|
||||||
|
def del_mail_notifications(self):
|
||||||
|
self._del_setting('mailnotifications')
|
||||||
|
|
||||||
|
def del_archiving_enabled(self):
|
||||||
|
self._del_setting('archivingenabled')
|
||||||
|
|
||||||
|
def del_gmail(self):
|
||||||
|
self._del_setting('gmail')
|
||||||
|
|
||||||
|
def del_email_verified(self):
|
||||||
|
self._del_setting('emailverified')
|
||||||
|
|
||||||
|
def del_domain_privacy_notice(self):
|
||||||
|
self._del_setting('domainprivacynotice')
|
@ -1,49 +0,0 @@
|
|||||||
from . import base
|
|
||||||
import logging
|
|
||||||
from xml.etree import cElementTree as ET
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class jobs(base.base_plugin):
|
|
||||||
def plugin_init(self):
|
|
||||||
self.xep = 'pubsubjob'
|
|
||||||
self.description = "Job distribution over Pubsub"
|
|
||||||
|
|
||||||
def post_init(self):
|
|
||||||
pass
|
|
||||||
#TODO add event
|
|
||||||
|
|
||||||
def createJobNode(self, host, jid, node, config=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def createJob(self, host, node, jobid=None, payload=None):
|
|
||||||
return self.xmpp.plugin['xep_0060'].setItem(host, node, ((jobid, payload),))
|
|
||||||
|
|
||||||
def claimJob(self, host, node, jobid, ifrom=None):
|
|
||||||
return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}claimed'))
|
|
||||||
|
|
||||||
def unclaimJob(self, host, node, jobid):
|
|
||||||
return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}unclaimed'))
|
|
||||||
|
|
||||||
def finishJob(self, host, node, jobid, payload=None):
|
|
||||||
finished = ET.Element('{http://andyet.net/protocol/pubsubjob}finished')
|
|
||||||
if payload is not None:
|
|
||||||
finished.append(payload)
|
|
||||||
return self._setState(host, node, jobid, finished)
|
|
||||||
|
|
||||||
def _setState(self, host, node, jobid, state, ifrom=None):
|
|
||||||
iq = self.xmpp.Iq()
|
|
||||||
iq['to'] = host
|
|
||||||
if ifrom: iq['from'] = ifrom
|
|
||||||
iq['type'] = 'set'
|
|
||||||
iq['psstate']['node'] = node
|
|
||||||
iq['psstate']['item'] = jobid
|
|
||||||
iq['psstate']['payload'] = state
|
|
||||||
result = iq.send()
|
|
||||||
if result is None or type(result) == bool or result['type'] != 'result':
|
|
||||||
log.error("Unable to change %s:%s to %s", node, jobid, state)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
@ -1,133 +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 __future__ import with_statement
|
|
||||||
from . import base
|
|
||||||
import logging
|
|
||||||
from xml.etree import cElementTree as ET
|
|
||||||
import time
|
|
||||||
|
|
||||||
class old_0050(base.base_plugin):
|
|
||||||
"""
|
|
||||||
XEP-0050 Ad-Hoc Commands
|
|
||||||
"""
|
|
||||||
|
|
||||||
def plugin_init(self):
|
|
||||||
self.xep = '0050'
|
|
||||||
self.description = 'Ad-Hoc Commands'
|
|
||||||
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='__None__'/></iq>" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc None')
|
|
||||||
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='execute'/></iq>" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc Execute')
|
|
||||||
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='next'/></iq>" % self.xmpp.default_ns, self.handler_command_next, name='Ad-Hoc Next', threaded=True)
|
|
||||||
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='cancel'/></iq>" % self.xmpp.default_ns, self.handler_command_cancel, name='Ad-Hoc Cancel')
|
|
||||||
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='complete'/></iq>" % self.xmpp.default_ns, self.handler_command_complete, name='Ad-Hoc Complete')
|
|
||||||
self.commands = {}
|
|
||||||
self.sessions = {}
|
|
||||||
self.sd = self.xmpp.plugin['xep_0030']
|
|
||||||
|
|
||||||
def post_init(self):
|
|
||||||
base.base_plugin.post_init(self)
|
|
||||||
self.sd.add_feature('http://jabber.org/protocol/commands')
|
|
||||||
|
|
||||||
def addCommand(self, node, name, form, pointer=None, multi=False):
|
|
||||||
self.sd.add_item(None, name, 'http://jabber.org/protocol/commands', node)
|
|
||||||
self.sd.add_identity('automation', 'command-node', name, node)
|
|
||||||
self.sd.add_feature('http://jabber.org/protocol/commands', node)
|
|
||||||
self.sd.add_feature('jabber:x:data', node)
|
|
||||||
self.commands[node] = (name, form, pointer, multi)
|
|
||||||
|
|
||||||
def getNewSession(self):
|
|
||||||
return str(time.time()) + '-' + self.xmpp.getNewId()
|
|
||||||
|
|
||||||
def handler_command(self, xml):
|
|
||||||
in_command = xml.find('{http://jabber.org/protocol/commands}command')
|
|
||||||
sessionid = in_command.get('sessionid', None)
|
|
||||||
node = in_command.get('node')
|
|
||||||
sessionid = self.getNewSession()
|
|
||||||
name, form, pointer, multi = self.commands[node]
|
|
||||||
self.sessions[sessionid] = {}
|
|
||||||
self.sessions[sessionid]['jid'] = xml.get('from')
|
|
||||||
self.sessions[sessionid]['to'] = xml.get('to')
|
|
||||||
self.sessions[sessionid]['past'] = [(form, None)]
|
|
||||||
self.sessions[sessionid]['next'] = pointer
|
|
||||||
npointer = pointer
|
|
||||||
if multi:
|
|
||||||
actions = ['next']
|
|
||||||
status = 'executing'
|
|
||||||
else:
|
|
||||||
if pointer is None:
|
|
||||||
status = 'completed'
|
|
||||||
actions = []
|
|
||||||
else:
|
|
||||||
status = 'executing'
|
|
||||||
actions = ['complete']
|
|
||||||
self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=form, id=xml.attrib['id'], sessionid=sessionid, status=status, actions=actions))
|
|
||||||
|
|
||||||
def handler_command_complete(self, xml):
|
|
||||||
in_command = xml.find('{http://jabber.org/protocol/commands}command')
|
|
||||||
sessionid = in_command.get('sessionid', None)
|
|
||||||
pointer = self.sessions[sessionid]['next']
|
|
||||||
results = self.xmpp.plugin['old_0004'].makeForm('result')
|
|
||||||
results.fromXML(in_command.find('{jabber:x:data}x'))
|
|
||||||
pointer(results,sessionid)
|
|
||||||
self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=None, id=xml.attrib['id'], sessionid=sessionid, status='completed', actions=[]))
|
|
||||||
del self.sessions[in_command.get('sessionid')]
|
|
||||||
|
|
||||||
|
|
||||||
def handler_command_next(self, xml):
|
|
||||||
in_command = xml.find('{http://jabber.org/protocol/commands}command')
|
|
||||||
sessionid = in_command.get('sessionid', None)
|
|
||||||
pointer = self.sessions[sessionid]['next']
|
|
||||||
results = self.xmpp.plugin['old_0004'].makeForm('result')
|
|
||||||
results.fromXML(in_command.find('{jabber:x:data}x'))
|
|
||||||
form, npointer, next = pointer(results,sessionid)
|
|
||||||
self.sessions[sessionid]['next'] = npointer
|
|
||||||
self.sessions[sessionid]['past'].append((form, pointer))
|
|
||||||
actions = []
|
|
||||||
actions.append('prev')
|
|
||||||
if npointer is None:
|
|
||||||
status = 'completed'
|
|
||||||
else:
|
|
||||||
status = 'executing'
|
|
||||||
if next:
|
|
||||||
actions.append('next')
|
|
||||||
else:
|
|
||||||
actions.append('complete')
|
|
||||||
self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=form, id=xml.attrib['id'], sessionid=sessionid, status=status, actions=actions))
|
|
||||||
|
|
||||||
def handler_command_cancel(self, xml):
|
|
||||||
command = xml.find('{http://jabber.org/protocol/commands}command')
|
|
||||||
try:
|
|
||||||
del self.sessions[command.get('sessionid')]
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
self.xmpp.send(self.makeCommand(xml.attrib['from'], command.attrib['node'], id=xml.attrib['id'], sessionid=command.attrib['sessionid'], status='canceled'))
|
|
||||||
|
|
||||||
def makeCommand(self, to, node, id=None, form=None, sessionid=None, status='executing', actions=[]):
|
|
||||||
if not id:
|
|
||||||
id = self.xmpp.getNewId()
|
|
||||||
iq = self.xmpp.makeIqResult(id)
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
iq.attrib['to'] = to
|
|
||||||
command = ET.Element('{http://jabber.org/protocol/commands}command')
|
|
||||||
command.attrib['node'] = node
|
|
||||||
command.attrib['status'] = status
|
|
||||||
xmlactions = ET.Element('actions')
|
|
||||||
for action in actions:
|
|
||||||
xmlactions.append(ET.Element(action))
|
|
||||||
if xmlactions:
|
|
||||||
command.append(xmlactions)
|
|
||||||
if not sessionid:
|
|
||||||
sessionid = self.getNewSession()
|
|
||||||
else:
|
|
||||||
iq.attrib['from'] = self.sessions[sessionid]['to']
|
|
||||||
command.attrib['sessionid'] = sessionid
|
|
||||||
if form is not None:
|
|
||||||
if hasattr(form,'getXML'):
|
|
||||||
form = form.getXML()
|
|
||||||
command.append(form)
|
|
||||||
iq.append(command)
|
|
||||||
return iq
|
|
@ -1,313 +0,0 @@
|
|||||||
from __future__ import with_statement
|
|
||||||
from . import base
|
|
||||||
import logging
|
|
||||||
#from xml.etree import cElementTree as ET
|
|
||||||
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET
|
|
||||||
from . import stanza_pubsub
|
|
||||||
from . xep_0004 import Form
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class xep_0060(base.base_plugin):
|
|
||||||
"""
|
|
||||||
XEP-0060 Publish Subscribe
|
|
||||||
"""
|
|
||||||
|
|
||||||
def plugin_init(self):
|
|
||||||
self.xep = '0060'
|
|
||||||
self.description = 'Publish-Subscribe'
|
|
||||||
|
|
||||||
def create_node(self, jid, node, config=None, collection=False, ntype=None):
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
|
|
||||||
create = ET.Element('create')
|
|
||||||
create.set('node', node)
|
|
||||||
pubsub.append(create)
|
|
||||||
configure = ET.Element('configure')
|
|
||||||
if collection:
|
|
||||||
ntype = 'collection'
|
|
||||||
#if config is None:
|
|
||||||
# submitform = self.xmpp.plugin['xep_0004'].makeForm('submit')
|
|
||||||
#else:
|
|
||||||
if config is not None:
|
|
||||||
submitform = config
|
|
||||||
if 'FORM_TYPE' in submitform.field:
|
|
||||||
submitform.field['FORM_TYPE'].setValue('http://jabber.org/protocol/pubsub#node_config')
|
|
||||||
else:
|
|
||||||
submitform.addField('FORM_TYPE', 'hidden', value='http://jabber.org/protocol/pubsub#node_config')
|
|
||||||
if ntype:
|
|
||||||
if 'pubsub#node_type' in submitform.field:
|
|
||||||
submitform.field['pubsub#node_type'].setValue(ntype)
|
|
||||||
else:
|
|
||||||
submitform.addField('pubsub#node_type', value=ntype)
|
|
||||||
else:
|
|
||||||
if 'pubsub#node_type' in submitform.field:
|
|
||||||
submitform.field['pubsub#node_type'].setValue('leaf')
|
|
||||||
else:
|
|
||||||
submitform.addField('pubsub#node_type', value='leaf')
|
|
||||||
submitform['type'] = 'submit'
|
|
||||||
configure.append(submitform.xml)
|
|
||||||
pubsub.append(configure)
|
|
||||||
iq = self.xmpp.makeIqSet(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
result = iq.send()
|
|
||||||
if result is False or result is None or result['type'] == 'error': return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def subscribe(self, jid, node, bare=True, subscribee=None):
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
|
|
||||||
subscribe = ET.Element('subscribe')
|
|
||||||
subscribe.attrib['node'] = node
|
|
||||||
if subscribee is None:
|
|
||||||
if bare:
|
|
||||||
subscribe.attrib['jid'] = self.xmpp.boundjid.bare
|
|
||||||
else:
|
|
||||||
subscribe.attrib['jid'] = self.xmpp.boundjid.full
|
|
||||||
else:
|
|
||||||
subscribe.attrib['jid'] = subscribee
|
|
||||||
pubsub.append(subscribe)
|
|
||||||
iq = self.xmpp.makeIqSet(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
result = iq.send()
|
|
||||||
if result is False or result is None or result['type'] == 'error': return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def unsubscribe(self, jid, node, bare=True, subscribee=None):
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
|
|
||||||
unsubscribe = ET.Element('unsubscribe')
|
|
||||||
unsubscribe.attrib['node'] = node
|
|
||||||
if subscribee is None:
|
|
||||||
if bare:
|
|
||||||
unsubscribe.attrib['jid'] = self.xmpp.boundjid.bare
|
|
||||||
else:
|
|
||||||
unsubscribe.attrib['jid'] = self.xmpp.boundjid.full
|
|
||||||
else:
|
|
||||||
unsubscribe.attrib['jid'] = subscribee
|
|
||||||
pubsub.append(unsubscribe)
|
|
||||||
iq = self.xmpp.makeIqSet(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
result = iq.send()
|
|
||||||
if result is False or result is None or result['type'] == 'error': return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def getNodeConfig(self, jid, node=None): # if no node, then grab default
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
|
|
||||||
if node is not None:
|
|
||||||
configure = ET.Element('configure')
|
|
||||||
configure.attrib['node'] = node
|
|
||||||
else:
|
|
||||||
configure = ET.Element('default')
|
|
||||||
pubsub.append(configure)
|
|
||||||
#TODO: Add configure support.
|
|
||||||
iq = self.xmpp.makeIqGet()
|
|
||||||
iq.append(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
#self.xmpp.add_handler("<iq id='%s'/>" % id, self.handlerCreateNodeResponse)
|
|
||||||
result = iq.send()
|
|
||||||
if result is None or result == False or result['type'] == 'error':
|
|
||||||
log.warning("got error instead of config")
|
|
||||||
return False
|
|
||||||
if node is not None:
|
|
||||||
form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}configure/{jabber:x:data}x')
|
|
||||||
else:
|
|
||||||
form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}default/{jabber:x:data}x')
|
|
||||||
if not form or form is None:
|
|
||||||
log.error("No form found.")
|
|
||||||
return False
|
|
||||||
return Form(xml=form)
|
|
||||||
|
|
||||||
def getNodeSubscriptions(self, jid, node):
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
|
|
||||||
subscriptions = ET.Element('subscriptions')
|
|
||||||
subscriptions.attrib['node'] = node
|
|
||||||
pubsub.append(subscriptions)
|
|
||||||
iq = self.xmpp.makeIqGet()
|
|
||||||
iq.append(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
result = iq.send()
|
|
||||||
if result is None or result == False or result['type'] == 'error':
|
|
||||||
log.warning("got error instead of config")
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}subscriptions/{http://jabber.org/protocol/pubsub#owner}subscription')
|
|
||||||
if results is None:
|
|
||||||
return False
|
|
||||||
subs = {}
|
|
||||||
for sub in results:
|
|
||||||
subs[sub.get('jid')] = sub.get('subscription')
|
|
||||||
return subs
|
|
||||||
|
|
||||||
def getNodeAffiliations(self, jid, node):
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
|
|
||||||
affiliations = ET.Element('affiliations')
|
|
||||||
affiliations.attrib['node'] = node
|
|
||||||
pubsub.append(affiliations)
|
|
||||||
iq = self.xmpp.makeIqGet()
|
|
||||||
iq.append(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
result = iq.send()
|
|
||||||
if result is None or result == False or result['type'] == 'error':
|
|
||||||
log.warning("got error instead of config")
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}affiliations/{http://jabber.org/protocol/pubsub#owner}affiliation')
|
|
||||||
if results is None:
|
|
||||||
return False
|
|
||||||
subs = {}
|
|
||||||
for sub in results:
|
|
||||||
subs[sub.get('jid')] = sub.get('affiliation')
|
|
||||||
return subs
|
|
||||||
|
|
||||||
def deleteNode(self, jid, node):
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
|
|
||||||
iq = self.xmpp.makeIqSet()
|
|
||||||
delete = ET.Element('delete')
|
|
||||||
delete.attrib['node'] = node
|
|
||||||
pubsub.append(delete)
|
|
||||||
iq.append(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
result = iq.send()
|
|
||||||
if result is not None and result is not False and result['type'] != 'error':
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def setNodeConfig(self, jid, node, config):
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
|
|
||||||
configure = ET.Element('configure')
|
|
||||||
configure.attrib['node'] = node
|
|
||||||
config = config.getXML('submit')
|
|
||||||
configure.append(config)
|
|
||||||
pubsub.append(configure)
|
|
||||||
iq = self.xmpp.makeIqSet(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
result = iq.send()
|
|
||||||
if result is None or result['type'] == 'error':
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def setItem(self, jid, node, items=[]):
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
|
|
||||||
publish = ET.Element('publish')
|
|
||||||
publish.attrib['node'] = node
|
|
||||||
for pub_item in items:
|
|
||||||
id, payload = pub_item
|
|
||||||
item = ET.Element('item')
|
|
||||||
if id is not None:
|
|
||||||
item.attrib['id'] = id
|
|
||||||
item.append(payload)
|
|
||||||
publish.append(item)
|
|
||||||
pubsub.append(publish)
|
|
||||||
iq = self.xmpp.makeIqSet(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
result = iq.send()
|
|
||||||
if result is None or result is False or result['type'] == 'error': return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def addItem(self, jid, node, items=[]):
|
|
||||||
return self.setItem(jid, node, items)
|
|
||||||
|
|
||||||
def deleteItem(self, jid, node, item):
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
|
|
||||||
retract = ET.Element('retract')
|
|
||||||
retract.attrib['node'] = node
|
|
||||||
itemn = ET.Element('item')
|
|
||||||
itemn.attrib['id'] = item
|
|
||||||
retract.append(itemn)
|
|
||||||
pubsub.append(retract)
|
|
||||||
iq = self.xmpp.makeIqSet(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
result = iq.send()
|
|
||||||
if result is None or result is False or result['type'] == 'error': return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def getNodes(self, jid):
|
|
||||||
response = self.xmpp.plugin['xep_0030'].getItems(jid)
|
|
||||||
items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item')
|
|
||||||
nodes = {}
|
|
||||||
if items is not None and items is not False:
|
|
||||||
for item in items:
|
|
||||||
nodes[item.get('node')] = item.get('name')
|
|
||||||
return nodes
|
|
||||||
|
|
||||||
def getItems(self, jid, node):
|
|
||||||
response = self.xmpp.plugin['xep_0030'].getItems(jid, node)
|
|
||||||
items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item')
|
|
||||||
nodeitems = []
|
|
||||||
if items is not None and items is not False:
|
|
||||||
for item in items:
|
|
||||||
nodeitems.append(item.get('node'))
|
|
||||||
return nodeitems
|
|
||||||
|
|
||||||
def addNodeToCollection(self, jid, child, parent=''):
|
|
||||||
config = self.getNodeConfig(jid, child)
|
|
||||||
if not config or config is None:
|
|
||||||
self.lasterror = "Config Error"
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
config.field['pubsub#collection'].setValue(parent)
|
|
||||||
except KeyError:
|
|
||||||
log.warning("pubsub#collection doesn't exist in config, trying to add it")
|
|
||||||
config.addField('pubsub#collection', value=parent)
|
|
||||||
if not self.setNodeConfig(jid, child, config):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def modifyAffiliation(self, ps_jid, node, user_jid, affiliation):
|
|
||||||
if affiliation not in ('owner', 'publisher', 'member', 'none', 'outcast'):
|
|
||||||
raise TypeError
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
|
|
||||||
affs = ET.Element('affiliations')
|
|
||||||
affs.attrib['node'] = node
|
|
||||||
aff = ET.Element('affiliation')
|
|
||||||
aff.attrib['jid'] = user_jid
|
|
||||||
aff.attrib['affiliation'] = affiliation
|
|
||||||
affs.append(aff)
|
|
||||||
pubsub.append(affs)
|
|
||||||
iq = self.xmpp.makeIqSet(pubsub)
|
|
||||||
iq.attrib['to'] = ps_jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
result = iq.send()
|
|
||||||
if result is None or result is False or result['type'] == 'error':
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def addNodeToCollection(self, jid, child, parent=''):
|
|
||||||
config = self.getNodeConfig(jid, child)
|
|
||||||
if not config or config is None:
|
|
||||||
self.lasterror = "Config Error"
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
config.field['pubsub#collection'].setValue(parent)
|
|
||||||
except KeyError:
|
|
||||||
log.warning("pubsub#collection doesn't exist in config, trying to add it")
|
|
||||||
config.addField('pubsub#collection', value=parent)
|
|
||||||
if not self.setNodeConfig(jid, child, config):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def removeNodeFromCollection(self, jid, child):
|
|
||||||
self.addNodeToCollection(jid, child, '')
|
|
||||||
|
|
@ -70,7 +70,7 @@ class XEP_0092(BasePlugin):
|
|||||||
iq['software_version']['os'] = self.os
|
iq['software_version']['os'] = self.os
|
||||||
iq.send()
|
iq.send()
|
||||||
|
|
||||||
def get_version(self, jid, ifrom=None):
|
def get_version(self, jid, ifrom=None, block=True, timeout=None, callback=None):
|
||||||
"""
|
"""
|
||||||
Retrieve the software version of a remote agent.
|
Retrieve the software version of a remote agent.
|
||||||
|
|
||||||
@ -82,14 +82,4 @@ class XEP_0092(BasePlugin):
|
|||||||
iq['from'] = ifrom
|
iq['from'] = ifrom
|
||||||
iq['type'] = 'get'
|
iq['type'] = 'get'
|
||||||
iq['query'] = Version.namespace
|
iq['query'] = Version.namespace
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
result = iq.send()
|
|
||||||
|
|
||||||
if result and result['type'] != 'error':
|
|
||||||
values = result['software_version'].values
|
|
||||||
del values['lang']
|
|
||||||
return values
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
XEP_0092.getVersion = XEP_0092.get_version
|
|
||||||
|
@ -9,8 +9,8 @@
|
|||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import sleekxmpp
|
from sleekxmpp.jid import JID
|
||||||
from sleekxmpp import Iq
|
from sleekxmpp.stanza import Iq
|
||||||
from sleekxmpp.exceptions import IqError, IqTimeout
|
from sleekxmpp.exceptions import IqError, IqTimeout
|
||||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
from sleekxmpp.xmlstream.matcher import StanzaPath
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
@ -38,7 +38,7 @@ class XEP_0199(BasePlugin):
|
|||||||
keepalive -- If True, periodically send ping requests
|
keepalive -- If True, periodically send ping requests
|
||||||
to the server. If a ping is not answered,
|
to the server. If a ping is not answered,
|
||||||
the connection will be reset.
|
the connection will be reset.
|
||||||
frequency -- Time in seconds between keepalive pings.
|
interval -- Time in seconds between keepalive pings.
|
||||||
Defaults to 300 seconds.
|
Defaults to 300 seconds.
|
||||||
timeout -- Time in seconds to wait for a ping response.
|
timeout -- Time in seconds to wait for a ping response.
|
||||||
Defaults to 30 seconds.
|
Defaults to 30 seconds.
|
||||||
@ -53,7 +53,7 @@ class XEP_0199(BasePlugin):
|
|||||||
stanza = stanza
|
stanza = stanza
|
||||||
default_config = {
|
default_config = {
|
||||||
'keepalive': False,
|
'keepalive': False,
|
||||||
'frequency': 300,
|
'interval': 300,
|
||||||
'timeout': 30
|
'timeout': 30
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +61,7 @@ class XEP_0199(BasePlugin):
|
|||||||
"""
|
"""
|
||||||
Start the XEP-0199 plugin.
|
Start the XEP-0199 plugin.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
register_stanza_plugin(Iq, Ping)
|
register_stanza_plugin(Iq, Ping)
|
||||||
|
|
||||||
self.xmpp.register_handler(
|
self.xmpp.register_handler(
|
||||||
@ -70,88 +71,70 @@ class XEP_0199(BasePlugin):
|
|||||||
|
|
||||||
if self.keepalive:
|
if self.keepalive:
|
||||||
self.xmpp.add_event_handler('session_start',
|
self.xmpp.add_event_handler('session_start',
|
||||||
self._handle_keepalive,
|
self.enable_keepalive,
|
||||||
threaded=True)
|
threaded=True)
|
||||||
self.xmpp.add_event_handler('session_end',
|
self.xmpp.add_event_handler('session_end',
|
||||||
self._handle_session_end)
|
self.disable_keepalive)
|
||||||
|
|
||||||
def plugin_end(self):
|
def plugin_end(self):
|
||||||
self.xmpp['xep_0030'].del_feature(feature=Ping.namespace)
|
self.xmpp['xep_0030'].del_feature(feature=Ping.namespace)
|
||||||
self.xmpp.remove_handler('Ping')
|
self.xmpp.remove_handler('Ping')
|
||||||
if self.keepalive:
|
if self.keepalive:
|
||||||
self.xmpp.del_event_handler('session_start',
|
self.xmpp.del_event_handler('session_start',
|
||||||
self._handle_keepalive)
|
self.enable_keepalive)
|
||||||
self.xmpp.del_event_handler('session_end',
|
self.xmpp.del_event_handler('session_end',
|
||||||
self._handle_session_end)
|
self.disable_keepalive)
|
||||||
|
|
||||||
def session_bind(self, jid):
|
def session_bind(self, jid):
|
||||||
self.xmpp['xep_0030'].add_feature(Ping.namespace)
|
self.xmpp['xep_0030'].add_feature(Ping.namespace)
|
||||||
|
|
||||||
def _handle_keepalive(self, event):
|
def enable_keepalive(self, interval=None, timeout=None):
|
||||||
"""
|
if interval:
|
||||||
Begin periodic pinging of the server. If a ping is not
|
self.interval = interval
|
||||||
answered, the connection will be restarted.
|
if timeout:
|
||||||
|
self.timeout = timeout
|
||||||
|
|
||||||
The pinging interval can be adjused using self.frequency
|
self.keepalive = True
|
||||||
before beginning processing.
|
self.xmpp.schedule('Ping keepalive',
|
||||||
|
self.interval,
|
||||||
Arguments:
|
self._keepalive,
|
||||||
event -- The session_start event.
|
|
||||||
"""
|
|
||||||
def scheduled_ping():
|
|
||||||
"""Send ping request to the server."""
|
|
||||||
log.debug("Pinging...")
|
|
||||||
try:
|
|
||||||
self.send_ping(self.xmpp.boundjid.host, self.timeout)
|
|
||||||
except IqError:
|
|
||||||
log.debug("Ping response was an error." + \
|
|
||||||
"Requesting Reconnect.")
|
|
||||||
self.xmpp.reconnect()
|
|
||||||
except IqTimeout:
|
|
||||||
log.debug("Did not recieve ping back in time." + \
|
|
||||||
"Requesting Reconnect.")
|
|
||||||
self.xmpp.reconnect()
|
|
||||||
|
|
||||||
self.xmpp.schedule('Ping Keep Alive',
|
|
||||||
self.frequency,
|
|
||||||
scheduled_ping,
|
|
||||||
repeat=True)
|
repeat=True)
|
||||||
|
|
||||||
def _handle_session_end(self, event):
|
def disable_keepalive(self, event=None):
|
||||||
self.xmpp.scheduler.remove('Ping Keep Alive')
|
self.xmpp.scheduler.remove('Ping keepalive')
|
||||||
|
|
||||||
|
def _keepalive(self, event):
|
||||||
|
log.debug("Keepalive ping...")
|
||||||
|
try:
|
||||||
|
rtt = self.ping(self.xmpp.boundjid.host, self.timeout)
|
||||||
|
except IqTimeout:
|
||||||
|
log.debug("Did not recieve ping back in time." + \
|
||||||
|
"Requesting Reconnect.")
|
||||||
|
self.xmpp.reconnect()
|
||||||
|
else:
|
||||||
|
log.debug('Keepalive RTT: %s' % rtt)
|
||||||
|
|
||||||
def _handle_ping(self, iq):
|
def _handle_ping(self, iq):
|
||||||
"""
|
"""Automatically reply to ping requests."""
|
||||||
Automatically reply to ping requests.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
iq -- The ping request.
|
|
||||||
"""
|
|
||||||
log.debug("Pinged by %s", iq['from'])
|
log.debug("Pinged by %s", iq['from'])
|
||||||
iq.reply().send()
|
iq.reply().send()
|
||||||
|
|
||||||
def send_ping(self, jid, timeout=None, errorfalse=False,
|
def send_ping(self, jid, ifrom=None, block=True, timeout=None, callback=None):
|
||||||
ifrom=None, block=True, callback=None):
|
"""Send a ping request.
|
||||||
"""
|
|
||||||
Send a ping request and calculate the response time.
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
jid -- The JID that will receive the ping.
|
jid -- The JID that will receive the ping.
|
||||||
timeout -- Time in seconds to wait for a response.
|
|
||||||
Defaults to self.timeout.
|
|
||||||
errorfalse -- Indicates if False should be returned
|
|
||||||
if an error stanza is received. Defaults
|
|
||||||
to False.
|
|
||||||
ifrom -- Specifiy the sender JID.
|
ifrom -- Specifiy the sender JID.
|
||||||
block -- Indicate if execution should block until
|
block -- Indicate if execution should block until
|
||||||
a pong response is received. Defaults
|
a pong response is received. Defaults
|
||||||
to True.
|
to True.
|
||||||
|
timeout -- Time in seconds to wait for a response.
|
||||||
|
Defaults to self.timeout.
|
||||||
callback -- Optional handler to execute when a pong
|
callback -- Optional handler to execute when a pong
|
||||||
is received. Useful in conjunction with
|
is received. Useful in conjunction with
|
||||||
the option block=False.
|
the option block=False.
|
||||||
"""
|
"""
|
||||||
log.debug("Pinging %s", jid)
|
if not timeout:
|
||||||
if timeout is None:
|
|
||||||
timeout = self.timeout
|
timeout = self.timeout
|
||||||
|
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
@ -160,21 +143,43 @@ class XEP_0199(BasePlugin):
|
|||||||
iq['from'] = ifrom
|
iq['from'] = ifrom
|
||||||
iq.enable('ping')
|
iq.enable('ping')
|
||||||
|
|
||||||
start_time = time.clock()
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def ping(self, jid=None, ifrom=None, timeout=None):
|
||||||
|
"""Send a ping request and calculate RTT.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
jid -- The JID that will receive the ping.
|
||||||
|
ifrom -- Specifiy the sender JID.
|
||||||
|
timeout -- Time in seconds to wait for a response.
|
||||||
|
Defaults to self.timeout.
|
||||||
|
"""
|
||||||
|
if not jid:
|
||||||
|
if self.xmpp.is_component:
|
||||||
|
jid = self.xmpp.server
|
||||||
|
else:
|
||||||
|
jid = self.xmpp.boundjid.host
|
||||||
|
jid = JID(jid)
|
||||||
|
if jid == self.xmpp.boundjid.host or \
|
||||||
|
self.xmpp.is_component and jid == self.xmpp.server:
|
||||||
|
own_host = True
|
||||||
|
|
||||||
|
if not timeout:
|
||||||
|
timeout = self.timeout
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
|
||||||
|
log.debug('Pinging %s' % jid)
|
||||||
try:
|
try:
|
||||||
resp = iq.send(block=block,
|
self.send_ping(jid, ifrom=ifrom, timeout=timeout)
|
||||||
timeout=timeout,
|
except IqError as e:
|
||||||
callback=callback)
|
if own_host:
|
||||||
except IqError as err:
|
rtt = time.time() - start
|
||||||
resp = err.iq
|
log.debug('Pinged %s, RTT: %s', jid, rtt)
|
||||||
|
return rtt
|
||||||
end_time = time.clock()
|
else:
|
||||||
|
raise e
|
||||||
delay = end_time - start_time
|
else:
|
||||||
|
rtt = time.time() - start
|
||||||
if not block:
|
log.debug('Pinged %s, RTT: %s', jid, rtt)
|
||||||
return None
|
return rtt
|
||||||
|
|
||||||
log.debug("Pong: %s %f", jid, delay)
|
|
||||||
return delay
|
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
from sleekxmpp.stanza import Iq
|
from sleekxmpp.stanza import Iq, Message, Presence
|
||||||
from sleekxmpp.exceptions import XMPPError
|
from sleekxmpp.exceptions import XMPPError
|
||||||
from sleekxmpp.xmlstream.handler import Callback
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
from sleekxmpp.xmlstream.matcher import StanzaPath
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
@ -36,6 +36,8 @@ class XEP_0231(BasePlugin):
|
|||||||
self._cids = {}
|
self._cids = {}
|
||||||
|
|
||||||
register_stanza_plugin(Iq, BitsOfBinary)
|
register_stanza_plugin(Iq, BitsOfBinary)
|
||||||
|
register_stanza_plugin(Message, BitsOfBinary)
|
||||||
|
register_stanza_plugin(Presence, BitsOfBinary)
|
||||||
|
|
||||||
self.xmpp.register_handler(
|
self.xmpp.register_handler(
|
||||||
Callback('Bits of Binary - Iq',
|
Callback('Bits of Binary - Iq',
|
||||||
|
@ -14,12 +14,6 @@ from sleekxmpp.xmlstream.stanzabase import ET
|
|||||||
from sleekxmpp.xmlstream.matcher.base import MatcherBase
|
from sleekxmpp.xmlstream.matcher.base import MatcherBase
|
||||||
|
|
||||||
|
|
||||||
# Flag indicating if the builtin XPath matcher should be used, which
|
|
||||||
# uses namespaces, or a custom matcher that ignores namespaces.
|
|
||||||
# Changing this will affect ALL XMLMask matchers.
|
|
||||||
IGNORE_NS = False
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -39,10 +33,6 @@ class MatchXMLMask(MatcherBase):
|
|||||||
:class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath`
|
:class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath`
|
||||||
should be used instead.
|
should be used instead.
|
||||||
|
|
||||||
The use of namespaces in the mask comparison is controlled by
|
|
||||||
``IGNORE_NS``. Setting ``IGNORE_NS`` to ``True`` will disable namespace
|
|
||||||
based matching for ALL XMLMask matchers.
|
|
||||||
|
|
||||||
:param criteria: Either an :class:`~xml.etree.ElementTree.Element` XML
|
:param criteria: Either an :class:`~xml.etree.ElementTree.Element` XML
|
||||||
object or XML string to use as a mask.
|
object or XML string to use as a mask.
|
||||||
"""
|
"""
|
||||||
@ -84,8 +74,6 @@ class MatchXMLMask(MatcherBase):
|
|||||||
do not have a specified namespace.
|
do not have a specified namespace.
|
||||||
Defaults to ``"__no_ns__"``.
|
Defaults to ``"__no_ns__"``.
|
||||||
"""
|
"""
|
||||||
use_ns = not IGNORE_NS
|
|
||||||
|
|
||||||
if source is None:
|
if source is None:
|
||||||
# If the element was not found. May happend during recursive calls.
|
# If the element was not found. May happend during recursive calls.
|
||||||
return False
|
return False
|
||||||
@ -96,17 +84,10 @@ class MatchXMLMask(MatcherBase):
|
|||||||
mask = ET.fromstring(mask)
|
mask = ET.fromstring(mask)
|
||||||
except ExpatError:
|
except ExpatError:
|
||||||
log.warning("Expat error: %s\nIn parsing: %s", '', mask)
|
log.warning("Expat error: %s\nIn parsing: %s", '', mask)
|
||||||
if not use_ns:
|
|
||||||
# Compare the element without using namespaces.
|
mask_ns_tag = "{%s}%s" % (self.default_ns, mask.tag)
|
||||||
source_tag = source.tag.split('}', 1)[-1]
|
if source.tag not in [mask.tag, mask_ns_tag]:
|
||||||
mask_tag = mask.tag.split('}', 1)[-1]
|
return False
|
||||||
if source_tag != mask_tag:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
# Compare the element using namespaces
|
|
||||||
mask_ns_tag = "{%s}%s" % (self.default_ns, mask.tag)
|
|
||||||
if source.tag not in [mask.tag, mask_ns_tag]:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# If the mask includes text, compare it.
|
# If the mask includes text, compare it.
|
||||||
if mask.text and source.text and \
|
if mask.text and source.text and \
|
||||||
@ -122,37 +103,15 @@ class MatchXMLMask(MatcherBase):
|
|||||||
# Recursively check subelements.
|
# Recursively check subelements.
|
||||||
matched_elements = {}
|
matched_elements = {}
|
||||||
for subelement in mask:
|
for subelement in mask:
|
||||||
if use_ns:
|
matched = False
|
||||||
matched = False
|
for other in source.findall(subelement.tag):
|
||||||
for other in source.findall(subelement.tag):
|
matched_elements[other] = False
|
||||||
matched_elements[other] = False
|
if self._mask_cmp(other, subelement, use_ns):
|
||||||
if self._mask_cmp(other, subelement, use_ns):
|
if not matched_elements.get(other, False):
|
||||||
if not matched_elements.get(other, False):
|
matched_elements[other] = True
|
||||||
matched_elements[other] = True
|
matched = True
|
||||||
matched = True
|
if not matched:
|
||||||
if not matched:
|
return False
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if not self._mask_cmp(self._get_child(source, subelement.tag),
|
|
||||||
subelement, use_ns):
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Everything matches.
|
# Everything matches.
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _get_child(self, xml, tag):
|
|
||||||
"""Return a child element given its tag, ignoring namespace values.
|
|
||||||
|
|
||||||
Returns ``None`` if the child was not found.
|
|
||||||
|
|
||||||
:param xml: The :class:`~xml.etree.ElementTree.Element` XML object
|
|
||||||
to search for the given child tag.
|
|
||||||
:param tag: The name of the subelement to find.
|
|
||||||
"""
|
|
||||||
tag = tag.split('}')[-1]
|
|
||||||
try:
|
|
||||||
children = [c.tag.split('}')[-1] for c in xml]
|
|
||||||
index = children.index(tag)
|
|
||||||
except ValueError:
|
|
||||||
return None
|
|
||||||
return list(xml)[index]
|
|
||||||
|
@ -9,16 +9,10 @@
|
|||||||
:license: MIT, see LICENSE for more details
|
:license: MIT, see LICENSE for more details
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from sleekxmpp.xmlstream.stanzabase import ET
|
from sleekxmpp.xmlstream.stanzabase import ET, fix_ns
|
||||||
from sleekxmpp.xmlstream.matcher.base import MatcherBase
|
from sleekxmpp.xmlstream.matcher.base import MatcherBase
|
||||||
|
|
||||||
|
|
||||||
# Flag indicating if the builtin XPath matcher should be used, which
|
|
||||||
# uses namespaces, or a custom matcher that ignores namespaces.
|
|
||||||
# Changing this will affect ALL XPath matchers.
|
|
||||||
IGNORE_NS = False
|
|
||||||
|
|
||||||
|
|
||||||
class MatchXPath(MatcherBase):
|
class MatchXPath(MatcherBase):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -38,6 +32,9 @@ class MatchXPath(MatcherBase):
|
|||||||
expressions will be matched without using namespaces.
|
expressions will be matched without using namespaces.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self, criteria):
|
||||||
|
self._criteria = fix_ns(criteria)
|
||||||
|
|
||||||
def match(self, xml):
|
def match(self, xml):
|
||||||
"""
|
"""
|
||||||
Compare a stanza's XML contents to an XPath expression.
|
Compare a stanza's XML contents to an XPath expression.
|
||||||
@ -59,28 +56,4 @@ class MatchXPath(MatcherBase):
|
|||||||
x = ET.Element('x')
|
x = ET.Element('x')
|
||||||
x.append(xml)
|
x.append(xml)
|
||||||
|
|
||||||
if not IGNORE_NS:
|
return x.find(self._criteria) is not None
|
||||||
# Use builtin, namespace respecting, XPath matcher.
|
|
||||||
if x.find(self._criteria) is not None:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
# Remove namespaces from the XPath expression.
|
|
||||||
criteria = []
|
|
||||||
for ns_block in self._criteria.split('{'):
|
|
||||||
criteria.extend(ns_block.split('}')[-1].split('/'))
|
|
||||||
|
|
||||||
# Walk the XPath expression.
|
|
||||||
xml = x
|
|
||||||
for tag in criteria:
|
|
||||||
if not tag:
|
|
||||||
# Skip empty tag name artifacts from the cleanup phase.
|
|
||||||
continue
|
|
||||||
|
|
||||||
children = [c.tag.split('}')[-1] for c in xml]
|
|
||||||
try:
|
|
||||||
index = children.index(tag)
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
xml = list(xml)[index]
|
|
||||||
return True
|
|
||||||
|
@ -192,7 +192,7 @@ def fix_ns(xpath, split=False, propagate_ns=True, default_ns=''):
|
|||||||
for element in elements:
|
for element in elements:
|
||||||
if element:
|
if element:
|
||||||
# Skip empty entry artifacts from splitting.
|
# Skip empty entry artifacts from splitting.
|
||||||
if propagate_ns:
|
if propagate_ns and element[0] != '*':
|
||||||
tag = '{%s}%s' % (namespace, element)
|
tag = '{%s}%s' % (namespace, element)
|
||||||
else:
|
else:
|
||||||
tag = element
|
tag = element
|
||||||
|
@ -36,7 +36,9 @@ class TestStreamSet(SleekTest):
|
|||||||
|
|
||||||
def query():
|
def query():
|
||||||
r = self.xmpp['xep_0092'].get_version('foo@bar')
|
r = self.xmpp['xep_0092'].get_version('foo@bar')
|
||||||
results.append(r)
|
results.append((r['software_version']['name'],
|
||||||
|
r['software_version']['version'],
|
||||||
|
r['software_version']['os']))
|
||||||
|
|
||||||
self.stream_start(mode='client', plugins=['xep_0030', 'xep_0092'])
|
self.stream_start(mode='client', plugins=['xep_0030', 'xep_0092'])
|
||||||
|
|
||||||
@ -61,7 +63,7 @@ class TestStreamSet(SleekTest):
|
|||||||
|
|
||||||
t.join()
|
t.join()
|
||||||
|
|
||||||
expected = [{'name': 'Foo', 'version': '1.0', 'os':'Linux'}]
|
expected = [('Foo', '1.0', 'Linux')]
|
||||||
self.assertEqual(results, expected,
|
self.assertEqual(results, expected,
|
||||||
"Did not receive expected results: %s" % results)
|
"Did not receive expected results: %s" % results)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user