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