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