searchapi 1.0.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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +175 -0
  3. data/bin/test +14 -0
  4. data/lib/searchapi/error_result.rb +50 -0
  5. data/lib/searchapi/google_finance_options.rb +40 -0
  6. data/lib/searchapi/google_finance_request.rb +26 -0
  7. data/lib/searchapi/google_finance_result.rb +262 -0
  8. data/lib/searchapi/google_flights_options.rb +85 -0
  9. data/lib/searchapi/google_flights_request.rb +25 -0
  10. data/lib/searchapi/google_flights_result.rb +275 -0
  11. data/lib/searchapi/google_images_options.rb +64 -0
  12. data/lib/searchapi/google_images_request.rb +26 -0
  13. data/lib/searchapi/google_images_result.rb +133 -0
  14. data/lib/searchapi/google_light_options.rb +43 -0
  15. data/lib/searchapi/google_light_request.rb +26 -0
  16. data/lib/searchapi/google_light_result.rb +198 -0
  17. data/lib/searchapi/google_local_options.rb +40 -0
  18. data/lib/searchapi/google_local_request.rb +26 -0
  19. data/lib/searchapi/google_local_result.rb +85 -0
  20. data/lib/searchapi/google_news_light_options.rb +47 -0
  21. data/lib/searchapi/google_news_light_request.rb +26 -0
  22. data/lib/searchapi/google_news_light_result.rb +94 -0
  23. data/lib/searchapi/google_news_options.rb +53 -0
  24. data/lib/searchapi/google_news_portal_options.rb +40 -0
  25. data/lib/searchapi/google_news_portal_request.rb +89 -0
  26. data/lib/searchapi/google_news_portal_result.rb +137 -0
  27. data/lib/searchapi/google_news_request.rb +26 -0
  28. data/lib/searchapi/google_news_result.rb +129 -0
  29. data/lib/searchapi/google_scholar_options.rb +48 -0
  30. data/lib/searchapi/google_scholar_request.rb +26 -0
  31. data/lib/searchapi/google_scholar_result.rb +195 -0
  32. data/lib/searchapi/google_search_options.rb +67 -0
  33. data/lib/searchapi/google_search_request.rb +26 -0
  34. data/lib/searchapi/google_search_result.rb +301 -0
  35. data/lib/searchapi/helpers.rb +10 -0
  36. data/lib/searchapi/instagram_profile_options.rb +31 -0
  37. data/lib/searchapi/instagram_profile_request.rb +26 -0
  38. data/lib/searchapi/instagram_profile_result.rb +117 -0
  39. data/lib/searchapi/module_methods.rb +80 -0
  40. data/lib/searchapi/request.rb +25 -0
  41. data/lib/searchapi/response_methods.rb +14 -0
  42. data/lib/searchapi/tiktok_profile_options.rb +31 -0
  43. data/lib/searchapi/tiktok_profile_request.rb +26 -0
  44. data/lib/searchapi/tiktok_profile_result.rb +71 -0
  45. data/lib/searchapi/version.rb +3 -0
  46. data/lib/searchapi/youtube_search_options.rb +62 -0
  47. data/lib/searchapi/youtube_search_request.rb +26 -0
  48. data/lib/searchapi/youtube_search_result.rb +411 -0
  49. data/lib/searchapi.rb +70 -0
  50. data/searchapi.gemspec +37 -0
  51. metadata +164 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d08a566b8b60588ec83cfb8c18f535de5fe329325dfafb1d8120a61f105afeac
