ovchipkaart 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: 09ccdb1d5717c059077262bbf71be5c193b0224c
4
+ data.tar.gz: 952b7c38c40a842f689bc4ebcf82c357681e15f1
5
+ SHA512:
6
+ metadata.gz: a9039725bc5bda89975e6bf04990265d17e22481e800661a6d0e2e981283ad052a77036f066eca264f287190165df9510c83576347df1563aecf597d79956da8
7
+ data.tar.gz: 10b222ac46cee284ae09c48b7510a246185d70072c3ec56e7bad50fafa8ddbfd74d83c8c837c59f7ca8fab643654ff0d55474572bc60387a6d5b5dc1a8bcd894
@@ -0,0 +1,19 @@
1
+ .DS_Store
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ credentials.yml
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ bundler_args: --without development
3
+ script: 'bundle exec rspec --tag ~web_driver:true'
4
+ env:
5
+ - TRAVIS_CI=true
6
+ rvm:
7
+ - 1.9.3
8
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'capybara'
6
+ gem 'selenium-webdriver'
7
+ gem 'chromedriver-helper', github: 'mars/chromedriver-helper', ref: '3be3d'
8
+
9
+ gem 'coveralls', require: false
10
+
11
+ group :development, :test do
12
+ gem 'pry', require: false
13
+ gem 'rspec'
14
+ end
15
+
16
+ group :test do
17
+ gem 'rake'
18
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jan van der Pas
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,107 @@
1
+ # OV Chipkaart
2
+
3
+ [![Coverage Status](https://coveralls.io/repos/JanDintel/ovchipkaart/badge.png)](https://coveralls.io/r/JanDintel/ovchipkaart)
4
+ [![Build Status](https://travis-ci.org/JanDintel/ovchipkaart.png?branch=master)](https://travis-ci.org/JanDintel/ovchipkaart)
5
+
6
+ An OV Chipkaart gem, which scrapes the OV Chipkaart website and provides an easy to use API.
7
+
8
+ The OV Chipkaart is the Dutch national transportation card for public transport. The use and transactions are regulated by the TLS.
9
+
10
+
11
+ ## Installation
12
+
13
+ The OV Chipkaart gem makes use of the [chromedriver](https://code.google.com/p/chromedriver/) in combination with [capybara](https://github.com/jnicklas/capybara) and [selenium](https://github.com/SeleniumHQ/selenium) under the hood.
14
+
15
+ For users with homebrew:
16
+
17
+ $ brew install chromedriver
18
+
19
+
20
+ Add the gem to your Gemfile:
21
+
22
+ ```ruby
23
+ gem 'ovchipkaart'
24
+ ```
25
+
26
+ You might need to update the chromedriver after you ran `bundle install`:
27
+
28
+ $ chromedriver-update
29
+
30
+ *Chromedriver update with thanks to [flavorjones/chromedriver-helper](https://github.com/flavorjones/chromedriver-helper) and [mars/chromedriver-helper](https://github.com/flavorjones/chromedriver-helper/pull/9).*
31
+
32
+ ## Configuration
33
+
34
+ You need to provide your credentials from the OV Chipkaart website. You can use a hash or a YAML file:
35
+
36
+ ```ruby
37
+ Ovchipkaart.configure({ username: 'foo', password: 'bar'})
38
+
39
+ Ovchipkaart.configure_with(path_to_yaml_file)
40
+ Ovchipkaart.configure_with('config/credentials.yml')
41
+ ```
42
+
43
+ *Don't forget to put this in your .gitignore*
44
+
45
+ ## Usage
46
+
47
+ Because the TLS doesn't provide an API for the OV Chipkaart, it scrapes the website and downloads the transactions to the filesystem for parsing. Therefore some methods might feel slow.
48
+
49
+ **Balance and timestamp**
50
+
51
+ ```ruby
52
+ ov_chipkaart = Ovchipkaart::Api.new
53
+ ov_chipkaart.balance #=> € 45,30
54
+ ov_chipkaart.last_updated #=> (12-10-2013 20:57)
55
+ ```
56
+
57
+ **Journeys and transactions**
58
+
59
+ Every journey or transaction is a hash with ten keys: `datum`, `checkin`, `vertrek`, `checkuit`, `bestemming`, `bedrag`, `transactie`, `klasse`, `product` and `opmerkingen`.
60
+
61
+ Based on the `transactie` key everything is sorted in six categories:
62
+
63
+ ```ruby
64
+ ov_chipkaart = Ovchipkaart::Api.new
65
+ ```
66
+
67
+ 1. `ov_chipkaart.journeys` contains all the finish journeys.
68
+ 2. `ov_chipkaart.checkins` contains all the checkins, when the journey starts.
69
+ 3. `ov_chipkaart.forgotten_checkouts` contains all the journeys that started, but didn't end, because there was no `checkuit`. These can be used to claim your money back.
70
+ 4. `ov_chipkaart.additions` contains all the additions to the balance of the OV Chipkaart.
71
+ 5. `ov_chipkaart.products` contains all the additions of products to the OV Chipkaart, such as subscriptions and discounts.
72
+ 6. `ov_chipkaart.unclassified` contains everything that couldn't be specified.
73
+
74
+ ## Troubleshooting
75
+
76
+ **Credentials**
77
+
78
+ The OV Chipkaart gem needs your credentials to login on the OV Chipkaart website.
79
+
80
+ If you have trouble configuring your credentials check the YAML for syntax errors and make sure that you only specify an `username` and `password`. Otherwise it will raise an error.
81
+
82
+ **Chromedriver**
83
+
84
+ Make sure you have installed the latest `chromedriver`. You can use homebrew to easily install the `chromedriver`. You might try running the `chromedriver-update` command, which downloads the helper for the `chromedriver` that is used by `selenium` with `capybara`.
85
+
86
+ The OV Chipkaart gem current uses `chromedriver` version `2.8.241036`.
87
+
88
+ **OV Chipkaart results**
89
+
90
+ Since this gem relies on scraping the OV Chipkaart website, it might break during an update of the website. If you experience this issue either help out with a Pull request or send me a message.
91
+
92
+ ## Contributing
93
+
94
+ **TODO/IDEAS:**
95
+
96
+ 1. Clean up the downloaded files if they are too old
97
+ 2. Add configuration option for the webdriver. (Firefox and PhantonJS)
98
+ 3. Add configuration option for the download path
99
+ 4. Encrypt the credentials and decrypt them in runtime with an OTP.
100
+
101
+ **Pull requests:**
102
+
103
+ 1. Fork it
104
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
105
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
106
+ 4. Push to the branch (`git push origin my-new-feature`)
107
+ 5. Create new Pull Request
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+ require 'ovchipkaart/setup'
3
+ require 'ovchipkaart/version'
4
+ require 'ovchipkaart/scraper'
5
+ require 'ovchipkaart/parser'
6
+ require 'ovchipkaart/api'
7
+
8
+ module Ovchipkaart
9
+ class OvchipkaartError < StandardError; end
10
+ class ConfigurationError < OvchipkaartError; end
11
+
12
+ # Configuration
13
+ MANDATORY_CONFIGURATION = { username: nil, password: nil }
14
+ CONFIGURATIONS_KEYS = MANDATORY_CONFIGURATION.merge!({})
15
+
16
+ # Global config hash
17
+ @config = {}
18
+ def self.config
19
+ @config
20
+ end
21
+
22
+ # Configuration with a hash
23
+ def self.configure(options = {})
24
+ MANDATORY_CONFIGURATION.each_key do |mandatory_option|
25
+ unless options.keys.map(&:to_sym).include?(mandatory_option)
26
+ raise ConfigurationError, 'Username and password are mandatory configuration options'
27
+ end
28
+ end
29
+
30
+ options.keys.map(&:to_sym).each do |given_configuration_key|
31
+ unless CONFIGURATIONS_KEYS.keys.include? given_configuration_key
32
+ raise ConfigurationError, 'Unknown configuration given'
33
+ end
34
+ end
35
+
36
+ options.each { |key, value| @config[key.to_sym] = value }
37
+ end
38
+
39
+ # Configuration with a YAML file
40
+ def self.configure_with(path_to_yaml_file)
41
+ begin
42
+ config = YAML::load(IO.read(path_to_yaml_file))
43
+ rescue Errno::ENOENT
44
+ raise ConfigurationError, 'YAML configuration file not found'
45
+ rescue Psych::SyntaxError
46
+ raise ConfigurationError, 'YAML configuration file contains invalid syntax'
47
+ end
48
+
49
+ configure(config)
50
+ end
51
+ end
@@ -0,0 +1,59 @@
1
+ # encoding: utf-8
2
+ module Ovchipkaart
3
+ class Api
4
+ attr_reader :parser
5
+
6
+ def initialize
7
+ @parser = parse_transactions
8
+ end
9
+
10
+ def balance
11
+ balance_and_date[:balance]
12
+ end
13
+
14
+ def last_updated
15
+ balance_and_date[:date]
16
+ end
17
+
18
+ def journeys
19
+ parser.journeys
20
+ end
21
+
22
+ def checkins
23
+ parser.check_ins
24
+ end
25
+
26
+ def forgotten_checkouts
27
+ parser.forgotten_check_outs
28
+ end
29
+
30
+ def additions
31
+ parser.additions
32
+ end
33
+
34
+ def products
35
+ parser.products
36
+ end
37
+
38
+ def unclassified
39
+ parser.others
40
+ end
41
+
42
+ private
43
+
44
+ def balance_and_date
45
+ regex = scraper.balance.match(/(€.*)(\(.*\))/)
46
+ {balance: regex[1].strip, date: regex[2]}
47
+ end
48
+
49
+ def scraper
50
+ @scraper ||= Scraper.scrape
51
+ end
52
+
53
+ def parse_transactions
54
+ parser = Parser.new
55
+ parser.sort_csv_file
56
+ @parser ||= parser
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,64 @@
1
+ # encoding: utf-8
2
+ require 'csv'
3
+
4
+ module Ovchipkaart
5
+ class Parser
6
+ attr_reader :journeys, :check_ins, :additions, :others, :products, :forgotten_check_outs
7
+
8
+ def initialize
9
+ @journeys = []
10
+ @check_ins = []
11
+ @additions = []
12
+ @others = []
13
+ @products = []
14
+ @forgotten_check_outs = []
15
+ end
16
+
17
+ def self.process_transactions
18
+ new.sort_csv_file
19
+ end
20
+
21
+ def sort_csv_file
22
+ transactions_enum = transactions.each
23
+ begin
24
+ transaction = transactions_enum.next
25
+ case transaction[:transactie]
26
+ when "Reis"
27
+ journeys << transaction
28
+ when "Check-in"
29
+ if transactions_enum.peek[:transactie] == "Check-in"
30
+ forgotten_check_outs << transaction
31
+ else
32
+ check_ins << transaction
33
+ end
34
+ when "Saldo opgeladen"
35
+ additions << transaction
36
+ when "Product op kaart gezet"
37
+ products << transaction
38
+ else
39
+ others << transaction
40
+ end
41
+ rescue StopIteration
42
+ break
43
+ end while true
44
+ end
45
+
46
+ def parse_csv_file
47
+ CSV.new(csv_file, headers: true, header_converters: :symbol, col_sep: ';')
48
+ end
49
+
50
+ def csv_file
51
+ File.read last_downloaded_file
52
+ end
53
+
54
+ def last_downloaded_file
55
+ Dir['tmp/downloads/*'].sort_by{ |f| File.basename(f) }.first
56
+ end
57
+
58
+ private
59
+
60
+ def transactions
61
+ @transactions ||= parse_csv_file.to_a.map { |row| row.to_hash }
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,68 @@
1
+ # encoding: utf-8
2
+ module Ovchipkaart
3
+ class Scraper
4
+ include Capybara::DSL
5
+
6
+ attr_reader :balance
7
+
8
+ def self.scrape
9
+ scraper = new
10
+ scraper.visit_ovchipkaart
11
+ scraper.login_user
12
+ scraper.visit_transaction_overview
13
+ scraper.select_transaction_period
14
+ scraper.checkbox_all_transactions
15
+ scraper.download_transaction_history
16
+ scraper.find_balance
17
+ scraper
18
+ end
19
+
20
+ def visit_ovchipkaart
21
+ visit('https://www.ov-chipkaart.nl/login/')
22
+ accept_cookies if need_to_accept_cookies?
23
+ self
24
+ end
25
+
26
+ def login_user
27
+ fill_in 'gebruikersnaam', with: Ovchipkaart.config[:username]
28
+ fill_in 'wachtwoord', with: Ovchipkaart.config[:password]
29
+ click_button 'Inloggen'
30
+ self
31
+ end
32
+
33
+ def visit_transaction_overview
34
+ click_link 'Transactieoverzicht'
35
+ self
36
+ end
37
+
38
+ def select_transaction_period
39
+ select('2013', from: 'periodes')
40
+ click_button 'Transacties tonen'
41
+ self
42
+ end
43
+
44
+ def checkbox_all_transactions
45
+ check('transactiePanel:container:transacties.declareerAlles')
46
+ self
47
+ end
48
+
49
+ def download_transaction_history
50
+ click_link 'Opslaan als CSV'
51
+ self
52
+ end
53
+
54
+ def find_balance
55
+ @balance = all('span', text: /[€,\d]/)[5].text
56
+ end
57
+
58
+ private
59
+
60
+ def need_to_accept_cookies?
61
+ !find_link('Ik accepteer deze cookies').nil? rescue Capybara::ElementNotFound
62
+ end
63
+
64
+ def accept_cookies
65
+ click_on('Ik accepteer deze cookies') rescue Capybara::ElementNotFound
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+ module Ovchipkaart
3
+ module Setup
4
+ # YAML Configuration
5
+ require 'yaml'
6
+
7
+ # Capybara
8
+ require 'capybara'
9
+ require 'capybara/dsl'
10
+ require 'selenium/webdriver'
11
+
12
+ Capybara.register_driver :selenium_chrome do |app|
13
+ preferences = {"download" => {"default_directory" => Dir.getwd + '/tmp/downloads', "directory_upgrade" => true, "extensions_to_open" => ""}}
14
+ capabilities = Selenium::WebDriver::Remote::Capabilities.chrome
15
+ capabilities['chromeOptions'] = {'prefs' => preferences}
16
+ Capybara::Selenium::Driver.new(app, :detach => false, :browser => :chrome, :desired_capabilities => capabilities)
17
+ end
18
+
19
+ Capybara.default_driver = :selenium_chrome
20
+ Capybara.run_server = false
21
+ end
22
+ end
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+ module Ovchipkaart
3
+ VERSION = '0.0.1'
4
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'ovchipkaart/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'ovchipkaart'
9
+ spec.version = Ovchipkaart::VERSION
10
+ spec.authors = ['Jan van der Pas']
11
+ spec.email = ['janvanderpas@gmail.com']
12
+ spec.description = "An unofficial OV Chipkaart gem, which scrapes the OV Chipkaart website (ov-chipkaart.nl) and provides an easy to use Ruby API."
13
+ spec.summary = 'Unofficial Ruby OV Chipkaart API gem'
14
+ spec.homepage = ''
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files`.split($/)
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.requirements << 'chromedriver, v2.8'
23
+ spec.post_install_message = "Make sure that the chromedriver is installed before using the ovchipkaart gem."
24
+
25
+ spec.add_runtime_dependency 'nokogiri', '~> 1.6.0'
26
+ spec.add_runtime_dependency 'capybara', '~> 2.2.0'
27
+ spec.add_runtime_dependency 'selenium-webdriver', '~> 2.38.0'
28
+
29
+ spec.add_development_dependency 'bundler', '~> 1.3'
30
+ spec.add_development_dependency 'rake'
31
+ end
@@ -0,0 +1,33 @@
1
+ module Factory
2
+
3
+ def self.journeys
4
+ [{:datum=>"13-12-2013", :checkin=>"", :vertrek=>"Grote Markt", :checkuit=>"19:57", :bestemming=>"Muurbloemweg", :bedrag=>"1,76", :transactie=>"Reis", :klasse=>"", :product=>"", :opmerkingen=>""},
5
+ {:datum=>"13-12-2013", :checkin=>"", :vertrek=>"Leiden Centraal", :checkuit=>"09:20", :bestemming=>"Haag HS (Den)", :bedrag=>"1,90", :transactie=>"Reis", :klasse=>"2", :product=>"Altijd Voordeel (maandabonnement)", :opmerkingen=>""},
6
+ {:datum=>"12-12-2013", :checkin=>"", :vertrek=>"Haag HS (Den)", :checkuit=>"18:30", :bestemming=>"Leiden Centraal", :bedrag=>"2,60", :transactie=>"Reis", :klasse=>"2", :product=>"Altijd Voordeel (maandabonnement)", :opmerkingen=>""},
7
+ {:datum=>"12-12-2013", :checkin=>"", :vertrek=>"Leiden Centraal", :checkuit=>"09:21", :bestemming=>"Haag HS (Den)", :bedrag=>"1,90", :transactie=>"Reis", :klasse=>"2", :product=>"Altijd Voordeel (maandabonnement)", :opmerkingen=>""}]
8
+ end
9
+
10
+ def self.checkins
11
+ [{:datum=>"13-12-2013", :checkin=>"19:38", :vertrek=>"Grote Markt", :checkuit=>"", :bestemming=>"", :bedrag=>"-4,00", :transactie=>"Check-in", :klasse=>"", :product=>"", :opmerkingen=>""},
12
+ {:datum=>"13-12-2013", :checkin=>"08:57", :vertrek=>"Leiden Centraal", :checkuit=>"", :bestemming=>"", :bedrag=>"-10,00", :transactie=>"Check-in", :klasse=>"", :product=>"", :opmerkingen=>""},
13
+ {:datum=>"12-12-2013", :checkin=>"18:10", :vertrek=>"Haag HS (Den)", :checkuit=>"", :bestemming=>"", :bedrag=>"-10,00", :transactie=>"Check-in", :klasse=>"", :product=>"", :opmerkingen=>""},
14
+ {:datum=>"12-12-2013", :checkin=>"08:55", :vertrek=>"Leiden Centraal", :checkuit=>"", :bestemming=>"", :bedrag=>"-10,00", :transactie=>"Check-in", :klasse=>"", :product=>"", :opmerkingen=>""},
15
+ {:datum=>"11-12-2013", :checkin=>"8:10", :vertrek=>"Leiden Centraal", :checkuit=>"", :bestemming=>"", :bedrag=>"-10,00", :transactie=>"Check-in", :klasse=>"", :product=>"", :opmerkingen=>""}]
16
+ end
17
+
18
+ def self.forgotten_checkouts
19
+ [{:datum=>"11-12-2013", :checkin=>"8:15", :vertrek=>"Den Haag Laan van NOI", :checkuit=>"", :bestemming=>"", :bedrag=>"-10,00", :transactie=>"Check-in", :klasse=>"", :product=>"", :opmerkingen=>""}]
20
+ end
21
+
22
+ def self.additions
23
+ [{:datum=>"12-12-2013", :checkin=>"08:52", :vertrek=>"", :checkuit=>"", :bestemming=>"", :bedrag=>"50,00", :transactie=>"Saldo opgeladen", :klasse=>"", :product=>"", :opmerkingen=>""}]
24
+ end
25
+
26
+ def self.products
27
+ [{:datum=>"10-12-2013", :checkin=>"8:00", :vertrek=>"", :checkuit=>"", :bestemming=>"", :bedrag=>"", :transactie=>"Product op kaart gezet", :klasse=>"2", :product=>"Altijd Voordeel (maandabonnement)", :opmerkingen=>""}]
28
+ end
29
+
30
+ def self.unclassified
31
+ [{:datum=>"9-12-2013", :checkin=>"7:00", :vertrek=>"", :checkuit=>"", :bestemming=>"", :bedrag=>"", :transactie=>"Foo", :klasse=>"", :product=>"", :opmerkingen=>""}]
32
+ end
33
+ end
@@ -0,0 +1,6 @@
1
+ module Factory
2
+
3
+ def self.csv_file
4
+ File.read 'spec/fixtures/test_transaction_1.csv'
5
+ end
6
+ end
@@ -0,0 +1,2 @@
1
+ foo
2
+ bar: baz
@@ -0,0 +1,14 @@
1
+ "Datum";"Check-in";"Vertrek";"Check-uit";"Bestemming";"Bedrag";"Transactie";"Klasse";"Product";"Opmerkingen"
2
+ "13-12-2013";"";"Grote Markt";"19:57";"Muurbloemweg";"1,76";"Reis";"";"";""
3
+ "13-12-2013";"19:38";"Grote Markt";"";"";"-4,00";"Check-in";"";"";""
4
+ "13-12-2013";"";"Leiden Centraal";"09:20";"Haag HS (Den)";"1,90";"Reis";"2";"Altijd Voordeel (maandabonnement)";""
5
+ "13-12-2013";"08:57";"Leiden Centraal";"";"";"-10,00";"Check-in";"";"";""
6
+ "12-12-2013";"";"Haag HS (Den)";"18:30";"Leiden Centraal";"2,60";"Reis";"2";"Altijd Voordeel (maandabonnement)";""
7
+ "12-12-2013";"18:10";"Haag HS (Den)";"";"";"-10,00";"Check-in";"";"";""
8
+ "12-12-2013";"";"Leiden Centraal";"09:21";"Haag HS (Den)";"1,90";"Reis";"2";"Altijd Voordeel (maandabonnement)";""
9
+ "12-12-2013";"08:55";"Leiden Centraal";"";"";"-10,00";"Check-in";"";"";""
10
+ "12-12-2013";"08:52";"";"";"";"50,00";"Saldo opgeladen";"";"";""
11
+ "11-12-2013";"8:15";"Den Haag Laan van NOI";"";"";"-10,00";"Check-in";"";"";""
12
+ "11-12-2013";"8:10";"Leiden Centraal";"";"";"-10,00";"Check-in";"";"";""
13
+ "10-12-2013";"8:00";"";"";"";"";"Product op kaart gezet";"2";"Altijd Voordeel (maandabonnement)";""
14
+ "9-12-2013";"7:00";"";"";"";"";"Foo";"";"";""
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Ovchipkaart::Api do
5
+ let(:csv_file) { Factory.csv_file }
6
+ let(:journeys) { Factory.journeys }
7
+ let(:checkins) { Factory.checkins }
8
+ let(:forgotten_checkouts) { Factory.forgotten_checkouts }
9
+ let(:additions) { Factory.additions }
10
+ let(:products) { Factory.products }
11
+ let(:unclassified) { Factory.unclassified }
12
+
13
+ describe 'public API' do
14
+ before { Ovchipkaart::Parser.any_instance.stub(:csv_file).and_return csv_file }
15
+
16
+ specify { expect(subject.journeys).to eql journeys }
17
+ specify { expect(subject.checkins).to eql checkins }
18
+ specify { expect(subject.forgotten_checkouts).to eql forgotten_checkouts }
19
+ specify { expect(subject.additions).to eql additions }
20
+ specify { expect(subject.products).to eql products }
21
+ specify { expect(subject.unclassified).to eql unclassified }
22
+
23
+ context 'with web_driver', :web_driver do
24
+ before { Ovchipkaart::Scraper.any_instance.stub(:balance).and_return "€ 30,51 (19-12-2013 20:57)" }
25
+
26
+ specify { expect(subject.balance).to eql '€ 30,51' }
27
+ specify { expect(subject.last_updated).to eql '(19-12-2013 20:57)' }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,63 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Ovchipkaart::Parser do
5
+ let(:csv_file) { Factory.csv_file }
6
+ let(:csv_file_stub) { described_class.any_instance.stub(:csv_file).and_return csv_file }
7
+
8
+ describe '.process_transactions' do
9
+ it 'sorts the transactions' do
10
+ expect_any_instance_of(described_class).to receive :sort_csv_file
11
+ described_class.process_transactions
12
+ end
13
+ end
14
+
15
+ describe '#sort_csv_file' do
16
+ before { csv_file_stub }
17
+ before { subject.sort_csv_file }
18
+
19
+ specify { expect(subject.journeys.count).to eql 4 }
20
+ specify { expect(subject.check_ins.count).to eql 5 }
21
+ specify { expect(subject.additions.count).to eql 1 }
22
+ specify { expect(subject.products.count).to eql 1 }
23
+ specify { expect(subject.others.count).to eql 1 }
24
+ specify {
25
+ expect(subject.forgotten_check_outs.count).to eql 1 }
26
+ end
27
+
28
+ describe '#parse_csv_file' do
29
+ before { csv_file_stub }
30
+
31
+ it 'parses the csv file' do
32
+ expect(CSV).to receive(:new).with(csv_file, { headers: true, header_converters: :symbol, col_sep: ";" })
33
+ subject.parse_csv_file
34
+ end
35
+ end
36
+
37
+ describe 'downloaded csv file' do
38
+ let(:tmp_dir) { "#{Dir.getwd}/tmp/downloads"}
39
+ let(:file_1_path) { "#{tmp_dir}/transacties_-0001.csv" }
40
+ let(:file_2_path) { "#{tmp_dir}/transacties_-0002.csv" }
41
+
42
+ before do
43
+ FileUtils.mkdir_p tmp_dir
44
+ File.open(file_1_path, 'w+') { |file| file.write csv_file }
45
+ File.new file_2_path, 'w+'
46
+ end
47
+
48
+ after do
49
+ FileUtils.rm file_1_path
50
+ FileUtils.rm file_2_path
51
+ end
52
+
53
+ context '#csv_file' do
54
+ specify { expect(subject.csv_file).to eql csv_file }
55
+ end
56
+
57
+ context '#last_downloaded_file' do
58
+ it 'sorts the file on their basename and returns first one' do
59
+ expect(subject.last_downloaded_file).to eql 'tmp/downloads/transacties_-0001.csv'
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,70 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require 'capybara/rspec'
4
+
5
+ # Methods in the Ovchipkaart::Scraper class are,
6
+ # dependent on the order of calling them.
7
+ RSpec.configure { |config| config.order = 'default' }
8
+
9
+ describe Ovchipkaart::Scraper, :web_driver do
10
+
11
+ describe '.scrape' do
12
+ it 'scrapes the ovchipkaart website' do
13
+ expect_any_instance_of(described_class).to receive(:visit_ovchipkaart)
14
+ expect_any_instance_of(described_class).to receive(:login_user)
15
+ expect_any_instance_of(described_class).to receive(:visit_transaction_overview)
16
+ expect_any_instance_of(described_class).to receive(:select_transaction_period)
17
+ expect_any_instance_of(described_class).to receive(:checkbox_all_transactions)
18
+ expect_any_instance_of(described_class).to receive(:download_transaction_history)
19
+ expect_any_instance_of(described_class).to receive(:find_balance)
20
+ described_class.scrape
21
+ end
22
+ end
23
+
24
+ describe '#visit_ovchipkaart' do
25
+ it 'lands on the login page of ovchipkaart' do
26
+ expect(subject.visit_ovchipkaart).to have_content 'Inloggen op Mijn OV-chipkaart'
27
+ end
28
+ end
29
+
30
+ describe '#login_user' do
31
+ it 'fills out the user credentials and logs in' do
32
+ expect(subject.login_user).to have_content 'Transactieoverzicht'
33
+ end
34
+ end
35
+
36
+ describe '#visit_transaction_overview' do
37
+ it 'visits the transaction overview page' do
38
+ expect(subject.visit_transaction_overview).to have_content 'Mijn transactieoverzicht'
39
+ end
40
+ end
41
+
42
+ describe '#select_transaction_period' do
43
+ it 'changes the time period of the transaction history' do
44
+ expect(subject.select_transaction_period).to have_select('periodes', selected: '2013')
45
+ end
46
+ end
47
+
48
+ describe '#checkbox_all_transactions' do
49
+ it 'checks on all transactions' do
50
+ expect(subject.checkbox_all_transactions).to have_checked_field('transactiePanel:container:transacties.declareerAlles', checked: true)
51
+ end
52
+ end
53
+
54
+ describe '#download_transaction_history' do
55
+ let(:download_path) { Dir.getwd + '/tmp/downloads/*' }
56
+
57
+ it 'downloads the transaction history' do
58
+ # Give time to process download before calling downloads count again
59
+ expect{ subject.download_transaction_history; sleep 3 }.to change{ Dir[download_path].length }.by(1)
60
+ end
61
+ end
62
+
63
+ describe '#find_balance' do
64
+ before { subject.find_balance }
65
+
66
+ it 'scrapes the balance of the transaction overview page' do
67
+ expect(subject.balance).to match /(€+\s+\d*,+\d*)+\s*(\(\d*-\d*-\d*\s\d*:\d*\))/
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Ovchipkaart do
5
+
6
+ describe 'configuration' do
7
+ context 'valid configuration' do
8
+ specify { expect{ subject.configure({ username: 'foo', password: 'bar' }) }.not_to raise_error }
9
+
10
+ it 'sets the configuration' do
11
+ subject.configure({ username: 'foo', password: 'bar' })
12
+ expect(subject.config[:username]).to eql 'foo'
13
+ expect(subject.config[:password]).to eql 'bar'
14
+ end
15
+ end
16
+
17
+ context 'mandatory configuration' do
18
+ specify { expect(subject.const_get :MANDATORY_CONFIGURATION).to eql({:username=>nil, :password=>nil}) }
19
+
20
+ it 'raises an Ovchipkaart::ConfigurationError without the mandatory configuration' do
21
+ expect{ subject.configure({ foo: 'bar' }) }.to raise_error Ovchipkaart::ConfigurationError, 'Username and password are mandatory configuration options'
22
+ end
23
+ end
24
+
25
+ context 'configuration keys' do
26
+ it 'raises an Ovchipkaart::ConfigurationError with invalid configuration keys' do
27
+ expect{ subject.configure({ username: 'foo', password: 'bar', baz: 'foobar' }) }.to raise_error Ovchipkaart::ConfigurationError, 'Unknown configuration given'
28
+ end
29
+ end
30
+
31
+ context 'YAML configuration' do
32
+ it 'raises an Ovchipkaart::ConfigurationError without a YAML file' do
33
+ expect{ subject.configure_with('') }.to raise_error Ovchipkaart::ConfigurationError, 'YAML configuration file not found'
34
+ end
35
+
36
+ it 'raises an Ovchipkaart::ConfigurationError with an invalid YAML file' do
37
+ expect{ subject.configure_with('spec/fixtures/invalid_configuration.yml') }.to raise_error Ovchipkaart::ConfigurationError, 'YAML configuration file contains invalid syntax'
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ require 'simplecov'
2
+ require 'coveralls'
3
+
4
+ SimpleCov.formatter = Coveralls::SimpleCov::Formatter
5
+ if ENV['TRAVIS_CI'].nil?
6
+ SimpleCov.start
7
+ else
8
+ SimpleCov.start do
9
+ add_filter 'lib/ovchipkaart/scraper.rb'
10
+ add_filter 'spec/lib/ovchipkaart/scraper_spec.rb'
11
+ end
12
+ end
13
+
14
+ require 'ovchipkaart'
15
+
16
+ # Add support files
17
+ Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")].each { |file| require file }
18
+
19
+ # Add factories
20
+ Dir[File.join(File.dirname(__FILE__), "factories/**/*.rb")].each { |file| require file }
21
+
22
+ RSpec.configure do |config|
23
+ config.expect_with :rspec do |c|
24
+ c.syntax = :expect
25
+ end
26
+
27
+ config.treat_symbols_as_metadata_keys_with_true_values = true
28
+
29
+ config.order = 'random'
30
+ end
@@ -0,0 +1 @@
1
+ Ovchipkaart.configure_with('spec/support/credentials.yml') if ENV['TRAVIS_CI'].nil?
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ovchipkaart
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jan van der Pas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-12-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 1.6.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 1.6.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: capybara
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 2.2.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 2.2.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: selenium-webdriver
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 2.38.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 2.38.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: An unofficial OV Chipkaart gem, which scrapes the OV Chipkaart website
84
+ (ov-chipkaart.nl) and provides an easy to use Ruby API.
85
+ email:
86
+ - janvanderpas@gmail.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - .gitignore
92
+ - .rspec
93
+ - .travis.yml
94
+ - Gemfile
95
+ - LICENSE.txt
96
+ - README.md
97
+ - Rakefile
98
+ - lib/ovchipkaart.rb
99
+ - lib/ovchipkaart/api.rb
100
+ - lib/ovchipkaart/parser.rb
101
+ - lib/ovchipkaart/scraper.rb
102
+ - lib/ovchipkaart/setup.rb
103
+ - lib/ovchipkaart/version.rb
104
+ - ovchipkaart.gemspec
105
+ - spec/factories/api_factory.rb
106
+ - spec/factories/csv_factory.rb
107
+ - spec/fixtures/invalid_configuration.yml
108
+ - spec/fixtures/test_transaction_1.csv
109
+ - spec/lib/ovchipkaart/api_spec.rb
110
+ - spec/lib/ovchipkaart/parser_spec.rb
111
+ - spec/lib/ovchipkaart/scraper_spec.rb
112
+ - spec/lib/ovchipkaart_spec.rb
113
+ - spec/spec_helper.rb
114
+ - spec/support/ovchipkaart_configuration_support.rb
115
+ homepage: ''
116
+ licenses:
117
+ - MIT
118
+ metadata: {}
119
+ post_install_message: Make sure that the chromedriver is installed before using the
120
+ ovchipkaart gem.
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - '>='
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements:
135
+ - chromedriver, v2.8
136
+ rubyforge_project:
137
+ rubygems_version: 2.1.11
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: Unofficial Ruby OV Chipkaart API gem
141
+ test_files:
142
+ - spec/factories/api_factory.rb
143
+ - spec/factories/csv_factory.rb
144
+ - spec/fixtures/invalid_configuration.yml
145
+ - spec/fixtures/test_transaction_1.csv
146
+ - spec/lib/ovchipkaart/api_spec.rb
147
+ - spec/lib/ovchipkaart/parser_spec.rb
148
+ - spec/lib/ovchipkaart/scraper_spec.rb
149
+ - spec/lib/ovchipkaart_spec.rb
150
+ - spec/spec_helper.rb
151
+ - spec/support/ovchipkaart_configuration_support.rb