anideo-embedly 1.3.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.
- data/.rvmrc +1 -0
- data/ChangeLog +56 -0
- data/Gemfile +15 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +105 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/anideo-embedly.gemspec +91 -0
- data/bin/embedly_objectify +12 -0
- data/bin/embedly_oembed +12 -0
- data/bin/embedly_preview +12 -0
- data/cucumber.yml +1 -0
- data/features/command_line.feature +17 -0
- data/features/objectify.feature +15 -0
- data/features/oembed.feature +80 -0
- data/features/steps/api_steps.rb +56 -0
- data/features/steps/env.rb +10 -0
- data/lib/embedly.rb +15 -0
- data/lib/embedly/api.rb +234 -0
- data/lib/embedly/command_line.rb +132 -0
- data/lib/embedly/configuration.rb +59 -0
- data/lib/embedly/exceptions.rb +20 -0
- data/lib/embedly/model.rb +32 -0
- data/spec/embedly/api_spec.rb +23 -0
- data/spec/embedly/command_line_spec.rb +118 -0
- data/spec/embedly/configuration_spec.rb +57 -0
- data/spec/spec_helper.rb +7 -0
- metadata +179 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
$:.unshift(File.expand_path('../../../lib',__FILE__))
|
2
|
+
require 'embedly'
|
3
|
+
|
4
|
+
# cache for hostnames
|
5
|
+
HOSTNAMES = {}
|
6
|
+
|
7
|
+
Given /an embedly api( with key)?$/ do |key_enabled|
|
8
|
+
opts = {}
|
9
|
+
if key_enabled
|
10
|
+
raise 'Please set env variable $EMBEDLY_KEY' unless ENV['EMBEDLY_KEY']
|
11
|
+
opts[:key] = ENV["EMBEDLY_KEY"]
|
12
|
+
opts[:secret] = ENV["EMBEDLY_SECRET"]
|
13
|
+
end
|
14
|
+
if not HOSTNAMES[opts]
|
15
|
+
HOSTNAMES[opts] = Embedly::API.new opts
|
16
|
+
end
|
17
|
+
@api = HOSTNAMES[opts]
|
18
|
+
end
|
19
|
+
|
20
|
+
When /(\w+) is called with the (.*) URLs?( and ([^\s]+) flag)?$/ do |method, urls, _, flag|
|
21
|
+
@result = nil
|
22
|
+
begin
|
23
|
+
urls = urls.split(',')
|
24
|
+
opts = {}
|
25
|
+
if urls.size == 1
|
26
|
+
opts[:url] = urls.first
|
27
|
+
else
|
28
|
+
opts[:urls] = urls
|
29
|
+
end
|
30
|
+
opts[flag.to_sym] = true if flag
|
31
|
+
@result = @api.send(method, opts)
|
32
|
+
rescue
|
33
|
+
@error = $!
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Then /an? (\w+) error should get thrown/ do |error|
|
38
|
+
@error.class.to_s.should == error
|
39
|
+
end
|
40
|
+
|
41
|
+
Then /objectify api_version is (\d+)$/ do |version|
|
42
|
+
@api.api_version[:objectify].should == version
|
43
|
+
end
|
44
|
+
|
45
|
+
Then /([^\s]+) should be (.+)$/ do |key, value|
|
46
|
+
raise @error if @error
|
47
|
+
@result.collect do |o|
|
48
|
+
o.send(key).to_s
|
49
|
+
end.join(',').should == value
|
50
|
+
end
|
51
|
+
|
52
|
+
Then /([^\s]+) should start with ([^\s]+)/ do |key, value|
|
53
|
+
raise @error if @error
|
54
|
+
v = key.split('.').inject(@result[0]){|o,c| o.send(c)}.to_s
|
55
|
+
v.to_s.should match(/^#{value}/)
|
56
|
+
end
|
data/lib/embedly.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
module Embedly
|
2
|
+
VERSION = File.read(File.expand_path('../../VERSION', __FILE__)).strip
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def configure
|
6
|
+
yield configuration
|
7
|
+
end
|
8
|
+
|
9
|
+
def configuration
|
10
|
+
@configuration ||= Configuration.new
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'embedly/api'
|
data/lib/embedly/api.rb
ADDED
@@ -0,0 +1,234 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'json'
|
4
|
+
require 'ostruct'
|
5
|
+
require 'embedly/configuration'
|
6
|
+
require 'embedly/model'
|
7
|
+
require 'embedly/exceptions'
|
8
|
+
require 'querystring'
|
9
|
+
require 'oauth'
|
10
|
+
require 'typhoeus'
|
11
|
+
|
12
|
+
# Performs api calls to embedly.
|
13
|
+
#
|
14
|
+
# You won't find methods. We are using method_missing and passing the method
|
15
|
+
# name to apicall.
|
16
|
+
#
|
17
|
+
# === Currently Supported Methods
|
18
|
+
#
|
19
|
+
# * +oembed+
|
20
|
+
# * +objectify+
|
21
|
+
# * +preview+
|
22
|
+
#
|
23
|
+
# All methods return ostructs, so fields can be accessed with the dot operator. ex.
|
24
|
+
#
|
25
|
+
# api = Embedly::API.new
|
26
|
+
# obj = api.oembed :url => 'http://blog.doki-pen.org/'
|
27
|
+
# puts obj[0].title, obj[0].description, obj[0].thumbnail_url
|
28
|
+
#
|
29
|
+
# Call parameters should be passed as the opts parameter. If set, key will
|
30
|
+
# automatically be added to the query string of the call, so no need to set it.
|
31
|
+
#
|
32
|
+
# This API _would_ be future compatible, if not for the version. In order to
|
33
|
+
# add support for a new method, you will need to add a version to the
|
34
|
+
# api_version hash. Here is an example.
|
35
|
+
#
|
36
|
+
# api = Embedly::API.new
|
37
|
+
# api.api_version[:new_method] = 3
|
38
|
+
# api.new_method :arg1 => '1', :arg2 => '2'
|
39
|
+
#
|
40
|
+
class Embedly::API
|
41
|
+
attr_reader :key, :hostname, :api_version, :headers, :secret
|
42
|
+
|
43
|
+
# === Options
|
44
|
+
#
|
45
|
+
# [:+hostname+] Hostname of embedly server. Defaults to api.embed.ly.
|
46
|
+
# [:+key+] Your api.embed.ly key.
|
47
|
+
# [:+secret+] Your api.embed.ly secret if you are using oauth.
|
48
|
+
# [:+user_agent+] Your User-Agent header. Defaults to Mozilla/5.0 (compatible; embedly-ruby/VERSION;)
|
49
|
+
# [:+timeout+] Request timeout (in seconds). Defaults to 180 seconds or 3 minutes
|
50
|
+
# [:+headers+] Additional headers to send with requests.
|
51
|
+
def initialize opts={}
|
52
|
+
@endpoints = [:oembed, :objectify, :preview]
|
53
|
+
@key = opts[:key]
|
54
|
+
@secret = opts[:secret] == "" ? nil : opts[:secret]
|
55
|
+
@api_version = Hash.new('1')
|
56
|
+
@api_version.merge!({:objectify => '2'})
|
57
|
+
@hostname = opts[:hostname] || 'api.embed.ly'
|
58
|
+
@timeout = opts[:timeout] || 180
|
59
|
+
@headers = {
|
60
|
+
'User-Agent' => opts[:user_agent] || "Mozilla/5.0 (compatible; embedly-ruby/#{Embedly::VERSION};)"
|
61
|
+
}.merge(opts[:headers]||{})
|
62
|
+
end
|
63
|
+
|
64
|
+
def _do_typhoeus_call path
|
65
|
+
scheme, host, port = uri_parse hostname
|
66
|
+
url = "#{scheme}://#{hostname}:#{port}#{path}"
|
67
|
+
logger.debug { "calling #{site}#{path} with headers #{headers} using Typhoeus" }
|
68
|
+
Typhoeus::Request.get(url, {:headers => headers, :timeout => (@timeout*1000) })
|
69
|
+
end
|
70
|
+
|
71
|
+
def _do_basic_call path
|
72
|
+
scheme, host, port = uri_parse hostname
|
73
|
+
logger.debug { "calling #{site}#{path} with headers #{headers} using Net::HTTP" }
|
74
|
+
Net::HTTP.start(host, port, :use_ssl => scheme == 'https') do |http|
|
75
|
+
http.read_timeout = @timeout
|
76
|
+
http.get(path, headers)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def _do_oauth_call path
|
81
|
+
consumer = OAuth::Consumer.new(key, secret,
|
82
|
+
:site => site,
|
83
|
+
:http_method => :get,
|
84
|
+
:scheme => :query_string)
|
85
|
+
# our implementation is broken for header authorization, thus the
|
86
|
+
# query_string
|
87
|
+
|
88
|
+
access_token = OAuth::AccessToken.new consumer
|
89
|
+
logger.debug { "calling #{site}#{path} with headers #{headers} via OAuth" }
|
90
|
+
access_token.get path, headers
|
91
|
+
end
|
92
|
+
|
93
|
+
def _do_call path
|
94
|
+
if key and secret
|
95
|
+
_do_oauth_call path
|
96
|
+
else
|
97
|
+
configuration.typhoeus ? _do_typhoeus_call(path) : _do_basic_call(path)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# <b>Use methods oembed, objectify, preview in favor of this method.</b>
|
102
|
+
#
|
103
|
+
# Normalizes url and urls parameters and calls the endpoint. url OR urls
|
104
|
+
# must be present
|
105
|
+
#
|
106
|
+
# === Options
|
107
|
+
#
|
108
|
+
# [:+url+] _(optional)_ A single url
|
109
|
+
# [:+urls+] _(optional)_ An array of urls
|
110
|
+
# [:+action+] The method that should be called. ex. oembed, objectify, preview
|
111
|
+
# [:+version+] The api version number.
|
112
|
+
# [_others_] All other parameters are used as query strings.
|
113
|
+
def apicall opts
|
114
|
+
opts[:urls] ||= []
|
115
|
+
opts[:urls] << opts[:url] if opts[:url]
|
116
|
+
|
117
|
+
raise 'must pass urls' if opts[:urls].size == 0
|
118
|
+
|
119
|
+
params = {:urls => opts[:urls]}
|
120
|
+
|
121
|
+
# store unsupported services as errors and don't send them to embedly
|
122
|
+
rejects = []
|
123
|
+
if not key
|
124
|
+
params[:urls].reject!.with_index do |url, i|
|
125
|
+
if url !~ services_regex
|
126
|
+
rejects << [i,
|
127
|
+
Embedly::EmbedlyObject.new(
|
128
|
+
:type => 'error',
|
129
|
+
:error_code => 401,
|
130
|
+
:error_message => 'Embedly api key is required.'
|
131
|
+
)
|
132
|
+
]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
if params[:urls].size > 0
|
138
|
+
params[:key] = key if key and not secret
|
139
|
+
params.merge!Hash[
|
140
|
+
opts.select{|k,_| not [:url, :urls, :action, :version].index k}
|
141
|
+
]
|
142
|
+
|
143
|
+
path = "/#{opts[:version]}/#{opts[:action]}?#{QueryString.stringify(params)}"
|
144
|
+
|
145
|
+
response = _do_call path
|
146
|
+
|
147
|
+
if response.code.to_i == 200
|
148
|
+
logger.debug { response.body }
|
149
|
+
# [].flatten is to be sure we have an array
|
150
|
+
objs = [JSON.parse(response.body)].flatten.collect do |o|
|
151
|
+
Embedly::EmbedlyObject.new(o)
|
152
|
+
end
|
153
|
+
else
|
154
|
+
logger.debug { response }
|
155
|
+
raise Embedly::BadResponseException.new(response, path)
|
156
|
+
end
|
157
|
+
|
158
|
+
# re-insert rejects into response
|
159
|
+
rejects.each do |i, obj|
|
160
|
+
objs.insert i, obj
|
161
|
+
end
|
162
|
+
|
163
|
+
objs
|
164
|
+
else
|
165
|
+
# we only have rejects, return them without calling embedly
|
166
|
+
rejects.collect{|i, obj| obj}
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Returns structured data from the services API method.
|
171
|
+
#
|
172
|
+
# Response is cached per API object.
|
173
|
+
#
|
174
|
+
# see http://api.embed.ly/docs/service for a description of the response.
|
175
|
+
def services
|
176
|
+
if not @services
|
177
|
+
response = _do_call '/1/services/ruby'
|
178
|
+
raise 'services call failed', response if response.code.to_i != 200
|
179
|
+
@services = JSON.parse(response.body)
|
180
|
+
end
|
181
|
+
@services
|
182
|
+
end
|
183
|
+
|
184
|
+
# Returns a regex suitable for checking urls against for non-key usage
|
185
|
+
def services_regex
|
186
|
+
r = services.collect {|p| p["regex"].join("|")}.join("|")
|
187
|
+
Regexp.new r
|
188
|
+
end
|
189
|
+
|
190
|
+
# Performs api call based on method name
|
191
|
+
#
|
192
|
+
# === Currently supported
|
193
|
+
#
|
194
|
+
# - +oembed+
|
195
|
+
# - +objectify+
|
196
|
+
# - +preview+
|
197
|
+
#
|
198
|
+
def method_missing(name, *args, &block)
|
199
|
+
if @endpoints.include?name
|
200
|
+
opts = args[0]
|
201
|
+
opts[:action] = name
|
202
|
+
opts[:version] = @api_version[name]
|
203
|
+
apicall opts
|
204
|
+
else
|
205
|
+
super
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
def uri_parse uri
|
211
|
+
uri =~ %r{^((http(s?))://)?([^:/]+)(:([\d]+))?(/.*)?$}
|
212
|
+
scheme = $2 || 'http'
|
213
|
+
host = $4
|
214
|
+
port = $6 ? $6 : ( scheme == 'https' ? 443 : 80)
|
215
|
+
[scheme, host, port.to_i]
|
216
|
+
end
|
217
|
+
|
218
|
+
def site
|
219
|
+
scheme, host, port = uri_parse hostname
|
220
|
+
if (scheme == 'http' and port == 80) or (scheme == 'https' and port == 443)
|
221
|
+
"#{scheme}://#{host}"
|
222
|
+
else
|
223
|
+
"#{scheme}://#{host}:#{port}"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def logger
|
228
|
+
configuration.logger
|
229
|
+
end
|
230
|
+
|
231
|
+
def configuration
|
232
|
+
Embedly.configuration
|
233
|
+
end
|
234
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require "optparse"
|
2
|
+
|
3
|
+
module Embedly
|
4
|
+
class CommandLine
|
5
|
+
|
6
|
+
class Parser
|
7
|
+
attr_accessor :options
|
8
|
+
|
9
|
+
def initialize(args)
|
10
|
+
@options, @args = default, args
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse!
|
14
|
+
parser.parse!(@args)
|
15
|
+
set_urls!
|
16
|
+
reject_nil!
|
17
|
+
options
|
18
|
+
rescue OptionParser::InvalidOption => error
|
19
|
+
puts "ERROR: #{error.message}"
|
20
|
+
puts parser.on_tail
|
21
|
+
exit
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.parse!(args)
|
25
|
+
new(args).parse!
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def default
|
31
|
+
{
|
32
|
+
:key => ENV['EMBEDLY_KEY'],
|
33
|
+
:secret => ENV['EMBEDLY_SECRET'],
|
34
|
+
:headers => {},
|
35
|
+
:query => {},
|
36
|
+
:typhoeus => true
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def reject_nil!
|
41
|
+
options.reject! { |_, opt| opt.nil? }
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_urls!
|
45
|
+
raise(OptionParser::InvalidOption, "url required") if @args.empty?
|
46
|
+
options[:query][:urls] = @args
|
47
|
+
end
|
48
|
+
|
49
|
+
def parser
|
50
|
+
OptionParser.new do |parser|
|
51
|
+
parser.banner = %{
|
52
|
+
Fetch JSON from the embedly service.
|
53
|
+
Usage [OPTIONS] <url> [url] ..
|
54
|
+
}
|
55
|
+
|
56
|
+
parser.separator ""
|
57
|
+
parser.separator "Options:"
|
58
|
+
|
59
|
+
parser.on('-H', '--hostname ENDPOINT', 'Embedly host. Default is api.embed.ly.') do |hostname|
|
60
|
+
options[:hostname] = hostname
|
61
|
+
end
|
62
|
+
|
63
|
+
parser.on("--header NAME=VALUE", "HTTP header to send with requests.") do |hash|
|
64
|
+
header, value = hash.split '='
|
65
|
+
options[:headers][header] = value
|
66
|
+
end
|
67
|
+
|
68
|
+
parser.on("-k", "--key KEY", "Embedly key [default: EMBEDLY_KEY environmental variable]") do |key|
|
69
|
+
options[:key] = key
|
70
|
+
end
|
71
|
+
|
72
|
+
parser.on("-N", "--no-key", "Ignore EMBEDLY_KEY environmental variable") do |key|
|
73
|
+
options[:key] = nil
|
74
|
+
end
|
75
|
+
|
76
|
+
parser.on("-s", "--secret SECRET", "Embedly secret [default: EMBEDLY_SECRET environmental variable]") do |secret|
|
77
|
+
options[:secret] = secret
|
78
|
+
end
|
79
|
+
|
80
|
+
parser.on("--no-secret", "Ignore EMBEDLY_SECRET environmental variable") do
|
81
|
+
options[:secret] = nil
|
82
|
+
end
|
83
|
+
|
84
|
+
parser.on("-o", "--option NAME=VALUE", "Set option to be passed as query param.") do |option|
|
85
|
+
key, value = option.split('=')
|
86
|
+
options[:query][key.to_sym] = value
|
87
|
+
end
|
88
|
+
|
89
|
+
parser.on("--no-typhoeus", "Don't use typhoeus.") do
|
90
|
+
Embedly.configuration.typhoeus = false
|
91
|
+
end
|
92
|
+
|
93
|
+
parser.separator ""
|
94
|
+
parser.separator "Common Options:"
|
95
|
+
|
96
|
+
parser.on("-v", "--[no-]verbose", "Run verbosely") do |verbose|
|
97
|
+
Embedly.configuration.debug = verbose
|
98
|
+
end
|
99
|
+
|
100
|
+
parser.on("-h", "--help", "Display this message") do
|
101
|
+
puts parser
|
102
|
+
exit
|
103
|
+
end
|
104
|
+
|
105
|
+
parser.separator ""
|
106
|
+
parser.separator "Bob Corsaro <bob@embed.ly>"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class << self
|
112
|
+
def run!(endpoint, args = [])
|
113
|
+
new(args).run(endpoint)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def initialize(args)
|
118
|
+
@options, @args = {}, args
|
119
|
+
end
|
120
|
+
|
121
|
+
def run(endpoint = :oembed)
|
122
|
+
api_options = options.dup
|
123
|
+
query = api_options.delete(:query)
|
124
|
+
Embedly::API.new(api_options).send(endpoint, query)
|
125
|
+
end
|
126
|
+
|
127
|
+
def options
|
128
|
+
@options = Parser.parse!(@args.dup)
|
129
|
+
@options
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|