bos 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a431e5d2c758bebe674427c3315193ad5cd37e76
4
+ data.tar.gz: 530fea7769d8dc1487a185af0ff04da80cda0ac0
5
+ SHA512:
6
+ metadata.gz: 9c4fe7f4f1629ffe8c4673dc534e611a5c96eafa2207adcda075b987f2fe04f738326a2b63d6f4b1d612b3c7c55fd60060f8d05c64d1e55cef1e572339c8dd40
7
+ data.tar.gz: 54869da17dc46b89e23347ef1fc041aa38e42f3635eb2b42705312012ea8ca5f69051ce1545898abe8f9b31359abd4276c06d0b6ee73bf7136c3e6868def1ac8
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bos.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Jingkai He
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,52 @@
1
+ # BOS
2
+
3
+ BOS is a ruby client that scrape your banking details from your bank of scotland web page. You are higly under risk (e.g. online bank account get blocked; Either password or security code leaks) when using it. So please do use it responsibly (The author of the gem under any circumstances will not responsible for any lost).
4
+
5
+ It seems like Lloyds Bank plc, TSB Bank plc, Bank of Scotland plc and Halifax, as all of them belong to Lloyds Banking Group, are sharing the same banking system. As a result, you might be able to migrate the gem to some other banking systems without too much difficulties. Again, please do use it responsibly!
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'bos'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install bos
22
+
23
+ ## Usage
24
+
25
+ BOS is a screen scraper that collect you your banking details.
26
+
27
+ If you use it for the first time, then you need do configuration. The code shown below would store your banking details into ~/.bos file and persit in JSON format.
28
+
29
+ ```ruby
30
+ BOS.confg USER_ID, PASSWORD, SECURITY_CODE
31
+ ```
32
+
33
+ The code snippet below show the basic usage of BOS gem (very easy to understand, isn't it?)
34
+
35
+ ```ruby
36
+ client = BOS.client # Return a bos client.
37
+
38
+ client.balance
39
+ client.account_number
40
+ client.sort_code
41
+ client.mini_statement
42
+ client.full_statement
43
+ ```
44
+
45
+
46
+ ## Contributing
47
+
48
+ 1. Fork it ( https://github.com/jaxi/bos/fork )
49
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
50
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
51
+ 4. Push to the branch (`git push origin my-new-feature`)
52
+ 5. Create a new Pull Request
@@ -0,0 +1,16 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ desc "Run an IRB session with BOS preloaded"
4
+ task :console do
5
+ exec "irb -I lib -r bos"
6
+ end
7
+
8
+ require 'rake/testtask'
9
+
10
+ task :default => :test
11
+
12
+ task :test do
13
+ require "bos"
14
+ $LOAD_PATH.unshift('lib', 'test')
15
+ Dir.glob('./test/**/*_test.rb') { |f| require f }
16
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'bos/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "bos"
8
+ spec.version = BOS::VERSION
9
+ spec.authors = ["Jingkai He"]
10
+ spec.email = ["jaxihe@gmail.com"]
11
+ spec.summary = %q{Bank of Scotland screen scraper}
12
+ spec.description = %q{Bank of Scotland screen scraper}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "mechanize", "~> 2.7.3"
22
+ spec.add_dependency "thor", "~> 0.19.1"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.7"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "minitest", "~> 5.4.3"
27
+ end
@@ -0,0 +1,54 @@
1
+ require "mechanize"
2
+ require "json"
3
+
4
+ require "bos/version"
5
+ require "bos/query"
6
+ require "bos/user"
7
+
8
+ module BOS
9
+ LOGIN_PAGE = "https://online.bankofscotland.co.uk/personal/logon/login.jsp".freeze
10
+ class << self
11
+ def agent
12
+ @agent ||= begin
13
+ a = Mechanize.new
14
+ a.agent.http.verify_mode = OpenSSL::SSL::VERIFY_NONE
15
+ a
16
+ end
17
+ end
18
+
19
+ # Public: Configure the user_id, password and security code
20
+ #
21
+ # Return: Nothing
22
+ def config(options = {})
23
+ if options[:file]
24
+ filename = options[:filename] || File.join(Dir.home, ".bos")
25
+ config_hash = JSON.parse(IO.read(filename), symbolize_names: true)
26
+ @user_id = config_hash[:user_id]
27
+ @password = config_hash[:password]
28
+ @security_code = config_hash[:security_code]
29
+ else
30
+ @user_id = options[:user_id]
31
+ @password = options[:password]
32
+ @security_code = options[:security_code]
33
+ File.open(File.join(Dir.home, ".bos"), "w") do |f|
34
+ f.write options.to_json
35
+ end
36
+ end
37
+ end
38
+
39
+ # Public: The client that answer your questions about your banking
40
+ #
41
+ # Returns: A BOS::User object
42
+ def client
43
+ @client ||= begin
44
+ config(file: true) if !user_id
45
+ User.new(user_id, password, security_code)
46
+ end
47
+ end
48
+
49
+ private
50
+ attr_reader :user_id, :password, :security_code
51
+ end
52
+
53
+ class LoginError < StandardError; end
54
+ end
@@ -0,0 +1,107 @@
1
+ module BOS
2
+ class Query
3
+ def initialize(session)
4
+ @session = session
5
+ end
6
+
7
+ def balance
8
+ @balance ||= session.at("p[class='balance']/span[2]").text
9
+ end
10
+
11
+ def sort_code
12
+ @sort_code ||= begin
13
+ txt = session.at("p[class='numbers wider']").text
14
+ /\d\d-\d\d-\d\d/.match(txt)[0]
15
+ end
16
+ end
17
+
18
+ def account_number
19
+ @account_number ||= begin
20
+ txt = session.at("p[class='numbers wider']").text
21
+ /\d{8}/.match(txt)[0]
22
+ end
23
+ end
24
+
25
+ def mini_statement_page
26
+ @mini_statement_page ||= begin
27
+ mini_link = session.at("a[id='lstAccLst:0:lstOptions:lkMiniAccountStmt']")
28
+ mini_link = / {ajaxURI:'([\S]+)'}/.match(mini_link.attributes["class"].value)[1]
29
+ BOS.agent.get mini_link
30
+ end
31
+ end
32
+
33
+ def mini_statement
34
+ @mini_statement ||= begin
35
+ rows = mini_statement_page.search('//table/tbody/tr')
36
+ result = []
37
+ rows.each_with_index do |row, index|
38
+ date_ele = row.at('td[1]/span/text()')
39
+ date = date_ele ? Date.parse(date_ele.text) : result[index - 1][:date]
40
+
41
+ description = row.at('td[2]/text()').text.strip
42
+
43
+ income = row.at('td[3]/text()')
44
+ income = income ? income.text.strip.to_f : 0
45
+
46
+ outcome = row.at('td[4]/text()')
47
+ outcome = outcome ? outcome.text.strip.to_f : 0
48
+
49
+ result << {
50
+ date: date,
51
+ description: description,
52
+ income: income,
53
+ outcome: outcome
54
+ }
55
+ end
56
+
57
+ result
58
+ end
59
+ end
60
+
61
+ def full_statement
62
+ @full_statement ||= begin
63
+ rows = full_statement_page.search('//table/tbody/tr')
64
+ result = []
65
+ rows.each do |row|
66
+ date = Date.parse(row.at("th/span/text()"))
67
+ tds = row.search("td")
68
+
69
+ description = tds[0].text.strip
70
+ type = tds[1].text.strip
71
+ income = tds[2].text.to_f
72
+ outcome = tds[3].text.to_f
73
+ balance = tds[4].text
74
+
75
+ result << {
76
+ date: date,
77
+ description: description,
78
+ type: type,
79
+ income: income,
80
+ outcome: outcome,
81
+ balance: balance
82
+ }
83
+ end
84
+
85
+ result
86
+ end
87
+ end
88
+
89
+ def full_statement_page
90
+ @full_statement_page ||= begin
91
+ full_statement_page_link = mini_statement_page
92
+ .at("a[id='miniaccountstatements:lkViewFullStatement']")["href"]
93
+
94
+ BOS.agent.get(full_statement_page_link)
95
+ end
96
+ end
97
+
98
+ # TODO: Implement query search
99
+ def transaction_query(query, start_date, end_date)
100
+ raise NoMethodError, "transaction_query method will come soon"
101
+ end
102
+
103
+ private
104
+
105
+ attr_reader :session
106
+ end
107
+ end
@@ -0,0 +1,84 @@
1
+ module BOS
2
+ class User
3
+ extend ::Forwardable
4
+
5
+ def initialize(user_id, password, security_code)
6
+ @user_id = user_id
7
+ @password = password
8
+ @security_code = security_code
9
+ end
10
+
11
+ delegate [:balance, :sort_code, :account_number,
12
+ :mini_statement, :full_statement] => :query
13
+
14
+ def inspect
15
+ "#<BOS::User:0x#{(object_id << 1).to_s(16)}>"
16
+ end
17
+
18
+ private
19
+
20
+ # Private: Login with the user's id and password
21
+ #
22
+ # Returns: Security Page
23
+ def enter_password
24
+ return if @security_page
25
+
26
+ login_page = BOS.agent.get(LOGIN_PAGE)
27
+ login_form = login_page.form("frmLogin")
28
+ login_form["frmLogin:strCustomerLogin_userID"] = user_id
29
+ login_form["frmLogin:strCustomerLogin_pwd"] = password
30
+
31
+ page = BOS.agent.submit(login_form, login_form.buttons.first)
32
+
33
+ if page.uri.to_s.include? LOGIN_PAGE
34
+ raise LoginError, "ID OR PASSWORD IS WRONG"
35
+ else
36
+ @security_page = page
37
+ end
38
+ end
39
+
40
+ # Private: Enter the security code on security information page
41
+ #
42
+ # Returns: Acccount Overall Page
43
+ def enter_security_code
44
+ return if @overview_page
45
+ security_code_form = security_page.form("frmentermemorableinformation1")
46
+
47
+ number1 = security_page.search("label[for='frmentermemorableinformation1:strEnterMemorableInformation_memInfo1']").text()
48
+ number1 = security_code[/\d+/.match(number1)[0].to_i - 1]
49
+
50
+ number2 = security_page.search("label[for='frmentermemorableinformation1:strEnterMemorableInformation_memInfo2']").text()
51
+ number2 = security_code[/\d+/.match(number2)[0].to_i - 1]
52
+
53
+ number3 = security_page.search("label[for='frmentermemorableinformation1:strEnterMemorableInformation_memInfo3']").text()
54
+ number3 = security_code[/\d+/.match(number3)[0].to_i - 1]
55
+
56
+ security_code_form.field_with(name: "frmentermemorableinformation1:strEnterMemorableInformation_memInfo1")
57
+ .value = "&nbsp;#{number1}"
58
+ security_code_form.field_with(name: "frmentermemorableinformation1:strEnterMemorableInformation_memInfo2")
59
+ .value = "&nbsp;#{number2}"
60
+ security_code_form.field_with(name: "frmentermemorableinformation1:strEnterMemorableInformation_memInfo3")
61
+ .value = "&nbsp;#{number3}"
62
+
63
+ page = BOS.agent.submit(security_code_form, security_code_form.buttons.first)
64
+
65
+ if /account_overview_personal/.match page.uri.to_s
66
+ @overview_page = page
67
+ else
68
+ raise LoginError, "SECURE CODE IS WRONG" unless /account_overview_personal/.match page.uri.to_s
69
+ end
70
+ end
71
+
72
+ def query
73
+ @query ||= begin
74
+ enter_password
75
+ enter_security_code
76
+ Query.new overview_page
77
+ end
78
+ end
79
+
80
+ attr_reader :user_id, :password, :security_code
81
+
82
+ attr_reader :security_page, :overview_page
83
+ end
84
+ end
@@ -0,0 +1,3 @@
1
+ module BOS
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,7 @@
1
+ require "minitest/autorun"
2
+
3
+ describe BOS do
4
+ it "works" do
5
+ BOS::VERSION.must_equal "0.0.1"
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bos
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jingkai He
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mechanize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 2.7.3
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.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.19.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.19.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 5.4.3
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 5.4.3
83
+ description: Bank of Scotland screen scraper
84
+ email:
85
+ - jaxihe@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - Gemfile
92
+ - LICENSE.txt
93
+ - README.md
94
+ - Rakefile
95
+ - bos.gemspec
96
+ - lib/bos.rb
97
+ - lib/bos/query.rb
98
+ - lib/bos/user.rb
99
+ - lib/bos/version.rb
100
+ - test/bos_test.rb
101
+ homepage: ''
102
+ licenses:
103
+ - MIT
104
+ metadata: {}
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubyforge_project:
121
+ rubygems_version: 2.4.5
122
+ signing_key:
123
+ specification_version: 4
124
+ summary: Bank of Scotland screen scraper
125
+ test_files:
126
+ - test/bos_test.rb