Merge pull request #77 from nwtti/master
[rainbowstream.git] / rainbowstream / rainbow.py
index 03a3f1315f33b0f22a67d840638a4dda4550fe25..7fdab8498e3908e4057c9fadbfec5b70f937ea26 100644 (file)
@@ -7,7 +7,12 @@ import time
 import threading
 import requests
 import webbrowser
+import traceback
+import pkg_resources
+import socks
+import socket
 
+from io import BytesIO
 from twitter.stream import TwitterStream, Timeout, HeartbeatTimeout, Hangup
 from twitter.api import *
 from twitter.oauth import OAuth, read_token_file
@@ -21,6 +26,8 @@ from .consumer import *
 from .interactive import *
 from .c_image import *
 from .py3patch import *
+from .emoji import *
+from .util import *
 
 # Global values
 g = {}
@@ -34,6 +41,11 @@ def parse_arguments():
     Parse the arguments
     """
     parser = argparse.ArgumentParser(description=__doc__ or "")
+    parser.add_argument(
+        '-s',
+        '--stream',
+       default="mine",
+        help='Default stream after program start. (Default: mine)')
     parser.add_argument(
         '-to',
         '--timeout',
@@ -55,9 +67,48 @@ def parse_arguments():
         '--image-on-term',
         action='store_true',
         help='Display all image on terminal.')
+    parser.add_argument(
+        '-ph',
+        '--proxy-host',
+        help='Use HTTP/SOCKS proxy for network connections.')
+    parser.add_argument(
+        '-pp',
+        '--proxy-port',
+        default=8080,
+        help='HTTP/SOCKS proxy port (Default: 8080).')
+    parser.add_argument(
+        '-pt',
+        '--proxy-type',
+        default='SOCKS5',
+        help='Proxy type (HTTP, SOCKS4, SOCKS5; Default: SOCKS5).')
     return parser.parse_args()
 
 
+def proxy_connect(args):
+    """
+    Connect to specified proxy
+    """
+    if args.proxy_host:
+        # Setup proxy by monkeypatching the standard lib
+        if args.proxy_type.lower() == "socks5" or not args.proxy_type:
+            socks.set_default_proxy(
+                socks.SOCKS5, args.proxy_host,
+                int(args.proxy_port))
+        elif args.proxy_type.lower() == "http":
+            socks.set_default_proxy(
+                socks.HTTP, args.proxy_host,
+                int(args.proxy_port))
+        elif args.proxy_type.lower() == "socks4":
+            socks.set_default_proxy(
+                socks.SOCKS4, args.proxy_host,
+                int(args.proxy_port))
+        else:
+            printNicely(
+                magenta('Sorry, wrong proxy type specified! Aborting...'))
+            sys.exit()
+        socket.socket = socks.socksocket
+
+
 def authen():
     """
     Authenticate with Twitter OAuth
@@ -69,7 +120,7 @@ def authen():
             'USERPROFILE',
             '')) + os.sep + '.rainbow_oauth'
     if not os.path.exists(twitter_credential):
-        oauth_dance("Rainbow Stream",
+        oauth_dance('Rainbow Stream',
                     CONSUMER_KEY,
                     CONSUMER_SECRET,
                     twitter_credential)
@@ -108,6 +159,40 @@ def build_mute_dict(dict_data=False):
         return screen_name_list
 
 
+def debug_option():
+    """
+    Save traceback when run in debug mode
+    """
+    if g['debug']:
+        g['traceback'].append(traceback.format_exc())
+
+
+def upgrade_center():
+    """
+    Check latest and notify to upgrade
+    """
+    try:
+        current = pkg_resources.get_distribution('rainbowstream').version
+        url = 'https://raw.githubusercontent.com/DTVD/rainbowstream/master/setup.py'
+        readme = requests.get(url).text
+        latest = readme.split('version = \'')[1].split('\'')[0]
+        if current != latest:
+            notice = light_magenta('RainbowStream latest version is ')
+            notice += light_green(latest)
+            notice += light_magenta(' while your current version is ')
+            notice += light_yellow(current) + '\n'
+            notice += light_magenta('You should upgrade with ')
+            notice += light_green('pip install -U rainbowstream')
+            printNicely(notice)
+        else:
+            notice = light_yellow('You are running latest version (')
+            notice += light_green(current)
+            notice += light_yellow(')')
+            printNicely(notice)
+    except:
+        pass
+
+
 def init(args):
     """
     Init function
@@ -115,29 +200,47 @@ def init(args):
     # Handle Ctrl C
     ctrl_c_handler = lambda signum, frame: quit()
     signal.signal(signal.SIGINT, ctrl_c_handler)
+    # Upgrade notify
+    upgrade_center()
     # Get name
     t = Twitter(auth=authen())
-    name = '@' + t.account.verify_credentials()['screen_name']
-    if not get_config('PREFIX'):
-        set_config('PREFIX', name)
-    g['original_name'] = name[1:]
+    credential = t.account.verify_credentials()
+    screen_name = '@' + credential['screen_name']
+    name = credential['name']
+    c['original_name'] = g['original_name'] = screen_name[1:]
+    g['listname'] = g['keyword'] = ''
+    g['PREFIX'] = u2str(emojize(format_prefix()))
+    g['full_name'] = name
     g['decorated_name'] = lambda x: color_func(
-        c['DECORATED_NAME'])('[' + x + ']: ')
+        c['DECORATED_NAME'])('[' + x + ']: ', rl=True)
     # Theme init
     files = os.listdir(os.path.dirname(__file__) + '/colorset')
     themes = [f.split('.')[0] for f in files if f.split('.')[-1] == 'json']
     g['themes'] = themes
+    g['pause'] = False
+    g['message_threads'] = {}
     # Startup cmd
     g['cmd'] = ''
+    # Debug option default = True
+    g['debug'] = True
+    g['traceback'] = []
+    # Events
+    c['events'] = []
     # Semaphore init
     c['lock'] = False
-    c['pause'] = False
     # Init tweet dict and message dict
     c['tweet_dict'] = []
     c['message_dict'] = []
     # Image on term
     c['IMAGE_ON_TERM'] = args.image_on_term
     set_config('IMAGE_ON_TERM', str(c['IMAGE_ON_TERM']))
+    # Check type of ONLY_LIST and IGNORE_LIST
+    if not isinstance(c['ONLY_LIST'], list):
+        printNicely(red('ONLY_LIST is not a valid list value.'))
+        c['ONLY_LIST'] = []
+    if not isinstance(c['IGNORE_LIST'], list):
+        printNicely(red('IGNORE_LIST is not a valid list value.'))
+        c['IGNORE_LIST'] = []
     # Mute dict
     c['IGNORE_LIST'] += build_mute_dict()
 
@@ -191,6 +294,18 @@ def home():
     printNicely('')
 
 
+def notification():
+    """
+    Show notifications
+    """
+    if c['events']:
+        for e in c['events']:
+            print_event(e)
+        printNicely('')
+    else:
+        printNicely(magenta('Nothing at this time.'))
+
+
 def mentions():
     """
     Mentions timeline
@@ -217,7 +332,8 @@ def whois():
                 include_entities=False)
             show_profile(user)
         except:
