syrup 0.0.13 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.rdoc +5 -1
- data/Gemfile.lock +33 -32
- data/README.rdoc +5 -4
- data/TODO.rdoc +0 -2
- data/lib/syrup/institutions/bank_af.rb +180 -0
- data/lib/syrup/institutions/cacert.pem +3 -41
- data/lib/syrup/institutions/institution_base.rb +2 -2
- data/lib/syrup/institutions/zions_bank.rb +168 -141
- data/lib/syrup/version.rb +1 -1
- data/spec/syrup/institutions/bank_af_spec.rb +41 -0
- data/spec/syrup/institutions/zions_bank_spec.rb +41 -41
- data/syrup.gemspec +1 -1
- metadata +86 -99
data/CHANGELOG.rdoc
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
syrup (0.0.
|
4
|
+
syrup (0.0.13)
|
5
5
|
mechanize (>= 1.0.0)
|
6
6
|
multi_json (>= 1.0.3)
|
7
7
|
|
@@ -9,50 +9,51 @@ GEM
|
|
9
9
|
remote: http://rubygems.org/
|
10
10
|
specs:
|
11
11
|
columnize (0.3.6)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
12
|
+
debugger (1.6.0)
|
13
|
+
columnize (>= 0.3.1)
|
14
|
+
debugger-linecache (~> 1.2.0)
|
15
|
+
debugger-ruby_core_source (~> 1.2.1)
|
16
|
+
debugger-linecache (1.2.0)
|
17
|
+
debugger-ruby_core_source (1.2.2)
|
18
|
+
diff-lcs (1.2.4)
|
19
|
+
domain_name (0.5.11)
|
20
|
+
unf (>= 0.0.5, < 1.0.0)
|
21
|
+
http-cookie (1.0.1)
|
22
|
+
domain_name (~> 0.5)
|
23
|
+
mechanize (2.7.1)
|
18
24
|
domain_name (~> 0.5, >= 0.5.1)
|
25
|
+
http-cookie (~> 1.0.0)
|
19
26
|
mime-types (~> 1.17, >= 1.17.2)
|
20
27
|
net-http-digest_auth (~> 1.1, >= 1.1.1)
|
21
28
|
net-http-persistent (~> 2.5, >= 2.5.2)
|
22
29
|
nokogiri (~> 1.4)
|
23
30
|
ntlm-http (~> 0.1, >= 0.1.1)
|
24
|
-
webrobots (
|
25
|
-
mime-types (1.
|
26
|
-
multi_json (1.
|
27
|
-
net-http-digest_auth (1.
|
28
|
-
net-http-persistent (2.
|
29
|
-
nokogiri (1.5.
|
31
|
+
webrobots (>= 0.0.9, < 0.2)
|
32
|
+
mime-types (1.23)
|
33
|
+
multi_json (1.7.4)
|
34
|
+
net-http-digest_auth (1.3)
|
35
|
+
net-http-persistent (2.8)
|
36
|
+
nokogiri (1.5.9)
|
30
37
|
ntlm-http (0.1.1)
|
31
|
-
rake (0.
|
32
|
-
|
33
|
-
|
34
|
-
rspec-
|
35
|
-
rspec-
|
36
|
-
|
37
|
-
rspec-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
ruby-debug (0.10.4)
|
42
|
-
columnize (>= 0.1)
|
43
|
-
ruby-debug-base (~> 0.10.4.0)
|
44
|
-
ruby-debug-base (0.10.4)
|
45
|
-
linecache (>= 0.3)
|
46
|
-
unf (0.0.5)
|
38
|
+
rake (10.0.4)
|
39
|
+
rspec (2.13.0)
|
40
|
+
rspec-core (~> 2.13.0)
|
41
|
+
rspec-expectations (~> 2.13.0)
|
42
|
+
rspec-mocks (~> 2.13.0)
|
43
|
+
rspec-core (2.13.1)
|
44
|
+
rspec-expectations (2.13.0)
|
45
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
46
|
+
rspec-mocks (2.13.1)
|
47
|
+
unf (0.1.1)
|
47
48
|
unf_ext
|
48
|
-
unf_ext (0.0.
|
49
|
-
webrobots (0.
|
49
|
+
unf_ext (0.0.6)
|
50
|
+
webrobots (0.1.1)
|
50
51
|
|
51
52
|
PLATFORMS
|
52
53
|
ruby
|
53
54
|
|
54
55
|
DEPENDENCIES
|
56
|
+
debugger
|
55
57
|
rake
|
56
58
|
rspec (>= 2.6.0)
|
57
|
-
ruby-debug
|
58
59
|
syrup!
|
data/README.rdoc
CHANGED
@@ -37,10 +37,11 @@ In <b>Rails 2</b>, add this to your environment.rb file.
|
|
37
37
|
|
38
38
|
== Supported Institutions
|
39
39
|
|
40
|
-
Currently, only
|
41
|
-
implementing UCCU, USAA, and Wells Fargo (probably in that order). If
|
42
|
-
like support for a different bank, you have two options:
|
40
|
+
Currently, only Zions Bank, UCCU, and Bank of American Fork are supported. I
|
41
|
+
will be implementing UCCU, USAA, and Wells Fargo (probably in that order). If
|
42
|
+
you would like support for a different bank, you have two options:
|
43
43
|
|
44
44
|
1. Get me the credentials to log into an account with that bank (you'd have to
|
45
45
|
trust me).
|
46
|
-
2. Implement it yourself and submit a pull request. See
|
46
|
+
2. Implement it yourself and submit a pull request. See
|
47
|
+
{Adding Support For Another Institution}[https://github.com/dontangg/syrup/wiki/Adding-Support-For-Another-Institution]
|
data/TODO.rdoc
CHANGED
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'bigdecimal'
|
3
|
+
require 'bigdecimal/util'
|
4
|
+
require 'csv'
|
5
|
+
|
6
|
+
module Syrup
|
7
|
+
module Institutions
|
8
|
+
|
9
|
+
class BankAf < InstitutionBase
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def name
|
13
|
+
"Bank of American Fork"
|
14
|
+
end
|
15
|
+
|
16
|
+
def id
|
17
|
+
"bank_af"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch_account(account_id)
|
22
|
+
fetch_accounts
|
23
|
+
end
|
24
|
+
|
25
|
+
def fetch_accounts
|
26
|
+
ensure_authenticated
|
27
|
+
|
28
|
+
# List accounts
|
29
|
+
page = agent.get('https://cm.netteller.com/login2008/Views/Retail/AccountListing.aspx')
|
30
|
+
|
31
|
+
accounts = []
|
32
|
+
page.search('#ctl00_PageContent_ctl00__acctsBase__depositsTab__depositsGrid tr').each do |row_element|
|
33
|
+
next if row_element["class"] == "th"
|
34
|
+
|
35
|
+
cells = row_element.css('td')
|
36
|
+
|
37
|
+
new_account = Account.new(:institution => self)
|
38
|
+
new_account.id = cells[1].inner_text.strip
|
39
|
+
new_account.name = new_account.id
|
40
|
+
new_account.available_balance = BigDecimal.new(parse_currency(cells[2].inner_text))
|
41
|
+
# new_account.current_balance =
|
42
|
+
# new_account.account_number =
|
43
|
+
# new_account.type = :deposit # :credit
|
44
|
+
|
45
|
+
accounts << new_account
|
46
|
+
end
|
47
|
+
|
48
|
+
accounts
|
49
|
+
end
|
50
|
+
|
51
|
+
def write_page(page, unique)
|
52
|
+
File.open("test#{unique}.html", 'w') do |f|
|
53
|
+
f.write page.uri.to_s
|
54
|
+
f.write page.body
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_event_target(html)
|
59
|
+
match = /doPostBack\('([^'"\\]+)'/.match(html)
|
60
|
+
match[1].gsub(/%24/, '$')
|
61
|
+
end
|
62
|
+
|
63
|
+
def fetch_transactions(account_id, starting_at, ending_at)
|
64
|
+
ensure_authenticated
|
65
|
+
|
66
|
+
# Get the accounts page and click on the desired account link
|
67
|
+
page = agent.get('https://cm.netteller.com/login2008/Views/Retail/AccountListing.aspx')
|
68
|
+
|
69
|
+
form = page.form('aspnetForm')
|
70
|
+
event_target = nil
|
71
|
+
page.search('#ctl00_PageContent_ctl00__acctsBase__depositsTab__depositsGrid tr').each do |row_element|
|
72
|
+
next if row_element["class"] == "th"
|
73
|
+
|
74
|
+
cells = row_element.css('td')
|
75
|
+
|
76
|
+
if cells[1].inner_text.strip == account_id
|
77
|
+
event_target = cells[4].css('select')[0]["name"]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
raise InformationMissingError, "Invalid account ID: #{account_id}" unless event_target
|
81
|
+
form["__EVENTTARGET"] = event_target
|
82
|
+
form.field_with(:name => 'ctl00$PageContent$ctl00$_acctsBase$_depositsTab$_depositsGrid$ctl02$_activity').option_with(:value => "TransactionDownloadViewAction").select
|
83
|
+
page = form.submit
|
84
|
+
|
85
|
+
# Tranferring to Coldfusion
|
86
|
+
form = page.forms[0]
|
87
|
+
form.action = "https://www.netteller.com/bankaf/hbProcessRequest.cfm?activity=D"
|
88
|
+
page = form.submit
|
89
|
+
|
90
|
+
# Submit the download request form
|
91
|
+
form = page.forms[0]
|
92
|
+
form.field_with(:name => 'AccountIndex').option_with(:text => account_id).select
|
93
|
+
form.field_with(:name => 'trans').option_with(:value => 'BetweenTwoDates').select
|
94
|
+
form.field_with(:name => 'format').option_with(:value => 'QFX').select
|
95
|
+
form["from"] = starting_at.strftime('%m/%d/%Y')
|
96
|
+
form["to"] = ending_at.strftime('%m/%d/%Y')
|
97
|
+
submit_button = form.button_with(:id => 'submitButton')
|
98
|
+
page = form.submit(submit_button)
|
99
|
+
|
100
|
+
page = page.link_with(:href => /DeliverContent/).click
|
101
|
+
|
102
|
+
# Get the transactions!
|
103
|
+
transactions = []
|
104
|
+
account = find_account_by_id(account_id)
|
105
|
+
page.body.each_line do |line|
|
106
|
+
line.strip!
|
107
|
+
|
108
|
+
if line.start_with?("<STMTTRN>")
|
109
|
+
match = /DTPOSTED>(\d+)<TRNAMT>\s?([0-9.-]+).*NAME>([^<]+)/.match(line)
|
110
|
+
txn = Transaction.new
|
111
|
+
|
112
|
+
txn.posted_at = Date.strptime(match[1][0..7], '%Y%m%d')
|
113
|
+
txn.amount = parse_currency(match[2])
|
114
|
+
txn.payee = match[3].strip
|
115
|
+
txn.status = :posted
|
116
|
+
|
117
|
+
transactions << txn
|
118
|
+
elsif line.start_with?("</BANKTRANLIST>")
|
119
|
+
match = /LEDGERBAL><BALAMT>([0-9.-]+).*AVAILBAL><BALAMT>([0-9.-]+)/.match(line)
|
120
|
+
account.name = account.id
|
121
|
+
account.current_balance = match[1].to_d
|
122
|
+
account.available_balance = match[2].to_d
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
transactions
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def ensure_authenticated
|
132
|
+
|
133
|
+
# Check to see if already authenticated
|
134
|
+
page = agent.get('https://cm.netteller.com/login2008/Views/Retail/AccountListing.aspx')
|
135
|
+
if page.body.include?("An Error Occurred While Processing Your Request")
|
136
|
+
|
137
|
+
raise InformationMissingError, "Please supply a username" unless self.username
|
138
|
+
raise InformationMissingError, "Please supply a password" unless self.password
|
139
|
+
|
140
|
+
# Enter the username and password
|
141
|
+
login_vars = { 'ID' => username, 'PIN' => password }
|
142
|
+
page = agent.post('https://cm.netteller.com/login2008/Authentication/Views/Login.aspx?fi=bankaf&bn=9de8ca724dd43418&burlid=dc1ba449ca4ad5c0', login_vars)
|
143
|
+
|
144
|
+
# If the supplied username/password is incorrect, raise an exception
|
145
|
+
raise InformationMissingError, "Invalid username or password" if page.body.include?("Invalid Online Banking ID or Password")
|
146
|
+
|
147
|
+
form = page.forms[0]
|
148
|
+
form["ctl00$PageContent$DevicePrintHiddenField"] = "version=1&pm_fpua=mozilla/5.0 (macintosh; intel mac os x 10.7; rv:11.0) gecko/20100101 firefox/11.0|5.0 (Macintosh)|MacIntel&pm_fpsc=24|1280|800|774&pm_fpsw=&pm_fptz=-6&pm_fpln=lang=en-US|syslang=|userlang=&pm_fpjv=1&pm_fpco=1"
|
149
|
+
page = form.submit
|
150
|
+
|
151
|
+
if page.uri.to_s == "https://cm.netteller.com/login2008/Authentication/Views/ChallengeQuestions.aspx"
|
152
|
+
form = page.forms[0]
|
153
|
+
question1 = page.search('#ctl00_PageContent_Question1Label').inner_text
|
154
|
+
form["ctl00$PageContent$Answer1TextBox"] = secret_questions[question1]
|
155
|
+
|
156
|
+
question2 = page.search('#ctl00_PageContent_Question2Label').inner_text
|
157
|
+
form["ctl00$PageContent$Answer2TextBox"] = secret_questions[question2]
|
158
|
+
|
159
|
+
submit_button = form.button_with(:name => 'ctl00$PageContent$SubmitButton')
|
160
|
+
|
161
|
+
page = form.submit(submit_button)
|
162
|
+
|
163
|
+
# TODO: What if the secret questions' answers were incorrect
|
164
|
+
#write_page(page, 'sq')
|
165
|
+
end
|
166
|
+
|
167
|
+
form = page.forms[0]
|
168
|
+
form.action = "https://cm.netteller.com/login2008/Default.aspx"
|
169
|
+
page = form.submit
|
170
|
+
|
171
|
+
# TODO: find a better way to test success
|
172
|
+
raise "Unknown URL reached. Try logging in manually through a browser." if page.uri.to_s != "https://cm.netteller.com/login2008/Views/Retail/AccountListing.aspx"
|
173
|
+
end
|
174
|
+
|
175
|
+
true
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -1,9 +1,7 @@
|
|
1
|
-
|
2
|
-
<!-- saved from url=(0033)http://curl.haxx.se/ca/cacert.pem -->
|
3
|
-
<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">##
|
1
|
+
##
|
4
2
|
## ca-bundle.crt -- Bundle of CA Root Certificates
|
5
3
|
##
|
6
|
-
## Certificate data from Mozilla as of:
|
4
|
+
## Certificate data from Mozilla as of: Wed Apr 25 15:02:13 2012
|
7
5
|
##
|
8
6
|
## This is a bundle of X.509 certificates of public Certificate Authorities
|
9
7
|
## (CA). These were automatically extracted from Mozilla's root certificates
|
@@ -16,42 +14,7 @@
|
|
16
14
|
## Just configure this file as the SSLCACertificateFile.
|
17
15
|
##
|
18
16
|
|
19
|
-
#
|
20
|
-
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
21
|
-
#
|
22
|
-
# The contents of this file are subject to the Mozilla Public License Version
|
23
|
-
# 1.1 (the "License"); you may not use this file except in compliance with
|
24
|
-
# the License. You may obtain a copy of the License at
|
25
|
-
# http://www.mozilla.org/MPL/
|
26
|
-
#
|
27
|
-
# Software distributed under the License is distributed on an "AS IS" basis,
|
28
|
-
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
29
|
-
# for the specific language governing rights and limitations under the
|
30
|
-
# License.
|
31
|
-
#
|
32
|
-
# The Original Code is the Netscape security libraries.
|
33
|
-
#
|
34
|
-
# The Initial Developer of the Original Code is
|
35
|
-
# Netscape Communications Corporation.
|
36
|
-
# Portions created by the Initial Developer are Copyright (C) 1994-2000
|
37
|
-
# the Initial Developer. All Rights Reserved.
|
38
|
-
#
|
39
|
-
# Contributor(s):
|
40
|
-
#
|
41
|
-
# Alternatively, the contents of this file may be used under the terms of
|
42
|
-
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
43
|
-
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
44
|
-
# in which case the provisions of the GPL or the LGPL are applicable instead
|
45
|
-
# of those above. If you wish to allow use of your version of this file only
|
46
|
-
# under the terms of either the GPL or the LGPL, and not to allow others to
|
47
|
-
# use your version of this file under the terms of the MPL, indicate your
|
48
|
-
# decision by deleting the provisions above and replace them with the notice
|
49
|
-
# and other provisions required by the GPL or the LGPL. If you do not delete
|
50
|
-
# the provisions above, a recipient may use your version of this file under
|
51
|
-
# the terms of any one of the MPL, the GPL or the LGPL.
|
52
|
-
#
|
53
|
-
# ***** END LICENSE BLOCK *****
|
54
|
-
# @(#) $RCSfile: certdata.txt,v $ $Revision: 1.82 $ $Date: 2012/02/18 21:41:46 $
|
17
|
+
# @(#) $RCSfile: certdata.txt,v $ $Revision: 1.83 $ $Date: 2012/04/25 14:49:29 $
|
55
18
|
|
56
19
|
GTE CyberTrust Global Root
|
57
20
|
==========================
|
@@ -3366,4 +3329,3 @@ l+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOSAgu+TGbrIP65y7WZf+a2
|
|
3366
3329
|
E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6Un/10asIbvPuW/mIPX64b24D
|
3367
3330
|
5EI=
|
3368
3331
|
-----END CERTIFICATE-----
|
3369
|
-
</pre></body></html>
|
@@ -140,7 +140,7 @@ module Syrup
|
|
140
140
|
|
141
141
|
# Provide path to cert bundle for Windows
|
142
142
|
# Downloaded from http://curl.haxx.se/ca/
|
143
|
-
@agent.agent.http.ca_file = File.expand_path(File.dirname(__FILE__) + "/cacert.pem")
|
143
|
+
@agent.agent.http.ca_file = File.expand_path(File.dirname(__FILE__) + "/cacert.pem")
|
144
144
|
end
|
145
145
|
|
146
146
|
@agent
|
@@ -151,7 +151,7 @@ module Syrup
|
|
151
151
|
#
|
152
152
|
# parse_currency('$ 1,234.56') #=> 1234.56
|
153
153
|
def parse_currency(currency)
|
154
|
-
BigDecimal.new(currency.gsub(/[^0-9
|
154
|
+
BigDecimal.new(currency.gsub(/[^0-9.-]/, ''))
|
155
155
|
end
|
156
156
|
|
157
157
|
# A helper method that replaces a few HTML entities with their actual characters
|
@@ -3,186 +3,213 @@ require 'date'
|
|
3
3
|
module Syrup
|
4
4
|
module Institutions
|
5
5
|
class ZionsBank < InstitutionBase
|
6
|
-
|
6
|
+
|
7
7
|
class << self
|
8
8
|
def name
|
9
9
|
"Zions Bank"
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
def id
|
13
13
|
"zions_bank"
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def fetch_account(account_id)
|
18
18
|
fetch_accounts
|
19
19
|
end
|
20
|
-
|
21
|
-
def fetch_accounts
|
22
|
-
ensure_authenticated
|
23
20
|
|
21
|
+
def fetch_accounts
|
22
|
+
# TODO: If I ever care about this, I'll add it in later. This is the url to grab from:
|
23
|
+
# https://banking.zionsbank.com/olb/retail/protected/myBank/balances?OWASP_CSRFTOKEN=
|
24
|
+
#
|
25
|
+
#ensure_authenticated
|
26
|
+
#
|
24
27
|
# List accounts
|
25
|
-
page = agent.get('https://banking.zionsbank.com/ibuir/displayAccountBalance.htm')
|
26
|
-
json = MultiJson.load(page.body)
|
27
|
-
|
28
|
-
accounts = []
|
29
|
-
json['accountBalance']['depositAccountList'].each do |account|
|
30
|
-
new_account = Account.new(:id => account['accountId'], :institution => self)
|
31
|
-
new_account.name = unescape_html(account['name'])
|
32
|
-
new_account.account_number = account['number']
|
33
|
-
new_account.current_balance = parse_currency(account['currentAmt'])
|
34
|
-
new_account.available_balance = parse_currency(account['availableAmt'])
|
35
|
-
new_account.type = :deposit
|
36
|
-
|
37
|
-
accounts << new_account
|
38
|
-
end
|
39
|
-
json['accountBalance']['creditAccountList'].each do |account|
|
40
|
-
new_account = Account.new(:id => account['accountId'], :institution => self)
|
41
|
-
new_account.name = unescape_html(account['name'])
|
42
|
-
new_account.account_number = account['number']
|
43
|
-
new_account.current_balance = parse_currency(account['balanceDueAmt'])
|
44
|
-
new_account.type = :credit
|
45
|
-
|
46
|
-
accounts << new_account
|
47
|
-
end
|
28
|
+
#page = agent.get('https://banking.zionsbank.com/ibuir/displayAccountBalance.htm')
|
29
|
+
#json = MultiJson.load(page.body)
|
48
30
|
|
49
|
-
accounts
|
31
|
+
#accounts = []
|
32
|
+
#json['accountBalance']['depositAccountList'].each do |account|
|
33
|
+
# new_account = Account.new(:id => account['accountId'], :institution => self)
|
34
|
+
# new_account.name = unescape_html(account['name'])
|
35
|
+
# new_account.account_number = account['number']
|
36
|
+
# new_account.current_balance = parse_currency(account['currentAmt'])
|
37
|
+
# new_account.available_balance = parse_currency(account['availableAmt'])
|
38
|
+
# new_account.type = :deposit
|
39
|
+
|
40
|
+
# accounts << new_account
|
41
|
+
#end
|
42
|
+
#json['accountBalance']['creditAccountList'].each do |account|
|
43
|
+
# new_account = Account.new(:id => account['accountId'], :institution => self)
|
44
|
+
# new_account.name = unescape_html(account['name'])
|
45
|
+
# new_account.account_number = account['number']
|
46
|
+
# new_account.current_balance = parse_currency(account['balanceDueAmt'])
|
47
|
+
# new_account.type = :credit
|
48
|
+
|
49
|
+
# accounts << new_account
|
50
|
+
#end
|
51
|
+
|
52
|
+
#accounts
|
53
|
+
|
54
|
+
[]
|
50
55
|
end
|
51
|
-
|
56
|
+
|
52
57
|
def fetch_transactions(account_id, starting_at, ending_at)
|
53
58
|
ensure_authenticated
|
54
|
-
|
59
|
+
|
55
60
|
transactions = []
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
page.
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
61
|
+
|
62
|
+
act_oid, act_attr = account_id.split('|')
|
63
|
+
|
64
|
+
url = "https://banking.zionsbank.com/olb/retail/protected/account/register/account?attr=#{act_attr}&#{@csrf}"
|
65
|
+
page = agent.get(url)
|
66
|
+
|
67
|
+
form = page.forms.first
|
68
|
+
form.action += "?#{@csrf}" unless form.action.include?(@csrf)
|
69
|
+
form["accountOid"] = act_oid
|
70
|
+
form["searchBy"] = "DR"
|
71
|
+
form['fromDate'] = starting_at.strftime('%m/%d/%Y')
|
72
|
+
form['toDate'] = ending_at.strftime('%m/%d/%Y')
|
73
|
+
submit_button = form.button_with(:id => 'formbutton')
|
74
|
+
page = form.submit(submit_button)
|
75
|
+
|
76
|
+
# Look for the account information first
|
77
|
+
account = find_account_by_id(account_id)
|
78
|
+
page.search('#subCell').first.element_children.each do |element|
|
79
|
+
if element.name == "div"
|
80
|
+
if match = /Prior Day Balance:\s*\$([0-9.,]+)/.match(element.inner_text)
|
68
81
|
account.prior_day_balance = parse_currency(match[1])
|
69
|
-
|
70
|
-
if match = /Current Balance:\s*([^<]+)/.match(datapart.inner_html)
|
82
|
+
elsif match = /Current Balance:\s*\$([0-9.,]+)/.match(element.inner_text)
|
71
83
|
account.current_balance = parse_currency(match[1])
|
72
|
-
|
73
|
-
if match = /Available Balance:\s*([^<]+)/.match(datapart.inner_html)
|
84
|
+
elsif match = /Available Balance:\s*\$([0-9.,]+)/.match(element.inner_text)
|
74
85
|
account.available_balance = parse_currency(match[1])
|
75
86
|
end
|
76
87
|
end
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
88
|
+
end
|
89
|
+
|
90
|
+
# Get all the transactions
|
91
|
+
include_pending = true
|
92
|
+
begin
|
93
|
+
transactions += get_transactions_from_page(page, include_pending)
|
94
|
+
|
95
|
+
has_next_page = false
|
96
|
+
pagination_div = page.search('#pagination').first
|
97
|
+
if pagination_div # if #pagination isn't there, then there aren't any more pages
|
98
|
+
next_page_link = pagination_div.search('.prevnext').last
|
99
|
+
if next_page_link.inner_text.strip.downcase == 'next'
|
100
|
+
url = "#{next_page_link['href']}&#{@csrf}"
|
101
|
+
page = agent.get(url)
|
102
|
+
has_next_page = true
|
103
|
+
include_pending = false # The pending txns are included on every page, so don't get them again when we switch pages
|
104
|
+
end
|
91
105
|
end
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
106
|
+
end while has_next_page
|
107
|
+
|
108
|
+
transactions
|
109
|
+
end
|
110
|
+
|
111
|
+
def get_transactions_from_page(page, include_pending)
|
112
|
+
transactions = []
|
113
|
+
|
114
|
+
page.search('#RegisterCntBox .list_table tr').each do |row_element|
|
115
|
+
|
116
|
+
date_cell = row_element.search('.table_column_0').first
|
117
|
+
if date_cell
|
118
|
+
status_image = row_element.search('.table_column_3 img').first
|
119
|
+
status = status_image['alt'] == 'Cleared' ? :posted : :pending
|
120
|
+
next unless status == :posted || include_pending
|
121
|
+
|
96
122
|
transaction = Transaction.new
|
97
123
|
|
98
|
-
transaction.posted_at = Date.strptime(
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
124
|
+
transaction.posted_at = Date.strptime(date_cell.inner_text.strip, '%m/%d/%Y')
|
125
|
+
|
126
|
+
payee_cell = row_element.search('.table_column_2 .printdisplay .changeText').first || row_element.search('.table_column_2').first
|
127
|
+
transaction.payee = payee_cell.inner_text.strip
|
128
|
+
|
129
|
+
transaction.status = status
|
130
|
+
|
131
|
+
debit_amount_cell = row_element.search('.table_column_4').first
|
132
|
+
debit_amount = debit_amount_cell.inner_text.strip
|
133
|
+
unless debit_amount.empty?
|
134
|
+
transaction.amount = -parse_currency(debit_amount)
|
103
135
|
end
|
104
|
-
|
105
|
-
|
136
|
+
|
137
|
+
credit_amount_cell = row_element.search('.table_column_5').first
|
138
|
+
credit_amount = credit_amount_cell.inner_text.strip
|
139
|
+
unless credit_amount.empty?
|
140
|
+
transaction.amount = parse_currency(credit_amount)
|
106
141
|
end
|
107
|
-
|
142
|
+
|
108
143
|
transactions << transaction
|
109
144
|
end
|
145
|
+
|
110
146
|
end
|
111
|
-
|
147
|
+
|
112
148
|
transactions
|
113
149
|
end
|
114
|
-
|
150
|
+
|
115
151
|
private
|
116
|
-
|
152
|
+
|
117
153
|
def ensure_authenticated
|
118
|
-
|
119
|
-
# Check to see if already authenticated
|
120
|
-
page = agent.get('https://banking.zionsbank.com/ibuir/')
|
121
|
-
if page.body.include?("SessionTimeOutException")
|
122
|
-
|
123
|
-
raise InformationMissingError, "Please supply a username" unless self.username
|
124
|
-
raise InformationMissingError, "Please supply a password" unless self.password
|
125
|
-
|
126
|
-
# Enter the username
|
127
|
-
page = agent.get('https://www.zionsbank.com')
|
128
|
-
form = page.form('logonForm')
|
129
|
-
form.pm_fp = "version%3D1%26pm%5Ffpua%3Dmozilla%2F5%2E0%20%28windows%20nt%206%2E1%3B%20wow64%29%20applewebkit%2F535%2E19%20%28khtml%2C%20like%20gecko%29%20chrome%2F18%2E0%2E1025%2E162%20safari%2F535%2E19%7C5%2E0%20%28Windows%20NT%206%2E1%3B%20WOW64%29%20AppleWebKit%2F535%2E19%20%28KHTML%2C%20like%20Gecko%29%20Chrome%2F18%2E0%2E1025%2E162%20Safari%2F535%2E19%7CWin32%26pm%5Ffpsc%3D32%7C1920%7C1200%7C1200%26pm%5Ffpsw%3D%7Cqt1%7Cqt2%7Cqt3%7Cqt4%7Cqt5%7Cqt6%26pm%5Ffptz%3D%2D6%26pm%5Ffpln%3Dlang%3Den%2DUS%7Csyslang%3D%7Cuserlang%3D%26pm%5Ffpjv%3D1%26pm%5Ffpco%3D1"
|
130
|
-
form.publicCred1 = username
|
131
|
-
page = form.submit
|
132
|
-
|
133
|
-
# If the supplied username is incorrect, raise an exception
|
134
|
-
raise InformationMissingError, "Invalid username" if page.title == "Error Page"
|
135
|
-
|
136
|
-
# Go on to the next page
|
137
|
-
page = page.links.first.click
|
138
|
-
|
139
|
-
refresh = page.body.match /meta http-equiv="Refresh" content="0; url=([^"]+)/
|
140
|
-
if refresh
|
141
|
-
url = refresh[1]
|
142
|
-
page = agent.get("https://securentry.zionsbank.com#{url}")
|
143
|
-
end
|
144
|
-
|
145
|
-
# Skip the secret question if we are prompted for the password
|
146
|
-
unless page.body.include?("Site Validation and Password")
|
147
|
-
# Find the secret question
|
148
|
-
question = page.search('div.form_field')[2].css('div').inner_text
|
149
|
-
|
150
|
-
# If the answer to this question was not supplied, raise an exception
|
151
|
-
raise InformationMissingError, "Please answer the question, \"#{question}\"" unless secret_questions && secret_questions[question]
|
152
|
-
|
153
|
-
# Enter the answer to the secret question
|
154
|
-
form = page.forms.first
|
155
|
-
form["challengeEntry.answerText"] = secret_questions[question]
|
156
|
-
form.radiobutton_with(:value => 'false').check
|
157
|
-
submit_button = form.button_with(:name => '_eventId_submit')
|
158
|
-
page = form.submit(submit_button)
|
159
|
-
|
160
|
-
# If the supplied answer is incorrect, raise an exception
|
161
|
-
raise InformationMissingError, "\"#{secret_questions[question]}\" is not the correct answer to, \"#{question}\"" unless page.search('#errorComponent').empty?
|
162
|
-
end
|
163
154
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
155
|
+
# We no longer have a way to check to see if we're logged in or not... assume we're not.
|
156
|
+
|
157
|
+
raise InformationMissingError, "Please supply a username" unless self.username
|
158
|
+
raise InformationMissingError, "Please supply a password" unless self.password
|
159
|
+
|
160
|
+
# Enter the username
|
161
|
+
page = agent.get('https://www.zionsbank.com')
|
162
|
+
form = page.form('logonForm')
|
163
|
+
form.publicCred1 = self.username
|
164
|
+
form.privateCred1 = self.password
|
165
|
+
page = form.submit
|
166
|
+
|
167
|
+
# If the supplied username is incorrect, raise an exception
|
168
|
+
# In my tests, invalid username takes you to the password page and an invalid password takes you to the error page
|
169
|
+
raise InformationMissingError, "Invalid username/password" if page.title == "Password Page" || page.title == "Error Page"
|
170
|
+
|
171
|
+
# Go on to the next page
|
172
|
+
# It is something like this: https://banking.zionsbank.com/olb/retail/logon/mfa/sso?SAMLart=<bigLongKey>
|
173
|
+
page = page.links.first.click
|
174
|
+
|
175
|
+
#refresh = page.body.match /meta http-equiv="Refresh" content="0; url=([^"]+)/
|
176
|
+
#if refresh
|
177
|
+
# url = refresh[1]
|
178
|
+
# page = agent.get("https://securentry.zionsbank.com#{url}")
|
179
|
+
#end
|
180
|
+
|
181
|
+
# TODO: figure out how this is supposed to work now
|
182
|
+
# Skip the secret question if we are prompted for the password
|
183
|
+
#unless page.body.include?("Site Validation and Password")
|
184
|
+
# # Find the secret question
|
185
|
+
# question = page.search('div.form_field')[2].css('div').inner_text
|
186
|
+
#
|
187
|
+
# # If the answer to this question was not supplied, raise an exception
|
188
|
+
# raise InformationMissingError, "Please answer the question, \"#{question}\"" unless secret_questions && secret_questions[question]
|
189
|
+
#
|
190
|
+
# # Enter the answer to the secret question
|
191
|
+
# form = page.forms.first
|
192
|
+
# form["challengeEntry.answerText"] = secret_questions[question]
|
193
|
+
# form.radiobutton_with(:value => 'false').check
|
194
|
+
# submit_button = form.button_with(:name => '_eventId_submit')
|
195
|
+
# page = form.submit(submit_button)
|
196
|
+
#
|
197
|
+
# # If the supplied answer is incorrect, raise an exception
|
198
|
+
# raise InformationMissingError, "\"#{secret_questions[question]}\" is not the correct answer to, \"#{question}\"" unless page.search('#errorComponent').empty?
|
199
|
+
#end
|
200
|
+
|
201
|
+
if !page.uri.to_s.start_with?("https://banking.zionsbank.com/olb/retail/protected/home")
|
202
|
+
raise "Unknown URL reached. Try logging in manually through a browser."
|
181
203
|
end
|
182
|
-
|
204
|
+
|
205
|
+
token_name = page.search('#csrfTokenName').text
|
206
|
+
token_value = page.search('#csrfTokenValue').text
|
207
|
+
|
208
|
+
@csrf = "#{token_name}=#{token_value}"
|
209
|
+
|
183
210
|
true
|
184
211
|
end
|
185
|
-
|
212
|
+
|
186
213
|
end
|
187
214
|
end
|
188
215
|
end
|
data/lib/syrup/version.rb
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
include Institutions
|
4
|
+
|
5
|
+
describe BankAf, :bank_integration => true do
|
6
|
+
before(:each) do
|
7
|
+
@bank = BankAf.new
|
8
|
+
@bank.setup do |config|
|
9
|
+
config.username = ""
|
10
|
+
config.password = ""
|
11
|
+
config.secret_questions = {}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it "fetches one account"
|
16
|
+
it "fetches all accounts" do
|
17
|
+
accounts = @bank.fetch_accounts
|
18
|
+
accounts.each do |account|
|
19
|
+
puts "#{account.id} #{account.name}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "fetches transactions given a date range" do
|
24
|
+
account_id = 'Checking'
|
25
|
+
|
26
|
+
account = @bank.find_account_by_id(account_id)
|
27
|
+
account.instance_variable_get(:@prior_day_balance).should be_nil
|
28
|
+
account.instance_variable_get(:@current_balance).should be_nil
|
29
|
+
account.instance_variable_get(:@available_balance).should be_nil
|
30
|
+
|
31
|
+
@bank.fetch_transactions(account_id, Date.today - 30, Date.today)
|
32
|
+
|
33
|
+
puts account.prior_day_balance
|
34
|
+
puts account.current_balance
|
35
|
+
puts account.available_balance
|
36
|
+
|
37
|
+
#account.prior_day_balance.should_not be_nil
|
38
|
+
account.current_balance.should_not be_nil
|
39
|
+
account.available_balance.should_not be_nil
|
40
|
+
end
|
41
|
+
end
|
@@ -1,41 +1,41 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
include Institutions
|
4
|
-
|
5
|
-
describe ZionsBank, :bank_integration => true do
|
6
|
-
before(:each) do
|
7
|
-
@bank = ZionsBank.new
|
8
|
-
@bank.setup do |config|
|
9
|
-
config.username = ""
|
10
|
-
config.password = ""
|
11
|
-
config.secret_questions = {}
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
it "fetches one account"
|
16
|
-
it "fetches all accounts" do
|
17
|
-
accounts = @bank.fetch_accounts
|
18
|
-
accounts.each do |account|
|
19
|
-
puts "#{account.id} #{account.name}"
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
it "fetches transactions given a date range" do
|
24
|
-
account_id = 1
|
25
|
-
|
26
|
-
account = @bank.find_account_by_id(account_id)
|
27
|
-
account.instance_variable_get(:@prior_day_balance).should be_nil
|
28
|
-
account.instance_variable_get(:@current_balance).should be_nil
|
29
|
-
account.instance_variable_get(:@available_balance).should be_nil
|
30
|
-
|
31
|
-
@bank.fetch_transactions(account_id, Date.today - 30, Date.today)
|
32
|
-
|
33
|
-
puts account.prior_day_balance
|
34
|
-
puts account.current_balance
|
35
|
-
puts account.available_balance
|
36
|
-
|
37
|
-
account.prior_day_balance.should_not be_nil
|
38
|
-
account.current_balance.should_not be_nil
|
39
|
-
account.available_balance.should_not be_nil
|
40
|
-
end
|
41
|
-
end
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
include Institutions
|
4
|
+
|
5
|
+
describe ZionsBank, :bank_integration => true do
|
6
|
+
before(:each) do
|
7
|
+
@bank = ZionsBank.new
|
8
|
+
@bank.setup do |config|
|
9
|
+
config.username = ""
|
10
|
+
config.password = ""
|
11
|
+
config.secret_questions = {}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
#it "fetches one account"
|
16
|
+
it "fetches all accounts" do
|
17
|
+
accounts = @bank.fetch_accounts
|
18
|
+
accounts.each do |account|
|
19
|
+
puts "#{account.id} #{account.name}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "fetches transactions given a date range" do
|
24
|
+
account_id = "1|a"
|
25
|
+
|
26
|
+
account = @bank.find_account_by_id(account_id)
|
27
|
+
account.instance_variable_get(:@prior_day_balance).should be_nil
|
28
|
+
account.instance_variable_get(:@current_balance).should be_nil
|
29
|
+
account.instance_variable_get(:@available_balance).should be_nil
|
30
|
+
|
31
|
+
@bank.fetch_transactions(account_id, Date.today - 30, Date.today)
|
32
|
+
|
33
|
+
puts "Prior day balance: $#{account.prior_day_balance.to_f}"
|
34
|
+
puts "Current balance: $#{account.current_balance.to_f}"
|
35
|
+
puts "Available balance: $#{account.available_balance.to_f}"
|
36
|
+
|
37
|
+
account.prior_day_balance.should_not be_nil
|
38
|
+
account.current_balance.should_not be_nil
|
39
|
+
account.available_balance.should_not be_nil
|
40
|
+
end
|
41
|
+
end
|
data/syrup.gemspec
CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.add_dependency "multi_json", ">= 1.0.3"
|
17
17
|
|
18
18
|
s.add_development_dependency "rspec", ">= 2.6.0"
|
19
|
-
s.add_development_dependency "
|
19
|
+
s.add_development_dependency "debugger"
|
20
20
|
s.add_development_dependency "rake"
|
21
21
|
|
22
22
|
s.rubyforge_project = s.name
|
metadata
CHANGED
@@ -1,109 +1,103 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: syrup
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.14
|
5
5
|
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 0
|
9
|
-
- 13
|
10
|
-
version: 0.0.13
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Don Wilson
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
dependencies:
|
21
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2013-06-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
22
15
|
name: mechanize
|
23
|
-
|
24
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
25
17
|
none: false
|
26
|
-
requirements:
|
27
|
-
- -
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
hash: 23
|
30
|
-
segments:
|
31
|
-
- 1
|
32
|
-
- 0
|
33
|
-
- 0
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
34
21
|
version: 1.0.0
|
35
22
|
type: :runtime
|
36
|
-
version_requirements: *id001
|
37
|
-
- !ruby/object:Gem::Dependency
|
38
|
-
name: multi_json
|
39
23
|
prerelease: false
|
40
|
-
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.0.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: multi_json
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
41
33
|
none: false
|
42
|
-
requirements:
|
43
|
-
- -
|
44
|
-
- !ruby/object:Gem::Version
|
45
|
-
hash: 17
|
46
|
-
segments:
|
47
|
-
- 1
|
48
|
-
- 0
|
49
|
-
- 3
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
50
37
|
version: 1.0.3
|
51
38
|
type: :runtime
|
52
|
-
version_requirements: *id002
|
53
|
-
- !ruby/object:Gem::Dependency
|
54
|
-
name: rspec
|
55
39
|
prerelease: false
|
56
|
-
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.0.3
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
57
49
|
none: false
|
58
|
-
requirements:
|
59
|
-
- -
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
hash: 23
|
62
|
-
segments:
|
63
|
-
- 2
|
64
|
-
- 6
|
65
|
-
- 0
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
66
53
|
version: 2.6.0
|
67
54
|
type: :development
|
68
|
-
version_requirements: *id003
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: ruby-debug
|
71
55
|
prerelease: false
|
72
|
-
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.6.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: debugger
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
73
65
|
none: false
|
74
|
-
requirements:
|
75
|
-
- -
|
76
|
-
- !ruby/object:Gem::Version
|
77
|
-
|
78
|
-
segments:
|
79
|
-
- 0
|
80
|
-
version: "0"
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
81
70
|
type: :development
|
82
|
-
version_requirements: *id004
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: rake
|
85
71
|
prerelease: false
|
86
|
-
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
87
73
|
none: false
|
88
|
-
requirements:
|
89
|
-
- -
|
90
|
-
- !ruby/object:Gem::Version
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rake
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
95
86
|
type: :development
|
96
|
-
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
97
94
|
description: Simple account balance and transactions extractor by scraping bank websites.
|
98
|
-
email:
|
95
|
+
email:
|
99
96
|
- dontangg@gmail.com
|
100
97
|
executables: []
|
101
|
-
|
102
98
|
extensions: []
|
103
|
-
|
104
99
|
extra_rdoc_files: []
|
105
|
-
|
106
|
-
files:
|
100
|
+
files:
|
107
101
|
- .gitignore
|
108
102
|
- .rspec
|
109
103
|
- CHANGELOG.rdoc
|
@@ -115,6 +109,7 @@ files:
|
|
115
109
|
- lib/syrup.rb
|
116
110
|
- lib/syrup/account.rb
|
117
111
|
- lib/syrup/information_missing_error.rb
|
112
|
+
- lib/syrup/institutions/bank_af.rb
|
118
113
|
- lib/syrup/institutions/cacert.pem
|
119
114
|
- lib/syrup/institutions/institution_base.rb
|
120
115
|
- lib/syrup/institutions/uccu.rb
|
@@ -123,49 +118,41 @@ files:
|
|
123
118
|
- lib/syrup/version.rb
|
124
119
|
- spec/spec_helper.rb
|
125
120
|
- spec/syrup/account_spec.rb
|
121
|
+
- spec/syrup/institutions/bank_af_spec.rb
|
126
122
|
- spec/syrup/institutions/institution_base_spec.rb
|
127
123
|
- spec/syrup/institutions/uccu_spec.rb
|
128
124
|
- spec/syrup/institutions/zions_bank_spec.rb
|
129
125
|
- spec/syrup/syrup_spec.rb
|
130
126
|
- spec/syrup/transaction_spec.rb
|
131
127
|
- syrup.gemspec
|
132
|
-
has_rdoc: true
|
133
128
|
homepage: http://github.com/dontangg/syrup
|
134
129
|
licenses: []
|
135
|
-
|
136
130
|
post_install_message:
|
137
131
|
rdoc_options: []
|
138
|
-
|
139
|
-
require_paths:
|
132
|
+
require_paths:
|
140
133
|
- lib
|
141
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
134
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
142
135
|
none: false
|
143
|
-
requirements:
|
144
|
-
- -
|
145
|
-
- !ruby/object:Gem::Version
|
146
|
-
|
147
|
-
|
148
|
-
- 0
|
149
|
-
version: "0"
|
150
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ! '>='
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
141
|
none: false
|
152
|
-
requirements:
|
153
|
-
- -
|
154
|
-
- !ruby/object:Gem::Version
|
155
|
-
|
156
|
-
segments:
|
157
|
-
- 0
|
158
|
-
version: "0"
|
142
|
+
requirements:
|
143
|
+
- - ! '>='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
159
146
|
requirements: []
|
160
|
-
|
161
147
|
rubyforge_project: syrup
|
162
|
-
rubygems_version: 1.
|
148
|
+
rubygems_version: 1.8.23
|
163
149
|
signing_key:
|
164
150
|
specification_version: 3
|
165
151
|
summary: Simple account balance and transactions extractor.
|
166
|
-
test_files:
|
152
|
+
test_files:
|
167
153
|
- spec/spec_helper.rb
|
168
154
|
- spec/syrup/account_spec.rb
|
155
|
+
- spec/syrup/institutions/bank_af_spec.rb
|
169
156
|
- spec/syrup/institutions/institution_base_spec.rb
|
170
157
|
- spec/syrup/institutions/uccu_spec.rb
|
171
158
|
- spec/syrup/institutions/zions_bank_spec.rb
|