kenpo_api 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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: []
|