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