This commit is contained in:
Mark Hammond 2010-09-02 16:29:21 +10:00
Родитель 8969339636
Коммит fa48a95884
4 изменённых файлов: 380 добавлений и 6 удалений

Просмотреть файл

@ -0,0 +1,124 @@
import logging
import datetime
import json
import urllib
from pylons import config, request, response, session
from pylons.controllers.util import abort, redirect
from pylons.decorators.util import get_pylons
from linkdrop.lib.base import BaseController
from linkdrop.lib.helpers import json_exception_response, api_response, api_entry, api_arg
from linkdrop.lib.oauth.base import get_oauth_config
from linkdrop.model.meta import Session
from linkdrop.model import Account, History
from linkdrop.model.types import UTCDateTime
from sqlalchemy.orm.exc import NoResultFound
log = logging.getLogger(__name__)
class SendController(BaseController):
"""
Send
====
The 'send' namespace is used to send updates to our supported services.
"""
__api_controller__ = True # for docs
def _handle_auth_failure(self, reason):
try:
redirect(request.POST['end_point_auth_failure'])
except KeyError:
abort(401, reason)
@api_response
@json_exception_response
def send(self):
# If we don't have a userkey in our session we bail early with a
# 401
userkey = session.get('userkey')
if not userkey:
self._handle_auth_failure('no session data')
try:
domain = request.params.get('domain')
message = request.params['message']
except KeyError, what:
raise ValueError("'%s' request param is not optional" % (what,))
# even if we have a session key, we must have an account for that
# user for the specified domain.
try:
acct = Session.query(Account).filter_by(userkey=userkey, domain=domain).one()
except NoResultFound:
self._handle_auth_failure("no account for that domain")
result = {}
error = None
# send the item.
if domain=="twitter.com":
from twitter.oauth import OAuth
from twitter.api import Twitter, TwitterHTTPError
oauth_config = get_oauth_config(domain)
auth = OAuth(token=acct.oauth_token,
token_secret=acct.oauth_token_secret,
consumer_key=oauth_config['consumer_key'],
consumer_secret=oauth_config['consumer_secret'])
try:
api = Twitter(auth=auth)
status = api.statuses.update(status=message)
result[domain] = status['id']
except TwitterHTTPError, exc:
details = json.load(exc.e)
if 'error' in details:
msg = details['error']
else:
msg = str(details)
error = {'provider': domain,
'reason': msg,
}
elif domain=="facebook.com":
url = "https://graph.facebook.com/me/feed"
body = urllib.urlencode({"message": message})
response = json.load(urllib.urlopen(url +
urllib.urlencode(dict(access_token=acct.oauth_token)), body))
if 'id' in response:
result[domain] = response['id']
elif 'error' in response:
if response['error'].get('type')=="OAuthInvalidRequestException":
abort(401, "oauth token was rejected (%s)" % (response['error'],))
error = {'provider': domain,
'reason': response['error'],
}
else:
log.error("unexpected facebook response: %r", response)
else:
raise ValueError, "unsupported service %r" % (domain,)
if error:
assert not result
log.info("send failure: %r", error)
try:
redirect(request.POST['end_point_failure'])
except KeyError:
pass
else:
# create a new record in the history table.
assert result
history = History()
history.account = acct
history.published = UTCDateTime.now()
history.message = message
Session.add(history)
Session.commit()
result['linkdrop'] = history.id
log.info("send success - linkdrop id is %s", history.id)
try:
redirect(request.POST['end_point_success'])
except KeyError:
pass
# no redirects requests, just return the response.
return {'result': result, 'error': error}

Просмотреть файл

@ -14,11 +14,7 @@ class History(JsonExpandoMixin, SerializerMixin, Base):
__table_args__ = make_table_args()
id = Column(Integer, primary_key=True)
userkey = Column(Integer, ForeignKey(Account.userkey), index=True)
account_id = Column(None, ForeignKey(Account.id), index=True)
published = Column(UTCDateTime, nullable=False)
# svckey is from the account table, a key to domain/userid/username,
# it might end up being a relationship
svckey = Column(RDUnicode(128), nullable=False)
account = relationship('Account', uselist=False)
account = relationship('Account')

145
web/scratch/send/index.html Normal file
Просмотреть файл

