bing-search 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +165 -0
- data/lib/bing-search.rb +93 -0
- data/lib/bing-search/client.rb +614 -0
- data/lib/bing-search/enums.rb +78 -0
- data/lib/bing-search/errors.rb +23 -0
- data/lib/bing-search/models.rb +255 -0
- data/lib/bing-search/version.rb +3 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3f89243c5039d162db74eea16a768640ca39c090
|
4
|
+
data.tar.gz: 5bbed193646f93ccd1b1bfa802cea73612a426ee
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7502295d14673dbe7880875c4db6b52edc09c97d9e39a2ff50a178ab18832bc784e1ba0af297b4d5a4c86aeea44bb34ecc99c5bb10e4e151a4f2a2c263bcbe85
|
7
|
+
data.tar.gz: 1639f625fdedaf8efceb98224e6da870bc10d5d92b387aed79fd90d30a6cb2eece3e033f1d1bb8062ba2b3b071f8e1adb8443bdd3595c26f7e76dc107781d607
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Jonah Burke
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
# bing-search
|
2
|
+
|
3
|
+
A Ruby client for the [Bing Search API](http://datamarket.azure.com/dataset/bing/search).
|
4
|
+
|
5
|
+
## Getting Started
|
6
|
+
|
7
|
+
### Installation
|
8
|
+
|
9
|
+
```bash
|
10
|
+
gem install bing-search
|
11
|
+
```
|
12
|
+
|
13
|
+
### Signup
|
14
|
+
|
15
|
+
Sign up for the [Bing Search API](https://datamarket.azure.com/dataset/bing/search) or the [Web-Only Bing Search API](https://datamarket.azure.com/dataset/bing/searchweb) at the Microsoft Azure Marketplace. Then retrieve your Account Key from the [My Account](https://datamarket.azure.com/account) section of the marketplace and provide it as shown below.
|
16
|
+
|
17
|
+
## Basics
|
18
|
+
|
19
|
+
To use bing-search, first supply your Account Key:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
BingSearch.account_key = 'hzy9+Y6...'
|
23
|
+
```
|
24
|
+
|
25
|
+
Then, use {BingSearch.web} to search for web pages:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
results = BingSearch.web('Dirac')
|
29
|
+
```
|
30
|
+
|
31
|
+
Or, use the other {BingSearch} class methods to search for images, news, video, related searches, and spelling suggestions:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
BingSearch.spelling('Feinman').first.suggestion # => "Feynman"
|
35
|
+
```
|
36
|
+
|
37
|
+
The type of result depends on the kind of search:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
BingSearch.web('Gell-Mann').class # => WebResult
|
41
|
+
BingSearch.image('Pauli').class # => ImageResult
|
42
|
+
BingSearch.video('von Neumann').class # => VideoResult
|
43
|
+
```
|
44
|
+
|
45
|
+
And each result type has its own attributes:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
web = BingSearch.web('Gell-Mann').first
|
49
|
+
web.summary # => "Murray Gell-Mann (born September 15, 1929) is an American physicist ..."
|
50
|
+
|
51
|
+
image = BingSearch.image('Pauli').first
|
52
|
+
image.media_type # => "image/jpeg"
|
53
|
+
|
54
|
+
video = BingSearch.video('von Neumann').first
|
55
|
+
video.duration # => 151000
|
56
|
+
```
|
57
|
+
|
58
|
+
See the documentation of the result types for a full list of the attributes.
|
59
|
+
|
60
|
+
## Options
|
61
|
+
|
62
|
+
The search methods take options that control the number of results returned;
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
BingSearch.web('Dyson', limit: 5).count # => 5
|
66
|
+
```
|
67
|
+
|
68
|
+
the size, orientation, and contents of images;
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
BingSearch.image 'Tesla', filters: [:large, :wide, :photo, :face]
|
72
|
+
```
|
73
|
+
|
74
|
+
whether to {BingSearch::HIGHLIGHT_DELIMITER highlight} query terms in the results;
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
BingSearch.news('Hawking', highlighting: true).first.title # => "How Intel Gave Stephen Hawking a Voice"
|
78
|
+
```
|
79
|
+
|
80
|
+
and many other aspects of the search. Note that "enumeration" options—those whose values are module-level constants—may be provided as underscored symbols:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
# equivalent searches
|
84
|
+
BingSearch.news 'Higgs', category: BingSearch::NewsCategory::ScienceAndTechnology
|
85
|
+
BingSearch.news 'Higgs', category: :science_and_technology
|
86
|
+
```
|
87
|
+
|
88
|
+
See {BingSearch::Client} for exhaustive documentation of the options.
|
89
|
+
|
90
|
+
## Composite Searches
|
91
|
+
|
92
|
+
To retrieve multiple result types at once, use {BingSearch.composite}:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
result = BingSearch.composite('Majorana', [:web, :image, :news])
|
96
|
+
```
|
97
|
+
|
98
|
+
The result is a {BingSearch::CompositeResult} ...
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
result.class # => BingSearch::CompositeResult
|
102
|
+
```
|
103
|
+
|
104
|
+
... containing an array for each result type:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
result.web.first.class # => BingSearch::WebResult
|
108
|
+
result.image.first.class # => BingSearch::ImageResult
|
109
|
+
result.news.first.class # => BingSearch::NewsResult
|
110
|
+
```
|
111
|
+
|
112
|
+
All of the single-type search options are supported in composite searches, though the names may have prefixes to specify the type they pertain to:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
BingSearch.composite 'Fermi', [:image, :video], image_filters: [:small], video_filters: [:short]
|
116
|
+
```
|
117
|
+
|
118
|
+
Composite searches also give you access more data about the search including the total number of results in the Bing index and whether Bing corrected apparent errors in the query text:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
result = BingSearch.composite('Feyman', [:web, :image, :news])
|
122
|
+
result.web_total # => 2400000
|
123
|
+
result.altered_query # => "feynman"
|
124
|
+
```
|
125
|
+
|
126
|
+
## Web-Only API
|
127
|
+
|
128
|
+
To use the less expensive [web-only API](https://datamarket.azure.com/dataset/bing/searchweb), set {BingSearch.web_only}:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
BingSearch.web_only = true
|
132
|
+
BingSearch.news 'Newton' # => BingSearch::ServiceError
|
133
|
+
BingSearch.web 'Newton'
|
134
|
+
```
|
135
|
+
|
136
|
+
## BingSearch::Client
|
137
|
+
|
138
|
+
{BingSearch::Client} is the class underlying the {BingSearch} class methods. You can use it on its own to run multiple searches over a single TCP connection:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
BingSearch::Client.open do |client|
|
142
|
+
client.web 'Lee'
|
143
|
+
client.web 'Wu'
|
144
|
+
client.web 'Yang'
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
Or to override global settings:
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
client = BingSearch::Client.new(access_key: 'hzy9+Y6...', web_only: true)
|
152
|
+
```
|
153
|
+
|
154
|
+
## Tests
|
155
|
+
|
156
|
+
To run the tests:
|
157
|
+
|
158
|
+
1. Sign up for both the standard and web-only APIs
|
159
|
+
2. Set the environment variable BING\_SEARCH\_ACCESS\_KEY to your Access Key
|
160
|
+
3. `rake`
|
161
|
+
|
162
|
+
## Contributing
|
163
|
+
|
164
|
+
Please submit issues and pull requests to [jonahb/bing-search](http://github.com/jonahb/bing-search) on GitHub.
|
165
|
+
|
data/lib/bing-search.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
%w{
|
2
|
+
client
|
3
|
+
enums
|
4
|
+
errors
|
5
|
+
models
|
6
|
+
version
|
7
|
+
}.each do |file|
|
8
|
+
require File.expand_path("../bing-search/#{file}", __FILE__)
|
9
|
+
end
|
10
|
+
|
11
|
+
module BingSearch
|
12
|
+
|
13
|
+
HIGHLIGHT_DELIMITER = "\u{e001}"
|
14
|
+
|
15
|
+
class << self
|
16
|
+
# An Access Key obtained from the Azure Marketplace. You can set this
|
17
|
+
# attribute once instead of instantiating each {Client} with an Access Key.
|
18
|
+
# @return [String]
|
19
|
+
attr_accessor :access_key
|
20
|
+
|
21
|
+
# Whether to use the less expensive web-only API
|
22
|
+
# @return [Boolean]
|
23
|
+
attr_accessor :web_only
|
24
|
+
|
25
|
+
# Convenience method that creates a {Client} and searches for web pages.
|
26
|
+
# Takes the same arguments as {Client#web}. Set {access_key} before calling.
|
27
|
+
# @return (see Client#web)
|
28
|
+
# @see Client#web
|
29
|
+
#
|
30
|
+
def web(*args)
|
31
|
+
Client.new.web *args
|
32
|
+
end
|
33
|
+
|
34
|
+
# Convenience method that creates a {Client} and searches for images. Takes
|
35
|
+
# the same arguments as {Client#image}. Set {access_key} before calling.
|
36
|
+
# @return (see Client#image)
|
37
|
+
# @see Client#image
|
38
|
+
#
|
39
|
+
def image(*args)
|
40
|
+
Client.new.image *args
|
41
|
+
end
|
42
|
+
|
43
|
+
# Convenience method that creates a {Client} and searches for videos. Takes
|
44
|
+
# the same arguments as {Client#video}. Set {access_key} before calling.
|
45
|
+
# @return (see Client#video)
|
46
|
+
# @see Client#video
|
47
|
+
#
|
48
|
+
def video(*args)
|
49
|
+
Client.new.video *args
|
50
|
+
end
|
51
|
+
|
52
|
+
# Convenience method that creates a {Client} and searches for news. Takes
|
53
|
+
# the same arguments as {Client#news}. Set {access_key} before calling.
|
54
|
+
# @return (see Client#news)
|
55
|
+
# @see Client#news
|
56
|
+
#
|
57
|
+
def news(*args)
|
58
|
+
Client.new.news *args
|
59
|
+
end
|
60
|
+
|
61
|
+
# Convenience method that creates a {Client} and searches for related
|
62
|
+
# queries. Takes the same arguments as {Client#related_search}. Set {access_key}
|
63
|
+
# before calling.
|
64
|
+
# @return (see Client#related_search)
|
65
|
+
# @see Client#related_search
|
66
|
+
#
|
67
|
+
def related_search(*args)
|
68
|
+
Client.new.related_search *args
|
69
|
+
end
|
70
|
+
alias_method :related, :related_search
|
71
|
+
|
72
|
+
# Convenience method that creates a {Client} and corrects spelling in the
|
73
|
+
# query text. Takes the same arguments as {Client#related_search}. Set
|
74
|
+
# {access_key} before calling.
|
75
|
+
# @return (see Client#spelling_suggestions)
|
76
|
+
# @see Client#spelling_suggestions
|
77
|
+
#
|
78
|
+
def spelling_suggestions(*args)
|
79
|
+
Client.new.spelling_suggestions *args
|
80
|
+
end
|
81
|
+
alias_method :spelling, :spelling_suggestions
|
82
|
+
|
83
|
+
# Convenience method that creates a {Client} and searches multiple sources
|
84
|
+
# Takes the same arguments as {Client#related_search}. Set {access_key} before
|
85
|
+
# calling.
|
86
|
+
# @return (see Client#composite)
|
87
|
+
# @see Client#composite
|
88
|
+
#
|
89
|
+
def composite(*args)
|
90
|
+
Client.new.composite *args
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,614 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'json'
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
require 'active_support/core_ext/hash/slice'
|
6
|
+
require 'active_support/core_ext/string/inflections'
|
7
|
+
require_relative 'errors'
|
8
|
+
require_relative 'models'
|
9
|
+
require_relative 'enums'
|
10
|
+
|
11
|
+
module BingSearch
|
12
|
+
class Client
|
13
|
+
# The Access Key obtained from the Azure Marketplace
|
14
|
+
# @return [String]
|
15
|
+
attr_reader :access_key
|
16
|
+
|
17
|
+
# Whether to use the less expensive web-only API
|
18
|
+
# @return [Boolean]
|
19
|
+
attr_reader :web_only
|
20
|
+
|
21
|
+
|
22
|
+
# @!group Constructors
|
23
|
+
|
24
|
+
# @param [String, nil] access_key
|
25
|
+
# An Access Key obtained from the Azure Marketplace. If nil,
|
26
|
+
# {BingSearch.access_key} is assumed.
|
27
|
+
# @param [Boolean, nil] web_only
|
28
|
+
# Whether to use the less expensive web-only API. If nil,
|
29
|
+
# {BingSearch.web_only} is assumed.
|
30
|
+
#
|
31
|
+
def initialize(access_key: nil, web_only: nil)
|
32
|
+
@access_key = access_key || BingSearch.access_key
|
33
|
+
@web_only = web_only.nil? ? BingSearch.web_only : web_only
|
34
|
+
|
35
|
+
unless @access_key
|
36
|
+
raise ArgumentError, "Pass an Access Key or set BingSearch.access_key"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
# @!group Sessions
|
42
|
+
|
43
|
+
# Opens a client and yields it to the given block. Takes the same arguments
|
44
|
+
# as {#initialize}.
|
45
|
+
# @see #initialize
|
46
|
+
# @yieldparam [Client] client
|
47
|
+
# @return [Client]
|
48
|
+
# @see #open
|
49
|
+
#
|
50
|
+
def self.open(*args)
|
51
|
+
raise "Block required" unless block_given?
|
52
|
+
client = new(*args)
|
53
|
+
client.open { yield client }
|
54
|
+
client
|
55
|
+
end
|
56
|
+
|
57
|
+
# Opens the client, creating a new TCP connection.
|
58
|
+
#
|
59
|
+
# If a block is given, yields to the block, closes the client when the
|
60
|
+
# block returns, and returns the return value of the block. If a
|
61
|
+
# block is not given, returns self and leaves the client open, relying on
|
62
|
+
# the caller to close the client with {#close}.
|
63
|
+
#
|
64
|
+
# Note that opening and closing the client is only required if you want to
|
65
|
+
# make several calls under one TCP connection. Otherwise, you can simply
|
66
|
+
# call the search methods ({#web}, {#image}, etc.), which call {#open} for
|
67
|
+
# you if necessary.
|
68
|
+
#
|
69
|
+
# @yield
|
70
|
+
# If a block is given, the client is closed when the block returns.
|
71
|
+
# @return [Object, self]
|
72
|
+
# If a block is given, the return value of the block; otherwise, +self+.
|
73
|
+
# @raise [StandardError]
|
74
|
+
# The client is already open
|
75
|
+
#
|
76
|
+
def open
|
77
|
+
raise "Already open" if open?
|
78
|
+
|
79
|
+
@session = Net::HTTP.new(HOST, Net::HTTP.https_default_port)
|
80
|
+
@session.use_ssl = true
|
81
|
+
|
82
|
+
begin
|
83
|
+
@session.start
|
84
|
+
block_given? ? yield : self
|
85
|
+
ensure
|
86
|
+
close if block_given?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Closes the client. Must be called after {#open} is called without a
|
91
|
+
# block.
|
92
|
+
# @return [self]
|
93
|
+
# @see #open
|
94
|
+
#
|
95
|
+
def close
|
96
|
+
@session.finish if open?
|
97
|
+
@session = nil
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
101
|
+
# Whether the client is open
|
102
|
+
# @return [Boolean]
|
103
|
+
# @see #open
|
104
|
+
#
|
105
|
+
def open?
|
106
|
+
@session && @session.started?
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
# @!group Searching
|
111
|
+
|
112
|
+
# @!macro general
|
113
|
+
# @param [String] query
|
114
|
+
# The query text; supports the
|
115
|
+
# {http://msdn.microsoft.com/en-us/library/ff795667.aspx Bing Query Language}
|
116
|
+
# @param [Hash<Symbol => Object>] opts
|
117
|
+
# @option opts [Integer] :limit
|
118
|
+
# The maximum number of results to return
|
119
|
+
# @option opts [Integer] :offset
|
120
|
+
# The zero-based ordinal of the first result to return
|
121
|
+
# @option opts [Adult, Symbol] :adult
|
122
|
+
# The level of filtering of sexually explicit content. If omitted, Bing
|
123
|
+
# uses the default level for the market.
|
124
|
+
# @option opts [Float] :latitude
|
125
|
+
# May range from -90 to 90
|
126
|
+
# @option opts [Float] :longitude
|
127
|
+
# May range from -180 to 180
|
128
|
+
# @option opts [String] :market
|
129
|
+
# A language tag specifying the market in which to search (e.g.
|
130
|
+
# +en-US+). If omitted, Bing infers the market from IP address, etc.
|
131
|
+
# @option opts [Boolean] :location_detection (true)
|
132
|
+
# Whether to infer location from the query text
|
133
|
+
# @raise [ServiceError]
|
134
|
+
# The Bing Search service returned an error
|
135
|
+
# @raise [StandardError]
|
136
|
+
# Invalid argument, unable to parse Bing response, networking error,
|
137
|
+
# and other error conditions
|
138
|
+
# @see http://msdn.microsoft.com/en-us/library/ff795667.aspx Bing Query Language reference
|
139
|
+
|
140
|
+
# Searches for web pages
|
141
|
+
# @!macro general
|
142
|
+
# @option opts [FileType] :file_type
|
143
|
+
# Type of file to return
|
144
|
+
# @option opts [Boolean] :highlighting (false)
|
145
|
+
# Whether to surround query terms in {WebResult#description} with the
|
146
|
+
# delimiter {BingSearch::HIGHLIGHT_DELIMITER}.
|
147
|
+
# @option opts [Boolean] :host_collapsing (true)
|
148
|
+
# Whether to suppress results from the same 'top-level URL'
|
149
|
+
# @option opts [Boolean] :query_alterations (true)
|
150
|
+
# Whether to alter the query in case of, e.g., supposed spelling errors
|
151
|
+
# @return [Array<WebResult>]
|
152
|
+
#
|
153
|
+
def web(query, opts = {})
|
154
|
+
invoke 'Web',
|
155
|
+
query,
|
156
|
+
opts,
|
157
|
+
passthrough_opts: %i(file_type),
|
158
|
+
enum_opt_to_module: {file_type: FileType},
|
159
|
+
param_name_replacements: {file_type: 'WebFileType'},
|
160
|
+
params: {web_search_options: web_search_options_from_opts(opts)}
|
161
|
+
end
|
162
|
+
|
163
|
+
# Searches for images
|
164
|
+
# @!macro general
|
165
|
+
# @option opts [Integer] :minimum_height
|
166
|
+
# In pixels; ANDed with other filters
|
167
|
+
# @option opts [Integer] :minimum_width
|
168
|
+
# In pixels; ANDed with other filters
|
169
|
+
# @option opts [Array<ImageFilter>] :filters
|
170
|
+
# Multiple filters are ANDed
|
171
|
+
# @return [Array<ImageResult>]
|
172
|
+
#
|
173
|
+
def image(query, opts = {})
|
174
|
+
invoke 'Image',
|
175
|
+
query,
|
176
|
+
opts,
|
177
|
+
param_name_replacements: {filters: 'ImageFilters'},
|
178
|
+
params: {filters: image_filters_from_opts(opts)}
|
179
|
+
end
|
180
|
+
|
181
|
+
# Searches for videos
|
182
|
+
# @!macro general
|
183
|
+
# @option opts [Array<VideoFilter>] :filters
|
184
|
+
# Multiple filters are ANDed. At most one duration is allowed.
|
185
|
+
# @option opts [VideoSort] :sort
|
186
|
+
# @return [Array<VideoResult>]
|
187
|
+
#
|
188
|
+
def video(query, opts = {})
|
189
|
+
invoke 'Video',
|
190
|
+
query,
|
191
|
+
opts,
|
192
|
+
passthrough_opts: %i(filters sort),
|
193
|
+
enum_opt_to_module: {filters: VideoFilter, sort: VideoSort},
|
194
|
+
param_name_replacements: {filters: 'VideoFilters', sort: 'VideoSortBy'}
|
195
|
+
end
|
196
|
+
|
197
|
+
# Searches for news
|
198
|
+
# @!macro general
|
199
|
+
# @option opts [Boolean] :highlighting (false)
|
200
|
+
# Whether to surround query terms in {NewsResult#description} with the
|
201
|
+
# delimiter {BingSearch::HIGHLIGHT_DELIMITER}.
|
202
|
+
# @option opts [NewsCategory] :category
|
203
|
+
# Only applies in the en-US market. If no news matches the category, Bing
|
204
|
+
# returns results from a mix of categories.
|
205
|
+
# @option opts [String] :location_override
|
206
|
+
# Overrides Bing's location detection. Example: +US.WA+
|
207
|
+
# @option opts [NewsSort] :sort
|
208
|
+
# @return [Array<NewsResult>]
|
209
|
+
#
|
210
|
+
def news(query, opts = {})
|
211
|
+
invoke 'News',
|
212
|
+
query,
|
213
|
+
opts,
|
214
|
+
passthrough_opts: %i(category location_override sort),
|
215
|
+
enum_opt_to_module: {category: NewsCategory, sort: NewsSort},
|
216
|
+
param_name_replacements: {category: 'NewsCategory', location_override: 'NewsLocationOverride', sort: 'NewsSortBy'}
|
217
|
+
end
|
218
|
+
|
219
|
+
# Searches for related queries
|
220
|
+
# @!macro general
|
221
|
+
# @return [Array<RelatedSearchResult>]
|
222
|
+
#
|
223
|
+
def related_search(query, opts = {})
|
224
|
+
invoke 'RelatedSearch', query, opts
|
225
|
+
end
|
226
|
+
alias_method :related, :related_search
|
227
|
+
|
228
|
+
# Corrects spelling in the query text
|
229
|
+
# @!macro general
|
230
|
+
# @return [Array<SpellingSuggestionsResult>]
|
231
|
+
#
|
232
|
+
def spelling_suggestions(query, opts = {})
|
233
|
+
invoke 'SpellingSuggestions', query, opts
|
234
|
+
end
|
235
|
+
alias_method :spelling, :spelling_suggestions
|
236
|
+
|
237
|
+
# Searches multiple sources. At most 15 news results are returned by
|
238
|
+
# a composite query regardless of the +:limit+ option.
|
239
|
+
# @macro general
|
240
|
+
# @param [Array<Source>] sources
|
241
|
+
# The sources to search
|
242
|
+
# @option opts [Boolean] :highlighting (false)
|
243
|
+
# Whether to surround query terms in {NewsResult#description} and
|
244
|
+
# {WebResult#description} with the delimiter {BingSearch::HIGHLIGHT_DELIMITER}.
|
245
|
+
# @option opts [FileType] :web_file_type
|
246
|
+
# Type of file to return. Applies to {Source::Web}; also affects
|
247
|
+
# {Source::Image} and {Source::Video} if {Source::Web} is specified.
|
248
|
+
# @option opts [Boolean] :web_host_collapsing (true)
|
249
|
+
# Whether to suppress results from the same 'top-level URL.' Applies to {Source::Web}.
|
250
|
+
# @option opts [Boolean] :web_query_alterations (true)
|
251
|
+
# Whether to alter the query in case of, e.g., supposed spelling errors. Applies to {Source::Web}.
|
252
|
+
# @option opts [Integer] :image_minimum_width
|
253
|
+
# In pixels; ANDed with other filters. Applies to {Source::Image}.
|
254
|
+
# @option opts [Integer] :image_minimum_height
|
255
|
+
# In pixels; ANDed with other image filters. Applies to {Source::Image}.
|
256
|
+
# @option opts [Array<ImageFilter>] :image_filters
|
257
|
+
# Multiple filters are ANDed. Applies to {Source::Image}.
|
258
|
+
# @option opts [Array<VideoFilter>] :video_filters
|
259
|
+
# Multiple filters are ANDed. At most one duration is allowed. Applies to {Source::Video}.
|
260
|
+
# @option opts [VideoSort] :video_sort
|
261
|
+
# Applies to {Source::Video}
|
262
|
+
# @option opts [NewsCategory] :news_category
|
263
|
+
# Only applies in the en_US market. If no news matches the category, Bing
|
264
|
+
# returns results from a mix of categories. Applies to {Source::News}.
|
265
|
+
# @option opts [String] :news_location_override
|
266
|
+
# Overrides Bing's location detection. Example: +US.WA+. Applies to {Source::News}.
|
267
|
+
# @option opts [NewsSort] :news_sort
|
268
|
+
# Applies to {Source::News}.
|
269
|
+
#
|
270
|
+
# @return [CompositeSearchResult]
|
271
|
+
#
|
272
|
+
def composite(query, sources, opts = {})
|
273
|
+
results = invoke('Composite',
|
274
|
+
query,
|
275
|
+
opts,
|
276
|
+
passthrough_opts: %i(
|
277
|
+
web_file_type
|
278
|
+
video_filters
|
279
|
+
video_sort
|
280
|
+
news_category
|
281
|
+
news_location_override
|
282
|
+
news_sort
|
283
|
+
),
|
284
|
+
enum_opt_to_module: {
|
285
|
+
web_file_type: FileType,
|
286
|
+
video_filters: VideoFilter,
|
287
|
+
video_sort: VideoSort,
|
288
|
+
news_category: NewsCategory,
|
289
|
+
news_sort: NewsSort
|
290
|
+
},
|
291
|
+
param_name_replacements: {
|
292
|
+
video_sort: 'VideoSortBy',
|
293
|
+
news_sort: 'NewsSortBy'
|
294
|
+
},
|
295
|
+
params: {
|
296
|
+
sources: sources.collect { |source| enum_value(source, Source) },
|
297
|
+
web_search_options: web_search_options_from_opts(opts, :web_),
|
298
|
+
image_filters: image_filters_from_opts(opts, :image_)
|
299
|
+
}
|
300
|
+
)
|
301
|
+
|
302
|
+
results.first
|
303
|
+
end
|
304
|
+
|
305
|
+
private
|
306
|
+
|
307
|
+
# @param [Hash] opts
|
308
|
+
# @param [#to_s] opt_prefix
|
309
|
+
# return [Array<String>]
|
310
|
+
#
|
311
|
+
def web_search_options_from_opts(opts = {}, opt_prefix = nil)
|
312
|
+
web_search_options = []
|
313
|
+
web_search_options << 'DisableHostCollapsing' if opts["#{opt_prefix}host_collapsing".to_sym] == false
|
314
|
+
web_search_options << 'DisableQueryAlterations' if opts["#{opt_prefix}query_alterations".to_sym] == false
|
315
|
+
web_search_options
|
316
|
+
end
|
317
|
+
|
318
|
+
# @param [Hash] opts
|
319
|
+
# @param [#to_s] opt_prefix
|
320
|
+
# @return [Array<String>]
|
321
|
+
#
|
322
|
+
def image_filters_from_opts(opts = {}, opt_prefix = nil)
|
323
|
+
filters = (opts["#{opt_prefix}filters".to_sym] || []).map { |filter| enum_value(filter, ImageFilter) }
|
324
|
+
|
325
|
+
height = opts["#{opt_prefix}minimum_height".to_sym]
|
326
|
+
width = opts["#{opt_prefix}minimum_width".to_sym]
|
327
|
+
filters << "Size:Width:#{width}" if width
|
328
|
+
filters << "Size:Height:#{height}" if height
|
329
|
+
|
330
|
+
filters
|
331
|
+
end
|
332
|
+
|
333
|
+
# @param [String] operation
|
334
|
+
# The first segment of the invocation URI path (after the base path),
|
335
|
+
# e.g. 'Web'
|
336
|
+
# @param [String] query
|
337
|
+
# The query text
|
338
|
+
# @param [Hash<Symbol => Object>] opts
|
339
|
+
# The options hash provided by the caller
|
340
|
+
# @param [Array<Symbol>] passthrough_opts
|
341
|
+
# Keys of the options to copy to the params hash
|
342
|
+
# @param [Hash<Symbol => Module>] enum_opt_to_module
|
343
|
+
# Maps an enum option key to the module containing the enum's values.
|
344
|
+
# Used to translate symbols to enum values. E.g. maps +:web_file_type+
|
345
|
+
# to {FileType}.
|
346
|
+
# @param [Hash<Symbol => Object>] params
|
347
|
+
# Parameters for the invocation
|
348
|
+
# @return [Object]
|
349
|
+
# @raise [ServiceError]
|
350
|
+
# @raise [RuntimeError]
|
351
|
+
#
|
352
|
+
def invoke(operation, query, opts, passthrough_opts: [], enum_opt_to_module: {}, param_name_replacements: {}, params: {})
|
353
|
+
options = []
|
354
|
+
options << 'DisableLocationDetection' if opts[:location_detection] == false
|
355
|
+
options << 'EnableHighlighting' if opts[:highlighting]
|
356
|
+
|
357
|
+
# Works around an apparent bug where Bing treats offsets 0 and 1 the same
|
358
|
+
offset = opts[:offset] && opts[:offset] + 1
|
359
|
+
|
360
|
+
opts = opts.each_with_object(Hash.new) do |(key, value), hash|
|
361
|
+
module_ = GENERAL_ENUM_OPT_TO_MODULE[key] || enum_opt_to_module[key]
|
362
|
+
hash[key] = module_ ? enum_value(value, module_) : value
|
363
|
+
end
|
364
|
+
|
365
|
+
params = params.
|
366
|
+
merge(opts.slice(*GENERAL_PASSTHROUGH_OPTS, *passthrough_opts)).
|
367
|
+
merge(query: query, offset: offset, options: options, format: :JSON).
|
368
|
+
delete_if { |_, v| v.nil? || (v.is_a?(Array) && v.empty?) }
|
369
|
+
|
370
|
+
params = format_params(replace_param_names(params, param_name_replacements))
|
371
|
+
query = URI.encode_www_form(params)
|
372
|
+
base_path = web_only ? WEB_ONLY_BASE_PATH : BASE_PATH
|
373
|
+
|
374
|
+
response = in_session do |session|
|
375
|
+
request = Net::HTTP::Get.new("#{base_path}/#{operation}?#{query}")
|
376
|
+
request.basic_auth(access_key, access_key)
|
377
|
+
session.request request
|
378
|
+
end
|
379
|
+
|
380
|
+
unless response.is_a?(Net::HTTPOK)
|
381
|
+
raise ServiceError.new(response.code, response.body)
|
382
|
+
end
|
383
|
+
|
384
|
+
raw = JSON.parse(response.body)
|
385
|
+
|
386
|
+
unless raw['d'] && raw['d']['results']
|
387
|
+
raise "Unexpected response format"
|
388
|
+
end
|
389
|
+
|
390
|
+
parse raw['d']['results']
|
391
|
+
end
|
392
|
+
|
393
|
+
# @yield [Net::HTTP]
|
394
|
+
# @return [Net::HTTPResponse]
|
395
|
+
#
|
396
|
+
def in_session
|
397
|
+
if open?
|
398
|
+
yield @session
|
399
|
+
else
|
400
|
+
open { yield @session }
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
# @param [Object] value
|
405
|
+
# @param [Module] module_
|
406
|
+
# @return [Object]
|
407
|
+
#
|
408
|
+
def enum_value(value, module_)
|
409
|
+
case value
|
410
|
+
when Symbol
|
411
|
+
enum_from_symbol(value, module_)
|
412
|
+
when Array
|
413
|
+
value.collect { |element| enum_value(element, module_) }
|
414
|
+
else
|
415
|
+
value
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
# @param [Symbol] symbol
|
420
|
+
# @param [Module] module_
|
421
|
+
# @return [Object]
|
422
|
+
# @raise [ArgumentError]
|
423
|
+
# The module does not contain a constant corresponing to the symbol
|
424
|
+
#
|
425
|
+
def enum_from_symbol(symbol, module_)
|
426
|
+
[symbol.to_s.camelcase, symbol.to_s.upcase].each do |const|
|
427
|
+
return module_.const_get(const) if module_.const_defined?(const)
|
428
|
+
end
|
429
|
+
raise ArgumentError, "#{module_} does not contain a constant corresponding to #{symbol}"
|
430
|
+
end
|
431
|
+
|
432
|
+
# @param [Hash<Symbol => Object>] params
|
433
|
+
# @return [Hash<String => Object]
|
434
|
+
#
|
435
|
+
def replace_param_names(params, replacements)
|
436
|
+
params.each_with_object(Hash.new) do |(key, value), hash|
|
437
|
+
key = replacements[key] || GENERAL_PARAM_NAME_REPLACEMENTS[key] || key.to_s.camelcase
|
438
|
+
hash[key] = value
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
# @param [Hash] params
|
443
|
+
# @return [Hash]
|
444
|
+
#
|
445
|
+
def format_params(params)
|
446
|
+
params.each_with_object(Hash.new) do |(key, value), hash|
|
447
|
+
hash[key] = format(value)
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
# @param [Object] value
|
452
|
+
# @return [String]
|
453
|
+
#
|
454
|
+
def format(value)
|
455
|
+
case value
|
456
|
+
when String then format_string(value)
|
457
|
+
when Array then format_array(value)
|
458
|
+
else value
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
# @param [String] string
|
463
|
+
# @return [String]
|
464
|
+
#
|
465
|
+
def format_string(string)
|
466
|
+
"'#{string}'"
|
467
|
+
end
|
468
|
+
|
469
|
+
# @param [Array] array
|
470
|
+
# @return [String]
|
471
|
+
#
|
472
|
+
def format_array(array)
|
473
|
+
"'#{array.join '+'}'"
|
474
|
+
end
|
475
|
+
|
476
|
+
# @param [Object] raw
|
477
|
+
# @param [Symbol, nil] type
|
478
|
+
# @return [Object]
|
479
|
+
#
|
480
|
+
def parse(raw, type = nil)
|
481
|
+
if type
|
482
|
+
parse_typed raw, type
|
483
|
+
elsif raw.is_a?(Array)
|
484
|
+
raw.collect { |element| parse element }
|
485
|
+
elsif raw_model?(raw)
|
486
|
+
parse_model raw
|
487
|
+
else
|
488
|
+
raw
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
# @param [Object] raw
|
493
|
+
# @param [Symbol] type
|
494
|
+
# @return [Object, nil]
|
495
|
+
# @raise [ArgumentError]
|
496
|
+
#
|
497
|
+
def parse_typed(raw, type)
|
498
|
+
case type
|
499
|
+
when :datetime
|
500
|
+
raw.empty? ? nil : DateTime.parse(raw)
|
501
|
+
when :integer
|
502
|
+
raw.empty? ? nil : Integer(raw)
|
503
|
+
else
|
504
|
+
raise ArgumentError, "Can't parse value #{raw} of type #{type}"
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
# @param [Object] raw
|
509
|
+
# @return [Model]
|
510
|
+
#
|
511
|
+
def parse_model(raw)
|
512
|
+
raw_type = raw['__metadata']['type']
|
513
|
+
model = model_class(raw_type).new
|
514
|
+
attr_to_type = RAW_MODEL_TYPE_TO_ATTR_TO_TYPE[raw_type] || {}
|
515
|
+
|
516
|
+
for key, value in raw
|
517
|
+
next if key == '__metadata'
|
518
|
+
attr = key.underscore.to_sym
|
519
|
+
model.set attr, parse(value, attr_to_type[attr])
|
520
|
+
end
|
521
|
+
|
522
|
+
model
|
523
|
+
end
|
524
|
+
|
525
|
+
# @param [Object] raw
|
526
|
+
# @return [Boolean]
|
527
|
+
#
|
528
|
+
def raw_model?(raw)
|
529
|
+
raw.is_a?(Hash) && raw['__metadata'] && raw['__metadata']['type']
|
530
|
+
end
|
531
|
+
|
532
|
+
# @param [String] raw_type
|
533
|
+
# @return [Class]
|
534
|
+
# @raise [ArgumentError]
|
535
|
+
#
|
536
|
+
def model_class(raw_type)
|
537
|
+
unless RAW_MODEL_TYPES.include?(raw_type)
|
538
|
+
raise ArgumentError, "Invalid model type: #{raw_type}"
|
539
|
+
end
|
540
|
+
|
541
|
+
case raw_type
|
542
|
+
when 'Bing.Thumbnail'
|
543
|
+
Image
|
544
|
+
when 'SpellResult'
|
545
|
+
SpellingSuggestionsResult
|
546
|
+
when 'ExpandableSearchResult'
|
547
|
+
CompositeSearchResult
|
548
|
+
else
|
549
|
+
BingSearch.const_get(raw_type)
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
|
554
|
+
HOST = 'api.datamarket.azure.com'
|
555
|
+
private_constant :HOST
|
556
|
+
|
557
|
+
BASE_PATH = '/Bing/Search'
|
558
|
+
private_constant :BASE_PATH
|
559
|
+
|
560
|
+
WEB_ONLY_BASE_PATH = '/Bing/SearchWeb'
|
561
|
+
private_constant :WEB_ONLY_BASE_PATH
|
562
|
+
|
563
|
+
GENERAL_PASSTHROUGH_OPTS = %i(limit adult latitude longitude market)
|
564
|
+
private_constant :GENERAL_PASSTHROUGH_OPTS
|
565
|
+
|
566
|
+
GENERAL_ENUM_OPT_TO_MODULE = {adult: Adult}
|
567
|
+
private_constant :GENERAL_ENUM_OPT_TO_MODULE
|
568
|
+
|
569
|
+
GENERAL_PARAM_NAME_REPLACEMENTS = {
|
570
|
+
limit: '$top',
|
571
|
+
offset: '$skip',
|
572
|
+
format: '$format',
|
573
|
+
}
|
574
|
+
private_constant :GENERAL_PARAM_NAME_REPLACEMENTS
|
575
|
+
|
576
|
+
RAW_MODEL_TYPES = %w{
|
577
|
+
WebResult
|
578
|
+
ImageResult
|
579
|
+
VideoResult
|
580
|
+
NewsResult
|
581
|
+
RelatedSearchResult
|
582
|
+
SpellResult
|
583
|
+
ExpandableSearchResult
|
584
|
+
Bing.Thumbnail
|
585
|
+
}
|
586
|
+
private_constant :RAW_MODEL_TYPES
|
587
|
+
|
588
|
+
RAW_MODEL_TYPE_TO_ATTR_TO_TYPE = {
|
589
|
+
'ImageResult' => {
|
590
|
+
width: :integer,
|
591
|
+
height: :integer
|
592
|
+
},
|
593
|
+
'NewsResult' => {
|
594
|
+
date: :datetime
|
595
|
+
},
|
596
|
+
'VideoResult' => {
|
597
|
+
run_time: :integer
|
598
|
+
},
|
599
|
+
'ExpandableSearchResult' => {
|
600
|
+
web_total: :integer,
|
601
|
+
web_offset: :integer,
|
602
|
+
image_total: :integer,
|
603
|
+
image_offset: :integer,
|
604
|
+
video_total: :integer,
|
605
|
+
video_offset: :integer,
|
606
|
+
news_total: :integer,
|
607
|
+
news_offset: :integer,
|
608
|
+
spelling_suggestions_total: :integer
|
609
|
+
}
|
610
|
+
}
|
611
|
+
private_constant :RAW_MODEL_TYPE_TO_ATTR_TO_TYPE
|
612
|
+
|
613
|
+
end
|
614
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module BingSearch
|
2
|
+
module Adult
|
3
|
+
Off = 'Off'
|
4
|
+
Moderate = 'Moderate'
|
5
|
+
Strict = 'Strict'
|
6
|
+
end
|
7
|
+
|
8
|
+
module FileType
|
9
|
+
DOC = 'DOC'
|
10
|
+
DWF = 'DWF'
|
11
|
+
FEED = 'FEED'
|
12
|
+
HTM = 'HTM'
|
13
|
+
HTML = 'HTML'
|
14
|
+
PDF = 'PDF'
|
15
|
+
PPT = 'PPT'
|
16
|
+
RTF = 'RTF'
|
17
|
+
TEXT = 'TEXT'
|
18
|
+
TXT = 'TXT'
|
19
|
+
XLS = 'XLS'
|
20
|
+
end
|
21
|
+
|
22
|
+
module ImageFilter
|
23
|
+
Small = 'Size:Small'
|
24
|
+
Medium = 'Size:Medium'
|
25
|
+
Large = 'Size:Large'
|
26
|
+
Square = 'Aspect:Square'
|
27
|
+
Wide = 'Aspect:Wide'
|
28
|
+
Tall = 'Aspect:Tall'
|
29
|
+
Color = 'Color:Color'
|
30
|
+
Monochrome = 'Color:Monochrome'
|
31
|
+
Photo = 'Style:Photo'
|
32
|
+
Graphics = 'Style:Graphics'
|
33
|
+
Face = 'Face:Face'
|
34
|
+
Portrait = 'Face:Portrait'
|
35
|
+
OtherFace = 'Face:Other'
|
36
|
+
end
|
37
|
+
|
38
|
+
module NewsCategory
|
39
|
+
Business = 'rt_Business'
|
40
|
+
Entertainment = 'rt_Entertainment'
|
41
|
+
Health = 'rt_Health'
|
42
|
+
Politics = 'rt_Politics'
|
43
|
+
Sports = 'rt_Sports'
|
44
|
+
US = 'rt_US'
|
45
|
+
World = 'rt_World'
|
46
|
+
ScienceAndTechnology = 'rt_ScienceAndTechnology'
|
47
|
+
end
|
48
|
+
|
49
|
+
module NewsSort
|
50
|
+
Date = 'Date'
|
51
|
+
Relevance = 'Relevance'
|
52
|
+
end
|
53
|
+
|
54
|
+
module Source
|
55
|
+
Web = 'Web'
|
56
|
+
Image = 'Image'
|
57
|
+
Video = 'Video'
|
58
|
+
News = 'News'
|
59
|
+
SpellingSuggestions = 'Spell'
|
60
|
+
RelatedSearch = 'RelatedSearch'
|
61
|
+
end
|
62
|
+
|
63
|
+
module VideoFilter
|
64
|
+
Short = 'Duration:Short'
|
65
|
+
Medium = 'Duration:Medium'
|
66
|
+
Long = 'Duration:Long'
|
67
|
+
StandardAspect = 'Aspect:Standard'
|
68
|
+
Widescreen = 'Aspect:Widescreen'
|
69
|
+
LowResolution = 'Resolution:Low'
|
70
|
+
MediumResolution = 'Resolution:Medium'
|
71
|
+
HighResolution = 'Resolution:High'
|
72
|
+
end
|
73
|
+
|
74
|
+
module VideoSort
|
75
|
+
Date = 'Date'
|
76
|
+
Relevance = 'Relevance'
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module BingSearch
|
2
|
+
|
3
|
+
class ServiceError < StandardError
|
4
|
+
# The error code returned by Bing
|
5
|
+
# @return [String]
|
6
|
+
attr_reader :code
|
7
|
+
|
8
|
+
# @param [String] code
|
9
|
+
# The error code returned by Bing
|
10
|
+
# @param [String] message
|
11
|
+
#
|
12
|
+
def initialize(code, message = nil)
|
13
|
+
super(message)
|
14
|
+
@code = code
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [String]
|
18
|
+
#
|
19
|
+
def to_s
|
20
|
+
"Bing error #{code}: #{super}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,255 @@
|
|
1
|
+
module BingSearch
|
2
|
+
|
3
|
+
class Model
|
4
|
+
# @param [Hash] attrs
|
5
|
+
def initialize(attrs = {})
|
6
|
+
attrs.each do |k, v|
|
7
|
+
public_send "#{k}=", v
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Sets an attribute via a public instance method on the receiver or its
|
12
|
+
# ancestors up to but not including Object
|
13
|
+
# @param [Symbol] attr
|
14
|
+
# @param value
|
15
|
+
# @return [self]
|
16
|
+
# @raise [ArgumentError]
|
17
|
+
# No public setter for +attr+ on the receiver or its ancestors up to
|
18
|
+
# Object
|
19
|
+
#
|
20
|
+
def set(attr, value)
|
21
|
+
setter = "#{attr}=".to_sym
|
22
|
+
|
23
|
+
self.class.attr_methods.include?(setter) ?
|
24
|
+
public_send(setter, value) :
|
25
|
+
raise(ArgumentError, "Can't set attr #{attr} of #{self}")
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def self.attr_methods
|
31
|
+
@attr_methods ||= model_ancestors.reduce([]) do |memo, class_|
|
32
|
+
memo + class_.public_instance_methods(false)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.model_ancestors
|
37
|
+
ancestors.select { |ancestor| ancestor < Model }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Result < Model
|
42
|
+
# @return [String]
|
43
|
+
# A universally unique identifier (UUID)
|
44
|
+
attr_accessor :id
|
45
|
+
end
|
46
|
+
|
47
|
+
class WebResult < Result
|
48
|
+
# @return [String]
|
49
|
+
attr_accessor :title
|
50
|
+
|
51
|
+
# The summary displayed below the title in bing.com search results
|
52
|
+
# @return [String]
|
53
|
+
attr_accessor :description
|
54
|
+
alias_method :summary, :description
|
55
|
+
|
56
|
+
# URL to display to the user. Omits the scheme if HTTP.
|
57
|
+
# @return [String]
|
58
|
+
attr_accessor :display_url
|
59
|
+
|
60
|
+
# Full URL of the result, including the scheme.
|
61
|
+
# @return [String]
|
62
|
+
attr_accessor :url
|
63
|
+
end
|
64
|
+
|
65
|
+
class ImageResult < Result
|
66
|
+
# @return [String]
|
67
|
+
attr_accessor :title
|
68
|
+
|
69
|
+
# URL of the image
|
70
|
+
# @return [String]
|
71
|
+
attr_accessor :media_url
|
72
|
+
alias_method :url, :media_url
|
73
|
+
|
74
|
+
# URL of the website that contains the image
|
75
|
+
# @return [String]
|
76
|
+
attr_accessor :source_url
|
77
|
+
|
78
|
+
# URL to display to the user. Omits the scheme if HTTP.
|
79
|
+
# @return [String]
|
80
|
+
attr_accessor :display_url
|
81
|
+
|
82
|
+
# In pixels, if available
|
83
|
+
# @return [Integer, nil]
|
84
|
+
attr_accessor :width
|
85
|
+
|
86
|
+
# In pixels, if available
|
87
|
+
# @return [Integer, nil]
|
88
|
+
attr_accessor :height
|
89
|
+
|
90
|
+
# In bytes, if available
|
91
|
+
# @return [Integer, nil]
|
92
|
+
attr_accessor :file_size
|
93
|
+
|
94
|
+
# {http://en.wikipedia.org/wiki/Internet_media_type Internet media type} (MIME type) of the image, if available
|
95
|
+
# @return [String]
|
96
|
+
attr_accessor :content_type
|
97
|
+
alias_method :media_type, :content_type
|
98
|
+
|
99
|
+
# @return [Image]
|
100
|
+
attr_accessor :thumbnail
|
101
|
+
end
|
102
|
+
|
103
|
+
class VideoResult < Result
|
104
|
+
# @return [String]
|
105
|
+
attr_accessor :title
|
106
|
+
|
107
|
+
# URL of the video, often a web page containing the video
|
108
|
+
# @return [String]
|
109
|
+
attr_accessor :media_url
|
110
|
+
alias_method :url, :media_url
|
111
|
+
|
112
|
+
# URL of a Bing page that displays the video
|
113
|
+
# @return [String]
|
114
|
+
attr_accessor :display_url
|
115
|
+
|
116
|
+
# Duration of the video in milliseconds, if available
|
117
|
+
# @return [Integer, nil]
|
118
|
+
attr_accessor :run_time
|
119
|
+
alias_method :duration, :run_time
|
120
|
+
|
121
|
+
# @return [Image]
|
122
|
+
attr_accessor :thumbnail
|
123
|
+
end
|
124
|
+
|
125
|
+
class NewsResult < Result
|
126
|
+
# @return [String]
|
127
|
+
attr_accessor :title
|
128
|
+
alias_method :headline, :title
|
129
|
+
|
130
|
+
# URL of the article
|
131
|
+
# @return [String]
|
132
|
+
attr_accessor :url
|
133
|
+
|
134
|
+
# Organization responsible for the article
|
135
|
+
# @return [String]
|
136
|
+
attr_accessor :source
|
137
|
+
|
138
|
+
# Sample of the article
|
139
|
+
# @return [String]
|
140
|
+
attr_accessor :description
|
141
|
+
|
142
|
+
# Date on which the article was indexed
|
143
|
+
# @return [Date]
|
144
|
+
attr_accessor :date
|
145
|
+
end
|
146
|
+
|
147
|
+
class RelatedSearchResult < Result
|
148
|
+
# The query text of the related search
|
149
|
+
# @return [String]
|
150
|
+
attr_accessor :title
|
151
|
+
alias_method :query, :title
|
152
|
+
|
153
|
+
# The URL of the Bing results page for the related search
|
154
|
+
# @return [String]
|
155
|
+
attr_accessor :bing_url
|
156
|
+
end
|
157
|
+
|
158
|
+
class SpellingSuggestionsResult < Result
|
159
|
+
# The suggested spelling
|
160
|
+
# @return [String]
|
161
|
+
attr_accessor :value
|
162
|
+
alias_method :suggestion, :value
|
163
|
+
end
|
164
|
+
|
165
|
+
class CompositeSearchResult < Result
|
166
|
+
# @!group Instance Attributes for Results
|
167
|
+
|
168
|
+
# @return [Array<WebResult>]
|
169
|
+
attr_accessor :web
|
170
|
+
|
171
|
+
# @return [Array<ImageResult>]
|
172
|
+
attr_accessor :image
|
173
|
+
|
174
|
+
# @return [Array<VideoResult>]
|
175
|
+
attr_accessor :video
|
176
|
+
|
177
|
+
# @return [Array<NewsResult>]
|
178
|
+
attr_accessor :news
|
179
|
+
|
180
|
+
# @return [Array<RelatedSearchResult>]
|
181
|
+
attr_accessor :related_search
|
182
|
+
|
183
|
+
# @return [Array<SpellingSuggestionsResult>]
|
184
|
+
attr_accessor :spelling_suggestions
|
185
|
+
|
186
|
+
# @!endgroup
|
187
|
+
|
188
|
+
# The number of web results in the Bing index
|
189
|
+
# @return [Integer, nil]
|
190
|
+
attr_accessor :web_total
|
191
|
+
|
192
|
+
# The ordinal of the first web result
|
193
|
+
# @return [Integer, nil]
|
194
|
+
attr_accessor :web_offset
|
195
|
+
|
196
|
+
# The number of image results in the Bing index
|
197
|
+
# @return [Integer, nil]
|
198
|
+
attr_accessor :image_total
|
199
|
+
|
200
|
+
# The ordinal of the first image result
|
201
|
+
# @return [Integer, nil]
|
202
|
+
attr_accessor :image_offset
|
203
|
+
|
204
|
+
# The number of video results in the Bing index
|
205
|
+
# @return [Integer, nil]
|
206
|
+
attr_accessor :video_total
|
207
|
+
|
208
|
+
# The ordinal of the first video result
|
209
|
+
# @return [Integer, nil]
|
210
|
+
attr_accessor :video_offset
|
211
|
+
|
212
|
+
# The number of news results in the Bing index
|
213
|
+
# @return [Integer, nil]
|
214
|
+
attr_accessor :news_total
|
215
|
+
|
216
|
+
# The ordinal of the first news result
|
217
|
+
# @return [Integer, nil]
|
218
|
+
attr_accessor :news_offset
|
219
|
+
|
220
|
+
# The number of spelling suggestions in the Bing index
|
221
|
+
# @return [Integer, nil]
|
222
|
+
attr_accessor :spelling_suggestions_total
|
223
|
+
|
224
|
+
# The query text after spelling errors have been corrected
|
225
|
+
# @return [String]
|
226
|
+
attr_accessor :altered_query
|
227
|
+
|
228
|
+
# Query text that forces the original query, preventing any alterations in {#altered_query}
|
229
|
+
# @return [String]
|
230
|
+
attr_accessor :alteration_override_query
|
231
|
+
end
|
232
|
+
|
233
|
+
class Image < Model
|
234
|
+
# URL of the image
|
235
|
+
# @return [String]
|
236
|
+
attr_accessor :media_url
|
237
|
+
alias_method :url, :media_url
|
238
|
+
|
239
|
+
# {http://en.wikipedia.org/wiki/Internet_media_type Internet media type} (MIME type) of the image, if available
|
240
|
+
# @return [String]
|
241
|
+
attr_accessor :content_type
|
242
|
+
|
243
|
+
# In pixels, if available
|
244
|
+
# @return [Integer, nil]
|
245
|
+
attr_accessor :width
|
246
|
+
|
247
|
+
# In pixels, if available
|
248
|
+
# @return [Integer, nil]
|
249
|
+
attr_accessor :height
|
250
|
+
|
251
|
+
# In bytes, if available
|
252
|
+
# @return [Integer, nil]
|
253
|
+
attr_accessor :file_size
|
254
|
+
end
|
255
|
+
end
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bing-search
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jonah Burke
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.2.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.2.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.7'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: yard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.8.7
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.8.7
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- jonah@jonahb.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- LICENSE.txt
|
77
|
+
- README.md
|
78
|
+
- lib/bing-search.rb
|
79
|
+
- lib/bing-search/client.rb
|
80
|
+
- lib/bing-search/enums.rb
|
81
|
+
- lib/bing-search/errors.rb
|
82
|
+
- lib/bing-search/models.rb
|
83
|
+
- lib/bing-search/version.rb
|
84
|
+
homepage: http://github.com/jonahb/bing-search
|
85
|
+
licenses:
|
86
|
+
- MIT
|
87
|
+
metadata: {}
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.0'
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 2.2.2
|
105
|
+
signing_key:
|
106
|
+
specification_version: 4
|
107
|
+
summary: A Ruby client for the Bing Search API
|
108
|
+
test_files: []
|
109
|
+
has_rdoc:
|