fine_ants 1.8.2 → 1.9.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.
- checksums.yaml +5 -5
- data/Gemfile +1 -1
- data/Rakefile +1 -1
- data/fine_ants.gemspec +16 -17
- data/lib/fine_ants/adapters/amex.rb +25 -25
- data/lib/fine_ants/adapters/betterment.rb +28 -28
- data/lib/fine_ants/adapters/chase.rb +11 -12
- data/lib/fine_ants/adapters/etrade.rb +11 -14
- data/lib/fine_ants/adapters/pnc.rb +16 -19
- data/lib/fine_ants/adapters/purduefed.rb +5 -5
- data/lib/fine_ants/adapters/schwab.rb +57 -0
- data/lib/fine_ants/adapters/simple.rb +14 -14
- data/lib/fine_ants/adapters/simple_bancorp.rb +11 -11
- data/lib/fine_ants/adapters/strs.rb +11 -16
- data/lib/fine_ants/adapters/target.rb +11 -12
- data/lib/fine_ants/adapters/ufb_direct.rb +61 -0
- data/lib/fine_ants/adapters/vanguard.rb +16 -16
- data/lib/fine_ants/adapters/zillow.rb +20 -11
- data/lib/fine_ants/adapters.rb +2 -2
- data/lib/fine_ants/capybara_setup.rb +2 -2
- data/lib/fine_ants/login_failed_error.rb +0 -1
- data/lib/fine_ants/runner.rb +2 -4
- data/lib/fine_ants/version.rb +1 -1
- data/lib/fine_ants.rb +4 -4
- metadata +34 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: '09710593f2fe74e7d9eceabd20925d0cf6adac747eb079d66e6ee73fc8b55843'
|
4
|
+
data.tar.gz: aab4bfde4086f433632744ad46820f776f99e59778ed5784ac7217a994b7f2b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1c16b6f083f70009ed80b043dbc1cf9e21519ee58c93986ff8dae68a67dbb7c94f747b79858da9e82e48366701de2d5e5079bf9062f4ea0876c323ebd0c654d
|
7
|
+
data.tar.gz: 1e852b45ea7e269b64f1b1edfda0b3ff29f8e6aa32767000ead02838995a36112012aeb2f6102a63607e1639a7b5870875736ad69e35352e5e639f5aa4f0d5af
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
-
|
2
|
+
require "standard/rake"
|
data/fine_ants.gemspec
CHANGED
@@ -1,27 +1,26 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
3
|
+
require "fine_ants/version"
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name
|
8
|
-
spec.version
|
9
|
-
spec.authors
|
10
|
-
spec.email
|
6
|
+
spec.name = "fine_ants"
|
7
|
+
spec.version = FineAnts::VERSION
|
8
|
+
spec.authors = ["Justin Searls"]
|
9
|
+
spec.email = ["searls@gmail.com"]
|
11
10
|
|
12
|
-
spec.summary
|
13
|
-
spec.
|
14
|
-
spec.homepage = "https://github.com/searls/fine_ants"
|
11
|
+
spec.summary = "Opens your browser and finds your bank account status."
|
12
|
+
spec.homepage = "https://github.com/searls/fine_ants"
|
15
13
|
|
16
|
-
spec.files
|
17
|
-
spec.bindir
|
18
|
-
spec.executables
|
14
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
15
|
+
spec.bindir = "exe"
|
16
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
17
|
spec.require_paths = ["lib"]
|
20
18
|
|
21
|
-
spec.add_dependency "capybara", "~>
|
22
|
-
spec.add_dependency "selenium-webdriver", "~>
|
19
|
+
spec.add_dependency "capybara", "~> 3.26"
|
20
|
+
spec.add_dependency "selenium-webdriver", "~> 3.142.0"
|
23
21
|
|
24
|
-
spec.add_development_dependency "
|
25
|
-
spec.add_development_dependency "rake", "~>
|
22
|
+
spec.add_development_dependency "bundle"
|
23
|
+
spec.add_development_dependency "rake", "~> 12.3"
|
26
24
|
spec.add_development_dependency "pry"
|
25
|
+
spec.add_development_dependency "standard"
|
27
26
|
end
|
@@ -10,48 +10,48 @@ module FineAnts
|
|
10
10
|
|
11
11
|
def login
|
12
12
|
visit "https://www.americanexpress.com"
|
13
|
-
|
14
|
-
|
15
|
-
fill_in "
|
16
|
-
|
13
|
+
form_css = find("form")[:id] == "ssoform" ? "#ssoform" : ".eliloMain"
|
14
|
+
within form_css do
|
15
|
+
fill_in "User ID", with: @user
|
16
|
+
fill_in "Password", with: @password
|
17
|
+
click_thing "Log In"
|
17
18
|
end
|
18
19
|
verify_login!
|
19
20
|
end
|
20
21
|
|
21
22
|
def download
|
22
23
|
visit "https://global.americanexpress.com/accounts"
|
24
|
+
find(".card-block")
|
23
25
|
all(".card-block > div").map do |account|
|
24
|
-
name = account.find(
|
25
|
-
owed = account.
|
26
|
+
name = account.find(".card > .pad > .heading-3").text
|
27
|
+
owed = account.text.include?("Total Balance")
|
26
28
|
{
|
27
|
-
:
|
28
|
-
:
|
29
|
-
:
|
30
|
-
:
|
31
|
-
:
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
adapter: :amex,
|
30
|
+
user: @user,
|
31
|
+
id: name,
|
32
|
+
name: name,
|
33
|
+
amount: -1 * BigDecimal(if owed
|
34
|
+
account.all("table td:nth-child(2) span").first.text.gsub(/[\$,]/, "")
|
35
|
+
else
|
36
|
+
"0"
|
37
|
+
end)
|
36
38
|
}
|
37
|
-
end.tap do
|
38
|
-
page.driver.go_back
|
39
|
-
click_button "Log Out"
|
40
39
|
end
|
41
40
|
end
|
42
41
|
|
43
|
-
|
42
|
+
private
|
44
43
|
|
45
44
|
def verify_login!
|
46
|
-
|
45
|
+
page.has_text? "Log Out"
|
47
46
|
rescue
|
48
47
|
raise FineAnts::LoginFailedError.new
|
49
48
|
end
|
49
|
+
|
50
|
+
def click_thing(locator)
|
51
|
+
click_link locator
|
52
|
+
rescue
|
53
|
+
click_button locator
|
54
|
+
end
|
50
55
|
end
|
51
56
|
end
|
52
57
|
end
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
@@ -10,68 +10,68 @@ module FineAnts
|
|
10
10
|
|
11
11
|
def login
|
12
12
|
visit "https://wwws.betterment.com/app/login"
|
13
|
-
fill_in "web_authentication[email]", :
|
14
|
-
fill_in "web_authentication[password]", :
|
13
|
+
fill_in "web_authentication[email]", with: @user
|
14
|
+
fill_in "web_authentication[password]", with: @password
|
15
15
|
click_button "Log in"
|
16
16
|
begin
|
17
17
|
find_field "web_second_factor_authentication[verification_code]"
|
18
|
-
|
18
|
+
false
|
19
19
|
rescue Capybara::ElementNotFound
|
20
20
|
verify_login!
|
21
|
-
|
21
|
+
true
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
25
|
def two_factor_response(answer)
|
26
|
-
fill_in "web_second_factor_authentication[verification_code]", :
|
26
|
+
fill_in "web_second_factor_authentication[verification_code]", with: answer
|
27
27
|
find(".web_second_factor_authentication_trust_device").click
|
28
28
|
click_button "Verify"
|
29
29
|
verify_login!
|
30
30
|
end
|
31
31
|
|
32
32
|
def download
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
all(".ft-goalAccordionLabel").each do |accordion|
|
34
|
+
accordion.click
|
35
|
+
sleep 0.3
|
36
|
+
end
|
37
|
+
accounts = all ".ft-goalExpandedFooter .sc-ContentLayout:nth-child(1) .ft-accounts .ft-subAccountExpandedRow"
|
38
|
+
accounts.map { |account|
|
37
39
|
{
|
38
|
-
:
|
39
|
-
:
|
40
|
-
:
|
41
|
-
:
|
42
|
-
:
|
40
|
+
adapter: :betterment,
|
41
|
+
user: @user,
|
42
|
+
id: id_for(account),
|
43
|
+
name: name_for(account),
|
44
|
+
amount: total_for(account)
|
43
45
|
}
|
44
|
-
|
45
|
-
|
46
|
-
find('.sc-Nav-panelTrigger').click
|
47
|
-
find('.sc-Nav-panelTrigger .SecondaryNavLogoutAction button', visible: false).click
|
46
|
+
}.tap do
|
47
|
+
find_logout_button.click
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
|
51
|
+
private
|
52
52
|
|
53
53
|
def verify_login!
|
54
|
-
|
54
|
+
find_logout_button
|
55
55
|
rescue
|
56
56
|
raise FineAnts::LoginFailedError.new
|
57
57
|
end
|
58
58
|
|
59
|
+
def find_logout_button
|
60
|
+
find(".Navigation-logoutAction")
|
61
|
+
end
|
62
|
+
|
59
63
|
def id_for(account)
|
60
|
-
|
61
|
-
link[:href].match(/\/app\/goals\/(\d+)\//)[1]
|
64
|
+
account.find("a[data-track-name=\"AutoDepositIntended\"]")["data-track-broker-dealer-account-id"]
|
62
65
|
end
|
63
66
|
|
64
67
|
def name_for(account)
|
65
|
-
"#{account.find(".
|
68
|
+
"#{account.find(".ft-subAccountName").text} - #{account.ancestor(".sc-Card").find(".ft-goalName").text}"
|
66
69
|
end
|
67
70
|
|
68
71
|
def total_for(account)
|
69
|
-
total_string = account.find(".
|
70
|
-
BigDecimal
|
72
|
+
total_string = account.find(".ft-subAccountBalance").text
|
73
|
+
BigDecimal(total_string.match(/\$(.*)$/)[1].delete(","))
|
71
74
|
end
|
72
75
|
end
|
73
76
|
end
|
74
77
|
end
|
75
|
-
|
76
|
-
|
77
|
-
|
@@ -12,8 +12,8 @@ module FineAnts
|
|
12
12
|
visit "https://www.chase.com"
|
13
13
|
|
14
14
|
within_frame(find("#logonbox")) do
|
15
|
-
fill_in "Username", :
|
16
|
-
fill_in "Password", :
|
15
|
+
fill_in "Username", with: @user
|
16
|
+
fill_in "Password", with: @password
|
17
17
|
# I'm not happy with this. Chase's site seems to blank out the
|
18
18
|
# username and password fields if you hit "Sign in" immediately after
|
19
19
|
# the dialog appears. We're sleeping to sidestep this behavior.
|
@@ -43,14 +43,14 @@ module FineAnts
|
|
43
43
|
|
44
44
|
accounts = [
|
45
45
|
{
|
46
|
-
:
|
47
|
-
:
|
48
|
-
:
|
49
|
-
:
|
50
|
-
:
|
51
|
-
:
|
52
|
-
:
|
53
|
-
:
|
46
|
+
adapter: :chase,
|
47
|
+
user: @user,
|
48
|
+
id: find(".ACTNAME").text,
|
49
|
+
name: find(".ACTNAME").text,
|
50
|
+
type: :credit_card,
|
51
|
+
amount: -1 * parse_currency(balance),
|
52
|
+
available_amount: parse_currency(available_balance),
|
53
|
+
next_due_date: parse_due_date(next_due_date)
|
54
54
|
}
|
55
55
|
]
|
56
56
|
|
@@ -61,7 +61,7 @@ module FineAnts
|
|
61
61
|
private
|
62
62
|
|
63
63
|
def parse_currency(currency_string)
|
64
|
-
BigDecimal
|
64
|
+
BigDecimal(currency_string.match(/\$(.*)$/)[1].delete(","))
|
65
65
|
end
|
66
66
|
|
67
67
|
def parse_due_date(due_date_string)
|
@@ -76,4 +76,3 @@ module FineAnts
|
|
76
76
|
end
|
77
77
|
end
|
78
78
|
end
|
79
|
-
|
@@ -10,27 +10,28 @@ module FineAnts
|
|
10
10
|
|
11
11
|
def login
|
12
12
|
visit "https://us.etrade.com/e/t/user/login"
|
13
|
-
fill_in "
|
14
|
-
|
13
|
+
fill_in "USER", with: @user
|
14
|
+
sleep 0.3
|
15
|
+
fill_in "PASSWORD", with: @password
|
15
16
|
click_button "Log on"
|
16
17
|
verify_login!
|
17
18
|
end
|
18
19
|
|
19
20
|
def download
|
20
|
-
all("section.account").map
|
21
|
+
all("section.account").map { |account|
|
21
22
|
{
|
22
|
-
:
|
23
|
-
:
|
24
|
-
:
|
25
|
-
:
|
26
|
-
:
|
23
|
+
adapter: :etrade,
|
24
|
+
user: @user,
|
25
|
+
id: account.find("#acctNum .number-reveal", visible: false).text(:all),
|
26
|
+
name: account.find("a.account-id").text,
|
27
|
+
amount: BigDecimal(account.find(".table-horizontal .text-right.secondary").text.gsub(/[\$,]/, ""))
|
27
28
|
}
|
28
|
-
|
29
|
+
}.tap do
|
29
30
|
click_link "Log Off"
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
33
|
-
|
34
|
+
private
|
34
35
|
|
35
36
|
def verify_login!
|
36
37
|
find_link "Log Off"
|
@@ -40,7 +41,3 @@ module FineAnts
|
|
40
41
|
end
|
41
42
|
end
|
42
43
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
@@ -10,26 +10,26 @@ module FineAnts
|
|
10
10
|
|
11
11
|
def login
|
12
12
|
visit "https://www.onlinebanking.pnc.com/alservlet/SignonInitServlet"
|
13
|
-
fill_in "userId", :
|
14
|
-
fill_in "password", :
|
15
|
-
click_button "Sign
|
13
|
+
fill_in "userId", with: @user
|
14
|
+
fill_in "password", with: @password
|
15
|
+
click_button "Sign In"
|
16
16
|
|
17
17
|
if all("input[value='Generate Code']").any?
|
18
18
|
click_button "Generate Code"
|
19
|
-
|
19
|
+
false
|
20
20
|
elsif all("input[name=answer]").any?
|
21
|
-
|
21
|
+
false
|
22
22
|
else
|
23
23
|
verify_login!
|
24
|
-
|
24
|
+
true
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
28
|
def two_factor_response(text_or_answer)
|
29
29
|
if all("input[name=answer]").any? # Security Q
|
30
|
-
fill_in "answer", :
|
30
|
+
fill_in "answer", with: text_or_answer
|
31
31
|
else # Text message challenge
|
32
|
-
fill_in "otpNumber", :
|
32
|
+
fill_in "otpNumber", with: text_or_answer
|
33
33
|
end
|
34
34
|
click_button "Continue"
|
35
35
|
verify_login!
|
@@ -37,28 +37,25 @@ module FineAnts
|
|
37
37
|
|
38
38
|
def download
|
39
39
|
rows = all("#depositAccountsWrapper tr.depAccount")
|
40
|
-
rows.map
|
40
|
+
rows.map { |row|
|
41
41
|
cells = row.all("td")
|
42
42
|
{
|
43
|
-
:
|
44
|
-
:
|
45
|
-
:
|
46
|
-
:
|
47
|
-
:
|
43
|
+
adapter: :pnc,
|
44
|
+
user: @user,
|
45
|
+
id: cells[2].text, # TODO - this won't be unique, only gives last 4 :-/
|
46
|
+
name: cells[0].text,
|
47
|
+
amount: BigDecimal(cells[3].text.match(/\$(.*)$/)[1].delete(","))
|
48
48
|
}
|
49
|
-
|
49
|
+
}.tap { click_link "Sign Off" }
|
50
50
|
end
|
51
51
|
|
52
|
-
|
52
|
+
private
|
53
53
|
|
54
54
|
def verify_login!
|
55
55
|
find ".lastSignOn"
|
56
56
|
rescue
|
57
57
|
raise FineAnts::LoginFailedError.new
|
58
58
|
end
|
59
|
-
|
60
59
|
end
|
61
60
|
end
|
62
61
|
end
|
63
|
-
|
64
|
-
|
@@ -18,11 +18,11 @@ module FineAnts
|
|
18
18
|
|
19
19
|
def download
|
20
20
|
deposit_table = find("span", text: /Deposit Accounts/)
|
21
|
-
|
22
|
-
|
21
|
+
.find(:xpath, "../..")
|
22
|
+
.find(".module_container")
|
23
23
|
loan_table = find("span", text: /Loans/)
|
24
|
-
|
25
|
-
|
24
|
+
.find(:xpath, "../..")
|
25
|
+
.find(".module_container")
|
26
26
|
deposit_transactions = process_table(deposit_table)
|
27
27
|
loan_transactions = process_table(loan_table, type: :loan)
|
28
28
|
|
@@ -65,7 +65,7 @@ module FineAnts
|
|
65
65
|
end
|
66
66
|
|
67
67
|
def parse_currency(currency_string)
|
68
|
-
BigDecimal
|
68
|
+
BigDecimal(currency_string.match(/\$(.*)$/)[1].delete(","))
|
69
69
|
end
|
70
70
|
|
71
71
|
def empty_table?(table)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "bigdecimal"
|
2
|
+
|
3
|
+
module FineAnts
|
4
|
+
module Adapters
|
5
|
+
class Schwab
|
6
|
+
def initialize(credentials)
|
7
|
+
@user = credentials[:user]
|
8
|
+
@password = credentials[:password]
|
9
|
+
end
|
10
|
+
|
11
|
+
def login
|
12
|
+
visit "https://client.schwab.com/Login/SignOn/CustomerCenterLogin.aspx"
|
13
|
+
within_frame 'lmsSecondaryLogin' do
|
14
|
+
fill_in "loginIdInput", with: @user
|
15
|
+
fill_in "passwordInput", with: @password
|
16
|
+
select "Accounts Summary", from: "landingPageOptions"
|
17
|
+
click_button "btnLogin"
|
18
|
+
end
|
19
|
+
|
20
|
+
begin
|
21
|
+
find("#otp_sms").click
|
22
|
+
false
|
23
|
+
rescue Capybara::ElementNotFound
|
24
|
+
verify_login!
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def two_factor_response(answer)
|
30
|
+
fill_in "securityCode", with: answer
|
31
|
+
click_button "continueButton"
|
32
|
+
verify_login!
|
33
|
+
end
|
34
|
+
|
35
|
+
def download
|
36
|
+
rows = all(".account-row")
|
37
|
+
rows.map { |row|
|
38
|
+
{
|
39
|
+
adapter: :schwab,
|
40
|
+
user: @user,
|
41
|
+
id: row.all(".account.number").first.text.strip,
|
42
|
+
name: row.all(".nickName-wrapper").first.text.strip,
|
43
|
+
amount: BigDecimal(row.all(".balance-container-cs .values-wrapper").first.text.match(/\$(.*)$/)[1].delete(","))
|
44
|
+
}
|
45
|
+
}.tap { click_button "Log Out" }
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def verify_login!
|
51
|
+
find_button "Log Out"
|
52
|
+
rescue Capybara::ElementNotFound
|
53
|
+
raise FineAnts::LoginFailedError.new
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -12,20 +12,20 @@ module FineAnts
|
|
12
12
|
visit "https://www.simple.com"
|
13
13
|
click_link "Log In"
|
14
14
|
|
15
|
-
fill_in "login_username", :
|
16
|
-
fill_in "login_password", :
|
15
|
+
fill_in "login_username", with: @user
|
16
|
+
fill_in "login_password", with: @password
|
17
17
|
click_button "Sign In"
|
18
18
|
begin
|
19
19
|
find_field "Enter 4-digit code"
|
20
|
-
|
20
|
+
false
|
21
21
|
rescue Capybara::ElementNotFound
|
22
22
|
verify_login!
|
23
|
-
|
23
|
+
true
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
27
|
def two_factor_response(answer)
|
28
|
-
fill_in "Enter 4-digit code", :
|
28
|
+
fill_in "Enter 4-digit code", with: answer
|
29
29
|
click_button "Verify"
|
30
30
|
end
|
31
31
|
|
@@ -36,23 +36,24 @@ module FineAnts
|
|
36
36
|
|
37
37
|
[
|
38
38
|
{
|
39
|
-
:
|
40
|
-
:
|
41
|
-
:
|
42
|
-
:
|
43
|
-
:
|
44
|
-
:
|
39
|
+
adapter: :simple,
|
40
|
+
user: @user,
|
41
|
+
id: user_name.to_s,
|
42
|
+
name: user_name.to_s,
|
43
|
+
amount: parse_currency(balance),
|
44
|
+
available_amount: parse_currency(available_balance)
|
45
45
|
}
|
46
|
-
].tap{ logout! }
|
46
|
+
].tap { logout! }
|
47
47
|
end
|
48
48
|
|
49
49
|
private
|
50
|
+
|
50
51
|
def logout!
|
51
52
|
visit "https://bank.simple.com/signout"
|
52
53
|
end
|
53
54
|
|
54
55
|
def parse_currency(currency_string)
|
55
|
-
BigDecimal
|
56
|
+
BigDecimal(currency_string.match(/\$?(.*)$/)[1].delete(","))
|
56
57
|
end
|
57
58
|
|
58
59
|
def verify_login!
|
@@ -63,4 +64,3 @@ module FineAnts
|
|
63
64
|
end
|
64
65
|
end
|
65
66
|
end
|
66
|
-
|
@@ -12,8 +12,8 @@ module FineAnts
|
|
12
12
|
visit "https://www.simple.com"
|
13
13
|
click_link "Log In"
|
14
14
|
|
15
|
-
fill_in "username", :
|
16
|
-
fill_in "passphrase", :
|
15
|
+
fill_in "username", with: @user
|
16
|
+
fill_in "passphrase", with: @password
|
17
17
|
click_button "Sign in"
|
18
18
|
verify_login!
|
19
19
|
end
|
@@ -25,23 +25,24 @@ module FineAnts
|
|
25
25
|
|
26
26
|
[
|
27
27
|
{
|
28
|
-
:
|
29
|
-
:
|
30
|
-
:
|
31
|
-
:
|
32
|
-
:
|
33
|
-
:
|
28
|
+
adapter: :simple_bancorp,
|
29
|
+
user: @user,
|
30
|
+
id: user_name.to_s,
|
31
|
+
name: user_name.to_s,
|
32
|
+
amount: parse_currency(balance),
|
33
|
+
available_amount: parse_currency(available_balance)
|
34
34
|
}
|
35
|
-
].tap{ logout! }
|
35
|
+
].tap { logout! }
|
36
36
|
end
|
37
37
|
|
38
38
|
private
|
39
|
+
|
39
40
|
def logout!
|
40
41
|
visit "https://bank.simple.com/signout"
|
41
42
|
end
|
42
43
|
|
43
44
|
def parse_currency(currency_string)
|
44
|
-
BigDecimal
|
45
|
+
BigDecimal(currency_string.match(/\$?(.*)$/)[1].delete(","))
|
45
46
|
end
|
46
47
|
|
47
48
|
def verify_login!
|
@@ -52,4 +53,3 @@ module FineAnts
|
|
52
53
|
end
|
53
54
|
end
|
54
55
|
end
|
55
|
-
|
@@ -10,20 +10,20 @@ module FineAnts
|
|
10
10
|
|
11
11
|
def login
|
12
12
|
visit "https://www.nrsstrsoh.org/iApp/tcm/nrsstrsoh/index.jsp"
|
13
|
-
fill_in "Username", :
|
14
|
-
fill_in "Password (Case sensitive)", :
|
13
|
+
fill_in "Username", with: @user
|
14
|
+
fill_in "Password (Case sensitive)", with: @password
|
15
15
|
click_button "Log In"
|
16
16
|
begin
|
17
17
|
find_field "contactPoint"
|
18
|
-
|
18
|
+
false
|
19
19
|
rescue Capybara::ElementNotFound
|
20
20
|
verify_login!
|
21
|
-
|
21
|
+
true
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
25
|
def two_factor_response(answer)
|
26
|
-
fill_in "confirmationCode", :
|
26
|
+
fill_in "confirmationCode", with: answer
|
27
27
|
find_field("continue").click
|
28
28
|
begin
|
29
29
|
find "#rememberTrue"
|
@@ -37,18 +37,18 @@ module FineAnts
|
|
37
37
|
def download
|
38
38
|
[
|
39
39
|
{
|
40
|
-
:
|
41
|
-
:
|
42
|
-
:
|
43
|
-
:
|
44
|
-
:
|
40
|
+
adapter: :strs,
|
41
|
+
user: @user,
|
42
|
+
id: find(".plan-info-plan .disabled-phone-link").text,
|
43
|
+
name: find(".plan-info-plan").text,
|
44
|
+
amount: BigDecimal(find(".dash-health-alt__total_number").text.gsub(/[\$,\s]/, ""))
|
45
45
|
}
|
46
46
|
].tap do
|
47
47
|
click_link "Log Out"
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
|
51
|
+
private
|
52
52
|
|
53
53
|
def verify_login!
|
54
54
|
find(".dash-health-alt__current_total_balance")
|
@@ -58,8 +58,3 @@ module FineAnts
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
end
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
@@ -11,8 +11,8 @@ module FineAnts
|
|
11
11
|
def login
|
12
12
|
visit "https://rcam.target.com/default.aspx"
|
13
13
|
|
14
|
-
fill_in "Login_UserName", :
|
15
|
-
fill_in "Login_Password", :
|
14
|
+
fill_in "Login_UserName", with: @user
|
15
|
+
fill_in "Login_Password", with: @password
|
16
16
|
find("#Login_btnSignIn_btnSignIn").click
|
17
17
|
|
18
18
|
verify_login!
|
@@ -26,14 +26,14 @@ module FineAnts
|
|
26
26
|
|
27
27
|
[
|
28
28
|
{
|
29
|
-
:
|
30
|
-
:
|
31
|
-
:
|
32
|
-
:
|
33
|
-
:
|
34
|
-
:
|
35
|
-
:
|
36
|
-
:
|
29
|
+
adapter: :target,
|
30
|
+
user: @user,
|
31
|
+
id: "REDcard #{card_number}",
|
32
|
+
name: "REDcard #{card_number}",
|
33
|
+
type: :credit_card,
|
34
|
+
amount: -1 * parse_currency(balance),
|
35
|
+
available_amount: parse_currency(available_balance),
|
36
|
+
next_due_date: parse_due_date(next_due_date)
|
37
37
|
}
|
38
38
|
]
|
39
39
|
end
|
@@ -41,7 +41,7 @@ module FineAnts
|
|
41
41
|
private
|
42
42
|
|
43
43
|
def parse_currency(currency_string)
|
44
|
-
BigDecimal
|
44
|
+
BigDecimal(currency_string.match(/\$(.*)$/)[1].delete(","))
|
45
45
|
end
|
46
46
|
|
47
47
|
def parse_due_date(date_string)
|
@@ -57,4 +57,3 @@ module FineAnts
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
end
|
60
|
-
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "bigdecimal"
|
2
|
+
|
3
|
+
module FineAnts
|
4
|
+
module Adapters
|
5
|
+
class UfbDirect
|
6
|
+
def initialize(credentials)
|
7
|
+
@user = credentials[:user]
|
8
|
+
@password = credentials[:password]
|
9
|
+
end
|
10
|
+
|
11
|
+
def login
|
12
|
+
visit "https://onlinebanking.ufbdirect.com/auth/login"
|
13
|
+
sleep 2
|
14
|
+
click_link "Login"
|
15
|
+
sleep 2
|
16
|
+
fill_in "Username", with: @user
|
17
|
+
fill_in "Password", with: @password
|
18
|
+
sleep 1
|
19
|
+
click_button "Login"
|
20
|
+
begin
|
21
|
+
find ".multi-factor"
|
22
|
+
click_button "btn-sms"
|
23
|
+
false
|
24
|
+
rescue Capybara::ElementNotFound
|
25
|
+
verify_login!
|
26
|
+
true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def two_factor_response(answer)
|
31
|
+
fill_in "access-code-entry", with: answer
|
32
|
+
click_button "Continue"
|
33
|
+
verify_login!
|
34
|
+
end
|
35
|
+
|
36
|
+
def download
|
37
|
+
sleep 5
|
38
|
+
rows = all(".details-container")
|
39
|
+
rows.map { |row|
|
40
|
+
{
|
41
|
+
adapter: :ufb_direct,
|
42
|
+
user: @user,
|
43
|
+
id: row.find(".details-container__account-name").text,
|
44
|
+
name: row.find(".details-container__account-name").text,
|
45
|
+
amount: BigDecimal(
|
46
|
+
row.find(".details-container__account-amount").text.match(/\$(.*)$/)[1].delete(",")
|
47
|
+
)
|
48
|
+
}
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def verify_login!
|
55
|
+
find ".details-container"
|
56
|
+
rescue Capybara::ElementNotFound
|
57
|
+
raise FineAnts::LoginFailedError.new
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -10,47 +10,47 @@ module FineAnts
|
|
10
10
|
|
11
11
|
def login
|
12
12
|
visit "https://personal.vanguard.com/us/hnwnesc/nesc/LoginPage"
|
13
|
-
fill_in "LoginForm:USER", :
|
14
|
-
|
13
|
+
fill_in "LoginForm:USER", with: @user
|
14
|
+
sleep 0.2
|
15
|
+
fill_in "LoginForm:PASSWORD-blocked", with: @password
|
15
16
|
click_button "LoginForm:submitInput"
|
16
17
|
begin
|
17
18
|
find_field "LoginForm:ANSWER"
|
18
|
-
|
19
|
+
false
|
19
20
|
rescue Capybara::ElementNotFound
|
20
21
|
verify_login!
|
21
|
-
|
22
|
+
true
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
25
26
|
def two_factor_response(answer)
|
26
|
-
fill_in "LoginForm:ANSWER", :
|
27
|
+
fill_in "LoginForm:ANSWER", with: answer
|
27
28
|
choose "LoginForm:DEVICE:0"
|
28
29
|
click_button "LoginForm:ContinueInput"
|
29
30
|
verify_login!
|
30
31
|
end
|
31
32
|
|
32
33
|
def download
|
33
|
-
rows = find("
|
34
|
-
rows.map
|
34
|
+
rows = find("[id='BalancesTabBoxId:balancesForm:balancesTable']").all("tr:not([tbodyid])")
|
35
|
+
rows.map { |row|
|
35
36
|
cells = row.all("td")
|
36
37
|
{
|
37
|
-
:
|
38
|
-
:
|
39
|
-
:
|
40
|
-
:
|
41
|
-
:
|
38
|
+
adapter: :vanguard,
|
39
|
+
user: @user,
|
40
|
+
id: cells.first.all("a").first[:href].match(/.*#(.*)$/)[1],
|
41
|
+
name: cells.first.text,
|
42
|
+
amount: BigDecimal(cells.last.text.match(/\$(.*)$/)[1].delete(","))
|
42
43
|
}
|
43
|
-
|
44
|
+
}.tap { click_link "Log off" }
|
44
45
|
end
|
45
46
|
|
46
|
-
|
47
|
+
private
|
47
48
|
|
48
49
|
def verify_login!
|
49
|
-
|
50
|
+
find_link "Log off"
|
50
51
|
rescue Capybara::ElementNotFound
|
51
52
|
raise FineAnts::LoginFailedError.new
|
52
53
|
end
|
53
54
|
end
|
54
55
|
end
|
55
56
|
end
|
56
|
-
|
@@ -8,24 +8,33 @@ module FineAnts
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def login
|
11
|
-
|
11
|
+
true # No login necessary
|
12
12
|
end
|
13
13
|
|
14
14
|
def download
|
15
15
|
visit "https://www.zillow.com/homedetails/total_nonsense/#{@user}_zpid/?fullpage=true"
|
16
|
-
zestimate =
|
16
|
+
zestimate = find_first(
|
17
|
+
".estimates .home-summary-row:nth-child(2) span:nth-child(2)",
|
18
|
+
".zestimate.primary-quote"
|
19
|
+
).text.match(/(\$.*)/)[1]
|
20
|
+
|
17
21
|
[{
|
18
|
-
:
|
19
|
-
:
|
20
|
-
:
|
21
|
-
:
|
22
|
-
:
|
22
|
+
adapter: :zillow,
|
23
|
+
user: @user,
|
24
|
+
id: @user,
|
25
|
+
name: find_first(".addr h1", ".hdp-home-header-st-addr").text,
|
26
|
+
amount: BigDecimal(zestimate.gsub(/[\$,]/, ""))
|
23
27
|
}]
|
24
28
|
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def find_first(*locators)
|
33
|
+
locators.each do |locator|
|
34
|
+
return find(locator)
|
35
|
+
rescue Capybara::ElementNotFound
|
36
|
+
end
|
37
|
+
end
|
25
38
|
end
|
26
39
|
end
|
27
40
|
end
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
data/lib/fine_ants/adapters.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
require "capybara/dsl"
|
2
2
|
|
3
3
|
# Autoload all predefined adapters
|
4
|
-
Dir[File.join(File.dirname(__FILE__),
|
4
|
+
Dir[File.join(File.dirname(__FILE__), "adapters", "*.rb")].sort.each { |f| require f }
|
5
5
|
|
6
6
|
module FineAnts
|
7
7
|
module Adapters
|
8
8
|
def self.look_up(name)
|
9
|
-
const_name = name.to_s.
|
9
|
+
const_name = name.to_s.tr("_", " ").split(" ").map(&:capitalize).join
|
10
10
|
const_get(const_name).tap do |adapter|
|
11
11
|
adapter.class_eval do
|
12
12
|
include Capybara::DSL
|
@@ -6,7 +6,7 @@
|
|
6
6
|
require "capybara"
|
7
7
|
|
8
8
|
Capybara.register_driver :selenium do |app|
|
9
|
-
Capybara::Selenium::Driver.new(app, :
|
9
|
+
Capybara::Selenium::Driver.new(app, browser: :chrome)
|
10
10
|
end
|
11
11
|
Capybara.default_driver = :selenium
|
12
|
-
Capybara.default_max_wait_time =
|
12
|
+
Capybara.default_max_wait_time = 10
|
data/lib/fine_ants/runner.rb
CHANGED
@@ -10,13 +10,13 @@ module FineAnts
|
|
10
10
|
@adapter.download
|
11
11
|
end
|
12
12
|
|
13
|
-
|
13
|
+
private
|
14
14
|
|
15
15
|
def login!
|
16
16
|
puts "Attempting to login to #{@adapter.class.name} as '#{@credentials[:user]}'"
|
17
17
|
login_complete = @adapter.login
|
18
18
|
if !login_complete && @adapter.respond_to?(:two_factor_response)
|
19
|
-
puts <<-TEXT.gsub(/^\s+/,
|
19
|
+
puts <<-TEXT.gsub(/^\s+/, "")
|
20
20
|
#{@adapter.class.name} is requiring two-factor auth.
|
21
21
|
Check your SMS/Email/TOTP and type it here:
|
22
22
|
TEXT
|
@@ -24,7 +24,5 @@ module FineAnts
|
|
24
24
|
@adapter.two_factor_response(response)
|
25
25
|
end
|
26
26
|
end
|
27
|
-
|
28
27
|
end
|
29
28
|
end
|
30
|
-
|
data/lib/fine_ants/version.rb
CHANGED
data/lib/fine_ants.rb
CHANGED
@@ -7,10 +7,10 @@ require "fine_ants/login_failed_error"
|
|
7
7
|
module FineAnts
|
8
8
|
def self.download(adapter_name, credentials)
|
9
9
|
adapter = if adapter_name.instance_of?(Class)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
adapter_name
|
11
|
+
else
|
12
|
+
Adapters.look_up(adapter_name)
|
13
|
+
end
|
14
14
|
Runner.new(adapter.new(credentials), credentials).download
|
15
15
|
end
|
16
16
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fine_ants
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Searls
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: capybara
|
@@ -16,56 +16,56 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '3.26'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '3.26'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: selenium-webdriver
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 3.142.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 3.142.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: bundle
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rake
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '12.3'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '12.3'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: pry
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,7 +80,21 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
-
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: standard
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description:
|
84
98
|
email:
|
85
99
|
- searls@gmail.com
|
86
100
|
executables: []
|
@@ -103,10 +117,12 @@ files:
|
|
103
117
|
- lib/fine_ants/adapters/etrade.rb
|
104
118
|
- lib/fine_ants/adapters/pnc.rb
|
105
119
|
- lib/fine_ants/adapters/purduefed.rb
|
120
|
+
- lib/fine_ants/adapters/schwab.rb
|
106
121
|
- lib/fine_ants/adapters/simple.rb
|
107
122
|
- lib/fine_ants/adapters/simple_bancorp.rb
|
108
123
|
- lib/fine_ants/adapters/strs.rb
|
109
124
|
- lib/fine_ants/adapters/target.rb
|
125
|
+
- lib/fine_ants/adapters/ufb_direct.rb
|
110
126
|
- lib/fine_ants/adapters/vanguard.rb
|
111
127
|
- lib/fine_ants/adapters/zillow.rb
|
112
128
|
- lib/fine_ants/capybara_setup.rb
|
@@ -116,7 +132,7 @@ files:
|
|
116
132
|
homepage: https://github.com/searls/fine_ants
|
117
133
|
licenses: []
|
118
134
|
metadata: {}
|
119
|
-
post_install_message:
|
135
|
+
post_install_message:
|
120
136
|
rdoc_options: []
|
121
137
|
require_paths:
|
122
138
|
- lib
|
@@ -131,9 +147,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
131
147
|
- !ruby/object:Gem::Version
|
132
148
|
version: '0'
|
133
149
|
requirements: []
|
134
|
-
|
135
|
-
|
136
|
-
signing_key:
|
150
|
+
rubygems_version: 3.3.6
|
151
|
+
signing_key:
|
137
152
|
specification_version: 4
|
138
153
|
summary: Opens your browser and finds your bank account status.
|
139
154
|
test_files: []
|