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