Last active

Embed URL

HTTPS clone URL

SSH clone URL

You can clone with HTTPS or SSH.

Download Gist
View app.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
from flask import Flask, redirect, url_for, session, request, render_template
from dropbox.client import DropboxClient, DropboxOAuth2Flow
import dropbox
import redis
from datetime import datetime, timedelta
import os
 
# Token for the account holding the images
my_token = os.environ['TOKEN']
 
redis_url = os.environ['REDISTOGO_URL']
redis_client = redis.from_url(redis_url)
 
# App key and secret from the App console (dropbox.com/developers/apps)
APP_KEY = os.environ['APP_KEY']
APP_SECRET = os.environ['APP_SECRET']
 
app = Flask(__name__)
app.debug = True
 
# A random secret used by Flask to encrypt session data cookies
app.secret_key = os.environ['FLASK_SECRET_KEY']
 
def get_callback_url():
'''Generate a proper callback URL, forcing HTTPS if not running locally'''
url = url_for(
'callback',
_external=True,
_scheme='http' if request.host.startswith('127.0.0.1') else 'https'
)
return url
 
def get_flow():
return DropboxOAuth2Flow(
APP_KEY,
APP_SECRET,
get_callback_url(),
session,
'dropbox-csrf-token')
 
def get_answer_url():
'''Based on the current date, generate a media link for the current photo (<date>.jpg).'''
pacific_now = datetime.utcnow() + timedelta(hours=-8)
return DropboxClient(my_token).media(pacific_now.strftime('%Y-%m-%d.jpg'))['url']
 
@app.route('/')
def index():
# Pass a URL for today's image into the view.
return render_template('index.html', url=get_answer_url())
 
@app.route('/login')
def login():
# A bit of JavaScript on the client passes a 'tz' parameter specifying the
# user's time zone. Pass that through the OAuth flow in the state parameter.
tz = request.args.get('tz', '0')
return redirect(get_flow().start(tz))
 
cache = {}
def get_copy_ref(token, date):
'''For today's photo, return a copy_ref that will be used to copy the file
into users' accounts. Cache these for efficiency when processing a lot of
accounts on the same day (which happens in the cron job).'''
if date not in cache:
cache[date] = DropboxClient(token).create_copy_ref(date + '.jpg')['copy_ref']
return cache[date]
 
def update_if_needed(uid, r, my_token):
'''For a given user, copy a new photo into their Dropbox if needed.'''
 
# Get the user's access token from Redis.
access_token = redis_client.hget('tokens', uid)
 
# Get the time zone, or use Pacific time by default.
tz = redis_client.hget('timezones', uid) or '-8'
 
date = (datetime.utcnow() + timedelta(hours=int(tz))).strftime('%Y-%m-%d')
 
# If the user hasn't been updated yet today, copy in a new photo.
if redis_client.hget('last_update', uid) != date:
 
# Get a copy ref from the master account
copy_ref = get_copy_ref(my_token, date)
 
client = DropboxClient(access_token)
try:
# Add the photo
client.add_copy_ref(copy_ref, 'Is %s Christmas.jpg' % date)
except:
# Ignore all errors! Probably a bad idea, but the most common
# error is that there's a conflict because the user actually
# already has this file. TODO: Catch that specifically. :-)
pass
 
# Track the last update so we don't revisit this user until tomorrow.
redis_client.hset('last_update', uid, date)
 
@app.route('/callback')
def callback():
'''Callback function for when the user returns from OAuth.'''
 
# Extract and store the access token, user ID, and time zone.
access_token, uid, tz = get_flow().finish(request.args)
redis_client.hset('tokens', uid, access_token)
redis_client.hset('timezones', uid, tz)
 
# For new users, this should give them today's photo.
update_if_needed(uid, r, my_token)
 
return render_template('done.html', url=get_answer_url())
 
@app.route('/cron')
def cron():
'''Cron job, triggered remotely on a regular basis by hitting this URL.'''
 
# Update any users that need it.
for uid in redis_client.hkeys('tokens'):
update_if_needed(uid, r, my_token)
 
return 'Okay.'
 
if __name__=='__main__':
app.run(debug=True)
View app.py
1 2 3 4 5 6
<!-- To be placed in templates/done.html -->
{% extends 'layout.html' %}
 
{% block body %}
<p>That's it! You should now see an image in the folder "<code>Dropbox/Apps/Is it Christmas</code>" each day.</p>
{% endblock %}
View app.py
1 2 3 4 5 6 7 8 9 10 11 12
<!-- To be placed in templates/index.html -->
{% extends 'layout.html' %}
{% block body %}
<a class="button" href="login">Connect to Dropbox for daily updates!</a>
<script>
var links = document.getElementsByTagName('a');
var tz = -new Date().getTimezoneOffset()/60;
for (var i = 0; i < links.length; i++) {
links[i].href += '?tz=' + tz;
}
</script>
{% endblock %}
View app.py
1 2 3 4 5 6 7 8 9 10 11 12 13
<!-- To be placed in templates/layout.html -->
<!doctype html>
<html>
<head>
<title>Is it Christmas?</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<h1>Is it Christmas?</h1>
<a class="image" href="/"><img src="{{url}}"></a>
{% block body %}{% endblock %}
</body>
</html>
View app.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
/* To be placed in static/style.css */
body { font-family: Helvetica, 'Helvetica Neue', 'Segoe UI', Arial; }
h1 { text-align: center; margin: 50px; padding-top: 0; font-weight: normal; }
code { font-family: Monaco, Menlo, Consolas, monospace; }
a.image {
width: 612px;
display: block;
margin: 0 auto;
}
a.image img { border: none; outline: none; }
a.button {
text-decoration: none;
text-align: center;
width: 300px;
outline: none;
color: white;
display: block;
margin: 50px auto;
background-color: #007ee5;
border: none;
font-size: 16px;
line-height: 24px;
padding: 14px;
-webkit-box-shadow: inset 0px -3px 1px rgba(0, 0, 0, 0.45);
-moz-box-shadow: inset 0px -3px 1px rgba(0, 0, 0, 0.45);
box-shadow: inset 0px -3px 1px rgba(0, 0, 0, 0.45);
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
a.button:active {
position: relative; top: 3px;
-webkit-box-shadow: inset 0px -3px 1px rgba(255, 255, 255, 1), inset 0 0px 3px rgba(0, 0, 0, 0.9);
-moz-box-shadow: inset 0px -3px 1px rgba(255, 255, 255, 1), inset 0 0px 3px rgba(0, 0, 0, 0.9);
box-shadow: inset 0px -3px 1px rgba(255, 255, 255, 1), inset 0 0px 3px rgba(0, 0, 0, 0.9);
}
a.button:active:after { content: ""; width: 100%; height: 3px; background: #fff; position: absolute; bottom: -1px; left: 0; }
p { text-align: center; font-size: 18px; margin-top: 50px; }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.