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