From d86adfa1b164293562e8b86dae7a82755b95626d Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 20 Jan 2013 13:54:01 -0800 Subject: Updated XEP-0092 to take callbacks and return the version result stanza. --- sleekxmpp/plugins/xep_0092/version.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/sleekxmpp/plugins/xep_0092/version.py b/sleekxmpp/plugins/xep_0092/version.py index 35813e1d..b16ad516 100644 --- a/sleekxmpp/plugins/xep_0092/version.py +++ b/sleekxmpp/plugins/xep_0092/version.py @@ -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) -- cgit v1.2.3 From ccf7916257d6af6ec9fd76d4c4c438b7f9d8078c Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 20 Jan 2013 15:43:02 -0800 Subject: Allow for simplified XPath namespaces --- sleekxmpp/xmlstream/matcher/xpath.py | 37 +++++------------------------------- sleekxmpp/xmlstream/stanzabase.py | 2 +- 2 files changed, 6 insertions(+), 33 deletions(-) diff --git a/sleekxmpp/xmlstream/matcher/xpath.py b/sleekxmpp/xmlstream/matcher/xpath.py index 3f03e68e..f3d28429 100644 --- a/sleekxmpp/xmlstream/matcher/xpath.py +++ b/sleekxmpp/xmlstream/matcher/xpath.py @@ -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 diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 122d7eb4..120373db 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -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 -- cgit v1.2.3 From a1716de6832531b4eccf2996201c943b2d1d0b75 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 20 Jan 2013 16:08:01 -0800 Subject: Update tests for XEP-0092 --- tests/test_stream_xep_0092.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_stream_xep_0092.py b/tests/test_stream_xep_0092.py index 4a038558..a3960fa2 100644 --- a/tests/test_stream_xep_0092.py +++ b/tests/test_stream_xep_0092.py @@ -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) -- cgit v1.2.3 From 93b8e66b5d80f8042635b526efc190a15fe59271 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 20 Jan 2013 16:24:50 -0800 Subject: Remove unused portions of XMLMask --- sleekxmpp/xmlstream/matcher/xmlmask.py | 67 +++++++--------------------------- 1 file changed, 13 insertions(+), 54 deletions(-) diff --git a/sleekxmpp/xmlstream/matcher/xmlmask.py b/sleekxmpp/xmlstream/matcher/xmlmask.py index a0568f08..cb202448 100644 --- a/sleekxmpp/xmlstream/matcher/xmlmask.py +++ b/sleekxmpp/xmlstream/matcher/xmlmask.py @@ -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] -- cgit v1.2.3 From 1f9286d39e70ef737232522ad7343ff730e9c518 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 20 Jan 2013 18:44:17 -0800 Subject: Add BoB data to message and presence stanzas. --- sleekxmpp/plugins/xep_0231/bob.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sleekxmpp/plugins/xep_0231/bob.py b/sleekxmpp/plugins/xep_0231/bob.py index d86a5ddf..5e1f590b 100644 --- a/sleekxmpp/plugins/xep_0231/bob.py +++ b/sleekxmpp/plugins/xep_0231/bob.py @@ -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', -- cgit v1.2.3 From 3423589ba1fc2a6284bf3e4bd7402f1e80fff275 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 20 Jan 2013 20:14:16 -0800 Subject: Updated XEP-0199 to take and return standardized values. Handles Iq errors appropriately when the recipient can't be found. --- examples/ping.py | 22 +++--- sleekxmpp/plugins/xep_0199/ping.py | 141 +++++++++++++++++++------------------ 2 files changed, 85 insertions(+), 78 deletions(-) diff --git a/examples/ping.py b/examples/ping.py index 0e53b1dd..8fbb5655 100755 --- a/examples/ping.py +++ b/examples/ping.py @@ -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() diff --git a/sleekxmpp/plugins/xep_0199/ping.py b/sleekxmpp/plugins/xep_0199/ping.py index 0bdeabf3..e095a551 100644 --- a/sleekxmpp/plugins/xep_0199/ping.py +++ b/sleekxmpp/plugins/xep_0199/ping.py @@ -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. - - The pinging interval can be adjused using self.frequency - before beginning processing. + def enable_keepalive(self, interval=None, timeout=None): + if interval: + self.interval = interval + if timeout: + self.timeout = timeout - 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 _handle_ping(self, iq): - """ - Automatically reply to ping requests. + 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) - Arguments: - iq -- The ping request. - """ + def _handle_ping(self, iq): + """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) - try: - resp = iq.send(block=block, - timeout=timeout, - callback=callback) - except IqError as err: - resp = err.iq + def ping(self, jid=None, ifrom=None, timeout=None): + """Send a ping request and calculate RTT. - end_time = time.clock() - - delay = end_time - start_time + 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 - if not block: - return None + start = time.time() - log.debug("Pong: %s %f", jid, delay) - return delay + log.debug('Pinging %s' % jid) + try: + 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 -- cgit v1.2.3 From ea0381fa0999853355321b66aaa58550a33bcfd9 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 20 Jan 2013 21:35:06 -0800 Subject: Remove old versions of some plugins. --- sleekxmpp/plugins/jobs.py | 49 ------- sleekxmpp/plugins/old_0050.py | 133 ------------------ sleekxmpp/plugins/old_0060.py | 313 ------------------------------------------ 3 files changed, 495 deletions(-) delete mode 100644 sleekxmpp/plugins/jobs.py delete mode 100644 sleekxmpp/plugins/old_0050.py delete mode 100644 sleekxmpp/plugins/old_0060.py diff --git a/sleekxmpp/plugins/jobs.py b/sleekxmpp/plugins/jobs.py deleted file mode 100644 index cb9deba8..00000000 --- a/sleekxmpp/plugins/jobs.py +++ /dev/null @@ -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 - diff --git a/sleekxmpp/plugins/old_0050.py b/sleekxmpp/plugins/old_0050.py deleted file mode 100644 index 6e969a51..00000000 --- a/sleekxmpp/plugins/old_0050.py +++ /dev/null @@ -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("" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc None') - self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc Execute') - self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_command_next, name='Ad-Hoc Next', threaded=True) - self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_command_cancel, name='Ad-Hoc Cancel') - self.xmpp.add_handler("" % 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 diff --git a/sleekxmpp/plugins/old_0060.py b/sleekxmpp/plugins/old_0060.py deleted file mode 100644 index 93124fca..00000000 --- a/sleekxmpp/plugins/old_0060.py +++ /dev/null @@ -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("" % 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, '') - -- cgit v1.2.3 From 27196a21aee2710b8c138cfd6265282bf0f92172 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 20 Jan 2013 23:01:54 -0800 Subject: Modernize the Gmail plugin. --- sleekxmpp/plugins/__init__.py | 2 +- sleekxmpp/plugins/gmail/__init__.py | 15 ++++ sleekxmpp/plugins/gmail/notifications.py | 92 +++++++++++++++++++ sleekxmpp/plugins/gmail/stanza.py | 101 +++++++++++++++++++++ sleekxmpp/plugins/gmail_notify.py | 149 ------------------------------- 5 files changed, 209 insertions(+), 150 deletions(-) create mode 100644 sleekxmpp/plugins/gmail/__init__.py create mode 100644 sleekxmpp/plugins/gmail/notifications.py create mode 100644 sleekxmpp/plugins/gmail/stanza.py delete mode 100644 sleekxmpp/plugins/gmail_notify.py diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py index 4f9dda21..4ff53ac2 100644 --- a/sleekxmpp/plugins/__init__.py +++ b/sleekxmpp/plugins/__init__.py @@ -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 diff --git a/sleekxmpp/plugins/gmail/__init__.py b/sleekxmpp/plugins/gmail/__init__.py new file mode 100644 index 00000000..a87c78bb --- /dev/null +++ b/sleekxmpp/plugins/gmail/__init__.py @@ -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) diff --git a/sleekxmpp/plugins/gmail/notifications.py b/sleekxmpp/plugins/gmail/notifications.py new file mode 100644 index 00000000..6c8a4a02 --- /dev/null +++ b/sleekxmpp/plugins/gmail/notifications.py @@ -0,0 +1,92 @@ +""" + 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 . + """ + + 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 Result', + MatchXPath('{%s}iq/{%s}%s' % ( + self.xmpp.default_ns, + stanza.MailBox.namespace, + stanza.MailBox.name)), + self._handle_gmail)) + + 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 Result') + self.xmpp.remove_handler('Gmail New Mail') + + def _handle_gmail(self, iq): + mailbox = iq['gmail_results'] + log.info('Gmail: Received%s %s emails', + ' approximately' if mailbox['estimated'] else '', + mailbox['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', iq) + self.check(block=False) + + def check(self, block=True, timeout=None, callback=None): + return self.search(newer=self._last_result_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) diff --git a/sleekxmpp/plugins/gmail/stanza.py b/sleekxmpp/plugins/gmail/stanza.py new file mode 100644 index 00000000..fe56177d --- /dev/null +++ b/sleekxmpp/plugins/gmail/stanza.py @@ -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_results' + 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) diff --git a/sleekxmpp/plugins/gmail_notify.py b/sleekxmpp/plugins/gmail_notify.py deleted file mode 100644 index fc97a2ab..00000000 --- a/sleekxmpp/plugins/gmail_notify.py +++ /dev/null @@ -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() -- cgit v1.2.3 From 7d0d96f94053472202fb2ef8f3f915f5ad3a2286 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 20 Jan 2013 23:59:28 -0800 Subject: Add Google Settings plugin. --- sleekxmpp/plugins/google_settings/__init__.py | 15 ++++ sleekxmpp/plugins/google_settings/settings.py | 65 +++++++++++++++ sleekxmpp/plugins/google_settings/stanza.py | 110 ++++++++++++++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 sleekxmpp/plugins/google_settings/__init__.py create mode 100644 sleekxmpp/plugins/google_settings/settings.py create mode 100644 sleekxmpp/plugins/google_settings/stanza.py diff --git a/sleekxmpp/plugins/google_settings/__init__.py b/sleekxmpp/plugins/google_settings/__init__.py new file mode 100644 index 00000000..ef4b2342 --- /dev/null +++ b/sleekxmpp/plugins/google_settings/__init__.py @@ -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) diff --git a/sleekxmpp/plugins/google_settings/settings.py b/sleekxmpp/plugins/google_settings/settings.py new file mode 100644 index 00000000..470660e3 --- /dev/null +++ b/sleekxmpp/plugins/google_settings/settings.py @@ -0,0 +1,65 @@ +""" + 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.google_settings import stanza + + +log = logging.getLogger(__name__) + + +class GoogleSettings(BasePlugin): + + """ + Google: Gmail Notifications + + Also see . + """ + + 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', + MatchXPath('{%s}iq/%s' % ( + self.xmpp.default_ns, + stanza.UserSettings.tag_name())), + 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(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): + self.xmpp.event('google_settings_change', iq) diff --git a/sleekxmpp/plugins/google_settings/stanza.py b/sleekxmpp/plugins/google_settings/stanza.py new file mode 100644 index 00000000..cc887880 --- /dev/null +++ b/sleekxmpp/plugins/google_settings/stanza.py @@ -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 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') -- cgit v1.2.3 From f34b9399cc3fa8bb87d0271293a891533c8dacfd Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Mon, 21 Jan 2013 01:37:42 -0800 Subject: Simplify Gmail notifications. --- sleekxmpp/plugins/gmail/notifications.py | 27 ++++++--------------------- sleekxmpp/plugins/gmail/stanza.py | 2 +- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/sleekxmpp/plugins/gmail/notifications.py b/sleekxmpp/plugins/gmail/notifications.py index 6c8a4a02..dbc68162 100644 --- a/sleekxmpp/plugins/gmail/notifications.py +++ b/sleekxmpp/plugins/gmail/notifications.py @@ -37,14 +37,6 @@ class Gmail(BasePlugin): register_stanza_plugin(Iq, stanza.MailBox) register_stanza_plugin(Iq, stanza.NewMail) - self.xmpp.register_handler( - Callback('Gmail Result', - MatchXPath('{%s}iq/{%s}%s' % ( - self.xmpp.default_ns, - stanza.MailBox.namespace, - stanza.MailBox.name)), - self._handle_gmail)) - self.xmpp.register_handler( Callback('Gmail New Mail', MatchXPath('{%s}iq/{%s}%s' % ( @@ -56,24 +48,17 @@ class Gmail(BasePlugin): self._last_result_time = None def plugin_end(self): - self.xmpp.remove_handler('Gmail Result') self.xmpp.remove_handler('Gmail New Mail') - def _handle_gmail(self, iq): - mailbox = iq['gmail_results'] - log.info('Gmail: Received%s %s emails', - ' approximately' if mailbox['estimated'] else '', - mailbox['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', iq) - self.check(block=False) + log.info("Gmail: New email!") + iq.reply().send() + self.xmpp.event('gmail_notification') def check(self, block=True, timeout=None, callback=None): - return self.search(newer=self._last_result_time, + 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) diff --git a/sleekxmpp/plugins/gmail/stanza.py b/sleekxmpp/plugins/gmail/stanza.py index fe56177d..e7e308e1 100644 --- a/sleekxmpp/plugins/gmail/stanza.py +++ b/sleekxmpp/plugins/gmail/stanza.py @@ -46,7 +46,7 @@ class GmailQuery(ElementBase): class MailBox(ElementBase): namespace = 'google:mail:notify' name = 'mailbox' - plugin_attrib = 'gmail_results' + plugin_attrib = 'gmail_messages' interfaces = set(['result_time', 'url', 'matched', 'estimate']) def get_matched(self): -- cgit v1.2.3 From 903e641457f30670af36a13b51ef0ab3e611bead Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Mon, 21 Jan 2013 01:38:42 -0800 Subject: Fix issues in Google settings plugin. --- sleekxmpp/plugins/google_settings/settings.py | 13 ++++++++----- sleekxmpp/plugins/google_settings/stanza.py | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/sleekxmpp/plugins/google_settings/settings.py b/sleekxmpp/plugins/google_settings/settings.py index 470660e3..6bd209c7 100644 --- a/sleekxmpp/plugins/google_settings/settings.py +++ b/sleekxmpp/plugins/google_settings/settings.py @@ -10,7 +10,7 @@ import logging from sleekxmpp.stanza import Iq from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import MatchXPath +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 @@ -37,9 +37,7 @@ class GoogleSettings(BasePlugin): self.xmpp.register_handler( Callback('Google Settings', - MatchXPath('{%s}iq/%s' % ( - self.xmpp.default_ns, - stanza.UserSettings.tag_name())), + StanzaPath('iq@type=set/google_settings'), self._handle_settings_change)) def plugin_end(self): @@ -51,7 +49,7 @@ class GoogleSettings(BasePlugin): iq.enable('google_settings') return iq.send(block=block, timeout=timeout, callback=callback) - def update(settings, block=True, timeout=None, callback=None): + def update(self, settings, block=True, timeout=None, callback=None): iq = self.xmpp.Iq() iq['type'] = 'set' iq.enable('google_settings') @@ -62,4 +60,9 @@ class GoogleSettings(BasePlugin): 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) diff --git a/sleekxmpp/plugins/google_settings/stanza.py b/sleekxmpp/plugins/google_settings/stanza.py index cc887880..d8161770 100644 --- a/sleekxmpp/plugins/google_settings/stanza.py +++ b/sleekxmpp/plugins/google_settings/stanza.py @@ -6,7 +6,7 @@ See the file LICENSE for copying permission. """ -from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin +from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin class UserSettings(ElementBase): -- cgit v1.2.3 From 4f9a95b011277ab64a137aceeb4df2e9fc0f1e25 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Mon, 21 Jan 2013 01:39:08 -0800 Subject: Add plugin for Google's nosave feature. --- sleekxmpp/plugins/google_nosave/__init__.py | 15 ++++++ sleekxmpp/plugins/google_nosave/nosave.py | 83 +++++++++++++++++++++++++++++ sleekxmpp/plugins/google_nosave/stanza.py | 59 ++++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 sleekxmpp/plugins/google_nosave/__init__.py create mode 100644 sleekxmpp/plugins/google_nosave/nosave.py create mode 100644 sleekxmpp/plugins/google_nosave/stanza.py diff --git a/sleekxmpp/plugins/google_nosave/__init__.py b/sleekxmpp/plugins/google_nosave/__init__.py new file mode 100644 index 00000000..eba50a35 --- /dev/null +++ b/sleekxmpp/plugins/google_nosave/__init__.py @@ -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) diff --git a/sleekxmpp/plugins/google_nosave/nosave.py b/sleekxmpp/plugins/google_nosave/nosave.py new file mode 100644 index 00000000..1d3b36db --- /dev/null +++ b/sleekxmpp/plugins/google_nosave/nosave.py @@ -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 . + """ + + 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) diff --git a/sleekxmpp/plugins/google_nosave/stanza.py b/sleekxmpp/plugins/google_nosave/stanza.py new file mode 100644 index 00000000..d8701322 --- /dev/null +++ b/sleekxmpp/plugins/google_nosave/stanza.py @@ -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) -- cgit v1.2.3