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