anideo-embedly 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|