bank_scrap 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
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