bank_scrap 0.0.7 → 0.0.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4680376ad7f26ae34f9cf4262fa04b98b5b466ea
4
- data.tar.gz: 4be7fda00b5c1a410ffb602979d20894d3511f6b
3
+ metadata.gz: c9c667caab2fefb56490b67d4e33be414f969ea7
4
+ data.tar.gz: af3751b08dfd95655f0cd86f9eaf0f13e186a6cf
5
5
  SHA512:
6
- metadata.gz: 9debe3eadab0fa8a98e3d52640cf2d2a49348f92f6a5b0d2bb5b2a9555ca19d4e61559ea3b990f28bbdcef9ceda1bf36e259d540ffe4c1e23ef25ec7ce53c250
7
- data.tar.gz: d314b783e5a962669d2717f1fa2d15568284e7c4e03195e8d32073296138f7325758d2d218aa492cbfff6fe1b0b47105b951a94d05f995ba2f7fc34bcb128e70
6
+ metadata.gz: 946cf536efb91eebb2b69d23b88d18b10d9a89cf73adc29fe6f23207388ed4078b36b829d45d049af76a6fff8d2ac8bf8532942dd95dfb6b531ac9e8f9a0f158
7
+ data.tar.gz: e63351124eaf1920397fcf116d0359d9a897948f61cab3ad84329ff7a4ea01336cf935e08a1d0a648e12d56bcf2504096f0f41d8a053df405ff90e08bdaf945e
data/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  # BankScrap
2
2
 
3
- Ruby gem to extract balance and transactions from banks. You can use it either as command line tool or as a library.
3
+ Ruby gem to extract account balance and transactions from banks. You can use it either as command line tool or as a library.
4
4
 
5
5
  Feel free to contribute and add your bank if it isn't supported.
6
6
 
7
7
  ## Supported banks
8
8
 
9
- | | Bankinter | BBVA | ING Direct |
10
- |--------------|:---------:|:------:|:----------:|
11
- | Balance | ✓ | ✓ | ✓ |
12
- | Transactions | WIP | WIP | ✘ |
9
+ | | BBVA | ING Direct | Bankinter |
10
+ |-----------------|:------:|:----------:|:---------:|
11
+ | Account Balance | ✓ | | WIP |
12
+ | Transactions | ✓ | ✓ | WIP |
13
13
 
14
14
  Interested in any other bank? Open a new Issue and we'll try to help.
15
15
 
@@ -50,24 +50,34 @@ Or, if you're using Bundler, just add the following to your Gemfile:
50
50
  ## Usage
51
51
 
52
52
  ### From terminal
53
- Retrieve balance account
53
+ #### Bank account balance
54
54
 
55
- ##### Bankinter
56
-
57
- $ bank_scrap balance bankinter --user YOUR_BANKINTER_USER --password YOUR_BANKINTER_PASSWORD
58
-
59
- ##### BBVA
55
+ ###### BBVA
60
56
 
61
57
  $ bank_scrap balance bbva --user YOUR_BBVA_USER --password YOUR_BBVA_PASSWORD
62
58
 
63
- ##### ING Direct
59
+ ###### ING Direct
64
60
  ING needs one more argument: your bithday.
65
61
 
66
62
  $ bank_scrap balance ing --user YOUR_DNI --password YOUR_PASSWORD --extra=birthday:01/01/1980
67
63
 
68
64
  Replace 01/01/1980 with your actual birthday.
69
65
 
66
+ #### Transactions for last 30 days
67
+ ###### BBVA
68
+
69
+ $ bank_scrap transactions bbva --user YOUR_BBVA_USER --password YOUR_BBVA_PASSWORD
70
+
71
+ ###### ING Direct
72
+
73
+ $ bank_scrap transactions ing --user YOUR_DNI --password YOUR_PASSWORD --extra=birthday:01/01/1980
74
+
70
75
  ---
76
+
77
+ By default it will use your first bank account, if you want to fetch transactions for a different account you can use this syntax:
78
+
79
+ $ bank_scrap transactions your_bank your_iban --user YOUR_DNI --password YOUR_PASSWORD
80
+
71
81
  If you don't want to pass your user and password everytime you can define them in your .bash_profile by adding:
72
82
 
73
83
  export BANK_SCRAP_USER=YOUR_BANK_USER
@@ -77,19 +87,45 @@ If you don't want to pass your user and password everytime you can define them i
77
87
 
78
88
  You can also use this gem from your own app as library. To do so first you must initialize a BankScrap::Bank object
79
89
 
