commerce-bank-client 1.0.0 → 1.1.0
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.tar.gz.sig +0 -0
- data/Manifest +1 -0
- data/Rakefile +1 -1
- data/commerce-bank-client.gemspec +3 -3
- data/lib/commerce-bank-client.rb +232 -0
- metadata +5 -3
- metadata.gz.sig +0 -0
data.tar.gz.sig
CHANGED
Binary file
|
data/Manifest
CHANGED
data/Rakefile
CHANGED
@@ -2,7 +2,7 @@ require 'rubygems'
|
|
2
2
|
require 'rake'
|
3
3
|
require 'echoe'
|
4
4
|
|
5
|
-
Echoe.new("commerce-bank-client", "1.
|
5
|
+
Echoe.new("commerce-bank-client", "1.1.0") do |p|
|
6
6
|
|
7
7
|
p.description = "An interface to the Commerce Bank website (https://banking.commercebank.com)."
|
8
8
|
p.url = "http://github.com/alexmchale/commerce-bank-client"
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{commerce-bank-client}
|
5
|
-
s.version = "1.
|
5
|
+
s.version = "1.1.0"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Alex McHale"]
|
@@ -10,8 +10,8 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.date = %q{2010-09-02}
|
11
11
|
s.description = %q{An interface to the Commerce Bank website (https://banking.commercebank.com).}
|
12
12
|
s.email = %q{alexmchale@gmail.com}
|
13
|
-
s.extra_rdoc_files = ["README.markdown", "lib/commercebank.rb", "lib/commercebank/monkey.rb"]
|
14
|
-
s.files = ["Manifest", "README.markdown", "Rakefile", "lib/commercebank.rb", "lib/commercebank/monkey.rb", "test/commerce_bank_client_test.rb", "test/monkeypatch_test.rb", "test/test_helper.rb", "commerce-bank-client.gemspec"]
|
13
|
+
s.extra_rdoc_files = ["README.markdown", "lib/commerce-bank-client.rb", "lib/commercebank.rb", "lib/commercebank/monkey.rb"]
|
14
|
+
s.files = ["Manifest", "README.markdown", "Rakefile", "lib/commerce-bank-client.rb", "lib/commercebank.rb", "lib/commercebank/monkey.rb", "test/commerce_bank_client_test.rb", "test/monkeypatch_test.rb", "test/test_helper.rb", "commerce-bank-client.gemspec"]
|
15
15
|
s.homepage = %q{http://github.com/alexmchale/commerce-bank-client}
|
16
16
|
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Commerce-bank-client", "--main", "README.markdown"]
|
17
17
|
s.require_paths = ["lib"]
|
@@ -0,0 +1,232 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'net/http'
|
3
|
+
require 'net/https'
|
4
|
+
require 'hpricot'
|
5
|
+
require 'andand'
|
6
|
+
require 'cgi'
|
7
|
+
require 'yaml'
|
8
|
+
require 'time'
|
9
|
+
require 'date'
|
10
|
+
require 'json'
|
11
|
+
require 'htmlentities'
|
12
|
+
require 'gmail'
|
13
|
+
require 'appconfig'
|
14
|
+
require 'commercebank/monkey.rb'
|
15
|
+
|
16
|
+
class Fixnum
|
17
|
+
def days; 24 * hours; end
|
18
|
+
def hours; 60 * minutes; end
|
19
|
+
def minutes; 60 * seconds; end
|
20
|
+
def seconds; self; end
|
21
|
+
end
|
22
|
+
|
23
|
+
class WebClient
|
24
|
+
attr_reader :fields, :cookies
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@cookies = Hash.new
|
28
|
+
@http = Net::HTTP.new('banking.commercebank.com', 443)
|
29
|
+
@http.use_ssl = true
|
30
|
+
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
31
|
+
end
|
32
|
+
|
33
|
+
def get(path, form = nil)
|
34
|
+
response = @http.get(path, header)
|
35
|
+
add_cookies(response)
|
36
|
+
@fields = (form && get_form(response.body, form)) || Hash.new
|
37
|
+
response
|
38
|
+
end
|
39
|
+
|
40
|
+
def post(path, form = nil)
|
41
|
+
response = @http.post(path, @fields.to_url, header)
|
42
|
+
add_cookies(response)
|
43
|
+
@fields = (form && get_form(response.body, form)) || Hash.new
|
44
|
+
response
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def header
|
50
|
+
{ 'Cookie' => @cookies.to_cookie }
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_form(body, name)
|
54
|
+
Hpricot.buffer_size = 262144
|
55
|
+
doc = Hpricot.parse(body)
|
56
|
+
form = (doc/"##{name}").first
|
57
|
+
raise "could not find form #{name}" unless form
|
58
|
+
fields = Hash[*((form/"input").map {|e| [ e.attributes['name'], e.attributes['value'] ]}.flatten)]
|
59
|
+
fields['TestJavaScript'] = 'OK'
|
60
|
+
fields
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_cookies(response)
|
64
|
+
CGI::Cookie.parse(response.header['set-cookie']).each do |key, value|
|
65
|
+
@cookies[key] = value.first
|
66
|
+
end
|
67
|
+
|
68
|
+
@cookies.delete 'path'
|
69
|
+
@cookies.delete 'expires'
|
70
|
+
|
71
|
+
self
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class CommerceBank
|
76
|
+
attr_reader :pending, :register, :current, :available
|
77
|
+
|
78
|
+
def initialize
|
79
|
+
@config = AppConfig.new('~/.commerce.yaml')
|
80
|
+
|
81
|
+
client = WebClient.new
|
82
|
+
|
83
|
+
client.get('/')
|
84
|
+
|
85
|
+
client.get('/CBI/login.aspx', 'MAINFORM')
|
86
|
+
|
87
|
+
client.fields['txtUserID'] = @config.get('username')
|
88
|
+
response = client.post('/CBI/login.aspx', 'MAINFORM')
|
89
|
+
|
90
|
+
# If a question was asked, answer it then get the password page.
|
91
|
+
question = response.body.scan(/Your security question: (.*?)<\/td>/i).first.andand.first
|
92
|
+
if question
|
93
|
+
client.fields['txtChallengeAnswer'] = @config.get(question)
|
94
|
+
client.fields['saveComputer'] = 'rdoBindDeviceNo'
|
95
|
+
response = client.post('/CBI/login.aspx', 'MAINFORM')
|
96
|
+
end
|
97
|
+
|
98
|
+
raise "could not reach the password page" unless client.fields['__EVENTTARGET'] == 'btnLogin'
|
99
|
+
|
100
|
+
client.fields['txtPassword'] = @config.get('password')
|
101
|
+
response = client.post('/CBI/login.aspx')
|
102
|
+
|
103
|
+
response = client.get('/CBI/Accounts/CBI/Activity.aspx', 'MAINFORM')
|
104
|
+
(@current, @available) = parse_balance(response.body)
|
105
|
+
@pending = parse_pending(response.body)
|
106
|
+
|
107
|
+
client.fields['Anthem_UpdatePage'] = 'true'
|
108
|
+
client.fields['txtFilterFromDate:textBox'] = (Time.now - 30.days).strftime('%m/%d/%Y')
|
109
|
+
client.fields['txtFilterToDate:textBox'] = Time.now.strftime('%m/%d/%Y')
|
110
|
+
response = client.post('/CBI/Accounts/CBI/Activity.aspx?Anthem_CallBack=true')
|
111
|
+
|
112
|
+
raw_data = JSON.parse(response.body)
|
113
|
+
@register = parse_register(raw_data['controls']['pnlPosted'])
|
114
|
+
|
115
|
+
@register.each do |entry|
|
116
|
+
entry[:images].map! do |image_url|
|
117
|
+
url = "https://banking.commercebank.com/CBI/Accounts/CBI/#{image_url[:url]}"
|
118
|
+
client.get(url).andand.body
|
119
|
+
end
|
120
|
+
entry[:images].compact!
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def daily_summary
|
125
|
+
today, yesterday, this_week, last_week = [], [], [], []
|
126
|
+
|
127
|
+
register.each do |entry|
|
128
|
+
if entry[:date] == Date.today then today << entry
|
129
|
+
elsif entry[:date] == (Date.today - 1) then yesterday << entry
|
130
|
+
elsif entry[:date] >= Date.today.last_sunday then this_week << entry
|
131
|
+
elsif entry[:date] >= (Date.today.last_sunday - 1).last_sunday then last_week << entry
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
yield 'Pending', @pending
|
136
|
+
yield 'Today', today
|
137
|
+
yield 'Yesterday', yesterday
|
138
|
+
yield 'This Week', this_week
|
139
|
+
yield 'Last Week', last_week
|
140
|
+
end
|
141
|
+
|
142
|
+
def monthly_summary(day_in_month = (Date.today - Date.today.day))
|
143
|
+
first_of_month = day_in_month - day_in_month.day + 1
|
144
|
+
last_of_month = first_of_month + day_in_month.days_in_month - 1
|
145
|
+
|
146
|
+
entries = register.find_all {|entry| entry[:date] >= first_of_month && entry[:date] <= last_of_month}
|
147
|
+
|
148
|
+
yield day_in_month.strftime('%B'), entries
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
def parse_balance(body)
|
154
|
+
Hpricot.buffer_size = 262144
|
155
|
+
doc = Hpricot.parse(body)
|
156
|
+
summaryRows = doc/"table.summaryTable"/"tr"
|
157
|
+
current = (summaryRows[3]/"td")[1].inner_html.to_cents
|
158
|
+
available = (summaryRows[4]/"td")[1].inner_html.to_cents
|
159
|
+
[current, available]
|
160
|
+
end
|
161
|
+
|
162
|
+
def parse_pending(body)
|
163
|
+
Hpricot.buffer_size = 262144
|
164
|
+
doc = Hpricot.parse(body)
|
165
|
+
coder = HTMLEntities.new
|
166
|
+
|
167
|
+
(doc/"#grdMemoPosted"/"tr").map do |e|
|
168
|
+
next nil unless (e['class'] == 'item' || e['class'] == 'alternatingItem')
|
169
|
+
|
170
|
+
values = (e/"td").map {|e1| coder.decode(e1.inner_html.strip)}
|
171
|
+
|
172
|
+
debit = values[2].to_cents
|
173
|
+
credit = values[3].to_cents
|
174
|
+
delta = credit - debit
|
175
|
+
|
176
|
+
{ :date => parse_date(values[0]),
|
177
|
+
:destination => values[1],
|
178
|
+
:delta => delta,
|
179
|
+
:debit => debit,
|
180
|
+
:credit => credit }
|
181
|
+
end.compact
|
182
|
+
end
|
183
|
+
|
184
|
+
def parse_register(body)
|
185
|
+
Hpricot.buffer_size = 262144
|
186
|
+
doc = Hpricot.parse(body)
|
187
|
+
coder = HTMLEntities.new
|
188
|
+
(doc/"#grdHistory"/"tr").map do |e|
|
189
|
+
next nil unless [ 'item', 'alternatingitem' ].include? e['class'].to_s.downcase
|
190
|
+
|
191
|
+
anchor = e.at("a")
|
192
|
+
values = (e/"td").map {|e1| e1.inner_html}
|
193
|
+
date = parse_date(values[0])
|
194
|
+
check = values[1].strip
|
195
|
+
debit = values[3].to_cents
|
196
|
+
credit = values[4].to_cents
|
197
|
+
delta = credit - debit
|
198
|
+
total = values[5].scan(/\$[\d,]+\.\d\d/).first.to_cents
|
199
|
+
|
200
|
+
images = (e/"a").find_all do |e1|
|
201
|
+
e1['target'].to_s.downcase == 'checkimage'
|
202
|
+
end.map do |e1|
|
203
|
+
{ :url => e1['href'], :title => e1.inner_html.strip }
|
204
|
+
end
|
205
|
+
|
206
|
+
{
|
207
|
+
:destination => coder.decode(anchor.inner_html.strip),
|
208
|
+
:url => anchor['href'],
|
209
|
+
:date => date,
|
210
|
+
:check => check,
|
211
|
+
:images => images,
|
212
|
+
:delta => delta,
|
213
|
+
:debit => debit,
|
214
|
+
:credit => credit,
|
215
|
+
:total => total
|
216
|
+
}
|
217
|
+
end.compact
|
218
|
+
end
|
219
|
+
|
220
|
+
def parse_date(date_string)
|
221
|
+
(month, day, year) = date_string.scan(/(\d+)\/(\d+)\/(\d+)/).first
|
222
|
+
return nil unless month && day && year
|
223
|
+
|
224
|
+
month = month.to_i
|
225
|
+
day = day.to_i
|
226
|
+
year = year.to_i
|
227
|
+
year += 2000 if year < 100
|
228
|
+
|
229
|
+
Date.parse("%04d-%02d-%02d" % [ year, month, day ])
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: commerce-bank-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 19
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 1.0.0
|
10
|
+
version: 1.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Alex McHale
|
@@ -48,12 +48,14 @@ extensions: []
|
|
48
48
|
|
49
49
|
extra_rdoc_files:
|
50
50
|
- README.markdown
|
51
|
+
- lib/commerce-bank-client.rb
|
51
52
|
- lib/commercebank.rb
|
52
53
|
- lib/commercebank/monkey.rb
|
53
54
|
files:
|
54
55
|
- Manifest
|
55
56
|
- README.markdown
|
56
57
|
- Rakefile
|
58
|
+
- lib/commerce-bank-client.rb
|
57
59
|
- lib/commercebank.rb
|
58
60
|
- lib/commercebank/monkey.rb
|
59
61
|
- test/commerce_bank_client_test.rb
|
metadata.gz.sig
CHANGED
Binary file
|