banks 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/banks.rb +11 -5
- data/lib/banks/account.rb +34 -0
- data/lib/banks/bank.rb +10 -0
- data/lib/banks/bofa.rb +119 -0
- data/lib/banks/transaction.rb +143 -0
- data/lib/banks/utils.rb +29 -0
- metadata +64 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 385c82b6a55091c4d6071ab61b593505b8fb7dc2
|
4
|
+
data.tar.gz: 3861b449f0c09a340418c58c4bbebb9736670f72
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0cb51c6eb12dbd39aec9a9ecb8de162f4e6bcfda23c1536cd745ff03007448a15708687338ccf0e89591d2b314fde5e7d1dc32aecf12cd30028769b5195a892d
|
7
|
+
data.tar.gz: 00aba1b8e44303b17340a16759295f5960f7cceb2061caf6c28346d9a1bda1525e79250c213f1ee5f19a4b236b4b3b21e9ff88ecb1c1fe5c2c260f517a793f3c
|
data/lib/banks.rb
CHANGED
@@ -1,5 +1,11 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
require "watir-webdriver"
|
2
|
+
require "nokogiri"
|
3
|
+
require "json"
|
4
|
+
require "date"
|
5
|
+
require "active_support/all"
|
6
|
+
|
7
|
+
require "banks/account"
|
8
|
+
require "banks/bank"
|
9
|
+
require "banks/bofa"
|
10
|
+
require "banks/transaction"
|
11
|
+
require "banks/utils"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Banks
|
2
|
+
class Account
|
3
|
+
attr_accessor :name, :type, :balance, :transactions, :link
|
4
|
+
|
5
|
+
# Types
|
6
|
+
CREDIT = "Credit"
|
7
|
+
DEBIT = "Debit"
|
8
|
+
UNKNOWN = "Unknown"
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
transactions.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
# accounts - Array of accounts.
|
15
|
+
def merge_accounts (accounts)
|
16
|
+
@transactions = TransactionArray.new
|
17
|
+
accounts.each do |account|
|
18
|
+
if account.type == CREDIT
|
19
|
+
convert_to_debit account
|
20
|
+
end
|
21
|
+
@transactions.merge_array account.transactions
|
22
|
+
end
|
23
|
+
@type = DEBIT
|
24
|
+
transactions.sort_by_date
|
25
|
+
end
|
26
|
+
|
27
|
+
def convert_to_debit account
|
28
|
+
account.type = DEBIT
|
29
|
+
account.transactions.each do|transaction|
|
30
|
+
transaction.amount = transaction.amount*-1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/banks/bank.rb
ADDED
data/lib/banks/bofa.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
module Banks
|
2
|
+
# Note: If you are running the script for the first time, you need to go online
|
3
|
+
# and validate your computer by answering security questions.
|
4
|
+
# TODO: Ask the user to enter security questions.
|
5
|
+
class BankOfAmerica < Bank
|
6
|
+
# Returns all accounts with all transactions.
|
7
|
+
# Currently supports Credit and Debit accounts.
|
8
|
+
def get_data (user, password)
|
9
|
+
# Connect
|
10
|
+
browser = ::Watir::Browser.new #:"phantomjs"
|
11
|
+
browser.goto 'http://bankofamerica.com'
|
12
|
+
browser.text_field(:id => 'onlineId1').set user
|
13
|
+
browser.text_field(:id => 'passcode1').set password
|
14
|
+
browser.button(:id => 'hp-sign-in-btn').click
|
15
|
+
|
16
|
+
# Wait until logged in.
|
17
|
+
# Watir wait did not work on Windows, so we do a hack.
|
18
|
+
# http://watirwebdriver.com/waiting/
|
19
|
+
# http://stackoverflow.com/questions/3504322/watir-webdriver-wait-for-page-load
|
20
|
+
until browser.ul(:class=>"AccountItems").exists? do sleep 1 end
|
21
|
+
|
22
|
+
# Get accounts
|
23
|
+
accounts = get_accounts browser.html
|
24
|
+
|
25
|
+
accounts.each do |account|
|
26
|
+
browser.goto "https://secure.bankofamerica.com" + account.link
|
27
|
+
transactions = nil
|
28
|
+
if account.type == Account::DEBIT
|
29
|
+
transactions = get_debit_transactions browser.html
|
30
|
+
elsif account.type == Account::CREDIT
|
31
|
+
transactions = get_credit_transactions browser
|
32
|
+
end
|
33
|
+
account.transactions = transactions
|
34
|
+
end
|
35
|
+
|
36
|
+
# Clean-up
|
37
|
+
browser.close
|
38
|
+
|
39
|
+
# Return accounts.
|
40
|
+
accounts
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
# Returns date object based on date_string. Today's date if string is nil or
|
45
|
+
# whitespace
|
46
|
+
def to_date date_string
|
47
|
+
result = nil
|
48
|
+
date_string.strip!
|
49
|
+
if date_string.nil? || date_string.empty? || date_string.eql?("Pending")
|
50
|
+
result = Date.today
|
51
|
+
elsif
|
52
|
+
result = Date.strptime(date_string, '%m/%d/%Y')
|
53
|
+
end
|
54
|
+
result
|
55
|
+
end
|
56
|
+
|
57
|
+
# Pass the browser and return an array with accounts.
|
58
|
+
def get_accounts(html)
|
59
|
+
accounts = Array.new
|
60
|
+
html_doc = ::Nokogiri::HTML(html)
|
61
|
+
html_doc.css('ul.AccountItems li').each do |account_li|
|
62
|
+
account = Account.new
|
63
|
+
account.name = account_li.css('span.AccountName a').text.strip
|
64
|
+
account.link = account_li.css('span.AccountName a')[0]['href']
|
65
|
+
account.balance = account_li.css('span.balanceValue').text.strip
|
66
|
+
|
67
|
+
account_type_text = account_li.css('div.AccountItem')[0]['class']
|
68
|
+
if account_type_text.include? "AccountItemDeposit"
|
69
|
+
account.type = Account::DEBIT
|
70
|
+
elsif account_type_text.include? "AccountItemCreditCard"
|
71
|
+
account.type = Account::CREDIT
|
72
|
+
else
|
73
|
+
account.type = Account::UNKNOWN
|
74
|
+
end
|
75
|
+
|
76
|
+
accounts << account
|
77
|
+
end
|
78
|
+
|
79
|
+
return accounts
|
80
|
+
end
|
81
|
+
|
82
|
+
def get_debit_transactions(html)
|
83
|
+
transactions = TransactionArray.new
|
84
|
+
html_doc = ::Nokogiri::HTML(html)
|
85
|
+
|
86
|
+
html_doc.css('table.transaction-records tbody tr.record').each do |record_tr|
|
87
|
+
t = Transaction.new
|
88
|
+
t.date = to_date(record_tr.css('td.date-action span:not([class])').text.strip)
|
89
|
+
t.description = record_tr.css('td.description span.transTitleForEditDesc').text.strip
|
90
|
+
t.amount = convert_money(record_tr.css('td.amount').text.strip)
|
91
|
+
t.type = record_tr.css('td.type')[0]['title']
|
92
|
+
transactions << t
|
93
|
+
end
|
94
|
+
|
95
|
+
return transactions
|
96
|
+
end
|
97
|
+
|
98
|
+
def get_credit_transactions(browser)
|
99
|
+
transactions = TransactionArray.new
|
100
|
+
html_doc = ::Nokogiri::HTML(browser.html)
|
101
|
+
|
102
|
+
# Navigate through each statement.
|
103
|
+
statement_quantity = html_doc.css('select#goto_select_trans_top option').length
|
104
|
+
statement_quantity.times do |i|
|
105
|
+
browser.select(:id => 'goto_select_trans_top').options[i].select
|
106
|
+
html_doc = ::Nokogiri::HTML(browser.html)
|
107
|
+
html_doc.css('table#transactions tbody tr').each do |record_tr|
|
108
|
+
t = Transaction.new
|
109
|
+
t.date = to_date(record_tr.css('td.trans-date-cell').text.strip)
|
110
|
+
t.description = record_tr.css('td.trans-desc-cell a.expand-trans-from-desc')[0].children[2].text.strip
|
111
|
+
t.amount = convert_money(record_tr.css('td.trans-amount-cell').text.strip)
|
112
|
+
t.type = record_tr.css('td.trans-type-cell div')[0]['title']
|
113
|
+
transactions << t
|
114
|
+
end
|
115
|
+
end
|
116
|
+
return transactions
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Banks
|
2
|
+
class Transaction
|
3
|
+
attr_accessor :date, :description, :amount, :type, :category
|
4
|
+
def to_s
|
5
|
+
return date.to_s + " " + category.to_s + " " + Banks.amount_to_s(amount) + " " + type + " " + description
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class TransactionArray < Array
|
10
|
+
def total
|
11
|
+
total = 0
|
12
|
+
each do |transaction|
|
13
|
+
total = total + transaction.amount
|
14
|
+
end
|
15
|
+
Banks.amount_to_s(total)
|
16
|
+
end
|
17
|
+
|
18
|
+
def total_positive
|
19
|
+
total = 0
|
20
|
+
each do |transaction|
|
21
|
+
total = total + transaction.amount unless transaction.amount < 0
|
22
|
+
end
|
23
|
+
Banks.amount_to_s(total)
|
24
|
+
end
|
25
|
+
|
26
|
+
def total_negative
|
27
|
+
total = 0
|
28
|
+
each do |transaction|
|
29
|
+
total = total + transaction.amount unless transaction.amount > 0
|
30
|
+
end
|
31
|
+
Banks.amount_to_s(total)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sorts transactions by date, from newer to older.
|
35
|
+
def sort_by_date
|
36
|
+
sort! {|x, y| y.date <=> x.date}
|
37
|
+
end
|
38
|
+
|
39
|
+
# Sort by amount. Negative to positive.
|
40
|
+
def sort_by_amount
|
41
|
+
sort! {|x, y| x.amount <=> y.amount}
|
42
|
+
end
|
43
|
+
|
44
|
+
# Sort by category.
|
45
|
+
def sort_by_category
|
46
|
+
sort! {|x, y| x.category <=> y.category}
|
47
|
+
end
|
48
|
+
|
49
|
+
# Remove transactions considered transfers to own accounts.
|
50
|
+
def remove_transfers
|
51
|
+
result = TransactionArray.new
|
52
|
+
each do |transaction|
|
53
|
+
should_add = true
|
54
|
+
|
55
|
+
# Currently unavailable.
|
56
|
+
puts "Removing transaction. " + transaction.to_s unless should_add == true
|
57
|
+
if should_add then result << transaction end
|
58
|
+
end
|
59
|
+
return result
|
60
|
+
end
|
61
|
+
|
62
|
+
# Return transactions within a date range.
|
63
|
+
def by_date (begin_date, end_date)
|
64
|
+
result = TransactionArray.new
|
65
|
+
|
66
|
+
# Filter all less than end date.
|
67
|
+
temp_array = TransactionArray.new
|
68
|
+
each do |transaction|
|
69
|
+
if transaction.date <= end_date then temp_array << transaction end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Filter all greater than begin date.
|
73
|
+
temp_array.each do |transaction|
|
74
|
+
if transaction.date >= begin_date then result << transaction end
|
75
|
+
end
|
76
|
+
|
77
|
+
return result
|
78
|
+
end
|
79
|
+
|
80
|
+
# Return current month transactions.
|
81
|
+
def current_month
|
82
|
+
return previous_month 0
|
83
|
+
end
|
84
|
+
|
85
|
+
# Return last month transactions.
|
86
|
+
def last_month
|
87
|
+
return previous_month 1
|
88
|
+
end
|
89
|
+
|
90
|
+
# months_back - how many months to go back from current date.
|
91
|
+
def previous_month(months_back)
|
92
|
+
year = Date.today.year
|
93
|
+
month = Date.today.month
|
94
|
+
|
95
|
+
begin_date = Date.civil(year, month, 1) - months_back.month
|
96
|
+
end_date = begin_date.end_of_month
|
97
|
+
|
98
|
+
return by_date(begin_date, end_date)
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_s
|
102
|
+
result = ""
|
103
|
+
each do |transaction|
|
104
|
+
result = result + transaction.to_s + "\n"
|
105
|
+
end
|
106
|
+
result
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_s_by_cat
|
110
|
+
result = ""
|
111
|
+
current_cat = nil
|
112
|
+
total = 0
|
113
|
+
last_transaction = nil
|
114
|
+
each do |transaction|
|
115
|
+
if !current_cat.nil? && current_cat != transaction.category
|
116
|
+
result = result + "Total for category #{current_cat}: " + Banks.amount_to_s(total) + "\n\n"
|
117
|
+
total = 0
|
118
|
+
end
|
119
|
+
result = result + transaction.to_s + "\n"
|
120
|
+
total = total + transaction.amount
|
121
|
+
current_cat = transaction.category
|
122
|
+
last_transaction = transaction
|
123
|
+
end
|
124
|
+
if !last_transaction.nil? then
|
125
|
+
result = result + "Total for category #{current_cat}: " + Banks.amount_to_s(total) + "\n\n"
|
126
|
+
total = 0
|
127
|
+
end
|
128
|
+
result
|
129
|
+
end
|
130
|
+
|
131
|
+
# Merge array
|
132
|
+
def merge_array(merging_array)
|
133
|
+
merging_array.each do |transaction|
|
134
|
+
self << transaction
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def << (transaction)
|
139
|
+
super
|
140
|
+
#transaction.category = (CategoryPolicy.categorize transaction.description).upcase
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
data/lib/banks/utils.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Banks
|
2
|
+
def Banks.amount_to_s amount
|
3
|
+
amount < 0 ? positive = false : positive = true
|
4
|
+
amount_string = amount.to_s
|
5
|
+
amount_string.sub!("-","")
|
6
|
+
amount_string = amount_string.rjust(3, '0')
|
7
|
+
amount_string.insert(-3, ".")
|
8
|
+
amount_string.insert(0, "-") unless positive
|
9
|
+
amount_string
|
10
|
+
end
|
11
|
+
|
12
|
+
class CategoryPolicy
|
13
|
+
@@categories = nil
|
14
|
+
def self.categorize description
|
15
|
+
# Lazy read.
|
16
|
+
if @@categories.nil? then
|
17
|
+
data = IO.read "categories.json"
|
18
|
+
@@categories = JSON.parse data
|
19
|
+
end
|
20
|
+
|
21
|
+
@@categories["categories"].each do |category, payment_types|
|
22
|
+
payment_types.each do |value|
|
23
|
+
if description.upcase.include? value.upcase then return category end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
return "Uncategorized"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,71 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: banks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jesus Herrera
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-06-
|
12
|
-
dependencies:
|
11
|
+
date: 2016-06-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: watir-webdriver
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.9'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: nokogiri
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: json
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.8'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.8'
|
13
69
|
description: Get transaction information from financial institutions
|
14
70
|
email: chuy.max@gmail.com
|
15
71
|
executables: []
|
@@ -17,6 +73,11 @@ extensions: []
|
|
17
73
|
extra_rdoc_files: []
|
18
74
|
files:
|
19
75
|
- lib/banks.rb
|
76
|
+
- lib/banks/account.rb
|
77
|
+
- lib/banks/bank.rb
|
78
|
+
- lib/banks/bofa.rb
|
79
|
+
- lib/banks/transaction.rb
|
80
|
+
- lib/banks/utils.rb
|
20
81
|
homepage: http://rubygems.org/gems/banks
|
21
82
|
licenses:
|
22
83
|
- MIT
|