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