<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">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-&gt;SUPER::new();
	#define elements that would be used and initialize them
	$self-&gt;{citi_url} = undef;
	$self-&gt;{username} = undef;
	$self-&gt;{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-&gt;get($amex_url);
	if ($result) {
		return (1);
	}
	else {
		return (0);
	}
}
sub login {
	my ($self, $username, $password) = @_;
	$self-&gt;form(1);
	$self-&gt;field("USERNAME", $username);
	$self-&gt;field("PASSWORD", $password);
	#we submit login form here:
	$self-&gt;click();
}
sub statement_summary {
	use HTML::TokeParser;
	my ($self, $stat_date_full) = @_;
	$self-&gt;form(4);
	$self-&gt;field("STATEMENT_DATE", $stat_date_full);
	#we submit form here:
	$self-&gt;click();

	my $stream = HTML::TokeParser-&gt;new(\$self-&gt;{content});
	my %data;
	# get min_due:
	for(my $i=0;$i&lt;9;$i++){
		$stream-&gt;get_tag("table");
	}
	for(my $i=0;$i&lt;1;$i++){
		$stream-&gt;get_tag("tr");
	}
	for(my $i=0;$i&lt;2;$i++){
		$stream-&gt;get_tag("td");
	}
	$stream-&gt;get_tag("b");
	$stream-&gt;get_tag("font");
	$data{min_due} = $stream-&gt;get_trimmed_text("/td");
	$data{min_due} = money_converter($data{min_due});

	# get due_date:
	for(my $i=0;$i&lt;1;$i++){
		$stream-&gt;get_tag("tr");
	}
	for(my $i=0;$i&lt;2;$i++){
		$stream-&gt;get_tag("td");
	}
	$stream-&gt;get_tag("b");
	$data{due_date} = $stream-&gt;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&lt;7;$i++){
		$stream-&gt;get_tag("table");
	}
	for(my $i=0;$i&lt;2;$i++){
		$stream-&gt;get_tag("tr");
	}
	for(my $i=0;$i&lt;1;$i++){
		$stream-&gt;get_tag("td");
	}
	$data{total_credit_line} = $stream-&gt;get_trimmed_text("/b");
	$data{total_credit_line} = money_converter($data{total_credit_line});

	# get available credit line:
	for(my $i=0;$i&lt;1;$i++){
		$stream-&gt;get_tag("td");
	}
	$data{avail_credit_line} = $stream-&gt;get_trimmed_text("/b");
	$data{avail_credit_line} = money_converter($data{avail_credit_line});

	# get cash advance limit:
	for(my $i=0;$i&lt;1;$i++){
		$stream-&gt;get_tag("td");
	}
	$data{cash_advance_limit} = $stream-&gt;get_trimmed_text("/b");
	$data{cash_advance_limit} = money_converter($data{cash_advance_limit});

	# get available cash limit:
	for(my $i=0;$i&lt;1;$i++){
		$stream-&gt;get_tag("td");
	}
	$data{avail_cash_limit} = $stream-&gt;get_trimmed_text("/b");
	$data{avail_cash_limit} = money_converter($data{avail_cash_limit});

	# get new balance:
	for(my $i=0;$i&lt;1;$i++){
		$stream-&gt;get_tag("td");
	}
	$data{new_balance} = $stream-&gt;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-&gt;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-&gt;form(4);
	$self-&gt;field("STATEMENT_DATE", $stat_date_full);
	#we submit form here:
	$self-&gt;click();

	my $stream = HTML::TokeParser-&gt;new(\$self-&gt;{content});

	for(my $i=0;$i&lt;19;$i++){
		$stream-&gt;get_tag("table");
	}
	for(my $i=0;$i&lt;5;$i++){
		$stream-&gt;get_tag("tr");
	}
	# we should be at the first row of the transactions table now
	my @t = $stream-&gt;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-&gt;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 &lt;td&gt; tag for amount the amount is wrapped with &lt;a&gt; and &lt;span&gt; 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 '&lt;/TABLE&gt;') {#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] =~ /&lt;TR VALIGN="TOP"&gt;/) {
			warn "inside loop\n" if $debug;
			@t = $stream-&gt;get_token;#get opening &lt;td tag for column 0 (sale date)
			warn "Right after -11 " .Dumper(\@t) if $debug;
			@t = $stream-&gt;get_token;#get text between &lt;td&gt;&lt;/td&gt; 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 &lt;td&gt;&lt;/td&gt; in array
			@t = $stream-&gt;get_token;#get closing &lt;/td&gt; tag - useless data
			warn "Right after -9 " .Dumper(\@t) if $debug;

			@t = $stream-&gt;get_token;#get opening &lt;td tag for column 1 (posting date)
			warn "Right after -8 " .Dumper(\@t) if $debug;
			@t = $stream-&gt;get_token;#get text between &lt;td&gt;&lt;/td&gt; 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 &lt;td&gt;&lt;/td&gt; in array
			@t = $stream-&gt;get_token;#get closing &lt;/td&gt; tag - useless data
			warn "Right after -6 " .Dumper(\@t) if $debug;

			@t = $stream-&gt;get_token;#get opening &lt;td tag for column 2 (reference #)
			warn "Right after -5 " .Dumper(\@t) if $debug;
			@t = $stream-&gt;get_token;#get text between &lt;td&gt;&lt;/td&gt; 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 &lt;td&gt;&lt;/td&gt; in array
			@t = $stream-&gt;get_token;#get closing &lt;/td&gt; tag - useless data
			warn "Right after -3 " .Dumper(\@t) if $debug;

			@t = $stream-&gt;get_token;#get opening &lt;td tag for column 3 (merchant name)
			warn "Right after -2 " .Dumper(\@t) if $debug;
			@t = $stream-&gt;get_token;#get text between &lt;td&gt;&lt;/td&gt; tags
				warn "I just read text: " .Dumper(\@t) if $debug;
			do {
				warn "colmn 03 stores long text b/w &lt;td&gt;'s: arry_return[$dim1][$dim2] = $t[0][1]" if $debug;
				$arry_return[$dim1][$dim2] .= $t[0][1];#put text between &lt;td&gt;&lt;/td&gt; in array
				@t = $stream-&gt;get_token;#get text between &lt;td&gt;&lt;/td&gt; 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-&gt;get_token;#get opening &lt;td tag for column 4 (trans. type)
			warn "Right after3 opening &lt;td&gt; tag" . Dumper(\@t) if $debug;
			@t = $stream-&gt;get_token;#get text for &lt;td&gt; of column 4 or &lt;span&gt; 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 &lt;span&gt; tag
				$span_tag = 1;
				warn "\$span_tag is defined" if $debug;
				@t = $stream-&gt;get_token;#get text after opening &lt;span&gt; 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 &lt;td&gt; and/or &lt;span&gt; tags
			if ($span_tag == 1) {
				warn "\$span_tag is defined" if $debug;
				@t = $stream-&gt;get_token;#get closing &lt;/span&gt; tag - useless data
				# the following only occurs if &lt;span&gt; tags were used in this td
				@t = $stream-&gt;get_token;#get hanging closing &lt;/a&gt; tag (BAD HTML)- useless data
				$span_tag = 0;
			}
			@t = $stream-&gt;get_token;#get closing &lt;/td&gt; tag - useless data
			warn "closing &lt;/td&gt; tag will be processed now: " if $debug;
			warn "closing &lt;/td&gt; tag: ". Dumper (\@t) if $debug;

			@t = $stream-&gt;get_token;#get opening &lt;td tag for column 5 (trans. descr)
			warn "opening &lt;td&gt; tag will be processed now: " if $debug;
			warn "opening &lt;td&gt; tag: " . Dumper (\@t) if $debug;
			@t = $stream-&gt;get_token;#get &lt;a&gt; 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-&gt;get_token;#get the opening &lt;span&gt; tag
			@t = $stream-&gt;get_token;#get text between &lt;span&gt;&lt;/span&gt; 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 &lt;span&gt;&lt;/span&gt; in array
			@t = $stream-&gt;get_token;#get closing &lt;/span&gt; tag - useless data
			@t = $stream-&gt;get_token;#get closing &lt;/a&gt; tag - useless data
			@t = $stream-&gt;get_token;#get closing &lt;/td&gt; tag - useless data

			@t = $stream-&gt;get_token;#get closing &lt;/tr&gt; tag - useless data
			$dim1++;
		}
		@t = $stream-&gt;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-&gt;get("$main_page");
	my $stream = HTML::TokeParser-&gt;new(\$self-&gt;{content});
	#let's start process of getting Statement Balance:
	for(my $i=0;$i&lt;8;$i++){#pass 16 opening table tags
		$stream-&gt;get_tag("table");
	}
	for(my $i=0;$i&lt;6;$i++){
		$stream-&gt;get_tag('tr');
	}
	$stream-&gt;get_tag('td');
	$stream-&gt;get_tag('td');
	my $current_balance;
	$current_balance= $stream-&gt;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-&gt;get("$main_page");
	my $stream = HTML::TokeParser-&gt;new(\$self-&gt;{content});
	#let's start process of getting Statement Balance:
	for(my $i=0;$i&lt;8;$i++){#pass 15 opening table tags
		$stream-&gt;get_tag("table");
	}
	for(my $i=0;$i&lt;12;$i++){#pass 13 opening table tags
		$stream-&gt;get_tag('tr');
	}
	$stream-&gt;get_tag('td');
	$stream-&gt;get_tag('td');
	my $available_credit;
	my @junk;
	$available_credit = $stream-&gt;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-&gt;get("https://www99.americanexpress.com/myca/acctsumm/us/action?request_type=authreg_acctAccountSummary&amp;Face=en_US");
#	my $stream = HTML::TokeParser-&gt;new(\$self-&gt;{content});
#	#let's start process of getting Statement Balance:
#	for(my $i=0;$i&lt;17;$i++){#pass 16 opening table tags
#		$stream-&gt;get_tag("table");
#	}
#	$stream-&gt;get_tag('tr');
#	$stream-&gt;get_tag('tr');
#	$stream-&gt;get_tag('td');
#	my $recent_pay;
#	$recent_pay= $stream-&gt;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-&gt;get("$main_page");
	my $stream = HTML::TokeParser-&gt;new(\$self-&gt;{content});
	#let's start process of getting Statement Balance:
	for(my $i=0;$i&lt;8;$i++){#pass 15 opening table tags
		$stream-&gt;get_tag("table");
	}
	for(my $i=0;$i&lt;18;$i++){
		$stream-&gt;get_tag('tr');
	}
	$stream-&gt;get_tag('td');
	$stream-&gt;get_tag('td');
	$stream-&gt;get_tag('td');
	my $pay_due_date;
	$pay_due_date= $stream-&gt;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-&gt;get("$main_page");
	my $stream = HTML::TokeParser-&gt;new(\$self-&gt;{content});
	#let's start process of getting Statement Balance:
	for(my $i=0;$i&lt;8;$i++){#pass 16 opening table tags
		$stream-&gt;get_tag("table");
	}
	for(my $i=0;$i&lt;16;$i++){
		$stream-&gt;get_tag('tr');
	}
	$stream-&gt;get_tag('td');
	$stream-&gt;get_tag('td');
	$stream-&gt;get_tag('td');
	my $pay_due_amount;
	#following must return Due Apr 14
	$pay_due_amount= $stream-&gt;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-&gt;get("$main_page");
	my $stream = HTML::TokeParser-&gt;new(\$self-&gt;{content});
	#let's start process of getting Statement Balance:
	for(my $i=0;$i&lt;8;$i++){#pass 16 opening table tags
		$stream-&gt;get_tag("table");
	}
	for(my $i=0;$i&lt;14;$i++){
		$stream-&gt;get_tag('tr');
	}
	$stream-&gt;get_tag('td');
	$stream-&gt;get_tag('td');
	$stream-&gt;get_tag('td');
	my $close_date;
	$close_date= $stream-&gt;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&amp;POSTING_DATE=04%2F05%2F2005&amp;TRANSACTION_TYPE_TEXT=++++++++++&amp;REFERENCE_NUMBER=&amp;PERSON_NAME=&amp;TRANSACTION_AMOUNT=-255.10&amp;FOREIGN_CURRENCY=&amp;MERCHANT_DESCRIPTION=CLICK-TO-PAY+PAYMENT%2C+THANK+YOU+++++++++&amp;SIC_DESCRIPTION=++++++++++++++++++++++++++++++++++++++++&amp;STATEMENT_DATE=04%2F14%2F2005&amp;TITLE_COLOR=%23FFFFFF&amp;SUB_TITLE_COLOR=%23DDDDDD&amp;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 ('\&amp;', $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/&lt;!--(.Â¦\n)*--&gt;//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 &amp;nbsp; for strings
	$string =~ s/^\s*&amp;nbsp;\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-&gt;get("$stat_page");
	my $stream = HTML::TokeParser-&gt;new(\$self-&gt;{content});
	my @t;
	for(my $i=0;$i&lt;11;$i++){
		$stream-&gt;get_tag("table");
	}
	for(my $i=0;$i&lt;4;$i++){
		$stream-&gt;get_tag("tr");
	}
	$stream-&gt;get_tag("td");
	$stream-&gt;get_tag("select");
	@t = $stream-&gt;get_token;# on the &lt;select tag
	my @arry_stat_dates; # will hold all statement dates available for pulling from web site
	#let's separate year and month of $stat_date in two separate variables:
	my ($yr, $mo) = split(/-|\//, $stat_date);# separator could be a dash "-" or a slash "/"
	for(my $i=0;$i&lt;5;){#loop thru the &lt;select tag - there are only 5 statement dates listed there in &lt;option tags
		@t = $stream-&gt;get_token;# on the input tag
		if(($t[0][0] eq 'S') and ($t[0][1] eq 'option')) {
			$arry_stat_dates[$i++] = "$t[0][2]{value}";
			if($t[0][2]{value} =~ /^$mo\/\d\d\/$yr$/){
				$stat_date_full = $t[0][2]{value};# figuring out what exactly the statement date is
				return $stat_date_full;
			}
		}
	}
	if (!$stat_date_full) {
		warn "statement for the period of $stat_date is not available" if $debug;
		return 0;
	}
}
1;
</pre></body></html>