Пример #1
0
        public override IOnlineStatement Parse( OnlineAccount account, StreamReader reader )
        {
            // Format of this data is HTML

            string html = reader.ReadToEnd();

            OnlineStatement statement = new OnlineStatement();

            Regex regex = new Regex( @"<td>\s*<a class=""blue noul m_link m_type""\s*onclick="".*?""\s*href=""/myaccount/.*?&historySequenceNumber=(\d+?)&.*?"">(\d\d/\d\d/\d\d\d\d)</a>\s*</td>\s*<td>\s*<div class=""s16 mb0 mr0"">\s*<div>\s*<a class=""blue m_link m_type"" onclick="".*?""\s*href=""/myaccount/.*?"">\s*<span class=""transaction_description"">\s*(.*?)\s*</span>\s*</a>\s*<div>\s*</div>\s*</td>\s*<td class=""right"">\s*<a class=""blue noul m_link m_type""\s*onclick="".*?""\s*href=""/myaccount/.*?"">(.*?)</a>\s*</td>\s*<td class=""right"">\s*<a class=""blue noul m_link m_type""\s*onclick="".*?""\s*href=""/myaccount/.*?"">(.*?)</a>\s*</td>\s*<td class=""right"">\s*<a class=""blue noul m_link m_type""\s*onclick="".*?""\s*href=""/myaccount/.*?"">(.*?)</a>\s*</td>",
                RegexOptions.Singleline );

            foreach( Match match in regex.Matches( html ) )
            {
                // 1 = "history sequence number" - hopefully a unique ID
                // 2 = date MM/YY/DDDD
                // 3 = description
                // 4 = debit (or &nbsp;)
                // 5 = credit (or &nbsp;)
                // 6 = balance
                OnlineTransaction record = new OnlineTransaction();

            }

            //// Columns:
            //// 0 = bank id
            //// 1 = account number
            //// 2 = account type (CHECKING)
            //// 3 = balance
            //// 4 = start date (YYYY-MM-DD)
            //// 5 = end date (YYYY-MM-DD)
            //// 6 = transaction type (CREDIT or DEBIT)
            //// 7 = transaction date (YYYY-MM-DD)
            //// 8 = transaction amount
            //// 9 = transaction id
            //// 10 = transaction description

            //reader.ReadLine(); // Skip headers

            //DateTime lastDate = DateTime.MinValue;
            //int lastSequence = 0;

            //string line;
            //while( (line = reader.ReadLine()) != null )
            //{
            //    string[] columns = line.Split( ',' );

            //    OnlineTransaction record = new OnlineTransaction();
            //    record.Amount = decimal.Parse( columns[8] );
            //    // Form BankId from account number and transaction id
            //    record.BankId = columns[1] + "-" + columns[9];
            //    record.Date = DateTime.Parse( columns[7] );
            //    record.Description = columns[10];
            //    record.Number = null;

            //    // Strip the "- Debit" suffix ING puts on the description
            //    if( record.Description.EndsWith( " - Debit" ) )
            //        record.Description = record.Description.Substring( 0, record.Description.Length - 8 );

            //    if( record.Date > lastDate ) lastSequence = -1;
            //    record.Sequence = ++lastSequence;

            //    // Transactions are given to us in descending order.
            //    // Reverse it so they are in ascending order
            //    // by inserting at the top of the list each time.
            //    statement.Transactions.Insert( 0, record );
            //}

            // TODO: Parse balances

            return statement;
        }
