Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions venmo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@

from . import ( # noqa: F401
auth,
balance,
cli,
cookies,
payment,
settings,
singletons,
transfer,
types,
user
)
50 changes: 33 additions & 17 deletions venmo/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,17 @@ 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
credentials = update_credentials()
if not credentials:
return False
else:
email, password = credentials
email, password, bank_account_id = credentials

# Log in to Auto
success = submit_credentials(email, password)
Expand All @@ -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
Expand Down Expand Up @@ -139,19 +143,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',
Expand Down Expand Up @@ -188,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.
Expand All @@ -203,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):
Expand Down Expand Up @@ -280,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)
Expand Down
23 changes: 23 additions & 0 deletions venmo/balance.py
Original file line number Diff line number Diff line change
@@ -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']))
10 changes: 10 additions & 0 deletions venmo/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -80,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__)

Expand Down
3 changes: 2 additions & 1 deletion venmo/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

# OAuth
CLIENT_ID = '2667'
CLIENT_SECRET = 'srDrmU3yf452HuFF63HqHEt25pa5DexZ'

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌


# Paths
DOT_VENMO = os.path.join(os.path.expanduser('~'), '.venmo')
Expand All @@ -17,3 +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'
27 changes: 27 additions & 0 deletions venmo/transfer.py
Original file line number Diff line number Diff line change
@@ -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'])))