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