-
-
Save rociiu/355163 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# 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 | |
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment