kenpo_api 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/README.md +86 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/kenpo_api.gemspec +39 -0
- data/lib/kenpo_api.rb +18 -0
- data/lib/kenpo_api/client.rb +72 -0
- data/lib/kenpo_api/resort.rb +130 -0
- data/lib/kenpo_api/routines.rb +45 -0
- data/lib/kenpo_api/service.rb +27 -0
- data/lib/kenpo_api/service_category.rb +51 -0
- data/lib/kenpo_api/service_group.rb +39 -0
- data/lib/kenpo_api/sport.rb +101 -0
- data/lib/kenpo_api/version.rb +3 -0
- metadata +159 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b82bbf5c88a60e118f141f57fba80bbddb6a3faf
|
4
|
+
data.tar.gz: 9c32cfa6e1cf75caeeee598b1ca8487e799b6ad3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 77d4c099e64c304931ac0c19621f5f7332114e2dce0590f803017b01ebbaec8085bfc4e07c4e560c82a5f6fd2ad8430ca889a2dee16b1b4d45e985c47285dc75
|
7
|
+
data.tar.gz: 01541a96c10f03919640366f21ff0aa23f74e07a158c35e6f3cdb9bb86a49b4342f3376456fc970b9e5924d563a287b520468026ea11b08b127467dd681e96f0
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# KenpoApi
|
2
|
+
|
3
|
+
Ruby binding for kenpo reservation API ([関東ITソフトウェア健康保険組合 施設・レクリエーション](https://as.its-kenpo.or.jp/)).
|
4
|
+
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'kenpo_api'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install kenpo_api
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
# Resort reservation
|
26
|
+
resort_names = KenpoApi::Resort.resort_names
|
27
|
+
KenpoApi::Resort.request_reservation_url(resort_name: 'トスラブ箱根和奏林', email: 'matsuno_osomatsu@example.com')
|
28
|
+
url = 'https://as.its-kenpo.or.jp/apply/new?c=aaaaaaaa-bbbb-cccc-dddd-012345678901'
|
29
|
+
criteria = KenpoApi::Resort.check_reservation_criteria(url)
|
30
|
+
reservation_data = {
|
31
|
+
sign_no: 6666,
|
32
|
+
insured_no: 666,
|
33
|
+
office_name: '株式会社FLAG',
|
34
|
+
kana_name: 'マツノ オソマツ',
|
35
|
+
birth_year: 1962,
|
36
|
+
birth_month: 5,
|
37
|
+
birth_day: 24,
|
38
|
+
gender: :man,
|
39
|
+
relationship: :myself,
|
40
|
+
contact_phone: '666-6666-6666',
|
41
|
+
postal_code: '180-0004',
|
42
|
+
state: 13,
|
43
|
+
address: '武蔵野市本町666-666',
|
44
|
+
join_time: '2017-04-15',
|
45
|
+
night_count: 1,
|
46
|
+
stay_persons: 6,
|
47
|
+
room_persons: 6, (1 room) # or [3, 3] (2 rooms) or [2, 2, 2] (3 rooms) ...
|
48
|
+
meeting_dates: nil, # (none) or [1] (first day only) or [1, 2] (both days) ...
|
49
|
+
must_meeting: false,
|
50
|
+
}
|
51
|
+
KenpoApi::Resort.apply_reservation(url, reservation_data)
|
52
|
+
|
53
|
+
# Sport reservation
|
54
|
+
sport_names = KenpoApi::Sport.sport_names
|
55
|
+
KenpoApi::Sport.request_reservation_url(sport_name: 'サマディ門前仲町', email: 'matsuno_osomatsu@example.com')
|
56
|
+
url = 'https://as.its-kenpo.or.jp/apply/new?c=aaaaaaaa-bbbb-cccc-dddd-901234567890'
|
57
|
+
criteria = KenpoApi::Sport.check_reservation_criteria(url)
|
58
|
+
reservation_data = {
|
59
|
+
sign_no: 6666,
|
60
|
+
insured_no: 666,
|
61
|
+
office_name: '株式会社FLAG',
|
62
|
+
kana_name: 'マツノ オソマツ',
|
63
|
+
birth_year: 1962,
|
64
|
+
birth_month: 5,
|
65
|
+
birth_day: 24,
|
66
|
+
contact_phone: '666-6666-6666',
|
67
|
+
postal_code: '180-0004',
|
68
|
+
state: 13,
|
69
|
+
address: '武蔵野市本町666-666',
|
70
|
+
join_time: '2017-03-11',
|
71
|
+
use_time_from: '13:00',
|
72
|
+
use_time_to: '15:00',
|
73
|
+
}
|
74
|
+
KenpoApi::Sport.apply_reservation(url, reservation_data)
|
75
|
+
```
|
76
|
+
|
77
|
+
## Development
|
78
|
+
|
79
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
80
|
+
|
81
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
82
|
+
|
83
|
+
## Contributing
|
84
|
+
|
85
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/tearoom6/kenpo_api.
|
86
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "kenpo_api"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/kenpo_api.gemspec
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'kenpo_api/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'kenpo_api'
|
8
|
+
spec.version = KenpoApi::VERSION
|
9
|
+
spec.authors = ['tearoom6']
|
10
|
+
spec.email = ['tearoom6.biz@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = %q{Unofficial API for ITS kenpo reservation system.}
|
13
|
+
spec.description = %q{Unofficial API for ITS kenpo reservation system.}
|
14
|
+
spec.homepage = 'https://github.com/tearoom6/kenpo_api'
|
15
|
+
|
16
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
17
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
18
|
+
if spec.respond_to?(:metadata)
|
19
|
+
#spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
|
20
|
+
else
|
21
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
22
|
+
'public gem pushes.'
|
23
|
+
end
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
f.match(%r{^(test|spec|features)/})
|
27
|
+
end
|
28
|
+
spec.bindir = 'exe'
|
29
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
30
|
+
spec.require_paths = ['lib']
|
31
|
+
|
32
|
+
spec.add_development_dependency 'bundler', '~> 1.13'
|
33
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
34
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
35
|
+
spec.add_development_dependency 'faraday', '~> 0.10.0'
|
36
|
+
spec.add_development_dependency 'cookiejar', '~> 0.3.3'
|
37
|
+
spec.add_development_dependency 'nokogiri', '~> 1.6'
|
38
|
+
spec.add_development_dependency 'dry-validation', '~> 0.10.5'
|
39
|
+
end
|
data/lib/kenpo_api.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'kenpo_api/version'
|
2
|
+
require 'kenpo_api/client'
|
3
|
+
require 'kenpo_api/service_category'
|
4
|
+
require 'kenpo_api/service_group'
|
5
|
+
require 'kenpo_api/service'
|
6
|
+
require 'kenpo_api/routines'
|
7
|
+
require 'kenpo_api/resort'
|
8
|
+
require 'kenpo_api/sport'
|
9
|
+
|
10
|
+
module KenpoApi
|
11
|
+
class KenpoApiError < StandardError; end
|
12
|
+
class NetworkError < KenpoApiError; end
|
13
|
+
class NotFoundError < KenpoApiError; end
|
14
|
+
class NotAvailableError < KenpoApiError; end
|
15
|
+
class ParseError < KenpoApiError; end
|
16
|
+
class ValidationError < KenpoApiError; end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'faraday'
|
3
|
+
require 'cookiejar'
|
4
|
+
require 'nokogiri'
|
5
|
+
|
6
|
+
module KenpoApi
|
7
|
+
class Client
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
BASE_URL = 'https://as.its-kenpo.or.jp/'
|
11
|
+
|
12
|
+
attr_accessor :timeout, :open_timeout
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@conn = Faraday.new(url: BASE_URL) do |builder|
|
16
|
+
builder.use Faraday::Request::UrlEncoded
|
17
|
+
builder.adapter Faraday.default_adapter
|
18
|
+
end
|
19
|
+
# Set default settings.
|
20
|
+
@timeout = 5
|
21
|
+
@open_timeout = 5
|
22
|
+
@cookiejar = CookieJar::Jar.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def access(path:, method: :get, params: {})
|
26
|
+
response = @conn.send(method, path, params) do |req|
|
27
|
+
req.options.timeout = @timeout
|
28
|
+
req.options.open_timeout = @open_timeout
|
29
|
+
req.headers['cookie'] = @cookiejar.get_cookie_header(@conn.url_prefix.to_s)
|
30
|
+
end
|
31
|
+
raise NetworkError.new("Failed to fetch http content. path: #{path} status_code: #{response.status}") unless response.success?
|
32
|
+
|
33
|
+
@cookiejar.set_cookie(@conn.url_prefix.to_s, response.headers['set-cookie']) if response.headers.has_key?('set-cookie')
|
34
|
+
|
35
|
+
return (yield response) if block_given?
|
36
|
+
response
|
37
|
+
rescue KenpoApiError => e
|
38
|
+
raise e
|
39
|
+
rescue => e
|
40
|
+
raise NetworkError.new("Failed to fetch http content. path: #{path} original_error: #{e.message}")
|
41
|
+
end
|
42
|
+
|
43
|
+
def fetch_document(path:, method: :get, params: {})
|
44
|
+
response = access(path: path, method: method, params: params)
|
45
|
+
document = Nokogiri::HTML(response.body)
|
46
|
+
|
47
|
+
return (yield document) if block_given?
|
48
|
+
document
|
49
|
+
end
|
50
|
+
|
51
|
+
def parse_single_form_page(path:, method: :get, params: {})
|
52
|
+
document = fetch_document(path: path, method: method, params: params)
|
53
|
+
form_element = document.xpath('//form').first
|
54
|
+
|
55
|
+
next_page_info = nil
|
56
|
+
unless form_element.nil?
|
57
|
+
path = form_element['action']
|
58
|
+
method = form_element['method']
|
59
|
+
params = document.xpath('//input[@type="hidden"]').select {|input| ! input['name'].nil?}.map {|input| [input['name'], input['value']]}.to_h
|
60
|
+
next_page_info = {path: path, method: method, params: params}
|
61
|
+
end
|
62
|
+
|
63
|
+
return (yield next_page_info, document) if block_given?
|
64
|
+
return next_page_info, document
|
65
|
+
rescue KenpoApiError => e
|
66
|
+
raise e
|
67
|
+
rescue => e
|
68
|
+
raise ParseError.new("Failed to parse HTML. message: #{e.message}")
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'dry-validation'
|
2
|
+
|
3
|
+
module KenpoApi
|
4
|
+
class Resort
|
5
|
+
extend Routines
|
6
|
+
|
7
|
+
def self.resort_names
|
8
|
+
category = ServiceCategory.find(:resort_reserve)
|
9
|
+
raise NotFoundError.new("Service category not found. code: #{category_code}") if category.nil?
|
10
|
+
category.service_groups.map {|group| group.name}
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.request_reservation_url(resort_name:, email:)
|
14
|
+
service = find_service(category_code: :resort_reserve, group_name: resort_name)
|
15
|
+
request_application_url(service_path: service.path, email: email)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.check_reservation_criteria(application_url)
|
19
|
+
html_document = Client.instance.fetch_document(path: application_url)
|
20
|
+
raise NotAvailableError.new("Application URL is invalid: #{html_document.xpath('//p').first.content}") if html_document.xpath('//form').first.nil?
|
21
|
+
reservation_criteria(html_document)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.apply_reservation(application_url, application_data)
|
25
|
+
apply(application_url: application_url) do |html_document|
|
26
|
+
reservation_data = self.validate_reservation_data(application_data, html_document)
|
27
|
+
convert_to_reservation_post_params(reservation_data)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def self.reservation_criteria(html_document)
|
34
|
+
criteria = {}
|
35
|
+
criteria[:note] = html_document.xpath('//div[@class="note mb10"]').first.text
|
36
|
+
criteria[:service_name] = html_document.xpath('//form/div[@class="form_box"]//dd[@class="elements"]').first.text
|
37
|
+
criteria[:birth_year] = html_document.xpath('id("apply_year")/*/@value') .map {|attr| attr.value }.select {|val| val != '' } # (1917..2017)
|
38
|
+
criteria[:birth_month] = html_document.xpath('id("apply_month")/*/@value') .map {|attr| attr.value }.select {|val| val != '' } # (1..12)
|
39
|
+
criteria[:birth_day] = html_document.xpath('id("apply_day")/*/@value') .map {|attr| attr.value }.select {|val| val != '' } # (1..31)
|
40
|
+
criteria[:gender] = html_document.xpath('id("apply_gender")/*/@value') .map {|attr| attr.value }.map {|val| val.to_sym } # [:man, :woman]
|
41
|
+
criteria[:relationship] = html_document.xpath('id("apply_relationship")/*/@value').map {|attr| attr.value }.map {|val| val.to_sym } # [:myself, :family]
|
42
|
+
criteria[:state] = html_document.xpath('id("apply_state")/*/@value') .map {|attr| attr.value }.select {|val| val != '' } # (1..47)
|
43
|
+
criteria[:join_time] = html_document.xpath('id("apply_join_time")/*/@value') .map {|attr| attr.value }.select {|val| val != '' } # ['2017-04-01', .., '2017-04-30']
|
44
|
+
criteria[:night_count] = html_document.xpath('id("apply_night_count")/*/@value') .map {|attr| attr.value }.select {|val| val != '' } # (1..2)
|
45
|
+
criteria[:room_number] = html_document.xpath('id("house_select")/*/@value') .map {|attr| attr.value.to_i } # (1..10)
|
46
|
+
criteria
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.preprocess_reservation_data(reservation_data)
|
50
|
+
reservation_data[:birth_year] = reservation_data[:birth_year].to_s
|
51
|
+
reservation_data[:birth_month] = reservation_data[:birth_month].to_s
|
52
|
+
reservation_data[:birth_day] = reservation_data[:birth_day].to_s
|
53
|
+
reservation_data[:state] = reservation_data[:state].to_s
|
54
|
+
reservation_data[:night_count] = reservation_data[:night_count].to_s
|
55
|
+
reservation_data[:room_persons] = Array(reservation_data[:room_persons])
|
56
|
+
reservation_data[:meeting_dates] = Array(reservation_data[:meeting_dates])
|
57
|
+
reservation_data
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.validate_reservation_data(reservation_data, html_document)
|
61
|
+
reservation_data = preprocess_reservation_data(reservation_data)
|
62
|
+
criteria = reservation_criteria(html_document)
|
63
|
+
|
64
|
+
schema = Dry::Validation.Schema do
|
65
|
+
required(:sign_no) .filled(:int?)
|
66
|
+
required(:insured_no) .filled(:int?)
|
67
|
+
required(:office_name) .filled(:str?)
|
68
|
+
required(:kana_name) .filled(:str?)
|
69
|
+
required(:birth_year) .filled(included_in?: criteria[:birth_year])
|
70
|
+
required(:birth_month) .filled(included_in?: criteria[:birth_month])
|
71
|
+
required(:birth_day) .filled(included_in?: criteria[:birth_day])
|
72
|
+
required(:gender) .filled(included_in?: criteria[:gender])
|
73
|
+
required(:relationship) .filled(included_in?: criteria[:relationship])
|
74
|
+
required(:contact_phone).filled(format?: /^[0-9-]+$/)
|
75
|
+
required(:postal_code) .filled(format?: /^[0-9]{3}-[0-9]{4}$/)
|
76
|
+
required(:state) .filled(included_in?: criteria[:state])
|
77
|
+
required(:address) .filled(:str?)
|
78
|
+
required(:join_time) .filled(included_in?: criteria[:join_time])
|
79
|
+
required(:night_count) .filled(included_in?: criteria[:night_count])
|
80
|
+
required(:stay_persons) .filled(:int?)
|
81
|
+
required(:room_persons) .filled{ array? & each(:int?) & size?(criteria[:room_number]) }
|
82
|
+
required(:meeting_dates).value{ array? & each{ int? & included_in?([1,2,3]) } & size?((0..3)) }
|
83
|
+
required(:must_meeting) .maybe(:bool?)
|
84
|
+
end
|
85
|
+
|
86
|
+
result = schema.call(reservation_data)
|
87
|
+
raise ValidationError.new("Reservation data is invalid. #{result.messages.to_s}") if result.failure?
|
88
|
+
reise ValidationError.new('Stay persons count should match the sum of room persons') unless reservation_data[:stay_persons] != reservation_data[:room_persons].inject(:+)
|
89
|
+
result.output
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.convert_to_reservation_post_params(original_data)
|
93
|
+
post_params = {}
|
94
|
+
post_params['apply[sign_no]'] = original_data[:sign_no]
|
95
|
+
post_params['apply[insured_no]'] = original_data[:insured_no]
|
96
|
+
post_params['apply[office_name]'] = original_data[:office_name]
|
97
|
+
post_params['apply[kana_name]'] = original_data[:kana_name]
|
98
|
+
post_params['apply[year]'] = original_data[:birth_year]
|
99
|
+
post_params['apply[month]'] = original_data[:birth_month]
|
100
|
+
post_params['apply[day]'] = original_data[:birth_day]
|
101
|
+
post_params['apply[gender]'] = original_data[:gender]
|
102
|
+
post_params['apply[relationship]'] = original_data[:relationship]
|
103
|
+
post_params['apply[contact_phone]'] = original_data[:contact_phone]
|
104
|
+
post_params['apply[postal]'] = original_data[:postal_code]
|
105
|
+
post_params['apply[state]'] = original_data[:state]
|
106
|
+
post_params['apply[address]'] = original_data[:address]
|
107
|
+
post_params['apply[join_time]'] = original_data[:join_time]
|
108
|
+
post_params['apply[night_count]'] = original_data[:night_count]
|
109
|
+
post_params['apply[stay_persons]'] = original_data[:stay_persons]
|
110
|
+
post_params['apply[hope_rooms]'] = original_data[:room_persons].size()
|
111
|
+
post_params['apply[hope_room1]'] = original_data[:room_persons][0]
|
112
|
+
post_params['apply[hope_room2]'] = original_data[:room_persons][1] || ''
|
113
|
+
post_params['apply[hope_room3]'] = original_data[:room_persons][2] || ''
|
114
|
+
post_params['apply[hope_room4]'] = original_data[:room_persons][3] || ''
|
115
|
+
post_params['apply[hope_room5]'] = original_data[:room_persons][4] || ''
|
116
|
+
post_params['apply[hope_room6]'] = original_data[:room_persons][5] || ''
|
117
|
+
post_params['apply[hope_room7]'] = original_data[:room_persons][6] || ''
|
118
|
+
post_params['apply[hope_room8]'] = original_data[:room_persons][7] || ''
|
119
|
+
post_params['apply[hope_room9]'] = original_data[:room_persons][8] || ''
|
120
|
+
post_params['apply[hope_room10]'] = original_data[:room_persons][9] || ''
|
121
|
+
post_params['apply[use_meeting_flag]'] = original_data[:meeting_dates].any? ? 'use' : 'no_use'
|
122
|
+
post_params['apply[use_meeting1]'] = original_data[:meeting_dates].include?(1) ? '1' : '0'
|
123
|
+
post_params['apply[use_meeting2]'] = original_data[:meeting_dates].include?(2) ? '1' : '0'
|
124
|
+
post_params['apply[use_meeting3]'] = (original_data[:meeting_dates].include?(3) && original_data[:night_count] >= 2) ? '1' : '0'
|
125
|
+
post_params['apply[must_meeting]'] = original_data[:must_meeting] ? 'must' : 'not_must'
|
126
|
+
post_params
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module KenpoApi
|
2
|
+
module Routines
|
3
|
+
|
4
|
+
# Returns first service if you specify nil to service_name.
|
5
|
+
def find_service(category_code:, group_name:, service_name: nil)
|
6
|
+
category = ServiceCategory.find(category_code.to_sym)
|
7
|
+
raise NotFoundError.new("Service category not found. code: #{category_code}") if category.nil?
|
8
|
+
|
9
|
+
group = category.find_service_group(group_name)
|
10
|
+
raise NotFoundError.new("Service group not found. name: #{group_name}") if group.nil?
|
11
|
+
raise NotAvailableError.new("No available services. name: #{group_name}") unless group.available?
|
12
|
+
|
13
|
+
return group.services.first if service_name.nil?
|
14
|
+
service = group.find_service(service_name)
|
15
|
+
raise NotFoundError.new("Service not found. name: #{service_name}") if service.nil?
|
16
|
+
service
|
17
|
+
end
|
18
|
+
|
19
|
+
def request_application_url(service_path:, email:)
|
20
|
+
# Accept agreement.
|
21
|
+
next_page_info, = Client.instance.parse_single_form_page(path: service_path)
|
22
|
+
|
23
|
+
# Input email.
|
24
|
+
next_page_info, = Client.instance.parse_single_form_page(next_page_info)
|
25
|
+
next_page_info[:params]['email'] = email
|
26
|
+
|
27
|
+
Client.instance.access(next_page_info)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Block param receives one argument (html_document), and should return additional POST params.
|
31
|
+
def apply(application_url:)
|
32
|
+
# Input application form.
|
33
|
+
next_page_info, html_document = Client.instance.parse_single_form_page(path: application_url)
|
34
|
+
raise NotAvailableError.new("Application URL is invalid: #{html_document.xpath('//p').first.content}") if next_page_info.nil?
|
35
|
+
|
36
|
+
next_page_info[:params].merge!(yield html_document) if block_given?
|
37
|
+
|
38
|
+
# Confirm.
|
39
|
+
next_page_info, = Client.instance.parse_single_form_page(next_page_info)
|
40
|
+
|
41
|
+
Client.instance.access(next_page_info)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module KenpoApi
|
2
|
+
class Service
|
3
|
+
attr_reader :group, :name, :path
|
4
|
+
|
5
|
+
def initialize(group:, name:, path:)
|
6
|
+
@group = group
|
7
|
+
@name = name
|
8
|
+
@path = path
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.list(service_group)
|
12
|
+
return [] if service_group.nil?
|
13
|
+
Client.instance.fetch_document(path: service_group.path).xpath('//section[@class="request-box"]//a').map do |link|
|
14
|
+
self.new(
|
15
|
+
group: service_group,
|
16
|
+
name: link.text,
|
17
|
+
path: link['href'],
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.find(service_group, name)
|
23
|
+
self.list(service_group).find { |service| service.name == name }
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module KenpoApi
|
2
|
+
class ServiceCategory
|
3
|
+
CATEGORIES = {
|
4
|
+
resort_reserve: '直営・通年・夏季保養施設(抽選申込)',
|
5
|
+
resort_search_vacant: '直営・通年・夏季保養施設(空き照会)',
|
6
|
+
sport_reserve: 'スポーツ施設(抽選申込)',
|
7
|
+
sport_search_vacant: 'スポーツ施設(空き照会)',
|
8
|
+
resort_alliance: '契約保養施設(補助金対象施設)',
|
9
|
+
travel_pack: 'ITS旅行パック(補助申請)',
|
10
|
+
golf_course: 'ITS契約ゴルフ場',
|
11
|
+
camp_site: 'ITS契約オートキャンプ場',
|
12
|
+
laforet: 'ラフォーレ倶楽部',
|
13
|
+
thalassotherapy: 'タラソテラピー',
|
14
|
+
recreation: '体育奨励イベント',
|
15
|
+
}
|
16
|
+
|
17
|
+
attr_reader :category_code, :name, :path
|
18
|
+
|
19
|
+
def initialize(name:, path:)
|
20
|
+
@category_code = CATEGORIES.key(name)
|
21
|
+
@name = name
|
22
|
+
@path = path
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.list
|
26
|
+
Client.instance.fetch_document(path: '/service_category/index').xpath('//div[@class="request-box"]//a').map do |link|
|
27
|
+
self.new(
|
28
|
+
name: link.text,
|
29
|
+
path: link['href'],
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.find(category_code)
|
35
|
+
self.list.find { |category| category.category_code == category_code }
|
36
|
+
end
|
37
|
+
|
38
|
+
def service_groups
|
39
|
+
ServiceGroup.list(self)
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_service_group(name)
|
43
|
+
ServiceGroup.find(self, name)
|
44
|
+
end
|
45
|
+
|
46
|
+
def available?
|
47
|
+
self.service_groups.any?
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module KenpoApi
|
2
|
+
class ServiceGroup
|
3
|
+
attr_reader :category, :name, :path
|
4
|
+
|
5
|
+
def initialize(category:, name:, path:)
|
6
|
+
@category = category
|
7
|
+
@name = name
|
8
|
+
@path = path
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.list(service_category)
|
12
|
+
return [] if service_category.nil?
|
13
|
+
Client.instance.fetch_document(path: service_category.path).xpath('//section[@class="request-box"]//a').map do |link|
|
14
|
+
self.new(
|
15
|
+
category: service_category,
|
16
|
+
name: link.text,
|
17
|
+
path: link['href'],
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.find(service_category, name)
|
23
|
+
self.list(service_category).find { |group| group.name == name }
|
24
|
+
end
|
25
|
+
|
26
|
+
def services
|
27
|
+
Service.list(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_service(name)
|
31
|
+
Service.find(self, name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def available?
|
35
|
+
self.services.any?
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'dry-validation'
|
2
|
+
|
3
|
+
module KenpoApi
|
4
|
+
class Sport
|
5
|
+
extend Routines
|
6
|
+
|
7
|
+
def self.sport_names
|
8
|
+
category = ServiceCategory.find(:sport_reserve)
|
9
|
+
raise NotFoundError.new("Service category not found. code: #{category_code}") if category.nil?
|
10
|
+
category.service_groups.map {|group| group.name}
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.request_reservation_url(sport_name:, email:)
|
14
|
+
service = find_service(category_code: :sport_reserve, group_name: sport_name)
|
15
|
+
request_application_url(service_path: service.path, email: email)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.check_reservation_criteria(application_url)
|
19
|
+
html_document = Client.instance.fetch_document(path: application_url)
|
20
|
+
raise NotAvailableError.new("Application URL is invalid: #{html_document.xpath('//p').first.content}") if html_document.xpath('//form').first.nil?
|
21
|
+
reservation_criteria(html_document)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.apply_reservation(application_url, application_data)
|
25
|
+
apply(application_url: application_url) do |html_document|
|
26
|
+
reservation_data = self.validate_reservation_data(application_data, html_document)
|
27
|
+
convert_to_reservation_post_params(reservation_data)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def self.reservation_criteria(html_document)
|
34
|
+
criteria = {}
|
35
|
+
criteria[:note] = html_document.xpath('//div[@class="note mb10"]').first.text
|
36
|
+
criteria[:service_name] = html_document.xpath('//form/div[@class="form_box"]//dd[@class="elements"]').first.text
|
37
|
+
criteria[:birth_year] = html_document.xpath('id("apply_year")/*/@value') .map {|attr| attr.value }.select {|val| val != '' } # (1917..2017)
|
38
|
+
criteria[:birth_month] = html_document.xpath('id("apply_month")/*/@value') .map {|attr| attr.value }.select {|val| val != '' } # (1..12)
|
39
|
+
criteria[:birth_day] = html_document.xpath('id("apply_day")/*/@value') .map {|attr| attr.value }.select {|val| val != '' } # (1..31)
|
40
|
+
criteria[:state] = html_document.xpath('id("apply_state")/*/@value') .map {|attr| attr.value }.select {|val| val != '' } # (1..47)
|
41
|
+
criteria[:join_time] = html_document.xpath('id("apply_join_time")/*/@value') .map {|attr| attr.value }.select {|val| val != '' } # ['2017-04-01', .., '2017-04-30'] (only Saterdays or Sundays?)
|
42
|
+
criteria[:use_time_from] = html_document.xpath('id("apply_use_time_from")/*/@value').map {|attr| attr.value }.select {|val| val != '' } # ['00:00', .., '24:00']
|
43
|
+
criteria[:use_time_to] = html_document.xpath('id("apply_use_time_to")/*/@value') .map {|attr| attr.value }.select {|val| val != '' } # ['00:00', .., '24:00']
|
44
|
+
criteria
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.preprocess_reservation_data(reservation_data)
|
48
|
+
reservation_data[:birth_year] = reservation_data[:birth_year].to_s
|
49
|
+
reservation_data[:birth_month] = reservation_data[:birth_month].to_s
|
50
|
+
reservation_data[:birth_day] = reservation_data[:birth_day].to_s
|
51
|
+
reservation_data[:state] = reservation_data[:state].to_s
|
52
|
+
reservation_data
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.validate_reservation_data(reservation_data, html_document)
|
56
|
+
reservation_data = preprocess_reservation_data(reservation_data)
|
57
|
+
criteria = reservation_criteria(html_document)
|
58
|
+
|
59
|
+
schema = Dry::Validation.Schema do
|
60
|
+
required(:sign_no) .filled(:int?)
|
61
|
+
required(:insured_no) .filled(:int?)
|
62
|
+
required(:office_name) .filled(:str?)
|
63
|
+
required(:kana_name) .filled(:str?)
|
64
|
+
required(:birth_year) .filled(included_in?: criteria[:birth_year])
|
65
|
+
required(:birth_month) .filled(included_in?: criteria[:birth_month])
|
66
|
+
required(:birth_day) .filled(included_in?: criteria[:birth_day])
|
67
|
+
required(:contact_phone).filled(format?: /^[0-9-]+$/)
|
68
|
+
required(:postal_code) .filled(format?: /^[0-9]{3}-[0-9]{4}$/)
|
69
|
+
required(:state) .filled(included_in?: criteria[:state])
|
70
|
+
required(:address) .filled(:str?)
|
71
|
+
required(:join_time) .filled(included_in?: criteria[:join_time])
|
72
|
+
required(:use_time_from).filled(included_in?: criteria[:use_time_from])
|
73
|
+
required(:use_time_to) .filled(included_in?: criteria[:use_time_to])
|
74
|
+
end
|
75
|
+
|
76
|
+
result = schema.call(reservation_data)
|
77
|
+
raise ValidationError.new("Reservation data is invalid. #{result.messages.to_s}") if result.failure?
|
78
|
+
result.output
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.convert_to_reservation_post_params(original_data)
|
82
|
+
post_params = {}
|
83
|
+
post_params['apply[sign_no]'] = original_data[:sign_no]
|
84
|
+
post_params['apply[insured_no]'] = original_data[:insured_no]
|
85
|
+
post_params['apply[office_name]'] = original_data[:office_name]
|
86
|
+
post_params['apply[kana_name]'] = original_data[:kana_name]
|
87
|
+
post_params['apply[year]'] = original_data[:birth_year]
|
88
|
+
post_params['apply[month]'] = original_data[:birth_month]
|
89
|
+
post_params['apply[day]'] = original_data[:birth_day]
|
90
|
+
post_params['apply[contact_phone]'] = original_data[:contact_phone]
|
91
|
+
post_params['apply[postal]'] = original_data[:postal_code]
|
92
|
+
post_params['apply[state]'] = original_data[:state]
|
93
|
+
post_params['apply[address]'] = original_data[:address]
|
94
|
+
post_params['apply[join_time]'] = original_data[:join_time]
|
95
|
+
post_params['apply[use_time_from]'] = original_data[:use_time_from]
|
96
|
+
post_params['apply[use_time_to]'] = original_data[:use_time_to]
|
97
|
+
post_params
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
metadata
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kenpo_api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- tearoom6
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-02-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.13'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.13'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: faraday
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.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: 0.10.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: cookiejar
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.3.3
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.3.3
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: nokogiri
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.6'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.6'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: dry-validation
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.10.5
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.10.5
|
111
|
+
description: Unofficial API for ITS kenpo reservation system.
|
112
|
+
email:
|
113
|
+
- tearoom6.biz@gmail.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".gitignore"
|
119
|
+
- ".rspec"
|
120
|
+
- ".travis.yml"
|
121
|
+
- Gemfile
|
122
|
+
- README.md
|
123
|
+
- Rakefile
|
124
|
+
- bin/console
|
125
|
+
- bin/setup
|
126
|
+
- kenpo_api.gemspec
|
127
|
+
- lib/kenpo_api.rb
|
128
|
+
- lib/kenpo_api/client.rb
|
129
|
+
- lib/kenpo_api/resort.rb
|
130
|
+
- lib/kenpo_api/routines.rb
|
131
|
+
- lib/kenpo_api/service.rb
|
132
|
+
- lib/kenpo_api/service_category.rb
|
133
|
+
- lib/kenpo_api/service_group.rb
|
134
|
+
- lib/kenpo_api/sport.rb
|
135
|
+
- lib/kenpo_api/version.rb
|
136
|
+
homepage: https://github.com/tearoom6/kenpo_api
|
137
|
+
licenses: []
|
138
|
+
metadata: {}
|
139
|
+
post_install_message:
|
140
|
+
rdoc_options: []
|
141
|
+
require_paths:
|
142
|
+
- lib
|
143
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '0'
|
148
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
requirements: []
|
154
|
+
rubyforge_project:
|
155
|
+
rubygems_version: 2.5.2
|
156
|
+
signing_key:
|
157
|
+
specification_version: 4
|
158
|
+
summary: Unofficial API for ITS kenpo reservation system.
|
159
|
+
test_files: []
|