-            printNicely(red('Omg no user.'))
+            debug_option()
+            printNicely(red('No user.'))
     else:
         printNicely(red('A name should begin with a \'@\''))
 
@@ -233,7 +349,8 @@ def view():
             num = int(g['stuff'].split()[1])
         except:
             num = c['HOME_TWEET_NUM']
-        for tweet in reversed(t.statuses.user_timeline(count=num, screen_name=user[1:])):
+        for tweet in reversed(
+                t.statuses.user_timeline(count=num, screen_name=user[1:])):
             draw(t=tweet)
         printNicely('')
     else:
@@ -245,13 +362,24 @@ def search():
     Search
     """
     t = Twitter(auth=authen())
-    g['stuff'] = g['stuff'].strip()
-    rel = t.search.tweets(q=g['stuff'])['statuses']
+    # Setup query
+    query = g['stuff'].strip()
+    type = c['SEARCH_TYPE']
+    if type not in ['mixed', 'recent', 'popular']:
+        type = 'mixed'
+    max_record = c['SEARCH_MAX_RECORD']
+    count = min(max_record, 100)
+    # Perform search
+    rel = t.search.tweets(
+        q=query,
+        type=type,
+        count=count
+    )['statuses']
+    # Return results
     if rel:
         printNicely('Newest tweets:')
-        for i in reversed(xrange(c['SEARCH_MAX_RECORD'])):
-            draw(t=rel[i],
-                 keyword=g['stuff'])
+        for i in reversed(xrange(count)):
+            draw(t=rel[i], keyword=query)
         printNicely('')
     else:
         printNicely(magenta('I\'m afraid there is no result'))
@@ -297,7 +425,8 @@ def quote():
     if not formater:
         return
     # Get comment
-    prefix = light_magenta('Compose your ') + light_green('#comment: ')
+    prefix = light_magenta('Compose your ', rl=True) + \
+        light_green('#comment: ', rl=True)
     comment = raw_input(prefix)
     if comment:
         quote = comment.join(formater.split('#comment'))
@@ -377,6 +506,27 @@ def reply():
     t.statuses.update(status=status, in_reply_to_status_id=tid)
 
 
+def reply_all():
+    """
+    Reply to all
+    """
+    t = Twitter(auth=authen())
+    try:
+        id = int(g['stuff'].split()[0])
+    except:
+        printNicely(red('Sorry I can\'t understand.'))
+        return
+    tid = c['tweet_dict'][id]
+    original_tweet = t.statuses.show(id=tid)
+    text = original_tweet['text']
+    owner = '@' + original_tweet['user']['screen_name']
+    nick_ary = ['@' + re.sub('[\W_]', '', w)
+                for w in text.split() if w.startswith('@')] + [owner]
+    status = ' '.join(g['stuff'].split()[1:])
+    status = ' '.join(nick_ary) + ' ' + str2u(status)
+    t.statuses.update(status=status, in_reply_to_status_id=tid)
+
+
 def favorite():
     """
     Favorite
@@ -411,6 +561,28 @@ def unfavorite():
     printNicely('')
 
 
