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