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