90
+
80
91
  ```ruby
81
92
  require 'bank_scrap'
82
- @bank = BankScrap::Bbva.new(YOUR_BBVA_USER, YOUR_BBVA_PASSWORD)
93
+ # BBVA
94
+ bbva = BankScrap::Bbva.new(YOUR_BBVA_USER, YOUR_BBVA_PASSWORD)
95
+ # ING
96
+ ing = BankScrap::Ing.new(YOUR_DNI, YOUR_ING_PASSWORD, extra_args: {"birthday" => "dd/mm/yyyy"})
83
97
  ```
84
98
 
85
- (Replace Bbva with your own bank)
86
99
 
87
- Now you can fetch your balance:
100
+ The initialize method will automatically login and fetch your bank accounts
101
+
102
+ You can now explore your bank accounts accounts:
88
103
 
89
104
  ```ruby
90
- @bank.get_balance
105
+ bank.accounts
91
106
  ```
92
107
 
108
+ And get its balance:
109
+ ```ruby
110
+ bank.accounts.first.balance
111
+ ```
112
+
113
+ Get last month transactions for a particular account:
114
+
115
+ ```ruby
116
+ account = bank.accounts.first
117
+ account.transactions
118
+ ```
119
+
120
+ Get transactions for last year (from now):
121
+
122
+ ```ruby
123
+ account = bank.accounts.first
124
+ account.transactions = account.fetch_transactions(start_date: Date.today - 1.year, end_date: Date.today)
125
+ account.transactions
126
+ ```
127
+
128
+
93
129
 
94
130
  ## Contributing
95
131
 
data/bank_scrap.gemspec CHANGED
@@ -30,4 +30,5 @@ Gem::Specification.new do |spec|
30
30
  spec.add_dependency 'mechanize', "~> 2.7.3"
31
31
  spec.add_dependency 'activesupport', "~> 4.1"
32
32
  spec.add_dependency 'rmagick', '~> 2.2', '>= 2.2.2'
33
+ spec.add_dependency 'money', '~> 6.5.0'
33
34
  end
@@ -0,0 +1,32 @@
1
+ module BankScrap
2
+ class Account
3
+ include Utils::Inspectable
4
+
5
+ attr_accessor :bank, :id, :name, :balance, :currency,
6
+ :available_balance, :description,
7
+ :transactions, :iban, :bic
8
+
9
+ def initialize(params = {})
10
+ params.each { |key, value| send "#{key}=", value }
11
+ end
12
+
13
+ def transactions
14
+ @transactions ||= bank.fetch_transactions_for(self)
15
+ end
16
+
17
+ def fetch_transactions(start_date: Date.today - 2.years, end_date: Date.today)
18
+ bank.fetch_transactions_for(self, start_date: start_date, end_date: end_date)
19
+ end
20
+
21
+ private
22
+
23
+ def inspect_attributes
24
+ [
25
+ :id, :name, :balance, :currency,
26
+ :available_balance, :description,
27
+ :iban, :bic
28
+ ]
29
+ end
30
+ end
31
+ end
32
+
@@ -5,12 +5,30 @@ module BankScrap
5
5
  class Bank
6
6
 
7
7
  WEB_USER_AGENT = 'Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 4 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19'
8
- attr_accessor :headers
8
+ attr_accessor :headers, :accounts
9
+
10
+ def initialize(user, password, log: false, debug: false, extra_args: nil)
11
+ @accounts = fetch_accounts
12
+ end
13
+
14
+ # Interface method placeholders
15
+
16
+ def fetch_accounts
17
+ raise Exception.new "#{self.class} should implement a fetch_account method"
18
+ end
19
+
20
+ def fetch_transactions_for(account, start_date: Date.today - 1.month, end_date: Date.today)
21
+ raise Exception.new "#{self.class} should implement a fetch_transactions method"
22
+ end
23
+
24
+ def account_with_iban(iban)
25
+ accounts.find { |account| account.iban.gsub(' ','') == iban.gsub(' ','') }
26
+ end
9
27
 
10
28
  private
11
29
 
12
- def get(url)
13
- @http.get(url).body
30
+ def get(url, params = {})
31
+ @http.get(url, params).body
14
32
  end
15
33
 
16
34
  def post(url, fields)
@@ -50,6 +68,7 @@ module BankScrap
50
68
  mechanize.user_agent = WEB_USER_AGENT
51
69
  mechanize.agent.http.verify_mode = OpenSSL::SSL::VERIFY_NONE
52
70
  mechanize.log = Logger.new(STDOUT) if @debug
71
+ # mechanize.set_proxy 'localhost', 8888
53
72
  end
54
73
 
55
74
  @headers = {}
@@ -4,7 +4,8 @@ module BankScrap
4
4
  class Bbva < Bank
5
5
  BASE_ENDPOINT = 'https://bancamovil.grupobbva.com'
