#!/usr/bin/perl # # get_discover_ofx.pl # # A quick hack to download the OFX formated data describing your Discover Card # transactions between any two dates. For usage instructions, try # the '-help' flag. # # If you intend to use the downloaded OFX without importing it into some # financial software package, you might find the (equally quick) hack by the # name of PrettyPrintXML.pl to be useful. It should be able to be found where # you got this script. It will make it *much* easier to parse visually... # # e.g. # $ ./get_discover_ofx.pl 6011222233334444 Y N | ./PrettyPrintXML.pl # # Copyright (C) 2004 Craig B. Agricola # # get_discover_ofx.pl is free software; you can redistribute it and/or # modify it under the terms of "The Artistic License", which can be found # at http://www.opensource.org/licenses/artistic-license.php # # $Id: get_discover_ofx.pl,v 1.2 2004/04/15 21:37:44 agricolc Exp $ use LWP; use Time::Local; sub fmtdate { my ($time) = @_; my (@t) = reverse((localtime($time))[0..5]); $t[0]+=1900; $t[1]+=1; return(sprintf("%04d".("%02d"x5), @t)); } sub gen_transaction_id { my $trnuid; for(my $i=0; $i<16; $i++) { $trnuid .= chr(int(rand(26))+65) } return($trnuid); } sub get_password { my $password; system("stty", "-echo"); my $tty = `tty`; chop($tty); open(TTY, "+<$tty"); print TTY ("Password: "); chop($password = ); print TTY "\n"; close(TTY); system("stty", "echo"); return($password); } sub parse_date { my ($argv) = @_; my $date = shift(@{$argv}); if (!defined($date)) { $date = undef; } elsif ($date =~ /^([NDWMY])?(([+-]\d+[smhDWMY])*)$/) { my $ds = $2; if (($1 eq "N") || ($1 eq "")) { $date = time(); } elsif ($1 eq "D") { $date = timelocal(0,0,0,(localtime(time()))[3,4,5]); } elsif ($1 eq "W") { my (@now) = (localtime(time()))[3,4,5,6]; $date = timelocal(0,0,0,@now[0,1,2]); $date -= $now[3] * 24 * 60 * 60; } elsif ($1 eq "M") { $date = timelocal(0,0,0,1,(localtime(time()))[4,5]); } elsif ($1 eq "Y") { $date = timelocal(0,0,0,1,0,(localtime(time()))[5]); } while ($ds=~/^([+-])(\d+)([smhDWMY])(.*)$/) { $ds = $4; my $inc = $2; if ($3 eq "M") { my $sign = ($1 eq "-")?-1:1; my @date = localtime($date); $date[5] += $sign * int($inc/12); $date[4] += $sign * ($inc % 12); if ($date[4]<0) { $date[5]--; $date[4]+=12; } $date = timelocal(@date); } else { if ($1 eq "-") { $inc *= -1; } if ($3 eq "m") { $inc *= 60; } elsif ($3 eq "h") { $inc *= 60 * 60; } elsif ($3 eq "D") { $inc *= 60 * 60 * 24; } elsif ($3 eq "W") { $inc *= 60 * 60 * 24 * 7; } elsif ($3 eq "Y") { $inc *= 60 * 60 * 24 * 365; } $date += $inc; } } } elsif ($date =~ m#^(\d+)/(\d+)/(\d+)(_(\d+):(\d+)(:(\d+))?)?#) { $date = timelocal($8,$6,$5,$3,$2-1,$1-1900); } return($date); } sub make_ofx_request { my ($userid, $userpass, $startdate, $enddate) = @_; my $req_content = join("",); my $curdate = fmtdate(time()); my $trnuid = gen_transaction_id(); if (defined($startdate)) { $startdate = fmtdate($startdate); $enddate = fmtdate($enddate); $req_content =~ s#().*()#$1$startdate$2#; $req_content =~ s#().*()#$1$enddate$2#; } else { $req_content =~ s#().*()##; $req_content =~ s#().*()##; } $req_content =~ s#().*()#$1$curdate$2#; $req_content =~ s#().*()#$1$trnuid$2#; $req_content =~ s#().*()#$1$userid$2#; $req_content =~ s#().*()#$1$userid$2#; $req_content =~ s#().*()#$1$userpass$2#; return($req_content); } ##################################### MAIN #################################### if (grep(/^-h/, @ARGV)) { print <<'EOF'; Arguments: is your 16 digit Discover Card number specifies the date after which you want transactions specifies the date before which you want transactions If the and are not supplied, all transactions available on the server will be downloaded. If is supplied, but is not, then the current time will be used for . You will be prompted for your password, which is the password you use for logging into the Account Center at http://discovercard.com/ and are encoded as either: YYYY/MM/DD_hh:mm:ss OR First character specifies the base time: N - [N]ow (current time) D - to[D]ay (12:00AM this morning) W - this [W]eek (12:00AM of this preceeding Sunday) M - this [M]onth (12:00AM of the first day of this month) Y - this [Y]ear (12:00AM of the first day of this year) After that, additions or subtractions to the base time: [+-]nnn[smhDWMY] s - [s]econds m - [m]inutes h - [h]ours D - [D]ays W - [W]eeks M - [M]onths Y - [Y]ears If the base time isn't specified, an 'N' (now) is implicit Examples: N-3D+1h - Three days ago from an hour from now D-1M+7h+30m - 7:30AM one month ago W+3D - The Wednesday of this week M-5M+17d - The 17th day of 5 months ago Y+4M+5D+16h+45m - 4:45PM on April 5th of this year Y-2Y+4M+5D+16h+45m - 4:45PM on April 5th of two years ago -5h-30m - 5 hours and 30 minutes ago EOF exit(0); } my $userid = shift(@ARGV); my $startdate = parse_date(\@ARGV); my $enddate = parse_date(\@ARGV); my $userpass = get_password(); if (defined($startdate) && !defined($enddate)) { $enddate = time(); } my $req_content = make_ofx_request($userid, $userpass, $startdate, $enddate); my $ua = new LWP::UserAgent(); my $req = new HTTP::Request(POST=>"https://ofx.discovercard.com"); $req->content_type("application/x-ofx"); $req->content($req_content); my $res = $ua->request($req); if ($res->is_success) { printf("%s\n", $res->content()); } else { printf("%s\n", $res->as_string()) } exit(0); ################################# OFX REQUEST ################################# __DATA__ 20010101010203 6011XXXXYYYYZZZZ Password ENG QWIN 1100 gyXJ5Tz4sH7xAU99XIUw 6011XXXXYYYYZZZZ 20010101010203 20010101010203 Y