diff --git a/sleekxmpp/plugins/xep_0012.py b/sleekxmpp/plugins/xep_0012.py deleted file mode 100644 index 01fb60a8..00000000 --- a/sleekxmpp/plugins/xep_0012.py +++ /dev/null @@ -1,122 +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 datetime import datetime -import logging - -from sleekxmpp.plugins import BasePlugin, register_plugin -from sleekxmpp import Iq -from sleekxmpp.xmlstream.handler.callback import Callback -from sleekxmpp.xmlstream.matcher.xpath import MatchXPath -from sleekxmpp.xmlstream import ElementBase, ET, JID, register_stanza_plugin - - -log = logging.getLogger(__name__) - - -class LastActivity(ElementBase): - name = 'query' - namespace = 'jabber:iq:last' - plugin_attrib = 'last_activity' - interfaces = set(('seconds', 'status')) - - def get_seconds(self): - return int(self._get_attr('seconds')) - - def set_seconds(self, value): - self._set_attr('seconds', str(value)) - - def get_status(self): - return self.xml.text - - def set_status(self, value): - self.xml.text = str(value) - - def del_status(self): - self.xml.text = '' - - -class XEP_0012(BasePlugin): - - """ - XEP-0012 Last Activity - """ - - name = 'xep_0012' - description = 'XEP-0012: Last Activity' - dependencies = set(['xep_0030']) - - def plugin_init(self): - self.xep = "0012" - - self.xmpp.registerHandler( - Callback('Last Activity', - MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns, - LastActivity.namespace)), - self.handle_last_activity_query)) - register_stanza_plugin(Iq, LastActivity) - - self.xmpp.add_event_handler('last_activity_request', self.handle_last_activity) - - if self.xmpp.is_component: - # We are a component, so we track the uptime - self.xmpp.add_event_handler("session_start", self._reset_uptime) - self._start_datetime = datetime.now() - self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:last') - - def _reset_uptime(self, event): - self._start_datetime = datetime.now() - - def handle_last_activity_query(self, iq): - if iq['type'] == 'get': - log.debug("Last activity requested by %s", iq['from']) - self.xmpp.event('last_activity_request', iq) - elif iq['type'] == 'result': - log.debug("Last activity result from %s", iq['from']) - self.xmpp.event('last_activity', iq) - - def handle_last_activity(self, iq): - jid = iq['from'] - - if self.xmpp.is_component: - # Send the uptime - result = LastActivity() - td = (datetime.now() - self._start_datetime) - result['seconds'] = td.seconds + td.days * 24 * 3600 - reply = iq.reply().setPayload(result.xml).send() - else: - barejid = JID(jid).bare - if barejid in self.xmpp.roster and ( self.xmpp.roster[barejid]['subscription'] in ('from', 'both') or - barejid == self.xmpp.boundjid.bare ): - # We don't know how to calculate it - iq.reply().error().setPayload(iq['last_activity'].xml) - iq['error']['code'] = '503' - iq['error']['type'] = 'cancel' - iq['error']['condition'] = 'service-unavailable' - iq.send() - else: - iq.reply().error().setPayload(iq['last_activity'].xml) - iq['error']['code'] = '403' - iq['error']['type'] = 'auth' - iq['error']['condition'] = 'forbidden' - iq.send() - - def get_last_activity(self, jid): - """Query the LastActivity of jid and return it in seconds""" - iq = self.xmpp.makeIqGet() - query = LastActivity() - iq.append(query.xml) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.boundjid.full - id = iq.get('id') - result = iq.send() - return result['last_activity']['seconds'] - - -xep_0012 = XEP_0012 -register_plugin(XEP_0012) diff --git a/sleekxmpp/plugins/xep_0012/__init__.py b/sleekxmpp/plugins/xep_0012/__init__.py new file mode 100644 index 00000000..6b778fc1 --- /dev/null +++ b/sleekxmpp/plugins/xep_0012/__init__.py @@ -0,0 +1,19 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.plugins.base import register_plugin + +from sleekxmpp.plugins.xep_0012.stanza import LastActivity +from sleekxmpp.plugins.xep_0012.last_activity import XEP_0012 + + +register_plugin(XEP_0012) + + +# Retain some backwards compatibility +xep_0004 = XEP_0012 diff --git a/sleekxmpp/plugins/xep_0012/last_activity.py b/sleekxmpp/plugins/xep_0012/last_activity.py new file mode 100644 index 00000000..b71b6907 --- /dev/null +++ b/sleekxmpp/plugins/xep_0012/last_activity.py @@ -0,0 +1,152 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import logging +from datetime import datetime, timedelta + +from sleekxmpp.plugins import BasePlugin, register_plugin +from sleekxmpp import Iq +from sleekxmpp.exceptions import XMPPError +from sleekxmpp.xmlstream import JID, register_stanza_plugin +from sleekxmpp.xmlstream.handler import Callback +from sleekxmpp.xmlstream.matcher import StanzaPath +from sleekxmpp.plugins.xep_0012 import stanza, LastActivity + + +log = logging.getLogger(__name__) + + +class XEP_0012(BasePlugin): + + """ + XEP-0012 Last Activity + """ + + name = 'xep_0012' + description = 'XEP-0012: Last Activity' + dependencies = set(['xep_0030']) + stanza = stanza + + def plugin_init(self): + register_stanza_plugin(Iq, LastActivity) + + self._last_activities = {} + + self.xmpp.registerHandler( + Callback('Last Activity', + StanzaPath('iq@type=get/last_activity'), + self._handle_get_last_activity)) + + self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:last') + + self.api.register(self._default_get_last_activity, + 'get_last_activity', + default=True) + self.api.register(self._default_set_last_activity, + 'set_last_activity', + default=True) + self.api.register(self._default_del_last_activity, + 'del_last_activity', + default=True) + + def begin_idle(self, jid=None, status=None): + self.set_last_activity(jid, 0, status) + + def end_idle(self, jid=None): + self.del_last_activity(jid) + + def start_uptime(self, status=None): + self.set_last_activity(jid, 0, status) + + def set_last_activity(self, jid=None, seconds=None, status=None): + self.api['set_last_activity'](jid, args={ + 'seconds': seconds, + 'status': status}) + + def del_last_activity(self, jid): + self.api['del_last_activity'](jid) + + def get_last_activity(self, jid, local=False, ifrom=None, block=True, + timeout=None, callback=None): + if jid is not None and not isinstance(jid, JID): + jid = JID(jid) + + if self.xmpp.is_component: + if jid.domain == self.xmpp.boundjid.domain: + local = True + else: + if str(jid) == str(self.xmpp.boundjid): + local = True + jid = jid.full + + if local or jid in (None, ''): + log.debug("Looking up local last activity data for %s", jid) + return self.api['get_last_activity'](jid, None, ifrom, None) + + iq = self.xmpp.Iq() + iq['from'] = ifrom + iq['to'] = jid + iq['type'] = 'get' + iq.enable('last_activity') + return iq.send(timeout=timeout, + block=block, + callback=callback) + + def _handle_get_last_activity(self, iq): + log.debug("Received last activity query from " + \ + "<%s> to <%s>.", iq['from'], iq['to']) + reply = self.api['get_last_activity'](iq['to'], None, iq['from'], iq) + reply.send() + + # ================================================================= + # Default in-memory implementations for storing last activity data. + # ================================================================= + + def _default_set_last_activity(self, jid, node, ifrom, data): + seconds = data.get('seconds', None) + if seconds is None: + seconds = 0 + + status = data.get('status', None) + if status is None: + status = '' + + self._last_activities[jid] = { + 'seconds': datetime.now() - timedelta(seconds=seconds), + 'status': status} + + def _default_del_last_activity(self, jid, node, ifrom, data): + if jid in self._last_activities: + del self._last_activities[jid] + + def _default_get_last_activity(self, jid, node, ifrom, iq): + if not isinstance(iq, Iq): + reply = self.xmpp.Iq() + else: + iq.reply() + reply = iq + + if jid not in self._last_activities: + raise XMPPError('service-unavailable') + + bare = JID(jid).bare + + if bare != self.xmpp.boundjid.bare: + if bare in self.xmpp.roster[jid]: + sub = self.xmpp.roster[jid][bare]['subscription'] + if sub not in ('from', 'both'): + raise XMPPError('forbidden') + + td = datetime.now() - self._last_activities[jid]['seconds'] + seconds = td.seconds + td.days * 24 * 3600 + status = self._last_activities[jid]['status'] + + reply['last_activity']['seconds'] = seconds + reply['last_activity']['status'] = status + + return reply diff --git a/sleekxmpp/plugins/xep_0012/stanza.py b/sleekxmpp/plugins/xep_0012/stanza.py new file mode 100644 index 00000000..079865b9 --- /dev/null +++ b/sleekxmpp/plugins/xep_0012/stanza.py @@ -0,0 +1,32 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.xmlstream import ElementBase + + +class LastActivity(ElementBase): + + name = 'query' + namespace = 'jabber:iq:last' + plugin_attrib = 'last_activity' + interfaces = set(('seconds', 'status')) + + def get_seconds(self): + return int(self._get_attr('seconds')) + + def set_seconds(self, value): + self._set_attr('seconds', str(value)) + + def get_status(self): + return self.xml.text + + def set_status(self, value): + self.xml.text = str(value) + + def del_status(self): + self.xml.text = ''