expedia_api 0.1.7 → 0.1.8

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 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