+def share():
+    """
+    Copy url of a tweet to clipboard
+    """
+    t = Twitter(auth=authen())
+    try:
+        id = int(g['stuff'].split()[0])
+        tid = c['tweet_dict'][id]
+    except:
+        printNicely(red('Tweet id is not valid.'))
+        return
+    tweet = t.statuses.show(id=tid)
+    url = 'https://twitter.com/' + \
+        tweet['user']['screen_name'] + '/status/' + str(tid)
+    import platform
+    if platform.system().lower() == 'darwin':
+        os.system("echo '%s' | pbcopy" % url)
+        printNicely(green('Copied tweet\'s url to clipboard.'))
+    else:
+        printNicely('Direct link: ' + yellow(url))
+
+
 def delete():
     """
     Delete
@@ -444,6 +616,7 @@ def show():
             img = Image.open(BytesIO(res.content))
             img.show()
     except:
+        debug_option()
         printNicely(red('Sorry I can\'t show this image.'))
 
 
@@ -457,30 +630,32 @@ def urlopen():
             return
         tid = c['tweet_dict'][int(g['stuff'])]
         tweet = t.statuses.show(id=tid)
-        link_ary = [
-            u for u in tweet['text'].split() if u.startswith('http://')]
+        link_prefix = ('http://', 'https://')
+        link_ary = [u for u in tweet['text'].split()
+                    if u.startswith(link_prefix)]
         if not link_ary:
             printNicely(light_magenta('No url here @.@!'))
             return
         for link in link_ary:
             webbrowser.open(link)
     except:
+        debug_option()
         printNicely(red('Sorry I can\'t open url in this tweet.'))
 
 
 def inbox():
     """
-    Inbox direct messages
+    Inbox threads
     """
     t = Twitter(auth=authen())
     num = c['MESSAGES_DISPLAY']
-    rel = []
     if g['stuff'].isdigit():
         num = g['stuff']
+    # Get inbox messages
     cur_page = 1
-    # Max message per page is 20 so we have to loop
+    inbox = []
     while num > 20:
-        rel = rel + t.direct_messages(
+        inbox = inbox + t.direct_messages(
             count=20,
             page=cur_page,
             include_entities=False,
@@ -488,32 +663,20 @@ def inbox():
         )
         num -= 20
         cur_page += 1
-    rel = rel + t.direct_messages(
+    inbox = inbox + t.direct_messages(
         count=num,
         page=cur_page,
         include_entities=False,
         skip_status=False
     )
-    # Display
-    printNicely('Inbox: newest ' + str(len(rel)) + ' messages.')
-    for m in reversed(rel):
-        print_message(m)
-    printNicely('')
-
-
-def sent():
-    """
-    Sent direct messages
-    """
-    t = Twitter(auth=authen())
+    # Get sent messages
     num = c['MESSAGES_DISPLAY']
-    rel = []
     if g['stuff'].isdigit():
-        num = int(g['stuff'])
+        num = g['stuff']
     cur_page = 1
-    # Max message per page is 20 so we have to loop
+    sent = []
     while num > 20:
-        rel = rel + t.direct_messages.sent(
+        sent = sent + t.direct_messages.sent(
             count=20,
             page=cur_page,
             include_entities=False,
@@ -521,17 +684,45 @@ def sent():
         )
         num -= 20
         cur_page += 1
-    rel = rel + t.direct_messages.sent(
+    sent = sent + t.direct_messages.sent(
         count=num,
         page=cur_page,
         include_entities=False,
         skip_status=False
     )
-    # Display
-    printNicely('Sent: newest ' + str(len(rel)) + ' messages.')
-    for m in reversed(rel):
-        print_message(m)
-    printNicely('')
+
+    d = {}
+    uniq_inbox = list(set(
+        [(m['sender_screen_name'], m['sender']['name']) for m in inbox]
+    ))
+    uniq_sent = list(set(
+        [(m['recipient_screen_name'], m['recipient']['name']) for m in sent]
+    ))
+    for partner in uniq_inbox:
+        inbox_ary = [m for m in inbox if m['sender_screen_name'] == partner[0]]
+        sent_ary = [
+            m for m in sent if m['recipient_screen_name'] == partner[0]]
+        d[partner] = inbox_ary + sent_ary
+    for partner in uniq_sent:
+        if partner not in d:
+            d[partner] = [
+                m for m in sent if m['recipient_screen_name'] == partner[0]]
+    g['message_threads'] = print_threads(d)
+
+
+def thread():
+    """
+    View a thread of message
+    """
+    try:
+        thread_id = int(g['stuff'])
+        print_thread(
+            g['message_threads'][thread_id],
+            g['original_name'],
+            g['full_name'])
+    except Exception:
+        debug_option()
+        printNicely(red('No such thread.'))
 
 
 def message():
@@ -539,19 +730,20 @@ def message():
     Send a direct message
     """
     t = Twitter(auth=authen())
-    user = g['stuff'].split()[0]
-    if user[0].startswith('@'):
-        try:
-            content = g['stuff'].split()[1]
-        except:
-            printNicely(red('Sorry I can\'t understand.'))
-        t.direct_messages.new(
-            screen_name=user[1:],
-            text=content
-        )
-        printNicely(green('Message sent.'))
-    else:
-        printNicely(red('A name should begin with a \'@\''))
+    try:
+        user = g['stuff'].split()[0]
+        if user[0].startswith('@'):
+            content = ' '.join(g['stuff'].split()[1:])
+            t.direct_messages.new(
+                screen_name=user[1:],
+                text=content
+            )
+            printNicely(green('Message sent.'))
+        else:
+            printNicely(red('A name should begin with a \'@\''))
+    except:
+        debug_option()
+        printNicely(red('Sorry I can\'t understand.'))
 
 
 def trash():
@@ -659,6 +851,7 @@ def mute():
             else:
                 printNicely(red(rel))
         except:
