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