Commit 221a1cf6 authored by Rémi FERRAND ⛰'s avatar Rémi FERRAND ⛰
Browse files

Merge branch 'release/0.1.0'

parents 5f7dcc72 047fca1f
This diff is collapsed.
# FREE ADSL Bill fetcher
#### Table of Contents
1. [Overview](#overview)
2. [Instructions](#instructions)
* [How to install](#how_to_install)
* [How to configure](#how_to_configure)
* [How to use](#how_to_use)
3. [Development - Guide for contributing](#development)
## Overview
This software was created to retrieve FREE telecom bills in an easy way.
## Instructions
### How to install
$ git clone
$ pip install -r free_adsl_bill_fetcher/requirements.txt
### How to configure
Software configuration file defaults to `~/.free_adsl_bill_fetcher.conf`.
"login": "TheLog1n",
"password": "thePassw0rd"
#### `login`
Your FREE telecom username.
#### `password`
Your FREE telecom account password.
### How to use
Usage: free_adsl_bill_fetcher [options]
-h, --help show this help message and exit
-p, --show-price show price when listing bills
--latest only fetch latest bill
-c FILE, --config=FILE
configuration file
--get=BILL_TITLE Download bill BILL_TITLE and write it as
-d DIR, --write-dir=DIR
write bills to directory DIR
--name-prefix=STR prefix each bill filename with STR
--name-suffix=STR suffix each bill filename with STR (before PDF
**Retrieve latest bill**
$ free_adsl_bill_fetcher --latest
Your latest bill was written to './Octobre_2014.pdf'
**List all available bills with their price**
$ free_adsl_bill_fetcher -p
| Month | Price |
| Octobre 2014 | 12 € |
| ... | ....... |
**Retrieve multiple specific bills**
$ free_adsl_bill_fetcher --get='Août 2014' --get='Août 2013'
[*] Août 2014 bill was written to './Août_2014.pdf'
[*] Août 2013 bill was written to './Août_2013.pdf'
**Add suffix and/or prefix to bills names**
$ free_adsl_bill_fetcher --get='Août 2014' --get='Août 2013' --name-prefix="Ferrand_Remi_" --name-suffix="_Facture_ADSL"
[*] Août 2014 bill was written to './Ferrand_Remi_Août_2014_Facture_ADSL.pdf'
[*] Août 2013 bill was written to './Ferrand_Remi_Août_2013_Facture_ADSL.pdf'
## Development - Guide for contributing
Please submit a Pull Request / Merge request if you want to contribute to this project.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) Remi Ferrand <>, 2014
# This software is a computer program whose purpose is to [describe
# functionalities and technical features of your software].
# This software is governed by the CeCILL license under French law and
# abiding by the rules of distribution of free software. You can use,
# modify and/ or redistribute the software under the terms of the CeCILL
# license as circulated by CEA, CNRS and INRIA at the following URL
# "".
# As a counterpart to the access to the source code and rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty and the software's author, the holder of the
# economic rights, and the successive licensors have only limited
# liability.
# In this respect, the user's attention is drawn to the risks associated
# with loading, using, modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean that it is complicated to manipulate, and that also
# therefore means that it is reserved for developers and experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and, more generally, to use and operate it in the
# same conditions as regards security.
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL license and that you accept its terms.
import re
import os
import sys
import json
import logging
import requests
import HTMLParser
from optparse import OptionParser
from prettytable import PrettyTable
# try:
# import http.client as http_client
# except ImportError:
# import httplib as http_client
# http_client.HTTPConnection.debuglevel = 1
# logging.basicConfig()
# logging.getLogger().setLevel(logging.DEBUG)
# requests_log = logging.getLogger("requests.packages.urllib3")
# requests_log.setLevel(logging.DEBUG)
# requests_log.propagate = True
class HTTPClient(object):
USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.104 Safari/537.36'
def __init__(this):
def _default_headers(this):
return {
'User-Agent': this.USER_AGENT
def get(this, url, headers={}, *args, **kwargs):
h = this._default_headers().copy()
return requests.get(url,
*args, **kwargs)
def post(this, url, headers={}, *args, **kwargs):
h = this._default_headers().copy()
*args, **kwargs)
def unescapeHTML(text):
return HTMLParser.HTMLParser().unescape(text)
class FreeAdslBillFetcher(HTTPClient):
BILLFINDER_RE = re.compile(r'<strong>(?P<month>\w+?\s*\d{4})</strong>.*?<strong>(?P<price>\d+(?:\.\d+)?) Euros</strong>.*?<a href="(?P<url>"', re.S | re.UNICODE)
class FreeSessionCredentials(object):
def __init__(this, id, idt): = id
this.idt = idt
def toDict(this):
return {'id':, 'idt': this.idt}
def __str__(this):
return "id=%s&idt=%s" % (, this.idt)
class FreeAdslBill(HTTPClient):
def __init__(this, title, amount, url):
this.title = title
this.amount = amount
this.url = url
def __eq__(this, other):
return this.title == other.title
def __repr__(this):
return "FreeAdslBill<%s>" % this.title.encode('utf-8')
def __init__(this, user, password):
this.user = user
this.password = password
this._transaction_creds = None
def login(this):
payload = {
'login': this.user,
'pass' : this.password,
'ok' : 'Valider',
'link' : ''
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
r =, headers=headers, data=payload,
location = r.headers['location']
def _buildSessionCreds(this, url):
ids_s = url[url.index('?')+1:]
h = dict(x.split('=') for x in ids_s.split('&'))
this._transaction_creds = FreeAdslBillFetcher.FreeSessionCredentials(id=h['id'], idt=h['idt'])
def _appendUrlCreds(this, url):
creds = this._transaction_creds
return url + '?' + str(creds)
def listBills(this):
if this._transaction_creds is None: this.login()
# We need to build the URL/params by hand.
# python-requests params doesn't work here
# it seems that url parameters are order dependant
# on this webservice
url = this._appendUrlCreds(this.LIST_BILLS_URL)
r = this.get(url, allow_redirects=False)
bills = this._parseBillsList(this.unescapeHTML(r.text))
return bills
def fetchBill(this, bill):
# assume that whole PDF fits in memory
return this.get(bill.url).content
def writeBillToFile(this, bill, file):
# assume that whole PDF fits in memory
with open(file, 'wb') as f:
def _parseBillsList(this, body):
match = this.BILLFINDER_RE.findall(body)
bills = []
for month, price, url in match:
bills.append(FreeAdslBillFetcher.FreeAdslBill(month, float(price), this.BILL_ROOT_URL + '/' + url))
return bills
def logout(this):
if this._transaction_creds is None:
url = this._appendUrlCreds(this.LOGOUT_URL)
this.get(url, allow_redirects=False)
def __enter__(this):
return this
def __exit__(this, type, value, traceback):
class FreeAdslBillFetcherCli(object):
PROG_NAME = 'free_adsl_bil_fetcher'
def _buildOptParser():
parser = OptionParser()
parser.add_option("-p", "--show-price", dest="show_price", default=False,
action="store_true", help="show price when listing bills")
parser.add_option("--latest", dest="fetch_latest", default=False,
action="store_true", help="only fetch latest bill")
parser.add_option("-c", "--config", dest="config_file",
default=os.path.expanduser('~') + '/.' + FreeAdslBillFetcherCli.PROG_NAME + '.conf',
metavar='FILE', help="configuration file")
parser.add_option("--get", dest="wanted_bills",
metavar='BILL_TITLE', help="Download bill BILL_TITLE and write it as BILL_TITLE.pdf")
parser.add_option("-d", "--write-dir", dest="write_directory",
metavar='DIR', help="write bills to directory DIR")
parser.add_option("--name-prefix", dest="name_prefix",
metavar='STR', help="prefix each bill filename with STR")
parser.add_option("--name-suffix", dest="name_suffix",
metavar='STR', help="suffix each bill filename with STR (before PDF extension)")
return parser
def parseArgs(this, arg):
(options, args) = this.opt_parser.parse_args(args=arg)
this.options = options
this.args = args
def __init__(this, args=sys.argv[1:]):
this.opt_parser = this._buildOptParser()
this.options = None
this.args = None
this.config = this._parseConfigFile(this.options.config_file)
this.fetcher = None
this.table = this._prepareTable()
def __enter__(this, *args, **kwargs):
this.fetcher = FreeAdslBillFetcher(this.config['login'], this.config['password'])
return this
def __exit__(this, type, value, traceback):
def _parseConfigFile(file):
with open(file) as f:
return json.load(f)
def _prepareTable(this):
headers = ['Month']
if this.options.show_price:
headers.insert(1, 'Price')
pt = PrettyTable(headers)
pt.align['Month'] = 'l'
return pt
def run(this):
wanted_bills = len(this.options.wanted_bills)
if wanted_bills != 0 or this.options.fetch_latest:
table_output = False
table_output = True
for bill in this.fetcher.listBills():
if this.options.fetch_latest is True:
pdf_path = this._fetchBill(bill)
print "Your latest bill was written to '%s'" % pdf_path
if wanted_bills > 0:
if bill.title in [x.decode('utf-8') for x in this.options.wanted_bills]:
pdf_path = this._fetchBill(bill)
print "[*] %s bill was written to '%s'" % (bill.title, pdf_path)
if table_output:
print this.table
def _appendBillToTable(this, bill):
row = [bill.title]
if this.options.show_price:
row.insert(1, '%.2f €' % bill.amount)
def _composeBillFilename(this, bill):
bill_filename = this.options.write_directory.rstrip('/') + '/'
bill_filename += this.options.name_prefix
bill_filename += re.sub(r'\s+', '_', bill.title)
bill_filename += this.options.name_suffix + '.pdf'
return bill_filename
def _fetchBill(this, bill):
bill_filename = this._composeBillFilename(bill)
this.fetcher.writeBillToFile(bill, bill_filename)
return bill_filename
if __name__ == "__main__":
with FreeAdslBillFetcherCli() as cli:
#!/usr/bin/env python
import os
import sys
import json
import requests
class FreeAdslFetcher(object):
USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.104 Safari/537.36'
def __init__(this, user, password):
this.user = user
this.password = password
this._auth_cookie = None
def _default_headers(this):
return {
'User-Agent': this.USER_AGENT
def _login(this):
payload = {
'login': this.user,
'pass' : this.password
r =, headers=this._default_headers(), data=payload)
print r.text
def main():
ids = None
with open('./ids.json') as f:
ids = json.load(f)
fetcher = FreeAdslFetcher(ids['login'], ids['password'])
if __name__ == "__main__":
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment