Skip to content

Instantly share code, notes, and snippets.

@lukassup
Last active July 1, 2017 02:40
Show Gist options
  • Save lukassup/cd8dcea35147d0fd4fbe949484266dfc to your computer and use it in GitHub Desktop.
Save lukassup/cd8dcea35147d0fd4fbe949484266dfc to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
R"""vilniustransport.lt public transport subscription card status viewer
Script depends on "requests" and "beautifulsoup4" Python packages, use:
$ pip install requests beautifulsoup4
to install them
HTTP request sequence
[1] Obtain the login form with a new CSRF token
Script automatically detects which form fields need to be populated
GET https://mano.vilniustransport.lt/login
<form id="loginFormCheck" action="/login_check" method="post">
<input name="_csrf_token" value="{CSRF_TOKEN}">
<input name="_username">
<input name="_password">
<input name="_submit" value="Prisijungti">
<input name="_remember_me" value="on">
</form>
[2] Authenticate by POSTing the form with provided credentials
POST https://mano.vilniustransport.lt/login_check
curl 'https://mano.vilniustransport.lt/login_check' \
-F '_csrf_token={CSRF_TOKEN}' \
-F '_username={USERNAME}' \
-F '_password={PASSWORD}' \
-F '_submit=Prisijungti' \
-F '_remember_me=on'
[3] After POSTing the form and following a redirect you get your card id
numbers along with their names and codes (the ones below the barcode)
GET https://mano.vilniustransport.lt/ (follow redirect)
<select id="card_number" name="card[number]">
<option value="000001">Some card 1111111111</option>
<option value="000002">Another card 2222222222</option>
This should match the code below barcode ^^^
...
</select>
[4] Make a POST request for each card id to retrieve its subscription status
POST 'https://mano.vilniustransport.lt/card-status'
curl 'https://mano.vilniustransport.lt/card-status' \
-b 'PHPSESSID={SESSION_ID}' \
-b 'REMEMBERME={TOKEN}%3D' \
-F 'card[number]={CARD_ID}'
[5] Make a GET request to logout to cleanup session
GET 'https://mano.vilniustransport.lt/logout'
"""
import os
from getpass import getpass
import requests
from bs4 import BeautifulSoup
BASE_URL = 'https://mano.vilniustransport.lt'
LOGIN_URI = '/login'
LOGIN_FORM_ID = 'loginFormCheck'
CARD_DROPDOWN_ID = 'card_number'
CARD_STATUS_URI = '/card-status'
LOGOUT_URI = '/logout'
def get_login_form(url, form_id, session=None):
session = session if session else requests.Session()
response = session.get(url)
html = BeautifulSoup(response.content, 'html.parser')
form = html.find(id=form_id)
form_attrs = form.attrs
form_action = form_attrs['action']
form_method = form_attrs.get('method', 'post')
# Collect form input field attributes
fields = [field.attrs for field in form.find_all('input')]
form_data = {}
# Populate dictionary with form name:value pairs
for field in fields:
name = field.get('name')
if name:
form_data[name] = field.get('value')
return {
'action': form_action,
'method': form_method,
'data': form_data
}
def fill_out_login_form(form_data):
for field_name in form_data:
if not form_data[field_name]:
prompt = 'Enter %r: ' % field_name
# don't echo typed chars if field_name has key or pass in it
if 'key' in field_name or 'pass' in field_name:
form_data[field_name] = getpass(prompt)
else:
form_data[field_name] = input(prompt)
return form_data
def get_cards(url, form_data, dropdown_id=CARD_DROPDOWN_ID, session=None):
session = session if session else requests.Session()
response = session.post(url, data=form_data)
html = BeautifulSoup(response.content, 'html.parser')
# Get the <select> element
card_dropdown = html.find('select', id=dropdown_id)
cards = {}
# Find all children <option> elements and extract their values
for option in card_dropdown.find_all('option'):
card_id = option.attrs['value']
card_name = option.get_text()
cards[card_id] = card_name
return cards
def get_card_subscription_status(card_id, url, session=None):
session = session if session else requests.Session()
data = {'card[number]': card_id}
response = session.post(url, data=data)
html = BeautifulSoup(response.content, 'html.parser')
output = []
for card_row in html.find_all(class_='card-row'):
line = ' '.join(card_row.stripped_strings)
output.append(line)
return os.linesep.join(output)
def logout(url, session=None):
session = session if session else requests.Session()
session.get(url)
def main():
session = requests.Session()
form = get_login_form(BASE_URL + LOGIN_URI, LOGIN_FORM_ID, session)
fill_out_login_form(form['data'])
print()
# only POST methods are allowed
if form['method'].lower() == 'post':
action = form['action']
data = form['data']
cards = get_cards(BASE_URL + action, data, session=session)
for card_id, card_name in cards.items():
print(card_name)
print('=' * 32)
card_status = get_card_subscription_status(
card_id, BASE_URL + CARD_STATUS_URI, session=session)
print(card_status)
print()
else:
raise NotImplementedError('Only POST method is supported')
# logging out deletes your session
logout(BASE_URL + LOGOUT_URI, session=session)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment