expedia_api 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 00c0cba3e1f70e0d30b927589783bf263ec9e4c8
4
- data.tar.gz: 8bc3717b0d80eae5ebb27494ab4f1af29d47a0b6
3
+ metadata.gz: 5e6ebd0c3a9096821ea7f7af3733dc9e581979a5
4
+ data.tar.gz: 640d4ed2e77bce8a2a5bb211074a9ab211457886
5
5
  SHA512:
6
- metadata.gz: 69e61b3e88dbee6ec9aafea1224e6d17b75099c5dbd113fb04c85ee86807637c1d595b7f646691fd0753294c7693cd11a6548c7ad770b0142210c3b821bad219
7
- data.tar.gz: 371056c4370c0216119f7ffa2d5167d865e0ae7a10a30026497147f4a63ce8e1b285f8e4e4aca9f6ceb7403125870311665ad0fd06a459b1c693b4493104278f
6
+ metadata.gz: 7f7de3574914acf821fbf26221cf4e1bc80c84a3228b5e9bf6d3a43ff4cf09d7a1ecb5c208ecc89c1604a8bda27da93baa37c33501f574f431aa0f9a00a49dfb
7
+ data.tar.gz: 11ff20b25f339faf3c5f0e479707322b1c42c76655ccd51d951b6c5b7c4b956f152fabb57609f35990467393dd6a0421ae550806e6d4a05043d79b0dac15c4e3
@@ -90,6 +90,21 @@ module ExpediaApi
90
90
  ExpediaApi::ResponseLists::Hotels.new(exception: e)
91
91
  end
92
92
 
93
+ def search_flights(from_date:, to_date:, from_airport:, to_airport:, other_options: {})
94
+ parameters = {adult:1}.merge(other_options)
95
+ path_uri = build_package_search_request_path(from_airport: from_airport, to_airport: to_airport, from_date: from_date, to_date: to_date)
96
+ # build the url for the request to match the specifications
97
+ path_uri = build_flight_search_request_path(from_airport: from_airport, to_airport: to_airport, from_date: from_date, to_date: to_date)
98
+ base_uri = "/xmlapi/rest/air/v2/airsearch"
99
+ full_uri = "#{base_uri}/#{path_uri}"
100
+ data = request(parameters: parameters, uri: full_uri)
101
+ ExpediaApi::ResponseLists::Flights.new(response: data)
102
+ rescue Faraday::ParsingError => e
103
+ ExpediaApi::ResponseLists::Flights.new(exception: e)
104
+ rescue Faraday::ConnectionFailed => e
105
+ ExpediaApi::ResponseLists::Flights.new(exception: e)
106
+ end
107
+
93
108
  def search_packages(hotel_ids: [], region_ids: [], from_date:, to_date:, from_airport:, to_airport:, other_options: {})
94
109
  # convert/validate the parameters. the api expects a comma separated
95
110
  # string.
@@ -130,5 +145,10 @@ module ExpediaApi
130
145
  "#{from_date}/#{from_airport}/#{to_airport}/#{to_date}/#{to_airport}/#{from_airport}"
131
146
  end
132
147
 
148
+ def build_flight_search_request_path(from_airport:, to_airport:, from_date:, to_date:)
149
+ from_date = from_date.strftime("%F")
150
+ to_date = to_date.strftime("%F")
151
+ "#{from_date}/#{from_airport}/#{to_airport}/#{to_date}/#{to_airport}/#{from_airport}"
152
+ end
133
153
  end
134
154
  end
@@ -0,0 +1,31 @@
1
+ module ExpediaApi
2
+ module Entities
3
+ class FlightCombination
4
+ attr_accessor :outbound_leg, :return_leg, :cheapest_combination_price
5
+ attr_reader :raw_data
6
+
7
+ def initialize(raw_data)
8
+ @raw_data = raw_data || {}
9
+ end
10
+
11
+ def piid
12
+ @raw_data[:PIID]
13
+ end
14
+
15
+ #returns a money object including the price. returns nil if there is no price
16
+ def total_price
17
+ if raw_data[:PriceInformation] && raw_data[:PriceInformation][:TotalPrice]
18
+ Money.new(raw_data[:PriceInformation][:TotalPrice][:Value].to_f * 100, raw_data[:PriceInformation][:TotalPrice][:CurrencyCode])
19
+ else
20
+ nil
21
+ end
22
+ end
23
+
24
+ def price_difference_to_cheapest_combination
25
+ return nil unless total_price && cheapest_combination_price
26
+ total_price - Money.new(cheapest_combination_price[:Value].to_f * 100, cheapest_combination_price[:CurrencyCode])
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,157 @@
1
+ module ExpediaApi
2
+ module Entities
3
+ # wrapper class for each of the flight legs
4
+ class FlightCombinationLeg
5
+
6
+ # The raw data by the API looks like this:
7
+ #
8
+ # {
9
+ # "AirLegIndex":1,
10
+ # "AirSegmentList":{
11
+ # "AirSegment":[
12
+ # {
13
+ # "AirCarrierCode": "AF",
14
+ # "AirProviderCode": "1",
15
+ # "AirSegmentIndex":1,
16
+ # "ArrivalDateTime": "2016-09-07T08:50:00+02:00",
17
+ # "ArrivalLocationCode": "CDG",
18
+ # "DepartureDateTime": "2016-09-07T07:00:00+02:00",
19
+ # "DepartureLocationCode": "TXL",
20
+ # "FlightDuration": "PT1H50M",
21
+ # "FlightNumber": "1135",
22
+ # "OperationSegmentList":{
23
+ # "OperationSegment":[
24
+ # {
25
+ # "AircraftCode": "320",
26
+ # "ArrivalDateTime": "2016-09-07T08:50:00+02:00",
27
+ # "ArrivalLocationCode": "CDG",
28
+ # "DepartureDateTime": "2016-09-07T07:00:00+02:00",
29
+ # "DepartureLocationCode": "TXL",
30
+ # "FlightDuration": "PT1H50M",
31
+ # "OperationSegmentIndex":1
32
+ # }
33
+ # ]
34
+ # }
35
+ # },
36
+ # {
37
+ # "AirCarrierCode": "TN",
38
+ # "AirProviderCode": "1",
39
+ # "AirSegmentIndex":2,
40
+ # "ArrivalDateTime": "2016-09-07T22:00:00-10:00",
41
+ # "ArrivalLocationCode": "PPT",
42
+ # "DepartureDateTime": "2016-09-07T11:30:00+02:00",
43
+ # "DepartureLocationCode": "CDG",
44
+ # "FlightDuration": "PT22H30M",
45
+ # "FlightNumber": "7",
46
+ # "OperationSegmentList":{
47
+ # "OperationSegment":[
48
+ # {
49
+ # "AircraftCode": "343",
50
+ # "ArrivalDateTime": "2016-09-07T22:00:00-10:00",
51
+ # "ArrivalLocationCode": "PPT",
52
+ # "DepartureDateTime": "2016-09-07T11:30:00+02:00",
53
+ # "DepartureLocationCode": "CDG",
54
+ # "FlightDuration": "PT22H30M",
55
+ # "OperationSegmentIndex":1
56
+ # }
57
+ # ]
58
+ # }
59
+ # },
60
+ # {
61
+ # "AirCarrierCode": "VT",
62
+ # "AirProviderCode": "1",
63
+ # "AirSegmentIndex":3,
64
+ # "ArrivalDateTime": "2016-09-08T07:35:00-10:00",
65
+ # "ArrivalLocationCode": "BOB",
66
+ # "DepartureDateTime": "2016-09-08T06:45:00-10:00",
67
+ # "DepartureLocationCode": "PPT",
68
+ # "FlightDuration": "PT50M",
69
+ # "FlightNumber": "487",
70
+ # "OperationSegmentList":{
71
+ # "OperationSegment":[
72
+ # {
73
+ # "AircraftCode": "AT7",
74
+ # "ArrivalDateTime": "2016-09-08T07:35:00-10:00",
75
+ # "ArrivalLocationCode": "BOB",
76
+ # "DepartureDateTime": "2016-09-08T06:45:00-10:00",
77
+ # "DepartureLocationCode": "PPT",
78
+ # "FlightDuration": "PT50M",
79
+ # "OperationSegmentIndex":1
80
+ # }
81
+ # ]
82
+ # }
83
+ # }
84
+ # ]
85
+ # },
86
+ # "FlightDuration": "PT36H35M0S",
87
+ # "TotalStops":2
88
+ # }
89
+
90
+ def initialize(raw_data)
91
+ @raw_data = raw_data || {}
92
+ end
93
+
94
+ # returns the flight segments of the flight
95
+ def segments
96
+ @segments ||= begin
97
+ segments = extract_segments.map do |segment|
98
+ FlightCombinationLegSegment.new(segment)
99
+ end
100
+ segments.each do |segment|
101
+ segment.sibling_segments = segments
102
+ end
103
+ segments
104
+ end
105
+ end
106
+
107
+ # extracts the segments of the flight from the json
108
+ def extract_segments
109
+ @raw_data[:AirSegmentList][:AirSegment] || []
110
+ end
111
+
112
+ # returns the total time it will in seconds.
113
+ #
114
+ # format by expedia: "PT21H50M"
115
+ def total_duration_seconds
116
+ hours = hours_all_segments
117
+ minutes = minutes_all_segments
118
+ if hours && minutes
119
+ hours * 3600 + minutes * 60
120
+ else
121
+ 0
122
+ end
123
+ end
124
+
125
+ # returns the hours of the flight, if nor parsable, returns nil
126
+ def hours_all_segments
127
+ stamp = @raw_data[:FlightDuration].split("PT")[1]
128
+ stamp.split("H")[0].to_i
129
+ rescue
130
+ nil
131
+ end
132
+
133
+ # returns the minutes of the flight, if nor parsable, returns nil
134
+ def minutes_all_segments
135
+ stamp = @raw_data[:FlightDuration].split("PT")[1]
136
+ stamp.split("H")[1].split("M")[0].to_i
137
+ rescue
138
+ nil
139
+ end
140
+
141
+ # returns the index of the flight leg
142
+ def index
143
+ @raw_data[:AirLegIndex].to_i
144
+ end
145
+
146
+ # returns true if we have a to flight
147
+ def is_to_flight?
148
+ index == 1
149
+ end
150
+
151
+ # returns true if we have a return flight leg.
152
+ def is_return_flight?
153
+ index == 2
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,140 @@
1
+ module ExpediaApi
2
+ module Entities
3
+ # class handling each of the flight legs segments
4
+ #
5
+ # receives data in this format:
6
+ # {
7
+ # "AirCarrierCode": "AF",
8
+ # "AirProviderCode": "1",
9
+ # "AirSegmentIndex":1,
10
+ # "ArrivalDateTime": "2016-09-07T08:50:00+02:00",
11
+ # "ArrivalLocationCode": "CDG",
12
+ # "DepartureDateTime": "2016-09-07T07:00:00+02:00",
13
+ # "DepartureLocationCode": "TXL",
14
+ # "FlightDuration": "PT1H50M",
15
+ # "FlightNumber": "1135",
16
+ # "OperationSegmentList":{
17
+ # "OperationSegment":[
18
+ # {
19
+ # "AircraftCode": "320",
20
+ # "ArrivalDateTime": "2016-09-07T08:50:00+02:00",
21
+ # "ArrivalLocationCode": "CDG",
22
+ # "DepartureDateTime": "2016-09-07T07:00:00+02:00",
23
+ # "DepartureLocationCode": "TXL",
24
+ # "FlightDuration": "PT1H50M",
25
+ # "OperationSegmentIndex":1
26
+ # }
27
+ # ]
28
+ # }
29
+ # }
30
+ class FlightCombinationLegSegment
31
+
32
+ attr_accessor :sibling_segments
33
+
34
+ def initialize(raw_data)
35
+ @raw_data = raw_data || {}
36
+ @sibling_segments = []
37
+ end
38
+
39
+ # returns a departure datetime for the departure time of the segment
40
+ def departure_time
41
+ DateTime.parse(@raw_data[:DepartureDateTime])
42
+ end
43
+
44
+ # returns a datetime for the arrival date of the segment
45
+ def arrival_time
46
+ DateTime.parse(@raw_data[:ArrivalDateTime])
47
+ end
48
+
49
+ # returns the index of the segment
50
+ def index
51
+ @raw_data[:AirSegmentIndex]
52
+ end
53
+
54
+ # returns the carrier code of the segment
55
+ def carrier_code
56
+ @raw_data[:AirCarrierCode]
57
+ end
58
+
59
+ # returns the departure airport code of the segment
60
+ def departure_airport_code
61
+ @raw_data[:DepartureLocationCode]
62
+ end
63
+
64
+ # returns the arrival airport code of the segment
65
+ def arrival_airport_code
66
+ @raw_data[:ArrivalLocationCode]
67
+ end
68
+
69
+ # returns the time between this segment and the next one.
70
+ def stay_duration_seconds
71
+ return 0 unless next_segment
72
+ # when we arrive at the next airport, minus when we arrived at this
73
+ # airport.
74
+ (next_segment.departure_time.to_time - arrival_time.to_time).to_i
75
+ end
76
+
77
+ # returns the duration how long the segment takes. returns 0 if it can
78
+ # not be identified.
79
+ def duration_seconds
80
+ hours = hours_flight
81
+ minutes = minutes_flight
82
+ if hours && minutes
83
+ hours * 3600 + minutes * 60
84
+ else
85
+ 0
86
+ end
87
+ end
88
+
89
+ # returns the hours of the flight, if nor parsable, returns nil
90
+ def hours_flight
91
+ stamp = @raw_data[:FlightDuration].split("PT")[1]
92
+ stamp.split("H")[0].to_i
93
+ rescue
94
+ nil
95
+ end
96
+
97
+ # returns the minutes of the flight, if nor parsable, returns nil
98
+ def minutes_flight
99
+ stamp = @raw_data[:FlightDuration].split("PT")[1]
100
+ stamp.split("H")[1].split("M")[0].to_i
101
+ rescue
102
+ nil
103
+ end
104
+
105
+ # returns the flight number of the flight
106
+ def flight_number
107
+ @raw_data[:FlightNumber]
108
+ end
109
+
110
+ # returns true if it is the last segment of the flight
111
+ def is_last_segment_of_flight?
112
+ return true if sibling_segments.empty?
113
+ sibling_segments.sort_by {|segment| segment.index }.reverse.first == self
114
+ end
115
+
116
+ # returns true if it is the first segment of the flight
117
+ def is_first_segment_of_flight?
118
+ return true if sibling_segments.empty?
119
+ sibling_segments.sort_by {|segment| segment.index }.first == self
120
+ end
121
+
122
+ # returns the next segment followed by this segment
123
+ def next_segment
124
+ return nil if sibling_segments.empty?
125
+ sibling_segments[sibling_segments.sort_by(&:index).index(self) + 1]
126
+ end
127
+
128
+ # returns the previous segment preceeded by this segment
129
+ def previous_segment
130
+ return nil if sibling_segments.empty?
131
+ index = sibling_segments.sort_by(&:index).index(self)
132
+ if index && index >= 1
133
+ sibling_segments[index - 1]
134
+ else
135
+ nil
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,54 @@
1
+ module ExpediaApi
2
+ module ResponseLists
3
+ class Flights < BaseResponseList
4
+ def entries=(entries)
5
+ @entries = entries
6
+ end
7
+
8
+ def extract_entries_from_response(response)
9
+ json = extract_data_from_response(response).with_indifferent_access
10
+ return [] if json.empty?
11
+ # dup?
12
+ json = json.with_indifferent_access
13
+ outbound_legs_json = json["AirLegListCollection"]["AirLegList"][0]["AirLeg"]
14
+ return_legs_json = json["AirLegListCollection"]["AirLegList"][1]["AirLeg"]
15
+ products_json = JSON.parse(response.body).with_indifferent_access[:AirProductList][:AirProduct]
16
+ outbound_legs = extract_legs(outbound_legs_json)
17
+ return_legs = extract_legs(return_legs_json)
18
+ combinations = extract_combinations(products_json, outbound_legs:outbound_legs, return_legs:return_legs)
19
+ combinations
20
+ end
21
+
22
+ # returns legs for each of the entries
23
+ def extract_legs(legs_json)
24
+ legs_json.map do |json|
25
+ ExpediaApi::Entities::FlightCombinationLeg.new(json)
26
+ end
27
+ end
28
+
29
+ # returns packages for each of the json data
30
+ def extract_combinations(products_json, outbound_legs:, return_legs:)
31
+ # combination_pairs, [outbound_leg_id, return_leg_id, raw_data]
32
+ combination_pairs = products_json.map do |json|
33
+ reference_json = json[:AirLegReferenceList][:AirLegReference]
34
+ [reference_json[0]["AirLegIndex"], reference_json[1]["AirLegIndex"], json]
35
+ end
36
+ outbound_legs_by_index = outbound_legs.map{|leg| [leg.index, leg]}.to_h
37
+ return_legs_by_index = return_legs.map{|leg| [leg.index, leg]}.to_h
38
+ cheapest_price = cheapest_combination_price(products_json)
39
+ combination_pairs.map do |outbound_leg_id, return_leg_id, raw_data|
40
+ entity = ExpediaApi::Entities::FlightCombination.new(raw_data)
41
+ entity.outbound_leg = outbound_legs_by_index[outbound_leg_id]
42
+ entity.return_leg = return_legs_by_index[return_leg_id]
43
+ entity.cheapest_combination_price = cheapest_price
44
+ entity
45
+ end
46
+ end
47
+
48
+ def cheapest_combination_price(products_json)
49
+ products_json.min{|p| p[:PriceInformation][:TotalPrice][:Value].to_f}[:PriceInformation][:TotalPrice]
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -7,12 +7,12 @@ module ExpediaApi
7
7
  end
8
8
 
9
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)
10
+ json = extract_data_from_response(response)
11
+ return [] if json.is_a?(Array) || json.empty?
12
+ json = json.with_indifferent_access
13
+ flights = extract_flights(extract_raw_flights_from_json(json))
14
+ hotels = extract_hotels(extract_raw_hotels_from_json(json))
15
+ packages = extract_packages(extract_raw_packages_from_json(json), hotels: hotels, flights: flights)
16
16
  packages
17
17
  end
18
18
 
@@ -46,6 +46,33 @@ module ExpediaApi
46
46
  entity
47
47
  end
48
48
  end
49
+
50
+ # returns the flight data extracted from the json
51
+ def extract_raw_flights_from_json(json)
52
+ if json[:FlightList] && json[:FlightList][:Flight]
53
+ json[:FlightList][:Flight]
54
+ else
55
+ []
56
+ end
57
+ end
58
+
59
+ # returns the hotel data extracted fron the json
60
+ def extract_raw_hotels_from_json(json)
61
+ if json[:HotelList] && json[:HotelList][:Hotel]
62
+ json[:HotelList][:Hotel]
63
+ else
64
+ []
65
+ end
66
+ end
67
+
68
+ # returns the package data extracted from the json
69
+ def extract_raw_packages_from_json(json)
70
+ if json[:PackageSearchResultList] && json[:PackageSearchResultList][:PackageSearchResult]
71
+ json[:PackageSearchResultList][:PackageSearchResult]
72
+ else
73
+ []
74
+ end
75
+ end
49
76
  end
