add first tip
[rainbowstream.git] / rainbowstream / rainbow.py
CommitLineData
91476ec3
O
1"""
2Colorful user's timeline stream
3"""
78b81730
O
4from __future__ import print_function
5from multiprocessing import Process
6from dateutil import parser
7
b2b933a9 8import os
9import os.path
10import sys
11import signal
12import argparse
13import time
14import datetime
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
91476ec3 21
2a6238f5
O
22from .colors import *
23from .config import *
94a5f62e 24from .interactive import *
18cab06a 25from .db import *
2a6238f5 26
f405a7d0 27g = {}
18cab06a 28db = RainbowDB()
94a5f62e 29cmdset = [
30 'home',
31 'view',
32 't',
33 'rt',
34 'rep',
35 'del',
36 's',
37 'fr',
38 'fl',
39 'h',
40 'c',
41 'q'
42]
22be990e 43
b2b933a9 44
22be990e 45def draw(t, keyword=None):
91476ec3
O
46 """
47 Draw the rainbow
48 """
49 # Retrieve tweet
829cc2d8 50 tid = t['id']
91476ec3
O
51 text = t['text']
52 screen_name = t['user']['screen_name']
53 name = t['user']['name']
54 created_at = t['created_at']
55 date = parser.parse(created_at)
e20af1c3 56 date = date - datetime.timedelta(seconds=time.timezone)
57 clock = date.strftime('%Y/%m/%d %H:%M:%S')
91476ec3 58
18cab06a
O
59 res = db.tweet_query(tid)
60 if not res:
61 db.store(tid)
62 res = db.tweet_query(tid)
63 rid = res[0].rainbow_id
64
91476ec3 65 # Format info
54277114 66 user = cycle_color(name) + grey(' ' + '@' + screen_name + ' ')
e20af1c3 67 meta = grey('[' + clock + '] [id=' + str(rid) + ']')
8c840a83 68 tweet = text.split()
b8dda704 69 # Highlight RT
2a6238f5 70 tweet = map(lambda x: grey(x) if x == 'RT' else x, tweet)
b8dda704 71 # Highlight screen_name
2a6238f5 72 tweet = map(lambda x: cycle_color(x) if x[0] == '@' else x, tweet)
b8dda704 73 # Highlight link
2a6238f5 74 tweet = map(lambda x: cyan(x) if x[0:7] == 'http://' else x, tweet)
b8dda704 75 # Highlight search keyword
7a431249 76 if keyword:
22be990e 77 tweet = map(
78 lambda x: on_yellow(x) if
79 ''.join(c for c in x if c.isalnum()).lower() == keyword.lower()
80 else x,
81 tweet
82 )
8c840a83 83 tweet = ' '.join(tweet)
91476ec3
O
84
85 # Draw rainbow
06773ffe 86 line1 = u"{u:>{uw}}:".format(
2a6238f5
O
87 u=user,
88 uw=len(user) + 2,
91476ec3 89 )
06773ffe 90 line2 = u"{c:>{cw}}".format(
829cc2d8
O
91 c=meta,
92 cw=len(meta) + 2,
06773ffe
O
93 )
94 line3 = ' ' + tweet
91476ec3 95
94a5f62e 96 printNicely('')
f405a7d0
O
97 printNicely(line1)
98 printNicely(line2)
99 printNicely(line3)
91476ec3
O
100
101
102def parse_arguments():
103 """
104 Parse the arguments
105 """
91476ec3 106 parser = argparse.ArgumentParser(description=__doc__ or "")
2a6238f5
O
107 parser.add_argument(
108 '-to',
109 '--timeout',
110 help='Timeout for the stream (seconds).')
111 parser.add_argument(
112 '-ht',
113 '--heartbeat-timeout',
114 help='Set heartbeat timeout.',
115 default=90)
116 parser.add_argument(
117 '-nb',
118 '--no-block',
119 action='store_true',
120 help='Set stream to non-blocking.')
121 parser.add_argument(
122 '-tt',
123 '--track-keywords',
124 help='Search the stream for specific text.')
91476ec3
O
125 return parser.parse_args()
126
127
54277114
O
128def authen():
129 """
7b674cef 130 Authenticate with Twitter OAuth
54277114 131 """
8c840a83 132 # When using rainbow stream you must authorize.
2a6238f5
O
133 twitter_credential = os.environ.get(
134 'HOME',
135 os.environ.get(
136 'USERPROFILE',
137 '')) + os.sep + '.rainbow_oauth'
8c840a83
O
138 if not os.path.exists(twitter_credential):
139 oauth_dance("Rainbow Stream",
140 CONSUMER_KEY,
141 CONSUMER_SECRET,
142 twitter_credential)
143 oauth_token, oauth_token_secret = read_token_file(twitter_credential)
54277114 144 return OAuth(
2a6238f5
O
145 oauth_token,
146 oauth_token_secret,
147 CONSUMER_KEY,
148 CONSUMER_SECRET)
91476ec3 149
54277114
O
150
151def get_decorated_name():
152 """
153 Beginning of every line
154 """
155 t = Twitter(auth=authen())
c5ff542b 156 name = '@' + t.account.verify_credentials()['screen_name']
f405a7d0 157 g['decorated_name'] = grey('[') + grey(name) + grey(']: ')
54277114 158
f405a7d0 159
7b674cef 160def home():
161 """
162 Home
163 """
164 t = Twitter(auth=authen())
94a5f62e 165 num = HOME_TWEET_NUM
7b674cef 166 if g['stuff'].isdigit():
94a5f62e 167 num = g['stuff']
168 for tweet in reversed(t.statuses.home_timeline(count=num)):
7b674cef 169 draw(t=tweet)
94a5f62e 170 printNicely('')
7b674cef 171
172
173def view():
174 """
175 Friend view
176 """
177 t = Twitter(auth=authen())
178 user = g['stuff'].split()[0]
b8fbcb70 179 if user[0] == '@':
180 try:
94a5f62e 181 num = int(g['stuff'].split()[1])
b8fbcb70 182 except:
94a5f62e 183 num = HOME_TWEET_NUM
184 for tweet in reversed(t.statuses.user_timeline(count=num, screen_name=user[1:])):
b8fbcb70 185 draw(t=tweet)
94a5f62e 186 printNicely('')
b8fbcb70 187 else:
c91f75f2 188 printNicely(red('A name should begin with a \'@\''))
7b674cef 189
190
f405a7d0 191def tweet():
54277114 192 """
7b674cef 193 Tweet
54277114
O
194 """
195 t = Twitter(auth=authen())
f405a7d0 196 t.statuses.update(status=g['stuff'])
94a5f62e 197 g['prefix'] = False
f405a7d0 198
b2b933a9 199
1ba4abfd
O
200def retweet():
201 """
202 ReTweet
203 """
204 t = Twitter(auth=authen())
205 try:
206 id = int(g['stuff'].split()[0])
207 tid = db.rainbow_query(id)[0].tweet_id
b2b933a9 208 t.statuses.retweet(id=tid, include_entities=False, trim_user=True)
1ba4abfd 209 except:
c91f75f2 210 printNicely(red('Sorry I can\'t retweet for you.'))
94a5f62e 211 g['prefix'] = False
1ba4abfd
O
212
213
7b674cef 214def reply():
829cc2d8 215 """
7b674cef 216 Reply
829cc2d8
O
217 """
218 t = Twitter(auth=authen())
7b674cef 219 try:
220 id = int(g['stuff'].split()[0])
18cab06a
O
221 tid = db.rainbow_query(id)[0].tweet_id
222 user = t.statuses.show(id=tid)['user']['screen_name']
7b674cef 223 status = ' '.join(g['stuff'].split()[1:])
224 status = '@' + user + ' ' + status.decode('utf-8')
18cab06a 225 t.statuses.update(status=status, in_reply_to_status_id=tid)
7b674cef 226 except:
c91f75f2 227 printNicely(red('Sorry I can\'t understand.'))
94a5f62e 228 g['prefix'] = False
7b674cef 229
230
231def delete():
232 """
233 Delete
234 """
235 t = Twitter(auth=authen())
236 try:
237 id = int(g['stuff'].split()[0])
18cab06a
O
238 tid = db.rainbow_query(id)[0].tweet_id
239 t.statuses.destroy(id=tid)
c91f75f2 240 printNicely(green('Okay it\'s gone.'))
7b674cef 241 except:
c91f75f2 242 printNicely(red('Sorry I can\'t delete this tweet for you.'))
829cc2d8
O
243
244
f405a7d0
O
245def search():
246 """
7b674cef 247 Search
f405a7d0
O
248 """
249 t = Twitter(auth=authen())
94a5f62e 250 try:
251 if g['stuff'][0] == '#':
252 rel = t.search.tweets(q=g['stuff'])['statuses']
c91f75f2 253 if len(rel):
254 printNicely('Newest tweets:')
255 for i in reversed(xrange(SEARCH_MAX_RECORD)):
256 draw(t=rel[i], keyword=g['stuff'].strip()[1:])
257 printNicely('')
258 else:
259 printNicely(magenta('I\'m afraid there is no result'))
94a5f62e 260 else:
c91f75f2 261 printNicely(red('A keyword should be a hashtag (like \'#AKB48\')'))
94a5f62e 262 except:
c91f75f2 263 printNicely(red('Sorry I can\'t understand.'))
b2b933a9 264
f405a7d0 265
843647ad
O
266def friend():
267 """
268 List of friend (following)
269 """
270 t = Twitter(auth=authen())
271 g['friends'] = t.friends.ids()['ids']
272 for i in g['friends']:
273 screen_name = t.users.lookup(user_id=i)[0]['screen_name']
22be990e 274 user = cycle_color('@' + screen_name)
843647ad 275 print(user, end=' ')
c91f75f2 276 printNicely('');
843647ad
O
277
278
279def follower():
280 """
281 List of follower
282 """
283 t = Twitter(auth=authen())
284 g['followers'] = t.followers.ids()['ids']
285 for i in g['followers']:
286 screen_name = t.users.lookup(user_id=i)[0]['screen_name']
22be990e 287 user = cycle_color('@' + screen_name)
843647ad 288 print(user, end=' ')
c91f75f2 289 printNicely('');
843647ad
O
290
291
f405a7d0
O
292def help():
293 """
7b674cef 294 Help
f405a7d0
O
295 """
296 usage = '''
297 Hi boss! I'm ready to serve you right now!
006f8cc4 298 -------------------------------------------------------------
c8f6a173 299 "home" will show your timeline. "home 7" will show 7 tweet.
b8fbcb70 300 "view @bob" will show your friend @bob's home.
7b674cef 301 "t oops" will tweet "oops" immediately.
1ba4abfd 302 "rt 12345" will retweet to tweet with id "12345".
a30eeccb 303 "rep 12345 oops" will reply "oops" to tweet with id "12345".
7b674cef 304 "del 12345" will delete tweet with id "12345".
b8fbcb70 305 "s #AKB48" will search for "AKB48" and return 5 newest tweet.
a30eeccb 306 "fr" will list out your following people.
307 "fl" will list out your followers.
c8f6a173 308 "h" will show this help again.
a30eeccb 309 "c" will clear the terminal.
310 "q" will exit.
006f8cc4 311 -------------------------------------------------------------
843647ad 312 Have fun and hang tight!
f405a7d0
O
313 '''
314 printNicely(usage)
f405a7d0
O
315
316
843647ad 317def clear():
f405a7d0 318 """
7b674cef 319 Clear screen
f405a7d0 320 """
843647ad 321 os.system('clear')
7186f557 322 g['prefix'] = False
f405a7d0
O
323
324
843647ad 325def quit():
b8dda704
O
326 """
327 Exit all
328 """
8e633322 329 os.system('rm -rf rainbow.db')
843647ad
O
330 os.kill(g['stream_pid'], signal.SIGKILL)
331 sys.exit()
b8dda704
O
332
333
94a5f62e 334def reset():
f405a7d0 335 """
94a5f62e 336 Reset prefix of line
f405a7d0 337 """
c91f75f2 338 if g['reset']:
339 printNicely(green('Need tips ? Type "h" and hit Enter key!'))
94a5f62e 340 g['prefix'] = True
c91f75f2 341 g['reset'] = False
54277114
O
342
343
94a5f62e 344def process(cmd):
54277114 345 """
94a5f62e 346 Process switch
54277114 347 """
94a5f62e 348 return dict(zip(
349 cmdset,
b2b933a9 350 [
351 home,
352 view,
353 tweet,
354 retweet,
355 reply,
356 delete,
357 search,
358 friend,
359 follower,
360 help,
361 clear,
362 quit
363 ]
94a5f62e 364 )).get(cmd, reset)
365
366
367def listen():
368 init_interactive_shell(cmdset)
b2b933a9 369 first = True
370 while True:
94a5f62e 371 if g['prefix'] and not first:
372 line = raw_input(g['decorated_name'])
373 else:
374 line = raw_input()
843647ad
O
375 try:
376 cmd = line.split()[0]
377 except:
378 cmd = ''
f405a7d0 379 # Save cmd to global variable and call process
843647ad
O
380 g['stuff'] = ' '.join(line.split()[1:])
381 process(cmd)()
94a5f62e 382 first = False
54277114
O
383
384
385def stream():
386 """
f405a7d0 387 Track the stream
54277114
O
388 """
389 args = parse_arguments()
390
391 # The Logo
392 ascii_art()
91476ec3
O
393 # These arguments are optional:
394 stream_args = dict(
395 timeout=args.timeout,
396 block=not args.no_block,
397 heartbeat_timeout=args.heartbeat_timeout)
398
399 # Track keyword
400 query_args = dict()
401 if args.track_keywords:
402 query_args['track'] = args.track_keywords
403
404 # Get stream
2a6238f5 405 stream = TwitterStream(
22be990e 406 auth=authen(),
c0440e50 407 domain=DOMAIN,
2a6238f5 408 **stream_args)
91476ec3
O
409 tweet_iter = stream.user(**query_args)
410
411 # Iterate over the sample stream.
412 for tweet in tweet_iter:
413 if tweet is None:
414 printNicely("-- None --")
415 elif tweet is Timeout:
416 printNicely("-- Timeout --")
417 elif tweet is HeartbeatTimeout:
418 printNicely("-- Heartbeat Timeout --")
419 elif tweet is Hangup:
420 printNicely("-- Hangup --")
421 elif tweet.get('text'):
f405a7d0 422 draw(t=tweet)
54277114
O
423
424
425def fly():
426 """
427 Main function
428 """
429 get_decorated_name()
94a5f62e 430 g['prefix'] = True
c91f75f2 431 g['reset'] = True
22be990e 432 p = Process(target=stream)
54277114 433 p.start()
f405a7d0 434 g['stream_pid'] = p.pid
94a5f62e 435 listen()