/usr/local/CPAN/Finance-Bank-easybank/Finance/Bank/easybank.pm
# $Id: easybank.pm,v 1.7 2004/05/02 11:39:52 florian Exp $
package Finance::Bank::easybank;
require 5.005_62;
use strict;
use warnings;
use Carp;
use WWW::Mechanize;
use HTML::TokeParser;
use constant {
LOGIN_URL => 'https://ebanking.easybank.at//InternetBanking/InternetBanking?d=login&svc=EASYBANK&lang=de&ui=html',
};
use Class::MethodMaker
new_hash_init => 'new',
get_set => [ qw/user pass _agent/ ],
boolean => [ qw/return_floats _connected/ ],
list => [ qw/accounts entries/ ];
our $VERSION = '1.05';
# login into the online banking system.
# fail if either user or password isn't defined.
#
# XXX: catch login errors.
sub _connect {
my $self = shift;
my $content;
croak "Need user to connect.\n" unless $self->user;
croak "Need password to connect.\n" unless $self->pass;
$self->_agent(WWW::Mechanize->new);
$self->_agent->agent_alias('Mac Safari');
$self->_agent->get(LOGIN_URL);
$self->_agent->form_number(1);
$self->_agent->field('tn', $self->user);
$self->_agent->field('pin', $self->pass);
$self->_agent->click('Bsenden1');
$content = $self->_agent->content;
croak "The online banking system told me, that the user was not found.\n"
if $content =~ /Der Verfüger ist nicht vorhanden/;
croak "The online banking system told me, that the user or the password was invalid.\n"
if $content =~ /Das Format der Verfügernummer oder des Passworts ist ungültig/;
croak "The online banking system told me, that the password was invalid.\n"
if $content =~ /Ihre PIN ist falsch/;
croak "There was a system error - please consult the hotline.\n"
if $content =~ /Es ist ein Systemfehler aufgetreten/;
}
# fetches and parses the summary page for all given accounts.
# if no accounts have been defined, fetches and parses the summary
# displayed right after the login.
#
# returns a reference to a list of summary hashes.
sub check_balance {
my $self = shift;
my @accounts;
# XXX: yeah, I'm lazy, but thats the easy way for a reset.
$self->_connect;
if($self->accounts_count > 0) {
foreach my $account ($self->accounts) {
$self->_select_account($account);
push @accounts, $self->_parse_summary($self->_agent->content);
}
} else {
push @accounts, $self->_parse_summary($self->_agent->content);
}
# return either a list with the accounts or a hashref
# with the accountno. as key.
return wantarray
? @accounts
: { map { $_->{account} => $_ } @accounts };
}
# fetches and parses the first entries page for all given accounts.
# if no accounts have been defined, fetches and parses the first
# entries page of the account displayed right after the login.
#
# returns a reference to a list of entry hashes.
sub get_entries {
my $self = shift;
my %accounts;
my $accountno;
my $entries;
# XXX: yeah, I'm lazy, but thats the easy way for a reset.
$self->_connect;
# go to the entries page.
$self->_agent->form_number(2);
$self->_agent->click;
if($self->entries_count > 0) {
foreach my $account ($self->entries) {
$self->_select_account($account);
($accountno, $entries) = $self->_parse_entries($self->_agent->content);
$accounts{$accountno} = $entries;
}
} else {
($accountno, $entries) = $self->_parse_entries($self->_agent->content);
$accounts{$accountno} = $entries;
}
\%accounts;
}
# selects given account ($account).
sub _select_account {
my($self, $account) = @_;
$self->_agent->form_number(1);
$self->_agent->field('selected-account', $account);
$self->_agent->click;
}
# parses given html ($content) containing the last 0 - 20 entries of an
# account and returns a hashref containing the single entries.
sub _parse_entries {
my ($self, $content) = @_;
my $stream = HTML::TokeParser->new(\$content);
my $accountno;
my @data;
$stream->get_tag('table') for 1 .. 3;
$stream->get_tag('tr') for 1 .. 3;
$stream->get_tag('td') for 1 .. 2;
$accountno = $stream->get_trimmed_text('/td');
$stream->get_tag('table') for 1 .. 2;
$stream->get_tag('tr');
# ugh...
while(1) {
my $nr;
my %entry;
$stream->get_tag('tr');
$stream->get_tag('td');
$nr = $stream->get_trimmed_text('/td');
# end the loop if we find the first cell in a row which isn't a
# numeric value (should be the first after the entries-table).
last unless $nr =~ /^\d+$/;
$entry{nr} = $nr;
for(qw/date text/) {
$stream->get_tag('td');
$entry{$_} = $stream->get_trimmed_text('/td');
}
$stream->get_tag('td');
for(qw/value currency amount/) {
$stream->get_tag('td');
$entry{$_} = $stream->get_trimmed_text('/td');
}
$entry{amount} = $self->_scalar2float($entry{amount})
if $self->return_floats;
push @data, \%entry;
}
($accountno, \@data);
}
# parses given html ($content) containing the summary of an account and
# returns a hashref containing the isolated data.
sub _parse_summary {
my ($self, $content) = @_;
my $stream = HTML::TokeParser->new(\$content);
my %data;
$stream->get_tag('table') for 1 .. 2;
$stream->get_tag('td');
for(qw/bc account currency name date/) {
$stream->get_tag('td');
$data{$_} = $stream->get_trimmed_text('/td');
}
$stream->get_tag('table') for 1 .. 2;
$stream->get_tag('b');
$data{balance} = $stream->get_trimmed_text('/b');
$stream->get_tag('b') for 1 .. 2;
$data{final} = $stream->get_trimmed_text('/b');
if($self->return_floats) {
$data{$_} = $self->_scalar2float($data{$_}) for qw/balance final/;
}
\%data;
}
# converts given scalar ($scalar) into a float and returns it.
sub _scalar2float {
my($self, $scalar) = @_;
$scalar =~ s/\.//g;
$scalar =~ s/,/\./g;
return $scalar;
}
1;