6
6
  LOGIN_ENDPOINT = '/DFAUTH/slod/DFServletXML'
7
- BALANCE_ENDPOINT = '/ENPP/enpp_mult_web_mobility_02/products/v1'
7
+ PRODUCTS_ENDPOINT = '/ENPP/enpp_mult_web_mobility_02/products/v1'
8
+ ACCOUNT_ENDPOINT = '/ENPP/enpp_mult_web_mobility_02/accounts/'
8
9
  # BBVA expects an identifier before the actual User Agent, but 12345 works fine
9
10
  USER_AGENT = '12345;Android;LGE;Nexus 5;1080x1776;Android;4.4.4;BMES;4.0.4'
10
11
 
@@ -29,22 +30,63 @@ module BankScrap
29
30
  })
30
31
 
31
32
  login
33
+ super
32
34
  end
33
35
 
34
- def get_balance
35
- log 'get_balance'
36
+ # Fetch all the accounts for the given user
37
+ # Returns an array of BankScrap::Account objects
38
+ def fetch_accounts
39
+ log 'fetch_accounts'
36
40
 
37
41
  # Even if the required method is an HTTP POST
38
42
  # the API requires a funny header that says is a GET
39
43
  # otherwise the request doesn't work.
40
44
  response = with_headers({'BBVA-Method' => 'GET'}) do
41
- post(BASE_ENDPOINT + BALANCE_ENDPOINT, {})
45
+ post(BASE_ENDPOINT + PRODUCTS_ENDPOINT, {})
42
46
  end
43
47
 
44
48
  json = JSON.parse(response)
45
- json["balances"]["personalAccounts"]
49
+ json["accounts"].collect { |data| build_account(data) }
46
50
  end
47
51
 
52
+ # Fetch transactions for the given account.
53
+ # By default it fetches transactions for the last month,
54
+ # The maximum allowed by the BBVA API is the last 3 years.
55
+ #
56
+ # Account should be a BankScrap::Account object
57
+ # Returns an array of BankScrap::Transaction objects
58
+ def fetch_transactions_for(account, start_date: Date.today - 1.month, end_date: Date.today)
59
+ fromDate = start_date.strftime("%Y-%m-%d")
60
+ toDate = end_date.strftime("%Y-%m-%d")
61
+
62
+ # Misteriously we need a specific content-type here
63
+ funny_headers = {
64
+ 'Content-Type' => 'application/json; charset=UTF-8',
65
+ 'BBVA-Method' => 'GET'
66
+ }
67
+
68
+ url = BASE_ENDPOINT + ACCOUNT_ENDPOINT + account.id+ "/movements/v1?fromDate=#{fromDate}&toDate=#{toDate}"
69
+ offset = nil
70
+ transactions = []
71
+
72
+ with_headers(funny_headers) do
73
+ # Loop over pagination
74
+ loop do
75
+ new_url = offset ? (url + "&offset=#{offset}") : url
76
+ json = JSON.parse(post(new_url, {}))
77
+
78
+ unless json["movements"].blank?
79
+ transactions += json["movements"].collect { |data| build_transaction(data, account) }
80
+ offset = json["offset"]
81
+ end
82
+
83
+ break unless json["thereAreMoreMovements"] == true
84
+ end
85
+ end
86
+
87
+ transactions
88
+ end
89
+
48
90
  private
49
91
 
50
92
  # As far as we know there are two types of identifiers BBVA uses
@@ -72,5 +114,34 @@ module BankScrap
72
114
  'eai_URLDestino' => '/ENPP/enpp_mult_web_mobility_02/sessions/v1'
73
115
  })
74
116
  end
117
+
118
+ # Build an Account object from API data
119
+ def build_account(data)
120
+ Account.new(
121
+ bank: self,
122
+ id: data['id'],
123
+ name: data['name'],
124
+ available_balance: data['availableBalance'],
125
+ balance: data['availableBalance'],
126
+ currency: data['currency'],
127
+ iban: data['iban'],
128
+ description: "#{data['typeDescription']} #{data['familyCode']}"
129
+ )
130
+ end
131
+
132
+ # Build a transaction object from API data
133
+ def build_transaction(data, account)
134
+ amount = Money.new(data['amount'] * 100, data['currency'])
135
+ balance = data['accountBalanceAfterMovement'] ? Money.new(data['accountBalanceAfterMovement'] * 100, data['currency']) : nil
136
+ Transaction.new(
137
+ account: account,
138
+ id: data['id'],
139
+ amount: amount,
140
+ description: data['conceptDescription'] || data['description'],
141
+ effective_date: Date.strptime(data['operationDate'], "%Y-%m-%d"),
142
+ currency: data['currency'],
143
+ balance: balance
144
+ )
145
+ end
75
146
  end
