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 ) // 5 = credit (or ) // 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; }
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; }
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 ); }
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 ); }
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 ); }
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; }
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; }
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 ); }