Пример #2
0
        public override IOnlineStatement Parse( OnlineAccount account, StreamReader reader )
        {
            string html = reader.ReadToEnd();

            // Mortgage is a special case
            if( account.Name.StartsWith( "Mortgage" ) )
                return this.ParseMortgagePage( html );

            OnlineStatement statement = new OnlineStatement();

            //string balance = this.browser.RegexCapture( @"Balance: \$[\d,.]+?</div>", html );
            //statement.Balance = decimal.Parse( balance );

            string regex;

            if( account.Name.StartsWith( "Checking" ) )
                regex = @"<td\s?>\s+(\d\d/\d\d/\d\d\d\d|PENDING)</td>\s+<td>.*?</td>\s+<td><span id="".*?""><a class="""" ID="".*?"" href='.*?ss_tempTransaction=(PD94.*?)&.*?'>(.*?)\s*</a></span></td>\s+<td class=""currency""\s*?>\s+\$?([\d.,]*?)</td>\s+<td class=""currency""\s*?>\s+\$?([\d.,]*?)</td>\s+<td class=""currency""\s*?>\s+(\$?[\d.,]*?)</td>";
            else if( account.Name.StartsWith( "Equity" ) )
                regex = @"<td>(\d\d/\d\d/\d\d\d\d)</td>\s+<td>.*?</td>\s+<td><span id="".*?""><a class="""" ID="".*?"" href='.*?ss_tempTransaction=(PD94.*?)&.*?'>(.*?)\s*</a></span></td>\s+<td class=""currency"">\$?([\d.,]*?)</td>\s+<td class=""currency"">\$?([\d.,]*?)</td>";
            else
                throw new OnlineServiceException( "Parsing the " + account.Name +
                    " account is not supported at this time." );

            MatchCollection matches = Regex.Matches( html, regex );

            foreach( Match match in matches )
            {
                if( match.Groups[1].Value != "PENDING" )
                {
                    OnlineTransaction otrans = new OnlineTransaction();

                    otrans.Date = DateTime.Parse( match.Groups[1].Value );

                    otrans.Description = this.Cleanse( match.Groups[3].Value );

                    // Parse check numbers
                    if( account.Name.StartsWith( "Checking" ) &&
                        otrans.Description.StartsWith( "CHECK #" ) )
                    {
                        otrans.Number = otrans.Description.Substring( 7 ).Trim();
                    }
                    if( account.Name.StartsWith( "Equity" ) )
                    {
                        // Parse out any 3+ digit number beginning with hash mark (#110)
                        Match checkmatch = Regex.Match( otrans.Description, @"[^#]*?#(\d\d\d+)[^#]*?" );
                        if( checkmatch.Success ) otrans.Number = checkmatch.Groups[1].Value;
                    }

                    // Compute positive or negative amount based on position in debit or credit column.
                    if( match.Groups[4].Value.Length > 0 )
                        otrans.Amount = -Decimal.Parse( match.Groups[4].Value );
                    else if( match.Groups[5].Value.Length > 0 )
                        otrans.Amount = Decimal.Parse( match.Groups[5].Value );
                    if( account.AccountType == OnlineAccountType.Liability )
                        otrans.Amount = -otrans.Amount;

                    // Add to beginning of list to reverse the order from oldest to newest
                    statement.Transactions.Insert( 0, otrans );
                }
            }

            DateTime lastDate = DateTime.MinValue;
            int lastSequence = 0;

            foreach( OnlineTransaction otrans in statement.Transactions )
            {
                // Calculate the Sequence
                if( otrans.Date > lastDate )
                    lastSequence = -1;
                otrans.Sequence = ++lastSequence;
                lastDate = otrans.Date;
            }

            return statement;
        }
Пример #3
0
        public override void Download( OnlineAccount account, StreamWriter writer )
        {
            IngOnlineAccount ingAccount = account as IngOnlineAccount;

            string baseUri = "https://secure.ingdirect.com";

            string html;

            // Go to Account page

            this.LogWrite( OnlineServiceEventType.Service,
                string.Format( "Navigating to {0} account", account.Name ) );

            html = this.browser.HttpGet( baseUri + ingAccount.Link );

            //System.Net.CookieCollection cookies = this.browser.Cookies.GetCookies( this.browser.Request.Address );
            //string domain = cookies[0].Domain;
            //string path = cookies[0].Path;

            //this.browser.Cookies.Add(
            //    new System.Net.Cookie( "__utma", "60842100.1257663593.1340193643.1347635755.1347639360.9", path, domain ) );

            //this.browser.Cookies.Add(
            //    new System.Net.Cookie( "__utmb", "60842100", path, domain ) );

            //this.browser.Cookies.Add(
            //    new System.Net.Cookie( "__utmc", "60842100", path, domain ) );

            //this.browser.Cookies.Add(
            //    new System.Net.Cookie( "__utmv", "60842100.Customer", path, domain ) );

            //this.browser.Cookies.Add(
            //    new System.Net.Cookie( "__utmz", "60842100.1347639360.9.3.utmccn=(referral)|utmcsr=secure.ingdirect.com|utmcct=/myaccount/INGDirect/logout.vm|utmcmd=referral", path, domain ) );

            // Go to Download page

            html = this.browser.HttpGet( baseUri + "/myaccount/INGDirect/downloadTransactions?startDate=&endDate=&transactionFilterType=ALL&transactionFilterDateType=LAST_30&fromPage=accountTransactions" );

            // Select download format and date range

            string acctNum = this.browser.RegexCapture( "<input type=\"hidden\" name=\"account\" value=\"(.*?)\"/>", html );

            NameValueCollection fields = new NameValueCollection();
            fields.Add( "pageToken", this.browser.RegexCapture( "<input type=\"hidden\" id=\"pageToken\" name=\"pageToken\" value=\"(.*?)\" />", html ) );
            fields.Add( "account", this.browser.RegexCapture( "<input type=\"hidden\" name=\"account\" value=\"(.*?)\"/>", html ) );
            fields.Add( "nickname", this.browser.RegexCapture( "<input type=\"hidden\" name=\"nickname\" value=\"(.*?)\"/>", html ) );
            fields.Add( "description", this.browser.RegexCapture( "<input type=\"hidden\" name=\"description\" value=\"(.*?)\"/>", html ) );
            fields.Add( "_eventId", "download" );
            fields.Add( "transactionFilterType", "ALL" );
            fields.Add( "transactionFilterDateType", "LAST_30" );
            fields.Add( "type", "CSV" );
            fields.Add( "startDate", string.Format( "{0:MM/dd/yyyy}", DateTime.Now.AddDays( -30 ) ) );
            fields.Add( "endDate", string.Format( "{0:MM/dd/yyyy}", DateTime.Now ) );
            fields.Add( "startDateInput", string.Format( "{0:MM/dd/yyyy}", DateTime.Now.AddDays( -30 ) ) );
            fields.Add( "endDateInput", string.Format( "{0:MM/dd/yyyy}", DateTime.Now ) );

            string action = this.browser.RegexCapture( "<form method=\"POST\" name=\"DownloadTransactionsForm\" id=\"DownloadTransactionsForm\" action=\"(.*?)\">", html );

            this.LogWrite( OnlineServiceEventType.Service,
                string.Format( "Downloading {0} transactions", account.ToString() ) );

            // This fails: I have no idea why.
            html = this.browser.HttpPostForm( baseUri + "/myaccount/INGDirect/" + action, fields, false );

            html = this.browser.HttpGet( this.browser.Response.Headers["Location"], false );

            html = this.browser.HttpGet( baseUri + "/myaccount/download.qfx", false );

            writer.Write( html );
        }
Пример #4
0
        public override void Download( OnlineAccount account, StreamWriter writer )
        {
            this.ValidateConnection();

            SuntrustOnlineAccount suntrustAccount = (SuntrustOnlineAccount)account;

            // Just fetch the link we recorded earlier in the Accounts property above.
            this.LogWrite( OnlineServiceEventType.Service, "Navigating to transaction page" );
            string html = this.browser.HttpGet( suntrustAccount.PageLink );

            writer.Write( html );
        }
Пример #5
0
        private void DownloadVisa( OnlineAccount account, StreamWriter writer )
        {
            string baseVacuUri = "https://command.onlinebank.com/1899/";

            // Get credit card "More Info" page

            string html = this.browser.HttpGet( baseVacuUri + this.visaLink );

            // Get Card History page

            string link = this.browser.RegexCapture( @"<a href=""(https://www\.epscu\.com/servlet/4989/raAppHtml.*?)"" class=""ilpMenu"">Card History</a>", html );

            string logoutLink = this.browser.RegexCapture( @"<a href=""(https://www\.epscu\.com/.*?)"">Log Out</a>", html );

            html = this.browser.HttpGet( link );

            this.browser.HttpGet( logoutLink ); // Logout from credit card page

            writer.Write( html );
        }
Пример #6
0
        public IOnlineStatement ParseVisa( OnlineAccount account, StreamReader reader )
        {
            // Format is HTML
            string html = reader.ReadToEnd();

            OnlineStatement statement = new OnlineStatement();

            // Rather complex regex expression...
            // This regex only matches purchases, not payments
            Regex regex = new Regex( @"<TR class="".*?"">\s+<TD vAlign=""middle"" height=""6"" width=""13%"">\s+<FONT color=""#000000"" size=""1"" face=""Arial"">(\d{1,2}/\d\d/\d\d)</FONT>\s+</TD><TD vAlign=""middle"" height=""6"" width=""13%"">\s+<FONT color=""#000000"" size=""1"" face=""Arial"">(\d{1,2}/\d\d/\d\d)</FONT>\s+</TD><TD vAlign=""middle"" height=""6"" width=""55%"">\s+<FONT color=""#000000"" size=""1"" face=""Arial"">(.*?)</FONT>\s+</TD><TD vAlign=""middle"" height=""6"" width=""12%"">\s+<FONT color=""#000000"" size=""1"" face=""Arial"">\s+\$([\d,.]+)</FONT>" );
            MatchCollection matches = regex.Matches( html );

            // Resulting list should be in order from oldest to newest.
            // Fortunately that is how the transactions are given to us.

            // TODO: Pick out payments and finance charges, too!

            foreach( Match match in matches )
            {
                OnlineTransaction otrans = new OnlineTransaction();

                try
                {
                    // First column is the date
                    otrans.Date = DateTime.Parse( Cleanse( match.Groups[2].Value ) );

                    otrans.Description = Cleanse( match.Groups[3].Value.Trim() ).Trim();

                    otrans.Amount = Decimal.Parse( Cleanse( match.Groups[4].Value ) );

                    otrans.BankId = this.CreateBankId( otrans, otrans.Description );

                    statement.Transactions.Add( otrans );
                }
                catch( FormatException ex )
                {
                    // Cannot parse transaction - just skip it.
                    this.LogWrite( OnlineServiceEventType.Warning,
                        string.Format( "Skipping {0}, cannot parse: {1}", match.Groups[0], ex.Message ) );
                }
            }

            // TODO: Parse balances

            return statement;
        }
Пример #7
0
        public override IOnlineStatement Parse( OnlineAccount account, StreamReader reader )
        {
            // Format is actually CSV, not HTML

            if( account.Name == "Visa" )
                return this.ParseVisa( account, reader );

            string text = reader.ReadToEnd();

            OnlineStatement statement = new OnlineStatement();

            // 1 = date
            // 2 = description
            // 3 = comments, with newlines
            // 4 = check number
            // 5 = debit "(" or credit ""
            // 6 = amount
            // 7 = balance
            string regexString = @"""(\d{1,2}/\d\d/\d\d\d\d)"",""(.*?)"",""(.*?)"",""(.*?)"",""(\(?)\$([\d,.-]*?)\)?"",""(.*?)""";
            Regex regex = new Regex( regexString, RegexOptions.Singleline );
            MatchCollection matches = regex.Matches( text );

            // Resulting list should be in order from oldest to newest.
            // Fortunately that is how the transactions are given to us.

            DateTime lastDate = DateTime.MinValue;
            int lastSequence = 0;

            foreach( Match match in matches )
            {
                OnlineTransaction otrans = new OnlineTransaction();

                try
                {
                    // First column is the date
                    otrans.Date = DateTime.Parse( Cleanse( match.Groups[1].Value ) );

                    // HACK: Reject dates before 12/5 because that is when the new Vacu
                    // site came online.
                    if( otrans.Date < new DateTime( 2009, 12, 5 ) ) continue;

                    if( match.Groups[2].Value.StartsWith( "DBT PURCHASE" )
                        || match.Groups[2].Value.StartsWith( "POS PURCHASE" ) )
                        // Leave off the "DBT"/"POS" information as it is somewhat useless.
                        otrans.Description = Cleanse( match.Groups[3].Value ).Trim();
                    else
                        otrans.Description = (Cleanse( match.Groups[2].Value ) + " " + Cleanse( match.Groups[3].Value )).Trim();

                    int i = otrans.Description.IndexOf( "CARD NBR:" );
                    if( i >= 0 ) otrans.Description = otrans.Description.Substring( 0, i ).Trim();

                    if( match.Groups[4].Value.Length > 0 )
                        otrans.Number = match.Groups[4].Value;

                    otrans.Amount = Decimal.Parse( Cleanse( match.Groups[6].Value ) );

                    // Values displayed in parens are negative (debits).
                    if( match.Groups[5].Value == "(" )
                        otrans.Amount = -otrans.Amount;

                    // We use a hash of the running bank balance as a unique identifier
                    otrans.BankId = this.CreateBankId( otrans, Cleanse( match.Groups[7].Value ) );

                    // Calculate the Sequence
                    if( otrans.Date > lastDate )
                        lastSequence = -1;
                    otrans.Sequence = ++lastSequence;

                    lastDate = otrans.Date;

                    statement.Transactions.Add( otrans );
                }
                catch( FormatException ex )
                {
                    // Cannot parse transaction for some reason - just skip it.
                    this.LogWrite( OnlineServiceEventType.Warning,
                        string.Format( "Skipping {0}, cannot parse: {1}", match.Groups[0], ex.Message ) );
                }
            }

            // TODO: Parse balances

            return statement;
        }
