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