├── .github ├── FUNDING.yml └── dependabot.yml ├── .gitignore ├── LICENSE ├── README.md ├── expressvpn ├── __init__.py ├── commands.py └── wrapper.py ├── expressvpn_2.3.4-1_amd64.deb ├── setup.py └── vpn.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [philipperemy] 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "20:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | .idea/ 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Philippe Rémy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ExpressVPN - Python Wrapper (LINUX) 2 | 3 | [![Downloads](https://static.pepy.tech/badge/expressvpn-python)](https://pepy.tech/project/expressvpn-python) 4 | [![Downloads](https://static.pepy.tech/badge/expressvpn-python/month)](https://pepy.tech/project/expressvpn-python) 5 | 6 | Full bash documentation: [https://www.expressvpn.com/support/vpn-setup/app-for-linux/](https://www.expressvpn.com/support/vpn-setup/app-for-linux/) 7 | 8 | This will not work on Windows! 9 | 10 | 11 | ## Installation with PyPI 12 | 13 | If the command `expressvpn` is already installed on your Ubuntu then just run this: 14 | 15 | ```bash 16 | pip install expressvpn-python 17 | ``` 18 | 19 | ## Download/Install the package on the official website 20 | 21 | The package DEB for Ubuntu 64bits 2.3.4 is already part of the repository. For another OS, please refer to: 22 | [https://www.expressvpn.com/support/vpn-setup/app-for-linux/#download](https://www.expressvpn.com/support/vpn-setup/app-for-linux/#download) 23 | 24 | ```bash 25 | git clone git@github.com:philipperemy/expressvpn-python.git evpn && cd evpn 26 | sudo dpkg -i expressvpn_2.3.4-1_amd64.deb # will install the binaries provided by ExpressVPN 27 | sudo pip install . # will install it as a package. Or install it within a virtualenv (better option). 28 | ``` 29 | 30 | ## Change your public IP every x seconds 31 | 32 | Check the script: [vpn.sh](vpn.sh). 33 | 34 | ## Set up expressvpn 35 | 36 | You can find your activation key here: [https://www.expressvpn.com/setup](https://www.expressvpn.com/setup). 37 | 38 | ```bash 39 | expressvpn activate # paste your activate key and press ENTER. 40 | expressvpn preferences set send_diagnostics false 41 | ``` 42 | 43 | After login and to logout, simply run: 44 | 45 | ```bash 46 | expressvpn logout 47 | ``` 48 | 49 | NOTE that you will have to activate `expressvpn` again if you logout. 50 | 51 | ## Python bindings 52 | 53 | ### Connect 54 | 55 | Bash 56 | ```bash 57 | expressvpn connect 58 | ``` 59 | 60 | Python 61 | ```python 62 | from expressvpn import connect 63 | connect() 64 | ``` 65 | 66 | ### Connect with alias 67 | 68 | Bash 69 | ```bash 70 | expressvpn connect [ALIAS] 71 | ``` 72 | 73 | Python 74 | ```python 75 | from expressvpn import connect_alias 76 | connect_alias(alias: str) 77 | ``` 78 | 79 | ### Random connect(From fastest servers) 80 | Python 81 | ```python 82 | from expressvpn.wrapper import random_connect 83 | random_connect() 84 | ``` 85 | 86 | ### Random connect(From all servers) 87 | Python 88 | ```python 89 | from expressvpn.wrapper import random_connect 90 | random_connect(True) 91 | ``` 92 | 93 | ### 94 | 95 | ### Disconnect 96 | 97 | Bash 98 | ```bash 99 | expressvpn disconnect 100 | ``` 101 | 102 | Python 103 | ```python 104 | from expressvpn import disconnect 105 | disconnect() 106 | ``` 107 | 108 | ## IP auto switching 109 | 110 | Sometimes websites like Amazon or Google will ban you after too many requests. It's easy to detect because your script will fail for some obscure reason. Most of the time, if the HTML contains the word captcha or if the websites returns 403, it means that you probably got banned. But don't panic, you can use a VPN coupled with IP auto switching. Here's an example of a scraper doing IP auto switching: 111 | 112 | ```python 113 | import logging 114 | 115 | from expressvpn import wrapper 116 | 117 | 118 | class BannedException(Exception): 119 | pass 120 | 121 | 122 | def main(): 123 | while True: 124 | try: 125 | scrape() 126 | except BannedException as be: 127 | logging.info('BANNED EXCEPTION in __MAIN__') 128 | logging.info(be) 129 | logging.info('Lets change our PUBLIC IP GUYS!') 130 | change_ip() 131 | except Exception as e: 132 | logging.error('Exception raised.') 133 | logging.error(e) 134 | 135 | 136 | def change_ip(): 137 | max_attempts = 10 138 | attempts = 0 139 | while True: 140 | attempts += 1 141 | try: 142 | logging.info('GETTING NEW IP') 143 | wrapper.random_connect() 144 | logging.info('SUCCESS') 145 | return 146 | except Exception as e: 147 | if attempts > max_attempts: 148 | logging.error('Max attempts reached for VPN. Check its configuration.') 149 | logging.error('Browse https://github.com/philipperemy/expressvpn-python.') 150 | logging.error('Program will exit.') 151 | exit(1) 152 | logging.error(e) 153 | logging.error('Skipping exception.') 154 | ``` 155 | -------------------------------------------------------------------------------- /expressvpn/__init__.py: -------------------------------------------------------------------------------- 1 | from expressvpn.wrapper import activation_check 2 | from expressvpn.wrapper import connect 3 | from expressvpn.wrapper import connect_alias 4 | from expressvpn.wrapper import disconnect 5 | from expressvpn.wrapper import random_connect 6 | -------------------------------------------------------------------------------- /expressvpn/commands.py: -------------------------------------------------------------------------------- 1 | # expressvpn connect [ALIAS] 2 | 3 | VPN_CONNECT = 'expressvpn connect' 4 | 5 | VPN_LIST = 'expressvpn list' 6 | VPN_LIST_ALL = 'expressvpn list all' 7 | 8 | VPN_DISCONNECT = 'expressvpn disconnect' 9 | -------------------------------------------------------------------------------- /expressvpn/wrapper.py: -------------------------------------------------------------------------------- 1 | import random 2 | import subprocess 3 | 4 | from expressvpn.commands import * 5 | 6 | 7 | class ConnectException(Exception): 8 | pass 9 | 10 | 11 | def run_command(command): 12 | p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) 13 | return list([str(v).replace('\\t', ' ').replace('\\n', ' ').replace('b\'', '').replace('\'', '') 14 | .replace('b"', '') 15 | for v in iter(p.stdout.readline, b'')]) 16 | 17 | 18 | def activation_check(): 19 | print('Checking if the client is activated... (Please wait)') 20 | out = connect() 21 | if not is_activated(out): 22 | print('Please run and provide your activation key. Program will exit.') 23 | exit(1) 24 | print('Client is successfully logged in.') 25 | disconnect() 26 | 27 | 28 | def connect(): 29 | return run_command(VPN_CONNECT) 30 | 31 | 32 | def disconnect(): 33 | return run_command(VPN_DISCONNECT) 34 | 35 | 36 | def is_activated(connect_output): 37 | return not check_if_string_is_in_output(connect_output, 'Please activate your account') 38 | 39 | 40 | def check_if_string_is_in_output(out, string): 41 | for item in out: 42 | if string in item: 43 | return True 44 | return False 45 | 46 | 47 | def print_output(out): 48 | for o in out: 49 | print('- {}'.format(o)) 50 | 51 | 52 | def connect_alias(alias): 53 | command = VPN_CONNECT + ' ' + str(alias) 54 | out = run_command(command) 55 | if check_if_string_is_in_output(out, 'We were unable to connect to this VPN location'): 56 | raise ConnectException() 57 | if check_if_string_is_in_output(out, 'not found'): 58 | raise ConnectException() 59 | print('Successfully connected to {}'.format(alias)) 60 | 61 | 62 | def extract_aliases(vpn_list): 63 | try: 64 | return extract_aliases_1(vpn_list) 65 | except: 66 | return extract_aliases_2(vpn_list) 67 | 68 | 69 | def extract_aliases_1(vpn_list): 70 | """ 71 | - ALIAS COUNTRY LOCATION RECOMMENDED 72 | - ----- --------------- ------------------------------ ----------- 73 | """ 74 | aliases = [] 75 | for vpn_item in vpn_list[2:]: 76 | alias = vpn_item.split()[0] 77 | aliases.append(alias) 78 | return aliases 79 | 80 | 81 | def extract_aliases_2(vpn_list): 82 | """ 83 | Recommended locations: 84 | - ALIAS COUNTRY LOCATION RECOMMENDED 85 | - ----- --------------- ------------------------------ ----------- 86 | """ 87 | aliases = [] 88 | for vpn_item in vpn_list[3:]: 89 | try: 90 | alias = vpn_item.split()[0] 91 | aliases.append(alias) 92 | except IndexError: 93 | return aliases 94 | return aliases 95 | 96 | 97 | def random_connect(useAllLocations = False): 98 | # activation_check() 99 | disconnect() 100 | vpn_list = run_command(VPN_LIST_ALL) if useAllLocations else run_command(VPN_LIST)[0:46] 101 | print_output(vpn_list) 102 | aliases = extract_aliases(vpn_list) 103 | random.shuffle(aliases) 104 | selected_alias = aliases[0] 105 | print('Selected alias : {}'.format(selected_alias)) 106 | connect_alias(selected_alias) # might raise a ConnectException. 107 | -------------------------------------------------------------------------------- /expressvpn_2.3.4-1_amd64.deb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipperemy/expressvpn-python/67a081ad507e3504325afbcfe038f4ce227a4610/expressvpn_2.3.4-1_amd64.deb -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='expressvpn-python', 5 | version='1.2', 6 | packages=['expressvpn'], 7 | url='https://github.com/philipperemy/expressvpn-python', 8 | license='MIT', 9 | author='Philippe Remy', 10 | author_email='premy.enseirb@gmail.com', 11 | description='ExpressVPN - Python Wrapper' 12 | ) 13 | -------------------------------------------------------------------------------- /vpn.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CHANGE_EVERY=60 # seconds. 1800 = 30 minutes 4 | 5 | echo_public_ip() { 6 | MY_INTERNET_IP=$(curl -s http://whatismyip.akamai.com/) 7 | echo "[$(date +'%H:%M:%S')] The public IP is ${MY_INTERNET_IP}." 8 | } 9 | 10 | 11 | # __MAIN__ 12 | 13 | echo "[$(date +'%H:%M:%S')] Welcome to the VPN script to auto-switch IP every ${CHANGE_EVERY} seconds." 14 | expressvpn disconnect >/dev/null 2>&1 15 | echo "[$(date +'%H:%M:%S')] Connection to VPN reset. The public IP without VPN is:" 16 | echo_public_ip 17 | while true 18 | do 19 | # Select a random VPN location from the 20 fastest ones. 20 | VPN_LOCATION=$(expressvpn list all | tail -n +4 | head -n 20 | cut -d ' ' -f 1 | shuf | head -n 1) 21 | echo "[$(date +'%H:%M:%S')] New VPN location selected: ${VPN_LOCATION}." 22 | echo "[$(date +'%H:%M:%S')] Connecting to the location. Please wait up to 15 seconds..." 23 | expressvpn connect ${VPN_LOCATION} >/dev/null 2>&1 24 | sleep 2 # just to be safe if expressvpn has some latency. 25 | echo "[$(date +'%H:%M:%S')] Connected to ${VPN_LOCATION}" 26 | echo_public_ip 27 | echo "[$(date +'%H:%M:%S')] Waiting for ${CHANGE_EVERY} seconds before switching location." 28 | sleep ${CHANGE_EVERY} 29 | expressvpn disconnect >/dev/null 2>&1 30 | sleep 2 # just to be safe if expressvpn has some latency. 31 | echo "[$(date +'%H:%M:%S')] Disconnected." 32 | # expressvpn status 33 | done 34 | 35 | # __MAIN__ END 36 | --------------------------------------------------------------------------------