@ -0,0 +1,145 @@
<!-- ***** BEGIN LICENSE BLOCK *****
- Version: MPL 1.1
-
- The contents of this file are subject to the Mozilla Public License Version
- 1.1 (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/
-
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- for the specific language governing rights and limitations under the
- License.
-
- The Original Code is Raindrop.
-
- The Initial Developer of the Original Code is
- Mozilla Messaging, Inc..
- Portions created by the Initial Developer are Copyright (C) 2009
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
- -->
<!-- A hacky sample to attempt a send and handle auth failure automatically
then attempt a resend
-->
<!DOCTYPE html>
<html>
<head>
<title>Send a message</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<script src="../../scripts/requireplugins-jquery-1.4.2.js" charset="utf-8"></script>
<script>require(["index.js"]);</script>
<style>
.hidden {
display: none;
}
.invisible {
visibility: hidden;
}
</style>
</head>
<body class="settings">
<div class="row">
<div class="c4 logo">
</div>
</div>
<!-- send form -->
<div class="section send">
<form name="messageForm" id="messageForm" action="/api/send/send" method="POST">
<div class="row">
<div class="c1">
<input name="message">
Send via
<input type="radio" name="domain" value="twitter.com">Twitter
<input type="radio" name="domain" value="facebook.com">Facebook
<input type="hidden" name="end_point_success" value="/scratch/send/#success">
<input type="hidden" name="end_point_failure" value="/scratch/send/#failure">
<input type="hidden" name="end_point_auth_failure" value="/scratch/send/#auth_failure">
<button>send</button>
</div>
<div class="c1">
<div class="usernameError error invisible">Please enter your Twitter name</div>
</div>
</div>
</form>
</div>
<!-- send success section -->
<div class="section success hidden">
<div class="row">
<div class="c1">
<h4 class="success">Success!</h4>
<p>Your message was sent</p>
</div>
</div>
</div>
<!-- auth failure section
This is the end_point for when the *send* request fails auth (but not
when the explicit auth request fails) - when this happens we do the
explicit auth.
-->
<div class="section auth_failure hidden">
<div class="row">
<div class="c1">
<p>Authorizing...</p>
<!-- the js code arranges to submit the auth form below... -->
</div>
</div>
</div>
<div class="section auth_form hidden"> <!-- this is never made visible -->
<form name="authForm" id="authForm" action="/api/account/authorize" method="POST">
<div class="row">
<input type="hidden" name="domain">
<input type="hidden" name="end_point_success" value="/scratch/send/#auth_success">
<input type="hidden" name="end_point_auth_failure" value="/scratch/send/#final_auth_failure">
</div>
</form>
</div>
<!-- final auth failure section
This is the end_point for when the explicit auth request fails - we have
given up here...
-->
<div class="section final_auth_failure hidden">
<div class="row">
<div class="c1">
<p>Failed to authorize...</p>
</div>
</div>
</div>
<!-- auth success section
This is the end_point for when the explicit auth request succeeds - we
then attempt to retry the send.
-->
<div class="section auth_success hidden">
<div class="row">
<div class="c1">
<p>Sending...</p>
<!-- the JS code re-submits the send form -->
</div>
</div>
</div>
<!-- general failure section - an error other than auth failure...-->
<div class="section failure hidden">
<div class="row">
<div class="c1">
<p>Sorry - we failed for obscure reasons...</p>
</div>
</div>
</div>
</body>
</html>

109
web/scratch/send/index.js Normal file
Просмотреть файл

@ -0,0 +1,109 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Raindrop.
*
* The Initial Developer of the Original Code is
* Mozilla Messaging, Inc..
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* */
/*jslint plusplus: false */
/*global require: false, location: true, window: false, alert: false */
"use strict";
require.def("send",
["require", "jquery", "blade/fn", "rdapi", "placeholder", "blade/url"],
function (require, $, fn, rdapi, placeholder, url) {
var validHashRegExp = /^\w+$/;
function onHashChange() {
var value = location.hash.split("#")[1],
start, end;
value = value || "send";
if (validHashRegExp.test(value)) {
$(".section").each(function (i, node) {
node = $(node);
if (node.hasClass(value)) {
end = node;
} else if (!node.hasClass("hidden")) {
start = node;
}
});
}
if (value==="auth_failure") {
document.authForm.domain.value = localStorage['X-Send-Domain']
document.authForm.submit()
console.log("submitted auth form...")
} else if (value==="auth_success") {
debugger;
var radio = document.messageForm.domain;
for(var i = 0; i < radio.length; i++) {
if(radio[i].value==localStorage['X-Send-Domain']) {
radio[i].checked = true;
}
}
document.messageForm.message.value = localStorage['X-Send-Domain'];
document.messageForm.submit()
}
//Animate!
if (start) start.addClass("hidden");
if (end) end.removeClass("hidden")
}
//Set up hashchange listener
window.addEventListener("hashchange", onHashChange, false);
$(function () {
$("#messageForm")
.submit(function (evt) {
//First clear old errors
$(".error").addClass("invisible");
var form = evt.target;
//Make sure all form elements are trimmed and username exists.
$.each(form.elements, function (i, node) {
var trimmed = node.value.trim();
if (node.getAttribute("placeholder") === trimmed) {
trimmed = "";
}
node.value = trimmed;
});
localStorage['X-Send-Message'] = form.message.value;
var radio = form.domain;
for(var i = 0; i < radio.length; i++) {
if(radio[i].checked) {
localStorage['X-Send-Domain'] = radio[i].value;
}
}
})
.each(function (i, node) {
placeholder(node);
});
//Make sure we set up initial state
onHashChange();
});
});