expedia_api 0.1.3 → 0.1.4
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 +4 -4
- data/README.md +10 -0
- data/expedia_api.gemspec +2 -1
- data/lib/expedia_api.rb +8 -1
- data/lib/expedia_api/client.rb +36 -7
- data/lib/expedia_api/entities/package.rb +38 -0
- data/lib/expedia_api/entities/package_flight.rb +47 -0
- data/lib/expedia_api/entities/package_hotel.rb +15 -0
- data/lib/expedia_api/http_service.rb +2 -3
- data/lib/expedia_api/response_lists.rb +4 -0
- data/lib/expedia_api/response_lists/base_response_list.rb +75 -0
- data/lib/expedia_api/response_lists/hotels.rb +28 -0
- data/lib/expedia_api/response_lists/packages.rb +46 -0
- data/lib/expedia_api/version.rb +1 -1
- metadata +24 -4
- data/lib/expedia_api/hotel_response_list.rb +0 -73
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1fe7e96bee4a14c1f2667b22eb9da9f5445e5b8
|
4
|
+
data.tar.gz: 2d5f8165948ef02e8de35ce24698b2c4126829fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d04cde9d1f73bbadc2d939339a4393eccf50ce33fca9504b0b129588ad2ef6691878339e70e83ad918234e27bdea3231f867ea44bdb2497fc44ec87fadd8d458
|
7
|
+
data.tar.gz: d1d264980a237298402a4901d1dc2b2089396887338fbcac11a0b490c2c0b2d51f69cffe5dcb4f005ce3f25f881cc8fc41f2ba67ae15ab031bb7294ec5f03cba
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Expedia Api
|
2
2
|
|
3
|
+
[](https://travis-ci.org/hendricius/expedia_api)
|
4
|
+
|
3
5
|
This is a ruby wrapper for the official Expedia.com API. Please note that the Expedia.com API is a different than the one provided by the Expedia Affiliate Network. They are different companies. If you want to use the EAN Api please use [expedia gem](https://github.com/zaidakram/expedia).
|
4
6
|
|
5
7
|
|
@@ -39,9 +41,17 @@ Then when using the API create a new client object:
|
|
39
41
|
|
40
42
|
```
|
41
43
|
client = ExpediaApi::Client.new
|
44
|
+
```
|
45
|
+
|
46
|
+
Search Hotels:
|
47
|
+
```
|
42
48
|
client.get_list({})
|
49
|
+
```
|
43
50
|
|
51
|
+
Search Packages:
|
44
52
|
|
53
|
+
```
|
54
|
+
client.search_packages({})
|
45
55
|
```
|
46
56
|
|
47
57
|
The parameters you can pass to `get_list` are the following:
|
data/expedia_api.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
|
12
12
|
spec.description = "Expediacom is a lightweight flexible Ruby SDK for the official Expedia.com API"
|
13
13
|
spec.summary = "Wrapper for the Expedia.com API"
|
14
|
-
spec.homepage = "https://github.com/hendricius/
|
14
|
+
spec.homepage = "https://github.com/hendricius/expedia_api"
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
17
|
spec.bindir = "exe"
|
@@ -26,5 +26,6 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_runtime_dependency(%q<faraday>, ["~> 0.8"])
|
27
27
|
spec.add_runtime_dependency "faraday_middleware"
|
28
28
|
spec.add_runtime_dependency "activesupport"
|
29
|
+
spec.add_runtime_dependency "money", '~> 6'
|
29
30
|
|
30
31
|
end
|
data/lib/expedia_api.rb
CHANGED
@@ -2,13 +2,20 @@ require 'json'
|
|
2
2
|
require 'faraday'
|
3
3
|
require 'faraday_middleware'
|
4
4
|
require 'active_support/core_ext/hash/indifferent_access'
|
5
|
+
require 'money'
|
5
6
|
|
6
7
|
require "expedia_api/version"
|
7
8
|
require "expedia_api/http_service"
|
8
9
|
require "expedia_api/client"
|
9
|
-
require "expedia_api/hotel_response_list"
|
10
10
|
require "expedia_api/entities"
|
11
11
|
require "expedia_api/entities/search_entity"
|
12
|
+
require "expedia_api/entities/package"
|
13
|
+
require "expedia_api/entities/package_flight"
|
14
|
+
require "expedia_api/entities/package_hotel"
|
15
|
+
require "expedia_api/response_lists"
|
16
|
+
require "expedia_api/response_lists/base_response_list"
|
17
|
+
require "expedia_api/response_lists/packages"
|
18
|
+
require "expedia_api/response_lists/hotels"
|
12
19
|
|
13
20
|
module ExpediaApi
|
14
21
|
class << self
|
data/lib/expedia_api/client.rb
CHANGED
@@ -81,19 +81,48 @@ module ExpediaApi
|
|
81
81
|
# true
|
82
82
|
# description:
|
83
83
|
# return available hotels only. default: true
|
84
|
-
def
|
85
|
-
data = request(parameters: parameters)
|
86
|
-
ExpediaApi::
|
84
|
+
def search_hotels(parameters = {})
|
85
|
+
data = request(parameters: parameters, uri: 'wsapi/rest/hotel/v1/search')
|
86
|
+
ExpediaApi::ResponseLists::Hotels.new(response: data)
|
87
87
|
rescue Faraday::ParsingError => e
|
88
|
-
ExpediaApi::
|
88
|
+
ExpediaApi::ResponseLists::Hotels.new(exception: e)
|
89
89
|
rescue Faraday::ConnectionFailed => e
|
90
|
-
ExpediaApi::
|
90
|
+
ExpediaApi::ResponseLists::Hotels.new(exception: e)
|
91
|
+
end
|
92
|
+
|
93
|
+
def search_packages(hotel_ids: [], region_ids: [], from_date:, to_date:, from_airport:, to_airport:, other_options: {})
|
94
|
+
validate_package_arguments({hotel_ids: hotel_ids, region_ids: region_ids})
|
95
|
+
parameters = {}
|
96
|
+
parameters[:hotelids] = hotel_ids if hotel_ids.length
|
97
|
+
parameters[:regionids] = region_ids if region_ids.length
|
98
|
+
path_uri = build_package_search_request_path(from_airport: from_airport, to_airport: to_airport, from_date: from_date, to_date: to_date)
|
99
|
+
base_uri = "/wsapi/rest/package/v1/search"
|
100
|
+
full_uri = "#{base_uri}/#{path_uri}"
|
101
|
+
data = request(parameters: parameters, uri: full_uri)
|
102
|
+
ExpediaApi::ResponseLists::Packages.new(response: data)
|
103
|
+
rescue Faraday::ParsingError => e
|
104
|
+
ExpediaApi::PackageResponseList.new(exception: e)
|
105
|
+
rescue Faraday::ConnectionFailed => e
|
106
|
+
ExpediaApi::PackageResponseList.new(exception: e)
|
91
107
|
end
|
92
108
|
|
93
109
|
private
|
94
110
|
|
95
|
-
def request(request_options: {}, parameters: {})
|
96
|
-
HTTPService.perform_request(request_options: request_options, parameters: parameters)
|
111
|
+
def request(request_options: {}, parameters: {}, uri:)
|
112
|
+
HTTPService.perform_request(request_options: request_options, parameters: parameters, uri: uri)
|
113
|
+
end
|
114
|
+
|
115
|
+
def validate_package_arguments(args)
|
116
|
+
if args[:hotel_ids].empty? && args[:region_ids].empty?
|
117
|
+
raise ArgumentError
|
118
|
+
end
|
119
|
+
true
|
120
|
+
end
|
121
|
+
|
122
|
+
def build_package_search_request_path(from_airport:, to_airport:, from_date:, to_date:)
|
123
|
+
from_date = from_date.strftime("%F")
|
124
|
+
to_date = to_date.strftime("%F")
|
125
|
+
"#{from_date}/#{from_airport}/#{to_airport}/#{to_date}/#{to_airport}/#{from_airport}"
|
97
126
|
end
|
98
127
|
|
99
128
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module ExpediaApi
|
2
|
+
module Entities
|
3
|
+
class Package
|
4
|
+
attr_accessor :flight, :hotel
|
5
|
+
attr_reader :raw_data
|
6
|
+
|
7
|
+
def initialize(raw_data)
|
8
|
+
@raw_data = raw_data || {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def flight_index
|
12
|
+
raw_data[:FlightReferenceIndex].to_i
|
13
|
+
end
|
14
|
+
|
15
|
+
def hotel_index
|
16
|
+
raw_data[:HotelReferenceIndex].to_i
|
17
|
+
end
|
18
|
+
|
19
|
+
# returns a money object including the price. returns nil if there is no price
|
20
|
+
def price
|
21
|
+
if raw_data[:PackagePrice] && raw_data[:PackagePrice][:TotalPrice]
|
22
|
+
Money.new(raw_data[:PackagePrice][:TotalPrice][:Value].to_f * 100, raw_data[:PackagePrice][:TotalPrice][:Currency])
|
23
|
+
else
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# returns a money object with the savings, nil if there are no savings.
|
29
|
+
def savings
|
30
|
+
if raw_data[:PackagePrice] && raw_data[:PackagePrice][:TotalSavings]
|
31
|
+
Money.new(raw_data[:PackagePrice][:TotalSavings][:Value].to_f * 100, raw_data[:PackagePrice][:TotalSavings][:Currency])
|
32
|
+
else
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module ExpediaApi
|
2
|
+
module Entities
|
3
|
+
class PackageFlight
|
4
|
+
attr_reader :raw_data
|
5
|
+
|
6
|
+
def initialize(raw_data)
|
7
|
+
@raw_data = raw_data || {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def index
|
11
|
+
raw_data[:FlightIndex].to_i
|
12
|
+
end
|
13
|
+
|
14
|
+
def flight_legs
|
15
|
+
extract_flightlegs.map do |leg|
|
16
|
+
FlightLeg.new(leg)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def extract_flightlegs
|
21
|
+
raw_data.fetch(:FlightItinerary, {}).fetch(:FlightLeg, {})
|
22
|
+
end
|
23
|
+
|
24
|
+
class FlightLeg
|
25
|
+
def initialize(raw_data)
|
26
|
+
@raw_data = raw_data || {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def segments
|
30
|
+
extract_segments.map do |segment|
|
31
|
+
FlightLegSegment.new(segment)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def extract_segments
|
36
|
+
@raw_data[:FlightSegment] || []
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class FlightLegSegment
|
41
|
+
def initialize(raw_data)
|
42
|
+
@raw_data = raw_data || {}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -2,7 +2,6 @@ module ExpediaApi
|
|
2
2
|
module HTTPService
|
3
3
|
|
4
4
|
API_SERVER = 'ews.expedia.com'
|
5
|
-
API_PATH = 'wsapi/rest/hotel/v1/search'
|
6
5
|
|
7
6
|
class << self
|
8
7
|
|
@@ -24,14 +23,14 @@ module ExpediaApi
|
|
24
23
|
options
|
25
24
|
end
|
26
25
|
|
27
|
-
def perform_request(request_options: {}, parameters: {})
|
26
|
+
def perform_request(request_options: {}, parameters: {}, uri:)
|
28
27
|
args = common_parameters.merge(parameters)
|
29
28
|
# figure out our options for this request
|
30
29
|
# set up our Faraday connection
|
31
30
|
connection = Faraday.new(faraday_options) do |faraday|
|
32
31
|
faraday.adapter Faraday.default_adapter
|
33
32
|
end
|
34
|
-
connection.get(
|
33
|
+
connection.get(uri, args)
|
35
34
|
end
|
36
35
|
|
37
36
|
def use_proxy?
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module ExpediaApi
|
2
|
+
module ResponseLists
|
3
|
+
class BaseResponseList
|
4
|
+
include Enumerable
|
5
|
+
attr_reader :response, :exception
|
6
|
+
|
7
|
+
def initialize(entries: [], response: nil, exception: nil)
|
8
|
+
@response = response
|
9
|
+
@exception = exception
|
10
|
+
end
|
11
|
+
|
12
|
+
def each(&block)
|
13
|
+
entries.each(&:block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def map(&block)
|
17
|
+
entries.map(&:block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def first
|
21
|
+
entries.first
|
22
|
+
end
|
23
|
+
|
24
|
+
def last
|
25
|
+
entries.last
|
26
|
+
end
|
27
|
+
|
28
|
+
def entries
|
29
|
+
@entries ||= begin
|
30
|
+
self.entries = extract_entries_from_response(@response)
|
31
|
+
self.entries
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def entries=(entries)
|
36
|
+
raise ArgumentError, "implement me"
|
37
|
+
end
|
38
|
+
|
39
|
+
def success?
|
40
|
+
exception.nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
def error?
|
44
|
+
!!exception
|
45
|
+
end
|
46
|
+
|
47
|
+
def extract_entries_from_response(response)
|
48
|
+
raise ArgumentError, "implement me"
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# extracts an array of data from the response
|
54
|
+
#
|
55
|
+
# returns an array.
|
56
|
+
def extract_data_from_response(response)
|
57
|
+
if response.nil?
|
58
|
+
return []
|
59
|
+
end
|
60
|
+
body = nil
|
61
|
+
begin
|
62
|
+
body = JSON.parse(response.body)
|
63
|
+
rescue JSON::ParserError => e
|
64
|
+
@exception = e
|
65
|
+
return []
|
66
|
+
end
|
67
|
+
if !body.is_a?(Hash) || body.nil?
|
68
|
+
return []
|
69
|
+
end
|
70
|
+
body
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ExpediaApi
|
2
|
+
module ResponseLists
|
3
|
+
class Hotels < BaseResponseList
|
4
|
+
|
5
|
+
def entries=(entries)
|
6
|
+
if entries
|
7
|
+
@entries = entries.map {|e| ExpediaApi::Entities::SearchEntity.new(e.with_indifferent_access) }
|
8
|
+
else
|
9
|
+
@entries = []
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def extract_entries_from_response(response)
|
14
|
+
body = extract_data_from_response(response)
|
15
|
+
return [] if body.empty?
|
16
|
+
hotel_count = body["HotelCount"].to_i
|
17
|
+
if hotel_count == 1
|
18
|
+
[body["HotelInfoList"]["HotelInfo"]]
|
19
|
+
elsif hotel_count > 1
|
20
|
+
body["HotelInfoList"]["HotelInfo"]
|
21
|
+
else
|
22
|
+
[]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ExpediaApi
|
2
|
+
module ResponseLists
|
3
|
+
class Packages < BaseResponseList
|
4
|
+
|
5
|
+
def entries=(entries)
|
6
|
+
@entries = entries
|
7
|
+
end
|
8
|
+
|
9
|
+
def extract_entries_from_response(response)
|
10
|
+
json = extract_data_from_response(response).with_indifferent_access
|
11
|
+
return [] if json.empty?
|
12
|
+
json = json.with_indifferent_access
|
13
|
+
flights = extract_flights(json[:FlightList][:Flight])
|
14
|
+
hotels = extract_hotels(json[:HotelList][:Hotel])
|
15
|
+
packages = extract_packages(json["PackageSearchResultList"]["PackageSearchResult"], hotels: hotels, flights: flights)
|
16
|
+
packages
|
17
|
+
end
|
18
|
+
|
19
|
+
# returns flights for each of the entries
|
20
|
+
def extract_flights(flights_json)
|
21
|
+
# right now only 1 flight
|
22
|
+
[ExpediaApi::Entities::PackageFlight.new(flights_json)]
|
23
|
+
end
|
24
|
+
|
25
|
+
# returns hotels for each hotel in the json
|
26
|
+
def extract_hotels(hotels_json)
|
27
|
+
hotels_json.map do |hotel|
|
28
|
+
ExpediaApi::Entities::PackageHotel.new(hotel)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# returns packages for each of the json data
|
33
|
+
def extract_packages(json, flights:, hotels:)
|
34
|
+
flights_by_index = flights.map {|flight| [flight.index, flight]}.to_h
|
35
|
+
hotels_by_index = hotels.map {|hotel| [hotel.index, hotel]}.to_h
|
36
|
+
json.map do |package|
|
37
|
+
entity = ExpediaApi::Entities::Package.new(package)
|
38
|
+
entity.flight = flights_by_index[entity.flight_index]
|
39
|
+
entity.hotel = hotels_by_index[entity.hotel_index]
|
40
|
+
entity
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
data/lib/expedia_api/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: expedia_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hendrik Kleinwaechter
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-04-
|
11
|
+
date: 2016-04-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -122,6 +122,20 @@ dependencies:
|
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: money
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '6'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '6'
|
125
139
|
description: Expediacom is a lightweight flexible Ruby SDK for the official Expedia.com
|
126
140
|
API
|
127
141
|
email:
|
@@ -142,11 +156,17 @@ files:
|
|
142
156
|
- lib/expedia_api.rb
|
143
157
|
- lib/expedia_api/client.rb
|
144
158
|
- lib/expedia_api/entities.rb
|
159
|
+
- lib/expedia_api/entities/package.rb
|
160
|
+
- lib/expedia_api/entities/package_flight.rb
|
161
|
+
- lib/expedia_api/entities/package_hotel.rb
|
145
162
|
- lib/expedia_api/entities/search_entity.rb
|
146
|
-
- lib/expedia_api/hotel_response_list.rb
|
147
163
|
- lib/expedia_api/http_service.rb
|
164
|
+
- lib/expedia_api/response_lists.rb
|
165
|
+
- lib/expedia_api/response_lists/base_response_list.rb
|
166
|
+
- lib/expedia_api/response_lists/hotels.rb
|
167
|
+
- lib/expedia_api/response_lists/packages.rb
|
148
168
|
- lib/expedia_api/version.rb
|
149
|
-
homepage: https://github.com/hendricius/
|
169
|
+
homepage: https://github.com/hendricius/expedia_api
|
150
170
|
licenses: []
|
151
171
|
metadata: {}
|
152
172
|
post_install_message:
|
@@ -1,73 +0,0 @@
|
|
1
|
-
module ExpediaApi
|
2
|
-
class HotelResponseList
|
3
|
-
include Enumerable
|
4
|
-
attr_reader :response, :exception
|
5
|
-
|
6
|
-
def initialize(entries: [], response: nil, exception: nil)
|
7
|
-
@response = response
|
8
|
-
self.entries = extract_entries_from_response(response)
|
9
|
-
@exception = exception
|
10
|
-
end
|
11
|
-
|
12
|
-
def each(&block)
|
13
|
-
@entries.each(&:block)
|
14
|
-
end
|
15
|
-
|
16
|
-
def map(&block)
|
17
|
-
@entries.map(&:block)
|
18
|
-
end
|
19
|
-
|
20
|
-
def first
|
21
|
-
@entries.first
|
22
|
-
end
|
23
|
-
|
24
|
-
def last
|
25
|
-
@entries.last
|
26
|
-
end
|
27
|
-
|
28
|
-
def entries
|
29
|
-
@entries
|
30
|
-
end
|
31
|
-
|
32
|
-
def entries=(entries)
|
33
|
-
@entries = entries.map {|e| ExpediaApi::Entities::SearchEntity.new(e.with_indifferent_access) }
|
34
|
-
end
|
35
|
-
|
36
|
-
def success?
|
37
|
-
exception.nil?
|
38
|
-
end
|
39
|
-
|
40
|
-
def error?
|
41
|
-
!!exception
|
42
|
-
end
|
43
|
-
|
44
|
-
def response_body
|
45
|
-
end
|
46
|
-
|
47
|
-
def extract_entries_from_response(response)
|
48
|
-
# probably an error ocurred connecting
|
49
|
-
if response.nil?
|
50
|
-
return []
|
51
|
-
end
|
52
|
-
body = nil
|
53
|
-
begin
|
54
|
-
body = JSON.parse(response.body)
|
55
|
-
rescue JSON::ParserError => e
|
56
|
-
@exception = e
|
57
|
-
return []
|
58
|
-
end
|
59
|
-
if !body.is_a?(Hash) || body.nil?
|
60
|
-
return []
|
61
|
-
end
|
62
|
-
hotel_count = body["HotelCount"].to_i
|
63
|
-
if hotel_count == 1
|
64
|
-
[body["HotelInfoList"]["HotelInfo"]]
|
65
|
-
elsif hotel_count > 1
|
66
|
-
body["HotelInfoList"]["HotelInfo"]
|
67
|
-
else
|
68
|
-
[]
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
end
|
73
|
-
end
|