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