Пример #8
0
        public override void Download( OnlineAccount account, StreamWriter writer )
        {
            string baseVacuUri = "https://command.onlinebank.com/1899/";

            string html;

            // VACU maintains credit card information on an entirely different page.
            if( account.Name == "Visa" )
            {
                this.DownloadVisa( account, writer );
                return;
            }

            this.LogWrite(OnlineServiceEventType.Service,
                string.Format( "Navigating to {0} account.", account ) );

            html = this.browser.HttpGet( baseVacuUri + "ViewAccounts/AccountActivity.aspx" );

            string action = this.browser.RegexCapture( "<form name=\"aspnetForm\" method=\"post\" action=\"(.*?)\"", html );
            if( action == null || action.Length == 0 )
                throw new OnlineServiceException( "Unable to locate form action URL on AccountActivity.aspx." );

            // On about 10/14/2010, VACU changed the following selection
            // so that it used an opaque value instead of the actual
            // account number.  Reworked to parse out the correct ID
            // based on the text of the selection.

            string r1 = @"<select name="".*?\$selectedAccount"" id="".*?_selectedAccount"">(.*?)</select>";
            string r2 = "<option (?:selected=\"selected\" )*value=\"(.*?)\">(.*?)</option>";

            // Enumerate each available option
            string accountId = null;
            string options = this.browser.RegexCapture( r1, RegexOptions.Singleline, html );
            foreach( Match match in Regex.Matches( html, r2 ) )
            {
                string optionValue = match.Groups[1].Value;
                string optionText = match.Groups[2].Value;
                switch( account.Name )
                {
                    case "Checking":
                        if( optionText.IndexOf( "CHECKING" ) >= 0 )
                            accountId = optionValue;
                        break;
                    case "Savings":
                        if( optionText.IndexOf( "SAVINGS" ) >= 0 )
                            accountId = optionValue;
                        break;
                    case "Loan":
                    case "MatrixLoan":
                        if( optionText.IndexOf( "MATRIX" ) >= 0 )
                            accountId = optionValue;
                        break;
                    case "HondaLoan":
                        if( optionText.IndexOf( "HONDA" ) >= 0 )
                            accountId = optionValue;
                        break;
                    case "Mortgage":
                        if( optionText.IndexOf( "MORTGAGE" ) >= 0 )
                            accountId = optionValue;
                        break;
                    default:
                        throw new OnlineServiceException(
                            string.Format( "Unexpected account '{0}' sent to Vacu reader.", account ) );
                }
            }

            NameValueCollection fields = new NameValueCollection();
            fields.Add( "M_content_ScriptManager_HiddenField", ";;System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35:en-US:1247b7d8-6b6c-419f-a45f-8ff264c90734:ea597d4b:b25378d2" );
            fields.Add( "UsePrinterFriendlyVersion", "Off" );
            fields.Add( "__EVENTTARGET", string.Empty );
            fields.Add( "__EVENTARGUMENT", string.Empty );
            fields.Add( "__VIEWSTATEID", this.browser.DotNetCaptureHidden( html, "__VIEWSTATEID" ) );
            fields.Add( "__VIEWSTATE", "" );
            fields.Add( "__EVENTVALIDATION", this.browser.DotNetCaptureHidden( html, "__EVENTVALIDATION" ) );
            foreach( Match match in Regex.Matches( html, @"<input name=""(M\$content\$PCDZ\$.*?\$state)"" type=""hidden"" id=""M_content_PCDZ_.*?_state"" />" ) )
                fields.Add( match.Groups[1].Value, string.Empty );
            fields.Add( "M$content$PCDZ$M2SN6LC$ctl00$selectedAccount", accountId );
            fields.Add( "M$content$PCDZ$M2SN6LC$ctl00$ClearedDateChoices", "30" );
            fields.Add( "M$content$PCDZ$M2SN6LC$ctl00$FromDate", string.Empty );
            fields.Add( "M$content$PCDZ$M2SN6LC$ctl00$ToDate", string.Empty );
            fields.Add( "M$content$PCDZ$M2SN6LC$ctl00$checkNumber", string.Empty );
            fields.Add( "M$content$PCDZ$M2SN6LC$ctl00$amount", string.Empty );
            fields.Add( "M$content$PCDZ$M2SN6LC$ctl00$sendAmount", string.Empty );
            fields.Add( "M$content$PCDZ$M2SN6LC$ctl00$search", "1" );

            // Post to AccountActivity.aspx
            html = this.browser.HttpPostForm( baseVacuUri + "ViewAccounts/" + action, fields );

            this.LogWrite(OnlineServiceEventType.Service,
                string.Format( "Downloading {0} transactions", account ) );

            action = this.browser.RegexCapture( "<form name=\"aspnetForm\" method=\"post\" action=\"(.*?)\"", html );
            if( action == null || action.Length == 0 )
                throw new OnlineServiceException( "Unable to locate form action URL on AccountActivity.aspx." );

            fields.Clear();
            fields.Add( "M_content_ScriptManager_HiddenField", ";;System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35:en-US:1247b7d8-6b6c-419f-a45f-8ff264c90734:ea597d4b:b25378d2" );
            fields.Add( "UsePrinterFriendlyVersion", "Off" );
            fields.Add( "__EVENTTARGET", "M:content:PCDZ:M2SN6LC:ctl00:DownloadOptions:ctl01:Download" );
            fields.Add( "__EVENTARGUMENT", string.Empty );
            fields.Add( "__VIEWSTATEID", this.browser.DotNetCaptureHidden( html, "__VIEWSTATEID" ) );
            fields.Add( "__VIEWSTATE", "" );
            fields.Add( "__EVENTVALIDATION", this.browser.DotNetCaptureHidden( html, "__EVENTVALIDATION" ) );
            foreach( Match match in Regex.Matches( html, @"<input name=""(M\$content\$PCDZ\$.*?\$state)"" type=""hidden"" id=""M_content_PCDZ_.*?_state"" />" ) )
                fields.Add( match.Groups[1].Value, string.Empty );
            fields.Add( "M$content$PCDZ$M2SN6LC$ctl00$selectedAccount", accountId );
            fields.Add( "M$content$PCDZ$M2SN6LC$ctl00$ClearedDateChoices", "30" );
            fields.Add( "M$content$PCDZ$M2SN6LC$ctl00$FromDate", string.Empty );
            fields.Add( "M$content$PCDZ$M2SN6LC$ctl00$ToDate", string.Empty );
            fields.Add( "M$content$PCDZ$M2SN6LC$ctl00$checkNumber", string.Empty );
            fields.Add( "M$content$PCDZ$M2SN6LC$ctl00$amount", string.Empty );
            fields.Add( "M$content$PCDZ$M2SN6LC$ctl00$sendAmount", string.Empty );

            // Post to AccountActivity.aspx
            html = this.browser.HttpPostForm( baseVacuUri + "ViewAccounts/" + action, fields );

            writer.Write( html );
        }