Skip to content

Instantly share code, notes, and snippets.

@rho333
Created August 2, 2014 05:39
Show Gist options
  • Save rho333/705cf46da7a9e1587104 to your computer and use it in GitHub Desktop.
Save rho333/705cf46da7a9e1587104 to your computer and use it in GitHub Desktop.
Simple Vodafone NZ usage scraper/tracker
# Simple Python script to fetch usage data for Vodafone NZ customers and push
# some summary stats to their mobile phones/tablets via Boxcar.
#
# Disclaimer: I in no way guarantee the security, continued functionality or support of this software.
# I am in no way affiliated with Vodafone NZ. This software is licensed under the MIT License.
#
# -----------------------
#
# The MIT License (MIT)
#
# Copyright (c) 2014 Richard Hofman
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import sys
from calendar import monthrange
from datetime import datetime
import urllib, urllib2, cookielib
import lxml.html as etree
def main(args):
try:
u = args[1]
p = args[2]
except IndexError:
usage("Insufficient arguments provided.")
try:
mode = args[3]
except IndexError:
debug("No mode specified, assuming push alert mode.")
mode = "pushalert"
if mode == "pushalert": # Send summaries via Boxcar
get_authenticated_connection(u, p)
data = get_usage_data()
send_results(get_daily_usage(data))
elif mode == "detailed": # Fetch detailed data on the current billing period and return it in JSON format
get_authenticated_connection(u, p)
data = get_usage_data()
print generate_report_data(data)
else:
fail("Unrecognised mode parameter specified.")
def fail(msg):
print "ERROR: " + msg
sys.exit(1)
def usage(msg=None):
import inspect
if msg:
print msg + "\n-------"
print "VFNZ Internet Usage Tracker\n"
print "Usage:"
print "python %s <username> <password> [(pushalert)|detailed]\n" % inspect.getfile(inspect.currentframe())
sys.exit(1)
def debug(msg):
#print "DEBUG: " + msg # uncomment this line to print boring stuff to stdout
pass
#
# Handles initial log in to the My Vodafone portal
#
def get_authenticated_connection(username, password):
strings = {'myvf_url':'https://www.vodafone.co.nz/myvodafone',
'loginform_id':'fabLoginFormMain',
'un_input_name':'login',
'pw_input_name':'password'
}
# Set up urllib
vf_opener = urllib2.build_opener(urllib2.HTTPHandler(), urllib2.HTTPRedirectHandler(), urllib2.HTTPCookieProcessor(cookielib.CookieJar()))
urllib2.install_opener(vf_opener)
# Connect and get page
lp_conn = urllib2.urlopen(strings['myvf_url'])
lp = lp_conn.read()
lp_tree = etree.fromstring(lp)
# Find login form
lp_forms = lp_tree.forms
loginform = None
for f in lp_forms:
#print "ID: %s" % f.get("id")
if f.get("id") == strings['loginform_id']:
loginform = f
if loginform is not None:
debug("Logging in using login form.")
# Parse form and fill in appropriate fields
login_url = loginform.action
login_values = dict()
for input in loginform.inputs:
if input.name is not None:
if input.name.lower() == strings["un_input_name"] or input.name.lower() == strings["pw_input_name"]:
pass
else:
login_values[input.name] = input.value
#print input.name + " " + ("(static)" if input.type.lower() == "hidden" else "(userdefined)")
else:
debug("Logging in using hard-coded fallbacks.")
# Set literal values - riskier, but worth a try if we can't find the login form
login_url = "https://the.vodafone.co.nz/acnts/myaccounts.pl/login"
login_values = {
"cmd": "login",
"force_connection": "true",
"frames": "true",
"selected_tpl": "true",
"no_tcode": "true",
"login-type": "internet",
"login-password-cover": "Password"
}
login_values[strings["un_input_name"]] = username
login_values[strings["pw_input_name"]] = password
# Submit login information
login_data = urllib.urlencode(login_values)
myvf_conn = urllib2.urlopen(url=login_url, data=login_data)
myvf_page = myvf_conn.read()
#
# Fetches and parses data usage page, and returns a dictionary of dates => usage amounts (in MB).
#
def get_usage_data():
usage_strings = {'usage_url':'https://the.vodafone.co.nz/acnts/myaccount-int.pl/usage',
'usage_form_action':'usage-data',
'usage_detail_url':'https://the.vodafone.co.nz/acnts/myaccount-int.pl/usage-data'
}
# Load usage page and parse into an lxml element tree
up = urllib2.urlopen(usage_strings['usage_url']).read()
up_tree = etree.fromstring(up)
# Find usage form (billing period selection)
up_forms = up_tree.forms
usage_form = None
for f in up_forms:
if f.action == usage_strings["usage_form_action"]:
usage_form = f
if usage_form is None:
fail("Could not find usage form - perhaps the page formatting has changed?")
# Get date ranges (billing periods) and login name from usage form dropdown
date_ranges = None
for i in usage_form.inputs:
if i.name == "range":
date_ranges = i
if i.name == "login":
login_name = i.value
if date_ranges is None:
fail("Could not find date ranges in usage form - perhaps the page formatting has changed?")
# Get date range options and pick current billing period
range_options = date_ranges.value_options
selected_range = range_options[0] # 0th entry is most recent
# Request detailed usage page for current billing period
usage_post_dict = {'login':login_name, 'range':selected_range}
data = urllib.urlencode(usage_post_dict)
dup = urllib2.urlopen(url=usage_strings['usage_detail_url'], data=data).read()
dup_tree = etree.fromstring(dup)
# Parse dates and usage values (in MB) from detailed usage page
usage_table_dates_el = dup_tree.xpath("//table[2]/tr/td[1]")
usage_table_dates = []
usage_table_values_el = dup_tree.xpath("//table[2]/tr/td[2]")
usage_table_values = []
for i in range(1, len(usage_table_dates_el)-1):
usage_table_dates.append(usage_table_dates_el[i].text_content().strip().lower())
usage_table_values.append(usage_table_values_el[i].text_content().strip())
# Relabel yesterday's entry as "yesterday"
if "today" not in usage_table_dates:
usage_table_dates[-2] = "yesterday"
else:
usage_table_dates[-3] = "yesterday"
# Map dates to their corresponding usage values
data = dict(zip(usage_table_dates, usage_table_values))
# Handles corner case where "Today" entry does not appear just after midnight...
if "today" not in data:
data["today"] = "0.000"
return data
#
# Gets current day's usage from raw data, returns dictionary of data to be pushed.
#
def get_daily_usage(data):
# BEGIN CONFIGURATION OPTIONS
monthly_max = 150 * 1024
send_summary_information = True
# END CONFIGURATION OPTIONS
usage_today = float(data["today"])
usage_yesterday = float(data["yesterday"])
cumulative_usage = float(data["total"])
daily_max = (monthly_max-cumulative_usage)/((monthrange(datetime.now().year, datetime.now().month)[1])+1 - datetime.now().day)
is_over_daily_limit = usage_today>daily_max
results = {'usage_today': usage_today,
'usage_yesterday': usage_yesterday,
'cumulative_usage': cumulative_usage,
'monthly_max': monthly_max,
'daily_max': daily_max,
'is_over_daily_limit': is_over_daily_limit,
'send_summary_information':send_summary_information
}
return results
#
# Takes the push_data dictionary in and generates the push notification to be sent.
# Also contains list of hard-coded Boxcar account tokens to receive the message.
#
def send_results(push_data):
# BEGIN CONFIGURATION OPTIONS
# Put Boxcar Access Tokens here (comma-separated).
# For just one token, simply don't include a comma: ['mytoken']
recipients = ['token1', 'token2']
# END CONFIGURATION OPTIONS
if push_data["is_over_daily_limit"] or push_data["send_summary_information"]:
gb_tot_usage = push_data['cumulative_usage']/1024.0
gb_today_usage = push_data['usage_today']/1024.0
gb_yesterday_usage = push_data['usage_yesterday']/1024.0
daily_max = push_data['daily_max']/1024.0
monthly_max = push_data['monthly_max']/1024.0
cumulative = "%.2f / %.2f GB (%.2f %%)" % (gb_tot_usage, monthly_max, 100*gb_tot_usage/monthly_max)
today = "%.2f / %.2f GB" % (gb_today_usage, daily_max)
yesterday = "%.2f / %.2f GB" % (gb_yesterday_usage, daily_max)
footer = "Please try to restrict your data usage where possible to avoid exceeding the cap."
content = '''<p>Usage for the month so far: %s.</p> \n
<p>Used today: %s (daily max)</p>
<p>Used yesterday: %s (daily max)</p>
<p>%s</p>
'''
body = content % (cumulative, today, yesterday, footer)
subject = "VF - INTERNET USAGE SUMMARY" if not push_data["is_over_daily_limit"] else "VF - DAILY LIMIT EXCEEDED"
for r in recipients:
send_boxcar_message(r, subject, body)
else:
debug("No warnings for today!")
#
# Sends notification to the given Boxcar account token
#
def send_boxcar_message(acctok, subject, body):
boxcar_data = { 'user_credentials': acctok,
'notification[title]': subject,
'notification[long_message]': body
}
urllib2.urlopen(url="https://new.boxcar.io/api/notifications", data=urllib.urlencode(boxcar_data))
#
# Generates JSON-formatted data set for Chart.js (to be used for web UI)
#
def generate_report_data(data):
return "{}"
if __name__ == "__main__":
main(sys.argv)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment