Permalink
Browse files

fix problem with lowercasing hook name, and implement receive hooks

  • Loading branch information...
jehiah committed Apr 28, 2010
1 parent c5c4128 commit 12f529a927f83b2223c66162ae27533287c5f765
Showing with 202 additions and 34 deletions.
  1. +14 −5 README.txt
  2. +69 −16 app.py
  3. +9 −2 lib.py
  4. +8 −3 model.py
  5. +5 −5 partychat_hooks.py
  6. +49 −3 templates/edit.html
  7. +6 −0 test/test.sh
  8. +21 −0 test/test_post_body.txt
  9. +21 −0 test/test_receive_body.txt
View
@@ -1,11 +1,19 @@
-partychat-hooks
+Partychat-Hooks
===============
-Partychat-hooks is a Google AppEngine application that provides
-http receive and send endpoints for partychat http://partychapp.appspot.com/
+http://partychat-hooks.appspot.com/
- * create a http endpoint that posts into a XMMP conversation
- * sends messages received from a XMMP conversation to a http endpoint
+Partychat-Hooks is a Google AppEngine application that provides
+http post and receive endpoints for partychat http://partychapp.appspot.com/
+
+ * creates a http endpoint that posts into a partychat XMMP conversation
+ * sends messages received from a partychat XMMP conversation to a http endpoint
+
+Development
+===========
+
+To run, place a copy of tornado inside the top level directory
+http://www.tornadoweb.org/
License
=======
@@ -24,3 +32,4 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/
+
View
85 app.py
@@ -1,17 +1,18 @@
import functools
import logging
-import urllib
import tornado.web
import tornado.escape
import tornado.template
from google.appengine.api import users
# from google.appengine.ext import db
-from google.appengine.api import xmpp
+# from google.appengine.api import xmpp
+from google.appengine.api import urlfetch
import lib
import model
+import urllib
def authenticated(method):
"""Decorate methods with this to require that the user be logged in."""
@@ -30,9 +31,37 @@ def wrapper(self, *args, **kwargs):
class XMPPHandler(tornado.web.RequestHandler):
def post(self):
- # LOOKUP from whence this came
- message = xmpp.Message(self.request.body)
- logging.info('got message %s' % message.body)
+ # stanza = self.get_argument('stanza')
+ from_addr = self.get_argument('from')
+ to_addr = self.get_argument('to')
+ body = self.get_argument('body')
+ logging.info('got message %s' % body)
+ # message = xmpp.Message({'from' : from_addr, 'to' : to_addr, 'body' : body})
+
+ token = to_addr.split('@',1)[0]
+ try:
+ jid = lib.lookup_token(token)
+ except:
+ return self.finish('RECEIVE HOOK NOT FOUND')
+
+ for receive_hook in jid.receivehook_set:
+ if receive_hook.endpoint == 'http://example.com/api/receive_endpoint':
+ # skip default
+ continue
+ if not receive_hook.active:
+ continue
+ if lib.match_command(receive_hook.command, body):
+ data = urllib.urlencode({'from':from_addr, 'body': body, 'partychat-hook':jid.token, 'on-behalf-of':jid.user.nickname()})
+ try:
+ urlfetch.fetch(
+ receive_hook.endpoint,
+ payload=data,
+ headers = {'Content-Type' : 'application/x-www-form-urlencoded'},
+ follow_redirects=False
+ )
+ except:
+ logging.exception('failed writing to %s for %s with data %s' % (receive_hook.endpoint, receive_hook.token, data))
+ self.finish('DONE')
class BaseHandler(tornado.web.RequestHandler):
"""Implements Google Accounts authentication methods."""
@@ -67,7 +96,7 @@ def get(self):
@authenticated
def post(self):
- jid = self.get_argument('jid').lower()
+ jid = self.get_argument('jid')
token = lib.get_new_token('jid')
obj = model.JID(jid=jid,
user=self.current_user,
@@ -81,14 +110,14 @@ def get(self, token):
if not token.startswith('H_'):
raise tornado.web.HTTPError(404)
- obj = lib.get_token(token)
+ obj = lib.lookup_token(token)
if not obj:
raise tornado.web.HTTPError(404)
self.render('edit.html', jid=obj)
@authenticated
def post(self, token):
- obj = lib.get_token(token, self.current_user)
+ obj = lib.lookup_token(token, self.current_user)
if not obj:
raise tornado.web.HTTPError(404)
if self.get_argument('action.update_alias', None):
@@ -98,20 +127,44 @@ def post(self, token):
obj.put()
lib.send(obj, '/alias %s' % alias)
- if self.get_argument('action.new_post_hook', None):
+ elif self.get_argument('action.new_post_hook', None):
t = model.PostHook(
token=lib.get_new_token('post'),
jid=obj
)
t.put()
- if self.get_argument('action.update_post_hook', None):
+ elif self.get_argument('action.update_post_hook', None):
hook_token = self.get_argument('token')
if hook_token.startswith('P_'):
- t = lib.get_token(hook_token, self.current_user)
+ t = lib.lookup_token(hook_token, self.current_user)
if t:
t.format = self.get_argument('format')
t.put()
-
+ elif self.get_argument('action.new_receive_hook', None):
+ t = model.ReceiveHook(
+ token=lib.get_new_token('receive'),
+ jid=obj)
+ t.put()
+ elif self.get_argument('action.update_receive_hook', None):
+ hook_token = self.get_argument('token')
+ if hook_token.startswith('R_'):
+ t = lib.lookup_token(hook_token, self.current_user)
+ if t:
+ t.endpoint = self.get_argument('endpoint')
+ t.command = self.get_argument('command') or '*'
+ t.put()
+ elif self.get_argument('action.activate', None):
+ hook_token = self.get_argument('action.activate')
+ t = lib.lookup_token(hook_token, self.current_user)
+ if t:
+ t.active = True
+ t.put()
+ elif self.get_argument('action.deactivate', None):
+ hook_token = self.get_argument('action.deactivate')
+ t = lib.lookup_token(hook_token, self.current_user)
+ if t:
+ t.active = False
+ t.put()
self.redirect('/edit/' + obj.token)
class PostHook(BaseHandler):
@@ -128,8 +181,8 @@ def render_string(self, format, **kwargs):
def get(self, token):
if not token.startswith('P_'):
raise tornado.web.HTTPError(404)
- obj = lib.get_token(token)
- if not obj:
+ obj = lib.lookup_token(token)
+ if not obj or not obj.active:
raise tornado.web.HTTPError(404)
msg = self.render_string(obj.format, post_json=None)
@@ -138,8 +191,8 @@ def get(self, token):
def post(self, token):
if not token.startswith('P_'):
raise tornado.web.HTTPError(404)
- obj = lib.get_token(token)
- if not obj:
+ obj = lib.lookup_token(token)
+ if not obj or not obj.active:
raise tornado.web.HTTPError(404)
post_json = None
View
11 lib.py
@@ -3,8 +3,15 @@
import model
import base64
import logging
+import re
from google.appengine.api import xmpp
-from google.appengine.ext import db
+
+def match_command(command, body):
+ if command == '*' or \
+ body.startswith(command) or \
+ re.findall('^\[[^\]]+\]\s%s' % re.escape(command), body):
+ return True
+ return False
def get_new_token(token_type):
if token_type is 'post':
@@ -18,7 +25,7 @@ def get_new_token(token_type):
token = prefix + base64.b64encode(str(uuid.uuid4()))[:8]
return token
-def get_token(token, user=None):
+def lookup_token(token, user=None):
if token.startswith('P_'):
q = model.PostHook.all()
elif token.startswith('H_'):
View
@@ -12,14 +12,19 @@ class JID(db.Model):
class PostHook(db.Model):
jid = db.ReferenceProperty(JID)
token = db.StringProperty(required=True)
- format = db.TextProperty(required=True, default='{{msg}}')
+ format = db.TextProperty(required=True, default="""
+query_argument:{{get_argument("subject")}}
+query_argument:{{get_argument("msg")}}
+""")
created = db.DateTimeProperty(required=True, auto_now_add=True)
updated = db.DateTimeProperty(auto_now=True)
+ active = db.BooleanProperty(default=True)
class ReceiveHook(db.Model):
jid = db.ReferenceProperty(JID)
token = db.StringProperty(required=True)
- endpoint = db.LinkProperty(required=True)
+ endpoint = db.LinkProperty(required=True, default='http://example.com/api/receive_endpoint')
+ command = db.StringProperty(required=False, default='/command')
+ active = db.BooleanProperty(default=False)
created = db.DateTimeProperty(required=True, auto_now_add=True)
updated = db.DateTimeProperty(auto_now=True)
- command = db.StringProperty(required=False, default='*')
View
@@ -1,23 +1,23 @@
+import os
import tornado.web
import tornado.wsgi
import wsgiref.handlers
-import os
+# application imports
import app
-
class Application(tornado.wsgi.WSGIApplication):
def __init__(self):
app_settings = {
"template_path": os.path.join(os.path.dirname(__file__), "templates"),
"debug" : True
}
handlers = [
- (r"/", app.MainHandler),
+ (r"^/$", app.MainHandler),
(r'/_ah/xmpp/message/chat/', app.XMPPHandler),
(r'^/add$', app.AddJID),
- (r'^/edit/([^/]*)$', app.EditHook),
- (r'^/post/([^/]*)$', app.PostHook),
+ (r'^/edit/(H_[^/]*)$', app.EditHook),
+ (r'^/post/(P_[^/]*)$', app.PostHook),
]
tornado.wsgi.WSGIApplication.__init__(self, handlers, **app_settings)
View
@@ -20,12 +20,18 @@
.example{
font-size:12px;
}
+.active {
+ color:#0b0;
+}
+.inactive {
+ color:#f00;
+}
</style>
{% end %}
{% block body %}
- <h2>Edit Hook</h2>
-
+ <h2>Edit {{escape(jid.token)}}</h2>
+ <p><a href="/">All My Hooks</a></p>
<p>
Created: {{escape(jid.created.strftime("%Y/%m/%d"))}}<br/>
JID: {{escape(jid.jid)}}<br/>
@@ -47,14 +53,22 @@ <h2>Edit Hook</h2>
<hr>
<h3>Post Hooks</h3>
- <p>These HTTP Endpoints will post data to the JID chat session</p>
+ <p>These HTTP Endpoints will post data to the XMMP chat session</p>
<form method="POST" action="/edit/{{escape(jid.token)}}">
<button type="submit" name="action.new_post_hook" value="1">New Post Hook</button>
</form>
{% for post_hook in jid.posthook_set.fetch(100) %}
<div class="post_hook">
+ <form method="POST" action="/edit/{{escape(jid.token)}}">
+ Active:
+ {% if post_hook.active %}
+ <span class="active">YES</span> <button type="submit" name="action.deactivate" value="{{post_hook.token}}">Deactivate</button>
+ {% else %}
+ <span class="inactive">NO</span> <button type="submit" name="action.activate" value="{{post_hook.token}}">Activate</button>
+ {% end %}
+ </form>
HTTP Endpoint: <strong><code>http://partychat-hooks.appspot.com/post/{{escape(post_hook.token)}}</code></strong><br/>
<form method="POST" action="/edit/{{escape(jid.token)}}">
<input type="hidden" value="{{escape(post_hook.token)}}" name="token">
@@ -66,4 +80,36 @@ <h3>Post Hooks</h3>
</div>
{% end %}
+ <hr>
+ <h3>Receive Hooks</h3>
+ <p>These HTTP Endpoints will receive a copy of data from the XMMP chat session</p>
+
+ <form method="POST" action="/edit/{{escape(jid.token)}}">
+ <button type="submit" name="action.new_receive_hook" value="1">New Receive Hook</button>
+ </form>
+
+ {% for receive_hook in jid.receivehook_set.fetch(100) %}
+ <div class="post_hook">
+ <form method="POST" action="/edit/{{escape(jid.token)}}">
+ Active:
+ {% if receive_hook.active %}
+ <span class="active">YES</span> <button type="submit" name="action.deactivate" value="{{receive_hook.token}}">Deactivate</button>
+ {% else %}
+ <span class="inactive">NO</span> <button type="submit" name="action.activate" value="{{receive_hook.token}}">Activate</button>
+ {% end %}
+ </form>
+ <form method="POST" action="/edit/{{escape(jid.token)}}">
+ <input type="hidden" value="{{escape(receive_hook.token)}}" name="token">
+ <label>Command sequence to match against.
+ <em>ie: send messages that start with <code>/todo</code></em><br/>
+ <input type="text" name="command" value="{{escape(receive_hook.command)}}" size="30"></label><br/>
+ <label>HTTP Endpoint <em>partychat will make a <code>HTTP POST</code> with the variable <code>from</code> and <code>body</code></em><br/>
+ <input type="text" name="endpoint" value="{{escape(receive_hook.endpoint)}}" size="100"></label><br/>
+
+ <button type="submit" name="action.update_receive_hook" value="1">Update Receive Hook</button>
+ </form>
+ </div>
+ {% end %}
+
+
{% end %}
View
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+curl -H 'Content-Type: multipart/form-data; boundary="----=_Part_837529_22096737.1272429593698"' --data-binary @test_post_body.txt http://127.0.0.1:8083/_ah/xmpp/message/chat/
+echo "";
+curl -H 'Content-Type: multipart/form-data; boundary="----=_Part_837529_22096737.1272429593698"' --data-binary @test_receive_body.txt http://127.0.0.1:8083/_ah/xmpp/message/chat/
+echo "";
View
@@ -0,0 +1,21 @@
+------=_Part_837529_22096737.1272429593698
+Content-Type: text/plain; charset="UTF-8"
+Content-Disposition: form-data; name="from"
+
+jehiah-test@partychapp.appspotchat.com/bot
+------=_Part_837529_22096737.1272429593698
+Content-Type: text/plain; charset="UTF-8"
+Content-Disposition: form-data; name="to"
+
+h_nmqzogfi@partychat-hooks.appspotchat.com
+------=_Part_837529_22096737.1272429593698
+Content-Type: text/plain; charset="UTF-8"
+Content-Disposition: form-data; name="body"
+
+[jehiah] testing tornado mutlipart/form-data handling
+------=_Part_837529_22096737.1272429593698
+Content-Type: text/xml; charset="UTF-8"
+Content-Disposition: form-data; name="stanza"
+
+<cli:message type="chat" from="jehiah-test@partychapp.appspotchat.com/bot" to="h_nmqzogfi@partychat-hooks.appspotchat.com" xmlns:cli="jabber:client"><cli:body>[jehiah] testing tornado mutlipart/form-data handling</cli:body></cli:message>
+------=_Part_837529_22096737.1272429593698--
Oops, something went wrong.

0 comments on commit 12f529a

Please sign in to comment.