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