[telepathy-ashes/master] Added support for !commands in text chats.

David Laban david.laban at collabora.co.uk
Tue Oct 13 11:57:53 PDT 2009


Currently implemented are:
!callme !delay !help !videome
---
 ashes/tools/commands.py    |  148 ++++++++++++++++++++++++++++++++++++++++++++
 ashes/tools/dispatchers.py |    8 ++-
 ashes/tools/echo_bot.py    |    3 +-
 ashes/tools/presence.py    |    3 +-
 4 files changed, 158 insertions(+), 4 deletions(-)
 create mode 100644 ashes/tools/commands.py

diff --git a/ashes/tools/commands.py b/ashes/tools/commands.py
new file mode 100644
index 0000000..1b2f8fe
--- /dev/null
+++ b/ashes/tools/commands.py
@@ -0,0 +1,148 @@
+import re
+import time
+import traceback
+import inspect
+import types
+
+import gobject
+import telepathy
+
+from bases import ChannelListener
+from helper_functions import (get_connections, get_property,
+        green, red, printer, rpartial)
+
+from text import TextChannelEchoer
+
+class CommandExecutor(TextChannelEchoer):
+    """
+    Handles !commands.
+
+    FIXME: The flow control in this whole class is really quite ugly.
+    """
+
+    def Received(self, id, timestamp, sender, type, flags, text):
+        """Handles Channel.Type.Text.Received() and sends it back."""
+        self.handle_message_and_reply(id, timestamp, sender, type, flags, text)
+        self.channel[telepathy.CHANNEL_TYPE_TEXT].AcknowledgePendingMessages([id])
+
+    def handle_message_and_reply(self, id, timestamp, sender, type, flags, text):
+        """
+        Dispatches the command to the correct function and sends back an appropriate reply.
+        """
+        print "Message received:", text
+        if text.startswith('!'):
+            reply = self.execute_command(id, timestamp, sender, type, flags, text)
+        else:
+            reply = self.identify(timestamp, text)
+        print "Responding:", reply
+        self.channel[telepathy.CHANNEL_TYPE_TEXT].Send(type, reply)
+
+    def execute_command(self, id, timestamp, sender, type, flags, text):
+        """
+        Dispatches the command to the correct function.
+        """
+        command_name = text.partition(" ")[0][1:]
+        method = getattr(self, "do_" + command_name, None)
+        if isinstance(method, types.MethodType):
+            try:
+                reply = method(id, timestamp, sender, type, flags, text)
+            except:
+                reply = "An error occurred:\n" + traceback.format_exc()
+        else:
+            reply =  'Invalid command: "%s".' % command_name
+
+        if not reply:
+            reply = self.identify(timestamp, text)
+        return reply
+
+    def get_help_for(self, command_name):
+        """
+        Returns the specific help for command_name.
+        """
+        method = getattr(self, "do_" + command_name, None)
+        if isinstance(method, types.MethodType):
+            return inspect.getdoc(method)
+        else:
+            return 'Invalid command: "%s".\n%s' % (command_name, self.get_help())
+
+    def get_help(self):
+        """
+        Returns the first non-blank line of help for each command.
+        """
+        help_strings = ["The following commands are valid.",
+                        "Type !help command for more details"]
+        command_names = [name for name in dir(self) if name.startswith('do_')]
+        for name in command_names:
+            method = getattr(self, name)
+            if isinstance(method, types.MethodType):
+                help = inspect.getdoc(method).strip().split('\n')[0]
+                help_strings.append(help)
+        return '\n'.join(help_strings)
+
+    def do_help(self, id, timestamp, sender, type, flags, text):
+        """
+        !help [command]
+
+        Prints help on command or the list of all commands.
+        """
+        help_, _, arg = text.partition(' ')
+        assert help_ == "!help"
+        if arg.strip(): # ignore whitespace
+            return self.get_help_for(arg)
+        else:
+            return self.get_help()
+
+    def do_delay(self, id, timestamp, sender, type, flags, text):
+        """
+        !delay timeout message
+
+        Pretends that message actually arrived timeout seconds later than it did.
+        """
+        delay_, _, args = text.partition(' ')
+        assert delay_ == "!delay"
+        timeout, _, message = args.strip().partition(' ')
+        timeout = int(timeout)
+        gobject.timeout_add_seconds(timeout, self.handle_message_and_reply,
+             id, timestamp+timeout, sender, type, flags, message)
+
+
+    def do_callme(self, id, timestamp, sender, type, flags, text):
+        """
+        !callme
+
+        Opens a voice call to whoever sent this command.
+        """
+        self.make_call(target, True, False)
+        return "Please wait."
+
+    def do_videome(self, id, timestamp, sender, type, flags, text):
+        """
+        !videome
+
+        Opens a video call to whoever sent this command.
+        """
+        self.make_call(target, True, True)
+        return "Please wait."
+
+        
+
+    def make_call(self, target, want_audio, want_video):
+        """
+        Makes a call to target with audio and video if wanted.
+        """
+        request = {
+            'org.freedesktop.Telepathy.Channel.ChannelType':
+                'org.freedesktop.Telepathy.Channel.Type.StreamedMedia',
+            'org.freedesktop.Telepathy.Channel.TargetHandleType':
+                1,
+            'org.freedesktop.Telepathy.Channel.TargetHandle':
+                target,
+            'org.freedesktop.Telepathy.Channel.Type.StreamedMedia.FUTURE.InitialAudio':
+                want_audio,
+            'org.freedesktop.Telepathy.Channel.Type.StreamedMedia.FUTURE.InitialVideo':
+                want_video,
+            }
+        self.connection[telepathy.interfaces.CONNECTION_INTERFACE_REQUESTS
+            ].CreateChannel(request)
+
+
diff --git a/ashes/tools/dispatchers.py b/ashes/tools/dispatchers.py
index 0e2055a..43b9ad9 100644
--- a/ashes/tools/dispatchers.py
+++ b/ashes/tools/dispatchers.py
@@ -121,8 +121,12 @@ class ChannelDispatcher(ConnectionListener):
         # Don't be putting it in any libraries.
         if handle_type == telepathy.HANDLE_TYPE_CONTACT:
             if re.match(self.contact_regexp, id):