50
77
 
51
78
  end
@@ -1,3 +1,3 @@
1
1
  module ExpediaApi
2
- VERSION = "0.1.7"
2
+ VERSION = "0.1.8"
3
3
  end
data/lib/expedia_api.rb CHANGED
@@ -14,10 +14,14 @@ require "expedia_api/entities/package_flight"
14
14
  require "expedia_api/entities/package_flight_leg"
15
15
  require "expedia_api/entities/package_flight_leg_segment"
16
16
  require "expedia_api/entities/package_hotel"
17
+ require "expedia_api/entities/flight_combination"
18
+ require "expedia_api/entities/flight_combination_leg"
19
+ require "expedia_api/entities/flight_combination_leg_segment"
17
20
  require "expedia_api/response_lists"
18
21
  require "expedia_api/response_lists/base_response_list"
19
22
  require "expedia_api/response_lists/packages"
20
23
  require "expedia_api/response_lists/hotels"
24
+ require "expedia_api/response_lists/flights"
21
25
 
22
26
  module ExpediaApi
23
27
  class << self
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.7
4
+ version: 0.1.8
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-05-13 00:00:00.000000000 Z
11
+ date: 2016-05-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -156,6 +156,9 @@ files:
156
156
  - lib/expedia_api.rb
157
157
  - lib/expedia_api/client.rb
158
158
  - lib/expedia_api/entities.rb
159
+ - lib/expedia_api/entities/flight_combination.rb
160
+ - lib/expedia_api/entities/flight_combination_leg.rb
161
+ - lib/expedia_api/entities/flight_combination_leg_segment.rb
159
162
  - lib/expedia_api/entities/package.rb
160
163
  - lib/expedia_api/entities/package_flight.rb
161
164
  - lib/expedia_api/entities/package_flight_leg.rb
@@ -165,6 +168,7 @@ files:
165
168
  - lib/expedia_api/http_service.rb
166
169
  - lib/expedia_api/response_lists.rb
167
170
  - lib/expedia_api/response_lists/base_response_list.rb
171
+ - lib/expedia_api/response_lists/flights.rb
168
172
  - lib/expedia_api/response_lists/hotels.rb
169
173
  - lib/expedia_api/response_lists/packages.rb
170
174
  - lib/expedia_api/version.rb