+            debug_option()
             printNicely(red('Something is wrong, can not mute now :('))
     else:
         printNicely(red('A name should begin with a \'@\''))
@@ -751,10 +944,11 @@ def report():
 
 def get_slug():
     """
-    Get Slug Decorator
+    Get slug
     """
     # Get list name
-    list_name = raw_input(light_magenta('Give me the list\'s name: '))
+    list_name = raw_input(
+        light_magenta('Give me the list\'s name ("@owner/list_name"): ', rl=True))
     # Get list name and owner
     try:
         owner, slug = list_name.split('/')
@@ -766,6 +960,20 @@ def get_slug():
             light_magenta('List name should follow "@owner/list_name" format.'))
         raise Exception('Wrong list name')
 
+def check_slug(list_name):
+    """
+    Check slug
+    """
+    # Get list name and owner
+    try:
+        owner, slug = list_name.split('/')
+        if slug.startswith('@'):
+            slug = slug[1:]
+        return owner, slug
+    except:
+        printNicely(
+            light_magenta('List name should follow "@owner/list_name" format.'))
+        raise Exception('Wrong list name')
 
 def show_lists(t):
     """
@@ -788,7 +996,7 @@ def list_home(t):
         owner_screen_name=owner,
         count=c['LIST_MAX'],
         include_entities=False)
-    for tweet in res:
+    for tweet in reversed(res):
         draw(t=tweet)
     printNicely('')
 
@@ -847,7 +1055,10 @@ def list_add(t):
     """
     owner, slug = get_slug()
     # Add
-    user_name = raw_input(light_magenta('Give me name of the newbie: '))
+    user_name = raw_input(
+        light_magenta(
+            'Give me name of the newbie: ',
+            rl=True))
     if user_name.startswith('@'):
         user_name = user_name[1:]
     try:
@@ -857,6 +1068,7 @@ def list_add(t):
             screen_name=user_name)
         printNicely(green('Added.'))
     except:
+        debug_option()
         printNicely(light_magenta('I\'m sorry we can not add him/her.'))
 
 
