Skip to content

Instantly share code, notes, and snippets.

@WorldMaker
Created September 23, 2009 21:09
Show Gist options
  • Select an option

  • Save WorldMaker/192277 to your computer and use it in GitHub Desktop.

Select an option

Save WorldMaker/192277 to your computer and use it in GitHub Desktop.

Revisions

  1. WorldMaker revised this gist Sep 23, 2009. 1 changed file with 63 additions and 0 deletions.
    63 changes: 63 additions & 0 deletions textapp.py
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,66 @@
    #
    # textapp -- Combined SMS and IM dispatching
    # Copyright 2009 Max Battcher. All Rights Reserved.
    #
    # Microsoft Public License (Ms-PL)
    #
    # This license governs use of the accompanying software. If you use the
    # software, you accept this license. If you do not accept the license,
    # do not use the software.
    #
    # 1. Definitions
    #
    # The terms “reproduce,” “reproduction,” “derivative works,” and
    # “distribution” have the same meaning here as under U.S. copyright
    # law. A “contribution” is the original software, or any additions or
    # changes to the software. A “contributor” is any person that
    # distributes its contribution under this license. “Licensed patents”
    # are a contributor’s patent claims that read directly on its
    # contribution.
    #
    # 2. Grant of Rights
    #
    # (A) Copyright Grant- Subject to the terms of this license, including
    # the license conditions and limitations in section 3, each contributor
    # grants you a non-exclusive, worldwide, royalty-free copyright license
    # to reproduce its contribution, prepare derivative works of its
    # contribution, and distribute its contribution or any derivative works
    # that you create.
    #
    # (B) Patent Grant- Subject to the terms of this license, including the
    # license conditions and limitations in section 3, each contributor
    # grants you a non-exclusive, worldwide, royalty-free license under its
    # licensed patents to make, have made, use, sell, offer for sale,
    # import, and/or otherwise dispose of its contribution in the software
    # or derivative works of the contribution in the software.
    #
    # 3. Conditions and Limitations
    #
    # (A) No Trademark License- This license does not grant you rights to
    # use any contributors’ name, logo, or trademarks.
    #
    # (B) If you bring a patent claim against any contributor over patents
    # that you claim are infringed by the software, your patent license from
    # such contributor to the software ends automatically.
    #
    # (C) If you distribute any portion of the software, you must retain all
    # copyright, patent, trademark, and attribution notices that are present
    # in the software.
    #
    # (D) If you distribute any portion of the software in source code form,
    # you may do so only under this license by including a complete copy of
    # this license with your distribution. If you distribute any portion of
    # the software in compiled or object code form, you may only do so under
    # a license that complies with this license.
    #
    # (E) The software is licensed “as-is.” You bear the risk of using it.
    # The contributors give no express warranties, guarantees or conditions.
    # You may have additional consumer rights under your local laws which
    # this license cannot change. To the extent permitted under your local
    # laws, the contributors exclude the implied warranties of
    # merchantability, fitness for a particular purpose and
    # non-infringement.
    #
    from google.appengine.api import xmpp
    from google.appengine.ext.webapp.xmpp_handlers import BaseHandler
    from models import Player
  2. @invalid-email-address Anonymous created this gist Sep 23, 2009.
    224 changes: 224 additions & 0 deletions textapp.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,224 @@
    from google.appengine.api import xmpp
    from google.appengine.ext.webapp.xmpp_handlers import BaseHandler
    from models import Player
    from webappfb import FacebookRequestHandler
    import logging
    import re

    class Error(Exception):
    """Text application error base class."""
    pass

    class NoHandlerError(Error):
    """No matching regex/handler error."""
    pass

    class UnregisteredJidError(Error):
    """Unregisted JID."""
    pass

    # Based on, for instance, Django's RegexURLPattern or webapps WSGIApplication
    class TextApplication(object):
    def __init__(self, mapping):
    compiled = []
    for regex, handler in mapping:
    if not regex.startswith('^'):
    regex = '^' + regex
    if not regex.endswith('$'):
    regex = regex + '$'
    compiled.append((re.compile(regex, re.IGNORECASE | re.UNICODE),
    handler))
    self._mapping = compiled

    def __call__(self, message):
    for regex, handler in self._mapping:
    match = regex.match(message.body.strip())
    if match:
    # If the groups are named, use kwargs, otherwise args
    args, kwargs = (), match.groupdict()
    if not kwargs:
    args = match.groups()
    return handler(message, *args, **kwargs)
    raise NoHandlerError

    # Borrowed from google.appengine.api.xmpp, replaced __'s for subclasses
    class Message(object):
    """Encapsulates an XMPP message received by the application."""

    def __init__(self, vars):
    """Constructs a new XMPP Message from an HTTP request.
    Args:
    vars: A dict-like object to extract message arguments from.
    """
    try:
    self._sender = vars["from"]
    self._to = vars["to"]
    self._body = vars["body"]
    except KeyError, e:
    raise xmpp.InvalidMessageError(e[0])
    self._command = None
    self._arg = None

    @property
    def sender(self):
    return self._sender

    @property
    def to(self):
    return self._to

    @property
    def body(self):
    return self._body

    def __parse_command(self):
    if self._arg != None:
    return

    body = self._body
    if body.startswith('\\'):
    body = '/' + body[1:]

    self._arg = ''
    if body.startswith('/'):
    parts = body.split(' ', 1)
    self._command = parts[0][1:]
    if len(parts) > 1:
    self._arg = parts[1].strip()
    else:
    self._arg = self._body.strip()

    @property
    def command(self):
    self._parse_command()
    return self._command

    @property
    def arg(self):
    self._parse_command()
    return self._arg

    def reply(self, body, message_type=xmpp.MESSAGE_TYPE_CHAT, raw_xml=False,
    send_message=xmpp.send_message):
    """Convenience function to reply to a message.
    Args:
    body: str: The body of the message
    message_type, raw_xml: As per send_message.
    send_message: Used for testing.
    Returns:
    A status code as per send_message.
    Raises:
    See send_message.
    """
    return send_message([self.sender], body, from_jid=self.to,
    message_type=message_type, raw_xml=raw_xml)

    class FacebookXmppMessage(Message):
    def __init__(self, vars, facebook):
    self._facebook = facebook
    super(FacebookXmppMessage, self).__init__(vars)
    barejid = self._sender
    slash = barejid.find('/')
    if slash >= 0:
    barejid = barejid[:slash]
    players = list(Player.all().filter('jid =', barejid))
    self._senderuid = None
    if len(players) == 1:
    self._senderuid = players[0].uid

    @property
    def facebook(self):
    return self._facebook

    @property
    def senderuid(self):
    if self._senderuid is None:
    raise UnregisteredJidError
    return self._senderuid

    class FacebookSmsMessage(FacebookXmppMessage):
    def __init__(self, vars, facebook):
    self._facebook = facebook
    self._sender = 'facebook-sms'
    self._to = 'facebook-sms'
    try:
    self._sid = vars['fb_sig_sms_sid']
    self._senderuid = vars['fb_sig_user']
    self._body = vars['fb_sig_message']
    except KeyError, e:
    raise xmpp.InvalidMessageError(e[0])
    self._command = None
    self._arg = None

    def reply(self, body, **kwargs):
    return self.facebook.sms.send(self._senderuid,
    body,
    self._sid,
    False,
    )

    class XmppHandler(BaseHandler):
    def message_received(self, message):
    self.application(message)

    class FacebookXmppHandler(FacebookRequestHandler):
    def handle_exception(self, exception, debug_mode):
    if self.message:
    if isinstance(exception, UnregisteredJidError):
    self.message.reply("""You need to register first:
    http://apps.facebook.com/enlark-assassins/my/settings""")
    elif isinstance(exception, NoHandlerError):
    self.message.reply("Unrecognized command.")
    else:
    self.message.reply('An error occurred processing your message.')
    else:
    super(FacebookXmppHandler, self).handle_exception(exception, debug_mode)

    def post(self):
    if self.redirecting: return # Unlikely, but...

    try:
    self.message = FacebookXmppMessage(self.request.POST,
    self.facebook)
    except xmpp.InvalidMessageError, e:
    logging.error("Invalid XMPP request: %s", e[0])
    return
    reply = self.application(self.message)
    if reply:
    self.message.reply(reply)

    class FacebookSmsHandler(FacebookRequestHandler):
    def canvas(self):
    raise NotImplementedError()

    def handle_exception(self, exception, debug_mode):
    if self.message:
    if isinstance(exception, NoHandlerError):
    self.message.reply('Unrecognized command.')
    else:
    self.message.reply('An error occurred processing your message.')
    else:
    super(FacebookSmsHandler, self).handle_exception(exception, debug_mode)

    def post(self):
    if self.redirecting: return

    sms = self.request.get('fb_sig_sms')
    if sms and int(sms) == 1:
    try:
    self.message = FacebookSmsMessage(self.request.POST,
    self.facebook)
    except xmpp.InvalidMessageError, e:
    logging.error("Invalid SMS request: %s", e[0])
    return
    reply = self.application(self.message)
    if reply:
    self.message.reply(reply)
    else:
    self.canvas()

    # vim: ai et ts=4 sts=4 sw=4