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