photo done
[rainbowstream.git] / rainbowstream / rainbow.py
CommitLineData
91476ec3
O
1"""
2Colorful user's timeline stream
3"""
78b81730
O
4from multiprocessing import Process
5from dateutil import parser
6
b2b933a9 7import os
8import os.path
9import sys
10import signal
11import argparse
12import time
13import datetime
991c30af 14import requests
91476ec3 15
91476ec3 16from twitter.stream import TwitterStream, Timeout, HeartbeatTimeout, Hangup
54277114 17from twitter.api import *
91476ec3 18from twitter.oauth import OAuth, read_token_file
8c840a83 19from twitter.oauth_dance import oauth_dance
91476ec3 20from twitter.util import printNicely
991c30af 21from StringIO import StringIO
91476ec3 22
2a6238f5
O
23from .colors import *
24from .config import *
777c52d4 25from .consumer import *
94a5f62e 26from .interactive import *
18cab06a 27from .db import *
991c30af 28from .c_image import *
2a6238f5 29
f405a7d0 30g = {}
18cab06a 31db = RainbowDB()
94a5f62e 32cmdset = [
42fde775 33 'switch',
94a5f62e 34 'home',
35 'view',
36 't',
37 'rt',
7e4ccbf3 38 'fav',
94a5f62e 39 'rep',
40 'del',
7e4ccbf3 41 'ufav',
94a5f62e 42 's',
43 'fr',
44 'fl',
45 'h',
46 'c',
47 'q'
48]
22be990e 49
b2b933a9 50
d51b4107 51def draw(t, keyword=None, fil=[], ig=[]):
91476ec3
O
52 """
53 Draw the rainbow
54 """
d51b4107 55
91476ec3 56 # Retrieve tweet
829cc2d8 57 tid = t['id']
91476ec3
O
58 text = t['text']
59 screen_name = t['user']['screen_name']
60 name = t['user']['name']
61 created_at = t['created_at']
7e4ccbf3 62 favorited = t['favorited']
91476ec3 63 date = parser.parse(created_at)
e20af1c3 64 date = date - datetime.timedelta(seconds=time.timezone)
65 clock = date.strftime('%Y/%m/%d %H:%M:%S')
91476ec3 66
991c30af
O
67 # Get expanded url
68 try:
69 expanded_url = []
70 url = []
71 urls = t['entities']['urls']
72 for u in urls:
73 expanded_url.append(u['expanded_url'])
74 url.append(u['url'])
75 except:
76 expanded_url = None
77 url = None
78
79 # Get media
80 try:
81 media_url = []
82 media = t['entities']['media']
83 for m in media:
84 media_url = m['media_url']
85 except:
86 media_url = None
87
d51b4107
O
88 # Filter and ignore
89 screen_name = '@' + screen_name
90 if fil and screen_name not in fil:
91 return
92 if ig and screen_name in ig:
93 return
94
18cab06a
O
95 res = db.tweet_query(tid)
96 if not res:
97 db.store(tid)
98 res = db.tweet_query(tid)
99 rid = res[0].rainbow_id
100
91476ec3 101 # Format info
d51b4107 102 user = cycle_color(name) + grey(' ' + screen_name + ' ')
7e4ccbf3 103 meta = grey('[' + clock + '] [id=' + str(rid) + '] ')
104 if favorited:
105 meta = meta + green(u'\u2605')
8c840a83 106 tweet = text.split()
991c30af
O
107 # Replace url
108 if expanded_url:
109 for index in range(len(expanded_url)):
110 tweet = map(
111 lambda x: expanded_url[index] if x == url[index] else x,
112 tweet)
b8dda704 113 # Highlight RT
2a6238f5 114 tweet = map(lambda x: grey(x) if x == 'RT' else x, tweet)
b8dda704 115 # Highlight screen_name
2a6238f5 116 tweet = map(lambda x: cycle_color(x) if x[0] == '@' else x, tweet)
b8dda704 117 # Highlight link
2a6238f5 118 tweet = map(lambda x: cyan(x) if x[0:7] == 'http://' else x, tweet)
b8dda704 119 # Highlight search keyword
7a431249 120 if keyword:
22be990e 121 tweet = map(
122 lambda x: on_yellow(x) if
123 ''.join(c for c in x if c.isalnum()).lower() == keyword.lower()
124 else x,
125 tweet
126 )
991c30af 127 # Recreate tweet
8c840a83 128 tweet = ' '.join(tweet)
91476ec3
O
129
130 # Draw rainbow
06773ffe 131 line1 = u"{u:>{uw}}:".format(
2a6238f5
O
132 u=user,
133 uw=len(user) + 2,
91476ec3 134 )
06773ffe 135 line2 = u"{c:>{cw}}".format(
829cc2d8
O
136 c=meta,
137 cw=len(meta) + 2,
06773ffe
O
138 )
139 line3 = ' ' + tweet
91476ec3 140
94a5f62e 141 printNicely('')
f405a7d0
O
142 printNicely(line1)
143 printNicely(line2)
144 printNicely(line3)
91476ec3 145
991c30af
O
146 # Display Image
147 if media_url:
148 response = requests.get(media_url)
149 image_to_display(StringIO(response.content))
150
91476ec3
O
151
152def parse_arguments():
153 """
154 Parse the arguments
155 """
91476ec3 156 parser = argparse.ArgumentParser(description=__doc__ or "")
2a6238f5
O
157 parser.add_argument(
158 '-to',
159 '--timeout',
160 help='Timeout for the stream (seconds).')
161 parser.add_argument(
162 '-ht',
163 '--heartbeat-timeout',
164 help='Set heartbeat timeout.',
165 default=90)
166 parser.add_argument(
167 '-nb',
168 '--no-block',
169 action='store_true',
170 help='Set stream to non-blocking.')
171 parser.add_argument(
172 '-tt',
173 '--track-keywords',
174 help='Search the stream for specific text.')
d51b4107
O
175 parser.add_argument(
176 '-fil',
177 '--filter',
178 help='Filter specific screen_name.')
179 parser.add_argument(
180 '-ig',
181 '--ignore',
182 help='Ignore specific screen_name.')
91476ec3
O
183 return parser.parse_args()
184
185
54277114
O
186def authen():
187 """
7b674cef 188 Authenticate with Twitter OAuth
54277114 189 """
8c840a83 190 # When using rainbow stream you must authorize.
2a6238f5
O
191 twitter_credential = os.environ.get(
192 'HOME',
193 os.environ.get(
194 'USERPROFILE',
195 '')) + os.sep + '.rainbow_oauth'
8c840a83
O
196 if not os.path.exists(twitter_credential):
197 oauth_dance("Rainbow Stream",
198 CONSUMER_KEY,
199 CONSUMER_SECRET,
200 twitter_credential)
201 oauth_token, oauth_token_secret = read_token_file(twitter_credential)
54277114 202 return OAuth(
2a6238f5
O
203 oauth_token,
204 oauth_token_secret,
205 CONSUMER_KEY,
206 CONSUMER_SECRET)
91476ec3 207
54277114
O
208
209def get_decorated_name():
210 """
211 Beginning of every line
212 """
213 t = Twitter(auth=authen())
c5ff542b 214 name = '@' + t.account.verify_credentials()['screen_name']
42fde775 215 g['original_name'] = name[1:]
f405a7d0 216 g['decorated_name'] = grey('[') + grey(name) + grey(']: ')
54277114 217
f405a7d0 218
42fde775 219def switch():
220 """
221 Switch stream
222 """
223 try:
224 target = g['stuff'].split()[0]
225
d51b4107
O
226 # Filter and ignore
227 args = parse_arguments()
7e4ccbf3 228 try:
d51b4107
O
229 if g['stuff'].split()[-1] == '-f':
230 only = raw_input('Only nicks: ')
231 ignore = raw_input('Ignore nicks: ')
7e4ccbf3 232 args.filter = filter(None, only.split(','))
233 args.ignore = filter(None, ignore.split(','))
d51b4107
O
234 elif g['stuff'].split()[-1] == '-d':
235 args.filter = ONLY_LIST
236 args.ignore = IGNORE_LIST
237 except:
238 printNicely(red('Sorry, wrong format.'))
239 return
240
42fde775 241 # Public stream
242 if target == 'public':
243 keyword = g['stuff'].split()[1]
244 if keyword[0] == '#':
245 keyword = keyword[1:]
42fde775 246 # Kill old process
247 os.kill(g['stream_pid'], signal.SIGKILL)
42fde775 248 args.track_keywords = keyword
42fde775 249 # Start new process
250 p = Process(
d51b4107 251 target=stream,
42fde775 252 args=(
d51b4107 253 PUBLIC_DOMAIN,
42fde775 254 args))
255 p.start()
256 g['stream_pid'] = p.pid
257
258 # Personal stream
259 elif target == 'mine':
42fde775 260 # Kill old process
261 os.kill(g['stream_pid'], signal.SIGKILL)
42fde775 262 # Start new process
263 p = Process(
264 target=stream,
265 args=(
266 USER_DOMAIN,
267 args,
268 g['original_name']))
269 p.start()
270 g['stream_pid'] = p.pid
d51b4107 271 printNicely('')
1551a7d3 272 printNicely(green('Stream switched.'))
d51b4107
O
273 if args.filter:
274 printNicely(cyan('Only: ' + str(args.filter)))
275 if args.ignore:
276 printNicely(red('Ignore: ' + str(args.ignore)))
277 printNicely('')
42fde775 278 except:
279 printNicely(red('Sorry I can\'t understand.'))
42fde775 280
281
7b674cef 282def home():
283 """
284 Home
285 """
286 t = Twitter(auth=authen())
94a5f62e 287 num = HOME_TWEET_NUM
7b674cef 288 if g['stuff'].isdigit():
94a5f62e 289 num = g['stuff']
290 for tweet in reversed(t.statuses.home_timeline(count=num)):
7b674cef 291 draw(t=tweet)
94a5f62e 292 printNicely('')
7b674cef 293
294
295def view():
296 """
297 Friend view
298 """
299 t = Twitter(auth=authen())
300 user = g['stuff'].split()[0]
b8fbcb70 301 if user[0] == '@':
302 try:
94a5f62e 303 num = int(g['stuff'].split()[1])
b8fbcb70 304 except:
94a5f62e 305 num = HOME_TWEET_NUM
306 for tweet in reversed(t.statuses.user_timeline(count=num, screen_name=user[1:])):
b8fbcb70 307 draw(t=tweet)
94a5f62e 308 printNicely('')
b8fbcb70 309 else:
c91f75f2 310 printNicely(red('A name should begin with a \'@\''))
7b674cef 311
312
f405a7d0 313def tweet():
54277114 314 """
7b674cef 315 Tweet
54277114
O
316 """
317 t = Twitter(auth=authen())
f405a7d0 318 t.statuses.update(status=g['stuff'])
f405a7d0 319
b2b933a9 320
1ba4abfd
O
321def retweet():
322 """
323 ReTweet
324 """
325 t = Twitter(auth=authen())
326 try:
327 id = int(g['stuff'].split()[0])
328 tid = db.rainbow_query(id)[0].tweet_id
b2b933a9 329 t.statuses.retweet(id=tid, include_entities=False, trim_user=True)
1ba4abfd 330 except:
c91f75f2 331 printNicely(red('Sorry I can\'t retweet for you.'))
1ba4abfd
O
332
333
7e4ccbf3 334def favorite():
335 """
336 Favorite
337 """
338 t = Twitter(auth=authen())
339 try:
340 id = int(g['stuff'].split()[0])
341 tid = db.rainbow_query(id)[0].tweet_id
342 t.favorites.create(_id=tid, include_entities=False)
343 printNicely(green('Favorited.'))
344 draw(t.statuses.show(id=tid))
345 except:
346 printNicely(red('Omg some syntax is wrong.'))
347
348
7b674cef 349def reply():
829cc2d8 350 """
7b674cef 351 Reply
829cc2d8
O
352 """
353 t = Twitter(auth=authen())
7b674cef 354 try:
355 id = int(g['stuff'].split()[0])
18cab06a
O
356 tid = db.rainbow_query(id)[0].tweet_id
357 user = t.statuses.show(id=tid)['user']['screen_name']
7b674cef 358 status = ' '.join(g['stuff'].split()[1:])
359 status = '@' + user + ' ' + status.decode('utf-8')
18cab06a 360 t.statuses.update(status=status, in_reply_to_status_id=tid)
7b674cef 361 except:
c91f75f2 362 printNicely(red('Sorry I can\'t understand.'))
7b674cef 363
364
365def delete():
366 """
367 Delete
368 """
369 t = Twitter(auth=authen())
370 try:
371 id = int(g['stuff'].split()[0])
18cab06a
O
372 tid = db.rainbow_query(id)[0].tweet_id
373 t.statuses.destroy(id=tid)
c91f75f2 374 printNicely(green('Okay it\'s gone.'))
7b674cef 375 except:
c91f75f2 376 printNicely(red('Sorry I can\'t delete this tweet for you.'))
829cc2d8
O
377
378
7e4ccbf3 379def unfavorite():
380 """
381 Unfavorite
382 """
383 t = Twitter(auth=authen())
384 try:
385 id = int(g['stuff'].split()[0])
386 tid = db.rainbow_query(id)[0].tweet_id
387 t.favorites.destroy(_id=tid)
388 printNicely(green('Okay it\'s unfavorited.'))
389 draw(t.statuses.show(id=tid))
390 except:
391 printNicely(red('Sorry I can\'t unfavorite this tweet for you.'))
392
393
f405a7d0
O
394def search():
395 """
7b674cef 396 Search
f405a7d0
O
397 """
398 t = Twitter(auth=authen())
94a5f62e 399 try:
400 if g['stuff'][0] == '#':
401 rel = t.search.tweets(q=g['stuff'])['statuses']
c91f75f2 402 if len(rel):
403 printNicely('Newest tweets:')
404 for i in reversed(xrange(SEARCH_MAX_RECORD)):
405 draw(t=rel[i], keyword=g['stuff'].strip()[1:])
406 printNicely('')
407 else:
408 printNicely(magenta('I\'m afraid there is no result'))
94a5f62e 409 else:
c91f75f2 410 printNicely(red('A keyword should be a hashtag (like \'#AKB48\')'))
94a5f62e 411 except:
c91f75f2 412 printNicely(red('Sorry I can\'t understand.'))
b2b933a9 413
f405a7d0 414
843647ad
O
415def friend():
416 """
417 List of friend (following)
418 """
419 t = Twitter(auth=authen())
420 g['friends'] = t.friends.ids()['ids']
421 for i in g['friends']:
7d1fa112
O
422 name = t.users.lookup(user_id=i)[0]['name']
423 screen_name = '@' + t.users.lookup(user_id=i)[0]['screen_name']
424 user = cycle_color(name) + grey(' ' + screen_name + ' ')
425 print user
843647ad
O
426
427
428def follower():
429 """
430 List of follower
431 """
432 t = Twitter(auth=authen())
433 g['followers'] = t.followers.ids()['ids']
434 for i in g['followers']:
7d1fa112
O
435 name = t.users.lookup(user_id=i)[0]['name']
436 screen_name = '@' + t.users.lookup(user_id=i)[0]['screen_name']
437 user = cycle_color(name) + grey(' ' + screen_name + ' ')
438 print user
843647ad
O
439
440
f405a7d0
O
441def help():
442 """
7b674cef 443 Help
f405a7d0 444 """
7e4ccbf3 445 s = ' ' * 2
446 h, w = os.popen('stty size', 'r').read().split()
e3885f55
O
447
448 usage = '\n'
449 usage += s + 'Hi boss! I\'m ready to serve you right now!\n'
7e4ccbf3 450 usage += s + '-' * (int(w) - 4) + '\n'
451
452 usage += s + 'You are ' + yellow('already') + ' on your personal stream.\n'
453 usage += s * 2 + green('switch public #AKB') + \
454 ' will switch to public stream and follow "' + \
455 yellow('AKB') + '" keyword.\n'
456 usage += s * 2 + green('switch mine') + \
457 ' will switch to your personal stream.\n'
458 usage += s * 2 + green('switch mine -f ') + \
459 ' will prompt to enter the filter.\n'
460 usage += s * 3 + yellow('Only nicks') + \
461 ' filter will decide nicks will be INCLUDE ONLY.\n'
462 usage += s * 3 + yellow('Ignore nicks') + \
463 ' filter will decide nicks will be EXCLUDE.\n'
464 usage += s * 2 + green('switch mine -d') + \
465 ' will use the config\'s ONLY_LIST and IGNORE_LIST.\n'
466 usage += s * 3 + '(see ' + grey('rainbowstream/config.py') + ').\n'
e3885f55
O
467
468 usage += s + 'For more action: \n'
7e4ccbf3 469 usage += s * 2 + green('home') + ' will show your timeline. ' + \
470 green('home 7') + ' will show 7 tweet.\n'
471 usage += s * 2 + green('view @mdo') + \
472 ' will show ' + yellow('@mdo') + '\'s home.\n'
473 usage += s * 2 + green('t oops ') + \
474 'will tweet "' + yellow('oops') + '" immediately.\n'
475 usage += s * 2 + \
476 green('rt 12 ') + ' will retweet to tweet with ' + \
477 yellow('[id=12]') + '.\n'
478 usage += s * 2 + \
479 green('fav 12 ') + ' will favorite the tweet with ' + \
480 yellow('[id=12]') + '.\n'
481 usage += s * 2 + green('rep 12 oops') + ' will reply "' + \
482 yellow('oops') + '" to tweet with ' + yellow('[id=12]') + '.\n'
483 usage += s * 2 + \
484 green('del 12 ') + ' will delete tweet with ' + \
485 yellow('[id=12]') + '.\n'
486 usage += s * 2 + \
487 green('ufav 12 ') + ' will unfavorite tweet with ' + \
488 yellow('[id=12]') + '.\n'
489 usage += s * 2 + green('s #AKB48') + ' will search for "' + \
490 yellow('AKB48') + '" and return 5 newest tweet.\n'
491 usage += s * 2 + green('fr') + ' will list out your following people.\n'
492 usage += s * 2 + green('fl') + ' will list out your follower.\n'
493 usage += s * 2 + green('h') + ' will show this help again.\n'
494 usage += s * 2 + green('c') + ' will clear the screen.\n'
495 usage += s * 2 + green('q') + ' will quit.\n'
496
497 usage += s + '-' * (int(w) - 4) + '\n'
e3885f55 498 usage += s + 'Have fun and hang tight!\n'
f405a7d0 499 printNicely(usage)
f405a7d0
O
500
501
843647ad 502def clear():
f405a7d0 503 """
7b674cef 504 Clear screen
f405a7d0 505 """
843647ad 506 os.system('clear')
f405a7d0
O
507
508
843647ad 509def quit():
b8dda704
O
510 """
511 Exit all
512 """
8e633322 513 os.system('rm -rf rainbow.db')
843647ad
O
514 os.kill(g['stream_pid'], signal.SIGKILL)
515 sys.exit()
b8dda704
O
516
517
94a5f62e 518def reset():
f405a7d0 519 """
94a5f62e 520 Reset prefix of line
f405a7d0 521 """
c91f75f2 522 if g['reset']:
e3885f55 523 printNicely(magenta('Need tips ? Type "h" and hit Enter key!'))
c91f75f2 524 g['reset'] = False
54277114
O
525
526
94a5f62e 527def process(cmd):
54277114 528 """
94a5f62e 529 Process switch
54277114 530 """
94a5f62e 531 return dict(zip(
532 cmdset,
b2b933a9 533 [
42fde775 534 switch,
b2b933a9 535 home,
536 view,
537 tweet,
538 retweet,
7e4ccbf3 539 favorite,
b2b933a9 540 reply,
541 delete,
7e4ccbf3 542 unfavorite,
b2b933a9 543 search,
544 friend,
545 follower,
546 help,
547 clear,
548 quit
549 ]
94a5f62e 550 )).get(cmd, reset)
551
552
553def listen():
42fde775 554 """
555 Listen to user's input
556 """
d51b4107
O
557 d = dict(zip(
558 cmdset,
559 [
7e4ccbf3 560 ['public #', 'mine'], # switch
561 [], # home
562 ['@'], # view
563 [], # tweet
564 [], # retweet
565 [], # reply
566 [], # delete
567 ['#'], # search
568 [], # friend
569 [], # follower
570 [], # help
571 [], # clear
572 [], # quit
d51b4107 573 ]
7e4ccbf3 574 ))
d51b4107 575 init_interactive_shell(d)
819569e8 576 reset()
b2b933a9 577 while True:
1dd312f5
O
578 if g['prefix']:
579 line = raw_input(g['decorated_name'])
580 else:
581 line = raw_input()
843647ad
O
582 try:
583 cmd = line.split()[0]
584 except:
585 cmd = ''
f405a7d0 586 # Save cmd to global variable and call process
843647ad
O
587 g['stuff'] = ' '.join(line.split()[1:])
588 process(cmd)()
7e4ccbf3 589 if cmd in ['switch', 't', 'rt', 'rep']:
1dd312f5
O
590 g['prefix'] = False
591 else:
592 g['prefix'] = True
54277114
O
593
594
42fde775 595def stream(domain, args, name='Rainbow Stream'):
54277114 596 """
f405a7d0 597 Track the stream
54277114 598 """
d51b4107 599
54277114 600 # The Logo
42fde775 601 art_dict = {
602 USER_DOMAIN: name,
603 PUBLIC_DOMAIN: args.track_keywords,
604 SITE_DOMAIN: 'Site Stream',
605 }
606 ascii_art(art_dict[domain])
d51b4107 607
91476ec3
O
608 # These arguments are optional:
609 stream_args = dict(
610 timeout=args.timeout,
611 block=not args.no_block,
612 heartbeat_timeout=args.heartbeat_timeout)
613
614 # Track keyword
615 query_args = dict()
616 if args.track_keywords:
617 query_args['track'] = args.track_keywords
618
619 # Get stream
2a6238f5 620 stream = TwitterStream(
22be990e 621 auth=authen(),
42fde775 622 domain=domain,
2a6238f5 623 **stream_args)
91476ec3 624
42fde775 625 if domain == USER_DOMAIN:
626 tweet_iter = stream.user(**query_args)
627 elif domain == SITE_DOMAIN:
628 tweet_iter = stream.site(**query_args)
629 else:
630 if args.track_keywords:
631 tweet_iter = stream.statuses.filter(**query_args)
632 else:
633 tweet_iter = stream.statuses.sample()
634
635 # Iterate over the stream.
91476ec3
O
636 for tweet in tweet_iter:
637 if tweet is None:
638 printNicely("-- None --")
639 elif tweet is Timeout:
640 printNicely("-- Timeout --")
641 elif tweet is HeartbeatTimeout:
642 printNicely("-- Heartbeat Timeout --")
643 elif tweet is Hangup:
644 printNicely("-- Hangup --")
645 elif tweet.get('text'):
7e4ccbf3 646 draw(
647 t=tweet,
648 keyword=args.track_keywords,
649 fil=args.filter,
650 ig=args.ignore)
54277114
O
651
652
653def fly():
654 """
655 Main function
656 """
42fde775 657 # Spawn stream process
658 args = parse_arguments()
54277114 659 get_decorated_name()
42fde775 660 p = Process(target=stream, args=(USER_DOMAIN, args, g['original_name']))
661 p.start()
662
663 # Start listen process
819569e8 664 time.sleep(0.5)
c91f75f2 665 g['reset'] = True
1dd312f5 666 g['prefix'] = True
f405a7d0 667 g['stream_pid'] = p.pid
94a5f62e 668 listen()