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