package citi; use lib "/usr/lib/perl5/vendor_perl/5.8.7/"; use strict; use WWW::Mechanize; use Data::Dumper; use vars qw(@ISA); @ISA = qw(WWW::Mechanize); my $main_page = 'https://www.accountonline.com/AccountSummary'; my $stat_page = 'https://www.accountonline.com/Statements';#monthly statements page my $debug = 0; sub new { my ($class) = @_; #call the constructor of the parent class, WWW:Mechanize my $self = $class->SUPER::new(); #define elements that would be used and initialize them $self->{citi_url} = undef; $self->{username} = undef; $self->{password} = undef; bless $self, $class; return $self; } sub open { #need to pass the $self initialized object in ($self), and then pass the url for the american express login page ($amex_url) my ($self, $amex_url) = @_;#grab function parameters and put in $self and $amex_url variables #use the "get" method of the "self" parent object my $result = $self->get($amex_url); if ($result) { return (1); } else { return (0); } } sub login { my ($self, $username, $password) = @_; $self->form(1); $self->field("USERNAME", $username); $self->field("PASSWORD", $password); #we submit login form here: $self->click(); } sub statement_summary { use HTML::TokeParser; my ($self, $stat_date_full) = @_; $self->form(4); $self->field("STATEMENT_DATE", $stat_date_full); #we submit form here: $self->click(); my $stream = HTML::TokeParser->new(\$self->{content}); my %data; # get min_due: for(my $i=0;$i<9;$i++){ $stream->get_tag("table"); } for(my $i=0;$i<1;$i++){ $stream->get_tag("tr"); } for(my $i=0;$i<2;$i++){ $stream->get_tag("td"); } $stream->get_tag("b"); $stream->get_tag("font"); $data{min_due} = $stream->get_trimmed_text("/td"); $data{min_due} = money_converter($data{min_due}); # get due_date: for(my $i=0;$i<1;$i++){ $stream->get_tag("tr"); } for(my $i=0;$i<2;$i++){ $stream->get_tag("td"); } $stream->get_tag("b"); $data{due_date} = $stream->get_trimmed_text("/font"); my @arry_date = split(/\//, $data{due_date}); $data{due_date} = "$arry_date[2]-$arry_date[0]-$arry_date[1]"; # get total credit line: for(my $i=0;$i<7;$i++){ $stream->get_tag("table"); } for(my $i=0;$i<2;$i++){ $stream->get_tag("tr"); } for(my $i=0;$i<1;$i++){ $stream->get_tag("td"); } $data{total_credit_line} = $stream->get_trimmed_text("/b"); $data{total_credit_line} = money_converter($data{total_credit_line}); # get available credit line: for(my $i=0;$i<1;$i++){ $stream->get_tag("td"); } $data{avail_credit_line} = $stream->get_trimmed_text("/b"); $data{avail_credit_line} = money_converter($data{avail_credit_line}); # get cash advance limit: for(my $i=0;$i<1;$i++){ $stream->get_tag("td"); } $data{cash_advance_limit} = $stream->get_trimmed_text("/b"); $data{cash_advance_limit} = money_converter($data{cash_advance_limit}); # get available cash limit: for(my $i=0;$i<1;$i++){ $stream->get_tag("td"); } $data{avail_cash_limit} = $stream->get_trimmed_text("/b"); $data{avail_cash_limit} = money_converter($data{avail_cash_limit}); # get new balance: for(my $i=0;$i<1;$i++){ $stream->get_tag("td"); } $data{new_balance} = $stream->get_trimmed_text("/b"); $data{new_balance} = money_converter($data{new_balance}); $data{stat_closing_date} = date_converter($stat_date_full); return %data; } sub statement_array { # this pulls the last statement with details of every transaction # if you run your script every month subroutine will download your monthly statement use HTML::TokeParser; my ($self, $stat_date_full) = @_; # load the page that has the statements $self->get("$stat_page"); # When user asks for a statement they say: "Give me my December 2004 statement". # They rarely say "Give me my December 14 2004 statement" because it could be that # that month it actually closed on Dec 13 2004 because Dec 14 2004 happened to be a # Saturday or Sunday. Therefore users rarely know the EXACT date of their statement. # the $stat_date parameter holds the argument user passes on to get_statement # it must be of format yyyy-mm or yyyy/mm. The statement page will not pull up # if we use date of the yyyy/mm or yyyy/mm format. We need to give it a date of # the exact mm/dd/yyyy format. Therefore we need code that will determine if date is # for example 12/15/2004 or 12/14/2004 or 12/16/2004 # the following code will grab the exact statement date for this month and year # using the already loaded Statements page. $self->form(4); $self->field("STATEMENT_DATE", $stat_date_full); #we submit form here: $self->click(); my $stream = HTML::TokeParser->new(\$self->{content}); for(my $i=0;$i<19;$i++){ $stream->get_tag("table"); } for(my $i=0;$i<5;$i++){ $stream->get_tag("tr"); } # we should be at the first row of the transactions table now my @t = $stream->get_token; warn "just before we start processing" . Dumper(\@t) if $debug; my @arry_return; # two-dimensional array holding transactions by date # to understand the following code you need to understand what array $stream->get_token returns # Here's an example: we have a table with rows # each row contains one transaction, every transaction (row) has six properties (row columns) # therefore we have a two-dimensional array - first key of array stores rows (distinct transactions) # second key stores each of the six properties # if our array is called $VAR, then $VAR[2][5] stores transaction #3 and the amount (amount is property #6 of table) # There are some peculiarities when pulling some of the properties of the transactions # e.g., in the tag for amount the amount is wrapped with and tags - # so we need to accomodate for this or we are going to pull the amount along # with the wrapping tags - which is not what we want my ($dim1, $dim2); $dim1 = 0; while ($t[0][2] ne '') {#read until the end of table $dim2 = 0; $t[0][4] = '' if (!$t[0][4]); #give it default value if not defined. warn "outside loop ".$t[0][4]."\n" if $debug; if ($t[0][4] =~ //) { warn "inside loop\n" if $debug; @t = $stream->get_token;#get opening get_token;#get text between tags warn "Right after -10 " .Dumper(\@t) if $debug; warn "stores colmn 01 - date of sale: arry_return[$dim1][$dim2] = $t[0][1]" if $debug; $arry_return[$dim1][$dim2++] = trim($t[0][1]);#put text between in array @t = $stream->get_token;#get closing tag - useless data warn "Right after -9 " .Dumper(\@t) if $debug; @t = $stream->get_token;#get opening get_token;#get text between tags warn "Right after -7 " .Dumper(\@t) if $debug; warn "stores colmn 01 - date of post: arry_return[$dim1][$dim2] = $t[0][1]" if $debug; $arry_return[$dim1][$dim2++] = trim($t[0][1]);#put text between in array @t = $stream->get_token;#get closing tag - useless data warn "Right after -6 " .Dumper(\@t) if $debug; @t = $stream->get_token;#get opening get_token;#get text between tags warn "Right after -4 " .Dumper(\@t) if $debug; warn "stores column 02 reference #: arry_return[$dim1][$dim2] = $t[0][1]" if $debug; $arry_return[$dim1][$dim2++] = trim($t[0][1]);#put text between in array @t = $stream->get_token;#get closing tag - useless data warn "Right after -3 " .Dumper(\@t) if $debug; @t = $stream->get_token;#get opening get_token;#get text between tags warn "I just read text: " .Dumper(\@t) if $debug; do { warn "colmn 03 stores long text b/w 's: arry_return[$dim1][$dim2] = $t[0][1]" if $debug; $arry_return[$dim1][$dim2] .= $t[0][1];#put text between in array @t = $stream->get_token;#get text between tags warn "this may be text or may not be text: " .Dumper(\@t) if $debug; } while ($t[0][0] eq 'T'); $dim2++; my $span_tag = 0; @t = $stream->get_token;#get opening tag" . Dumper(\@t) if $debug; @t = $stream->get_token;#get text for of column 4 or tag (trans. type) if ($t[0][0] eq 'T') { warn "colmn 04 stores transaction type: arry_return[$dim1][$dim2++] = $t[0][1]" if $debug; $arry_return[$dim1][$dim2++] = trim($t[0][1]); } warn "Right after4 " . Dumper(\@t) if $debug; if ($t[0][0] eq 'S') { # check if this is an opening tag $span_tag = 1; warn "\$span_tag is defined" if $debug; @t = $stream->get_token;#get text after opening tag } warn "colmn 04 transaction type: arry_return[$dim1][$dim2] = $t[0][1]" if $debug; $arry_return[$dim1][$dim2++] = trim($t[0][1]);#get text in and/or tags if ($span_tag == 1) { warn "\$span_tag is defined" if $debug; @t = $stream->get_token;#get closing tag - useless data # the following only occurs if tags were used in this td @t = $stream->get_token;#get hanging closing tag (BAD HTML)- useless data $span_tag = 0; } @t = $stream->get_token;#get closing tag - useless data warn "closing tag will be processed now: " if $debug; warn "closing tag: ". Dumper (\@t) if $debug; @t = $stream->get_token;#get opening tag will be processed now: " if $debug; warn "opening tag: " . Dumper (\@t) if $debug; @t = $stream->get_token;#get tag warn Dumper (\@t) if $debug; warn "(get_trans_descr) arry_return[$dim1][$dim2] = ". get_transaction_description($t[0][2]{'onclick'}) if $debug; $arry_return[$dim1][$dim2++] = get_transaction_description($t[0][2]{'onclick'}); @t = $stream->get_token;#get the opening tag @t = $stream->get_token;#get text between tags warn "colmn 06 stores amount \$\$: arry_return[$dim1][$dim2] = $t[0][1]" if $debug; $arry_return[$dim1][$dim2++] = trim($t[0][1]);#put text between in array @t = $stream->get_token;#get closing tag - useless data @t = $stream->get_token;#get closing tag - useless data @t = $stream->get_token;#get closing tag - useless data @t = $stream->get_token;#get closing tag - useless data $dim1++; } @t = $stream->get_token; #get next token please (tag or text b/w tags) } return (@arry_return); } sub get_bal { use HTML::TokeParser; my ($self) = @_; # let's go to the summary of accounts page: $self->get("$main_page"); my $stream = HTML::TokeParser->new(\$self->{content}); #let's start process of getting Statement Balance: for(my $i=0;$i<8;$i++){#pass 16 opening table tags $stream->get_tag("table"); } for(my $i=0;$i<6;$i++){ $stream->get_tag('tr'); } $stream->get_tag('td'); $stream->get_tag('td'); my $current_balance; $current_balance= $stream->get_trimmed_text("/td"); $current_balance = money_converter($current_balance); return ($current_balance); } sub avail_credit { use HTML::TokeParser; my ($self) = @_; # let's go to the summary of accounts page: $self->get("$main_page"); my $stream = HTML::TokeParser->new(\$self->{content}); #let's start process of getting Statement Balance: for(my $i=0;$i<8;$i++){#pass 15 opening table tags $stream->get_tag("table"); } for(my $i=0;$i<12;$i++){#pass 13 opening table tags $stream->get_tag('tr'); } $stream->get_tag('td'); $stream->get_tag('td'); my $available_credit; my @junk; $available_credit = $stream->get_trimmed_text("/td"); $available_credit = money_converter($available_credit); return ($available_credit); } #sub recent_payments { # use HTML::TokeParser; # my ($self) = @_; # # let's go to the summary of accounts page: # $self->get("https://www99.americanexpress.com/myca/acctsumm/us/action?request_type=authreg_acctAccountSummary&Face=en_US"); # my $stream = HTML::TokeParser->new(\$self->{content}); # #let's start process of getting Statement Balance: # for(my $i=0;$i<17;$i++){#pass 16 opening table tags # $stream->get_tag("table"); # } # $stream->get_tag('tr'); # $stream->get_tag('tr'); # $stream->get_tag('td'); # my $recent_pay; # $recent_pay= $stream->get_trimmed_text("/td"); # $recent_pay = money_converter($recent_pay); # return ($recent_pay); #} sub paymnt_due_date { use HTML::TokeParser; my ($self) = @_; # let's go to the summary of accounts page: $self->get("$main_page"); my $stream = HTML::TokeParser->new(\$self->{content}); #let's start process of getting Statement Balance: for(my $i=0;$i<8;$i++){#pass 15 opening table tags $stream->get_tag("table"); } for(my $i=0;$i<18;$i++){ $stream->get_tag('tr'); } $stream->get_tag('td'); $stream->get_tag('td'); $stream->get_tag('td'); my $pay_due_date; $pay_due_date= $stream->get_trimmed_text("/td"); $pay_due_date = date_converter($pay_due_date); return ($pay_due_date); } # pulls value for balance, not for Minimum payment due!!! sub paymnt_due_amt { use HTML::TokeParser; my ($self) = @_; # let's go to the summary of accounts page: $self->get("$main_page"); my $stream = HTML::TokeParser->new(\$self->{content}); #let's start process of getting Statement Balance: for(my $i=0;$i<8;$i++){#pass 16 opening table tags $stream->get_tag("table"); } for(my $i=0;$i<16;$i++){ $stream->get_tag('tr'); } $stream->get_tag('td'); $stream->get_tag('td'); $stream->get_tag('td'); my $pay_due_amount; #following must return Due Apr 14 $pay_due_amount= $stream->get_trimmed_text("/td"); $pay_due_amount = money_converter($pay_due_amount); return ($pay_due_amount); } sub bal_close_date {#date of last statement balance use HTML::TokeParser; my ($self) = @_; # let's go to the summary of accounts page: $self->get("$main_page"); my $stream = HTML::TokeParser->new(\$self->{content}); #let's start process of getting Statement Balance: for(my $i=0;$i<8;$i++){#pass 16 opening table tags $stream->get_tag("table"); } for(my $i=0;$i<14;$i++){ $stream->get_tag('tr'); } $stream->get_tag('td'); $stream->get_tag('td'); $stream->get_tag('td'); my $close_date; $close_date= $stream->get_trimmed_text("/td"); $close_date = date_converter($close_date); return ($close_date); } sub get_transaction_description { #this subroutine will receive this as input: # #window.open('/StatementDetail?SALE_DATE=04%2F05%2F2005&POSTING_DATE=04%2F05%2F2005&TRANSACTION_TYPE_TEXT=++++++++++&REFERENCE_NUMBER=&PERSON_NAME=&TRANSACTION_AMOUNT=-255.10&FOREIGN_CURRENCY=&MERCHANT_DESCRIPTION=CLICK-TO-PAY+PAYMENT%2C+THANK+YOU+++++++++&SIC_DESCRIPTION=++++++++++++++++++++++++++++++++++++++++&STATEMENT_DATE=04%2F14%2F2005&TITLE_COLOR=%23FFFFFF&SUB_TITLE_COLOR=%23DDDDDD&CHARGED_TO=','DETAIL','resizable=yes,scrollbars,width=600,height=310,top=100,left=100,screenx=100,screeny=100') # # and pull "ELECTRONIC+STORES+++++++++++++++++++++++" out of it my $description_str = shift; my $return_val; if ($description_str =~ /^window\.open\(\'\/StatementDetail\?(.*)\'\,\ \'.*\'\)/) { $description_str = $1; my @keys_values = split ('\&', $description_str); my %keys_values_hash; foreach my $element (@keys_values) { my ($key, $value) = split('\=', $element); # let's url-un-encode strings: # convert '+' to space $value =~ tr/\+/ /; # convert embedded comma to space delimiting purposes $value =~ tr/\,/ /; # convert hex symbols to alphanumeric $value=~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("c", hex($1))/ge; $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("c", hex($1))/ge; $value =~ s///g; $keys_values_hash{$key} = $value; } if ($keys_values_hash{'SIC_DESCRIPTION'}){ $return_val = $keys_values_hash{'SIC_DESCRIPTION'}; } else { $return_val = ''; } } } sub date_converter { #original date we are feeding this subroutine is in this format: mm/dd/yy my ($date_string) = @_; my (@arry_date) = split(/\//, $date_string); #format my date like this yyyy-mm-dd $date_string ="$arry_date[2]-$arry_date[0]-$arry_date[1]"; return ($date_string); } sub statement_date_converter { # receives two arguments: the statement date (2005-04) # and transaction date (03/22) # converts a statement transaction date to a mysql date. # for example, if my statement date is 01/15/2005 # I would have activities ranging from 12/16 to 01/15 # this function turns 12/16 to 2004-12-16 and 01/15 to 2005-01-15 my ($self, $date_statement, $date_transaction) = @_; my ($date_stat_year, $date_stat_month) = split('\-', $date_statement); my ($date_trans_month, $date_trans_day) = split('\/', $date_transaction); my ($date, $year); if ($date_stat_month == 1){ if ($date_trans_month == 12) { $year = $date_stat_year - 1; $date = $year.'-'.$date_trans_month.'-'.$date_trans_day; } else { $date = $date_stat_year.'-'.$date_trans_month.'-'.$date_trans_day; } } else { $date = $date_stat_year.'-'.$date_trans_month.'-'.$date_trans_day; } return $date; } sub money_converter { #takes values like $234.45 or $234** and converts them in numeric format of 234.45 and 234 respectively my ($money_to_numeric) = @_; $money_to_numeric =~ s/\$//g; #replace all dollar signs ($) with nothing, i.e. remove them $money_to_numeric =~ s/\*//g; #replace all stars (*) with nothing, i.e. remove them $money_to_numeric =~ s/\,//g; #replace all commas (,) with nothing, i.e. remove them return ($money_to_numeric); } sub trim { my $string = shift; $string =~ s/^\s+//; $string =~ s/\s+$//; #remove all   for strings $string =~ s/^\s* \s*$//g; return $string; } sub exact_statement_date { use HTML::TokeParser; my ($self, $stat_date) = @_; my $stat_date_full; # load the page that has the statements $self->get("$stat_page"); my $stream = HTML::TokeParser->new(\$self->{content}); my @t; for(my $i=0;$i<11;$i++){ $stream->get_tag("table"); } for(my $i=0;$i<4;$i++){ $stream->get_tag("tr"); } $stream->get_tag("td"); $stream->get_tag("select"); @t = $stream->get_token;# on the