b859bab832bbe13276a794c321470b416957756d
[rainbowstream.git] / rainbowstream / rainbow.py
1 import os
2 import os.path
3 import sys
4 import signal
5 import argparse
6 import time
7 import threading
8 import requests
9 import webbrowser
10 import traceback
11 import pkg_resources
12 import socks
13 import socket
14
15 from io import BytesIO
16 from twitter.stream import TwitterStream, Timeout, HeartbeatTimeout, Hangup
17 from twitter.api import *
18 from twitter.oauth import OAuth, read_token_file
19 from twitter.oauth_dance import oauth_dance
20 from twitter.util import printNicely
21
22 from .draw import *
23 from .colors import *
24 from .config import *
25 from .consumer import *
26 from .interactive import *
27 from .c_image import *
28 from .py3patch import *
29 from .emoji import *
30 from .util import *
31
32 # Global values
33 g = {}
34
35 # Lock for streams
36 StreamLock = threading.Lock()
37
38
39 def parse_arguments():
40 """
41 Parse the arguments
42 """
43 parser = argparse.ArgumentParser(description=__doc__ or "")
44 parser.add_argument(
45 '-to',
46 '--timeout',
47 help='Timeout for the stream (seconds).')
48 parser.add_argument(
49 '-tt',
50 '--track-keywords',
51 help='Search the stream for specific text.')
52 parser.add_argument(
53 '-fil',
54 '--filter',
55 help='Filter specific screen_name.')
56 parser.add_argument(
57 '-ig',
58 '--ignore',
59 help='Ignore specific screen_name.')
60 parser.add_argument(
61 '-iot',
62 '--image-on-term',
63 action='store_true',
64 help='Display all image on terminal.')
65 parser.add_argument(
66 '-ph',
67 '--proxy-host',
68 help='Use HTTP/SOCKS proxy for network connections.')
69 parser.add_argument(
70 '-pp',
71 '--proxy-port',
72 default=8080,
73 help='HTTP/SOCKS proxy port (Default: 8080).')
74 parser.add_argument(
75 '-pt',
76 '--proxy-type',
77 default='SOCKS5',
78 help='Proxy type (HTTP, SOCKS4, SOCKS5; Default: SOCKS5).')
79 return parser.parse_args()
80
81
82 def proxy_connect(args):
83 """
84 Connect to specified proxy
85 """
86 if args.proxy_host:
87 # Setup proxy by monkeypatching the standard lib
88 if args.proxy_type.lower() == "socks5" or not args.proxy_type:
89 socks.set_default_proxy(
90 socks.SOCKS5, args.proxy_host,
91 int(args.proxy_port))
92 elif args.proxy_type.lower() == "http":
93 socks.set_default_proxy(
94 socks.HTTP, args.proxy_host,
95 int(args.proxy_port))
96 elif args.proxy_type.lower() == "socks4":
97 socks.set_default_proxy(
98 socks.SOCKS4, args.proxy_host,
99 int(args.proxy_port))
100 else:
101 printNicely(
102 magenta('Sorry, wrong proxy type specified! Aborting...'))
103 sys.exit()
104 socket.socket = socks.socksocket
105
106
107 def authen():
108 """
109 Authenticate with Twitter OAuth
110 """
111 # When using rainbow stream you must authorize.
112 twitter_credential = os.environ.get(
113 'HOME',
114 os.environ.get(
115 'USERPROFILE',
116 '')) + os.sep + '.rainbow_oauth'
117 if not os.path.exists(twitter_credential):
118 oauth_dance('Rainbow Stream',
119 CONSUMER_KEY,
120 CONSUMER_SECRET,
121 twitter_credential)
122 oauth_token, oauth_token_secret = read_token_file(twitter_credential)
123 return OAuth(
124 oauth_token,
125 oauth_token_secret,
126 CONSUMER_KEY,
127 CONSUMER_SECRET)
128
129
130 def build_mute_dict(dict_data=False):
131 """
132 Build muting list
133 """
134 t = Twitter(auth=authen())
135 # Init cursor
136 next_cursor = -1
137 screen_name_list = []
138 name_list = []
139 # Cursor loop
140 while next_cursor != 0:
141 list = t.mutes.users.list(
142 screen_name=g['original_name'],
143 cursor=next_cursor,
144 skip_status=True,
145 include_entities=False,
146 )
147 screen_name_list += ['@' + u['screen_name'] for u in list['users']]
148 name_list += [u['name'] for u in list['users']]
149 next_cursor = list['next_cursor']
150 # Return dict or list
151 if dict_data:
152 return dict(zip(screen_name_list, name_list))
153 else:
154 return screen_name_list
155
156
157 def debug_option():
158 """
159 Save traceback when run in debug mode
160 """
161 if g['debug']:
162 g['traceback'].append(traceback.format_exc())
163
164
165 def upgrade_center():
166 """
167 Check latest and notify to upgrade
168 """
169 try:
170 current = pkg_resources.get_distribution('rainbowstream').version
171 url = 'https://raw.githubusercontent.com/DTVD/rainbowstream/master/setup.py'
172 readme = requests.get(url).text
173 latest = readme.split('version = \'')[1].split('\'')[0]
174 if current != latest:
175 notice = light_magenta('RainbowStream latest version is ')
176 notice += light_green(latest)
177 notice += light_magenta(' while your current version is ')
178 notice += light_yellow(current) + '\n'
179 notice += light_magenta('You should upgrade with ')
180 notice += light_green('pip install -U rainbowstream')
181 printNicely(notice)
182 else:
183 notice = light_yellow('You are running latest version (')
184 notice += light_green(current)
185 notice += light_yellow(')')
186 printNicely(notice)
187 except:
188 pass
189
190
191 def init(args):
192 """
193 Init function
194 """
195 # Handle Ctrl C
196 ctrl_c_handler = lambda signum, frame: quit()
197 signal.signal(signal.SIGINT, ctrl_c_handler)
198 # Upgrade notify
199 upgrade_center()
200 # Get name
201 t = Twitter(auth=authen())
202 credential = t.account.verify_credentials()
203 screen_name = '@' + credential['screen_name']
204 name = credential['name']
205 c['original_name'] = g['original_name'] = screen_name[1:]
206 g['listname'] = g['keyword'] = ''
207 g['PREFIX'] = u2str(emojize(format_prefix()))
208 g['full_name'] = name
209 g['decorated_name'] = lambda x: color_func(
210 c['DECORATED_NAME'])('[' + x + ']: ', rl=True)
211 # Theme init
212 files = os.listdir(os.path.dirname(__file__) + '/colorset')
213 themes = [f.split('.')[0] for f in files if f.split('.')[-1] == 'json']
214 g['themes'] = themes
215 g['pause'] = False
216 g['message_threads'] = {}
217 # Startup cmd
218 g['cmd'] = ''
219 # Debug option default = True
220 g['debug'] = True
221 g['traceback'] = []
222 # Events
223 c['events'] = []
224 # Semaphore init
225 c['lock'] = False
226 # Init tweet dict and message dict
227 c['tweet_dict'] = []
228 c['message_dict'] = []
229 # Image on term
230 c['IMAGE_ON_TERM'] = args.image_on_term
231 set_config('IMAGE_ON_TERM', str(c['IMAGE_ON_TERM']))
232 # Check type of ONLY_LIST and IGNORE_LIST
233 if not isinstance(c['ONLY_LIST'], list):
234 printNicely(red('ONLY_LIST is not a valid list value.'))
235 c['ONLY_LIST'] = []
236 if not isinstance(c['IGNORE_LIST'], list):
237 printNicely(red('IGNORE_LIST is not a valid list value.'))
238 c['IGNORE_LIST'] = []
239 # Mute dict
240 c['IGNORE_LIST'] += build_mute_dict()
241
242
243 def trend():
244 """
245 Trend
246 """
247 t = Twitter(auth=authen())
248 # Get country and town
249 try:
250 country = g['stuff'].split()[0]
251 except:
252 country = ''
253 try:
254 town = g['stuff'].split()[1]
255 except:
256 town = ''
257 avail = t.trends.available()
258 # World wide
259 if not country:
260 trends = t.trends.place(_id=1)[0]['trends']
261 print_trends(trends)
262 else:
263 for location in avail:
264 # Search for country and Town
265 if town:
266 if location['countryCode'] == country \
267 and location['placeType']['name'] == 'Town' \
268 and location['name'] == town:
269 trends = t.trends.place(_id=location['woeid'])[0]['trends']
270 print_trends(trends)
271 # Search for country only
272 else:
273 if location['countryCode'] == country \
274 and location['placeType']['name'] == 'Country':
275 trends = t.trends.place(_id=location['woeid'])[0]['trends']
276 print_trends(trends)
277
278
279 def home():
280 """
281 Home
282 """
283 t = Twitter(auth=authen())
284 num = c['HOME_TWEET_NUM']
285 if g['stuff'].isdigit():
286 num = int(g['stuff'])
287 for tweet in reversed(t.statuses.home_timeline(count=num)):
288 draw(t=tweet)
289 printNicely('')
290
291
292 def notification():
293 """
294 Show notifications
295 """
296 if c['events']:
297 for e in c['events']:
298 print_event(e)
299 printNicely('')
300 else:
301 printNicely(magenta('Nothing at this time.'))
302
303
304 def mentions():
305 """
306 Mentions timeline
307 """
308 t = Twitter(auth=authen())
309 num = c['HOME_TWEET_NUM']
310 if g['stuff'].isdigit():
311 num = int(g['stuff'])
312 for tweet in reversed(t.statuses.mentions_timeline(count=num)):
313 draw(t=tweet)
314 printNicely('')
315
316
317 def whois():
318 """
319 Show profile of a specific user
320 """
321 t = Twitter(auth=authen())
322 screen_name = g['stuff'].split()[0]
323 if screen_name.startswith('@'):
324 try:
325 user = t.users.show(
326 screen_name=screen_name[1:],
327 include_entities=False)
328 show_profile(user)
329 except:
330 debug_option()
331 printNicely(red('No user.'))
332 else:
333 printNicely(red('A name should begin with a \'@\''))
334
335
336 def view():
337 """
338 Friend view
339 """
340 t = Twitter(auth=authen())
341 user = g['stuff'].split()[0]
342 if user[0] == '@':
343 try:
344 num = int(g['stuff'].split()[1])
345 except:
346 num = c['HOME_TWEET_NUM']
347 for tweet in reversed(
348 t.statuses.user_timeline(count=num, screen_name=user[1:])):
349 draw(t=tweet)
350 printNicely('')
351 else:
352 printNicely(red('A name should begin with a \'@\''))
353
354
355 def search():
356 """
357 Search
358 """
359 t = Twitter(auth=authen())
360 # Setup query
361 query = g['stuff'].strip()
362 type = c['SEARCH_TYPE']
363 if type not in ['mixed', 'recent', 'popular']:
364 type = 'mixed'
365 max_record = c['SEARCH_MAX_RECORD']
366 count = min(max_record, 100)
367 # Perform search
368 rel = t.search.tweets(
369 q=query,
370 type=type,
371 count=count
372 )['statuses']
373 # Return results
374 if rel:
375 printNicely('Newest tweets:')
376 for i in reversed(xrange(count)):
377 draw(t=rel[i], keyword=query)
378 printNicely('')
379 else:
380 printNicely(magenta('I\'m afraid there is no result'))
381
382
383 def tweet():
384 """
385 Tweet
386 """
387 t = Twitter(auth=authen())
388 t.statuses.update(status=g['stuff'])
389
390
391 def retweet():
392 """
393 ReTweet
394 """
395 t = Twitter(auth=authen())
396 try:
397 id = int(g['stuff'].split()[0])
398 except:
399 printNicely(red('Sorry I can\'t understand.'))
400 return
401 tid = c['tweet_dict'][id]
402 t.statuses.retweet(id=tid, include_entities=False, trim_user=True)
403
404
405 def quote():
406 """
407 Quote a tweet
408 """
409 # Get tweet
410 t = Twitter(auth=authen())
411 try:
412 id = int(g['stuff'].split()[0])
413 except:
414 printNicely(red('Sorry I can\'t understand.'))
415 return
416 tid = c['tweet_dict'][id]
417 tweet = t.statuses.show(id=tid)
418 # Get formater
419 formater = format_quote(tweet)
420 if not formater:
421 return
422 # Get comment
423 prefix = light_magenta('Compose your ', rl=True) + \
424 light_green('#comment: ', rl=True)
425 comment = raw_input(prefix)
426 if comment:
427 quote = comment.join(formater.split('#comment'))
428 t.statuses.update(status=quote)
429 else:
430 printNicely(light_magenta('No text added.'))
431
432
433 def allretweet():
434 """
435 List all retweet
436 """
437 t = Twitter(auth=authen())
438 # Get rainbow id
439 try:
440 id = int(g['stuff'].split()[0])
441 except:
442 printNicely(red('Sorry I can\'t understand.'))
443 return
444 tid = c['tweet_dict'][id]
445 # Get display num if exist
446 try:
447 num = int(g['stuff'].split()[1])
448 except:
449 num = c['RETWEETS_SHOW_NUM']
450 # Get result and display
451 rt_ary = t.statuses.retweets(id=tid, count=num)
452 if not rt_ary:
453 printNicely(magenta('This tweet has no retweet.'))
454 return
455 for tweet in reversed(rt_ary):
456 draw(t=tweet)
457 printNicely('')
458
459
460 def conversation():
461 """
462 Conversation view
463 """
464 t = Twitter(auth=authen())
465 try:
466 id = int(g['stuff'].split()[0])
467 except:
468 printNicely(red('Sorry I can\'t understand.'))
469 return
470 tid = c['tweet_dict'][id]
471 tweet = t.statuses.show(id=tid)
472 limit = c['CONVERSATION_MAX']
473 thread_ref = []
474 thread_ref.append(tweet)
475 prev_tid = tweet['in_reply_to_status_id']
476 while prev_tid and limit:
477 limit -= 1
478 tweet = t.statuses.show(id=prev_tid)
479 prev_tid = tweet['in_reply_to_status_id']
480 thread_ref.append(tweet)
481
482 for tweet in reversed(thread_ref):
483 draw(t=tweet)
484 printNicely('')
485
486
487 def reply():
488 """
489 Reply
490 """
491 t = Twitter(auth=authen())
492 try:
493 id = int(g['stuff'].split()[0])
494 except:
495 printNicely(red('Sorry I can\'t understand.'))
496 return
497 tid = c['tweet_dict'][id]
498 user = t.statuses.show(id=tid)['user']['screen_name']
499 status = ' '.join(g['stuff'].split()[1:])
500 status = '@' + user + ' ' + str2u(status)
501 t.statuses.update(status=status, in_reply_to_status_id=tid)
502
503
504 def favorite():
505 """
506 Favorite
507 """
508 t = Twitter(auth=authen())
509 try:
510 id = int(g['stuff'].split()[0])
511 except:
512 printNicely(red('Sorry I can\'t understand.'))
513 return
514 tid = c['tweet_dict'][id]
515 t.favorites.create(_id=tid, include_entities=False)
516 printNicely(green('Favorited.'))
517 draw(t.statuses.show(id=tid))
518 printNicely('')
519
520
521 def unfavorite():
522 """
523 Unfavorite
524 """
525 t = Twitter(auth=authen())
526 try:
527 id = int(g['stuff'].split()[0])
528 except:
529 printNicely(red('Sorry I can\'t understand.'))
530 return
531 tid = c['tweet_dict'][id]
532 t.favorites.destroy(_id=tid)
533 printNicely(green('Okay it\'s unfavorited.'))
534 draw(t.statuses.show(id=tid))
535 printNicely('')
536
537
538 def share():
539 """
540 Copy url of a tweet to clipboard
541 """
542 t = Twitter(auth=authen())
543 try:
544 id = int(g['stuff'].split()[0])
545 tid = c['tweet_dict'][id]
546 except:
547 printNicely(red('Tweet id is not valid.'))
548 return
549 tweet = t.statuses.show(id=tid)
550 url = 'https://twitter.com/' + \
551 tweet['user']['screen_name'] + '/status/' + str(tid)
552 import platform
553 if platform.system().lower() == 'darwin':
554 os.system("echo '%s' | pbcopy" % url)
555 printNicely(green('Copied tweet\'s url to clipboard.'))
556 else:
557 printNicely('Direct link: ' + yellow(url))
558
559
560 def delete():
561 """
562 Delete
563 """
564 t = Twitter(auth=authen())
565 try:
566 id = int(g['stuff'].split()[0])
567 except:
568 printNicely(red('Sorry I can\'t understand.'))
569 return
570 tid = c['tweet_dict'][id]
571 t.statuses.destroy(id=tid)
572 printNicely(green('Okay it\'s gone.'))
573
574
575 def show():
576 """
577 Show image
578 """
579 t = Twitter(auth=authen())
580 try:
581 target = g['stuff'].split()[0]
582 if target != 'image':
583 return
584 id = int(g['stuff'].split()[1])
585 tid = c['tweet_dict'][id]
586 tweet = t.statuses.show(id=tid)
587 media = tweet['entities']['media']
588 for m in media:
589 res = requests.get(m['media_url'])
590 img = Image.open(BytesIO(res.content))
591 img.show()
592 except:
593 debug_option()
594 printNicely(red('Sorry I can\'t show this image.'))
595
596
597 def urlopen():
598 """
599 Open url
600 """
601 t = Twitter(auth=authen())
602 try:
603 if not g['stuff'].isdigit():
604 return
605 tid = c['tweet_dict'][int(g['stuff'])]
606 tweet = t.statuses.show(id=tid)
607 link_prefix = ('http://', 'https://')
608 link_ary = [u for u in tweet['text'].split()
609 if u.startswith(link_prefix)]
610 if not link_ary:
611 printNicely(light_magenta('No url here @.@!'))
612 return
613 for link in link_ary:
614 webbrowser.open(link)
615 except:
616 debug_option()
617 printNicely(red('Sorry I can\'t open url in this tweet.'))
618
619
620 def inbox():
621 """
622 Inbox threads
623 """
624 t = Twitter(auth=authen())
625 num = c['MESSAGES_DISPLAY']
626 if g['stuff'].isdigit():
627 num = g['stuff']
628 # Get inbox messages
629 cur_page = 1
630 inbox = []
631 while num > 20:
632 inbox = inbox + t.direct_messages(
633 count=20,
634 page=cur_page,
635 include_entities=False,
636 skip_status=False
637 )
638 num -= 20
639 cur_page += 1
640 inbox = inbox + t.direct_messages(
641 count=num,
642 page=cur_page,
643 include_entities=False,
644 skip_status=False
645 )
646 # Get sent messages
647 num = c['MESSAGES_DISPLAY']
648 if g['stuff'].isdigit():
649 num = g['stuff']
650 cur_page = 1
651 sent = []
652 while num > 20:
653 sent = sent + t.direct_messages.sent(
654 count=20,
655 page=cur_page,
656 include_entities=False,
657 skip_status=False
658 )
659 num -= 20
660 cur_page += 1
661 sent = sent + t.direct_messages.sent(
662 count=num,
663 page=cur_page,
664 include_entities=False,
665 skip_status=False
666 )
667
668 d = {}
669 uniq_inbox = list(set(
670 [(m['sender_screen_name'], m['sender']['name']) for m in inbox]
671 ))
672 uniq_sent = list(set(
673 [(m['recipient_screen_name'], m['recipient']['name']) for m in sent]
674 ))
675 for partner in uniq_inbox:
676 inbox_ary = [m for m in inbox if m['sender_screen_name'] == partner[0]]
677 sent_ary = [
678 m for m in sent if m['recipient_screen_name'] == partner[0]]
679 d[partner] = inbox_ary + sent_ary
680 for partner in uniq_sent:
681 if partner not in d:
682 d[partner] = [
683 m for m in sent if m['recipient_screen_name'] == partner[0]]
684 g['message_threads'] = print_threads(d)
685
686
687 def thread():
688 """
689 View a thread of message
690 """
691 try:
692 thread_id = int(g['stuff'])
693 print_thread(
694 g['message_threads'][thread_id],
695 g['original_name'],
696 g['full_name'])
697 except Exception:
698 debug_option()
699 printNicely(red('No such thread.'))
700
701
702 def message():
703 """
704 Send a direct message
705 """
706 t = Twitter(auth=authen())
707 try:
708 user = g['stuff'].split()[0]
709 if user[0].startswith('@'):
710 content = ' '.join(g['stuff'].split()[1:])
711 t.direct_messages.new(
712 screen_name=user[1:],
713 text=content
714 )
715 printNicely(green('Message sent.'))
716 else:
717 printNicely(red('A name should begin with a \'@\''))
718 except:
719 debug_option()
720 printNicely(red('Sorry I can\'t understand.'))
721
722
723 def trash():
724 """
725 Remove message
726 """
727 t = Twitter(auth=authen())
728 try:
729 id = int(g['stuff'].split()[0])
730 except:
731 printNicely(red('Sorry I can\'t understand.'))
732 mid = c['message_dict'][id]
733 t.direct_messages.destroy(id=mid)
734 printNicely(green('Message deleted.'))
735
736
737 def ls():
738 """
739 List friends for followers
740 """
741 t = Twitter(auth=authen())
742 # Get name
743 try:
744 name = g['stuff'].split()[1]
745 if name.startswith('@'):
746 name = name[1:]
747 else:
748 printNicely(red('A name should begin with a \'@\''))
749 raise Exception('Invalid name')
750 except:
751 name = g['original_name']
752 # Get list followers or friends
753 try:
754 target = g['stuff'].split()[0]
755 except:
756 printNicely(red('Omg some syntax is wrong.'))
757 # Init cursor
758 d = {'fl': 'followers', 'fr': 'friends'}
759 next_cursor = -1
760 rel = {}
761 # Cursor loop
762 while next_cursor != 0:
763 list = getattr(t, d[target]).list(
764 screen_name=name,
765 cursor=next_cursor,
766 skip_status=True,
767 include_entities=False,
768 )
769 for u in list['users']:
770 rel[u['name']] = '@' + u['screen_name']
771 next_cursor = list['next_cursor']
772 # Print out result
773 printNicely('All: ' + str(len(rel)) + ' ' + d[target] + '.')
774 for name in rel:
775 user = ' ' + cycle_color(name)
776 user += color_func(c['TWEET']['nick'])(' ' + rel[name] + ' ')
777 printNicely(user)
778
779
780 def follow():
781 """
782 Follow a user
783 """
784 t = Twitter(auth=authen())
785 screen_name = g['stuff'].split()[0]
786 if screen_name.startswith('@'):
787 t.friendships.create(screen_name=screen_name[1:], follow=True)
788 printNicely(green('You are following ' + screen_name + ' now!'))
789 else:
790 printNicely(red('A name should begin with a \'@\''))
791
792
793 def unfollow():
794 """
795 Unfollow a user
796 """
797 t = Twitter(auth=authen())
798 screen_name = g['stuff'].split()[0]
799 if screen_name.startswith('@'):
800 t.friendships.destroy(
801 screen_name=screen_name[1:],
802 include_entities=False)
803 printNicely(green('Unfollow ' + screen_name + ' success!'))
804 else:
805 printNicely(red('A name should begin with a \'@\''))
806
807
808 def mute():
809 """
810 Mute a user
811 """
812 t = Twitter(auth=authen())
813 try:
814 screen_name = g['stuff'].split()[0]
815 except:
816 printNicely(red('A name should be specified. '))
817 return
818 if screen_name.startswith('@'):
819 try:
820 rel = t.mutes.users.create(screen_name=screen_name[1:])
821 if isinstance(rel, dict):
822 printNicely(green(screen_name + ' is muted.'))
823 c['IGNORE_LIST'] += [unc(screen_name)]
824 c['IGNORE_LIST'] = list(set(c['IGNORE_LIST']))
825 else:
826 printNicely(red(rel))
827 except:
828 debug_option()
829 printNicely(red('Something is wrong, can not mute now :('))
830 else:
831 printNicely(red('A name should begin with a \'@\''))
832
833
834 def unmute():
835 """
836 Unmute a user
837 """
838 t = Twitter(auth=authen())
839 try:
840 screen_name = g['stuff'].split()[0]
841 except:
842 printNicely(red('A name should be specified. '))
843 return
844 if screen_name.startswith('@'):
845 try:
846 rel = t.mutes.users.destroy(screen_name=screen_name[1:])
847 if isinstance(rel, dict):
848 printNicely(green(screen_name + ' is unmuted.'))
849 c['IGNORE_LIST'].remove(screen_name)
850 else:
851 printNicely(red(rel))
852 except:
853 printNicely(red('Maybe you are not muting this person ?'))
854 else:
855 printNicely(red('A name should begin with a \'@\''))
856
857
858 def muting():
859 """
860 List muting user
861 """
862 # Get dict of muting users
863 md = build_mute_dict(dict_data=True)
864 printNicely('All: ' + str(len(md)) + ' people.')
865 for name in md:
866 user = ' ' + cycle_color(md[name])
867 user += color_func(c['TWEET']['nick'])(' ' + name + ' ')
868 printNicely(user)
869 # Update from Twitter
870 c['IGNORE_LIST'] = [n for n in md]
871
872
873 def block():
874 """
875 Block a user
876 """
877 t = Twitter(auth=authen())
878 screen_name = g['stuff'].split()[0]
879 if screen_name.startswith('@'):
880 t.blocks.create(
881 screen_name=screen_name[1:],
882 include_entities=False,
883 skip_status=True)
884 printNicely(green('You blocked ' + screen_name + '.'))
885 else:
886 printNicely(red('A name should begin with a \'@\''))
887
888
889 def unblock():
890 """
891 Unblock a user
892 """
893 t = Twitter(auth=authen())
894 screen_name = g['stuff'].split()[0]
895 if screen_name.startswith('@'):
896 t.blocks.destroy(
897 screen_name=screen_name[1:],
898 include_entities=False,
899 skip_status=True)
900 printNicely(green('Unblock ' + screen_name + ' success!'))
901 else:
902 printNicely(red('A name should begin with a \'@\''))
903
904
905 def report():
906 """
907 Report a user as a spam account
908 """
909 t = Twitter(auth=authen())
910 screen_name = g['stuff'].split()[0]
911 if screen_name.startswith('@'):
912 t.users.report_spam(
913 screen_name=screen_name[1:])
914 printNicely(green('You reported ' + screen_name + '.'))
915 else:
916 printNicely(red('Sorry I can\'t understand.'))
917
918
919 def get_slug():
920 """
921 Get slug
922 """
923 # Get list name
924 list_name = raw_input(
925 light_magenta('Give me the list\'s name ("@owner/list_name"): ', rl=True))
926 # Get list name and owner
927 try:
928 owner, slug = list_name.split('/')
929 if slug.startswith('@'):
930 slug = slug[1:]
931 return owner, slug
932 except:
933 printNicely(
934 light_magenta('List name should follow "@owner/list_name" format.'))
935 raise Exception('Wrong list name')
936
937
938 def show_lists(t):
939 """
940 List list
941 """
942 rel = t.lists.list(screen_name=g['original_name'])
943 if rel:
944 print_list(rel)
945 else:
946 printNicely(light_magenta('You belong to no lists :)'))
947
948
949 def list_home(t):
950 """
951 List home
952 """
953 owner, slug = get_slug()
954 res = t.lists.statuses(
955 slug=slug,
956 owner_screen_name=owner,
957 count=c['LIST_MAX'],
958 include_entities=False)
959 for tweet in reversed(res):
960 draw(t=tweet)
961 printNicely('')
962
963
964 def list_members(t):
965 """
966 List members
967 """
968 owner, slug = get_slug()
969 # Get members
970 rel = {}
971 next_cursor = -1
972 while next_cursor != 0:
973 m = t.lists.members(
974 slug=slug,
975 owner_screen_name=owner,
976 cursor=next_cursor,
977 include_entities=False)
978 for u in m['users']:
979 rel[u['name']] = '@' + u['screen_name']
980 next_cursor = m['next_cursor']
981 printNicely('All: ' + str(len(rel)) + ' members.')
982 for name in rel:
983 user = ' ' + cycle_color(name)
984 user += color_func(c['TWEET']['nick'])(' ' + rel[name] + ' ')
985 printNicely(user)
986
987
988 def list_subscribers(t):
989 """
990 List subscribers
991 """
992 owner, slug = get_slug()
993 # Get subscribers
994 rel = {}
995 next_cursor = -1
996 while next_cursor != 0:
997 m = t.lists.subscribers(
998 slug=slug,
999 owner_screen_name=owner,
1000 cursor=next_cursor,
1001 include_entities=False)
1002 for u in m['users']:
1003 rel[u['name']] = '@' + u['screen_name']
1004 next_cursor = m['next_cursor']
1005 printNicely('All: ' + str(len(rel)) + ' subscribers.')
1006 for name in rel:
1007 user = ' ' + cycle_color(name)
1008 user += color_func(c['TWEET']['nick'])(' ' + rel[name] + ' ')
1009 printNicely(user)
1010
1011
1012 def list_add(t):
1013 """
1014 Add specific user to a list
1015 """
1016 owner, slug = get_slug()
1017 # Add
1018 user_name = raw_input(
1019 light_magenta(
1020 'Give me name of the newbie: ',
1021 rl=True))
1022 if user_name.startswith('@'):
1023 user_name = user_name[1:]
1024 try:
1025 t.lists.members.create(
1026 slug=slug,
1027 owner_screen_name=owner,
1028 screen_name=user_name)
1029 printNicely(green('Added.'))
1030 except:
1031 debug_option()
1032 printNicely(light_magenta('I\'m sorry we can not add him/her.'))
1033
1034
1035 def list_remove(t):
1036 """
1037 Remove specific user from a list
1038 """
1039 owner, slug = get_slug()
1040 # Remove
1041 user_name = raw_input(
1042 light_magenta(
1043 'Give me name of the unlucky one: ',
1044 rl=True))
1045 if user_name.startswith('@'):
1046 user_name = user_name[1:]
1047 try:
1048 t.lists.members.destroy(
1049 slug=slug,
1050 owner_screen_name=owner,
1051 screen_name=user_name)
1052 printNicely(green('Gone.'))
1053 except:
1054 debug_option()
1055 printNicely(light_magenta('I\'m sorry we can not remove him/her.'))
1056
1057
1058 def list_subscribe(t):
1059 """
1060 Subscribe to a list
1061 """
1062 owner, slug = get_slug()
1063 # Subscribe
1064 try:
1065 t.lists.subscribers.create(
1066 slug=slug,
1067 owner_screen_name=owner)
1068 printNicely(green('Done.'))
1069 except:
1070 debug_option()
1071 printNicely(
1072 light_magenta('I\'m sorry you can not subscribe to this list.'))
1073
1074
1075 def list_unsubscribe(t):
1076 """
1077 Unsubscribe a list
1078 """
1079 owner, slug = get_slug()
1080 # Subscribe
1081 try:
1082 t.lists.subscribers.destroy(
1083 slug=slug,
1084 owner_screen_name=owner)
1085 printNicely(green('Done.'))
1086 except:
1087 debug_option()
1088 printNicely(
1089 light_magenta('I\'m sorry you can not unsubscribe to this list.'))
1090
1091
1092 def list_own(t):
1093 """
1094 List own
1095 """
1096 rel = []
1097 next_cursor = -1
1098 while next_cursor != 0:
1099 res = t.lists.ownerships(
1100 screen_name=g['original_name'],
1101 cursor=next_cursor)
1102 rel += res['lists']
1103 next_cursor = res['next_cursor']
1104 if rel:
1105 print_list(rel)
1106 else:
1107 printNicely(light_magenta('You own no lists :)'))
1108
1109
1110 def list_new(t):
1111 """
1112 Create a new list
1113 """
1114 name = raw_input(light_magenta('New list\'s name: ', rl=True))
1115 mode = raw_input(
1116 light_magenta(
1117 'New list\'s mode (public/private): ',
1118 rl=True))
1119 description = raw_input(
1120 light_magenta(
1121 'New list\'s description: ',
1122 rl=True))
1123 try:
1124 t.lists.create(
1125 name=name,
1126 mode=mode,
1127 description=description)
1128 printNicely(green(name + ' list is created.'))
1129 except:
1130 debug_option()
1131 printNicely(red('Oops something is wrong with Twitter :('))
1132
1133
1134 def list_update(t):
1135 """
1136 Update a list
1137 """
1138 slug = raw_input(
1139 light_magenta(
1140 'Your list that you want to update: ',
1141 rl=True))
1142 name = raw_input(
1143 light_magenta(
1144 'Update name (leave blank to unchange): ',
1145 rl=True))
1146 mode = raw_input(light_magenta('Update mode (public/private): ', rl=True))
1147 description = raw_input(light_magenta('Update description: ', rl=True))
1148 try:
1149 if name:
1150 t.lists.update(
1151 slug='-'.join(slug.split()),
1152 owner_screen_name=g['original_name'],
1153 name=name,
1154 mode=mode,
1155 description=description)
1156 else:
1157 t.lists.update(
1158 slug=slug,
1159 owner_screen_name=g['original_name'],
1160 mode=mode,
1161 description=description)
1162 printNicely(green(slug + ' list is updated.'))
1163 except:
1164 debug_option()
1165 printNicely(red('Oops something is wrong with Twitter :('))
1166
1167
1168 def list_delete(t):
1169 """
1170 Delete a list
1171 """
1172 slug = raw_input(
1173 light_magenta(
1174 'Your list that you want to delete: ',
1175 rl=True))
1176 try:
1177 t.lists.destroy(
1178 slug='-'.join(slug.split()),
1179 owner_screen_name=g['original_name'])
1180 printNicely(green(slug + ' list is deleted.'))
1181 except:
1182 debug_option()
1183 printNicely(red('Oops something is wrong with Twitter :('))
1184
1185
1186 def twitterlist():
1187 """
1188 Twitter's list
1189 """
1190 t = Twitter(auth=authen())
1191 # List all lists or base on action
1192 try:
1193 g['list_action'] = g['stuff'].split()[0]
1194 except:
1195 show_lists(t)
1196 return
1197 # Sub-function
1198 action_ary = {
1199 'home': list_home,
1200 'all_mem': list_members,
1201 'all_sub': list_subscribers,
1202 'add': list_add,
1203 'rm': list_remove,
1204 'sub': list_subscribe,
1205 'unsub': list_unsubscribe,
1206 'own': list_own,
1207 'new': list_new,
1208 'update': list_update,
1209 'del': list_delete,
1210 }
1211 try:
1212 return action_ary[g['list_action']](t)
1213 except:
1214 printNicely(red('Please try again.'))
1215
1216
1217 def switch():
1218 """
1219 Switch stream
1220 """
1221 try:
1222 target = g['stuff'].split()[0]
1223 # Filter and ignore
1224 args = parse_arguments()
1225 try:
1226 if g['stuff'].split()[-1] == '-f':
1227 guide = 'To ignore an option, just hit Enter key.'
1228 printNicely(light_magenta(guide))
1229 only = raw_input('Only nicks [Ex: @xxx,@yy]: ')
1230 ignore = raw_input('Ignore nicks [Ex: @xxx,@yy]: ')
1231 args.filter = filter(None, only.split(','))
1232 args.ignore = filter(None, ignore.split(','))
1233 except:
1234 printNicely(red('Sorry, wrong format.'))
1235 return
1236 # Public stream
1237 if target == 'public':
1238 keyword = g['stuff'].split()[1]
1239 if keyword[0] == '#':
1240 keyword = keyword[1:]
1241 # Kill old thread
1242 g['stream_stop'] = True
1243 args.track_keywords = keyword
1244 # Set the variable to tracked keyword
1245 # and reset the listname
1246 g['keyword'] = keyword
1247 g['listname'] = ''
1248 # Reset prefix
1249 g['PREFIX'] = u2str(emojize(format_prefix(keyword=g['keyword'])))
1250 # Start new thread
1251 th = threading.Thread(
1252 target=stream,
1253 args=(
1254 c['PUBLIC_DOMAIN'],
1255 args))
1256 th.daemon = True
1257 th.start()
1258 # Personal stream
1259 elif target == 'mine':
1260 # Kill old thread
1261 g['stream_stop'] = True
1262 # Reset the tracked keyword and listname
1263 g['keyword'] = g['listname'] = ''
1264 # Reset prefix
1265 g['PREFIX'] = u2str(emojize(format_prefix()))
1266 # Start new thread
1267 th = threading.Thread(
1268 target=stream,
1269 args=(
1270 c['USER_DOMAIN'],
1271 args,
1272 g['original_name']))
1273 th.daemon = True
1274 th.start()
1275 # Stream base on list
1276 elif target == 'list':
1277 owner, slug = get_slug()
1278 # Force python 2 not redraw readline buffer
1279 listname = '/'.join([owner, slug])
1280 # Set the listname variable
1281 # and reset tracked keyword
1282 g['listname'] = listname
1283 g['keyword'] = ''
1284 g['PREFIX'] = g['cmd'] = u2str(emojize(format_prefix(
1285 listname=g['listname']
1286 )))
1287 printNicely(light_yellow('getting list members ...'))
1288 # Get members
1289 t = Twitter(auth=authen())
1290 members = []
1291 next_cursor = -1
1292 while next_cursor != 0:
1293 m = t.lists.members(
1294 slug=slug,
1295 owner_screen_name=owner,
1296 cursor=next_cursor,
1297 include_entities=False)
1298 for u in m['users']:
1299 members.append('@' + u['screen_name'])
1300 next_cursor = m['next_cursor']
1301 printNicely(light_yellow('... done.'))
1302 # Build thread filter array
1303 args.filter = members
1304 # Kill old thread
1305 g['stream_stop'] = True
1306 # Start new thread
1307 th = threading.Thread(
1308 target=stream,
1309 args=(
1310 c['USER_DOMAIN'],
1311 args,
1312 slug))
1313 th.daemon = True
1314 th.start()
1315 printNicely('')
1316 if args.filter:
1317 printNicely(cyan('Include: ' + str(len(args.filter)) + ' people.'))
1318 if args.ignore:
1319 printNicely(red('Ignore: ' + str(len(args.ignore)) + ' people.'))
1320 printNicely('')
1321 except:
1322 debug_option()
1323 printNicely(red('Sorry I can\'t understand.'))
1324
1325
1326 def cal():
1327 """
1328 Unix's command `cal`
1329 """
1330 # Format
1331 rel = os.popen('cal').read().split('\n')
1332 month = rel.pop(0)
1333 date = rel.pop(0)
1334 show_calendar(month, date, rel)
1335
1336
1337 def theme():
1338 """
1339 List and change theme
1340 """
1341 if not g['stuff']:
1342 # List themes
1343 for theme in g['themes']:
1344 line = light_magenta(theme)
1345 if c['THEME'] == theme:
1346 line = ' ' * 2 + light_yellow('* ') + line
1347 else:
1348 line = ' ' * 4 + line
1349 printNicely(line)
1350 else:
1351 # Change theme
1352 try:
1353 # Load new theme
1354 c['THEME'] = reload_theme(g['stuff'], c['THEME'])
1355 # Redefine decorated_name
1356 g['decorated_name'] = lambda x: color_func(
1357 c['DECORATED_NAME'])(
1358 '[' + x + ']: ')
1359 printNicely(green('Theme changed.'))
1360 except:
1361 printNicely(red('No such theme exists.'))
1362
1363
1364 def config():
1365 """
1366 Browse and change config
1367 """
1368 all_config = get_all_config()
1369 g['stuff'] = g['stuff'].strip()
1370 # List all config
1371 if not g['stuff']:
1372 for k in all_config:
1373 line = ' ' * 2 + \
1374 green(k) + ': ' + light_yellow(str(all_config[k]))
1375 printNicely(line)
1376 guide = 'Detailed explanation can be found at ' + \
1377 color_func(c['TWEET']['link'])(
1378 'http://rainbowstream.readthedocs.org/en/latest/#config-explanation')
1379 printNicely(guide)
1380 # Print specific config
1381 elif len(g['stuff'].split()) == 1:
1382 if g['stuff'] in all_config:
1383 k = g['stuff']
1384 line = ' ' * 2 + \
1385 green(k) + ': ' + light_yellow(str(all_config[k]))
1386 printNicely(line)
1387 else:
1388 printNicely(red('No such config key.'))
1389 # Print specific config's default value
1390 elif len(g['stuff'].split()) == 2 and g['stuff'].split()[-1] == 'default':
1391 key = g['stuff'].split()[0]
1392 try:
1393 value = get_default_config(key)
1394 line = ' ' * 2 + green(key) + ': ' + light_magenta(value)
1395 printNicely(line)
1396 except:
1397 debug_option()
1398 printNicely(red('Just can not get the default.'))
1399 # Delete specific config key in config file
1400 elif len(g['stuff'].split()) == 2 and g['stuff'].split()[-1] == 'drop':
1401 key = g['stuff'].split()[0]
1402 try:
1403 delete_config(key)
1404 printNicely(green('Config key is dropped.'))
1405 except:
1406 debug_option()
1407 printNicely(red('Just can not drop the key.'))
1408 # Set specific config
1409 elif len(g['stuff'].split()) == 3 and g['stuff'].split()[1] == '=':
1410 key = g['stuff'].split()[0]
1411 value = g['stuff'].split()[-1]
1412 if key == 'THEME' and not validate_theme(value):
1413 printNicely(red('Invalid theme\'s value.'))
1414 return
1415 try:
1416 set_config(key, value)
1417 # Keys that needs to be apply immediately
1418 if key == 'THEME':
1419 c['THEME'] = reload_theme(value, c['THEME'])
1420 g['decorated_name'] = lambda x: color_func(
1421 c['DECORATED_NAME'])('[' + x + ']: ')
1422 elif key == 'PREFIX':
1423 g['PREFIX'] = u2str(emojize(format_prefix(
1424 listname=g['listname'],
1425 keyword=g['keyword']
1426 )))
1427 reload_config()
1428 printNicely(green('Updated successfully.'))
1429 except:
1430 debug_option()
1431 printNicely(red('Just can not set the key.'))
1432 else:
1433 printNicely(light_magenta('Sorry I can\'s understand.'))
1434
1435
1436 def help_discover():
1437 """
1438 Discover the world
1439 """
1440 s = ' ' * 2
1441 # Discover the world
1442 usage = '\n'
1443 usage += s + grey(u'\u266A' + ' Discover the world \n')
1444 usage += s * 2 + light_green('trend') + ' will show global trending topics. ' + \
1445 'You can try ' + light_green('trend US') + ' or ' + \
1446 light_green('trend JP Tokyo') + '.\n'
1447 usage += s * 2 + light_green('home') + ' will show your timeline. ' + \
1448 light_green('home 7') + ' will show 7 tweets.\n'
1449 usage += s * 2 + \
1450 light_green('notification') + ' will show your recent notification.\n'
1451 usage += s * 2 + light_green('mentions') + ' will show mentions timeline. ' + \
1452 light_green('mentions 7') + ' will show 7 mention tweets.\n'
1453 usage += s * 2 + light_green('whois @mdo') + ' will show profile of ' + \
1454 magenta('@mdo') + '.\n'
1455 usage += s * 2 + light_green('view @mdo') + \
1456 ' will show ' + magenta('@mdo') + '\'s home.\n'
1457 usage += s * 2 + light_green('s AKB48') + ' will search for "' + \
1458 light_yellow('AKB48') + '" and return 5 newest tweet. ' + \
1459 'Search can be performed with or without hashtag.\n'
1460 printNicely(usage)
1461
1462
1463 def help_tweets():
1464 """
1465 Tweets
1466 """
1467 s = ' ' * 2
1468 # Tweet
1469 usage = '\n'
1470 usage += s + grey(u'\u266A' + ' Tweets \n')
1471 usage += s * 2 + light_green('t oops ') + \
1472 'will tweet "' + light_yellow('oops') + '" immediately.\n'
1473 usage += s * 2 + \
1474 light_green('rt 12 ') + ' will retweet to tweet with ' + \
1475 light_yellow('[id=12]') + '.\n'
1476 usage += s * 2 + \
1477 light_green('quote 12 ') + ' will quote the tweet with ' + \
1478 light_yellow('[id=12]') + '. If no extra text is added, ' + \
1479 'the quote will be canceled.\n'
1480 usage += s * 2 + \
1481 light_green('allrt 12 20 ') + ' will list 20 newest retweet of the tweet with ' + \
1482 light_yellow('[id=12]') + '.\n'
1483 usage += s * 2 + light_green('conversation 12') + ' will show the chain of ' + \
1484 'replies prior to the tweet with ' + light_yellow('[id=12]') + '.\n'
1485 usage += s * 2 + light_green('rep 12 oops') + ' will reply "' + \
1486 light_yellow('oops') + '" to tweet with ' + \
1487 light_yellow('[id=12]') + '.\n'
1488 usage += s * 2 + \
1489 light_green('fav 12 ') + ' will favorite the tweet with ' + \
1490 light_yellow('[id=12]') + '.\n'
1491 usage += s * 2 + \
1492 light_green('ufav 12 ') + ' will unfavorite tweet with ' + \
1493 light_yellow('[id=12]') + '.\n'
1494 usage += s * 2 + \
1495 light_green('share 12 ') + ' will get the direct link of the tweet with ' + \
1496 light_yellow('[id=12]') + '.\n'
1497 usage += s * 2 + \
1498 light_green('del 12 ') + ' will delete tweet with ' + \
1499 light_yellow('[id=12]') + '.\n'
1500 usage += s * 2 + light_green('show image 12') + ' will show image in tweet with ' + \
1501 light_yellow('[id=12]') + ' in your OS\'s image viewer.\n'
1502 usage += s * 2 + light_green('open 12') + ' will open url in tweet with ' + \
1503 light_yellow('[id=12]') + ' in your OS\'s default browser.\n'
1504 printNicely(usage)
1505
1506
1507 def help_messages():
1508 """
1509 Messages
1510 """
1511 s = ' ' * 2
1512 # Direct message
1513 usage = '\n'
1514 usage += s + grey(u'\u266A' + ' Direct messages \n')
1515 usage += s * 2 + light_green('inbox') + ' will show inbox messages. ' + \
1516 light_green('inbox 7') + ' will show newest 7 messages.\n'
1517 usage += s * 2 + light_green('thread 2') + ' will show full thread with ' + \
1518 light_yellow('[thread_id=2]') + '.\n'
1519 usage += s * 2 + light_green('mes @dtvd88 hi') + ' will send a "hi" messege to ' + \
1520 magenta('@dtvd88') + '.\n'
1521 usage += s * 2 + light_green('trash 5') + ' will remove message with ' + \
1522 light_yellow('[message_id=5]') + '.\n'
1523 printNicely(usage)
1524
1525
1526 def help_friends_and_followers():
1527 """
1528 Friends and Followers
1529 """
1530 s = ' ' * 2
1531 # Follower and following
1532 usage = '\n'
1533 usage += s + grey(u'\u266A' + ' Friends and followers \n')
1534 usage += s * 2 + \
1535 light_green('ls fl') + \
1536 ' will list all followers (people who are following you).\n'
1537 usage += s * 2 + \
1538 light_green('ls fr') + \
1539 ' will list all friends (people who you are following).\n'
1540 usage += s * 2 + light_green('fl @dtvd88') + ' will follow ' + \
1541 magenta('@dtvd88') + '.\n'
1542 usage += s * 2 + light_green('ufl @dtvd88') + ' will unfollow ' + \
1543 magenta('@dtvd88') + '.\n'
1544 usage += s * 2 + light_green('mute @dtvd88') + ' will mute ' + \
1545 magenta('@dtvd88') + '.\n'
1546 usage += s * 2 + light_green('unmute @dtvd88') + ' will unmute ' + \
1547 magenta('@dtvd88') + '.\n'
1548 usage += s * 2 + light_green('muting') + ' will list muting users.\n'
1549 usage += s * 2 + light_green('block @dtvd88') + ' will block ' + \
1550 magenta('@dtvd88') + '.\n'
1551 usage += s * 2 + light_green('unblock @dtvd88') + ' will unblock ' + \
1552 magenta('@dtvd88') + '.\n'
1553 usage += s * 2 + light_green('report @dtvd88') + ' will report ' + \
1554 magenta('@dtvd88') + ' as a spam account.\n'
1555 printNicely(usage)
1556
1557
1558 def help_list():
1559 """
1560 Lists
1561 """
1562 s = ' ' * 2
1563 # Twitter list
1564 usage = '\n'
1565 usage += s + grey(u'\u266A' + ' Twitter list\n')
1566 usage += s * 2 + light_green('list') + \
1567 ' will show all lists you are belong to.\n'
1568 usage += s * 2 + light_green('list home') + \
1569 ' will show timeline of list. You will be asked for list\'s name.\n'
1570 usage += s * 2 + light_green('list all_mem') + \
1571 ' will show list\'s all members.\n'
1572 usage += s * 2 + light_green('list all_sub') + \
1573 ' will show list\'s all subscribers.\n'
1574 usage += s * 2 + light_green('list add') + \
1575 ' will add specific person to a list owned by you.' + \
1576 ' You will be asked for list\'s name and person\'s name.\n'
1577 usage += s * 2 + light_green('list rm') + \
1578 ' will remove specific person from a list owned by you.' + \
1579 ' You will be asked for list\'s name and person\'s name.\n'
1580 usage += s * 2 + light_green('list sub') + \
1581 ' will subscribe you to a specific list.\n'
1582 usage += s * 2 + light_green('list unsub') + \
1583 ' will unsubscribe you from a specific list.\n'
1584 usage += s * 2 + light_green('list own') + \
1585 ' will show all list owned by you.\n'
1586 usage += s * 2 + light_green('list new') + \
1587 ' will create a new list.\n'
1588 usage += s * 2 + light_green('list update') + \
1589 ' will update a list owned by you.\n'
1590 usage += s * 2 + light_green('list del') + \
1591 ' will delete a list owned by you.\n'
1592 printNicely(usage)
1593
1594
1595 def help_stream():
1596 """
1597 Stream switch
1598 """
1599 s = ' ' * 2
1600 # Switch
1601 usage = '\n'
1602 usage += s + grey(u'\u266A' + ' Switching streams \n')
1603 usage += s * 2 + light_green('switch public #AKB') + \
1604 ' will switch to public stream and follow "' + \
1605 light_yellow('AKB') + '" keyword.\n'
1606 usage += s * 2 + light_green('switch mine') + \
1607 ' will switch to your personal stream.\n'
1608 usage += s * 2 + light_green('switch mine -f ') + \
1609 ' will prompt to enter the filter.\n'
1610 usage += s * 3 + light_yellow('Only nicks') + \
1611 ' filter will decide nicks will be INCLUDE ONLY.\n'
1612 usage += s * 3 + light_yellow('Ignore nicks') + \
1613 ' filter will decide nicks will be EXCLUDE.\n'
1614 usage += s * 2 + light_green('switch list') + \
1615 ' will switch to a Twitter list\'s stream. You will be asked for list name\n'
1616 printNicely(usage)
1617
1618
1619 def help():
1620 """
1621 Help
1622 """
1623 s = ' ' * 2
1624 h, w = os.popen('stty size', 'r').read().split()
1625 # Start
1626 usage = '\n'
1627 usage += s + 'Hi boss! I\'m ready to serve you right now!\n'
1628 usage += s + '-' * (int(w) - 4) + '\n'
1629 usage += s + 'You are ' + \
1630 light_yellow('already') + ' on your personal stream.\n'
1631 usage += s + 'Any update from Twitter will show up ' + \
1632 light_yellow('immediately') + '.\n'
1633 usage += s + 'In addition, following commands are available right now:\n'
1634 # Twitter help section
1635 usage += '\n'
1636 usage += s + grey(u'\u266A' + ' Twitter help\n')
1637 usage += s * 2 + light_green('h discover') + \
1638 ' will show help for discover commands.\n'
1639 usage += s * 2 + light_green('h tweets') + \
1640 ' will show help for tweets commands.\n'
1641 usage += s * 2 + light_green('h messages') + \
1642 ' will show help for messages commands.\n'
1643 usage += s * 2 + light_green('h friends_and_followers') + \
1644 ' will show help for friends and followers commands.\n'
1645 usage += s * 2 + light_green('h list') + \
1646 ' will show help for list commands.\n'
1647 usage += s * 2 + light_green('h stream') + \
1648 ' will show help for stream commands.\n'
1649 # Smart shell
1650 usage += '\n'
1651 usage += s + grey(u'\u266A' + ' Smart shell\n')
1652 usage += s * 2 + light_green('111111 * 9 / 7') + ' or any math expression ' + \
1653 'will be evaluate by Python interpreter.\n'
1654 usage += s * 2 + 'Even ' + light_green('cal') + ' will show the calendar' + \
1655 ' for current month.\n'
1656 # Config
1657 usage += '\n'
1658 usage += s + grey(u'\u266A' + ' Config \n')
1659 usage += s * 2 + light_green('theme') + ' will list available theme. ' + \
1660 light_green('theme monokai') + ' will apply ' + light_yellow('monokai') + \
1661 ' theme immediately.\n'
1662 usage += s * 2 + light_green('config') + ' will list all config.\n'
1663 usage += s * 3 + \
1664 light_green('config ASCII_ART') + ' will output current value of ' +\
1665 light_yellow('ASCII_ART') + ' config key.\n'
1666 usage += s * 3 + \
1667 light_green('config TREND_MAX default') + ' will output default value of ' + \
1668 light_yellow('TREND_MAX') + ' config key.\n'
1669 usage += s * 3 + \
1670 light_green('config CUSTOM_CONFIG drop') + ' will drop ' + \
1671 light_yellow('CUSTOM_CONFIG') + ' config key.\n'
1672 usage += s * 3 + \
1673 light_green('config IMAGE_ON_TERM = true') + ' will set value of ' + \
1674 light_yellow('IMAGE_ON_TERM') + ' config key to ' + \
1675 light_yellow('True') + '.\n'
1676 # Screening
1677 usage += '\n'
1678 usage += s + grey(u'\u266A' + ' Screening \n')
1679 usage += s * 2 + light_green('h') + ' will show this help again.\n'
1680 usage += s * 2 + light_green('p') + ' will pause the stream.\n'
1681 usage += s * 2 + light_green('r') + ' will unpause the stream.\n'
1682 usage += s * 2 + light_green('c') + ' will clear the screen.\n'
1683 usage += s * 2 + light_green('v') + ' will show version info.\n'
1684 usage += s * 2 + light_green('q') + ' will quit.\n'
1685 # End
1686 usage += '\n'
1687 usage += s + '-' * (int(w) - 4) + '\n'
1688 usage += s + 'Have fun and hang tight! \n'
1689 # Show help
1690 d = {
1691 'discover': help_discover,
1692 'tweets': help_tweets,
1693 'messages': help_messages,
1694 'friends_and_followers': help_friends_and_followers,
1695 'list': help_list,
1696 'stream': help_stream,
1697 }
1698 if g['stuff']:
1699 d.get(
1700 g['stuff'].strip(),
1701 lambda: printNicely(red('No such command.'))
1702 )()
1703 else:
1704 printNicely(usage)
1705
1706
1707 def pause():
1708 """
1709 Pause stream display
1710 """
1711 g['pause'] = True
1712 printNicely(green('Stream is paused'))
1713
1714
1715 def replay():
1716 """
1717 Replay stream
1718 """
1719 g['pause'] = False
1720 printNicely(green('Stream is running back now'))
1721
1722
1723 def clear():
1724 """
1725 Clear screen
1726 """
1727 os.system('clear')
1728
1729
1730 def quit():
1731 """
1732 Exit all
1733 """
1734 try:
1735 save_history()
1736 printNicely(green('See you next time :)'))
1737 except:
1738 pass
1739 sys.exit()
1740
1741
1742 def reset():
1743 """
1744 Reset prefix of line
1745 """
1746 if g['reset']:
1747 if c.get('USER_JSON_ERROR'):
1748 printNicely(red('Your ~/.rainbow_config.json is messed up:'))
1749 printNicely(red('>>> ' + c['USER_JSON_ERROR']))
1750 printNicely('')
1751 printNicely(magenta('Need tips ? Type "h" and hit Enter key!'))
1752 g['reset'] = False
1753 try:
1754 printNicely(str(eval(g['cmd'])))
1755 except Exception:
1756 pass
1757
1758
1759 # Command set
1760 cmdset = [
1761 'switch',
1762 'trend',
1763 'home',
1764 'notification',
1765 'view',
1766 'mentions',
1767 't',
1768 'rt',
1769 'quote',
1770 'allrt',
1771 'conversation',
1772 'fav',
1773 'rep',
1774 'del',
1775 'ufav',
1776 'share',
1777 's',
1778 'mes',
1779 'show',
1780 'open',
1781 'ls',
1782 'inbox',
1783 'thread',
1784 'trash',
1785 'whois',
1786 'fl',
1787 'ufl',
1788 'mute',
1789 'unmute',
1790 'muting',
1791 'block',
1792 'unblock',
1793 'report',
1794 'list',
1795 'cal',
1796 'config',
1797 'theme',
1798 'h',
1799 'p',
1800 'r',
1801 'c',
1802 'v',
1803 'q',
1804 ]
1805
1806 # Handle function set
1807 funcset = [
1808 switch,
1809 trend,
1810 home,
1811 notification,
1812 view,
1813 mentions,
1814 tweet,
1815 retweet,
1816 quote,
1817 allretweet,
1818 conversation,
1819 favorite,
1820 reply,
1821 delete,
1822 unfavorite,
1823 share,
1824 search,
1825 message,
1826 show,
1827 urlopen,
1828 ls,
1829 inbox,
1830 thread,
1831 trash,
1832 whois,
1833 follow,
1834 unfollow,
1835 mute,
1836 unmute,
1837 muting,
1838 block,
1839 unblock,
1840 report,
1841 twitterlist,
1842 cal,
1843 config,
1844 theme,
1845 help,
1846 pause,
1847 replay,
1848 clear,
1849 upgrade_center,
1850 quit,
1851 ]
1852
1853
1854 def process(cmd):
1855 """
1856 Process switch
1857 """
1858 return dict(zip(cmdset, funcset)).get(cmd, reset)
1859
1860
1861 def listen():
1862 """
1863 Listen to user's input
1864 """
1865 d = dict(zip(
1866 cmdset,
1867 [
1868 ['public', 'mine', 'list'], # switch
1869 [], # trend
1870 [], # home
1871 [], # notification
1872 ['@'], # view
1873 [], # mentions
1874 [], # tweet
1875 [], # retweet
1876 [], # quote
1877 [], # allretweet
1878 [], # conversation
1879 [], # favorite
1880 [], # reply
1881 [], # delete
1882 [], # unfavorite
1883 [], # url
1884 ['#'], # search
1885 ['@'], # message
1886 ['image'], # show image
1887 [''], # open url
1888 ['fl', 'fr'], # list
1889 [], # inbox
1890 [i for i in g['message_threads']], # sent
1891 [], # trash
1892 ['@'], # whois
1893 ['@'], # follow
1894 ['@'], # unfollow
1895 ['@'], # mute
1896 ['@'], # unmute
1897 ['@'], # muting
1898 ['@'], # block
1899 ['@'], # unblock
1900 ['@'], # report
1901 [
1902 'home',
1903 'all_mem',
1904 'all_sub',
1905 'add',
1906 'rm',
1907 'sub',
1908 'unsub',
1909 'own',
1910 'new',
1911 'update',
1912 'del'
1913 ], # list
1914 [], # cal
1915 [key for key in dict(get_all_config())], # config
1916 g['themes'], # theme
1917 [
1918 'discover',
1919 'tweets',
1920 'messages',
1921 'friends_and_followers',
1922 'list',
1923 'stream'
1924 ], # help
1925 [], # pause
1926 [], # reconnect
1927 [], # clear
1928 [], # version
1929 [], # quit
1930 ]
1931 ))
1932 init_interactive_shell(d)
1933 read_history()
1934 reset()
1935 while True:
1936 try:
1937 # raw_input
1938 if g['prefix']:
1939 # Only use PREFIX as a string with raw_input
1940 line = raw_input(g['decorated_name'](g['PREFIX']))
1941 else:
1942 line = raw_input()
1943 # Save cmd to compare with readline buffer
1944 g['cmd'] = line.strip()
1945 # Get short cmd to pass to handle function
1946 try:
1947 cmd = line.split()[0]
1948 except:
1949 cmd = ''
1950 # Lock the semaphore
1951 c['lock'] = True
1952 # Save cmd to global variable and call process
1953 g['stuff'] = ' '.join(line.split()[1:])
1954 # Process the command
1955 process(cmd)()
1956 # Not re-display
1957 if cmd in ['switch', 't', 'rt', 'rep']:
1958 g['prefix'] = False
1959 else:
1960 g['prefix'] = True
1961 # Release the semaphore lock
1962 c['lock'] = False
1963 except EOFError:
1964 printNicely('')
1965 except Exception:
1966 debug_option()
1967 printNicely(red('OMG something is wrong with Twitter right now.'))
1968
1969
1970 def reconn_notice():
1971 """
1972 Notice when Hangup or Timeout
1973 """
1974 guide = light_magenta('You can use ') + \
1975 light_green('switch') + \
1976 light_magenta(' command to return to your stream.\n')
1977 guide += light_magenta('Type ') + \
1978 light_green('h stream') + \
1979 light_magenta(' for more details.')
1980 printNicely(guide)
1981 sys.stdout.write(g['decorated_name'](g['PREFIX']))
1982 sys.stdout.flush()
1983
1984
1985 def stream(domain, args, name='Rainbow Stream'):
1986 """
1987 Track the stream
1988 """
1989 # The Logo
1990 art_dict = {
1991 c['USER_DOMAIN']: name,
1992 c['PUBLIC_DOMAIN']: args.track_keywords,
1993 c['SITE_DOMAIN']: name,
1994 }
1995 if c['ASCII_ART']:
1996 ascii_art(art_dict[domain])
1997 # These arguments are optional:
1998 stream_args = dict(
1999 timeout=0.5, # To check g['stream_stop'] after each 0.5 s
2000 block=True,
2001 heartbeat_timeout=c['HEARTBEAT_TIMEOUT'] * 60)
2002 # Track keyword
2003 query_args = dict()
2004 if args.track_keywords:
2005 query_args['track'] = args.track_keywords
2006 # Get stream
2007 stream = TwitterStream(
2008 auth=authen(),
2009 domain=domain,
2010 **stream_args)
2011 try:
2012 if domain == c['USER_DOMAIN']:
2013 tweet_iter = stream.user(**query_args)
2014 elif domain == c['SITE_DOMAIN']:
2015 tweet_iter = stream.site(**query_args)
2016 else:
2017 if args.track_keywords:
2018 tweet_iter = stream.statuses.filter(**query_args)
2019 else:
2020 tweet_iter = stream.statuses.sample()
2021 # Block new stream until other one exits
2022 StreamLock.acquire()
2023 g['stream_stop'] = False
2024 last_tweet_time = time.time()
2025 for tweet in tweet_iter:
2026 if tweet is None:
2027 printNicely('-- None --')
2028 elif tweet is Timeout:
2029 # Because the stream check for each 0.3s
2030 # so we shouldn't output anything here
2031 if(g['stream_stop']):
2032 StreamLock.release()
2033 break
2034 elif tweet is HeartbeatTimeout:
2035 printNicely('-- Heartbeat Timeout --')
2036 reconn_notice()
2037 StreamLock.release()
2038 break
2039 elif tweet is Hangup:
2040 printNicely('-- Hangup --')
2041 reconn_notice()
2042 StreamLock.release()
2043 break
2044 elif tweet.get('text'):
2045 # Slow down the stream by STREAM_DELAY config key
2046 if time.time() - last_tweet_time < c['STREAM_DELAY']:
2047 continue
2048 last_tweet_time = time.time()
2049 # Check the semaphore pause and lock (stream process only)
2050 if g['pause']:
2051 continue
2052 while c['lock']:
2053 time.sleep(0.5)
2054 # Draw the tweet
2055 draw(
2056 t=tweet,
2057 keyword=args.track_keywords,
2058 humanize=False,
2059 fil=args.filter,
2060 ig=args.ignore,
2061 )
2062 # Current readline buffer
2063 current_buffer = readline.get_line_buffer().strip()
2064 # There is an unexpected behaviour in MacOSX readline + Python 2:
2065 # after completely delete a word after typing it,
2066 # somehow readline buffer still contains
2067 # the 1st character of that word
2068 if current_buffer and g['cmd'] != current_buffer:
2069 sys.stdout.write(
2070 g['decorated_name'](g['PREFIX']) + current_buffer)
2071 sys.stdout.flush()
2072 elif not c['HIDE_PROMPT']:
2073 sys.stdout.write(g['decorated_name'](g['PREFIX']))
2074 sys.stdout.flush()
2075 elif tweet.get('direct_message'):
2076 # Check the semaphore pause and lock (stream process only)
2077 if g['pause']:
2078 continue
2079 while c['lock']:
2080 time.sleep(0.5)
2081 print_message(tweet['direct_message'])
2082 elif tweet.get('event'):
2083 c['events'].append(tweet)
2084 print_event(tweet)
2085 except TwitterHTTPError as e:
2086 printNicely('')
2087 printNicely(
2088 magenta('We have connection problem with twitter stream API right now :('))
2089 detail_twitter_error(e)
2090 sys.stdout.write(g['decorated_name'](g['PREFIX']))
2091 sys.stdout.flush()
2092
2093
2094 def fly():
2095 """
2096 Main function
2097 """
2098 # Initial
2099 args = parse_arguments()
2100 try:
2101 proxy_connect(args)
2102 init(args)
2103 # Twitter API connection problem
2104 except TwitterHTTPError as e:
2105 printNicely('')
2106 printNicely(
2107 magenta('We have connection problem with twitter REST API right now :('))
2108 detail_twitter_error(e)
2109 save_history()
2110 sys.exit()
2111 # Proxy connection problem
2112 except (socks.ProxyConnectionError, URLError):
2113 printNicely(
2114 magenta('There seems to be a connection problem.'))
2115 printNicely(
2116 magenta('You might want to check your proxy settings (host, port and type)!'))
2117 save_history()
2118 sys.exit()
2119
2120 # Spawn stream thread
2121 th = threading.Thread(
2122 target=stream,
2123 args=(
2124 c['USER_DOMAIN'],
2125 args,
2126 g['original_name']))
2127 th.daemon = True
2128 th.start()
2129 # Start listen process
2130 time.sleep(0.5)
2131 g['reset'] = True
2132 g['prefix'] = True
2133 listen()