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