@@ -866,7 +1078,10 @@ def list_remove(t):
     """
     owner, slug = get_slug()
     # Remove
-    user_name = raw_input(light_magenta('Give me name of the unlucky one: '))
+    user_name = raw_input(
+        light_magenta(
+            'Give me name of the unlucky one: ',
+            rl=True))
     if user_name.startswith('@'):
         user_name = user_name[1:]
     try:
@@ -876,6 +1091,7 @@ def list_remove(t):
             screen_name=user_name)
         printNicely(green('Gone.'))
     except:
+        debug_option()
         printNicely(light_magenta('I\'m sorry we can not remove him/her.'))
 
 
@@ -891,6 +1107,7 @@ def list_subscribe(t):
             owner_screen_name=owner)
         printNicely(green('Done.'))
     except:
+        debug_option()
         printNicely(
             light_magenta('I\'m sorry you can not subscribe to this list.'))
 
@@ -907,6 +1124,7 @@ def list_unsubscribe(t):
             owner_screen_name=owner)
         printNicely(green('Done.'))
     except:
+        debug_option()
         printNicely(
             light_magenta('I\'m sorry you can not unsubscribe to this list.'))
 
@@ -933,9 +1151,15 @@ def list_new(t):
     """
     Create a new list
     """
-    name = raw_input(light_magenta('New list\'s name: '))
-    mode = raw_input(light_magenta('New list\'s mode (public/private): '))
-    description = raw_input(light_magenta('New list\'s description: '))
+    name = raw_input(light_magenta('New list\'s name: ', rl=True))
+    mode = raw_input(
+        light_magenta(
+            'New list\'s mode (public/private): ',
+            rl=True))
+    description = raw_input(
+        light_magenta(
+            'New list\'s description: ',
+            rl=True))
     try:
         t.lists.create(
             name=name,
@@ -943,6 +1167,7 @@ def list_new(t):
             description=description)
         printNicely(green(name + ' list is created.'))
     except:
+        debug_option()
         printNicely(red('Oops something is wrong with Twitter :('))
 
 
@@ -950,10 +1175,16 @@ def list_update(t):
     """
     Update a list
     """
-    slug = raw_input(light_magenta('Your list that you want to update: '))
-    name = raw_input(light_magenta('Update name (leave blank to unchange): '))
-    mode = raw_input(light_magenta('Update mode (public/private): '))
-    description = raw_input(light_magenta('Update description: '))
+    slug = raw_input(
+        light_magenta(
+            'Your list that you want to update: ',
+            rl=True))
+    name = raw_input(
+        light_magenta(
+            'Update name (leave blank to unchange): ',
+            rl=True))
+    mode = raw_input(light_magenta('Update mode (public/private): ', rl=True))
+    description = raw_input(light_magenta('Update description: ', rl=True))
     try:
         if name:
             t.lists.update(
@@ -970,6 +1201,7 @@ def list_update(t):
                 description=description)
         printNicely(green(slug + ' list is updated.'))
     except:
+        debug_option()
         printNicely(red('Oops something is wrong with Twitter :('))
 
 
@@ -977,13 +1209,17 @@ def list_delete(t):
     """
     Delete a list
     """
-    slug = raw_input(light_magenta('Your list that you want to delete: '))
+    slug = raw_input(
+        light_magenta(
+            'Your list that you want to delete: ',
+            rl=True))
     try:
         t.lists.destroy(
             slug='-'.join(slug.split()),
             owner_screen_name=g['original_name'])
         printNicely(green(slug + ' list is deleted.'))
     except:
+        debug_option()
         printNicely(red('Oops something is wrong with Twitter :('))
 
 
@@ -1034,9 +1270,6 @@ def switch():
                 ignore = raw_input('Ignore nicks [Ex: @xxx,@yy]: ')
                 args.filter = filter(None, only.split(','))
                 args.ignore = filter(None, ignore.split(','))
-            elif g['stuff'].split()[-1] == '-d':
-                args.filter = c['ONLY_LIST']
-                args.ignore = c['IGNORE_LIST']
         except:
             printNicely(red('Sorry, wrong format.'))
             return
@@ -1048,6 +1281,12 @@ def switch():
             # Kill old thread
             g['stream_stop'] = True
             args.track_keywords = keyword
+            # Set the variable to tracked keyword
+            # and reset the listname
+            g['keyword'] = keyword
+            g['listname'] = ''
+            # Reset prefix
+            g['PREFIX'] = u2str(emojize(format_prefix(keyword=g['keyword'])))
             # Start new thread
             th = threading.Thread(
                 target=stream,
@@ -1060,6 +1299,10 @@ def switch():
         elif target == 'mine':
             # Kill old thread
             g['stream_stop'] = True
+            # Reset the tracked keyword and listname
+            g['keyword'] = g['listname'] = ''
+            # Reset prefix
+            g['PREFIX'] = u2str(emojize(format_prefix()))
             # Start new thread
             th = threading.Thread(
                 target=stream,
@@ -1069,13 +1312,57 @@ def switch():
                     g['original_name']))
             th.daemon = True
             th.start()
+        # Stream base on list
+        elif target == 'list':
+            try:
+                owner, slug = check_slug(g['stuff'].split()[1])
+            except:
+                owner, slug = get_slug()
+            # Force python 2 not redraw readline buffer
+            listname = '/'.join([owner, slug])
+            # Set the listname variable
+            # and reset tracked keyword
+            g['listname'] = listname
+            g['keyword'] = ''
+            g['PREFIX'] = g['cmd'] = u2str(emojize(format_prefix(
+                listname=g['listname']
+            )))
+            printNicely(light_yellow('getting list members ...'))
+            # Get members
+            t = Twitter(auth=authen())
+            members = []
+            next_cursor = -1
+            while next_cursor != 0:
+                m = t.lists.members(
+                    slug=slug,
+                    owner_screen_name=owner,
+                    cursor=next_cursor,
+                    include_entities=False)
+                for u in m['users']:
+                    members.append('@' + u['screen_name'])
+                next_cursor = m['next_cursor']
+            printNicely(light_yellow('... done.'))
+            # Build thread filter array
+            args.filter = members
+            # Kill old thread
+            g['stream_stop'] = True
+            # Start new thread
+            th = threading.Thread(
+                target=stream,
+                args=(
+                    c['USER_DOMAIN'],
+                    args,
+                    slug))
+            th.daemon = True
+            th.start()
         printNicely('')
         if args.filter:
-            printNicely(cyan('Only: ' + str(args.filter)))
+            printNicely(cyan('Include: ' + str(len(args.filter)) + ' people.'))
         if args.ignore:
-            printNicely(red('Ignore: ' + str(args.ignore)))
+            printNicely(red('Ignore: ' + str(len(args.ignore)) + ' people.'))
         printNicely('')
     except:
+        debug_option()
         printNicely(red('Sorry I can\'t understand.'))
 
 
@@ -1149,16 +1436,18 @@ def config():
             value = get_default_config(key)
             line = ' ' * 2 + green(key) + ': ' + light_magenta(value)
             printNicely(line)
-        except Exception as e:
-            printNicely(red(e))
+        except:
+            debug_option()
+            printNicely(red('Just can not get the default.'))
     # Delete specific config key in config file
     elif len(g['stuff'].split()) == 2 and g['stuff'].split()[-1] == 'drop':
         key = g['stuff'].split()[0]
         try:
             delete_config(key)
             printNicely(green('Config key is dropped.'))
-        except Exception as e:
-            printNicely(red(e))
+        except:
+            debug_option()
+            printNicely(red('Just can not drop the key.'))
     # Set specific config
     elif len(g['stuff'].split()) == 3 and g['stuff'].split()[1] == '=':
         key = g['stuff'].split()[0]
@@ -1168,15 +1457,21 @@ def config():
             return
         try:
             set_config(key, value)
-            # Apply theme immediately
+            # Keys that needs to be apply immediately
             if key == 'THEME':
                 c['THEME'] = reload_theme(value, c['THEME'])
                 g['decorated_name'] = lambda x: color_func(
                     c['DECORATED_NAME'])('[' + x + ']: ')
+            elif key == 'PREFIX':
+                g['PREFIX'] = u2str(emojize(format_prefix(
+                    listname=g['listname'],
+                    keyword=g['keyword']
+                )))
             reload_config()
             printNicely(green('Updated successfully.'))
-        except Exception as e:
-            printNicely(red(e))
+        except:
+            debug_option()
+            printNicely(red('Just can not set the key.'))
     else:
         printNicely(light_magenta('Sorry I can\'s understand.'))
 
@@ -1194,6 +1489,8 @@ def help_discover():
         light_green('trend JP Tokyo') + '.\n'
     usage += s * 2 + light_green('home') + ' will show your timeline. ' + \
         light_green('home 7') + ' will show 7 tweets.\n'
+    usage += s * 2 + \
+        light_green('notification') + ' will show your recent notification.\n'
     usage += s * 2 + light_green('mentions') + ' will show mentions timeline. ' + \
         light_green('mentions 7') + ' will show 7 mention tweets.\n'
     usage += s * 2 + light_green('whois @mdo') + ' will show profile  of ' + \
@@ -1229,7 +1526,10 @@ def help_tweets():
     usage += s * 2 + light_green('conversation 12') + ' will show the chain of ' + \
         'replies prior to the tweet with ' + light_yellow('[id=12]') + '.\n'
     usage += s * 2 + light_green('rep 12 oops') + ' will reply "' + \
-        light_yellow('oops') + '" to tweet with ' + \
+        light_yellow('oops') + '" to the owner of the tweet with ' + \
+        light_yellow('[id=12]') + '.\n'
+    usage += s * 2 + light_green('repall 12 oops') + ' will reply "' + \
+        light_yellow('oops') + '" to all people in the tweet with ' + \
         light_yellow('[id=12]') + '.\n'
     usage += s * 2 + \
         light_green('fav 12 ') + ' will favorite the tweet with ' + \
@@ -1237,6 +1537,9 @@ def help_tweets():
     usage += s * 2 + \
         light_green('ufav 12 ') + ' will unfavorite tweet with ' + \
         light_yellow('[id=12]') + '.\n'
+    usage += s * 2 + \
+        light_green('share 12 ') + ' will get the direct link of the tweet with ' + \
+        light_yellow('[id=12]') + '.\n'
     usage += s * 2 + \
         light_green('del 12 ') + ' will delete tweet with ' + \
         light_yellow('[id=12]') + '.\n'
@@ -1257,8 +1560,8 @@ def help_messages():
     usage += s + grey(u'\u266A' + ' Direct messages \n')
     usage += s * 2 + light_green('inbox') + ' will show inbox messages. ' + \
         light_green('inbox 7') + ' will show newest 7 messages.\n'
-    usage += s * 2 + light_green('sent') + ' will show sent messages. ' + \
-        light_green('sent 7') + ' will show newest 7 messages.\n'
+    usage += s * 2 + light_green('thread 2') + ' will show full thread with ' + \
+        light_yellow('[thread_id=2]') + '.\n'
     usage += s * 2 + light_green('mes @dtvd88 hi') + ' will send a "hi" messege to ' + \
         magenta('@dtvd88') + '.\n'
     usage += s * 2 + light_green('trash 5') + ' will remove message with ' + \
@@ -1354,8 +1657,8 @@ def help_stream():
         ' filter will decide nicks will be INCLUDE ONLY.\n'
     usage += s * 3 + light_yellow('Ignore nicks') + \
         ' filter will decide nicks will be EXCLUDE.\n'
-    usage += s * 2 + light_green('switch mine -d') + \
-        ' will use the config\'s ONLY_LIST and IGNORE_LIST.\n'
+    usage += s * 2 + light_green('switch list') + \
+        ' will switch to a Twitter list\'s stream. You will be asked for list name\n'
     printNicely(usage)
 
 
@@ -1423,6 +1726,7 @@ def help():
     usage += s * 2 + light_green('p') + ' will pause the stream.\n'
     usage += s * 2 + light_green('r') + ' will unpause the stream.\n'
     usage += s * 2 + light_green('c') + ' will clear the screen.\n'
+    usage += s * 2 + light_green('v') + ' will show version info.\n'
     usage += s * 2 + light_green('q') + ' will quit.\n'
     # End
     usage += '\n'
@@ -1450,7 +1754,7 @@ def pause():
     """
     Pause stream display
     """
-    c['pause'] = True
+    g['pause'] = True
     printNicely(green('Stream is paused'))
 
 
@@ -1458,7 +1762,7 @@ def replay():
     """
     Replay stream
     """
-    c['pause'] = False
+    g['pause'] = False
     printNicely(green('Stream is running back now'))
 
 
@@ -1503,6 +1807,7 @@ cmdset = [
     'switch',
     'trend',
     'home',
+    'notification',
     'view',
     'mentions',
     't',
@@ -1512,15 +1817,17 @@ cmdset = [
     'conversation',
     'fav',
     'rep',
+    'repall',
     'del',
     'ufav',
+    'share',
     's',
     'mes',
     'show',
     'open',
     'ls',
     'inbox',
-    'sent',
+    'thread',
     'trash',
     'whois',
     'fl',
@@ -1539,7 +1846,8 @@ cmdset = [
     'p',
     'r',
     'c',
-    'q'
+    'v',
+    'q',
 ]
 
 # Handle function set
@@ -1547,6 +1855,7 @@ funcset = [
     switch,
     trend,
     home,
+    notification,
     view,
     mentions,
     tweet,
@@ -1556,15 +1865,17 @@ funcset = [
     conversation,
     favorite,
     reply,
+    reply_all,
     delete,
     unfavorite,
+    share,
     search,
     message,
     show,
     urlopen,
     ls,
     inbox,
-    sent,
+    thread,
     trash,
     whois,
     follow,
@@ -1583,7 +1894,8 @@ funcset = [
     pause,
     replay,
     clear,
-    quit
+    upgrade_center,
+    quit,
 ]
 
 
@@ -1601,9 +1913,10 @@ def listen():
     d = dict(zip(
         cmdset,
         [
-            ['public', 'mine'],  # switch
+            ['public', 'mine', 'list'],  # switch
             [],  # trend
             [],  # home
+            [],  # notification
             ['@'],  # view
             [],  # mentions
             [],  # tweet
@@ -1613,15 +1926,17 @@ def listen():
             [],  # conversation
             [],  # favorite
             [],  # reply
+            [],  # reply_all
             [],  # delete
             [],  # unfavorite
+            [],  # url
             ['#'],  # search
             ['@'],  # message
             ['image'],  # show image
             [''],  # open url
             ['fl', 'fr'],  # list
             [],  # inbox
-            [],  # sent
+            [i for i in g['message_threads']],  # sent
             [],  # trash
             ['@'],  # whois
             ['@'],  # follow
@@ -1659,6 +1974,7 @@ def listen():
             [],  # pause
             [],  # reconnect
             [],  # clear
+            [],  # version
             [],  # quit
         ]
     ))
@@ -1666,19 +1982,20 @@ def listen():
     read_history()
     reset()
     while True:
-        # raw_input
-        if g['prefix']:
-            line = raw_input(g['decorated_name'](c['PREFIX']))
-        else:
-            line = raw_input()
-        # Save cmd to compare with readline buffer
-        g['cmd'] = line.strip()
-        # Get short cmd to pass to handle function
-        try:
-            cmd = line.split()[0]
-        except:
-            cmd = ''
         try:
+            # raw_input
+            if g['prefix']:
+                # Only use PREFIX as a string with raw_input
+                line = raw_input(g['decorated_name'](g['PREFIX']))
+            else:
+                line = raw_input()
+            # Save cmd to compare with readline buffer
+            g['cmd'] = line.strip()
+            # Get short cmd to pass to handle function
+            try:
+                cmd = line.split()[0]
+            except:
+                cmd = ''
             # Lock the semaphore
             c['lock'] = True
             # Save cmd to global variable and call process
@@ -1692,10 +2009,28 @@ def listen():
                 g['prefix'] = True
             # Release the semaphore lock
             c['lock'] = False
+        except EOFError:
+            printNicely('')
         except Exception:
+            debug_option()
             printNicely(red('OMG something is wrong with Twitter right now.'))
 
 
+def reconn_notice():
+    """
+    Notice when Hangup or Timeout
+    """
+    guide = light_magenta('You can use ') + \
+        light_green('switch') + \
+        light_magenta(' command to return to your stream.\n')
+    guide += light_magenta('Type ') + \
+        light_green('h stream') + \
+        light_magenta(' for more details.')
+    printNicely(guide)
+    sys.stdout.write(g['decorated_name'](g['PREFIX']))
+    sys.stdout.flush()
+
+
 def stream(domain, args, name='Rainbow Stream'):
     """
     Track the stream