76
147
  end
@@ -18,27 +18,70 @@ module BankScrap
18
18
  def initialize(user, password, log: false, debug: false, extra_args:)
19
19
  @dni = user
20
20
  @password = password.to_s
21
- @birthday = extra_args['birthday']
21
+ @birthday = extra_args.with_indifferent_access['birthday']
22
22
  @log = log
23
23
  @debug = debug
24
24
 
25
25
  initialize_connection
26
26
  bundled_login
27
- get_products
27
+
28
+ super
28
29
  end
29
30
 
30
31
  def get_balance
31
32
  log 'get_balance'
32
- balance = {}
33
- @data.each do |item|
34
- balance[item['name']] = item['balance']
33
+ balances = {}
34
+ total_balance = 0
35
+ @accounts.each do |account|
36
+ balances[account.description] = account.balance
37
+ total_balance += account.balance
35
38
  end
36
39
 
37
- balance
40
+ balances['TOTAL'] = total_balance
41
+ balances
42
+ end
43
+
44
+ def raw_accounts_data
45
+ @raw_accounts_data
38
46
  end
39
47
 
40
- def raw_product_data
41
- @data
48
+ def fetch_accounts
49
+ log 'fetch_accounts'
50
+ set_headers({
51
+ "Accept" => '*/*',
52
+ 'Content-Type' => 'application/json; charset=utf-8'
53
+ })
54
+
55
+ @raw_accounts_data = JSON.parse(get(PRODUCTS_ENDPOINT))
56
+
57
+ @raw_accounts_data.collect do |account|
58
+ if account['iban']
59
+ build_account(account)
60
+ end
61
+ end.compact
62
+ end
63
+
64
+ def fetch_transactions_for(account, start_date: Date.today - 1.month, end_date: Date.today)
65
+ log "fetch_transactions for #{account.id}"
66
+
67
+ # The API allows any limit to be passed, but we better keep
68
+ # being good API citizens and make a loop with a short limit
69
+ params = {
70
+ fromDate: start_date.strftime("%d/%m/%Y"),
71
+ toDate: end_date.strftime("%d/%m/%Y"),
72
+ limit: 25,
73
+ offset: 0
74
+ }
75
+
76
+ transactions = []
77
+ loop do
78
+ request = get("#{PRODUCTS_ENDPOINT}/#{account.id}/movements", params)
79
+ json = JSON.parse(request)
80
+ transactions += json['elements'].collect { |transaction| build_transaction(transaction, account) }
81
+ params[:offset] += 25
82
+ break if (params[:offset] > json['total']) || json['elements'].blank?
83
+ end
84
+ transactions
42
85
  end
43
86
 
44
87
  private
@@ -55,16 +98,17 @@ module BankScrap
55
98
  'Content-Type' => 'application/json; charset=utf-8'
56
99
  })
57
100
 
58
- param = '{' +
59
- '"loginDocument":{' +
60
- '"documentType":0,"document":"' + @dni.to_s +
61
- '"},' +
62
- '"birthday":"' + @birthday.to_s + '",' +
63
- '"companyDocument":null,' +
64
- '"device":"desktop"}' +
65
- '}'
66
-
67
- response = JSON.parse(post(LOGIN_ENDPOINT, param))
101
+ param = {
102
+ loginDocument: {
103
+ documentType: 0,
104
+ document: @dni.to_s
105
+ },
106
+ birthday: @birthday.to_s,
107
+ companyDocument: nil,
108
+ device: 'desktop'
109
+ }
110
+
111
+ response = JSON.parse(post(LOGIN_ENDPOINT, param.to_json))
68
112
  positions = response['pinPositions']
69
113
  pinpad = response['pinpad']
70
114
 
@@ -95,19 +139,10 @@ module BankScrap
95
139
  post(POST_AUTH_ENDPOINT, param)
96
140
  end
97
141
 
98
- def get_products
99
- set_headers({
100
- "Accept" => '*/*',
101
- 'Content-Type' => 'application/json; charset=utf-8'
102
- })
103
-
104
- @data = JSON.parse(get(PRODUCTS_ENDPOINT))
105
- end
106
-
107
142
  def save_pinpad_numbers(pinpad)
108
143
  pinpad_numbers_paths = []
109
144
  pinpad.each_with_index do |digit,index|
110
- tmp = Tempfile.new(["pinpad_number_#{index}__", '.png'])
145
+ tmp = Tempfile.new(["pinpad_number_#{index}", '.png'])
111
146
  File.open(tmp.path, 'wb'){ |f| f.write(Base64.decode64(digit)) }
112
147
  pinpad_numbers_paths << tmp.path
