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