@@ -1735,34 +2070,41 @@ def stream(domain, args, name='Rainbow Stream'):
         # Block new stream until other one exits
         StreamLock.acquire()
         g['stream_stop'] = False
+        last_tweet_time = time.time()
         for tweet in tweet_iter:
             if tweet is None:
-                printNicely("-- None --")
+                printNicely('-- None --')
             elif tweet is Timeout:
+                # Because the stream check for each 0.3s
+                # so we shouldn't output anything here
                 if(g['stream_stop']):
                     StreamLock.release()
                     break
             elif tweet is HeartbeatTimeout:
-                printNicely("-- Heartbeat Timeout --")
-                guide = light_magenta("You can use ") + \
-                    light_green("switch") + \
-                    light_magenta(" command to return to your stream.\n")
-                guide += light_magenta("Type ") + \
-                    light_green("h stream") + \
-                    light_magenta(" for more details.")
-                printNicely(guide)
-                sys.stdout.write(g['decorated_name'](c['PREFIX']))
-                sys.stdout.flush()
+                printNicely('-- Heartbeat Timeout --')
+                reconn_notice()
                 StreamLock.release()
                 break
             elif tweet is Hangup:
-                printNicely("-- Hangup --")
+                printNicely('-- Hangup --')
+                reconn_notice()
+                StreamLock.release()
+                break
             elif tweet.get('text'):
