Skip to content

Instantly share code, notes, and snippets.

@dadatuputi
Last active June 7, 2022 16:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dadatuputi/ae961b3573da664c0bfcfa8d3fe35d68 to your computer and use it in GitHub Desktop.
Save dadatuputi/ae961b3573da664c0bfcfa8d3fe35d68 to your computer and use it in GitHub Desktop.
Uses Selenium to get (or refresh) your Windscribe ephemeral port, absent a proper API to do it. It does not require a gui, or even x to be installed.
#!/usr/bin/python3
# Step 1: install firefox: $ sudo apt install firefox
# Step 2: install selenium: $ pip3 install selenium
# Step 3: install geckodriver: https://github.com/mozilla/geckodriver/releases
from selenium.webdriver.firefox.options import Options as FirefoxOptions
from selenium.common.exceptions import NoSuchElementException
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import time
import logging
import argparse
from getpass import getpass
options = FirefoxOptions()
options.add_argument("--headless")
driver = webdriver.Firefox(options=options)
url_login = "https://windscribe.com/login"
url_account = "https://windscribe.com/myaccount"
url_logout = "https://windscribe.com/myaccount/logout"
def login(username, password):
# Logs in and returns webdriver used in get_port
driver.get(url_login)
logging.debug('Navigated to {}'.format(url_login))
# Wait for CSRF
WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.XPATH, '//input[@id="csrf_time"][not(@value="")]')))
WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.XPATH, '//input[@id="csrf_token"][not(@value="")]')))
driver.find_element(By.ID, "username").send_keys(username)
driver.find_element(By.ID, "pass").send_keys(password)
driver.find_element(By.ID, "login_button").click()
logging.debug('Entered credentials and clicked login button')
# Check that login succeeded:
# If we are still at the login url, check for error message
if driver.current_url == url_login:
_error = "Login failure: "
try:
_error += driver.find_element(By.XPATH, "//div[@class='content_message error'][not(@hidden)]").text
except NoSuchElementException:
_error += "No Error, but login failed. Source: \n\n"
_error += driver.page_source
raise Exception(_error)
WebDriverWait(driver, 5).until(EC.url_matches(url_account))
logging.info('Successfully logged in as user {}'.format(username))
return driver
def get_port(driver, refresh=False):
driver.get(url_account)
logging.debug('Navigated to {}'.format(url_account))
# Navigate to port forwarding tab
_port_forwarding_button = WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.XPATH, '//a[@id="menu-ports"][@onclick][string-length(@onclick) > 0]')))
_port_forwarding_button.click()
logging.debug('Clicked Port Forwarding tab')
# Navigate to ephemeral port forwarding tab
_ephemeral_button = WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.XPATH, '//li[@id="pf-eph-btn"][@onclick][string-length(@onclick) > 0]')))
time.sleep(1) # This is a dirty hack, but there is some js going on in the background I need to wait for
_ephemeral_button.click()
logging.debug('Clicked Ephemeral tab')
# If refresh, delete existing port
if refresh:
try:
WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.XPATH, '//button[normalize-space()="Delete Port"]'))).click()
WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.XPATH, '//button[normalize-space()="Request Matching Port"]')))
logging.info("Deleted existing port")
except NoSuchElementException:
# No port to delete
pass
# Generate matching port if possible
try:
# Wait until request matching port button appears to continue:
driver.find_element(By.XPATH, '//button[normalize-space()="Request Matching Port"]').click()
logging.debug('Requesting a matching port')
except NoSuchElementException:
# We already have a port
pass
# Get the port and return
WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.XPATH, '//button[normalize-space()="Delete Port"]')))
_port = WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.XPATH, '//div[@id="epf-port-info"]/span[string-length(text()) > 0]'))).text
_port = int(_port)
assert 1024 < _port < 65535
logging.info('Received matching port of {}'.format(_port))
_time = WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.XPATH, '//span[@id="epf-countdown"][string-length(text()) > 0]'))).text
logging.info('Port will expire in {}'.format(_time))
return _port, _time
def cleanup(driver):
driver.get(url_logout)
driver.quit()
def main(username, password, refresh):
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO)
try:
driver = login(username, password)
port, time = get_port(driver, refresh)
print(port)
except Exception as e:
logging.error(e)
finally:
if driver:
cleanup(driver)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Get or refresh matching ephemeral port from Windscribe")
parser.add_argument('-u', '--user', required=True, type=str)
parser.add_argument('-p', '--password', required=False, type=str)
parser.add_argument('-r', '--refresh', required=False, action="store_true", help="Force a refresh of the port if possible")
args = parser.parse_args()
if not args.password:
args.password = getpass()
main(args.user, args.password, args.refresh)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment