Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 815 lines (697 sloc) 28.828 kB
1047afb @peterbe saving tweeters to mongo
authored
1 import re
2 import datetime
876d6a6 @peterbe adding so it randomly picks a funny background image
authored
3 import random
d0a1b66 @peterbe adding one more screenshot
authored
4 import os
b5b1ad1 @peterbe bustage debugging
authored
5 import logging
75c5cc2 @peterbe more stats
authored
6 from pprint import pprint, pformat
6441c6d @peterbe initial commit
authored
7 import tornado.auth
8 import tornado.web
f41058b @peterbe replace callbacks with tornado.gen (still might need some more refact…
authored
9 import tornado.gen
6441c6d @peterbe initial commit
authored
10 from tornado.web import HTTPError
d855cc0 @peterbe adding a basic test framework
authored
11 from tornado_utils.routes import route
6441c6d @peterbe initial commit
authored
12 from tornado.escape import json_decode, json_encode
8775fee @peterbe using mongodb now
authored
13 from pymongo.objectid import InvalidId, ObjectId
c3e5ea7 @peterbe implementing new everyone I follow feature
authored
14 import utils
5132691 @peterbe doing some pep8 cleanup
authored
15
1047afb @peterbe saving tweeters to mongo
authored
16 from models import User, Tweeter
8775fee @peterbe using mongodb now
authored
17
6441c6d @peterbe initial commit
authored
18
19 class BaseHandler(tornado.web.RequestHandler):
20
21 def write_json(self, struct, javascript=False):
f0a8efd @peterbe changing to a bookmarklet
authored
22 self.set_header("Content-Type", "application/json; charset=UTF-8")
6441c6d @peterbe initial commit
authored
23 self.write(tornado.escape.json_encode(struct))
24
25 def write_jsonp(self, callback, struct):
26 self.set_header("Content-Type", "text/javascript; charset=UTF-8")
27 self.write('%s(%s)' % (callback, tornado.escape.json_encode(struct)))
28
29 def get_current_user(self):
8775fee @peterbe using mongodb now
authored
30 _id = self.get_secure_cookie('user')
31 if _id:
32 try:
33 return self.db.User.find_one({'_id': ObjectId(_id)})
34 except InvalidId: # pragma: no cover
35 return self.db.User.find_one({'username': _id})
6441c6d @peterbe initial commit
authored
36
37 @property
38 def redis(self):
39 return self.application.redis
40
8775fee @peterbe using mongodb now
authored
41 @property
42 def db(self):
43 return self.application.db
44
876d6a6 @peterbe adding so it randomly picks a funny background image
authored
45 def save_following(self, source_username, dest_username, result):
46 assert isinstance(result, bool)
47 following = (self.db.Following
48 .find_one({'user': source_username,
49 'follows': dest_username}))
50 if not following:
51 following = self.db.Following()
52 following['user'] = source_username
53 following['follows'] = dest_username
54
55 if result != following['following']:
56 following['following'] = result
57 following.save()
58
1047afb @peterbe saving tweeters to mongo
authored
59 def save_tweeter_user(self, user):
60 user_id = user['id']
61 tweeter = self.db.Tweeter.find_one({'user_id': user_id})
62 _save = False
63 if not tweeter:
64 tweeter = self.db.Tweeter()
65 tweeter['user_id'] = user_id
66 _save = True
67
68 if tweeter['name'] != user['name']:
69 tweeter['name'] = user['name']
70 _save = True
71
72 if tweeter['username'] != user['screen_name']:
73 tweeter['username'] = user['screen_name']
74 _save = True
75
76 if tweeter['followers'] != user['followers_count']:
77 tweeter['followers'] = user['followers_count']
78 _save = True
79
80 if tweeter['following'] != user['friends_count']:
81 tweeter['following'] = user['friends_count']
82 _save = True
83
84 def parse_status_date(dstr):
85 dstr = re.sub('\+\d{1,4}', '', dstr)
86 return datetime.datetime.strptime(
87 dstr,
88 '%a %b %d %H:%M:%S %Y'
89 )
90 last_tweet_date = None
91 if 'status' in user:
92 last_tweet_date = user['status']['created_at']
93 last_tweet_date = parse_status_date(last_tweet_date)
94 if tweeter['last_tweet_date'] != last_tweet_date:
95 tweeter['last_tweet_date'] = last_tweet_date
96 _save = True
97
98 ratio_before = tweeter['ratio']
99 ratio = tweeter.set_ratio()
100 if ratio != ratio_before:
101 _save = True
102
103 if _save:
104 tweeter.save()
105
876d6a6 @peterbe adding so it randomly picks a funny background image
authored
106 return tweeter
107
108 BACKGROUND_IMAGES = [
109 '/static/images/chuck.jpg',
110 '/static/images/rock.jpg',
111 ]
112
113 def render(self, template, **options):
114 background_image = self.redis.get('background_image')
115 if not background_image:
116 background_image = random.choice(self.BACKGROUND_IMAGES)
117 self.redis.setex('background_image', background_image, 60)
118 options['background_image'] = background_image
119 return tornado.web.RequestHandler.render(self, template, **options)
120
6441c6d @peterbe initial commit
authored
121
439d8dd @peterbe working version
authored
122 @route('/')
123 class HomeHandler(BaseHandler):
124
125 def get(self):
75c5cc2 @peterbe more stats
authored
126 options = {
127 'page_title': 'Too Cool for Me?',
128 }
439d8dd @peterbe working version
authored
129 user = self.get_current_user()
130 if user:
131 url = '/static/bookmarklet.js'
132 url = '%s://%s%s' % (self.request.protocol,
133 self.request.host,
134 url)
135 options['full_bookmarklet_url'] = url
136
137 options['user'] = user
138 self.render('home.html', **options)
139
140
f86c1e6 @peterbe greatly improve test coverage
authored
141 @route('/json', name='json')
142 @route('/jsonp', name='jsonp')
6441c6d @peterbe initial commit
authored
143 class FollowsHandler(BaseHandler, tornado.auth.TwitterMixin):
144
3760cf7 @peterbe adding a lookup counter
authored
145 def increment_lookup_count(self, username, usernames, jsonp=False):
146 if jsonp:
147 key = 'lookups:jsonp'
148 else:
149 key = 'lookups:json'
150 if not isinstance(usernames, int):
151 usernames = len(usernames)
152 self.redis.incr(key)
153
154 key = 'lookups:username:%s' % username
1047afb @peterbe saving tweeters to mongo
authored
155 assert username
3760cf7 @peterbe adding a lookup counter
authored
156 self.redis.incr(key)
157
158 key = 'lookups:usernames'
159 self.redis.incr(key, usernames)
160
6441c6d @peterbe initial commit
authored
161 @tornado.web.asynchronous
f41058b @peterbe replace callbacks with tornado.gen (still might need some more refact…
authored
162 @tornado.gen.engine
f86c1e6 @peterbe greatly improve test coverage
authored
163 def get(self):
164 jsonp = 'jsonp' in self.request.path
1dcfe99 @peterbe various fixes and rank
authored
165
6441c6d @peterbe initial commit
authored
166 if (self.get_argument('username', None) and
167 not self.get_argument('usernames', None)):
168 usernames = self.get_arguments('username')
169 else:
170 usernames = self.get_arguments('usernames')
171 if isinstance(usernames, basestring):
172 usernames = [usernames]
439d8dd @peterbe working version
authored
173 elif (isinstance(usernames, list)
174 and len(usernames) == 1
175 and ',' in usernames[0]):
176 usernames = [x.strip() for x in
177 usernames[0].split(',')
178 if x.strip()]
179 # make sure it's a unique list
180 usernames = set(usernames)
f86c1e6 @peterbe greatly improve test coverage
authored
181
f0a8efd @peterbe changing to a bookmarklet
authored
182 if jsonp:
183 self.jsonp = self.get_argument('callback', 'callback')
184 else:
185 self.jsonp = False
186
a99d391 @peterbe fix to jsonp handler
authored
187 if not usernames:
188 msg = {'ERROR': 'No usernames asked for'}
189 if jsonp:
190 self.write_jsonp(self.jsonp, msg)
191 else:
192 self.write_json(msg)
193 self.finish()
194 return
195
f0a8efd @peterbe changing to a bookmarklet
authored
196 # All of this is commented out until I can figure out why cookie
197 # headers aren't sent from bookmarklet's AJAX code
8775fee @peterbe using mongodb now
authored
198 this_username = self.get_argument('you', None)
199 access_token = None
200 if this_username is not None:
201 user = self.db.User.find_one({'username': this_username})
202 if user:
203 access_token = user['access_token']
204 else:
205 user = self.get_current_user()
206 if user:
207 this_username = user['username']
208 access_token = user['access_token']
209
439d8dd @peterbe working version
authored
210 if not access_token:
a99d391 @peterbe fix to jsonp handler
authored
211 msg = {'ERROR': ('Not authorized. Go to http://%s and sign in' %
212 self.request.host)}
213 if self.jsonp:
214 self.write_jsonp(self.jsonp, msg)
215 else:
216 self.write_json(msg)
1dcfe99 @peterbe various fixes and rank
authored
217 self.finish()
439d8dd @peterbe working version
authored
218 return
219
3760cf7 @peterbe adding a lookup counter
authored
220 self.increment_lookup_count(this_username, len(usernames), jsonp=jsonp)
221
439d8dd @peterbe working version
authored
222 results = {}
223 # pick some up already from the cache
224 _drop = set()
225 for username in usernames:
226 key = 'follows:%s:%s' % (this_username, username)
227 value = self.redis.get(key)
228 if value is not None:
229 results[username] = bool(int(value))
230 _drop.add(username)
231 usernames -= _drop
6441c6d @peterbe initial commit
authored
232
233 if len(usernames) == 1:
362d15e @peterbe a basic website to go with it
authored
234 username = list(usernames)[0]
6441c6d @peterbe initial commit
authored
235 # See https://dev.twitter.com/docs/api/1/get/friendships/show
f41058b @peterbe replace callbacks with tornado.gen (still might need some more refact…
authored
236
237 result = yield tornado.gen.Task(self.twitter_request,
238 "/friendships/show",
239 source_screen_name=this_username,
240 target_screen_name=username,
241 access_token=access_token)
242 self._on_show(result, this_username, username, results)
439d8dd @peterbe working version
authored
243 elif usernames:
c3e5ea7 @peterbe implementing new everyone I follow feature
authored
244 if len(usernames) > 100:
245 raise HTTPError(400, "Too many usernames to look up (max 100)")
6441c6d @peterbe initial commit
authored
246 # See https://dev.twitter.com/docs/api/1/get/friendships/lookup
c3e5ea7 @peterbe implementing new everyone I follow feature
authored
247 result = None
248 attempts = 0
249 while result is None:
250 result = yield tornado.gen.Task(self.twitter_request,
251 "/friendships/lookup",
252 screen_name=','.join(usernames),
253 access_token=access_token)
254 if result is not None:
255 break
256 else:
257 attempts += 1
258 from time import sleep
259 sleep(1)
260 if attempts > 2:
261 raise HTTPError(500, "Unable to look up friendships")
f41058b @peterbe replace callbacks with tornado.gen (still might need some more refact…
authored
262 self._on_lookup(result, this_username, results)
439d8dd @peterbe working version
authored
263 else:
264 # all usernames were lookup'able by cache
f0a8efd @peterbe changing to a bookmarklet
authored
265 if self.jsonp:
266 self.write_jsonp(self.jsonp, results)
267 else:
268 self.write_json(results)
439d8dd @peterbe working version
authored
269 self.finish()
6441c6d @peterbe initial commit
authored
270
439d8dd @peterbe working version
authored
271 def _on_lookup(self, result, this_username, data):
6441c6d @peterbe initial commit
authored
272 for each in result:
273 if 'followed_by' in each['connections']:
274 data[each['screen_name']] = True
275 else:
276 data[each['screen_name']] = False
439d8dd @peterbe working version
authored
277 key = 'follows:%s:%s' % (this_username, each['screen_name'])
c3e5ea7 @peterbe implementing new everyone I follow feature
authored
278 self.redis.setex(key, int(data[each['screen_name']]), 60 * 5)
876d6a6 @peterbe adding so it randomly picks a funny background image
authored
279 self.save_following(each['screen_name'], this_username,
280 bool(data[each['screen_name']]))
439d8dd @peterbe working version
authored
281
f0a8efd @peterbe changing to a bookmarklet
authored
282 if self.jsonp:
283 self.write_jsonp(self.jsonp, data)
284 else:
285 self.write_json(data)
6441c6d @peterbe initial commit
authored
286 self.finish()
287
362d15e @peterbe a basic website to go with it
authored
288 def _on_show(self, result, this_username, username, data):
6441c6d @peterbe initial commit
authored
289 target_follows = None
290 if result and 'relationship' in result:
291 target_follows = result['relationship']['target']['following']
439d8dd @peterbe working version
authored
292 key = 'follows:%s:%s' % (this_username, username)
1dcfe99 @peterbe various fixes and rank
authored
293 if target_follows is not None:
294 self.redis.setex(key, int(bool(target_follows)), 60)
876d6a6 @peterbe adding so it randomly picks a funny background image
authored
295 self.save_following(username, this_username, bool(target_follows))
362d15e @peterbe a basic website to go with it
authored
296 data[username] = target_follows
f0a8efd @peterbe changing to a bookmarklet
authored
297 if self.jsonp:
298 self.write_jsonp(self.jsonp, data)
299 else:
300 self.write_json(data)
6441c6d @peterbe initial commit
authored
301 self.finish()
302
303
304 class BaseAuthHandler(BaseHandler):
305
306 def get_next_url(self):
307 return '/'
308
5132691 @peterbe doing some pep8 cleanup
authored
309
6441c6d @peterbe initial commit
authored
310 @route('/auth/twitter/', name='auth_twitter')
311 class TwitterAuthHandler(BaseAuthHandler, tornado.auth.TwitterMixin):
312
3760cf7 @peterbe adding a lookup counter
authored
313 def increment_authentication_count(self, username):
314 key = 'auths:username:%s' % username
315 self.redis.incr(key)
316 key = 'auths:total'
317 self.redis.incr(key)
318
6441c6d @peterbe initial commit
authored
319 @tornado.web.asynchronous
320 def get(self):
321 if self.get_argument("oauth_token", None):
322 self.get_authenticated_user(self.async_callback(self._on_auth))
323 return
324 self.authenticate_redirect()
325
326 def _on_auth(self, user_struct):
327 if not user_struct:
a556cc7 @peterbe better handling for twitter auth failure
authored
328 options = {}
329 options['page_title'] = "Twitter authentication failed"
330 self.render('twitter_auth_failed.html', **options)
5838ceb @peterbe fixing when twitter authentiction fails
authored
331 return
1047afb @peterbe saving tweeters to mongo
authored
332
333 username = user_struct.get('username',
334 user_struct.get('screen_name'))
6441c6d @peterbe initial commit
authored
335 access_token = user_struct['access_token']
1dcfe99 @peterbe various fixes and rank
authored
336 assert access_token
8775fee @peterbe using mongodb now
authored
337 user = self.db.User.find_one({'username': username})
338 if user is None:
339 user = self.db.User()
340 user['username'] = username
341 user['access_token'] = access_token
342 user.save()
343
3760cf7 @peterbe adding a lookup counter
authored
344 self.increment_authentication_count(username)
345
5132691 @peterbe doing some pep8 cleanup
authored
346 self.set_secure_cookie("user",
8775fee @peterbe using mongodb now
authored
347 str(user['_id']),
5132691 @peterbe doing some pep8 cleanup
authored
348 expires_days=30, path='/')
1047afb @peterbe saving tweeters to mongo
authored
349
350 self.save_tweeter_user(user_struct)
6441c6d @peterbe initial commit
authored
351 self.redirect('/')
352
5132691 @peterbe doing some pep8 cleanup
authored
353
6441c6d @peterbe initial commit
authored
354 @route(r'/auth/logout/', name='logout')
355 class AuthLogoutHandler(BaseAuthHandler):
356 def get(self):
357 self.clear_all_cookies()
358 self.redirect(self.get_next_url())
362d15e @peterbe a basic website to go with it
authored
359
360
361 @route(r'/test', name='test')
362 class TestServiceHandler(BaseHandler):
363
364 def get(self):
365 options = {}
366 user = self.get_current_user()
367 if not user:
368 self.redirect('/auth/twitter/')
369 return
370 options['user'] = user
6477ff3 @peterbe fixing the test page
authored
371 options['page_title'] = "Test the service"
362d15e @peterbe a basic website to go with it
authored
372 self.render('test.html', **options)
9ff6e88 @peterbe adding thing about dotjs
authored
373
5132691 @peterbe doing some pep8 cleanup
authored
374
f86c1e6 @peterbe greatly improve test coverage
authored
375 @route('/following/(\w+)', name='following')
f0a8efd @peterbe changing to a bookmarklet
authored
376 class FollowingHandler(BaseHandler, tornado.auth.TwitterMixin):
377
378 @tornado.web.asynchronous
f41058b @peterbe replace callbacks with tornado.gen (still might need some more refact…
authored
379 @tornado.gen.engine
f0a8efd @peterbe changing to a bookmarklet
authored
380 def get(self, username):
876d6a6 @peterbe adding so it randomly picks a funny background image
authored
381 options = {
382 'username': username,
383 'compared_to': None
384 }
8775fee @peterbe using mongodb now
authored
385 current_user = self.get_current_user()
386 if not current_user:
f86c1e6 @peterbe greatly improve test coverage
authored
387 self.redirect(self.reverse_url('auth_twitter'))
f0a8efd @peterbe changing to a bookmarklet
authored
388 return
8775fee @peterbe using mongodb now
authored
389 this_username = current_user['username']
75c5cc2 @peterbe more stats
authored
390 options['this_username'] = this_username
f0a8efd @peterbe changing to a bookmarklet
authored
391 options['follows'] = None
75c5cc2 @peterbe more stats
authored
392 key = 'follows:%s:%s' % (this_username, username)
393 value = self.redis.get(key)
394 if value is None:
8775fee @peterbe using mongodb now
authored
395 access_token = current_user['access_token']
f41058b @peterbe replace callbacks with tornado.gen (still might need some more refact…
authored
396 result = yield tornado.gen.Task(self.twitter_request,
397 "/friendships/show",
398 source_screen_name=this_username,
399 target_screen_name=username,
400 access_token=access_token)
876d6a6 @peterbe adding so it randomly picks a funny background image
authored
401 if result and 'relationship' in result:
402 value = result['relationship']['target']['following']
403 self.save_following(username, this_username, value)
75c5cc2 @peterbe more stats
authored
404 else:
f41058b @peterbe replace callbacks with tornado.gen (still might need some more refact…
authored
405 result = bool(int(value))
406 key = None
407 self._on_friendship(result, key, options)
f0a8efd @peterbe changing to a bookmarklet
authored
408
75c5cc2 @peterbe more stats
authored
409 def _on_friendship(self, result, key, options):
dc13e72 @peterbe better error handling on failing twitter api
authored
410 if result is None:
411 options['error'] = ("Unable to look up friendship for %s" %
412 options['username'])
413 self._render(options)
414 return
415
75c5cc2 @peterbe more stats
authored
416 if isinstance(result, bool):
417 value = result
418 else:
419 if result and 'relationship' in result:
420 value = result['relationship']['target']['following']
1dcfe99 @peterbe various fixes and rank
authored
421 if key and value is not None:
75c5cc2 @peterbe more stats
authored
422 self.redis.setex(key, int(bool(value)), 60)
f0a8efd @peterbe changing to a bookmarklet
authored
423 options['follows'] = value
75c5cc2 @peterbe more stats
authored
424 self._fetch_info(options)
425
f41058b @peterbe replace callbacks with tornado.gen (still might need some more refact…
authored
426 @tornado.gen.engine
75c5cc2 @peterbe more stats
authored
427 def _fetch_info(self, options, username=None):
428 if username is None:
429 username = options['username']
8775fee @peterbe using mongodb now
authored
430
75c5cc2 @peterbe more stats
authored
431 key = 'info:%s' % username
432 value = self.redis.get(key)
1dcfe99 @peterbe various fixes and rank
authored
433
75c5cc2 @peterbe more stats
authored
434 if value is None:
8775fee @peterbe using mongodb now
authored
435 user = self.db.User.find_one({'username': options['this_username']})
436 access_token = user['access_token']
f41058b @peterbe replace callbacks with tornado.gen (still might need some more refact…
authored
437 result = yield tornado.gen.Task(self.twitter_request,
438 "/users/show",
439 screen_name=username,
440 access_token=access_token)
1047afb @peterbe saving tweeters to mongo
authored
441 if result:
442 self.save_tweeter_user(result)
75c5cc2 @peterbe more stats
authored
443 else:
f41058b @peterbe replace callbacks with tornado.gen (still might need some more refact…
authored
444 result = json_decode(value)
445 key = None
75c5cc2 @peterbe more stats
authored
446
dc13e72 @peterbe better error handling on failing twitter api
authored
447 if result is None:
448 options['error'] = "Unable to look up info for %s" % username
449 self._render(options)
450 return
75c5cc2 @peterbe more stats
authored
451 if isinstance(result, basestring):
452 result = json_decode(result)
453 if key:
b5b1ad1 @peterbe bustage debugging
authored
454 self.redis.setex(key, json_encode(result), 60 * 60)
75c5cc2 @peterbe more stats
authored
455 if 'info' not in options:
456 options['info'] = {options['username']: result}
457 self._fetch_info(options, username=options['this_username'])
458 else:
459 options['info'][options['this_username']] = result
460 self._render(options)
461
462 def _render(self, options):
dc13e72 @peterbe better error handling on failing twitter api
authored
463 if 'error' not in options:
464 if options['follows']:
465 page_title = '%s follows me'
466 else:
467 page_title = '%s is too cool for me'
468 self._set_ratio(options, 'username')
469 self._set_ratio(options, 'this_username')
470 options['page_title'] = page_title % options['username']
471 self.render('following.html', **options)
75c5cc2 @peterbe more stats
authored
472 else:
dc13e72 @peterbe better error handling on failing twitter api
authored
473 options['page_title'] = 'Error :('
474 self.render('following_error.html', **options)
75c5cc2 @peterbe more stats
authored
475
476 def _set_ratio(self, options, key):
f86c1e6 @peterbe greatly improve test coverage
authored
477 value = options[key]
478 followers = options['info'][value]['followers_count']
479 following = options['info'][value]['friends_count']
b91e61b @peterbe fixed bug with zero division on 0 following
authored
480 ratio = 1.0 * followers / max(following, 1)
f86c1e6 @peterbe greatly improve test coverage
authored
481 options['info'][value]['ratio'] = '%.1f' % ratio
482 key = 'ratios'
876d6a6 @peterbe adding so it randomly picks a funny background image
authored
483 tweeter = self.db.Tweeter.find_one({'username': value})
484 assert tweeter
485 rank = tweeter.get('ratio_rank', None)
486 # This should be re-calculated periodically
487 if rank is None:
488 rank = 0
489 for each in (self.db.Tweeter
490 .find(fields=('username',))
491 .sort('ratio', -1)):
492 rank += 1
493 if each['username'] == value:
494 tweeter['ratio_rank'] = rank
495 tweeter.save()
496 break
497 options['info'][value]['rank'] = rank
498
499
500 @route('/following/suggest_tweet.json', name='suggest_tweet')
501 class SuggestTweetHandler(BaseHandler):
502
503 def get(self):
504 username = self.get_argument('username')
505 current_user = self.get_current_user()
506 if not current_user:
507 raise HTTPError(403, "Not logged in")
508 compared_to = current_user['username']
509
510 tweeter = self.db.Tweeter.find_one({'username': username})
511 if not tweeter:
512 raise HTTPError(400, "Unknown tweeter %r" % username)
513 compared_tweeter = self.db.Tweeter.find_one({'username': compared_to})
514 if not tweeter:
515 raise HTTPError(400, "Unknown tweeter %r" % compared_to)
516
517 def make_message(include_hashtag=False, include_fullname=False):
518 if include_fullname:
519 name = '@%s (%s)' % (username, fullname)
520 else:
521 name = '@%s' % username
522 tweet = "Apparently "
523 if abs(a - b) < 1.0:
524 tweet += "%s is " % name
525 tweet += "as cool as me"
526 elif b > a:
527 tweet += "I am "
528 tweet += "%s times cooler than %s" % (get_times(a, b), name)
529 elif a > b:
530 tweet += "%s is " % name
531 tweet += "%s times cooler than me" % get_times(a, b)
532
533 hashtag = "#toocool"
534 if include_hashtag:
535 tweet += " %s" % hashtag
536
537 return tweet
538
539 def get_times(*numbers):
540 small = min(numbers)
541 big = max(numbers)
542 bigger = round(big / small)
543 if int(bigger) == 2:
544 return "two"
545 if int(bigger) == 3:
546 return "three"
547 return "about %s" % int(bigger)
548
549 a, b = tweeter['ratio'], compared_tweeter['ratio']
550 fullname = tweeter['name']
551
552 tweet = make_message(include_hashtag=False, include_fullname=True)
553 if len(tweet) > 140:
554 tweet = make_message(include_hashtag=False, include_fullname=False)
555 if len(tweet) > 140:
556 tweet = make_message(include_hashtag=False,
557 include_fullname=False)
558
559 base_url = self.request.host
560 perm_url = self.reverse_url('following_compared',
561 username,
562 compared_to)
563 url = 'http://%s%s' % (base_url, perm_url)
564
565 #self.write_json({'tweet': tweet})
566 self.write_json({'text': tweet, 'url': url})
567
568
569 @route('/following/(\w+)/vs/(\w+)', name='following_compared')
570 class FollowingComparedtoHandler(FollowingHandler):
f86c1e6 @peterbe greatly improve test coverage
authored
571
876d6a6 @peterbe adding so it randomly picks a funny background image
authored
572 @tornado.web.asynchronous
573 @tornado.gen.engine
574 def get(self, username, compared_to):
575 options = {'compared_to': compared_to}
576 tweeter = self.db.Tweeter.find_one({'username': username})
577 compared_tweeter = self.db.Tweeter.find_one({'username': compared_to})
578
579 current_user = self.get_current_user()
580 if current_user:
581 # if we don't have tweeter info on any of them, fetch it
582 if not tweeter:
583 # fetch it
584 result = yield tornado.gen.Task(self.twitter_request,
585 "/users/show",
586 screen_name=username,
587 access_token=current_user['access_token'])
588 tweeter = self.save_tweeter_user(result)
589 if not compared_tweeter:
590 result = yield tornado.gen.Task(self.twitter_request,
591 "/users/show",
592 screen_name=compared_to,
593 access_token=current_user['access_token'])
594 compared_tweeter = self.save_tweeter_user(result)
595
596 elif not tweeter and not compared_tweeter:
597 options = {
598 'page_title': 'Comparing %s to %s' % (username, compared_to)
599 }
600 options['missing_info'] = []
601 if not tweeter:
602 options['missing_info'].append(username)
603 if not compared_tweeter:
604 options['missing_info'].append(compared_to)
605 options['next_url'] = self.request.path
606 self.render('following_compared_missing.html', **options)
607 return
608
609 key = 'follows:%s:%s' % (compared_to, username)
610 value = self.redis.get(key)
611 if value is None:
612 following = (self.db.Following
613 .find_one({'user': tweeter['_id'],
614 'follows': compared_tweeter['_id']}))
615 if following:
616 options['follows'] = following['following']
617 else:
618 options['follows'] = False
619 else:
620 value = bool(int(value))
621 options['follows'] = value
622
623 if options['follows']:
624 options['page_title'] = ('%s follows %s' %
625 (username, compared_to))
626 else:
627 options['page_title'] = ('%s is too cool for %s' %
628 (username, compared_to))
629
630 options['info'] = {
631 username: {
632 'followers_count': tweeter['followers'],
633 'friends_count': tweeter['following'],
634 },
635 compared_to: {
636 'followers_count': compared_tweeter['followers'],
637 'friends_count': compared_tweeter['following'],
638 }
639 }
640 options['username'] = username
641 options['this_username'] = compared_to
642 self._set_ratio(options, 'username')
643 self._set_ratio(options, 'this_username')
644 options['compared_to'] = compared_to
645 self.render('following.html', **options)
f71b432 @peterbe coolest URL
authored
646
647
b91e61b @peterbe fixed bug with zero division on 0 following
authored
648 @route(r'/coolest', name='coolest')
8775fee @peterbe using mongodb now
authored
649 class CoolestHandler(BaseHandler): # pragma: no cover (under development)
f71b432 @peterbe coolest URL
authored
650
651 def get(self):
652 options = {}
653 user = self.get_current_user()
654 key = 'ratios'
f4f4be5 @peterbe new coolest template
authored
655
656 #ratios = self.redis.zrange(key, 0, -1, withscores=True)
657 #ratios.reverse()
658
659 options['ratios'] = self.db.Tweeter.find().sort('ratio', -1)
f71b432 @peterbe coolest URL
authored
660 options['user'] = user
5132691 @peterbe doing some pep8 cleanup
authored
661 options['page_title'] = \
662 "Coolest in the world! ...on Twitter ...using this site"
f71b432 @peterbe coolest URL
authored
663 self.render('coolest.html', **options)
ed1dd42 @peterbe screenshots page
authored
664
665 @route(r'/screenshots', name='screenshots')
666 class ScreenshotsHandler(BaseHandler): # pragma: no cover (under development)
d0a1b66 @peterbe adding one more screenshot
authored
667 IMAGES = (
668 ('bookmarklet-in-toolbar.png',
669 u"Bookmarklet in toolbar"),
670 ('on-twitter.png',
671 u"On Twitter"),
672 ('follows-me.png',
673 u"Someone who follows me"),
674 ('too-cool.png',
675 u"Someone who is too cool for me"),
676 ('everyone.png',
677 u"Complete list of all people you follow and if they follow you"),
678 ('lookups.png',
679 u"On /lookups you can see all Twitter traffic in near-real-time"),
680 )
ed1dd42 @peterbe screenshots page
authored
681
682 def get(self):
683 options = {}
684 options['page_title'] = "Screenshots"
d0a1b66 @peterbe adding one more screenshot
authored
685 images = []
686 static_base_path = os.path.join(
687 self.application.settings['static_path'],
688 'images',
689 'screenshots',
690 )
691 for filename, title in self.IMAGES:
692 file_path = os.path.join('images', 'screenshots', filename)
693 file_path_small = file_path.replace('.png', '_small.png')
694 images.append((
695 file_path,
696 file_path_small,
697 title
698 ))
699
700 options['images'] = images
ed1dd42 @peterbe screenshots page
authored
701 self.render('screenshots.html', **options)
c3e5ea7 @peterbe implementing new everyone I follow feature
authored
702
703 @route('/everyone', name='everyone')
704 class EveryoneIFollowHandler(BaseHandler, tornado.auth.TwitterMixin):
705
706 def get(self):
707 current_user = self.get_current_user()
708 if not current_user:
709 self.redirect(self.reverse_url('auth_twitter'))
710 return
d5a3462 @peterbe more ajax
authored
711 options = {}
712 options['page_title'] = "Everyone I follow"
713 self.render('everyone.html', **options)
714
715 @route('/everyone.json', name='everyone_json')
716 class EveryoneIFollowJSONHandler(BaseHandler, tornado.auth.TwitterMixin):
717
718 @tornado.web.asynchronous
719 @tornado.gen.engine
720 def get(self):
721
722 current_user = self.get_current_user()
723 if not current_user:
724 raise HTTPError(403, "Not logged in")
725
c3e5ea7 @peterbe implementing new everyone I follow feature
authored
726 this_username = current_user['username']
727 access_token = current_user['access_token']
728 key = 'friends:%s' % this_username
729 result = self.redis.get(key)
730 if result is None:
731 result = yield tornado.gen.Task(self.twitter_request,
732 "/friends/ids",
733 screen_name=this_username,
734 access_token=access_token
735 )
736 self.redis.setex(key, json_encode(result), 60 * 60)
737 else:
738 result = json_decode(result)
739 # now turn these IDs into real screen names
740 unknown = []
741 screen_names = []
742 for id_ in result:
743 user = self.db.User.find_one({'user_id': id_})
744 if user:
745 screen_names.append(user['username'])
746 else:
747 key = 'screen_name:%s' % id_
748 screen_name = self.redis.get(key)
749 if screen_name is None:
750 unknown.append(id_)
751 else:
752 screen_names.append(screen_name)
753
754 buckets = utils.bucketize(unknown, 100)
755
756 for bucket in buckets:
757 users = None
758 attempts = 0
759 while True:
760 users = yield tornado.gen.Task(self.twitter_request,
761 "/users/lookup",
762 user_id=','.join(str(x) for x in bucket)
763 )
764 if users is not None:
765 break
766 else:
767 from time import sleep
768 sleep(1)
769 attempts += 1
770 if attempts > 3:
771 raise HTTPError(500, "Unable to connect to twitter")
772 for user in users:
773 username = user['screen_name']
774 key = 'screen_name:%s' % user['id']
1047afb @peterbe saving tweeters to mongo
authored
775 self.save_tweeter_user(user)
c3e5ea7 @peterbe implementing new everyone I follow feature
authored
776 self.redis.setex(key, username, 7 * 24 * 60 * 60)
777 screen_names.append(username)
778
779 assert len(result) == len(screen_names)
780
34fda5a @peterbe new screenshot
authored
781 screen_names.sort()
d5a3462 @peterbe more ajax
authored
782 self.write_json(screen_names)
783 self.finish()
3760cf7 @peterbe adding a lookup counter
authored
784
785
1047afb @peterbe saving tweeters to mongo
authored
786
3760cf7 @peterbe adding a lookup counter
authored
787 @route('/lookups', name='lookups')
788 class LookupsHandler(BaseHandler):
789
790 def get_lookups(self, username=None):
791 data = {}
792 data['lookups_json'] = self.redis.get('lookups:json') or 0
793 data['lookups_jsonp'] = self.redis.get('lookups:jsonp') or 0
794 data['auths'] = self.redis.get('auths:total') or 0
795 data['lookups_usernames'] = self.redis.get('lookups:usernames') or 0
796 if username:
797 print "NotImplmented"
798 for key, value in data.items():
799 data[key] = int(value)
800 return data
801
802 def get(self):
803 options = {}
804 options['page_title'] = "Lookups"
805 options.update(self.get_lookups())
806 self.render('lookups.html', **options)
807
876d6a6 @peterbe adding so it randomly picks a funny background image
authored
808
3760cf7 @peterbe adding a lookup counter
authored
809 @route('/lookups.json', name='lookups_json')
810 class LookupsJSONHandler(LookupsHandler):
811
812 def get(self):
813 data = self.get_lookups()
814 self.write_json(data)
Something went wrong with that request. Please try again.