+                # Slow down the stream by STREAM_DELAY config key
+                if time.time() - last_tweet_time < c['STREAM_DELAY']:
+                    continue
+                last_tweet_time = time.time()
+                # Check the semaphore pause and lock (stream process only)
+                if g['pause']:
+                    continue
+                while c['lock']:
+                    time.sleep(0.5)
+                # Draw the tweet
                 draw(
                     t=tweet,
                     keyword=args.track_keywords,
                     humanize=False,
-                    check_semaphore=True,
                     fil=args.filter,
                     ig=args.ignore,
                 )
@@ -1774,17 +2116,33 @@ def stream(domain, args, name='Rainbow Stream'):
                 # the 1st character of that word
                 if current_buffer and g['cmd'] != current_buffer:
                     sys.stdout.write(
-                        g['decorated_name'](c['PREFIX']) + str2u(current_buffer))
+                        g['decorated_name'](g['PREFIX']) + current_buffer)
                     sys.stdout.flush()
                 elif not c['HIDE_PROMPT']:
-                    sys.stdout.write(g['decorated_name'](c['PREFIX']))
+                    sys.stdout.write(g['decorated_name'](g['PREFIX']))
                     sys.stdout.flush()
             elif tweet.get('direct_message'):
-                print_message(tweet['direct_message'], check_semaphore=True)
-    except TwitterHTTPError:
+                # Check the semaphore pause and lock (stream process only)
+                if g['pause']:
+                    continue
+                while c['lock']:
+                    time.sleep(0.5)
+                print_message(tweet['direct_message'])
+            elif tweet.get('event'):
+                c['events'].append(tweet)
+                print_event(tweet)
+    except TwitterHTTPError as e:
         printNicely('')
         printNicely(
-            magenta("We have maximum connection problem with twitter'stream API right now :("))
+            magenta('We have connection problem with twitter stream API right now :('))
+        detail_twitter_error(e)
+        sys.stdout.write(g['decorated_name'](g['PREFIX']))
+        sys.stdout.flush()
+    except (URLError, ConnectionResetError):
+        printNicely(
+            magenta('There seems to be a connection problem.'))
+        save_history()
+        sys.exit()
 
 
 def fly():