-                assert not requested, "We shouldn't be creating any new channels."
-                print "incoming channel", id, 'matching', self.contact_regexp
+                #assert not requested, "We shouldn't be creating any new channels."
+                if requested:
+                    print "Outgoing channel",
+                else:
+                    print "Incoming channel",
+                print id, 'matching', self.contact_regexp
             else:
                 print id, 'does not match', self.contact_regexp
                 return
diff --git a/ashes/tools/echo_bot.py b/ashes/tools/echo_bot.py
index f44f893..5594714 100644
--- a/ashes/tools/echo_bot.py
+++ b/ashes/tools/echo_bot.py
@@ -19,6 +19,7 @@ from media_echoer import MediaChannelEchoer, IceEchoer
 from presence import PresenceEchoer, Onlineifier
 from groups import ContactSubscriber
 from text import TextChannelEchoer
+from commands import CommandExecutor
 from file_transfer import FileTransferEchoer
 #TODO: wrap everything to 80 chars so that I can hack on my eeepc.
 
@@ -36,7 +37,7 @@ class EchoBot(  ContactSubscriber,
     """
     # Using python's name mangling to avoid name collisions.
     __channel_handler_classes = [MediaChannelEchoer,
-        TextChannelEchoer, FileTransferEchoer]
+        CommandExecutor, FileTransferEchoer]
 
 
 
diff --git a/ashes/tools/presence.py b/ashes/tools/presence.py
index 90db60b..51c3897 100644
--- a/ashes/tools/presence.py
+++ b/ashes/tools/presence.py
@@ -25,7 +25,8 @@ class Onlineifier(ConnectionListener):
     def StatusChanged(self, status, reason=0):
         if status == 0: #Connected
             print "Setting status to online"
-            self.connection[self.SIMPLE_PRESENCE].SetPresence('available', '')
+            self.connection[self.SIMPLE_PRESENCE].SetPresence('available',
+                'Type !help in a conversation for more info.')
         # We know that ConnectionListener listens to this signal to detect
         # disconnects. Other mixin classes MAY also override this.
         super(Onlineifier, self).StatusChanged(status, reason)
-- 
1.5.6.5




More information about the telepathy-commits mailing list