113
148
  end
@@ -154,5 +189,33 @@ module BankScrap
154
189
  pinpad_numbers.index(third_digit.to_i)
155
190
  ]
156
191
  end
192
+
193
+ # Build an Account object from API data
194
+ def build_account(data)
195
+ Account.new(
196
+ bank: self,
197
+ id: data['uuid'],
198
+ name: data['name'],
199
+ balance: data['balance'],
200
+ currency: 'EUR',
201
+ available_balance: data['availableBalance'],
202
+ description: (data['alias'] || data['name']),
203
+ iban: data['iban'],
204
+ bic: data['bic']
205
+ )
206
+ end
207
+
208
+ # Build a transaction object from API data
209
+ def build_transaction(data, account)
210
+ Transaction.new(
211
+ account: account,
212
+ id: data['uuid'],
213
+ amount: data['amount'],
214
+ currency: data['EUR'],
215
+ effective_date: data['effectiveDate'],
216
+ description: data['description'],
217
+ balance: data['balance']
218
+ )
219
+ end
157
220
  end
158
221
  end
@@ -15,21 +15,30 @@ module BankScrap
15
15
  option :extra, type: :hash
16
16
  end
17
17
 
18
- desc "balance BANK", "get account's balance"
18
+ desc "balance BANK", "get accounts' balance"
19
19
  shared_options
20
20
  def balance(bank)
21
21
  assign_shared_options
22
22
  initialize_client_for(bank)
23
23
 
24
- say "Balance: #{@client.get_balance}", :green
24
+ @client.accounts.each do |account|
25
+ say "Account: #{account.description} (#{account.iban})", :cyan
26
+ say "Balance: #{account.balance}", :green
27
+ end
25
28
  end
26
29
 
27
30
  desc "transactions BANK", "get account's transactions"
28
31
  shared_options
29
- def transactions(bank)
32
+ def transactions(bank, iban = nil)
30
33
  assign_shared_options
31
34
  initialize_client_for(bank)
32
- transactions = @client.get_transactions
35
+ account = iban ? @client.account_with_iban(iban) : @client.accounts.first
36
+ transactions = account.transactions
37
+ say "Transactions for: #{account.description} (#{account.iban})", :cyan
38
+
39
+ transactions.each do |transaction|
40
+ say transaction.to_s, (transaction.amount > Money.new(0) ? :green : :red)
41
+ end
33
42
  end
34
43
 
35
44
  private
@@ -0,0 +1,5 @@
1
+ module BankScrap
2
+ # Default format for money: 1.000,00 €
3
+ Money.default_formatting_rules = {symbol_position: :after}
4
+ I18n.load_path += Dir.glob(File.expand_path('../locale/*.yml', __FILE__))
5
+ end
@@ -0,0 +1,12 @@
1
+ en:
2
+ number:
3
+ currency:
4
+ format:
5
+ delimiter: "."
6
+ format: "%n %u"
7
+ negative_format: "-%n %u"
8
+ precision: 2
9
+ separator: ","
10
+ significant: false
11
+ strip_insignificant_zeros: false
12
+ unit: "€"
@@ -0,0 +1,28 @@
1
+ module BankScrap
2
+ class Transaction
3
+ include Utils::Inspectable
4
+
5
+ attr_accessor :id, :amount, :currency,
6
+ :effective_date, :description,
7
+ :balance, :account
8
+
9
+ def initialize(params = {})
10
+ params.each{ |key, value| send "#{key}=", value }
11
+ end
12
+
13
+ def to_s
14
+ "#{effective_date.strftime("%d/%m/%Y")} #{description.ljust(45)} #{amount.format.rjust(20)}"
15
+ end
16
+
17
+ private
18
+
19
+ def inspect_attributes
20
+ [
21
+ :id, :amount, :currency,
22
+ :effective_date, :description,
23
+ :balance
24
+ ]
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,19 @@
1
+ module BankScrap
2
+ module Utils
3
+ module Inspectable
4
+ def inspect
5
+ attributes = inspect_attributes.reject { |x|
6
+ begin
7
+ attribute = send x
8
+ !attribute || (attribute.respond_to?(:empty?) && attribute.empty?)
9
+ rescue NoMethodError
10
+ true
11
+ end
12
+ }.map { |attribute|
13
+ "#{attribute.to_s}: #{send(attribute).inspect}"
14
+ }.join ' '
15
+ "#<#{self.class.name}:#{sprintf("0x%x", object_id)} #{attributes}>"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,3 +1,3 @@
1
1
  module BankScrap
2
- VERSION = "0.0.7"
2
+ VERSION = "0.0.8"
3
3
  end