@@ -1794,23 +2152,132 @@ def fly():
     # Initial
     args = parse_arguments()
     try:
+        proxy_connect(args)
         init(args)
-    except TwitterHTTPError:
+    # Twitter API connection problem
+    except TwitterHTTPError as e:
         printNicely('')
         printNicely(
-            magenta("We have connection problem with twitter'stream API right now :("))
-        printNicely(magenta("Let's try again later."))
+            magenta('We have connection problem with twitter REST API right now :('))
+        detail_twitter_error(e)
+        save_history()
+        sys.exit()
+    # Proxy connection problem
+    except (socks.ProxyConnectionError, URLError):
+        printNicely(
+            magenta('There seems to be a connection problem.'))
+        printNicely(
+            magenta('You might want to check your proxy settings (host, port and type)!'))
         save_history()
         sys.exit()
+
     # Spawn stream thread
-    th = threading.Thread(
-        target=stream,
-        args=(
-            c['USER_DOMAIN'],
-            args,
-            g['original_name']))
-    th.daemon = True
-    th.start()
+    target = args.stream.split()[0]
+    if target == 'public':
+        try:
+            keyword = args.stream.split()[1]
+            if keyword[0] == '#':
+                keyword = keyword[1:]
+            args.track_keywords = keyword
+            # Set the variable to tracked keyword
+            g['keyword'] = keyword
+            # Reset prefix
+            g['PREFIX'] = u2str(emojize(format_prefix(keyword=g['keyword'])))
+            # Start new thread
+            th = threading.Thread(
+                target=stream,
+                args=(
+                    c['PUBLIC_DOMAIN'],
+                    args))
+            th.daemon = True
+            th.start()
+        except:
+             printNicely(red('Public requires a keyword! Loading your personal stream.'))
+             # Start new thread
+             th = threading.Thread(
+                 target=stream,
+                 args=(
+                     c['USER_DOMAIN'],
+                     args,
+                     g['original_name']))
+             th.daemon = True
+             th.start()
+    elif target == "list":
+        try:
+            owner, slug = check_slug(args.stream.split()[1])
+            # Force python 2 not redraw readline buffer
+            listname = '/'.join([owner, slug])
+            # Set the listname variable
+            # and reset tracked keyword
+            g['listname'] = listname
+            g['keyword'] = ''
+            g['PREFIX'] = g['cmd'] = u2str(emojize(format_prefix(
+                listname=g['listname']
+            )))
+            printNicely(light_yellow('getting list members ...'))
+            # Get members
+            t = Twitter(auth=authen())
+            members = []
+            next_cursor = -1
+            while next_cursor != 0:
+                m = t.lists.members(
+                    slug=slug,
+                    owner_screen_name=owner,
+                    cursor=next_cursor,
+                    include_entities=False)
+                for u in m['users']:
+                    members.append('@' + u['screen_name'])
+                next_cursor = m['next_cursor']
+            printNicely(light_yellow('... done.'))
+            # Build thread filter array
+            args.filter = members
+            # Kill old thread
+            g['stream_stop'] = True
+            # Start new thread
+            th = threading.Thread(
+                target=stream,
+                args=(
+                    c['USER_DOMAIN'],
+                    args,
+                    slug))
+            th.daemon = True
+            th.start()
+            printNicely('')
+            if args.filter:
+                printNicely(cyan('Include: ' + str(len(args.filter)) + ' people.'))
+        except:
+             printNicely(red('List requieres a correct name of a list! Loading your personal stream.'))
+             # Start new thread
+             th = threading.Thread(
+                 target=stream,
+                 args=(
+                     c['USER_DOMAIN'],
+                     args,
+                     g['original_name']))
+             th.daemon = True
+             th.start()
+    elif target == "mine": 
+        # Start new thread
+        th = threading.Thread(
+            target=stream,
+            args=(
+                c['USER_DOMAIN'],
+                args,
+                g['original_name']))
+        th.daemon = True
+        th.start()
+    else: 
+        printNicely(red('Wrong -s/--stream argument given. Loading your personal stream.'))
+        # Start new thread
+        th = threading.Thread(
+            target=stream,
+            args=(
+                c['USER_DOMAIN'],
+                args,
+                g['original_name']))
+        th.daemon = True
+        th.start()
+
     # Start listen process
     time.sleep(0.5)
     g['reset'] = True