Last active
July 1, 2017 02:40
-
-
Save lukassup/cd8dcea35147d0fd4fbe949484266dfc to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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