data/lib/bank_scrap.rb CHANGED
@@ -1,6 +1,12 @@
1
+ require 'active_support/all'
2
+ require 'money'
3
+ require 'bank_scrap/utils/inspectable'
1
4
  require 'bank_scrap/version'
5
+ require 'bank_scrap/config'
2
6
  require 'bank_scrap/cli'
3
7
  require 'bank_scrap/bank'
8
+ require 'bank_scrap/account'
9
+ require 'bank_scrap/transaction'
4
10
 
5
11
  module BankScrap
6
12
  # autoload only requires the file when the specified
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bank_scrap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ismael Sánchez
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2015-01-25 00:00:00.000000000 Z
14
+ date: 2015-01-29 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
@@ -151,6 +151,20 @@ dependencies:
151
151
  - - ">="
152
152
  - !ruby/object:Gem::Version
153
153
  version: 2.2.2
154
+ - !ruby/object:Gem::Dependency
155
+ name: money
156
+ requirement: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - "~>"
159
+ - !ruby/object:Gem::Version
160
+ version: 6.5.0
161
+ type: :runtime
162
+ prerelease: false
163
+ version_requirements: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - "~>"
166
+ - !ruby/object:Gem::Version
167
+ version: 6.5.0
154
168
  description: Command line tools to get bank account details from some banks.
155
169
  email:
156
170
  - root@ismagnu.com
@@ -167,6 +181,7 @@ files:
167
181
  - bank_scrap.gemspec
168
182
  - bin/bank_scrap
169
183
  - lib/bank_scrap.rb
184
+ - lib/bank_scrap/account.rb
170
185
  - lib/bank_scrap/bank.rb
171
186
  - lib/bank_scrap/banks/bankinter.rb
172
187
  - lib/bank_scrap/banks/bbva.rb
@@ -181,8 +196,11 @@ files:
181
196
  - lib/bank_scrap/banks/ing/numbers/pinpad7.png
182
197
  - lib/bank_scrap/banks/ing/numbers/pinpad8.png
183
198
  - lib/bank_scrap/banks/ing/numbers/pinpad9.png
184
- - lib/bank_scrap/banks/ing_backup.rb
185
199
  - lib/bank_scrap/cli.rb
200
+ - lib/bank_scrap/config.rb
201
+ - lib/bank_scrap/locale/en.yml
202
+ - lib/bank_scrap/transaction.rb
203
+ - lib/bank_scrap/utils/inspectable.rb
186
204
  - lib/bank_scrap/version.rb
187
205
  homepage: https://github.com/ismaGNU/bank_scrap
188
206
  licenses:
