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