Merge pull request #77 from nwtti/master
[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 try:
1318 owner, slug = check_slug(g['stuff'].split()[1])
1319 except:
1320 owner, slug = get_slug()
1321 # Force python 2 not redraw readline buffer
1322 listname = '/'.join([owner, slug])
1323 # Set the listname variable
1324 # and reset tracked keyword
1325 g['listname'] = listname
1326 g['keyword'] = ''
1327 g['PREFIX'] = g['cmd'] = u2str(emojize(format_prefix(
1328 listname=g['listname']
1329 )))
1330 printNicely(light_yellow('getting list members ...'))
1331 # Get members
1332 t = Twitter(auth=authen())
1333 members = []
1334 next_cursor = -1
1335 while next_cursor != 0:
1336 m = t.lists.members(
1337 slug=slug,
1338 owner_screen_name=owner,
1339 cursor=next_cursor,
1340 include_entities=False)
1341 for u in m['users']:
1342 members.append('@' + u['screen_name'])
1343 next_cursor = m['next_cursor']
1344 printNicely(light_yellow('... done.'))
1345 # Build thread filter array
1346 args.filter = members
1347 # Kill old thread
1348 g['stream_stop'] = True
1349 # Start new thread
1350 th = threading.Thread(
1351 target=stream,
1352 args=(
1353 c['USER_DOMAIN'],
1354 args,
1355 slug))
1356 th.daemon = True
1357 th.start()
1358 printNicely('')
1359 if args.filter:
1360 printNicely(cyan('Include: ' + str(len(args.filter)) + ' people.'))
1361 if args.ignore:
1362 printNicely(red('Ignore: ' + str(len(args.ignore)) + ' people.'))
1363 printNicely('')
1364 except:
1365 debug_option()
1366 printNicely(red('Sorry I can\'t understand.'))
1367
1368
1369 def cal():
1370 """
1371 Unix's command `cal`
1372 """
1373 # Format
1374 rel = os.popen('cal').read().split('\n')
1375 month = rel.pop(0)
1376 date = rel.pop(0)
1377 show_calendar(month, date, rel)
1378
1379
1380 def theme():
1381 """
1382 List and change theme
1383 """
1384 if not g['stuff']:
1385 # List themes
1386 for theme in g['themes']:
1387 line = light_magenta(theme)
1388 if c['THEME'] == theme:
1389 line = ' ' * 2 + light_yellow('* ') + line
1390 else:
1391 line = ' ' * 4 + line
1392 printNicely(line)
1393 else:
1394 # Change theme
1395 try:
1396 # Load new theme
1397 c['THEME'] = reload_theme(g['stuff'], c['THEME'])
1398 # Redefine decorated_name
1399 g['decorated_name'] = lambda x: color_func(
1400 c['DECORATED_NAME'])(
1401 '[' + x + ']: ')
1402 printNicely(green('Theme changed.'))
1403 except:
1404 printNicely(red('No such theme exists.'))
1405
1406
1407 def config():
1408 """
1409 Browse and change config
1410 """
1411 all_config = get_all_config()
1412 g['stuff'] = g['stuff'].strip()
1413 # List all config
1414 if not g['stuff']:
1415 for k in all_config:
1416 line = ' ' * 2 + \
1417 green(k) + ': ' + light_yellow(str(all_config[k]))
1418 printNicely(line)
1419 guide = 'Detailed explanation can be found at ' + \
1420 color_func(c['TWEET']['link'])(
1421 'http://rainbowstream.readthedocs.org/en/latest/#config-explanation')
1422 printNicely(guide)
1423 # Print specific config
1424 elif len(g['stuff'].split()) == 1:
1425 if g['stuff'] in all_config:
1426 k = g['stuff']
1427 line = ' ' * 2 + \
1428 green(k) + ': ' + light_yellow(str(all_config[k]))
1429 printNicely(line)
1430 else:
1431 printNicely(red('No such config key.'))
1432 # Print specific config's default value
1433 elif len(g['stuff'].split()) == 2 and g['stuff'].split()[-1] == 'default':
1434 key = g['stuff'].split()[0]
1435 try:
1436 value = get_default_config(key)
1437 line = ' ' * 2 + green(key) + ': ' + light_magenta(value)
1438 printNicely(line)
1439 except:
1440 debug_option()
1441 printNicely(red('Just can not get the default.'))
1442 # Delete specific config key in config file
1443 elif len(g['stuff'].split()) == 2 and g['stuff'].split()[-1] == 'drop':
1444 key = g['stuff'].split()[0]
1445 try:
1446 delete_config(key)
1447 printNicely(green('Config key is dropped.'))
1448 except:
1449 debug_option()
1450 printNicely(red('Just can not drop the key.'))
1451 # Set specific config
1452 elif len(g['stuff'].split()) == 3 and g['stuff'].split()[1] == '=':
1453 key = g['stuff'].split()[0]
1454 value = g['stuff'].split()[-1]
1455 if key == 'THEME' and not validate_theme(value):
1456 printNicely(red('Invalid theme\'s value.'))
1457 return
1458 try:
1459 set_config(key, value)
1460 # Keys that needs to be apply immediately
1461 if key == 'THEME':
1462 c['THEME'] = reload_theme(value, c['THEME'])
1463 g['decorated_name'] = lambda x: color_func(
1464 c['DECORATED_NAME'])('[' + x + ']: ')
1465 elif key == 'PREFIX':
1466 g['PREFIX'] = u2str(emojize(format_prefix(
1467 listname=g['listname'],
1468 keyword=g['keyword']
1469 )))
1470 reload_config()
1471 printNicely(green('Updated successfully.'))
1472 except:
1473 debug_option()
1474 printNicely(red('Just can not set the key.'))
1475 else:
1476 printNicely(light_magenta('Sorry I can\'s understand.'))
1477
1478
1479 def help_discover():
1480 """
1481 Discover the world
1482 """
1483 s = ' ' * 2
1484 # Discover the world
1485 usage = '\n'
1486 usage += s + grey(u'\u266A' + ' Discover the world \n')
1487 usage += s * 2 + light_green('trend') + ' will show global trending topics. ' + \
1488 'You can try ' + light_green('trend US') + ' or ' + \
1489 light_green('trend JP Tokyo') + '.\n'
1490 usage += s * 2 + light_green('home') + ' will show your timeline. ' + \
1491 light_green('home 7') + ' will show 7 tweets.\n'
1492 usage += s * 2 + \
1493 light_green('notification') + ' will show your recent notification.\n'
1494 usage += s * 2 + light_green('mentions') + ' will show mentions timeline. ' + \
1495 light_green('mentions 7') + ' will show 7 mention tweets.\n'
1496 usage += s * 2 + light_green('whois @mdo') + ' will show profile of ' + \
1497 magenta('@mdo') + '.\n'
1498 usage += s * 2 + light_green('view @mdo') + \
1499 ' will show ' + magenta('@mdo') + '\'s home.\n'
1500 usage += s * 2 + light_green('s AKB48') + ' will search for "' + \
1501 light_yellow('AKB48') + '" and return 5 newest tweet. ' + \
1502 'Search can be performed with or without hashtag.\n'
1503 printNicely(usage)
1504
1505
1506 def help_tweets():
1507 """
1508 Tweets
1509 """
1510 s = ' ' * 2
1511 # Tweet
1512 usage = '\n'
1513 usage += s + grey(u'\u266A' + ' Tweets \n')
1514 usage += s * 2 + light_green('t oops ') + \
1515 'will tweet "' + light_yellow('oops') + '" immediately.\n'
1516 usage += s * 2 + \
1517 light_green('rt 12 ') + ' will retweet to tweet with ' + \
1518 light_yellow('[id=12]') + '.\n'
1519 usage += s * 2 + \
1520 light_green('quote 12 ') + ' will quote the tweet with ' + \
1521 light_yellow('[id=12]') + '. If no extra text is added, ' + \
1522 'the quote will be canceled.\n'
1523 usage += s * 2 + \
1524 light_green('allrt 12 20 ') + ' will list 20 newest retweet of the tweet with ' + \
1525 light_yellow('[id=12]') + '.\n'
1526 usage += s * 2 + light_green('conversation 12') + ' will show the chain of ' + \
1527 'replies prior to the tweet with ' + light_yellow('[id=12]') + '.\n'
1528 usage += s * 2 + light_green('rep 12 oops') + ' will reply "' + \
1529 light_yellow('oops') + '" to the owner of the tweet with ' + \
1530 light_yellow('[id=12]') + '.\n'
1531 usage += s * 2 + light_green('repall 12 oops') + ' will reply "' + \
1532 light_yellow('oops') + '" to all people in the tweet with ' + \
1533 light_yellow('[id=12]') + '.\n'
1534 usage += s * 2 + \
1535 light_green('fav 12 ') + ' will favorite the tweet with ' + \
1536 light_yellow('[id=12]') + '.\n'
1537 usage += s * 2 + \
1538 light_green('ufav 12 ') + ' will unfavorite tweet with ' + \
1539 light_yellow('[id=12]') + '.\n'
1540 usage += s * 2 + \
1541 light_green('share 12 ') + ' will get the direct link of the tweet with ' + \
1542 light_yellow('[id=12]') + '.\n'
1543 usage += s * 2 + \
1544 light_green('del 12 ') + ' will delete tweet with ' + \
1545 light_yellow('[id=12]') + '.\n'
1546 usage += s * 2 + light_green('show image 12') + ' will show image in tweet with ' + \
1547 light_yellow('[id=12]') + ' in your OS\'s image viewer.\n'
1548 usage += s * 2 + light_green('open 12') + ' will open url in tweet with ' + \
1549 light_yellow('[id=12]') + ' in your OS\'s default browser.\n'
1550 printNicely(usage)
1551
1552
1553 def help_messages():
1554 """
1555 Messages
1556 """
1557 s = ' ' * 2
1558 # Direct message
1559 usage = '\n'
1560 usage += s + grey(u'\u266A' + ' Direct messages \n')
1561 usage += s * 2 + light_green('inbox') + ' will show inbox messages. ' + \
1562 light_green('inbox 7') + ' will show newest 7 messages.\n'
1563 usage += s * 2 + light_green('thread 2') + ' will show full thread with ' + \
1564 light_yellow('[thread_id=2]') + '.\n'
1565 usage += s * 2 + light_green('mes @dtvd88 hi') + ' will send a "hi" messege to ' + \
1566 magenta('@dtvd88') + '.\n'
1567 usage += s * 2 + light_green('trash 5') + ' will remove message with ' + \
1568 light_yellow('[message_id=5]') + '.\n'
1569 printNicely(usage)
1570
1571
1572 def help_friends_and_followers():
1573 """
1574 Friends and Followers
1575 """
1576 s = ' ' * 2
1577 # Follower and following
1578 usage = '\n'
1579 usage += s + grey(u'\u266A' + ' Friends and followers \n')
1580 usage += s * 2 + \
1581 light_green('ls fl') + \
1582 ' will list all followers (people who are following you).\n'
1583 usage += s * 2 + \
1584 light_green('ls fr') + \
1585 ' will list all friends (people who you are following).\n'
1586 usage += s * 2 + light_green('fl @dtvd88') + ' will follow ' + \
1587 magenta('@dtvd88') + '.\n'
1588 usage += s * 2 + light_green('ufl @dtvd88') + ' will unfollow ' + \
1589 magenta('@dtvd88') + '.\n'
1590 usage += s * 2 + light_green('mute @dtvd88') + ' will mute ' + \
1591 magenta('@dtvd88') + '.\n'
1592 usage += s * 2 + light_green('unmute @dtvd88') + ' will unmute ' + \
1593 magenta('@dtvd88') + '.\n'
1594 usage += s * 2 + light_green('muting') + ' will list muting users.\n'
1595 usage += s * 2 + light_green('block @dtvd88') + ' will block ' + \
1596 magenta('@dtvd88') + '.\n'
1597 usage += s * 2 + light_green('unblock @dtvd88') + ' will unblock ' + \
1598 magenta('@dtvd88') + '.\n'
1599 usage += s * 2 + light_green('report @dtvd88') + ' will report ' + \
1600 magenta('@dtvd88') + ' as a spam account.\n'
1601 printNicely(usage)
1602
1603
1604 def help_list():
1605 """
1606 Lists
1607 """
1608 s = ' ' * 2
1609 # Twitter list
1610 usage = '\n'
1611 usage += s + grey(u'\u266A' + ' Twitter list\n')
1612 usage += s * 2 + light_green('list') + \
1613 ' will show all lists you are belong to.\n'
1614 usage += s * 2 + light_green('list home') + \
1615 ' will show timeline of list. You will be asked for list\'s name.\n'
1616 usage += s * 2 + light_green('list all_mem') + \
1617 ' will show list\'s all members.\n'
1618 usage += s * 2 + light_green('list all_sub') + \
1619 ' will show list\'s all subscribers.\n'
1620 usage += s * 2 + light_green('list add') + \
1621 ' will add specific person to 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 rm') + \
1624 ' will remove specific person from a list owned by you.' + \
1625 ' You will be asked for list\'s name and person\'s name.\n'
1626 usage += s * 2 + light_green('list sub') + \
1627 ' will subscribe you to a specific list.\n'
1628 usage += s * 2 + light_green('list unsub') + \
1629 ' will unsubscribe you from a specific list.\n'
1630 usage += s * 2 + light_green('list own') + \
1631 ' will show all list owned by you.\n'
1632 usage += s * 2 + light_green('list new') + \
1633 ' will create a new list.\n'
1634 usage += s * 2 + light_green('list update') + \
1635 ' will update a list owned by you.\n'
1636 usage += s * 2 + light_green('list del') + \
1637 ' will delete a list owned by you.\n'
1638 printNicely(usage)
1639
1640
1641 def help_stream():
1642 """
1643 Stream switch
1644 """
1645 s = ' ' * 2
1646 # Switch
1647 usage = '\n'
1648 usage += s + grey(u'\u266A' + ' Switching streams \n')
1649 usage += s * 2 + light_green('switch public #AKB') + \
1650 ' will switch to public stream and follow "' + \
1651 light_yellow('AKB') + '" keyword.\n'
1652 usage += s * 2 + light_green('switch mine') + \
1653 ' will switch to your personal stream.\n'
1654 usage += s * 2 + light_green('switch mine -f ') + \
1655 ' will prompt to enter the filter.\n'
1656 usage += s * 3 + light_yellow('Only nicks') + \
1657 ' filter will decide nicks will be INCLUDE ONLY.\n'
1658 usage += s * 3 + light_yellow('Ignore nicks') + \
1659 ' filter will decide nicks will be EXCLUDE.\n'
1660 usage += s * 2 + light_green('switch list') + \
1661 ' will switch to a Twitter list\'s stream. You will be asked for list name\n'
1662 printNicely(usage)
1663
1664
1665 def help():
1666 """
1667 Help
1668 """
1669 s = ' ' * 2
1670 h, w = os.popen('stty size', 'r').read().split()
1671 # Start
1672 usage = '\n'
1673 usage += s + 'Hi boss! I\'m ready to serve you right now!\n'
1674 usage += s + '-' * (int(w) - 4) + '\n'
1675 usage += s + 'You are ' + \
1676 light_yellow('already') + ' on your personal stream.\n'
1677 usage += s + 'Any update from Twitter will show up ' + \
1678 light_yellow('immediately') + '.\n'
1679 usage += s + 'In addition, following commands are available right now:\n'
1680 # Twitter help section
1681 usage += '\n'
1682 usage += s + grey(u'\u266A' + ' Twitter help\n')
1683 usage += s * 2 + light_green('h discover') + \
1684 ' will show help for discover commands.\n'
1685 usage += s * 2 + light_green('h tweets') + \
1686 ' will show help for tweets commands.\n'
1687 usage += s * 2 + light_green('h messages') + \
1688 ' will show help for messages commands.\n'
1689 usage += s * 2 + light_green('h friends_and_followers') + \
1690 ' will show help for friends and followers commands.\n'
1691 usage += s * 2 + light_green('h list') + \
1692 ' will show help for list commands.\n'
1693 usage += s * 2 + light_green('h stream') + \
1694 ' will show help for stream commands.\n'
1695 # Smart shell
1696 usage += '\n'
1697 usage += s + grey(u'\u266A' + ' Smart shell\n')
1698 usage += s * 2 + light_green('111111 * 9 / 7') + ' or any math expression ' + \
1699 'will be evaluate by Python interpreter.\n'
1700 usage += s * 2 + 'Even ' + light_green('cal') + ' will show the calendar' + \
1701 ' for current month.\n'
1702 # Config
1703 usage += '\n'
1704 usage += s + grey(u'\u266A' + ' Config \n')
1705 usage += s * 2 + light_green('theme') + ' will list available theme. ' + \
1706 light_green('theme monokai') + ' will apply ' + light_yellow('monokai') + \
1707 ' theme immediately.\n'
1708 usage += s * 2 + light_green('config') + ' will list all config.\n'
1709 usage += s * 3 + \
1710 light_green('config ASCII_ART') + ' will output current value of ' +\
1711 light_yellow('ASCII_ART') + ' config key.\n'
1712 usage += s * 3 + \
1713 light_green('config TREND_MAX default') + ' will output default value of ' + \
1714 light_yellow('TREND_MAX') + ' config key.\n'
1715 usage += s * 3 + \
1716 light_green('config CUSTOM_CONFIG drop') + ' will drop ' + \
1717 light_yellow('CUSTOM_CONFIG') + ' config key.\n'
1718 usage += s * 3 + \
1719 light_green('config IMAGE_ON_TERM = true') + ' will set value of ' + \
1720 light_yellow('IMAGE_ON_TERM') + ' config key to ' + \
1721 light_yellow('True') + '.\n'
1722 # Screening
1723 usage += '\n'
1724 usage += s + grey(u'\u266A' + ' Screening \n')
1725 usage += s * 2 + light_green('h') + ' will show this help again.\n'
1726 usage += s * 2 + light_green('p') + ' will pause the stream.\n'
1727 usage += s * 2 + light_green('r') + ' will unpause the stream.\n'
1728 usage += s * 2 + light_green('c') + ' will clear the screen.\n'
1729 usage += s * 2 + light_green('v') + ' will show version info.\n'
1730 usage += s * 2 + light_green('q') + ' will quit.\n'
1731 # End
1732 usage += '\n'
1733 usage += s + '-' * (int(w) - 4) + '\n'
1734 usage += s + 'Have fun and hang tight! \n'
1735 # Show help
1736 d = {
1737 'discover': help_discover,
1738 'tweets': help_tweets,
1739 'messages': help_messages,
1740 'friends_and_followers': help_friends_and_followers,
1741 'list': help_list,
1742 'stream': help_stream,
1743 }
1744 if g['stuff']:
1745 d.get(
1746 g['stuff'].strip(),
1747 lambda: printNicely(red('No such command.'))
1748 )()
1749 else:
1750 printNicely(usage)
1751
1752
1753 def pause():
1754 """
1755 Pause stream display
1756 """
1757 g['pause'] = True
1758 printNicely(green('Stream is paused'))
1759
1760
1761 def replay():
1762 """
1763 Replay stream
1764 """
1765 g['pause'] = False
1766 printNicely(green('Stream is running back now'))
1767
1768
1769 def clear():
1770 """
1771 Clear screen
1772 """
1773 os.system('clear')
1774
1775
1776 def quit():
1777 """
1778 Exit all
1779 """
1780 try:
1781 save_history()
1782 printNicely(green('See you next time :)'))
1783 except:
1784 pass
1785 sys.exit()
1786
1787
1788 def reset():
1789 """
1790 Reset prefix of line
1791 """
1792 if g['reset']:
1793 if c.get('USER_JSON_ERROR'):
1794 printNicely(red('Your ~/.rainbow_config.json is messed up:'))
1795 printNicely(red('>>> ' + c['USER_JSON_ERROR']))
1796 printNicely('')
1797 printNicely(magenta('Need tips ? Type "h" and hit Enter key!'))
1798 g['reset'] = False
1799 try:
1800 printNicely(str(eval(g['cmd'])))
1801 except Exception:
1802 pass
1803
1804
1805 # Command set
1806 cmdset = [
1807 'switch',
1808 'trend',
1809 'home',
1810 'notification',
1811 'view',
1812 'mentions',
1813 't',
1814 'rt',
1815 'quote',
1816 'allrt',
1817 'conversation',
1818 'fav',
1819 'rep',
1820 'repall',
1821 'del',
1822 'ufav',
1823 'share',
1824 's',
1825 'mes',
1826 'show',
1827 'open',
1828 'ls',
1829 'inbox',
1830 'thread',
1831 'trash',
1832 'whois',
1833 'fl',
1834 'ufl',
1835 'mute',
1836 'unmute',
1837 'muting',
1838 'block',
1839 'unblock',
1840 'report',
1841 'list',
1842 'cal',
1843 'config',
1844 'theme',
1845 'h',
1846 'p',
1847 'r',
1848 'c',
1849 'v',
1850 'q',
1851 ]
1852
1853 # Handle function set
1854 funcset = [
1855 switch,
1856 trend,
1857 home,
1858 notification,
1859 view,
1860 mentions,
1861 tweet,
1862 retweet,
1863 quote,
1864 allretweet,
1865 conversation,
1866 favorite,
1867 reply,
1868 reply_all,
1869 delete,
1870 unfavorite,
1871 share,
1872 search,
1873 message,
1874 show,
1875 urlopen,
1876 ls,
1877 inbox,
1878 thread,
1879 trash,
1880 whois,
1881 follow,
1882 unfollow,
1883 mute,
1884 unmute,
1885 muting,
1886 block,
1887 unblock,
1888 report,
1889 twitterlist,
1890 cal,
1891 config,
1892 theme,
1893 help,
1894 pause,
1895 replay,
1896 clear,
1897 upgrade_center,
1898 quit,
1899 ]
1900
1901
1902 def process(cmd):
1903 """
1904 Process switch
1905 """
1906 return dict(zip(cmdset, funcset)).get(cmd, reset)
1907
1908
1909 def listen():
1910 """
1911 Listen to user's input
1912 """
1913 d = dict(zip(
1914 cmdset,
1915 [
1916 ['public', 'mine', 'list'], # switch
1917 [], # trend
1918 [], # home
1919 [], # notification
1920 ['@'], # view
1921 [], # mentions
1922 [], # tweet
1923 [], # retweet
1924 [], # quote
1925 [], # allretweet
1926 [], # conversation
1927 [], # favorite
1928 [], # reply
1929 [], # reply_all
1930 [], # delete
1931 [], # unfavorite
1932 [], # url
1933 ['#'], # search
1934 ['@'], # message
1935 ['image'], # show image
1936 [''], # open url
1937 ['fl', 'fr'], # list
1938 [], # inbox
1939 [i for i in g['message_threads']], # sent
1940 [], # trash
1941 ['@'], # whois
1942 ['@'], # follow
1943 ['@'], # unfollow
1944 ['@'], # mute
1945 ['@'], # unmute
1946 ['@'], # muting
1947 ['@'], # block
1948 ['@'], # unblock
1949 ['@'], # report
1950 [
1951 'home',
1952 'all_mem',
1953 'all_sub',
1954 'add',
1955 'rm',
1956 'sub',
1957 'unsub',
1958 'own',
1959 'new',
1960 'update',
1961 'del'
1962 ], # list
1963 [], # cal
1964 [key for key in dict(get_all_config())], # config
1965 g['themes'], # theme
1966 [
1967 'discover',
1968 'tweets',
1969 'messages',
1970 'friends_and_followers',
1971 'list',
1972 'stream'
1973 ], # help
1974 [], # pause
1975 [], # reconnect
1976 [], # clear
1977 [], # version
1978 [], # quit
1979 ]
1980 ))
1981 init_interactive_shell(d)
1982 read_history()
1983 reset()
1984 while True:
1985 try:
1986 # raw_input
1987 if g['prefix']:
1988 # Only use PREFIX as a string with raw_input
1989 line = raw_input(g['decorated_name'](g['PREFIX']))
1990 else:
1991 line = raw_input()
1992 # Save cmd to compare with readline buffer
1993 g['cmd'] = line.strip()
1994 # Get short cmd to pass to handle function
1995 try:
1996 cmd = line.split()[0]
1997 except:
1998 cmd = ''
1999 # Lock the semaphore
2000 c['lock'] = True
2001 # Save cmd to global variable and call process
2002 g['stuff'] = ' '.join(line.split()[1:])
2003 # Process the command
2004 process(cmd)()
2005 # Not re-display
2006 if cmd in ['switch', 't', 'rt', 'rep']:
2007 g['prefix'] = False
2008 else:
2009 g['prefix'] = True
2010 # Release the semaphore lock
2011 c['lock'] = False
2012 except EOFError:
2013 printNicely('')
2014 except Exception:
2015 debug_option()
2016 printNicely(red('OMG something is wrong with Twitter right now.'))
2017
2018
2019 def reconn_notice():
2020 """
2021 Notice when Hangup or Timeout
2022 """
2023 guide = light_magenta('You can use ') + \
2024 light_green('switch') + \
2025 light_magenta(' command to return to your stream.\n')
2026 guide += light_magenta('Type ') + \
2027 light_green('h stream') + \
2028 light_magenta(' for more details.')
2029 printNicely(guide)
2030 sys.stdout.write(g['decorated_name'](g['PREFIX']))
2031 sys.stdout.flush()
2032
2033
2034 def stream(domain, args, name='Rainbow Stream'):
2035 """
2036 Track the stream
2037 """
2038 # The Logo
2039 art_dict = {
2040 c['USER_DOMAIN']: name,
2041 c['PUBLIC_DOMAIN']: args.track_keywords,
2042 c['SITE_DOMAIN']: name,
2043 }
2044 if c['ASCII_ART']:
2045 ascii_art(art_dict[domain])
2046 # These arguments are optional:
2047 stream_args = dict(
2048 timeout=0.5, # To check g['stream_stop'] after each 0.5 s
2049 block=True,
2050 heartbeat_timeout=c['HEARTBEAT_TIMEOUT'] * 60)
2051 # Track keyword
2052 query_args = dict()
2053 if args.track_keywords:
2054 query_args['track'] = args.track_keywords
2055 # Get stream
2056 stream = TwitterStream(
2057 auth=authen(),
2058 domain=domain,
2059 **stream_args)
2060 try:
2061 if domain == c['USER_DOMAIN']:
2062 tweet_iter = stream.user(**query_args)
2063 elif domain == c['SITE_DOMAIN']:
2064 tweet_iter = stream.site(**query_args)
2065 else:
2066 if args.track_keywords:
2067 tweet_iter = stream.statuses.filter(**query_args)
2068 else:
2069 tweet_iter = stream.statuses.sample()
2070 # Block new stream until other one exits
2071 StreamLock.acquire()
2072 g['stream_stop'] = False
2073 last_tweet_time = time.time()
2074 for tweet in tweet_iter:
2075 if tweet is None:
2076 printNicely('-- None --')
2077 elif tweet is Timeout:
2078 # Because the stream check for each 0.3s
2079 # so we shouldn't output anything here
2080 if(g['stream_stop']):
2081 StreamLock.release()
2082 break
2083 elif tweet is HeartbeatTimeout:
2084 printNicely('-- Heartbeat Timeout --')
2085 reconn_notice()
2086 StreamLock.release()
2087 break
2088 elif tweet is Hangup:
2089 printNicely('-- Hangup --')
2090 reconn_notice()
2091 StreamLock.release()
2092 break
2093 elif tweet.get('text'):
2094 # Slow down the stream by STREAM_DELAY config key
2095 if time.time() - last_tweet_time < c['STREAM_DELAY']:
2096 continue
2097 last_tweet_time = time.time()
2098 # Check the semaphore pause and lock (stream process only)
2099 if g['pause']:
2100 continue
2101 while c['lock']:
2102 time.sleep(0.5)
2103 # Draw the tweet
2104 draw(
2105 t=tweet,
2106 keyword=args.track_keywords,
2107 humanize=False,
2108 fil=args.filter,
2109 ig=args.ignore,
2110 )
2111 # Current readline buffer
2112 current_buffer = readline.get_line_buffer().strip()
2113 # There is an unexpected behaviour in MacOSX readline + Python 2:
2114 # after completely delete a word after typing it,
2115 # somehow readline buffer still contains
2116 # the 1st character of that word
2117 if current_buffer and g['cmd'] != current_buffer:
2118 sys.stdout.write(
2119 g['decorated_name'](g['PREFIX']) + current_buffer)
2120 sys.stdout.flush()
2121 elif not c['HIDE_PROMPT']:
2122 sys.stdout.write(g['decorated_name'](g['PREFIX']))
2123 sys.stdout.flush()
2124 elif tweet.get('direct_message'):
2125 # Check the semaphore pause and lock (stream process only)
2126 if g['pause']:
2127 continue
2128 while c['lock']:
2129 time.sleep(0.5)
2130 print_message(tweet['direct_message'])
2131 elif tweet.get('event'):
2132 c['events'].append(tweet)
2133 print_event(tweet)
2134 except TwitterHTTPError as e:
2135 printNicely('')
2136 printNicely(
2137 magenta('We have connection problem with twitter stream API right now :('))
2138 detail_twitter_error(e)
2139 sys.stdout.write(g['decorated_name'](g['PREFIX']))
2140 sys.stdout.flush()
2141 except (URLError, ConnectionResetError):
2142 printNicely(
2143 magenta('There seems to be a connection problem.'))
2144 save_history()
2145 sys.exit()
2146
2147
2148 def fly():
2149 """
2150 Main function
2151 """
2152 # Initial
2153 args = parse_arguments()
2154 try:
2155 proxy_connect(args)
2156 init(args)
2157 # Twitter API connection problem
2158 except TwitterHTTPError as e:
2159 printNicely('')
2160 printNicely(
2161 magenta('We have connection problem with twitter REST API right now :('))
2162 detail_twitter_error(e)
2163 save_history()
2164 sys.exit()
2165 # Proxy connection problem
2166 except (socks.ProxyConnectionError, URLError):
2167 printNicely(
2168 magenta('There seems to be a connection problem.'))
2169 printNicely(
2170 magenta('You might want to check your proxy settings (host, port and type)!'))
2171 save_history()
2172 sys.exit()
2173
2174 # Spawn stream thread
2175 target = args.stream.split()[0]
2176 if target == 'public':
2177 try:
2178 keyword = args.stream.split()[1]
2179 if keyword[0] == '#':
2180 keyword = keyword[1:]
2181 args.track_keywords = keyword
2182 # Set the variable to tracked keyword
2183 g['keyword'] = keyword
2184 # Reset prefix
2185 g['PREFIX'] = u2str(emojize(format_prefix(keyword=g['keyword'])))
2186 # Start new thread
2187 th = threading.Thread(
2188 target=stream,
2189 args=(
2190 c['PUBLIC_DOMAIN'],
2191 args))
2192 th.daemon = True
2193 th.start()
2194 except:
2195 printNicely(red('Public requires a keyword! Loading your personal stream.'))
2196 # Start new thread
2197 th = threading.Thread(
2198 target=stream,
2199 args=(
2200 c['USER_DOMAIN'],
2201 args,
2202 g['original_name']))
2203 th.daemon = True
2204 th.start()
2205 elif target == "list":
2206 try:
2207 owner, slug = check_slug(args.stream.split()[1])
2208 # Force python 2 not redraw readline buffer
2209 listname = '/'.join([owner, slug])
2210 # Set the listname variable
2211 # and reset tracked keyword
2212 g['listname'] = listname
2213 g['keyword'] = ''
2214 g['PREFIX'] = g['cmd'] = u2str(emojize(format_prefix(
2215 listname=g['listname']
2216 )))
2217 printNicely(light_yellow('getting list members ...'))
2218 # Get members
2219 t = Twitter(auth=authen())
2220 members = []
2221 next_cursor = -1
2222 while next_cursor != 0:
2223 m = t.lists.members(
2224 slug=slug,
2225 owner_screen_name=owner,
2226 cursor=next_cursor,
2227 include_entities=False)
2228 for u in m['users']:
2229 members.append('@' + u['screen_name'])
2230 next_cursor = m['next_cursor']
2231 printNicely(light_yellow('... done.'))
2232 # Build thread filter array
2233 args.filter = members
2234 # Kill old thread
2235 g['stream_stop'] = True
2236 # Start new thread
2237 th = threading.Thread(
2238 target=stream,
2239 args=(
2240 c['USER_DOMAIN'],
2241 args,
2242 slug))
2243 th.daemon = True
2244 th.start()
2245 printNicely('')
2246 if args.filter:
2247 printNicely(cyan('Include: ' + str(len(args.filter)) + ' people.'))
2248 except:
2249 printNicely(red('List requieres a correct name of a list! Loading your personal stream.'))
2250 # Start new thread
2251 th = threading.Thread(
2252 target=stream,
2253 args=(
2254 c['USER_DOMAIN'],
2255 args,
2256 g['original_name']))
2257 th.daemon = True
2258 th.start()
2259 elif target == "mine":
2260 # Start new thread
2261 th = threading.Thread(
2262 target=stream,
2263 args=(
2264 c['USER_DOMAIN'],
2265 args,
2266 g['original_name']))
2267 th.daemon = True
2268 th.start()
2269 else:
2270 printNicely(red('Wrong -s/--stream argument given. Loading your personal stream.'))
2271 # Start new thread
2272 th = threading.Thread(
2273 target=stream,
2274 args=(
2275 c['USER_DOMAIN'],
2276 args,
2277 g['original_name']))
2278 th.daemon = True
2279 th.start()
2280
2281 # Start listen process
2282 time.sleep(0.5)
2283 g['reset'] = True
2284 g['prefix'] = True
2285 listen()