package Finance::Card::Citibank; ########################################################################### # Finance::Card::Citibank # Mark Grimes # # Check your credit card balances. # Copyright (c) 2005-8 Mark Grimes (mgrimes@cpan.org). # All rights reserved. This program is free software; you can redistribute # it and/or modify it under the same terms as Perl itself. # # Parts of this package were inspired by Simon Cozens' # Finance::Bank::Lloyds module. Thanks Simon! # # Jon Keller contributed much of the code to work with multiple accounts. # Thanks Jon! # ########################################################################### use strict; use warnings; use Carp; use WWW::Mechanize; use HTML::TreeBuilder::XPath; use HTML::Element; our $VERSION = '1.80'; my $ua = WWW::Mechanize->new( env_proxy => 1, keep_alive => 1, timeout => 30, ); sub check_balance { my ( $class, %opts ) = @_; my $self = bless {%opts}, $class; my $content; if ( $opts{content} ) { # If we give it a file, use the file rather than downloading open my $fh, "<", $opts{content} or confess; $content = do { local $/ = undef; <$fh> }; close $fh; } else { croak "Must provide a password" unless exists $opts{password}; croak "Must provide a username" unless exists $opts{username}; $ua->get("http://www.citicards.com/cards/wv/home.do") or confess "couldn't load the initial page"; $ua->submit_form( form_name => 'LOGIN', fields => { 'USERNAME' => $opts{username}, 'PASSWORD' => $opts{password}, 'NEXT_SCREEN' => '/AccountSummary', }, ) or confess "couldn't submit the login form"; $content = $ua->content; # First account's data is in content, subsequent accounts' data are # ajax loaded from .../GetDashboardAccount. Concatenate the two pages # and search them together $ua->get("https://www.accountonline.com/GetDashboardAccounts") or confess "couldn't load GetDashboardAccounts"; $content .= $ua->content; $ua->get( "https://www.accountonline.com/cards/svc/GetDashboardNonDefaultAccounts.do" ) or confess "couldn't load GetDashboardNonDefaultAccounts"; $content .= $ua->content; } if ( $opts{log} ) { # Dump to the filename passed in log open( my $fh, ">", $opts{log} ) or confess; print $fh $content; close $fh; } my $position = 1; my @accounts; my $tree = HTML::TreeBuilder::XPath->new; $tree->parse_content($content) or confess "Couldn't parse content"; my @accnts = $tree->findnodes('//div[@class="main_module"]'); for my $accnt (@accnts) { my $tree = HTML::TreeBuilder::XPath->new; $tree->parse_content( $accnt->as_HTML ) or confess; my @names = $tree->findnodes('//span[@class="card_num"]'); next unless @names; my $name = $names[0]->as_trimmed_text; ## warn "# Name: $name\n"; my @balances = $tree->findnodes('//div[@class="curr_balance"]'); next unless @balances; my $balance = $balances[0]->as_trimmed_text; $balance =~ s/[\$,]//g; $balance *= -1; ## warn "# Balance: $balance\n"; my ($account_no) = $name =~ m/(\d+)/; ## warn "# Account: $account_no\n"; ## warn "# Position: $position\n"; # i.e. "1" for the 1st account..."n" for the nth account push @accounts, ( bless { balance => $balance, name => $name, sort_code => $account_no, account_no => $account_no, position => $position++, # redundant since just = array index + 1 statement => undef, ## parent => $self, }, "Finance::Card::Citibank::Account" ); } return @accounts; } package Finance::Card::Citibank::Account; use base qw(Class::Accessor::Fast); __PACKAGE__->mk_accessors( qw(balance name sort_code account_no position statement)); 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME Finance::Card::Citibank - Check your Citigroup credit card accounts from Perl =head1 SYNOPSIS use Finance::Card::Citibank; my @accounts = Finance::Card::Citibank->check_balance( username => "xxxxxxxxxxxx", password => "12345", ); foreach (@accounts) { printf "%20s : %8s / %8s : USD %9.2f\n", $_->name, $_->sort_code, $_->account_no, $_->balance; } =head1 DESCRIPTION This module provides a rudimentary interface to Citigroup online at C. You will need either C or C installed for HTTPS support to work. C is required. =head1 CLASS METHODS =head2 check_balance() check_balance( usename => $u, password => $p ) Return an array of account objects, one for each of your bank accounts. =head1 OBJECT METHODS $ac->name $ac->sort_code $ac->account_no Return the account name, sort code and the account number. The sort code is just the name in this case, but it has been included for consistency with other Finance::Bank::* modules. $ac->balance Return the account balance as a signed floating point value. =head1 WARNING This warning is verbatim from Simon Cozens' C, and certainly applies to this module as well. This is code for B, and that means B, and that means B. You are encouraged, nay, expected, to audit the source of this module yourself to reassure yourself that I am not doing anything untoward with your banking data. This software is useful to me, but is provided under B, explicit or implied. =head1 THANKS Simon Cozens for C. The interface to this module, some code and the pod were all taken from Simon's module. Jon Keller added the ability to pull multiple accounts. =head1 AUTHOR Mark Grimes =head1 COPYRIGHT AND LICENSE Copyright (C) 2005-8 by mgrimes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.2 or, at your option, any later version of Perl 5 you may have available. =cut