From 51b8dd6a07b4dd2881d4464f1ce7c5dc99520ac1 Mon Sep 17 00:00:00 2001 From: urq Date: Tue, 3 Jul 2018 18:18:35 -0400 Subject: [PATCH 1/3] Remove dead code This stuff isn't being used anywhere in the codebase, so get rid of it. --- venmo/auth.py | 13 ------------- venmo/settings.py | 1 - 2 files changed, 14 deletions(-) diff --git a/venmo/auth.py b/venmo/auth.py index 057cc09..25a8e97 100644 --- a/venmo/auth.py +++ b/venmo/auth.py @@ -139,19 +139,6 @@ def extract_otp_secret(text): raise Exception('msg="Could not extract data-otp-secret"') -def retrieve_access_token(code): - data = { - 'client_id': venmo.settings.CLIENT_ID, - 'client_secret': venmo.settings.CLIENT_SECRET, - 'code': code, - } - response = venmo.singletons.session().post(venmo.settings.ACCESS_TOKEN_URL, - data) - response_dict = response.json() - access_token = response_dict['access_token'] - return access_token - - def _authorization_url(): scopes = [ 'make_payments', diff --git a/venmo/settings.py b/venmo/settings.py index 1ad3c59..42b69eb 100644 --- a/venmo/settings.py +++ b/venmo/settings.py @@ -3,7 +3,6 @@ # OAuth CLIENT_ID = '2667' -CLIENT_SECRET = 'srDrmU3yf452HuFF63HqHEt25pa5DexZ' # Paths DOT_VENMO = os.path.join(os.path.expanduser('~'), '.venmo') From d77d81f3011587a8e06cd8c858839106214b8da6 Mon Sep 17 00:00:00 2001 From: urq Date: Tue, 3 Jul 2018 18:18:48 -0400 Subject: [PATCH 2/3] Add balance command --- venmo/__init__.py | 1 + venmo/balance.py | 23 +++++++++++++++++++++++ venmo/cli.py | 3 +++ venmo/settings.py | 1 + 4 files changed, 28 insertions(+) create mode 100644 venmo/balance.py diff --git a/venmo/__init__.py b/venmo/__init__.py index 58ff6a8..d4ff137 100644 --- a/venmo/__init__.py +++ b/venmo/__init__.py @@ -10,6 +10,7 @@ from . import ( # noqa: F401 auth, + balance, cli, cookies, payment, diff --git a/venmo/balance.py b/venmo/balance.py new file mode 100644 index 0000000..a17c9d6 --- /dev/null +++ b/venmo/balance.py @@ -0,0 +1,23 @@ + +''' +Balance module. +''' +import sys + +import requests + +import venmo + + +def balance(): + access_token = venmo.auth.get_access_token() + response = requests.get( + venmo.settings.BALANCE_URL, + params={'access_token': access_token} + ) + res = response.json() + if 'error' in res: + print(res['error']['message']) + sys.exit(1) + else: + print('{balance:.2f}'.format(balance=res['balance'])) diff --git a/venmo/cli.py b/venmo/cli.py index 433e0bd..28e1d82 100644 --- a/venmo/cli.py +++ b/venmo/cli.py @@ -66,6 +66,9 @@ def parse_args(): subparser.add_argument('note', help='what the request is for') subparser.set_defaults(func=getattr(venmo.payment, action)) + parser_balance = subparsers.add_parser('balance', help='get venmo balance') + parser_balance.set_defaults(func=venmo.balance.balance) + parser_configure = subparsers.add_parser('configure', help='set up credentials') parser_configure.set_defaults(func=venmo.auth.configure) diff --git a/venmo/settings.py b/venmo/settings.py index 42b69eb..6f73a81 100644 --- a/venmo/settings.py +++ b/venmo/settings.py @@ -16,3 +16,4 @@ TWO_FACTOR_URL = 'https://venmo.com/api/v5/two_factor/token' TWO_FACTOR_AUTHORIZATION_URL = 'https://venmo.com/login' USERS_URL = 'https://api.venmo.com/v1/users' +BALANCE_URL = 'https://venmo.com/api/v5/balance' From 92bc447d098a189c7cccf6addee0049abd0f2589 Mon Sep 17 00:00:00 2001 From: urq Date: Tue, 3 Jul 2018 18:18:53 -0400 Subject: [PATCH 3/3] Add transfer command This command allows the user to transfer an amount from their venmo balance into one of their already-registered bank accounts. To support this, a new configuration parameter for the bank account ID has been added to the `configure` command. --- README.rst | 16 ++++++++++++++++ venmo/__init__.py | 1 + venmo/auth.py | 37 +++++++++++++++++++++++++++++++++---- venmo/cli.py | 7 +++++++ venmo/settings.py | 1 + venmo/transfer.py | 27 +++++++++++++++++++++++++++ 6 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 venmo/transfer.py diff --git a/README.rst b/README.rst index 47763a8..246efde 100644 --- a/README.rst +++ b/README.rst @@ -33,10 +33,26 @@ Set up venmo by running: > Venmo email [None]: zackhsi@gmail.com > Venmo password [None]: + > Bank Account ID (Optional, see https://github.com/zackhsi/venmo/blob/master/README.rst for instructions) [None]: > Verification code: 908126 # for 2 factor authentication That's it! +Finding your Bank Account ID +---------------------------- +You can set the optional configuration parameter `Bank Account ID` to enable +transfering money from your venmo balance to your bank. The `Bank Account ID` +is a UUID that is Venmo's unique reference to your account. To find it, do the +following: + +1. Navigate to venmo.com in your browser and sign in. +2. Click on the "Transfer to Bank" link to bring up the Transfer modal. +3. You will see your bank accounts listed in the modal. Right-click on the + bank of interest and inspect the HTML element. +4. Traverse the parents of the selected DOM node (navigate up the DOM tree) + to the first tag. It should have a "ba-id" property whose value is a UUID. + This is your bank ID. + Contributing ------------ Pull requests welcome! To get started, first clone the repository: diff --git a/venmo/__init__.py b/venmo/__init__.py index d4ff137..b474805 100644 --- a/venmo/__init__.py +++ b/venmo/__init__.py @@ -16,6 +16,7 @@ payment, settings, singletons, + transfer, types, user ) diff --git a/venmo/auth.py b/venmo/auth.py index 25a8e97..93e4079 100644 --- a/venmo/auth.py +++ b/venmo/auth.py @@ -39,6 +39,9 @@ def configure(): First, set username and password. If those change, then get access token. If that fails, unset username and password. + Also optionally save a bank account ID if you want to transfer funds from + venmo to your bank account. + Return whether or not we save an access token. ''' # Update credentials @@ -46,7 +49,7 @@ def configure(): if not credentials: return False else: - email, password = credentials + email, password, bank_account_id = credentials # Log in to Auto success = submit_credentials(email, password) @@ -60,10 +63,11 @@ def configure(): logger.error('invalid credentials') return False - # Write email password + # Write email password bank_account_id config = read_config() config.set(configparser.DEFAULTSECT, 'email', email) config.set(configparser.DEFAULTSECT, 'password', password) + config.set(configparser.DEFAULTSECT, 'bank_account_id', bank_account_id) write_config(config) # Do 2FA @@ -175,7 +179,7 @@ def _filter_tag(input_xml, tag): def update_credentials(): - '''Save username and password to config file. + '''Save username, password, and bank_account_id to config file. Entering nothing keeps the current credentials. Returns whether or not the credentials changed. @@ -190,21 +194,38 @@ def update_credentials(): old_password = config.get(configparser.DEFAULTSECT, 'password') except configparser.NoOptionError: old_password = '' + try: + old_bank_account_id = config.get(configparser.DEFAULTSECT, + 'bank_account_id') + except configparser.NoOptionError: + old_bank_account_id = '' # Prompt new credentials email = input('Venmo email [{}]: ' .format(old_email if old_email else None)) password = getpass.getpass(prompt='Venmo password [{}]: ' .format('*' * 10 if old_password else None)) + bank_account_id = getpass.getpass( + prompt=('Bank Account ID (Optional, see ' + 'https://github.com/zackhsi/venmo/blob/master/README.rst for ' + 'instructions) [{}]: ').format('*' * 10 + if old_bank_account_id + else None)) + email = email or old_email password = password or old_password + bank_account_id = bank_account_id or old_bank_account_id incomplete = not email or not password if incomplete: logger.warn('credentials incomplete') return False - return email, password + if not bank_account_id: + logger.warn('bank_account_id not set. Cannot transfer venmo funds to ' + 'bank.') + + return email, password, bank_account_id def submit_credentials(email, password): @@ -267,6 +288,14 @@ def get_access_token(): return None +def get_bank_account_id(): + config = read_config() + try: + return config.get(configparser.DEFAULTSECT, 'bank_account_id') + except configparser.NoOptionError: + return None + + def read_config(): config = configparser.RawConfigParser() config.read(venmo.settings.CREDENTIALS_FILE) diff --git a/venmo/cli.py b/venmo/cli.py index 28e1d82..74f03c5 100644 --- a/venmo/cli.py +++ b/venmo/cli.py @@ -83,6 +83,13 @@ def parse_args(): parser_reset = subparsers.add_parser('reset', help='reset saved data') parser_reset.set_defaults(func=venmo.auth.reset) + parser_transfer = subparsers.add_parser('transfer', help=('transfer funds ' + 'to bank acct')) + parser_transfer.add_argument('amount', help=('the amount to transfer from' + ' your venmo balance to the ' + 'specified bank account')) + parser_transfer.set_defaults(func=venmo.transfer.transfer) + parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + venmo.__version__) diff --git a/venmo/settings.py b/venmo/settings.py index 6f73a81..f505400 100644 --- a/venmo/settings.py +++ b/venmo/settings.py @@ -16,4 +16,5 @@ TWO_FACTOR_URL = 'https://venmo.com/api/v5/two_factor/token' TWO_FACTOR_AUTHORIZATION_URL = 'https://venmo.com/login' USERS_URL = 'https://api.venmo.com/v1/users' +CASHOUTS_URL = 'https://venmo.com/api/v5/cashouts' BALANCE_URL = 'https://venmo.com/api/v5/balance' diff --git a/venmo/transfer.py b/venmo/transfer.py new file mode 100644 index 0000000..31c0bed --- /dev/null +++ b/venmo/transfer.py @@ -0,0 +1,27 @@ +''' +Transfer module. +''' +import sys + +import requests + +import venmo + + +def transfer(amount): + access_token = venmo.auth.get_access_token() + bank_account_id = venmo.auth.get_bank_account_id() + response = requests.post( + venmo.settings.CASHOUTS_URL, + params={'access_token': access_token}, + data={'amount': amount, 'bank_account_id': bank_account_id} + ).json() + if 'error' in response: + print(response['error']['message']) + sys.exit(1) + else: + print('Transferring {} to {} on {}.\nNew balance: {}'.format( + amount, + response['bank_name'], + response['next_business_day'], + str(response['balance'])))