fine_ants 1.8.5 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +14 -22
- 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 +15 -15
- data/lib/fine_ants/adapters/zillow.rb +10 -16
- 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", "~> 3.
|
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
|
@@ -12,8 +12,8 @@ module FineAnts
|
|
12
12
|
visit "https://www.americanexpress.com"
|
13
13
|
form_css = find("form")[:id] == "ssoform" ? "#ssoform" : ".eliloMain"
|
14
14
|
within form_css do
|
15
|
-
fill_in "User ID", :
|
16
|
-
fill_in "Password", :
|
15
|
+
fill_in "User ID", with: @user
|
16
|
+
fill_in "Password", with: @password
|
17
17
|
click_thing "Log In"
|
18
18
|
end
|
19
19
|
verify_login!
|
@@ -23,29 +23,26 @@ module FineAnts
|
|
23
23
|
visit "https://global.americanexpress.com/accounts"
|
24
24
|
find(".card-block")
|
25
25
|
all(".card-block > div").map do |account|
|
26
|
-
name = account.find(
|
26
|
+
name = account.find(".card > .pad > .heading-3").text
|
27
27
|
owed = account.text.include?("Total Balance")
|
28
28
|
{
|
29
|
-
:
|
30
|
-
:
|
31
|
-
:
|
32
|
-
:
|
33
|
-
:
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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)
|
38
38
|
}
|
39
|
-
end.tap do |accounts|
|
40
|
-
page.driver.go_back
|
41
|
-
click_button "Log Out"
|
42
39
|
end
|
43
40
|
end
|
44
41
|
|
45
|
-
|
42
|
+
private
|
46
43
|
|
47
44
|
def verify_login!
|
48
|
-
|
45
|
+
page.has_text? "Log Out"
|
49
46
|
rescue
|
50
47
|
raise FineAnts::LoginFailedError.new
|
51
48
|
end
|
@@ -58,8 +55,3 @@ module FineAnts
|
|
58
55
|
end
|
59
56
|
end
|
60
57
|
end
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
@@ -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,40 +10,41 @@ 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"
|
@@ -53,4 +54,3 @@ module FineAnts
|
|
53
54
|
end
|
54
55
|
end
|
55
56
|
end
|
56
|
-
|
@@ -8,22 +8,22 @@ 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
16
|
zestimate = find_first(
|
17
|
-
|
18
|
-
|
17
|
+
".estimates .home-summary-row:nth-child(2) span:nth-child(2)",
|
18
|
+
".zestimate.primary-quote"
|
19
19
|
).text.match(/(\$.*)/)[1]
|
20
20
|
|
21
21
|
[{
|
22
|
-
:
|
23
|
-
:
|
24
|
-
:
|
25
|
-
:
|
26
|
-
:
|
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(/[\$,]/, ""))
|
27
27
|
}]
|
28
28
|
end
|
29
29
|
|
@@ -31,16 +31,10 @@ module FineAnts
|
|
31
31
|
|
32
32
|
def find_first(*locators)
|
33
33
|
locators.each do |locator|
|
34
|
-
|
35
|
-
|
36
|
-
rescue Capybara::ElementNotFound
|
37
|
-
end
|
34
|
+
return find(locator)
|
35
|
+
rescue Capybara::ElementNotFound
|
38
36
|
end
|
39
37
|
end
|
40
38
|
end
|
41
39
|
end
|
42
40
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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: []
|