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