@@ -1,237 +0,0 @@
1
- require 'execjs'
2
- require 'pp'
3
- require 'json'
4
- require 'base64'
5
- require 'RMagick'
6
- require 'active_support'
7
- require 'byebug'
8
- require 'open-uri'
9
-
10
- module BankScrap
11
- class Ing < Bank
12
-
13
- DESKTOP_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36'
14
-
15
- BASE_ENDPOINT = 'https://ing.ingdirect.es/'
16
- FALSE_LOGIN_ENDPOINT = BASE_ENDPOINT + 'login/'
17
- CACHE_ENDPOINT = BASE_ENDPOINT + 'login/cache.manifest'
18
- DELETE_SESSION_ENDPOINT = BASE_ENDPOINT + 'genoma_api/rest/session'
19
- LOGIN_ENDPOINT = BASE_ENDPOINT + 'genoma_login/rest/session'
20
- POST_AUTH_ENDPOINT = BASE_ENDPOINT + 'genoma_api/login/auth/response'
21
- CLIENT_ENDPOINT = BASE_ENDPOINT + 'genoma_api/rest/client'
22
- PRODUCTS_ENDPOINT = BASE_ENDPOINT + 'genoma_api/rest/products'
23
-
24
- SAMPLE_WIDTH = 30
25
- SAMPLE_HEIGHT = 30
26
-
27
- def initialize(dni, birthday, password, log: false, debug: false)
28
- @dni = dni
29
- @birthday = birthday
30
- @password = password.to_s
31
- @log = log
32
- @debug = debug
33
-
34
- initialize_connection
35
-
36
- @curl.proxy_port = 8888
37
- @curl.proxy_url = '192.168.1.21'
38
-
39
- false_login
40
- cache
41
- delete_session
42
- selected_positions = login
43
-
44
- ticket = pass_pinpad(selected_positions)
45
-
46
- post_auth(ticket)
47
- call_client
48
-
49
- get_products
50
- end
51
-
52
- private
53
-
54
- def false_login
55
- @curl.url = FALSE_LOGIN_ENDPOINT
56
- @curl.headers['Host'] = 'ing.ingdirect.es'
57
- @curl.headers['Connection'] = 'keep-alive'
58
- @curl.headers['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
59
- @curl.headers['Accept-Encoding'] = 'gzip,deflate,sdch'
60
- @curl.headers['Accept-Language'] = 'en,es;q=0.8'
61
-
62
- response = @curl.get
63
- end
64
-
65
- def cache
66
- @curl.url = CACHE_ENDPOINT
67
- @curl.headers['Host'] = 'ing.ingdirect.es'
68
- @curl.headers['Connection'] = 'keep-alive'
69
- @curl.headers['Accept-Encoding'] = 'gzip,deflate,sdch'
70
- @curl.headers['Accept-Language'] = 'en,es;q=0.8'
71
- end
72
-
73
- def delete_session
74
- @curl.headers['Host'] = 'ing.ingdirect.es'
75
- @curl.headers['Connection'] = 'keep-alive'
76
- @curl.headers['Pragma'] = 'no-cache'
77
- @curl.headers['Accept'] = 'application/json, text/javascript, */*; q=0.01'
78
- @curl.headers['Origin'] = 'https://ing.ingdirect.es'
79
- @curl.headers['X-Requested-With'] = 'XMLHttpRequest'
80
- @curl.headers['Content-Type'] = 'application/json; charset=utf-8'
81
- @curl.headers['Referer'] = 'https://ing.ingdirect.es/login/'
82
- @curl.headers['Accept-Encoding'] = 'zip,deflate,sdch'
83
- @curl.headers['Accept-Language'] = 'n,es;q=0.8'
84
- @curl.headers['Cookie'] = 's_cc=true; s_mca=Direct; s_gts=1; s_nr=1414955726141; s_sq=%5B%5BB%5D%5D'
85
-
86
- response = @curl.delete
87
- pp response
88
- end
89
-
90
- def login
91
- param = '{"loginDocument":{"documentType":0,"document":"' + @dni.to_s +
92
- '"},"birthday":"' + @birthday.to_s + '","companyDocument":null,"device":"desktop"}'
93
- puts param
94
- @curl.url = LOGIN_ENDPOINT
95
- @curl.headers['Host'] = 'ing.ingdirect.es'
96
- @curl.headers['Connection'] = 'keep-alive'
97
- @curl.headers['Pragma'] = 'no-cache'
98
-
99
- @curl.headers['Accept'] = 'application/json, text/javascript, */*; q=0.01'
100
- @curl.headers['Origin'] = 'https://ing.ingdirect.es'
101
- @curl.headers['X-Requested-With'] = 'XMLHttpRequest'
102
- @curl.headers['Content-Type'] = 'application/json; charset=utf-8'
103
- @curl.headers['Referer'] = 'https://ing.ingdirect.es/login/'
104
- @curl.headers['Accept-Encoding'] = 'zip,deflate,sdch'
105
- @curl.headers['Accept-Language'] = 'n,es;q=0.8'
106
- @curl.headers['Cookie'] = 's_cc=true; s_mca=Direct; s_gts=1; s_nr=1414955726141; s_sq=%5B%5BB%5D%5D'
107
-
108
- response = post(LOGIN_ENDPOINT, param)
109
- # response = @curl.body_str
110
- response = JSON.parse(response)
111
- positions = response['pinPositions']
112
- pinpad = response['pinpad']
113
-
114
- save_pinpad_numbers(pinpad)
115
- pinpad_numbers = recognize_pinpad_numbers
116
-
117
- get_correct_positions(pinpad_numbers, positions)
118
- end
119
-
120
- def pass_pinpad(positions)
121
- param = "{\"pinPositions\": #{positions}}"
122
- @curl.url = LOGIN_ENDPOINT
123
- @curl.headers['Host'] = 'ing.ingdirect.es'
124
- @curl.headers['Connection'] = 'keep-alive'
125
- @curl.headers['Accept'] = 'application/json, text/javascript, */*; q=0.01'
126
- @curl.headers['Origin'] = 'https://ing.ingdirect.es'
127
- @curl.headers['X-Requested-With'] = 'XMLHttpRequest'
128
- @curl.headers['Content-Type'] = 'application/json; charset=utf-8'
129
- @curl.headers['Referer'] = 'https://ing.ingdirect.es/login/?clientId=281afde24c938607e5edeac6239e8a38&continue=%2Fpfm%2F'
130
- @curl.headers['Accept-Encoding'] = 'gzip,deflate,sdch'
131
-
132
- response = put(LOGIN_ENDPOINT, param)
133
- response = ActiveSupport::Gzip.decompress(response)
134
- response = JSON.parse(response)
135
-
136
- response['ticket']
137
- end
138
-
139
- def post_auth(ticket)
140
- @curl.headers['Host'] = 'ing.ingdirect.es'
141
- @curl.headers['Connection'] = 'keep-alive'
142
- @curl.headers['Accept'] = 'application/json, text/javascript, */*; q=0.01'
143
- @curl.headers['Origin'] = 'https://ing.ingdirect.es'
144
- @curl.headers['X-Requested-With'] = 'XMLHttpRequest'
145
- @curl.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
146
- @curl.headers['Referer'] = 'https://ing.ingdirect.es/login'
147
- @curl.headers['Accept-Encoding'] = 'gzip,deflate'
148
-
149
- @curl.url = POST_AUTH_ENDPOINT
150
- param = "ticket=#{ticket}&device=desktop"
151
- @curl.post(param)
152
- response = @curl.body_str
153
- end
154
-
155
- def call_client
156
- @curl.headers['Host'] = 'ing.ingdirect.es'
157
- @curl.headers['Connection'] = 'keep-alive'
158
- @curl.headers['Accept'] = 'application/json, text/javascript, */*; q=0.01'
159
- @curl.headers['X-Requested-With'] = 'XMLHttpRequest'
160
- @curl.headers['Content-Type'] = 'application/json; charset=utf-8'
161
- @curl.headers['Referer'] = 'https://ing.ingdirect.es/pfm'
162
- @curl.headers['Accept-Encoding'] = 'gzip,deflate,sdch'
163
-
164
- response = get(CLIENT_ENDPOINT)
165
-
166
- response = ActiveSupport::Gzip.decompress(response)
167
- response = JSON.parse(response)
168
- end
169
-
170
- def get_products
171
- @curl.headers['Host'] = 'ing.ingdirect.es'
172
- @curl.headers['Connection'] = 'keep-alive'
173
- @curl.headers['Accept'] = '*/*'
174
- @curl.headers['X-Requested-With'] = 'XMLHttpRequest'
175
- @curl.headers['Content-Type'] = 'application/json; charset=utf-8'
176
- @curl.headers['Referer'] = 'https://ing.ingdirect.es/pfm'
177
- @curl.headers['Accept-Encoding'] = 'gzip,deflate,sdch'
178
-
179
- response = get(PRODUCTS_ENDPOINT)
180
-
181
- File.open('response_raw.txt', 'w') { |file| file.write(response) }
182
- response = ActiveSupport::Gzip.decompress(response)
183
- File.open('response_decompressed.txt', 'w') { |file| file.write(response) }
184
- File.open('response_parsed.txt', 'w') { |file| file.write(JSON.parse(response)) }
185
- end
186
-
187
- def save_pinpad_numbers(pinpad)
188
- pinpad.each_with_index do |p,index|
189
- File.open(build_tmp_path(index), 'wb'){ |f| f.write(Base64.decode64(p)) }
190
- end
191
- end
192
-
193
- def build_tmp_path(number)
194
- "tmp/original_pinpad_#{number}.png"
195
- end
196
-
197
- def recognize_pinpad_numbers
198
- pinpad_numbers = []
199
- 0.upto(9) do |i|
200
- pinpad = Magick::ImageList.new(build_tmp_path(i)).first
201
-
202
- differences = []
203
- 0.upto(9) do |j|
204
- pinpad_pixels_sample = pinpad.get_pixels(0,0, SAMPLE_WIDTH, SAMPLE_HEIGHT)
205
-
206
- img = Magick::ImageList.new("numbers/pinpad#{j}.png").first
207
- number_pixels_sample = img.get_pixels(0, 0, SAMPLE_WIDTH, SAMPLE_HEIGHT)
208
- diff = 0
209
- pinpad_pixels_sample.each_with_index do |pixel, index|
210
- sample_pixel = number_pixels_sample[index]
211
- diff += (pixel.red - sample_pixel.red).abs +
212
- (pixel.green - sample_pixel.green).abs +
213
- (pixel.blue - sample_pixel.blue).abs
214
- end
215
- differences << diff
216
- end
217
-
218
- real_number = differences.each_with_index.min.last
219
- pinpad_numbers << real_number
220
- end
221
-
222
- pinpad_numbers
223
- end
224
-
225
- def get_correct_positions(pinpad_numbers, positions)
226
- first_digit = @password[positions[0] - 1]
227
- second_digit = @password[positions[1] - 1]
228
- third_digit = @password[positions[2] - 1]
229
-
230
- [
231
- pinpad_numbers.index(first_digit.to_i),
232
- pinpad_numbers.index(second_digit.to_i),
233
- pinpad_numbers.index(third_digit.to_i)
234
- ]
235
- end
236
- end
237
- end