4
+ data.tar.gz: 4a59d8ca1b3921e1d419abd37789ca5098b61f4dd0a5465c1674b76445cff156
5
+ SHA512:
6
+ metadata.gz: 680bf0570d6b8b814cf7256c5bc3df04d0a3e60f3c38cc7ff420044a51e15836835c8b40ead4c8a613c69016512206cc8f72fe41602124127b8130883d1408bd
7
+ data.tar.gz: 1dc489acd03a4540dcf63121ae4c4ece11f550f795c0e86a6f4326420c263dee4b0ac0bfc92b805ede693c24232f6d62dbf135bbb64424177963d52c342a7e45
data/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # SearchAPI Ruby Gem
2
+
3
+ A Ruby client for the [SearchAPI.io](https://searchapi.io) API, providing access to Google Search, YouTube, Instagram, TikTok, and more.
4
+
5
+ ## Installation
6
+
7
+ Add to your Gemfile:
8
+
9
+ ```ruby
10
+ gem 'searchapi'
11
+ ```
12
+
13
+ Or install directly:
14
+
15
+ ```bash
16
+ gem install searchapi
17
+ ```
18
+
19
+ ## Configuration
20
+
21
+ ```ruby
22
+ require 'searchapi'
23
+
24
+ SearchAPI.api_key ENV['SEARCH_API_KEY']
25
+ ```
26
+
27
+ ## Available APIs
28
+
29
+ | API | Method | Description |
30
+ |-----|--------|-------------|
31
+ | Google Search | `SearchAPI.google(query)` | Full Google search with all features |
32
+ | Google Light | `SearchAPI.google_light(query)` | Fast, lightweight Google search |
33
+ | Google News | `SearchAPI.news(query)` | Google News articles |
34
+ | Google News Portal | `SearchAPI.news_portal(query)` | Google News Portal with topics |
35
+ | Google News Light | `SearchAPI.news_light(query)` | Fast, lightweight news search |
36
+ | Google Images | `SearchAPI.images(query)` | Google Image search |
37
+ | Google Local | `SearchAPI.local(query)` | Local business search |
38
+ | Google Scholar | `SearchAPI.scholar(query)` | Academic papers and citations |
39
+ | Google Finance | `SearchAPI.finance(symbol)` | Stock quotes and financial data |
40
+ | Google Flights | `SearchAPI.flights(options)` | Flight search and booking |
41
+ | YouTube | `SearchAPI.youtube(query)` | YouTube video search |
42
+ | Instagram | `SearchAPI.instagram(username)` | Instagram profile data |
43
+ | TikTok | `SearchAPI.tiktok(username)` | TikTok profile data |
44
+
45
+ ## Quick Examples
46
+
47
+ ### Google Search
48
+
49
+ ```ruby
50
+ response = SearchAPI.google('ruby programming')
51
+ response.result.organic_results.each do |result|
52
+ puts "#{ result.title } - #{ result.link }"
53
+ end
54
+ ```
55
+
56
+ ### Google News
57
+
58
+ ```ruby
59
+ response = SearchAPI.news('technology', time_period: :last_week)
60
+ response.result.each do |article|
61
+ puts "#{ article.title } (#{ article.source })"
62
+ end
63
+ ```
64
+
65
+ ### Google Images
66
+
67
+ ```ruby
68
+ response = SearchAPI.images('sunset', size: :large, color: :orange)
69
+ response.result.each do |image|
70
+ puts "#{ image.title } - #{ image.original.link }"
71
+ end
72
+ ```
73
+
74
+ ### Google Finance
75
+
76
+ ```ruby
77
+ response = SearchAPI.finance('AAPL:NASDAQ')
78
+ puts "#{ response.result.summary.title }: $#{ response.result.summary.price }"
79
+ ```
80
+
81
+ ### Google Flights
82
+
83
+ ```ruby
84
+ options = SearchAPI::GoogleFlightsOptions.build do
85
+ departure_id 'JFK'
86
+ arrival_id 'LAX'
87
+ outbound_date '2026-06-15'
88
+ flight_type :one_way
89
+ end
90
+
91
+ response = SearchAPI.flights(options)
92
+ puts "Cheapest: $#{ response.result.cheapest&.price }"
93
+ ```
94
+
95
+ ### YouTube Search
96
+
97
+ ```ruby
98
+ response = SearchAPI.youtube('ruby tutorial')
99
+ response.result.videos.each do |video|
100
+ puts "#{ video.title } by #{ video.channel.title }"
101
+ end
102
+ ```
103
+
104
+ ### Social Media Profiles
105
+
106
+ ```ruby
107
+ # Instagram
108
+ response = SearchAPI.instagram('instagram')
109
+ puts "#{ response.result.profile.username }: #{ response.result.followers } followers"
110
+
111
+ # TikTok
112
+ response = SearchAPI.tiktok('tiktok')
113
+ puts "#{ response.result.profile.name }: #{ response.result.hearts } hearts"
114
+ ```
115
+
116
+ ## Using Options
117
+
118
+ Each API supports options via a block or hash:
119
+
120
+ ```ruby
121
+ # Block syntax
122
+ options = SearchAPI::GoogleSearchOptions.build do
123
+ device :mobile
124
+ gl 'us'
125
+ hl 'en'
126
+ time_period :last_week
127
+ end
128
+ response = SearchAPI.google('news', options)
129
+
130
+ # Hash syntax
131
+ response = SearchAPI.google('news', { device: :mobile, gl: 'us' })
132
+ ```
133
+
134
+ Options are case-insensitive and normalized automatically:
135
+
136
+ ```ruby
137
+ # These are equivalent
138
+ SearchAPI::GoogleFinanceOptions.build { window :'1d' } # => '1D'
139
+ SearchAPI::GoogleFinanceOptions.build { window :'1D' } # => '1D'
140
+ SearchAPI::GoogleFinanceOptions.build { window :MAX } # => 'MAX'
141
+ ```
142
+
143
+ ## Response Structure
144
+
145
+ All responses return a Faraday response with a `result` accessor:
146
+
147
+ ```ruby
148
+ response = SearchAPI.google('test')
149
+ response.success? # HTTP success
150
+ response.result.success? # API success (no error field)
151
+ response.result.search_metadata
152
+ response.result.search_parameters
153
+ response.result.organic_results
154
+ ```
155
+
156
+ Results are enumerable where it makes sense:
157
+
158
+ ```ruby
159
+ response = SearchAPI.google('ruby')
160
+ response.result.each { |r| puts r.title } # Iterates organic_results
161
+ response.result.first # First organic result
162
+ response.result.count # Number of organic results
163
+ ```
164
+
165
+ ## Documentation
166
+
167
+ See the `/readme` directory for detailed documentation on each API endpoint.
168
+
169
+ ## Contributing
170
+
171
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/EndlessInternational/searchapi](https://github.com/EndlessInternational/searchapi).
172
+
173
+ ## License
174
+
175
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/bin/test ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+
5
+ $LOAD_PATH.unshift File.expand_path( '../lib', __dir__ )
6
+ $LOAD_PATH.unshift File.expand_path( '../test', __dir__ )
7
+
8
+ test_files = if ARGV.empty?
9
+ Dir.glob( File.expand_path( '../test/**/*_test.rb', __dir__ ) )
10
+ else
11
+ ARGV.map { | arg | File.expand_path( arg, Dir.pwd ) }
12
+ end
13
+
14
+ test_files.each { | file | require file }
@@ -0,0 +1,50 @@
1
+ module SearchAPI
2
+ class ErrorResult
3
+
4
+ attr_reader :error_type, :error_description
5
+
6
+ def initialize( status_code, attributes = nil )
7
+ @error_type, @error_description = status_code_to_error( status_code )
8
+ if attributes&.respond_to?( :[] ) && attributes[ :error ]
9
+ @error_description = attributes[ :error ]
10
+ end
11
+ end
12
+
13
+ def success?
14
+ false
15
+ end
16
+
17
+ private
18
+
19
+ def status_code_to_error( status_code )
20
+ case status_code
21
+ when 200
22
+ [ :unexpected_error,
23
+ "The response was successful but it did not include a valid payload." ]
24
+ when 400
25
+ [ :invalid_request_error,
26
+ "The request is invalid due to malformed parameters, malformed JSON, or " \
27
+ "missing required fields." ]
28
+ when 401
29
+ [ :authentication_error,
30
+ "The API key is missing or invalid." ]
31
+ when 403
32
+ [ :forbidden_error,
33
+ "The API key is valid but has insufficient permissions." ]
34
+ when 404
35
+ [ :not_found_error,
36
+ "The requested resource was not found." ]
37
+ when 429
38
+ [ :rate_limit_error,
39
+ "The rate limit has been exceeded." ]
40
+ when 500..599
41
+ [ :server_error,
42
+ "The SearchAPI server encountered an error while processing the request." ]
43
+ else
44
+ [ :unknown_error,
45
+ "The SearchAPI service returned an unexpected status code: '#{ status_code }'." ]
46
+ end
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,40 @@
1
+ module SearchAPI
2
+ class GoogleFinanceOptions
3
+ include DynamicSchema::Definable
4
+ include Helpers
5
+
6
+ WINDOWS = %w[ 1D 5D 1M 6M YTD 1Y 5Y MAX ]
7
+ NORMALIZE_UPCASE = ->(v) { v.to_s.upcase }
8
+
9
+ schema do
10
+ # query (stock symbol, index, currency pair, etc.)
11
+ q String
12
+
13
+ # language
14
+ hl String
15
+
16
+ # time window for historical data
17
+ window String, in: WINDOWS, normalize: NORMALIZE_UPCASE
18
+ end
19
+
20
+ def self.build( options = nil, &block )
21
+ new( api_options: builder.build( options, &block ) )
22
+ end
23
+
24
+ def self.build!( options = nil, &block )
25
+ new( api_options: builder.build!( options, &block ) )
26
+ end
27
+
28
+ def initialize( options = {}, api_options: nil )
29
+ @options = self.class.builder.build( options || {} )
30
+ @options = api_options.merge( @options ) if api_options
31
+ end
32
+
33
+ def to_h
34
+ result = @options.to_h
35
+ result[ :engine ] = 'google_finance'
36
+ result
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+ module SearchAPI
2
+ class GoogleFinanceRequest < Request
3
+
4
+ def quote( symbol, options = nil, &block )
5
+ if options
6
+ options = options.is_a?( GoogleFinanceOptions ) ? options : GoogleFinanceOptions.build!( options.to_h )
7
+ options = options.to_h
8
+ else
9
+ options = { engine: 'google_finance' }
10
+ end
11
+ options[ :q ] = symbol.to_s
12
+
13
+ response = get( "#{ BASE_URI }/search", options, &block )
14
+ attributes = ( JSON.parse( response.body, symbolize_names: true ) rescue nil )
15
+
16
+ result = if response.success?
17
+ GoogleFinanceResult.new( attributes || {} )
18
+ else
19
+ ErrorResult.new( response.status, attributes )
20
+ end
21
+
22
+ ResponseMethods.install( response, result )
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,262 @@
1
+ require 'forwardable'
2
+
3
+ module SearchAPI
4
+
5
+ # Google Finance Search metadata
6
+ GoogleFinanceMetadataSchema = DynamicSchema::Struct.define do
7
+ id String
8
+ status String
9
+ created_at String, as: :created_at
10
+ request_time_taken Float, as: :request_time_taken
11
+ parsing_time_taken Float, as: :parsing_time_taken
12
+ total_time_taken Float, as: :total_time_taken
13
+ request_url String, as: :request_url
14
+ html_url String, as: :html_url
15
+ json_url String, as: :json_url
16
+ end
17
+
18
+ class GoogleFinanceMetadata < GoogleFinanceMetadataSchema
19
+ end
20
+
21
+ # Google Finance Search parameters
22
+ GoogleFinanceParametersSchema = DynamicSchema::Struct.define do
23
+ engine String
24
+ q String
25
+ hl String
26
+ window String
27
+ end
28
+
29
+ class GoogleFinanceParameters < GoogleFinanceParametersSchema
30
+ end
31
+
32
+ # Price change
33
+ GoogleFinancePriceChangeSchema = DynamicSchema::Struct.define do
34
+ percentage Float
35
+ amount Float
36
+ movement String
37
+ end
38
+
39
+ class GoogleFinancePriceChange < GoogleFinancePriceChangeSchema
40
+ end
41
+
42
+ # Summary
43
+ GoogleFinanceSummarySchema = DynamicSchema::Struct.define do
44
+ title String
45
+ stock String
46
+ exchange String
47
+ price Float
48
+ currency String
49
+ date String
50
+ price_change GoogleFinancePriceChange, as: :price_change
51
+ end
52
+
53
+ class GoogleFinanceSummary < GoogleFinanceSummarySchema
54
+ end
55
+
56
+ # Key event
57
+ GoogleFinanceKeyEventSchema = DynamicSchema::Struct.define do
58
+ title String
59
+ link String
60
+ source String
61
+ source_date String, as: :source_date
62
+ date String
63
+ price_change GoogleFinancePriceChange, as: :price_change
64
+ end
65
+
66
+ class GoogleFinanceKeyEvent < GoogleFinanceKeyEventSchema
67
+ end
68
+
69
+ # Graph point
70
+ GoogleFinanceGraphPointSchema = DynamicSchema::Struct.define do
71
+ price Float
72
+ currency String
73
+ date String
74
+ volume Integer
75
+ end
76
+
77
+ class GoogleFinanceGraphPoint < GoogleFinanceGraphPointSchema
78
+ end
79
+
80
+ # Knowledge graph tag
81
+ GoogleFinanceTagSchema = DynamicSchema::Struct.define do
82
+ title String
83
+ description String
84
+ end
85
+
86
+ class GoogleFinanceTag < GoogleFinanceTagSchema
87
+ end
88
+
89
+ # Knowledge graph stat
90
+ GoogleFinanceStatSchema = DynamicSchema::Struct.define do
91
+ label String
92
+ description String
93
+ value String
94
+ end
95
+
96
+ class GoogleFinanceStat < GoogleFinanceStatSchema
97
+ end
98
+
99
+ # Company about
100
+ GoogleFinanceCompanyAboutSchema = DynamicSchema::Struct.define do
101
+ company String
102
+ description String
103
+ address String
104
+ founded String
105
+ ceo String
106
+ employees Integer
107
+ website String
108
+ wikipedia String
109
+ end
110
+
111
+ class GoogleFinanceCompanyAbout < GoogleFinanceCompanyAboutSchema
112
+ end
113
+
114
+ # Knowledge graph
115
+ GoogleFinanceKnowledgeGraphSchema = DynamicSchema::Struct.define do
116
+ tags GoogleFinanceTag, array: true
117
+ stats GoogleFinanceStat, array: true
118
+ about GoogleFinanceCompanyAbout
119
+ end
120
+
121
+ class GoogleFinanceKnowledgeGraph < GoogleFinanceKnowledgeGraphSchema
122
+ end
123
+
124
+ # News article
125
+ GoogleFinanceArticleSchema = DynamicSchema::Struct.define do
126
+ snippet String
127
+ link String
128
+ source String
129
+ date String
130
+ thumbnail String
131
+ end
132
+
133
+ class GoogleFinanceArticle < GoogleFinanceArticleSchema
134
+ end
135
+
136
+ # News category
137
+ GoogleFinanceNewsCategorySchema = DynamicSchema::Struct.define do
138
+ title String
139
+ articles GoogleFinanceArticle, array: true
140
+ end
141
+
142
+ class GoogleFinanceNewsCategory < GoogleFinanceNewsCategorySchema
143
+ end
144
+
145
+ # Financial metric
146
+ GoogleFinanceMetricSchema = DynamicSchema::Struct.define do
147
+ value Float
148
+ last_year_value Float, as: :last_year_value
149
+ price_change GoogleFinancePriceChange, as: :price_change
150
+ end
151
+
152
+ class GoogleFinanceMetric < GoogleFinanceMetricSchema
153
+ end
154
+
155
+ # Quarterly financial
156
+ GoogleFinanceQuarterlySchema = DynamicSchema::Struct.define do
157
+ year Integer
158
+ quarter String
159
+ currency String
160
+ revenue GoogleFinanceMetric
161
+ net_income GoogleFinanceMetric, as: :net_income
162
+ earnings_per_share GoogleFinanceMetric, as: :earnings_per_share
163
+ net_profit_margin GoogleFinanceMetric, as: :net_profit_margin
164
+ total_assets GoogleFinanceMetric, as: :total_assets
165
+ total_equity GoogleFinanceMetric, as: :total_equity
166
+ total_liabilities GoogleFinanceMetric, as: :total_liabilities
167
+ free_cash_flow GoogleFinanceMetric, as: :free_cash_flow
168
+ end
169
+
170
+ class GoogleFinanceQuarterly < GoogleFinanceQuarterlySchema
171
+ end
172
+
173
+ # Annual financial
174
+ GoogleFinanceAnnualSchema = DynamicSchema::Struct.define do
175
+ year Integer
176
+ currency String
177
+ revenue GoogleFinanceMetric
178
+ net_income GoogleFinanceMetric, as: :net_income
179
+ earnings_per_share GoogleFinanceMetric, as: :earnings_per_share
180
+ net_profit_margin GoogleFinanceMetric, as: :net_profit_margin
181
+ total_assets GoogleFinanceMetric, as: :total_assets
182
+ total_equity GoogleFinanceMetric, as: :total_equity
183
+ total_liabilities GoogleFinanceMetric, as: :total_liabilities
184
+ free_cash_flow GoogleFinanceMetric, as: :free_cash_flow
185
+ end
186
+
187
+ class GoogleFinanceAnnual < GoogleFinanceAnnualSchema
188
+ end
189
+
190
+ # Financials
191
+ GoogleFinanceFinancialsSchema = DynamicSchema::Struct.define do
192
+ quarterly GoogleFinanceQuarterly, array: true
193
+ annual GoogleFinanceAnnual, array: true
194
+ end
195
+
196
+ class GoogleFinanceFinancials < GoogleFinanceFinancialsSchema
197
+ end
198
+
199
+ # Market stock
200
+ GoogleFinanceMarketStockSchema = DynamicSchema::Struct.define do
201
+ stock String
202
+ exchange String
203
+ company String
204
+ link String
205
+ name String
206
+ price Float
207
+ price_change GoogleFinancePriceChange, as: :price_change
208
+ date String
209
+ end
210
+
211
+ class GoogleFinanceMarketStock < GoogleFinanceMarketStockSchema
212
+ end
213
+
214
+ # Discover item
215
+ GoogleFinanceDiscoverItemSchema = DynamicSchema::Struct.define do
216
+ stock String
217
+ link String
218
+ name String
219
+ price Float
220
+ price_change GoogleFinancePriceChange, as: :price_change
221
+ end
222
+
223
+ class GoogleFinanceDiscoverItem < GoogleFinanceDiscoverItemSchema
224
+ end
225
+
226
+ # Discover section
227
+ GoogleFinanceDiscoverSectionSchema = DynamicSchema::Struct.define do
228
+ title String
229
+ items GoogleFinanceDiscoverItem, array: true
230
+ end
231
+
232
+ class GoogleFinanceDiscoverSection < GoogleFinanceDiscoverSectionSchema
233
+ end
234
+
235
+ # Main Google Finance result
236
+ GoogleFinanceResultSchema = DynamicSchema::Struct.define do
237
+ search_metadata GoogleFinanceMetadata, as: :search_metadata
238
+ search_parameters GoogleFinanceParameters, as: :search_parameters
239
+ summary GoogleFinanceSummary
240
+ key_events GoogleFinanceKeyEvent, array: true, as: :key_events
241
+ graph GoogleFinanceGraphPoint, array: true
242
+ knowledge_graph GoogleFinanceKnowledgeGraph, as: :knowledge_graph
243
+ news GoogleFinanceNewsCategory, array: true
244
+ articles GoogleFinanceArticle, array: true
245
+ financials GoogleFinanceFinancials
246
+ markets Hash
247
+ compare_to GoogleFinanceMarketStock, array: true, as: :compare_to
248
+ discover_more GoogleFinanceDiscoverSection, array: true, as: :discover_more
249
+ error String
250
+ end
251
+
252
+ class GoogleFinanceResult < GoogleFinanceResultSchema
253
+ extend Forwardable
254
+
255
+ def_delegators :summary, :title, :stock, :exchange, :price, :currency
256
+
257
+ def success?
258
+ self.error.nil?
259
+ end
260
+ end
261
+
262
+ end
@@ -0,0 +1,85 @@
1
+ module SearchAPI
2
+ class GoogleFlightsOptions
3
+ include DynamicSchema::Definable
4
+ include Helpers
5
+
6
+ FLIGHT_TYPES = %w[ round_trip one_way multi_city ]
7
+ STOPS = %w[ any nonstop one_stop_or_fewer two_stops_or_fewer ]
8
+ SORT_BY = %w[ top_flights price departure_time arrival_time duration emissions ]
9
+ TRAVEL_CLASSES = %w[ economy premium_economy business first_class ]
10
+
11
+ NORMALIZE_DOWNCASE = ->(v) { v.to_s.downcase }
12
+
13
+ schema do
14
+ # locations
15
+ departure_id String
16
+ arrival_id String
17
+
18
+ # dates
19
+ outbound_date String
20
+ return_date String
21
+
22
+ # localization
23
+ hl String
24
+ gl String
25
+ currency String
26
+
27
+ # flight options
28
+ flight_type String, in: FLIGHT_TYPES, normalize: NORMALIZE_DOWNCASE
29
+ stops String, in: STOPS, normalize: NORMALIZE_DOWNCASE
30
+ sort_by String, in: SORT_BY, normalize: NORMALIZE_DOWNCASE
31
+ travel_class String, in: TRAVEL_CLASSES, normalize: NORMALIZE_DOWNCASE
32
+
33
+ # passengers
34
+ adults Integer, in: 1..9
35
+ children Integer, in: 0..9
36
+ infants_in_seat Integer, in: 0..9
37
+ infants_on_lap Integer, in: 0..9
38
+
39
+ # filters
40
+ max_price Integer
41
+ carry_on_bags Integer
42
+ checked_bags Integer
43
+ included_airlines String
44
+ excluded_airlines String
45
+ included_connecting_airports String
46
+ excluded_connecting_airports String
47
+ outbound_times String
48
+ return_times String
49
+ emissions String
50
+ layover_duration_min Integer
51
+ layover_duration_max Integer
52
+ max_flight_duration Integer
53
+ separate_tickets String
54
+
55
+ # pagination/booking
56
+ departure_token String
57
+ booking_token String
58
+ multi_city_json String
59
+
60
+ # display options
61
+ show_cheapest_flights [ TrueClass, FalseClass ]
62
+ show_hidden_flights [ TrueClass, FalseClass ]
63
+ end
64
+
65
+ def self.build( options = nil, &block )
66
+ new( api_options: builder.build( options, &block ) )
67
+ end
68
+
69
+ def self.build!( options = nil, &block )
70
+ new( api_options: builder.build!( options, &block ) )
71
+ end
72
+
73
+ def initialize( options = {}, api_options: nil )
74
+ @options = self.class.builder.build( options || {} )
75
+ @options = api_options.merge( @options ) if api_options
76
+ end
77
+
78
+ def to_h
79
+ result = @options.to_h
80
+ result[ :engine ] = 'google_flights'
81
+ result
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,25 @@
1
+ module SearchAPI
2
+ class GoogleFlightsRequest < Request
3
+
4
+ def search( options = nil, &block )
5
+ if options
6
+ options = options.is_a?( GoogleFlightsOptions ) ? options : GoogleFlightsOptions.build!( options.to_h )
7
+ options = options.to_h
8
+ else
9
+ options = { engine: 'google_flights' }
10
+ end
11
+
12
+ response = get( "#{ BASE_URI }/search", options, &block )
13
+ attributes = ( JSON.parse( response.body, symbolize_names: true ) rescue nil )
14
+
15
+ result = if response.success?
16
+ GoogleFlightsResult.new( attributes || {} )
17
+ else
18
+ ErrorResult.new( response.status, attributes )
19
+ end
20
+
21
+ ResponseMethods.install( response, result )
22
+ end
23
+
24
+ end
25
+ end