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