From 629f6e76a9c003ef8befdfdf023f2e5b9eadc718 Mon Sep 17 00:00:00 2001
From: Lance stout <lancestout@gmail.com>
Date: Mon, 31 May 2010 13:24:14 -0400
Subject: Added implementation and tests for XEP-0085 - Chat State
 Notifications.

Chat states may be set using:

msg['chat_state'].active()
msg['chat_state'].composing()
msg['chat_state'].gone()
msg['chat_state'].inactive()
msg['chat_state'].paused()

Checking a chat state can be done with either:

msg['chat_state'].getState()
msg['chat_state'].name

When a message with a chat state is receieved, the following events
may occur:

chatstate_active
chatstate_composing
chatstate_gone
chatstate_inactive
chatstate_paused

where the event data is the message stanza. Note that currently these
events are also triggered for messages sent by SleekXMPP, not just those
received.
---
 sleekxmpp/plugins/__init__.py |   2 +-
 sleekxmpp/plugins/xep_0085.py | 100 ++++++++++++++++++++++++++++++++++++++++++
 tests/test_chatstates.py      |  47 ++++++++++++++++++++
 3 files changed, 148 insertions(+), 1 deletion(-)
 create mode 100644 sleekxmpp/plugins/xep_0085.py
 create mode 100644 tests/test_chatstates.py

diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py
index 1868365e..674c3de2 100644
--- a/sleekxmpp/plugins/__init__.py
+++ b/sleekxmpp/plugins/__init__.py
@@ -17,4 +17,4 @@
     along with SleekXMPP; if not, write to the Free Software
     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 """
-__all__ = ['xep_0004', 'xep_0030', 'xep_0045', 'xep_0050', 'xep_0078', 'xep_0092', 'xep_0199', 'gmail_notify', 'xep_0060']
+__all__ = ['xep_0004', 'xep_0030', 'xep_0045', 'xep_0050', 'xep_0078', 'xep_0085', 'xep_0092', 'xep_0199', 'gmail_notify', 'xep_0060']
diff --git a/sleekxmpp/plugins/xep_0085.py b/sleekxmpp/plugins/xep_0085.py
new file mode 100644
index 00000000..e183ec27
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0085.py
@@ -0,0 +1,100 @@
+"""
+    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.txt for copying permissio
+"""
+
+import logging
+from . import base
+from .. xmlstream.handler.callback import Callback
+from .. xmlstream.matcher.xpath import MatchXPath
+from .. xmlstream.stanzabase import ElementBase, ET, JID
+from .. stanza.message import Message
+
+
+class ChatState(ElementBase):
+    namespace = 'http://jabber.org/protocol/chatstates'
+    plugin_attrib = 'chat_state'
+    interface = set(('state',))
+    states = set(('active', 'composing', 'gone', 'inactive', 'paused'))
+    
+    def active(self):
+        self.setState('active')
+        
+    def composing(self):
+        self.setState('composing')
+
+    def gone(self):
+        self.setState('gone')
+
+    def inactive(self):
+        self.setState('inactive')
+
+    def paused(self):
+        self.setState('paused')
+
+    def setState(self, state):
+        if state in self.states:
+            self.name = state
+            self.xml.tag = state
+            self.xml.attrib['xmlns'] = self.namespace
+
+    def getState(self):
+        return self.name
+
+# In order to match the various chat state elements,
+# we need one stanza object per state, even though
+# they are all the same except for the initial name
+# value. Do not depend on the type of the chat state
+# stanza object for the actual state.
+
+class Active(ChatState):
+    name = 'active'
+class Composing(ChatState):
+    name = 'composing'
+class Gone(ChatState):
+    name = 'gone'
+class Inactive(ChatState):
+    name = 'inactive'
+class Paused(ChatState):
+    name = 'paused'
+
+
+class xep_0085(base.base_plugin):
+    """
+    XEP-0085 Chat State Notifications
+    """
+    
+    def plugin_init(self):
+        self.xep = '0085'
+        self.description = 'Chat State Notifications'
+        
+        handlers = [('Active Chat State', 'active'),
+                    ('Composing Chat State', 'composing'),
+                    ('Gone Chat State', 'gone'),
+                    ('Inactive Chat State', 'inactive'),
+                    ('Paused Chat State', 'paused')]
+        for handler in handlers:
+            self.xmpp.registerHandler(
+                Callback(handler[0], 
+                         MatchXPath("{%s}message/{%s}%s" % (self.xmpp.default_ns, 
+                                                            ChatState.namespace,
+                                                            handler[1])), 
+                         self._handleChatState))
+
+        self.xmpp.stanzaPlugin(Message, Active)
+        self.xmpp.stanzaPlugin(Message, Composing)
+        self.xmpp.stanzaPlugin(Message, Gone)
+        self.xmpp.stanzaPlugin(Message, Inactive)
+        self.xmpp.stanzaPlugin(Message, Paused)
+        
+    def post_init(self):
+        base.base_plugin.post_init(self)
+        self.xmpp.plugin['xep_0030'].add_feature('http://jabber.org/protocol/chatstates')
+        
+    def _handleChatState(self, msg):
+        state = msg['chat_state'].name
+        logging.debug("Chat State: %s, %s" % (state, msg['from'].jid))
+        self.xmpp.event('chatstate_%s' % state, msg)
diff --git a/tests/test_chatstates.py b/tests/test_chatstates.py
new file mode 100644
index 00000000..8878e318
--- /dev/null
+++ b/tests/test_chatstates.py
@@ -0,0 +1,47 @@
+import unittest
+from xml.etree import cElementTree as ET
+from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath
+from . import xmlcompare
+
+import sleekxmpp.plugins.xep_0085 as cs
+
+def stanzaPlugin(stanza, plugin):                                                                       
+	stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin                                             
+	stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin 
+
+class testchatstates(unittest.TestCase):
+
+	def setUp(self):
+		self.cs = cs
+		stanzaPlugin(self.cs.Message, self.cs.Active)
+		stanzaPlugin(self.cs.Message, self.cs.Composing)
+		stanzaPlugin(self.cs.Message, self.cs.Gone)
+		stanzaPlugin(self.cs.Message, self.cs.Inactive)
+		stanzaPlugin(self.cs.Message, self.cs.Paused)
+
+	def try2Methods(self, xmlstring, msg):
+		msg2 = self.cs.Message(None, self.cs.ET.fromstring(xmlstring))
+		self.failUnless(xmlstring == str(msg) == str(msg2), 
+				"Two methods for creating stanza don't match")
+        
+	def testCreateChatState(self):
+		"""Testing creating chat states."""
+		xmlstring = """<message><%s xmlns="http://jabber.org/protocol/chatstates" /></message>"""
+
+		msg = self.cs.Message()
+		msg['chat_state'].active()
+		self.try2Methods(xmlstring % 'active', msg)
+
+		msg['chat_state'].composing()
+		self.try2Methods(xmlstring % 'composing', msg)
+
+		msg['chat_state'].gone()
+		self.try2Methods(xmlstring % 'gone', msg)
+
+		msg['chat_state'].inactive()
+		self.try2Methods(xmlstring % 'inactive', msg)
+
+		msg['chat_state'].paused()
+		self.try2Methods(xmlstring % 'paused', msg)
+
+suite = unittest.TestLoader().loadTestsFromTestCase(testchatstates)
-- 
cgit v1.2.3