first cut at send
This commit is contained in:
Родитель
8969339636
Коммит
fa48a95884
|
@ -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')
|
||||
|
|
|
@ -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>
|
|
@ -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();
|
||||
});
|
||||
});
|
Загрузка…
Ссылка в новой задаче