anz_bank_client 0.1.1
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 +7 -0
- data/.gitignore +13 -0
- data/.rubocop.yml +50 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +68 -0
- data/LICENSE.txt +21 -0
- data/README.md +97 -0
- data/Rakefile +16 -0
- data/anz_bank_client.gemspec +36 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/exe/anzcli +104 -0
- data/lib/anz_bank_client/version.rb +5 -0
- data/lib/anz_bank_client.rb +206 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c6bda86e04ac9e4f972bd220896ed3b153e090c0c9c07a90da02fbac2a478a78
|
4
|
+
data.tar.gz: 1a068361988675e5e211e3b93b608f3b0a7dd63f44f8ee9d64b5cecb278ba575
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 807b91ec4851c8aa8c82ad9b9b17d0db013be7cbfb6a5935e4173d2cd1939284f7fe5f3d42eb8eb3add90988d0de1fc52072badabcac630e635fc6e180b828d9
|
7
|
+
data.tar.gz: 9fa8ab3bb45e89bba0d75cc533c9c6a2ae0434db79f9d9333bec24bb5987af4bbf34fa3afb3b5e4994b8aa0481eaf3ae82db21b35f9065cfc980238f91dbcd06
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 3.0
|
3
|
+
|
4
|
+
Style/StringLiterals:
|
5
|
+
Enabled: true
|
6
|
+
EnforcedStyle: double_quotes
|
7
|
+
|
8
|
+
Style/StringLiteralsInInterpolation:
|
9
|
+
Enabled: true
|
10
|
+
EnforcedStyle: double_quotes
|
11
|
+
|
12
|
+
Layout/LineLength:
|
13
|
+
Max: 120
|
14
|
+
IgnoredPatterns: ['Mozilla/5.0', 'https://']
|
15
|
+
|
16
|
+
Metrics/AbcSize:
|
17
|
+
Enabled: false
|
18
|
+
|
19
|
+
Metrics/MethodLength:
|
20
|
+
Enabled: false
|
21
|
+
|
22
|
+
Style/TrailingCommaInHashLiteral:
|
23
|
+
EnforcedStyleForMultiline: consistent_comma
|
24
|
+
|
25
|
+
Style/TrailingCommaInArguments:
|
26
|
+
EnforcedStyleForMultiline: consistent_comma
|
27
|
+
|
28
|
+
Style/RescueModifier:
|
29
|
+
Enabled: false
|
30
|
+
|
31
|
+
Style/FormatString:
|
32
|
+
EnforcedStyle: percent
|
33
|
+
|
34
|
+
Metrics/ClassLength:
|
35
|
+
Enabled: false
|
36
|
+
|
37
|
+
Style/IfUnlessModifier:
|
38
|
+
Enabled: false
|
39
|
+
|
40
|
+
Metrics/CyclomaticComplexity:
|
41
|
+
Enabled: false
|
42
|
+
|
43
|
+
Style/GuardClause:
|
44
|
+
Enabled: false
|
45
|
+
|
46
|
+
Style/Documentation:
|
47
|
+
Enabled: false
|
48
|
+
|
49
|
+
Style/FrozenStringLiteralComment:
|
50
|
+
Enabled: false
|
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in anz_bank_client.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem "rake", "~> 13.0"
|
9
|
+
|
10
|
+
gem "minitest", "~> 5.0"
|
11
|
+
|
12
|
+
gem "rubocop", "~> 1.4.0"
|
13
|
+
|
14
|
+
gem "table_print"
|
15
|
+
|
16
|
+
gem "dotenv"
|
17
|
+
|
18
|
+
gem "thor"
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
anz_bank_client (0.1.1)
|
5
|
+
faraday (~> 2.7)
|
6
|
+
faraday-cookie_jar (~> 0.0.7)
|
7
|
+
faraday-follow_redirects (~> 0.3.0)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
ast (2.4.2)
|
13
|
+
domain_name (0.6.20240107)
|
14
|
+
dotenv (2.8.1)
|
15
|
+
faraday (2.9.0)
|
16
|
+
faraday-net_http (>= 2.0, < 3.2)
|
17
|
+
faraday-cookie_jar (0.0.7)
|
18
|
+
faraday (>= 0.8.0)
|
19
|
+
http-cookie (~> 1.0.0)
|
20
|
+
faraday-follow_redirects (0.3.0)
|
21
|
+
faraday (>= 1, < 3)
|
22
|
+
faraday-net_http (3.1.0)
|
23
|
+
net-http
|
24
|
+
http-cookie (1.0.5)
|
25
|
+
domain_name (~> 0.5)
|
26
|
+
minitest (5.20.0)
|
27
|
+
net-http (0.4.1)
|
28
|
+
uri
|
29
|
+
parallel (1.23.0)
|
30
|
+
parser (3.2.2.4)
|
31
|
+
ast (~> 2.4.1)
|
32
|
+
racc
|
33
|
+
racc (1.7.3)
|
34
|
+
rainbow (3.1.1)
|
35
|
+
rake (13.1.0)
|
36
|
+
regexp_parser (2.8.2)
|
37
|
+
rexml (3.2.6)
|
38
|
+
rubocop (1.4.2)
|
39
|
+
parallel (~> 1.10)
|
40
|
+
parser (>= 2.7.1.5)
|
41
|
+
rainbow (>= 2.2.2, < 4.0)
|
42
|
+
regexp_parser (>= 1.8)
|
43
|
+
rexml
|
44
|
+
rubocop-ast (>= 1.1.1)
|
45
|
+
ruby-progressbar (~> 1.7)
|
46
|
+
unicode-display_width (>= 1.4.0, < 2.0)
|
47
|
+
rubocop-ast (1.30.0)
|
48
|
+
parser (>= 3.2.1.0)
|
49
|
+
ruby-progressbar (1.13.0)
|
50
|
+
table_print (1.5.7)
|
51
|
+
thor (1.3.0)
|
52
|
+
unicode-display_width (1.8.0)
|
53
|
+
uri (0.13.0)
|
54
|
+
|
55
|
+
PLATFORMS
|
56
|
+
x86_64-linux
|
57
|
+
|
58
|
+
DEPENDENCIES
|
59
|
+
anz_bank_client!
|
60
|
+
dotenv
|
61
|
+
minitest (~> 5.0)
|
62
|
+
rake (~> 13.0)
|
63
|
+
rubocop (~> 1.4.0)
|
64
|
+
table_print
|
65
|
+
thor
|
66
|
+
|
67
|
+
BUNDLED WITH
|
68
|
+
2.2.3
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2023 George Dewar
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# ANZ Bank Client
|
2
|
+
|
3
|
+
This gem is a client for the private, undocumented API behind ANZ New Zealand's personal internet banking website. It is
|
4
|
+
not affiliated with ANZ Bank in any way and is intended only for personal use.
|
5
|
+
|
6
|
+
It can be used in a Ruby script or application, or alternatively as a command-line tool (anzcli).
|
7
|
+
|
8
|
+
It is currently capable of logging in and retrieving account balances and transaction history, but could be easily
|
9
|
+
extended to do other things. Please note that this gem is not officially supported by ANZ Bank and may stop working at
|
10
|
+
any time due to changes to how their internet banking application works. ANZ's terms and conditions for their internet
|
11
|
+
banking product may also change at any time, and you should ensure that you are complying with them if you use this gem.
|
12
|
+
|
13
|
+
## Use as a command line tool
|
14
|
+
|
15
|
+
Install the gem globally with `gem install anz_bank_client` and then run `anzcli` to get started.
|
16
|
+
|
17
|
+
You can use the `help` option to see a list of available commands and options.
|
18
|
+
|
19
|
+
```
|
20
|
+
anzcli commands:
|
21
|
+
anzcli help [COMMAND] # Describe available commands or one specific command
|
22
|
+
anzcli login # Login to ANZ
|
23
|
+
anzcli logout # Logout
|
24
|
+
anzcli ls-accounts # List accounts
|
25
|
+
anzcli ls-transactions ACCOUNT_NUMBER # List transactions
|
26
|
+
|
27
|
+
Options:
|
28
|
+
-f, [--format=FORMAT] # Output format
|
29
|
+
# Default: table
|
30
|
+
# Possible values: table, json
|
31
|
+
```
|
32
|
+
|
33
|
+
The remaining sections are about using the gem in a Ruby script or application.
|
34
|
+
|
35
|
+
## Installation
|
36
|
+
|
37
|
+
Add this line to your application's Gemfile:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
gem 'anz_bank_client'
|
41
|
+
```
|
42
|
+
|
43
|
+
And then execute:
|
44
|
+
|
45
|
+
$ bundle install
|
46
|
+
|
47
|
+
Or install it yourself as:
|
48
|
+
|
49
|
+
$ gem install anz_bank_client
|
50
|
+
|
51
|
+
## Usage
|
52
|
+
|
53
|
+
First, create an instance of the client.
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
@session = AnzBankClient::Session.new
|
57
|
+
```
|
58
|
+
|
59
|
+
Then, log in.
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
@session.login(customer_number, password)
|
63
|
+
```
|
64
|
+
|
65
|
+
You can then list accounts and transactions.
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
accounts = @session.list_accounts
|
69
|
+
|
70
|
+
first_account_number = accounts.first[:accountNo] # e.g. 01-1234-5678901-00
|
71
|
+
start_date = Date.today.prev_month.to_s
|
72
|
+
end_date = Date.today.to_s
|
73
|
+
transactions = @session.list_transactions(first_account_number, start_date, end_date)
|
74
|
+
```
|
75
|
+
|
76
|
+
Finally, log out.
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
@session.logout
|
80
|
+
```
|
81
|
+
|
82
|
+
It's very important to log out, as ANZ's internet banking application has a maximum number of concurrent sessions and
|
83
|
+
you could lock yourself out of your account for a while if you create too many of them without logging out.
|
84
|
+
|
85
|
+
## Development
|
86
|
+
|
87
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
88
|
+
|
89
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
90
|
+
|
91
|
+
## Contributing
|
92
|
+
|
93
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/GeorgeDewar/anz_bank_client.
|
94
|
+
|
95
|
+
## License
|
96
|
+
|
97
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
7
|
+
t.libs << "test"
|
8
|
+
t.libs << "lib"
|
9
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
10
|
+
end
|
11
|
+
|
12
|
+
require "rubocop/rake_task"
|
13
|
+
|
14
|
+
RuboCop::RakeTask.new
|
15
|
+
|
16
|
+
task default: %i[test rubocop]
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/anz_bank_client/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "anz_bank_client"
|
7
|
+
spec.version = AnzBankClient::VERSION
|
8
|
+
spec.authors = ["George Dewar"]
|
9
|
+
spec.email = ["george@dewar.co.nz"]
|
10
|
+
|
11
|
+
spec.summary = "Fetch transaction data from ANZ Bank (New Zealand)"
|
12
|
+
spec.description = "This gem can log into ANZ online banking and fetch account and transaction data"
|
13
|
+
spec.homepage = "https://github.com/GeorgeDewar/anz_bank_client"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
23
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
24
|
+
end
|
25
|
+
spec.bindir = "exe"
|
26
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
27
|
+
spec.require_paths = ["lib"]
|
28
|
+
|
29
|
+
# Uncomment to register a new dependency of your gem
|
30
|
+
spec.add_dependency "faraday", "~> 2.7"
|
31
|
+
spec.add_dependency "faraday-cookie_jar", "~> 0.0.7"
|
32
|
+
spec.add_dependency "faraday-follow_redirects", "~> 0.3.0"
|
33
|
+
|
34
|
+
# For more information and examples about making a new gem, checkout our
|
35
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
36
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "anz_bank_client"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/exe/anzcli
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "anz_bank_client"
|
3
|
+
require "readline"
|
4
|
+
require "optparse"
|
5
|
+
require "thor"
|
6
|
+
require "table_print"
|
7
|
+
require "io/console"
|
8
|
+
require "faraday"
|
9
|
+
|
10
|
+
class AnzCli < Thor
|
11
|
+
def initialize(args = [], local_options = {}, config = {})
|
12
|
+
super(args, local_options, config)
|
13
|
+
@session = AnzBankClient::Session.new
|
14
|
+
end
|
15
|
+
|
16
|
+
package_name "anzcli"
|
17
|
+
|
18
|
+
class_option :format, aliases: "-f", desc: "Output format", default: "table", enum: %w[table json]
|
19
|
+
|
20
|
+
desc "login", "Login to ANZ"
|
21
|
+
long_desc <<~DESC
|
22
|
+
Login to ANZ. If the environment variables ANZ_CUSTOMER_NO and ANZ_PASSWORD are set, they will be used. Otherwise,
|
23
|
+
you will be prompted for your customer number and password. Session details are saved in ~/.anzbank.yaml and used
|
24
|
+
for future commands.
|
25
|
+
DESC
|
26
|
+
def login
|
27
|
+
if ENV["ANZ_CUSTOMER_NO"]
|
28
|
+
@session.login(ENV["ANZ_CUSTOMER_NO"], ENV["ANZ_PASSWORD"])
|
29
|
+
else
|
30
|
+
print "Customer number: "
|
31
|
+
customer_no = gets.chomp
|
32
|
+
print "Password: "
|
33
|
+
password = $stdin.noecho(&:gets).chomp
|
34
|
+
@session.login(customer_no, password)
|
35
|
+
end
|
36
|
+
cookies = @session.export
|
37
|
+
File.open("#{Dir.home}/.anzbank.yaml", "w") { |f| f.write(cookies) }
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "ls-accounts", "List accounts"
|
41
|
+
def ls_accounts
|
42
|
+
check_session
|
43
|
+
if options[:format] == "json"
|
44
|
+
puts @session.list_accounts.to_json
|
45
|
+
else
|
46
|
+
tp @session.list_accounts
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "ls-transactions ACCOUNT_NUMBER", "List transactions"
|
51
|
+
method_option :from, desc: "From date (YYYY-MM-DD)", default: Date.today.prev_month.to_s
|
52
|
+
method_option :to, desc: "To date (YYYY-MM-DD)", default: Date.today.to_s
|
53
|
+
def ls_transactions(account)
|
54
|
+
check_session
|
55
|
+
transactions = @session.list_transactions(account, options[:from], options[:to])
|
56
|
+
if options[:format] == "json"
|
57
|
+
puts transactions.to_json
|
58
|
+
else
|
59
|
+
txns = transactions.map do |t|
|
60
|
+
{
|
61
|
+
date: DateTime.parse(t[:createdDateTime] || t[:date]).strftime("%Y-%m-%d"),
|
62
|
+
time: t[:createdDateTime] ? DateTime.parse(t[:createdDateTime]).strftime("%H:%M:%S") : "",
|
63
|
+
posted_date: t[:postedDate],
|
64
|
+
type: t[:type],
|
65
|
+
description1: t[:details][0],
|
66
|
+
description2: t[:details][1],
|
67
|
+
amount: "% 12.2f" % t[:amount],
|
68
|
+
balance: "% 12.2f" % t[:balance],
|
69
|
+
}
|
70
|
+
end
|
71
|
+
tp txns
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
desc "logout", "Logout"
|
76
|
+
def logout
|
77
|
+
cookies = File.read("#{Dir.home}/.anzbank.yaml") rescue nil
|
78
|
+
if cookies
|
79
|
+
@session.load(cookies)
|
80
|
+
@session.logout
|
81
|
+
File.delete("#{Dir.home}/.anzbank.yaml") rescue nil
|
82
|
+
else
|
83
|
+
puts "No session to log out from"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.exit_on_failure?
|
88
|
+
true
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# Fetch the session details if they exist, otherwise login
|
94
|
+
def check_session
|
95
|
+
cookies = File.read("#{Dir.home}/.anzbank.yaml") rescue nil
|
96
|
+
if cookies
|
97
|
+
@session.load(cookies)
|
98
|
+
else
|
99
|
+
login
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
AnzCli.start(ARGV)
|
@@ -0,0 +1,206 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "dotenv/load"
|
5
|
+
rescue LoadError
|
6
|
+
# Dotenv is not available, so move on without loading it. It's only used for development.
|
7
|
+
end
|
8
|
+
require_relative "anz_bank_client/version"
|
9
|
+
require "faraday"
|
10
|
+
require "faraday-cookie_jar"
|
11
|
+
require "faraday/follow_redirects"
|
12
|
+
|
13
|
+
module AnzBankClient
|
14
|
+
class Error < StandardError; end
|
15
|
+
|
16
|
+
def self.login(username, password)
|
17
|
+
session = Session.new
|
18
|
+
session.login(username, password)
|
19
|
+
session
|
20
|
+
end
|
21
|
+
|
22
|
+
class Session
|
23
|
+
def initialize
|
24
|
+
@logger = Logger.new $stderr
|
25
|
+
@logger.level = Logger::INFO
|
26
|
+
|
27
|
+
@cookie_jar = HTTP::CookieJar.new
|
28
|
+
@user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"
|
29
|
+
|
30
|
+
# Set IBCookieDetect cookie so it knows we have cookies enabled
|
31
|
+
@cookie_jar.add(HTTP::Cookie.new(name: "IBCookieDetect", value: "1", domain: "digital.anz.co.nz", path: "/"))
|
32
|
+
@client = Faraday.new(
|
33
|
+
headers: { "User-Agent" => @user_agent },
|
34
|
+
# proxy: "http://Thinkbook.local:8080",
|
35
|
+
# :ssl => {:verify => false}
|
36
|
+
) do |builder|
|
37
|
+
builder.response :follow_redirects
|
38
|
+
builder.use Faraday::CookieJar, jar: @cookie_jar
|
39
|
+
# builder.response :logger
|
40
|
+
builder.adapter Faraday.default_adapter
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Write the cookie jar out to a string so that the session can be restored later
|
45
|
+
def export
|
46
|
+
cookies = StringIO.new
|
47
|
+
@cookie_jar.save(cookies, format: :yaml, session: true)
|
48
|
+
{
|
49
|
+
cookies: cookies.string,
|
50
|
+
initialise_response: @initialise_response,
|
51
|
+
}.to_json
|
52
|
+
end
|
53
|
+
|
54
|
+
# Load the cookie jar from a string to restore a session
|
55
|
+
def load(str)
|
56
|
+
json = JSON.parse(str)
|
57
|
+
@initialise_response = json["initialise_response"]
|
58
|
+
cookies = StringIO.new
|
59
|
+
cookies.write(json["cookies"])
|
60
|
+
cookies.rewind
|
61
|
+
@cookie_jar.load(cookies, format: :yaml)
|
62
|
+
end
|
63
|
+
|
64
|
+
def login(username, password)
|
65
|
+
@logger.info "Fetching login page"
|
66
|
+
|
67
|
+
login_page = @client.get("https://digital.anz.co.nz/preauth/web/service/login")
|
68
|
+
if login_page.status != 200
|
69
|
+
raise "Error getting login page: #{login_page.status} #{login_page.body}"
|
70
|
+
end
|
71
|
+
|
72
|
+
@encryption_key = login_page.body.match(/encryptionKey: "(.*)",/)[1]
|
73
|
+
@encryption_key_id = login_page.body.match(/encryptionKeyId: "(.*)",/)[1]
|
74
|
+
|
75
|
+
# Encrypt password using encryption key
|
76
|
+
encrypted_password = encrypt_password(password).strip
|
77
|
+
|
78
|
+
# Log in
|
79
|
+
@logger.info "Logging in as #{username}"
|
80
|
+
response = @client.post("https://digital.anz.co.nz/preauth/web/service/login") do |req|
|
81
|
+
req.headers = {
|
82
|
+
"User-Agent" => @user_agent,
|
83
|
+
"Accept" => "application/json",
|
84
|
+
"Content-Type" => "application/json",
|
85
|
+
}
|
86
|
+
req.body = {
|
87
|
+
userId: username,
|
88
|
+
password: encrypted_password,
|
89
|
+
"referrer": "",
|
90
|
+
"firstPage": "",
|
91
|
+
"publicKeyId": @encryption_key_id,
|
92
|
+
}.to_json
|
93
|
+
end
|
94
|
+
if JSON.parse(response.body)["code"] != "success"
|
95
|
+
raise "Error logging in: #{response.status}\n\n#{response.body}"
|
96
|
+
end
|
97
|
+
|
98
|
+
@logger.info "Fetching session details"
|
99
|
+
response = @client.get("https://secure.anz.co.nz/IBCS/service/session?referrer=https%3A%2F%2Fsecure.anz.co.nz%2F")
|
100
|
+
if response.status != 200
|
101
|
+
raise "Session setup failed with status #{response.status}\n\n#{response.body}"
|
102
|
+
end
|
103
|
+
|
104
|
+
csrf_token_match = response.body.match(/sessionCsrfToken *= *"(.*)";/)
|
105
|
+
unless csrf_token_match
|
106
|
+
raise "Could not find CSRF token in page body:\n\n#{response.body}"
|
107
|
+
end
|
108
|
+
|
109
|
+
csrf_token = csrf_token_match[1]
|
110
|
+
@logger.info "CSRF Token: #{csrf_token}"
|
111
|
+
|
112
|
+
@logger.info "Getting initial details"
|
113
|
+
response = @client.get("https://secure.anz.co.nz/IBCS/service/home/initialise")
|
114
|
+
raise "Error getting initial details: #{response.status}\n\n#{response.body}" unless response.status == 200
|
115
|
+
|
116
|
+
@initialise_response = JSON.parse(response.body)
|
117
|
+
end
|
118
|
+
|
119
|
+
def list_accounts
|
120
|
+
@initialise_response["viewableAccounts"].map do |account|
|
121
|
+
balance = account.dig("accountBalance", "amount")
|
122
|
+
overdrawn = balance && (
|
123
|
+
account.dig("accountBalance", "indicator") == "overdrawn" || account["isLoan"] || account["isCreditCard"]
|
124
|
+
)
|
125
|
+
normalised_balance = if balance
|
126
|
+
overdrawn ? -balance : balance
|
127
|
+
end
|
128
|
+
|
129
|
+
{
|
130
|
+
accountNo: account["accountNo"],
|
131
|
+
nickname: account["nicknameEscaped"],
|
132
|
+
accountType: account["productDescription"],
|
133
|
+
customerName: account["accountOwnerName"],
|
134
|
+
accountBalance: normalised_balance,
|
135
|
+
availableFunds: account.dig("availableFunds", "amount"),
|
136
|
+
isLiabilityType: account["isLoan"] || account["isCreditCard"],
|
137
|
+
}
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Fetches transactions for an account
|
142
|
+
#
|
143
|
+
# @param account_no the account number (e.g. 01-1234-1234567-00)
|
144
|
+
# @param start_date in iso8601 format
|
145
|
+
# @param end_date in iso8601 format
|
146
|
+
def list_transactions(account_no, start_date, end_date)
|
147
|
+
@logger.info "Getting transactions for account #{account_no} from #{start_date} to #{end_date}"
|
148
|
+
account_uuid = @initialise_response["viewableAccounts"]
|
149
|
+
.find { |account| account["accountNo"] == account_no }["accountUuid"]
|
150
|
+
raise "Could not find account #{account_no}" unless account_uuid
|
151
|
+
|
152
|
+
response = @client.get("https://secure.anz.co.nz/IBCS/service/api/transactions?account=#{account_uuid}&ascending=false&from=#{start_date}&order=postdate&to=#{end_date}")
|
153
|
+
raise "Error getting transactions: #{response.status}\n\n#{response.body}" unless response.status == 200
|
154
|
+
|
155
|
+
response_json = JSON.parse(response.body)
|
156
|
+
response_json["transactions"].map do |transaction|
|
157
|
+
{
|
158
|
+
date: transaction["date"],
|
159
|
+
postedDate: transaction["postedDate"],
|
160
|
+
details: transaction["details"],
|
161
|
+
amount: transaction["amount"]["amount"],
|
162
|
+
currencyCode: transaction["amount"]["currencyCode"],
|
163
|
+
type: transaction["type"],
|
164
|
+
balance: transaction.dig("balance", "amount"),
|
165
|
+
createdDateTime: transaction["createdDateTime"],
|
166
|
+
}
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def logout
|
171
|
+
@logger.info "Logging out"
|
172
|
+
response = @client.get("https://secure.anz.co.nz/IBCS/service/goodbye")
|
173
|
+
if response.status != 200
|
174
|
+
raise "Error logging out: #{response.status}\n\n#{response.body}"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
# Method to convert a base64 encoded key into PEM format
|
181
|
+
def convert_to_pem_format(key_str)
|
182
|
+
trimmed_key = key_str.strip
|
183
|
+
|
184
|
+
header = "-----BEGIN PUBLIC KEY-----"
|
185
|
+
footer = "-----END PUBLIC KEY-----"
|
186
|
+
|
187
|
+
# Split the key into 64-character lines
|
188
|
+
split_lines = trimmed_key.scan(/.{1,64}/).join("\n")
|
189
|
+
"#{header}\n#{split_lines}\n#{footer}"
|
190
|
+
end
|
191
|
+
|
192
|
+
def encrypt_password(password)
|
193
|
+
# Convert the key to PEM format
|
194
|
+
pem_formatted_key = convert_to_pem_format(@encryption_key)
|
195
|
+
|
196
|
+
# Create a public key object from the PEM-formatted string
|
197
|
+
public_key = OpenSSL::PKey::RSA.new(pem_formatted_key)
|
198
|
+
|
199
|
+
# Encrypt the message using the public key and PKCS1 padding
|
200
|
+
encrypted_password = public_key.public_encrypt(password, OpenSSL::PKey::RSA::PKCS1_PADDING)
|
201
|
+
|
202
|
+
# Encode the encrypted message with base64
|
203
|
+
Base64.encode64(encrypted_password).gsub("\n", "")
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: anz_bank_client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- George Dewar
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-02-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.7'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: faraday-cookie_jar
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.0.7
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.0.7
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: faraday-follow_redirects
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.3.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.3.0
|
55
|
+
description: This gem can log into ANZ online banking and fetch account and transaction
|
56
|
+
data
|
57
|
+
email:
|
58
|
+
- george@dewar.co.nz
|
59
|
+
executables:
|
60
|
+
- anzcli
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- ".gitignore"
|
65
|
+
- ".rubocop.yml"
|
66
|
+
- Gemfile
|
67
|
+
- Gemfile.lock
|
68
|
+
- LICENSE.txt
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- anz_bank_client.gemspec
|
72
|
+
- bin/console
|
73
|
+
- bin/setup
|
74
|
+
- exe/anzcli
|
75
|
+
- lib/anz_bank_client.rb
|
76
|
+
- lib/anz_bank_client/version.rb
|
77
|
+
homepage: https://github.com/GeorgeDewar/anz_bank_client
|
78
|
+
licenses:
|
79
|
+
- MIT
|
80
|
+
metadata:
|
81
|
+
homepage_uri: https://github.com/GeorgeDewar/anz_bank_client
|
82
|
+
source_code_uri: https://github.com/GeorgeDewar/anz_bank_client
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: 3.0.0
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubygems_version: 3.2.3
|
99
|
+
signing_key:
|
100
|
+
specification_version: 4
|
101
|
+
summary: Fetch transaction data from ANZ Bank (New Zealand)
|
102
|
+
test_files: []
|