movie_searcher 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +7 -0
- data/.rspec +5 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +32 -0
- data/LICENSE +20 -0
- data/README.md +26 -0
- data/Rakefile +56 -0
- data/lib/imdb_party.rb +8 -0
- data/lib/imdb_party/httparty_icebox.rb +259 -0
- data/lib/imdb_party/imdb.rb +45 -0
- data/lib/imdb_party/movie.rb +51 -0
- data/lib/imdb_party/person.rb +13 -0
- data/lib/movie_searcher/movie_searcher.rb +63 -0
- data/movie_searcher.gemspec +22 -0
- data/spec/movie_searcher_spec.rb +179 -0
- data/spec/spec_helper.rb +6 -0
- metadata +129 -0
data/.document
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
MovieSearcher (0.0.1)
|
5
|
+
httparty
|
6
|
+
levenshtein
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: http://rubygems.org/
|
10
|
+
specs:
|
11
|
+
crack (0.1.8)
|
12
|
+
diff-lcs (1.1.2)
|
13
|
+
httparty (0.7.3)
|
14
|
+
crack (= 0.1.8)
|
15
|
+
levenshtein (0.2.0)
|
16
|
+
rspec (2.4.0)
|
17
|
+
rspec-core (~> 2.4.0)
|
18
|
+
rspec-expectations (~> 2.4.0)
|
19
|
+
rspec-mocks (~> 2.4.0)
|
20
|
+
rspec-core (2.4.0)
|
21
|
+
rspec-expectations (2.4.0)
|
22
|
+
diff-lcs (~> 1.1.2)
|
23
|
+
rspec-mocks (2.4.0)
|
24
|
+
|
25
|
+
PLATFORMS
|
26
|
+
ruby
|
27
|
+
|
28
|
+
DEPENDENCIES
|
29
|
+
MovieSearcher!
|
30
|
+
httparty
|
31
|
+
levenshtein
|
32
|
+
rspec
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Jon Maddox
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# ImdbParty!
|
2
|
+
|
3
|
+
## How To Use
|
4
|
+
|
5
|
+
### Create an instance
|
6
|
+
|
7
|
+
imdb = ImdbParty::Imdb.new
|
8
|
+
### Search for a movie by title
|
9
|
+
|
10
|
+
imdb.find_by_title("The Dark Knight") => [{:title => "The Dark Knight", :year => "2008", :imdb_id => "tt0468569"}, {:title => "Batman Unmasked", ...}]
|
11
|
+
|
12
|
+
### Get a movie by its imdb_id
|
13
|
+
|
14
|
+
movie = imdb.find_movie_by_id("tt0468569")
|
15
|
+
|
16
|
+
movie.title => "The Dark Knight"
|
17
|
+
movie.rating => 8.1
|
18
|
+
movie.certification => "PG-13"
|
19
|
+
|
20
|
+
### Find the top 250 movies of all time
|
21
|
+
|
22
|
+
imdb.top_250 => [{:title => "Shawshank Redemption", :year => "1994", :imdb_id => "tt0111161"}, {:title => "The Godfather", ...}]
|
23
|
+
|
24
|
+
### Get the currently popular tv shows
|
25
|
+
|
26
|
+
imdb.popular_shows => [{:title => "Glee", :year => "2009", :imdb_id => "tt1327801"}, {:title => "Dexter", ...}]
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "imdb_party"
|
8
|
+
gem.summary = %Q{IMDB client using the IMDB API that their iPhone app uses}
|
9
|
+
gem.description = %Q{IMDB client using the IMDB API that their iPhone app uses}
|
10
|
+
gem.email = "jon@mustacheinc.com"
|
11
|
+
gem.homepage = "http://github.com/maddox/imdb_party"
|
12
|
+
gem.authors = ["Jon Maddox"]
|
13
|
+
gem.add_development_dependency "shoulda"
|
14
|
+
gem.add_dependency "httparty"
|
15
|
+
end
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'rake/testtask'
|
21
|
+
Rake::TestTask.new(:test) do |test|
|
22
|
+
test.libs << 'lib' << 'test'
|
23
|
+
test.pattern = 'test/**/*_test.rb'
|
24
|
+
test.verbose = true
|
25
|
+
end
|
26
|
+
|
27
|
+
begin
|
28
|
+
require 'rcov/rcovtask'
|
29
|
+
Rcov::RcovTask.new do |test|
|
30
|
+
test.libs << 'test'
|
31
|
+
test.pattern = 'test/**/*_test.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
rescue LoadError
|
35
|
+
task :rcov do
|
36
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
task :test => :check_dependencies
|
41
|
+
|
42
|
+
task :default => :test
|
43
|
+
|
44
|
+
require 'rake/rdoctask'
|
45
|
+
Rake::RDocTask.new do |rdoc|
|
46
|
+
if File.exist?('VERSION')
|
47
|
+
version = File.read('VERSION')
|
48
|
+
else
|
49
|
+
version = ""
|
50
|
+
end
|
51
|
+
|
52
|
+
rdoc.rdoc_dir = 'rdoc'
|
53
|
+
rdoc.title = "imdb_party #{version}"
|
54
|
+
rdoc.rdoc_files.include('README*')
|
55
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
56
|
+
end
|
data/lib/imdb_party.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
directory = File.expand_path(File.dirname(__FILE__))
|
4
|
+
require File.join(directory, 'imdb_party', 'httparty_icebox')
|
5
|
+
require File.join(directory, 'imdb_party', 'imdb')
|
6
|
+
require File.join(directory, 'imdb_party', 'movie')
|
7
|
+
require File.join(directory, 'imdb_party', 'person')
|
8
|
+
|
@@ -0,0 +1,259 @@
|
|
1
|
+
# = Icebox : Caching for HTTParty
|
2
|
+
#
|
3
|
+
# Cache responses in HTTParty models [http://github.com/jnunemaker/httparty]
|
4
|
+
#
|
5
|
+
# === Usage
|
6
|
+
#
|
7
|
+
# class Foo
|
8
|
+
# include HTTParty
|
9
|
+
# include HTTParty::Icebox
|
10
|
+
# cache :store => 'file', :timeout => 600, :location => MY_APP_ROOT.join('tmp', 'cache')
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# Modeled after Martyn Loughran's APICache [http://github.com/newbamboo/api_cache]
|
14
|
+
# and Ruby On Rails's caching [http://api.rubyonrails.org/classes/ActiveSupport/Cache.html]
|
15
|
+
#
|
16
|
+
# Author: Karel Minarik [www.karmi.cz]
|
17
|
+
#
|
18
|
+
# === Notes
|
19
|
+
#
|
20
|
+
# Thanks to Amit Chakradeo to point out objects have to be stored marhalled on FS
|
21
|
+
# Thanks to Marlin Forbes to point out query parameters have to be include in the cache key
|
22
|
+
#
|
23
|
+
#
|
24
|
+
|
25
|
+
require 'logger'
|
26
|
+
require 'fileutils'
|
27
|
+
require 'tmpdir'
|
28
|
+
require 'pathname'
|
29
|
+
require 'digest/md5'
|
30
|
+
|
31
|
+
module HTTParty #:nodoc:
|
32
|
+
module Icebox
|
33
|
+
|
34
|
+
module ClassMethods
|
35
|
+
|
36
|
+
# Enable caching and set cache options
|
37
|
+
# Returns memoized cache object
|
38
|
+
#
|
39
|
+
# Following options are available, default values are in []:
|
40
|
+
#
|
41
|
+
# +store+:: Storage mechanism for cached data (memory, filesystem, your own) [memory]
|
42
|
+
# +timeout+:: Cache expiration in seconds [60]
|
43
|
+
# +logger+:: Path to logfile or logger instance [STDOUT]
|
44
|
+
#
|
45
|
+
# Any additional options are passed to the Cache constructor
|
46
|
+
#
|
47
|
+
# Usage:
|
48
|
+
#
|
49
|
+
# # Enable caching in HTTParty, in memory, for 1 minute
|
50
|
+
# cache # Use default values
|
51
|
+
#
|
52
|
+
# # Enable caching in HTTParty, on filesystem (/tmp), for 10 minutes
|
53
|
+
# cache :store => 'file', :timeout => 600, :location => '/tmp/'
|
54
|
+
#
|
55
|
+
# # Use your own cache store (see AbstractStore class below)
|
56
|
+
# cache :store => 'memcached', :timeout => 600, :server => '192.168.1.1:1001'
|
57
|
+
#
|
58
|
+
def cache(options={})
|
59
|
+
options[:store] ||= 'memory'
|
60
|
+
options[:timeout] ||= 60
|
61
|
+
logger = options[:logger]
|
62
|
+
@cache ||= Cache.new( options.delete(:store), options )
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
# When included, extend class with +cache+ method
|
68
|
+
# and redefine +get+ method to use cache
|
69
|
+
#
|
70
|
+
def self.included(receiver) #:nodoc:
|
71
|
+
receiver.extend ClassMethods
|
72
|
+
receiver.class_eval do
|
73
|
+
|
74
|
+
# Get reponse from network
|
75
|
+
# TODO: Why alias :new :old is not working here? Returns NoMethodError
|
76
|
+
#
|
77
|
+
def self.get_without_caching(path, options={})
|
78
|
+
perform_request Net::HTTP::Get, path, options
|
79
|
+
end
|
80
|
+
|
81
|
+
# Get response from cache, if available
|
82
|
+
#
|
83
|
+
def self.get_with_caching(path, options={})
|
84
|
+
key = path.clone
|
85
|
+
key << options[:query].to_s if defined? options[:query]
|
86
|
+
|
87
|
+
if cache.exists?(key) and not cache.stale?(key)
|
88
|
+
Cache.logger.debug "CACHE -- GET #{path}#{options[:query]}"
|
89
|
+
return cache.get(key)
|
90
|
+
else
|
91
|
+
Cache.logger.debug "/!\\ NETWORK -- GET #{path}#{options[:query]}"
|
92
|
+
response = get_without_caching(path, options)
|
93
|
+
cache.set(key, response) if response.code == 200
|
94
|
+
return response
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Redefine original HTTParty +get+ method to use cache
|
99
|
+
#
|
100
|
+
def self.get(path, options={})
|
101
|
+
self.get_with_caching(path, options)
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# === Cache container
|
108
|
+
#
|
109
|
+
# Pass a store name ('memory', etc) to initializer
|
110
|
+
#
|
111
|
+
class Cache
|
112
|
+
attr_accessor :store
|
113
|
+
|
114
|
+
def initialize(store, options={})
|
115
|
+
self.class.logger = options[:logger]
|
116
|
+
@store = self.class.lookup_store(store).new(options)
|
117
|
+
end
|
118
|
+
|
119
|
+
def get(key); @store.get encode(key) unless stale?(key); end
|
120
|
+
def set(key, value); @store.set encode(key), value; end
|
121
|
+
def exists?(key); @store.exists? encode(key); end
|
122
|
+
def stale?(key); @store.stale? encode(key); end
|
123
|
+
|
124
|
+
def self.logger; @logger || default_logger; end
|
125
|
+
def self.default_logger; logger = ::Logger.new(STDERR); end
|
126
|
+
|
127
|
+
# Pass a filename (String), IO object, Logger instance or +nil+ to silence the logger
|
128
|
+
def self.logger=(device); @logger = device.kind_of?(::Logger) ? device : ::Logger.new(device); end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
# Return store class based on passed name
|
133
|
+
def self.lookup_store(name)
|
134
|
+
store_name = "#{name.capitalize}Store"
|
135
|
+
return Store::const_get(store_name)
|
136
|
+
rescue NameError => e
|
137
|
+
raise Store::StoreNotFound, "The cache store '#{store_name}' was not found. Did you loaded any such class?"
|
138
|
+
end
|
139
|
+
|
140
|
+
def encode(key); Digest::MD5.hexdigest(key); end
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
# === Cache stores
|
145
|
+
#
|
146
|
+
module Store
|
147
|
+
|
148
|
+
class StoreNotFound < StandardError; end #:nodoc:
|
149
|
+
|
150
|
+
# ==== Abstract Store
|
151
|
+
# Inherit your store from this class
|
152
|
+
# *IMPORTANT*: Do not forget to call +super+ in your +initialize+ method!
|
153
|
+
#
|
154
|
+
class AbstractStore
|
155
|
+
def initialize(options={})
|
156
|
+
raise ArgumentError, "You need to set the :timeout parameter" unless options[:timeout]
|
157
|
+
@timeout = options[:timeout]
|
158
|
+
message = "Cache: Using #{self.class.to_s.split('::').last}"
|
159
|
+
message << " in location: #{options[:location]}" if options[:location]
|
160
|
+
message << " with timeout #{options[:timeout]} sec"
|
161
|
+
Cache.logger.info message unless options[:logger].nil?
|
162
|
+
return self
|
163
|
+
end
|
164
|
+
%w{set get exists? stale?}.each do |method_name|
|
165
|
+
define_method(method_name) { raise NoMethodError, "Please implement method set in your store class" }
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# ===== Store objects in memory
|
170
|
+
#
|
171
|
+
Struct.new("TvdbResponse", :code, :body, :headers) { def to_s; self.body; end }
|
172
|
+
class MemoryStore < AbstractStore
|
173
|
+
def initialize(options={})
|
174
|
+
super; @store = {}; self
|
175
|
+
end
|
176
|
+
def set(key, value)
|
177
|
+
Cache.logger.info("Cache: set (#{key})")
|
178
|
+
@store[key] = [Time.now, value]; true
|
179
|
+
end
|
180
|
+
def get(key)
|
181
|
+
data = @store[key][1]
|
182
|
+
Cache.logger.info("Cache: #{data.nil? ? "miss" : "hit"} (#{key})")
|
183
|
+
data
|
184
|
+
end
|
185
|
+
def exists?(key)
|
186
|
+
!@store[key].nil?
|
187
|
+
end
|
188
|
+
def stale?(key)
|
189
|
+
return true unless exists?(key)
|
190
|
+
Time.now - created(key) > @timeout
|
191
|
+
end
|
192
|
+
private
|
193
|
+
def created(key)
|
194
|
+
@store[key][0]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# ===== Store objects on the filesystem
|
199
|
+
#
|
200
|
+
class FileStore < AbstractStore
|
201
|
+
def initialize(options={})
|
202
|
+
super
|
203
|
+
options[:location] ||= Dir::tmpdir
|
204
|
+
@path = Pathname.new( options[:location] )
|
205
|
+
FileUtils.mkdir_p( @path )
|
206
|
+
self
|
207
|
+
end
|
208
|
+
def set(key, value)
|
209
|
+
Cache.logger.info("Cache: set (#{key})")
|
210
|
+
File.open( @path.join(key), 'w' ) { |file| file << Marshal.dump(value) }
|
211
|
+
true
|
212
|
+
end
|
213
|
+
def get(key)
|
214
|
+
data = Marshal.load(File.read( @path.join(key)))
|
215
|
+
Cache.logger.info("Cache: #{data.nil? ? "miss" : "hit"} (#{key})")
|
216
|
+
data
|
217
|
+
end
|
218
|
+
def exists?(key)
|
219
|
+
File.exists?( @path.join(key) )
|
220
|
+
end
|
221
|
+
def stale?(key)
|
222
|
+
return true unless exists?(key)
|
223
|
+
Time.now - created(key) > @timeout
|
224
|
+
end
|
225
|
+
private
|
226
|
+
def created(key)
|
227
|
+
File.mtime( @path.join(key) )
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
# Major parts of this code are based on architecture of ApiCache.
|
237
|
+
# Copyright (c) 2008 Martyn Loughran
|
238
|
+
#
|
239
|
+
# Other parts are inspired by the ActiveSupport::Cache in Ruby On Rails.
|
240
|
+
# Copyright (c) 2005-2009 David Heinemeier Hansson
|
241
|
+
#
|
242
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
243
|
+
# a copy of this software and associated documentation files (the
|
244
|
+
# "Software"), to deal in the Software without restriction, including
|
245
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
246
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
247
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
248
|
+
# the following conditions:
|
249
|
+
#
|
250
|
+
# The above copyright notice and this permission notice shall be
|
251
|
+
# included in all copies or substantial portions of the Software.
|
252
|
+
#
|
253
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
254
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
255
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
256
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
257
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
258
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
259
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ImdbParty
|
2
|
+
class Imdb
|
3
|
+
include HTTParty
|
4
|
+
include HTTParty::Icebox
|
5
|
+
cache :store => 'file', :timeout => 120, :location => Dir.tmpdir
|
6
|
+
|
7
|
+
base_uri 'app.imdb.com'
|
8
|
+
default_params = {"api" => "v1", "appid" => "iphone1", "locale" => "en_US", "timestamp" => Time.now.to_i, "sig" => "heres my signature"}
|
9
|
+
|
10
|
+
|
11
|
+
def find_by_title(title)
|
12
|
+
movie_results = []
|
13
|
+
results = self.class.get('/find', :query => {:q => title}).parsed_response
|
14
|
+
|
15
|
+
if results["data"] && results["data"]["results"]
|
16
|
+
results["data"]["results"].each do |result_section|
|
17
|
+
result_section["list"].each do |r|
|
18
|
+
next unless r["tconst"] && r["title"]
|
19
|
+
h = {:title => r["title"], :year => r["year"], :imdb_id => r["tconst"]}
|
20
|
+
h.merge!(:poster_url => r["image"]["url"]) if r["image"] && r["image"]["url"]
|
21
|
+
movie_results << h
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
movie_results
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_movie_by_id(imdb_id)
|
30
|
+
result = self.class.get('/title/maindetails', :query => {:tconst => imdb_id}).parsed_response
|
31
|
+
Movie.new(result["data"]) unless result["data"].nil?
|
32
|
+
end
|
33
|
+
|
34
|
+
def top_250
|
35
|
+
results = self.class.get('/chart/top').parsed_response
|
36
|
+
results["data"]["list"]["list"].map { |r| {:title => r["title"], :imdb_id => r["tconst"], :year => r["year"], :poster_url => (r["image"] ? r["image"]["url"] : nil)} }
|
37
|
+
end
|
38
|
+
|
39
|
+
def popular_shows
|
40
|
+
results = self.class.get('/chart/tv').parsed_response
|
41
|
+
results["data"]["list"].map { |r| {:title => r["title"], :imdb_id => r["tconst"], :year => r["year"], :poster_url => (r["image"] ? r["image"]["url"] : nil)} }
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module ImdbParty
|
2
|
+
class Movie
|
3
|
+
attr_accessor :imdb_id, :title, :directors, :writers, :tagline, :company, :plot, :runtime, :rating, :poster_url, :release_date, :certification, :genres, :actors, :trailers
|
4
|
+
|
5
|
+
def initialize(options={})
|
6
|
+
if not options.nil? and options.keys.first.class == Symbol
|
7
|
+
options.keys.each { |name| instance_variable_set "@" + name.to_s, options[name] }; return
|
8
|
+
end
|
9
|
+
|
10
|
+
@imdb_id = options["tconst"]
|
11
|
+
@title = options["title"]
|
12
|
+
@tagline = options["tagline"]
|
13
|
+
@plot = options["plot"]["outline"] if options["plot"]
|
14
|
+
@runtime = "#{(options["runtime"]["time"]/60).to_i} min" if options["runtime"]
|
15
|
+
@rating = options["rating"]
|
16
|
+
@poster_url = options["image"]["url"] if options["image"]
|
17
|
+
@release_date = options["release_date"]["normal"] if options["release_date"] && options["release_date"]["normal"]
|
18
|
+
@certification = options["certificate"]["certificate"] if options["certificate"] && options["certificate"]["certificate"]
|
19
|
+
@genres = options["genres"] || []
|
20
|
+
|
21
|
+
# Sometimes the @release_date object is a string instead of a date object
|
22
|
+
if @release_date.class == String
|
23
|
+
[{:regex => /^\d{4}-\d{2}$/, :concat => "-01"}, {:regex => /^\d{4}$/, :concat => "-01-01"}].each do |value|
|
24
|
+
if @release_date.match(value[:regex])
|
25
|
+
@release_date = Date.parse(@release_date << value[:concat]); break
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# parse directors
|
31
|
+
@directors = options["directors_summary"] ? options["directors_summary"].map { |d| Person.new(d) } : []
|
32
|
+
|
33
|
+
# parse directors
|
34
|
+
@writers = []
|
35
|
+
@writers = options["writers_summary"] ? options["writers_summary"].map { |w| Person.new(w) } : []
|
36
|
+
|
37
|
+
# parse actors
|
38
|
+
@actors = []
|
39
|
+
@actors = options["cast_summary"] ? options["cast_summary"].map { |a| Person.new(a) } : []
|
40
|
+
|
41
|
+
#parse trailers
|
42
|
+
@trailers = {}
|
43
|
+
if options["trailer"] && options["trailer"]["encodings"]
|
44
|
+
options["trailer"]["encodings"].each_pair do |k,v|
|
45
|
+
@trailers[v["format"]] = v["url"]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/../imdb_party.rb"
|
2
|
+
require 'levenshtein'
|
3
|
+
|
4
|
+
class MovieSearcher
|
5
|
+
def initialize(args)
|
6
|
+
args.keys.each { |name| instance_variable_set "@" + name.to_s, args[name] unless name == :options }
|
7
|
+
|
8
|
+
@options = {
|
9
|
+
:long => 15,
|
10
|
+
:split => /\s+|\./,
|
11
|
+
:imdb => ImdbParty::Imdb.new,
|
12
|
+
:limit => 0.4
|
13
|
+
}
|
14
|
+
|
15
|
+
@options.merge!(args[:options]) unless args[:options].nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.find_by_release_name(search_value, options = {})
|
19
|
+
this = MovieSearcher.new(options.merge(:search_value => search_value.to_s))
|
20
|
+
return if this.to_long?
|
21
|
+
|
22
|
+
return this.find_the_movie!
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_long?
|
26
|
+
@split = @search_value.split(@options[:split])
|
27
|
+
@split.length > @options[:long]
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_the_movie!
|
31
|
+
current = @split.length
|
32
|
+
|
33
|
+
until current <= 0 do
|
34
|
+
title = @split.take(current).join(' ')
|
35
|
+
movies = @options[:imdb].find_by_title(title)
|
36
|
+
break if movies.any?
|
37
|
+
current -= 1
|
38
|
+
end
|
39
|
+
|
40
|
+
return if movies.nil? or not movies.any?
|
41
|
+
|
42
|
+
# Cleaning up the title, no years allowed
|
43
|
+
title = title.gsub(/[^a-z0-9]/i, '').gsub(/(19|20)\d{2}/, '')
|
44
|
+
|
45
|
+
movie = movies.map do |movie|
|
46
|
+
[movie, Levenshtein.normalized_distance(movie[:title].gsub(/[^a-z0-9]/i, ''), title, @options[:limit])]
|
47
|
+
end.reject do |value|
|
48
|
+
value.last.nil?
|
49
|
+
end.sort_by do |_,value|
|
50
|
+
value
|
51
|
+
end.first
|
52
|
+
|
53
|
+
return if movie.nil?
|
54
|
+
|
55
|
+
return ImdbParty::Movie.new(movie.first)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.method_missing(m, *args, &block)
|
59
|
+
result = ImdbParty::Imdb.new.send(m, *args)
|
60
|
+
return if result.nil?
|
61
|
+
result.class == Array ? result.map{|r| ImdbParty::Movie.new(r)} : result
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "movie_searcher"
|
6
|
+
s.version = "0.0.1"
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = ["Linus Oleander", "Jon Maddox"]
|
9
|
+
s.email = ["linus@oleander.nu", "jon@mustacheinc.com"]
|
10
|
+
s.homepage = "https://github.com/oleander/Undertexter"
|
11
|
+
s.summary = %q{IMDB client using the IMDB API that their iPhone app uses}
|
12
|
+
s.description = %q{IMDB client using the IMDB API that their iPhone app uses. It can also figure out what movie you are looking for just by looking at the release name of the movie}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_dependency('httparty')
|
20
|
+
s.add_dependency('levenshtein')
|
21
|
+
s.add_development_dependency('rspec')
|
22
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MovieSearcher do
|
4
|
+
it "should only contain should instances of {ImdbParty::Movie}" do
|
5
|
+
MovieSearcher.find_by_title("The Dark Knight").each{|movie| movie.should be_instance_of(ImdbParty::Movie)}
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should only contain one instance of {ImdbParty::Movie}" do
|
9
|
+
MovieSearcher.find_movie_by_id("tt0468569").should be_instance_of(ImdbParty::Movie)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should return nil if no movie is found" do
|
13
|
+
MovieSearcher.find_movie_by_id("tt23223423423").should be_nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should return nil if the movie title is to long" do
|
17
|
+
MovieSearcher.find_by_release_name("asd asd asd asd asd asd asd asd asd asd asd asd asd asd asd asd asd asd asd asd asd asd asd").should be_nil
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should return nil when setting the limit to low" do
|
21
|
+
MovieSearcher.find_by_release_name('Paranormal Activity 2 2010 UNRATED DVDRip XviD-Larceny', :options => {:limit => 0}).should be_nil
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should return the right movie" do
|
25
|
+
[{
|
26
|
+
:title => "Live Free Or Die Hard 2007 DVDRIP XviD-CRNTV", :iid => "tt0337978"
|
27
|
+
}, {
|
28
|
+
:title => "The Chronicles of Narnia - The Voyage of the Dawn Treader TS XViD - FLAWL3SS", :iid => "tt0980970"
|
29
|
+
}, {
|
30
|
+
:title => "Heartbreaker 2010 LIMITED DVDRip XviD-SUBMERGE", :iid => "tt1465487"
|
31
|
+
},{
|
32
|
+
:title => "Paranormal Activity 2 2010 UNRATED DVDRip XviD-Larceny", :iid => "tt1536044"
|
33
|
+
}].each do |movie|
|
34
|
+
MovieSearcher.find_by_release_name(movie[:title]).imdb_id.should eq(movie[:iid])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should return nil if no value is being passed to it" do
|
39
|
+
MovieSearcher.find_by_release_name("").should be_nil
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should return nil if nil is being passed to it" do
|
43
|
+
MovieSearcher.find_by_release_name(nil).should be_nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe MovieSearcher, "should work as before" do
|
48
|
+
before(:all) do
|
49
|
+
@movie = MovieSearcher.find_movie_by_id("tt0382932")
|
50
|
+
end
|
51
|
+
|
52
|
+
it "have a title" do
|
53
|
+
@movie.title.should eq("Ratatouille")
|
54
|
+
end
|
55
|
+
|
56
|
+
it "have an imdb_id" do
|
57
|
+
@movie.imdb_id.should eq("tt0382932")
|
58
|
+
end
|
59
|
+
|
60
|
+
it "have a tagline" do
|
61
|
+
@movie.tagline.should eq("Dinner is served... Summer 2007")
|
62
|
+
end
|
63
|
+
|
64
|
+
it "have a plot" do
|
65
|
+
@movie.plot.should eq("Remy is a young rat in the French countryside who arrives in Paris, only to find out that his cooking idol is dead. When he makes an unusual alliance with a restaurant's new garbage boy, the culinary and personal adventures begin despite Remy's family's skepticism and the rat-hating world of humans.")
|
66
|
+
end
|
67
|
+
|
68
|
+
it "have a runtime" do
|
69
|
+
@movie.runtime.should eq("111 min")
|
70
|
+
end
|
71
|
+
|
72
|
+
it "have a rating" do
|
73
|
+
@movie.rating.should eq(8.1)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "have a poster_url" do
|
77
|
+
@movie.poster_url.should match(/http:\/\/ia.media-imdb.com\/images\/.*/)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "have a release date" do
|
81
|
+
@movie.release_date.should eq(DateTime.parse("2007-06-29"))
|
82
|
+
end
|
83
|
+
|
84
|
+
it "have a certification" do
|
85
|
+
@movie.certification.should eq("G")
|
86
|
+
end
|
87
|
+
|
88
|
+
it "have trailers" do
|
89
|
+
@movie.trailers.should be_instance_of(Hash)
|
90
|
+
@movie.should have(4).trailers
|
91
|
+
@movie.trailers[@movie.trailers.keys.first].should eq("http://www.totaleclips.com/Player/Bounce.aspx?eclipid=e27826&bitrateid=461&vendorid=102&type=.mp4")
|
92
|
+
end
|
93
|
+
|
94
|
+
it "have genres" do
|
95
|
+
@movie.genres.should be_instance_of(Array)
|
96
|
+
@movie.should have(4).genres
|
97
|
+
end
|
98
|
+
|
99
|
+
it "have actors" do
|
100
|
+
@movie.actors.should be_instance_of(Array)
|
101
|
+
@movie.should have(4).actors
|
102
|
+
end
|
103
|
+
|
104
|
+
it "have directors" do
|
105
|
+
@movie.directors.should be_instance_of(Array)
|
106
|
+
@movie.should have(2).directors
|
107
|
+
end
|
108
|
+
|
109
|
+
it "have writers" do
|
110
|
+
@movie.writers.should be_instance_of(Array)
|
111
|
+
@movie.should have(2).writers
|
112
|
+
end
|
113
|
+
|
114
|
+
it "always return a date object when requesting the release date" do
|
115
|
+
[{:imdb => "tt0066026", :date => "1970-03-01"}, {:imdb => "tt1446072", :date => "2010-11-27"}].each do |value|
|
116
|
+
movie = MovieSearcher.find_movie_by_id(value[:imdb])
|
117
|
+
movie.release_date.strftime('%Y %m %d').should eq(DateTime.parse(value[:date]).strftime('%Y %m %d'))
|
118
|
+
movie.release_date.should be_instance_of(Date)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe MovieSearcher, "should also work as before" do
|
124
|
+
it "should have at least 15 results" do
|
125
|
+
MovieSearcher.find_by_title("ratatouille").count.should >= 15
|
126
|
+
end
|
127
|
+
|
128
|
+
it "search for title with spaces in the name" do
|
129
|
+
MovieSearcher.find_by_title("the truman show").count.should >= 1
|
130
|
+
end
|
131
|
+
|
132
|
+
it "search for bad title with no results" do
|
133
|
+
MovieSearcher.find_by_title("sdkljlkkl123j4lk23kl3").count.should eq(0)
|
134
|
+
end
|
135
|
+
|
136
|
+
it "find movie by id" do
|
137
|
+
MovieSearcher.find_movie_by_id("tt0382932").should be_instance_of(ImdbParty::Movie)
|
138
|
+
end
|
139
|
+
|
140
|
+
it "be an Array of {ImdbParty::Movie}" do
|
141
|
+
MovieSearcher.top_250.should be_instance_of(Array)
|
142
|
+
MovieSearcher.top_250.first.should be_instance_of(ImdbParty::Movie)
|
143
|
+
end
|
144
|
+
|
145
|
+
it "find popular shows" do
|
146
|
+
MovieSearcher.popular_shows.should be_instance_of(Array)
|
147
|
+
MovieSearcher.popular_shows.first.should be_instance_of(ImdbParty::Movie)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe MovieSearcher, "should still have the same people" do
|
152
|
+
before(:all) do
|
153
|
+
@movie = MovieSearcher.find_movie_by_id("tt0382932")
|
154
|
+
end
|
155
|
+
|
156
|
+
it "have a name" do
|
157
|
+
@movie.actors.map(&:name).should include('Patton Oswalt')
|
158
|
+
end
|
159
|
+
|
160
|
+
it "have a role" do
|
161
|
+
@movie.actors.map(&:role).should include('Remy')
|
162
|
+
end
|
163
|
+
|
164
|
+
it "have a name" do
|
165
|
+
@movie.directors.map(&:name).should include('Brad Bird')
|
166
|
+
end
|
167
|
+
|
168
|
+
it "not have a role" do
|
169
|
+
@movie.directors.first.role.should be_nil
|
170
|
+
end
|
171
|
+
|
172
|
+
it "have a name" do
|
173
|
+
@movie.writers.map(&:name).should include('Brad Bird')
|
174
|
+
end
|
175
|
+
|
176
|
+
it "not have a role" do
|
177
|
+
@movie.writers.first.role.should be_nil
|
178
|
+
end
|
179
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: movie_searcher
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Linus Oleander
|
14
|
+
- Jon Maddox
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2011-01-25 00:00:00 +01:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: httparty
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 3
|
31
|
+
segments:
|
32
|
+
- 0
|
33
|
+
version: "0"
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: levenshtein
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
version: "0"
|
48
|
+
type: :runtime
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: rspec
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
type: :development
|
63
|
+
version_requirements: *id003
|
64
|
+
description: IMDB client using the IMDB API that their iPhone app uses. It can also figure out what movie you are looking for just by looking at the release name of the movie
|
65
|
+
email:
|
66
|
+
- linus@oleander.nu
|
67
|
+
- jon@mustacheinc.com
|
68
|
+
executables: []
|
69
|
+
|
70
|
+
extensions: []
|
71
|
+
|
72
|
+
extra_rdoc_files: []
|
73
|
+
|
74
|
+
files:
|
75
|
+
- .bundle/config
|
76
|
+
- .document
|
77
|
+
- .gitignore
|
78
|
+
- .rspec
|
79
|
+
- Gemfile
|
80
|
+
- Gemfile.lock
|
81
|
+
- LICENSE
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- lib/imdb_party.rb
|
85
|
+
- lib/imdb_party/httparty_icebox.rb
|
86
|
+
- lib/imdb_party/imdb.rb
|
87
|
+
- lib/imdb_party/movie.rb
|
88
|
+
- lib/imdb_party/person.rb
|
89
|
+
- lib/movie_searcher/movie_searcher.rb
|
90
|
+
- movie_searcher.gemspec
|
91
|
+
- spec/movie_searcher_spec.rb
|
92
|
+
- spec/spec_helper.rb
|
93
|
+
has_rdoc: true
|
94
|
+
homepage: https://github.com/oleander/Undertexter
|
95
|
+
licenses: []
|
96
|
+
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
hash: 3
|
108
|
+
segments:
|
109
|
+
- 0
|
110
|
+
version: "0"
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
hash: 3
|
117
|
+
segments:
|
118
|
+
- 0
|
119
|
+
version: "0"
|
120
|
+
requirements: []
|
121
|
+
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 1.3.7
|
124
|
+
signing_key:
|
125
|
+
specification_version: 3
|
126
|
+
summary: IMDB client using the IMDB API that their iPhone app uses
|
127
|
+
test_files:
|
128
|
+
- spec/movie_searcher_spec.rb
|
129
|
+
- spec/spec_helper.rb
|