Merge branch 'master' into develop
This commit is contained in:
commit
bad405bea9
@ -62,16 +62,18 @@ class PingTest(sleekxmpp.ClientXMPP):
|
||||
"""
|
||||
self.send_presence()
|
||||
self.get_roster()
|
||||
result = self['xep_0199'].send_ping(self.pingjid,
|
||||
timeout=10,
|
||||
errorfalse=True)
|
||||
logging.info("Pinging...")
|
||||
if result is False:
|
||||
logging.info("Couldn't ping.")
|
||||
self.disconnect()
|
||||
sys.exit(1)
|
||||
else:
|
||||
logging.info("Success! RTT: %s", str(result))
|
||||
|
||||
try:
|
||||
rtt = self['xep_0199'].ping(self.pingjid,
|
||||
timeout=10)
|
||||
logging.info("Success! RTT: %s", rtt)
|
||||
except IqError as e:
|
||||
logging.info("Error pinging %s: %s",
|
||||
self.pingjid,
|
||||
e.iq['error']['condition'])
|
||||
except IqTimeout:
|
||||
logging.info("No response from %s", self.pingjid)
|
||||
finally:
|
||||
self.disconnect()
|
||||
|
||||
|
||||
|
@ -12,7 +12,7 @@ from sleekxmpp.plugins.base import register_plugin, load_plugin
|
||||
|
||||
__all__ = [
|
||||
# Non-standard
|
||||
'gmail_notify', # Gmail searching and notifications
|
||||
'gmail', # Gmail searching and notifications
|
||||
|
||||
# XEPS
|
||||
'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.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.
|
||||
|
||||
@ -82,14 +82,4 @@ class XEP_0092(BasePlugin):
|
||||
iq['from'] = ifrom
|
||||
iq['type'] = 'get'
|
||||
iq['query'] = Version.namespace
|
||||
|
||||
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
|
||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||
|
@ -9,8 +9,8 @@
|
||||
import time
|
||||
import logging
|
||||
|
||||
import sleekxmpp
|
||||
from sleekxmpp import Iq
|
||||
from sleekxmpp.jid import JID
|
||||
from sleekxmpp.stanza import Iq
|
||||
from sleekxmpp.exceptions import IqError, IqTimeout
|
||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||
@ -38,7 +38,7 @@ class XEP_0199(BasePlugin):
|
||||
keepalive -- If True, periodically send ping requests
|
||||
to the server. If a ping is not answered,
|
||||
the connection will be reset.
|
||||
frequency -- Time in seconds between keepalive pings.
|
||||
interval -- Time in seconds between keepalive pings.
|
||||
Defaults to 300 seconds.
|
||||
timeout -- Time in seconds to wait for a ping response.
|
||||
Defaults to 30 seconds.
|
||||
@ -53,7 +53,7 @@ class XEP_0199(BasePlugin):
|
||||
stanza = stanza
|
||||
default_config = {
|
||||
'keepalive': False,
|
||||
'frequency': 300,
|
||||
'interval': 300,
|
||||
'timeout': 30
|
||||
}
|
||||
|
||||
@ -61,6 +61,7 @@ class XEP_0199(BasePlugin):
|
||||
"""
|
||||
Start the XEP-0199 plugin.
|
||||
"""
|
||||
|
||||
register_stanza_plugin(Iq, Ping)
|
||||
|
||||
self.xmpp.register_handler(
|
||||
@ -70,88 +71,70 @@ class XEP_0199(BasePlugin):
|
||||
|
||||
if self.keepalive:
|
||||
self.xmpp.add_event_handler('session_start',
|
||||
self._handle_keepalive,
|
||||
self.enable_keepalive,
|
||||
threaded=True)
|
||||
self.xmpp.add_event_handler('session_end',
|
||||
self._handle_session_end)
|
||||
self.disable_keepalive)
|
||||
|
||||
def plugin_end(self):
|
||||
self.xmpp['xep_0030'].del_feature(feature=Ping.namespace)
|
||||
self.xmpp.remove_handler('Ping')
|
||||
if self.keepalive:
|
||||
self.xmpp.del_event_handler('session_start',
|
||||
self._handle_keepalive)
|
||||
self.enable_keepalive)
|
||||
self.xmpp.del_event_handler('session_end',
|
||||
self._handle_session_end)
|
||||
self.disable_keepalive)
|
||||
|
||||
def session_bind(self, jid):
|
||||
self.xmpp['xep_0030'].add_feature(Ping.namespace)
|
||||
|
||||
def _handle_keepalive(self, event):
|
||||
"""
|
||||
Begin periodic pinging of the server. If a ping is not
|
||||
answered, the connection will be restarted.
|
||||
def enable_keepalive(self, interval=None, timeout=None):
|
||||
if interval:
|
||||
self.interval = interval
|
||||
if timeout:
|
||||
self.timeout = timeout
|
||||
|
||||
The pinging interval can be adjused using self.frequency
|
||||
before beginning processing.
|
||||
|
||||
Arguments:
|
||||
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,
|
||||
self.keepalive = True
|
||||
self.xmpp.schedule('Ping keepalive',
|
||||
self.interval,
|
||||
self._keepalive,
|
||||
repeat=True)
|
||||
|
||||
def _handle_session_end(self, event):
|
||||
self.xmpp.scheduler.remove('Ping Keep Alive')
|
||||
def disable_keepalive(self, event=None):
|
||||
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):
|
||||
"""
|
||||
Automatically reply to ping requests.
|
||||
|
||||
Arguments:
|
||||
iq -- The ping request.
|
||||
"""
|
||||
"""Automatically reply to ping requests."""
|
||||
log.debug("Pinged by %s", iq['from'])
|
||||
iq.reply().send()
|
||||
|
||||
def send_ping(self, jid, timeout=None, errorfalse=False,
|
||||
ifrom=None, block=True, callback=None):
|
||||
"""
|
||||
Send a ping request and calculate the response time.
|
||||
def send_ping(self, jid, ifrom=None, block=True, timeout=None, callback=None):
|
||||
"""Send a ping request.
|
||||
|
||||
Arguments:
|
||||
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.
|
||||
block -- Indicate if execution should block until
|
||||
a pong response is received. Defaults
|
||||
to True.
|
||||
timeout -- Time in seconds to wait for a response.
|
||||
Defaults to self.timeout.
|
||||
callback -- Optional handler to execute when a pong
|
||||
is received. Useful in conjunction with
|
||||
the option block=False.
|
||||
"""
|
||||
log.debug("Pinging %s", jid)
|
||||
if timeout is None:
|
||||
if not timeout:
|
||||
timeout = self.timeout
|
||||
|
||||
iq = self.xmpp.Iq()
|
||||
@ -160,21 +143,43 @@ class XEP_0199(BasePlugin):
|
||||
iq['from'] = ifrom
|
||||
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:
|
||||
resp = iq.send(block=block,
|
||||
timeout=timeout,
|
||||
callback=callback)
|
||||
except IqError as err:
|
||||
resp = err.iq
|
||||
|
||||
end_time = time.clock()
|
||||
|
||||
delay = end_time - start_time
|
||||
|
||||
if not block:
|
||||
return None
|
||||
|
||||
log.debug("Pong: %s %f", jid, delay)
|
||||
return delay
|
||||
self.send_ping(jid, ifrom=ifrom, timeout=timeout)
|
||||
except IqError as e:
|
||||
if own_host:
|
||||
rtt = time.time() - start
|
||||
log.debug('Pinged %s, RTT: %s', jid, rtt)
|
||||
return rtt
|
||||
else:
|
||||
raise e
|
||||
else:
|
||||
rtt = time.time() - start
|
||||
log.debug('Pinged %s, RTT: %s', jid, rtt)
|
||||
return rtt
|
||||
|
@ -10,7 +10,7 @@
|
||||
import logging
|
||||
import hashlib
|
||||
|
||||
from sleekxmpp.stanza import Iq
|
||||
from sleekxmpp.stanza import Iq, Message, Presence
|
||||
from sleekxmpp.exceptions import XMPPError
|
||||
from sleekxmpp.xmlstream.handler import Callback
|
||||
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||
@ -36,6 +36,8 @@ class XEP_0231(BasePlugin):
|
||||
self._cids = {}
|
||||
|
||||
register_stanza_plugin(Iq, BitsOfBinary)
|
||||
register_stanza_plugin(Message, BitsOfBinary)
|
||||
register_stanza_plugin(Presence, BitsOfBinary)
|
||||
|
||||
self.xmpp.register_handler(
|
||||
Callback('Bits of Binary - Iq',
|
||||
|
@ -14,12 +14,6 @@ from sleekxmpp.xmlstream.stanzabase import ET
|
||||
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__)
|
||||
|
||||
|
||||
@ -39,10 +33,6 @@ class MatchXMLMask(MatcherBase):
|
||||
:class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath`
|
||||
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
|
||||
object or XML string to use as a mask.
|
||||
"""
|
||||
@ -84,8 +74,6 @@ class MatchXMLMask(MatcherBase):
|
||||
do not have a specified namespace.
|
||||
Defaults to ``"__no_ns__"``.
|
||||
"""
|
||||
use_ns = not IGNORE_NS
|
||||
|
||||
if source is None:
|
||||
# If the element was not found. May happend during recursive calls.
|
||||
return False
|
||||
@ -96,17 +84,10 @@ class MatchXMLMask(MatcherBase):
|
||||
mask = ET.fromstring(mask)
|
||||
except ExpatError:
|
||||
log.warning("Expat error: %s\nIn parsing: %s", '', mask)
|
||||
if not use_ns:
|
||||
# Compare the element without using namespaces.
|
||||
source_tag = source.tag.split('}', 1)[-1]
|
||||
mask_tag = mask.tag.split('}', 1)[-1]
|
||||
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
|
||||
|
||||
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 mask.text and source.text and \
|
||||
@ -122,37 +103,15 @@ class MatchXMLMask(MatcherBase):
|
||||
# Recursively check subelements.
|
||||
matched_elements = {}
|
||||
for subelement in mask:
|
||||
if use_ns:
|
||||
matched = False
|
||||
for other in source.findall(subelement.tag):
|
||||
matched_elements[other] = False
|
||||
if self._mask_cmp(other, subelement, use_ns):
|
||||
if not matched_elements.get(other, False):
|
||||
matched_elements[other] = True
|
||||
matched = True
|
||||
if not matched:
|
||||
return False
|
||||
else:
|
||||
if not self._mask_cmp(self._get_child(source, subelement.tag),
|
||||
subelement, use_ns):
|
||||
return False
|
||||
matched = False
|
||||
for other in source.findall(subelement.tag):
|
||||
matched_elements[other] = False
|
||||
if self._mask_cmp(other, subelement, use_ns):
|
||||
if not matched_elements.get(other, False):
|
||||
matched_elements[other] = True
|
||||
matched = True
|
||||
if not matched:
|
||||
return False
|
||||
|
||||
# Everything matches.
|
||||
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
|
||||
"""
|
||||
|
||||
from sleekxmpp.xmlstream.stanzabase import ET
|
||||
from sleekxmpp.xmlstream.stanzabase import ET, fix_ns
|
||||
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):
|
||||
|
||||
"""
|
||||
@ -38,6 +32,9 @@ class MatchXPath(MatcherBase):
|
||||
expressions will be matched without using namespaces.
|
||||
"""
|
||||
|
||||
def __init__(self, criteria):
|
||||
self._criteria = fix_ns(criteria)
|
||||
|
||||
def match(self, xml):
|
||||
"""
|
||||
Compare a stanza's XML contents to an XPath expression.
|
||||
@ -59,28 +56,4 @@ class MatchXPath(MatcherBase):
|
||||
x = ET.Element('x')
|
||||
x.append(xml)
|
||||
|
||||
if not IGNORE_NS:
|
||||
# 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
|
||||
return x.find(self._criteria) is not None
|
||||
|
@ -192,7 +192,7 @@ def fix_ns(xpath, split=False, propagate_ns=True, default_ns=''):
|
||||
for element in elements:
|
||||
if element:
|
||||
# Skip empty entry artifacts from splitting.
|
||||
if propagate_ns:
|
||||
if propagate_ns and element[0] != '*':
|
||||
tag = '{%s}%s' % (namespace, element)
|
||||
else:
|
||||
tag = element
|
||||
|
@ -36,7 +36,9 @@ class TestStreamSet(SleekTest):
|
||||
|
||||
def query():
|
||||
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'])
|
||||
|
||||
@ -61,7 +63,7 @@ class TestStreamSet(SleekTest):
|
||||
|
||||
t.join()
|
||||
|
||||
expected = [{'name': 'Foo', 'version': '1.0', 'os':'Linux'}]
|
||||
expected = [('Foo', '1.0', 'Linux')]
|
||||
self.assertEqual(results, expected,
|
||||
"Did not receive expected results: %s" % results)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user