File Coverage

blib/lib/Finance/Bank/easybank.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             # $Id: easybank.pm,v 1.7 2004/05/02 11:39:52 florian Exp $
2              
3             package Finance::Bank::easybank;
4              
5             require 5.005_62;
6 1     1   794 use strict;
  1         2  
  1         35  
7 1     1   6 use warnings;
  1         1  
  1         30  
8              
9 1     1   6 use Carp;
  1         5  
  1         99  
10 1     1   1672 use WWW::Mechanize;
  0            
  0            
11             use HTML::TokeParser;
12             use constant {
13             LOGIN_URL => 'https://ebanking.easybank.at//InternetBanking/InternetBanking?d=login&svc=EASYBANK&lang=de&ui=html',
14             };
15             use Class::MethodMaker
16             new_hash_init => 'new',
17             get_set => [ qw/user pass _agent/ ],
18             boolean => [ qw/return_floats _connected/ ],
19             list => [ qw/accounts entries/ ];
20              
21             our $VERSION = '1.05';
22              
23              
24             # login into the online banking system.
25             # fail if either user or password isn't defined.
26             #
27             # XXX: catch login errors.
28             sub _connect {
29             my $self = shift;
30             my $content;
31              
32             croak "Need user to connect.\n" unless $self->user;
33             croak "Need password to connect.\n" unless $self->pass;
34              
35             $self->_agent(WWW::Mechanize->new);
36             $self->_agent->agent_alias('Mac Safari');
37             $self->_agent->get(LOGIN_URL);
38             $self->_agent->form_number(1);
39             $self->_agent->field('tn', $self->user);
40             $self->_agent->field('pin', $self->pass);
41             $self->_agent->click('Bsenden1');
42              
43             $content = $self->_agent->content;
44             croak "The online banking system told me, that the user was not found.\n"
45             if $content =~ /Der Verfüger ist nicht vorhanden/;
46             croak "The online banking system told me, that the user or the password was invalid.\n"
47             if $content =~ /Das Format der Verfügernummer oder des Passworts ist ungültig/;
48             croak "The online banking system told me, that the password was invalid.\n"
49             if $content =~ /Ihre PIN ist falsch/;
50             croak "There was a system error - please consult the hotline.\n"
51             if $content =~ /Es ist ein Systemfehler aufgetreten/;
52             }
53              
54              
55             # fetches and parses the summary page for all given accounts.
56             # if no accounts have been defined, fetches and parses the summary
57             # displayed right after the login.
58             #
59             # returns a reference to a list of summary hashes.
60             sub check_balance {
61             my $self = shift;
62             my @accounts;
63              
64             # XXX: yeah, I'm lazy, but thats the easy way for a reset.
65             $self->_connect;
66              
67             if($self->accounts_count > 0) {
68             foreach my $account ($self->accounts) {
69             $self->_select_account($account);
70             push @accounts, $self->_parse_summary($self->_agent->content);
71             }
72             } else {
73             push @accounts, $self->_parse_summary($self->_agent->content);
74             }
75              
76             # return either a list with the accounts or a hashref
77             # with the accountno. as key.
78             return wantarray
79             ? @accounts
80             : { map { $_->{account} => $_ } @accounts };
81             }
82              
83              
84             # fetches and parses the first entries page for all given accounts.
85             # if no accounts have been defined, fetches and parses the first
86             # entries page of the account displayed right after the login.
87             #
88             # returns a reference to a list of entry hashes.
89             sub get_entries {
90             my $self = shift;
91             my %accounts;
92             my $accountno;
93             my $entries;
94              
95             # XXX: yeah, I'm lazy, but thats the easy way for a reset.
96             $self->_connect;
97              
98             # go to the entries page.
99             $self->_agent->form_number(2);
100             $self->_agent->click;
101              
102             if($self->entries_count > 0) {
103             foreach my $account ($self->entries) {
104             $self->_select_account($account);
105              
106             ($accountno, $entries) = $self->_parse_entries($self->_agent->content);
107             $accounts{$accountno} = $entries;
108             }
109             } else {
110             ($accountno, $entries) = $self->_parse_entries($self->_agent->content);
111             $accounts{$accountno} = $entries;
112             }
113              
114             \%accounts;
115             }
116              
117              
118             # selects given account ($account).
119             sub _select_account {
120             my($self, $account) = @_;
121              
122             $self->_agent->form_number(1);
123             $self->_agent->field('selected-account', $account);
124             $self->_agent->click;
125             }
126              
127              
128             # parses given html ($content) containing the last 0 - 20 entries of an
129             # account and returns a hashref containing the single entries.
130             sub _parse_entries {
131             my ($self, $content) = @_;
132             my $stream = HTML::TokeParser->new(\$content);
133             my $accountno;
134             my @data;
135              
136             $stream->get_tag('table') for 1 .. 3;
137             $stream->get_tag('tr') for 1 .. 3;
138             $stream->get_tag('td') for 1 .. 2;
139              
140             $accountno = $stream->get_trimmed_text('/td');
141              
142             $stream->get_tag('table') for 1 .. 2;
143             $stream->get_tag('tr');
144              
145             # ugh...
146             while(1) {
147             my $nr;
148             my %entry;
149              
150             $stream->get_tag('tr');
151             $stream->get_tag('td');
152              
153             $nr = $stream->get_trimmed_text('/td');
154             # end the loop if we find the first cell in a row which isn't a
155             # numeric value (should be the first after the entries-table).
156             last unless $nr =~ /^\d+$/;
157             $entry{nr} = $nr;
158              
159             for(qw/date text/) {
160             $stream->get_tag('td');
161             $entry{$_} = $stream->get_trimmed_text('/td');
162             }
163              
164             $stream->get_tag('td');
165              
166             for(qw/value currency amount/) {
167             $stream->get_tag('td');
168             $entry{$_} = $stream->get_trimmed_text('/td');
169             }
170              
171             $entry{amount} = $self->_scalar2float($entry{amount})
172             if $self->return_floats;
173              
174             push @data, \%entry;
175             }
176              
177             ($accountno, \@data);
178             }
179              
180              
181             # parses given html ($content) containing the summary of an account and
182             # returns a hashref containing the isolated data.
183             sub _parse_summary {
184             my ($self, $content) = @_;
185             my $stream = HTML::TokeParser->new(\$content);
186             my %data;
187              
188             $stream->get_tag('table') for 1 .. 2;
189             $stream->get_tag('td');
190              
191             for(qw/bc account currency name date/) {
192             $stream->get_tag('td');
193             $data{$_} = $stream->get_trimmed_text('/td');
194             }
195              
196             $stream->get_tag('table') for 1 .. 2;
197             $stream->get_tag('b');
198             $data{balance} = $stream->get_trimmed_text('/b');
199             $stream->get_tag('b') for 1 .. 2;
200             $data{final} = $stream->get_trimmed_text('/b');
201              
202             if($self->return_floats) {
203             $data{$_} = $self->_scalar2float($data{$_}) for qw/balance final/;
204             }
205              
206             \%data;
207             }
208              
209              
210             # converts given scalar ($scalar) into a float and returns it.
211             sub _scalar2float {
212             my($self, $scalar) = @_;
213              
214             $scalar =~ s/\.//g;
215             $scalar =~ s/,/\./g;
216              
217             return $scalar;
218             }
219              
220              
221             1;