Print twitter errors
[rainbowstream.git] / rainbowstream / rainbow.py
index b4cd5f9b7fca71534c679eaa6480680b15d47e65..7480e2a566402399f9c5236187c131cb6594da13 100644 (file)
@@ -9,7 +9,10 @@ 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
@@ -24,6 +27,7 @@ from .interactive import *
 from .c_image import *
 from .py3patch import *
 from .emoji import *
+from .util import printTwitterErrors
 
 # Global values
 g = {}
@@ -58,9 +62,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
@@ -126,7 +169,7 @@ def upgrade_center():
     try:
         current = pkg_resources.get_distribution("rainbowstream").version
         url = 'https://raw.githubusercontent.com/DTVD/rainbowstream/master/setup.py'
-        readme = requests.get(url).content
+        readme = requests.get(url).text
         latest = readme.split("version = \'")[1].split("\'")[0]
         if current != latest:
             notice = light_magenta('RainbowStream latest version is ')
@@ -183,6 +226,13 @@ def init(args):
     # 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()
 
@@ -291,7 +341,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:
@@ -481,6 +532,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
@@ -880,7 +953,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('')
 
@@ -1154,9 +1227,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
@@ -1168,6 +1238,8 @@ def switch():
             # Kill old thread
             g['stream_stop'] = True
             args.track_keywords = keyword
+            # Reset prefix
+            g['PREFIX'] = u2str(emojize(c['PREFIX']))
             # Start new thread
             th = threading.Thread(
                 target=stream,
@@ -1180,6 +1252,8 @@ def switch():
         elif target == 'mine':
             # Kill old thread
             g['stream_stop'] = True
+            # Reset prefix
+            g['PREFIX'] = u2str(emojize(c['PREFIX']))
             # Start new thread
             th = threading.Thread(
                 target=stream,
@@ -1193,7 +1267,7 @@ def switch():
         elif target == 'list':
             owner, slug = get_slug()
             # Force python 2 not redraw readline buffer
-            g['cmd'] = '/'.join([owner, slug])
+            g['PREFIX'] = g['cmd'] = '/'.join([owner, slug])
             printNicely(light_yellow('getting list members ...'))
             # Get members
             t = Twitter(auth=authen())
@@ -1228,7 +1302,7 @@ def switch():
         if args.ignore:
             printNicely(red('Ignore: ' + str(len(args.ignore)) + ' people.'))
         printNicely('')
-    except Exception:
+    except:
         debug_option()
         printNicely(red('Sorry I can\'t understand.'))
 
@@ -1303,16 +1377,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]
@@ -1322,15 +1398,18 @@ 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(c['PREFIX']))
             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.'))
 
@@ -1393,6 +1472,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'
@@ -1510,8 +1592,6 @@ 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)
@@ -1673,6 +1753,7 @@ cmdset = [
     'rep',
     'del',
     'ufav',
+    'share',
     's',
     'mes',
     'show',
@@ -1718,6 +1799,7 @@ funcset = [
     reply,
     delete,
     unfavorite,
+    share,
     search,
     message,
     show,
@@ -1776,6 +1858,7 @@ def listen():
             [],  # reply
             [],  # delete
             [],  # unfavorite
+            [],  # url
             ['#'],  # search
             ['@'],  # message
             ['image'],  # show image
@@ -1861,6 +1944,21 @@ def listen():
             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'](c['PREFIX']))
+    sys.stdout.flush()
+
+
 def stream(domain, args, name='Rainbow Stream'):
     """
     Track the stream
@@ -1900,29 +1998,31 @@ 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 --")
             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()
+                reconn_notice()
                 StreamLock.release()
                 break
             elif tweet is 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
@@ -1959,10 +2059,11 @@ def stream(domain, args, name='Rainbow Stream'):
             elif tweet.get('event'):
                 c['events'].append(tweet)
                 print_event(tweet)
-    except TwitterHTTPError:
+    except TwitterHTTPError as e:
         printNicely('')
         printNicely(
             magenta("We have maximum connection problem with twitter'stream API right now :("))
+        printTwitterErrors(e)
 
 
 def fly():
@@ -1972,14 +2073,26 @@ 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 :("))
+        printTwitterErrors(e)
         printNicely(magenta("Let's try again later."))
         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,