]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - data/rbot/plugins/imdb.rb
imdb plugin: fix detection of multiple titles per year
[user/henk/code/ruby/rbot.git] / data / rbot / plugins / imdb.rb
1 #-- vim:sw=2:et
2 #++
3 #
4 # :title: IMDB plugin for rbot
5 #
6 # Author:: Arnaud Cornet <arnaud.cornet@gmail.com>
7 # Author:: Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>
8 #
9 # Copyright:: (C) 2005 Arnaud Cornet
10 # Copyright:: (C) 2007 Giuseppe Bilotta
11 #
12 # License:: MIT license
13
14 require 'uri/common'
15
16 class Imdb
17   IMDB = "http://us.imdb.com"
18   TITLE_OR_NAME_MATCH = /<a href="(\/(?:title|name)\/(?:tt|nm)[0-9]+\/?)[^"]*"(?:[^>]*)>([^<]*)<\/a>/
19   TITLE_MATCH = /<a href="(\/title\/tt[0-9]+\/?)[^"]*"(?:[^>]*)>([^<]*)<\/a>/
20   NAME_MATCH = /<a href="(\/name\/nm[0-9]+\/?)[^"]*"(?:[^>]*)>([^<]*)<\/a>/
21   FINAL_ARTICLE_MATCH = /, ([A-Z]\S{0,2})$/
22
23   def initialize(bot)
24     @bot = bot
25   end
26
27   def search(rawstr)
28     str = URI.escape(rawstr)
29     str << ";site=aka" if @bot.config['imdb.aka']
30     return do_search(str)
31   end
32
33   def do_search(str)
34     resp = nil
35     begin
36       resp = @bot.httputil.get_response(IMDB + "/find?q=#{str}",
37                                         :max_redir => -1)
38     rescue Exception => e
39       error e.message
40       warning e.backtrace.join("\n")
41       return nil
42     end
43
44     if resp.code == "200"
45       m = []
46       m << TITLE_OR_NAME_MATCH.match(resp.body) if @bot.config['imdb.popular']
47       if resp.body.match(/\(Exact Matches\)<\/b>/) and @bot.config['imdb.exact']
48         m << TITLE_OR_NAME_MATCH.match($')
49       end
50       m.compact!
51       unless m.empty?
52         return m.map { |mm|
53           mm[1]
54         }.uniq
55       end
56     elsif resp.code == "302"
57       debug "automatic redirection"
58       new_loc = resp['location'].gsub(IMDB, "")
59       if new_loc.match(/\/find\?q=(.*)/)
60         return do_search($1)
61       else
62         return [new_loc.gsub(/\?.*/, "")]
63       end
64     end
65     return nil
66   end
67
68   def info(rawstr)
69     urls = search(rawstr)
70     debug urls
71     if urls.nil_or_empty?
72       debug "IMDB: search returned NIL"
73       return nil
74     end
75     results = []
76     urls.each { |sr|
77       type = sr.match(/^\/([^\/]+)\//)[1].downcase.intern rescue nil
78       case type
79       when :title
80         results << info_title(sr)
81       when :name
82         results << info_name(sr)
83       else
84         results << "#{sr}"
85       end
86     }
87     return results
88   end
89
90   def grab_info(info, body)
91     /<div class="info">\s+<h5>#{info}:<\/h5>\s+(.*?)<\/div>/mi.match(body)[1] rescue nil
92   end
93
94   def fix_article(org_tit)
95     title = org_tit.dup
96     if @bot.config['imdb.fix_article'] and title.gsub!(FINAL_ARTICLE_MATCH, '')
97       art = $1.dup
98       debug art.inspect
99       if art[-1,1].match(/[a-z]/)
100         art << " "
101       end
102       return art + title
103     end
104     return title
105   end
106
107   def info_title(sr)
108     resp = nil
109     begin
110       resp = @bot.httputil.get_response(IMDB + sr, :max_redir => -1)
111     rescue Exception => e
112       error e.message
113       warning e.backtrace.join("\n")
114       return nil
115     end
116
117     info = []
118
119     if resp.code == "200"
120       m = /<title>([^<]*)<\/title>/.match(resp.body)
121       return nil if !m
122       title_date = m[1]
123       pre_title, date, extra = title_date.scan(/^(.*)\((\d\d\d\d(?:\/[IV]+)?)\)\s*(.+)?$/).first
124       pre_title.strip!
125       title = fix_article(pre_title)
126
127       dir = nil
128       data = grab_info(/Directors?/, resp.body)
129       if data
130         dir = data.scan(NAME_MATCH).map { |url, name|
131           name
132         }.join(', ')
133       end
134
135       country = nil
136       data = grab_info(/Country/, resp.body)
137       if data
138         country = data.ircify_html
139       end
140
141       info << [title, "(#{country}, #{date})", extra, dir ? "[#{dir}]" : nil, ": http://us.imdb.com#{sr}"].compact.join(" ")
142
143       ratings = "no votes"
144       m = /<b>([0-9.]+)\/10<\/b>\n?\r?\s+<small>\(<a href="ratings">([0-9,]+) votes?<\/a>\)<\/small>/.match(resp.body)
145       if m
146         ratings = "#{m[1]}/10 (#{m[2]} voters)"
147       end
148
149       genre = Array.new
150       resp.body.scan(/<a href="\/Sections\/Genres\/[^\/]+\/">([^<]+)<\/a>/) do |gnr|
151         genre << gnr
152       end
153
154       plot = nil
155       data = grab_info(/Plot (?:Outline|Summary)/, resp.body)
156       if data
157         plot = "Plot: " + data.ircify_html.gsub(/\s+more$/,'')
158       end
159
160       info << ["Ratings: " << ratings, "Genre: " << genre.join('/') , plot].compact.join(". ")
161
162       return info
163     end
164     return nil
165   end
166
167   def info_name(sr)
168     resp = nil
169     begin
170       resp = @bot.httputil.get_response(IMDB + sr, :max_redir => -1)
171     rescue Exception => e
172       error e.message
173       warning e.backtrace.join("\n")
174       return nil
175     end
176
177     info = []
178
179     if resp.code == "200"
180       m = /<title>([^<]*)<\/title>/.match(resp.body)
181       return nil if !m
182       name = m[1]
183
184       info << "#{name} : http://us.imdb.com#{sr}"
185
186       birth = nil
187       data = grab_info("Date of Birth", resp.body)
188       if data
189         birth = "Birth: #{data.ircify_html.gsub(/\s+more$/,'')}"
190       end
191
192       death = nil
193       data = grab_info("Date of Death", resp.body)
194       if data
195         death = "Death: #{data.ircify_html.gsub(/\s+more$/,'')}"
196       end
197
198       info << [birth, death].compact.join('. ') if birth or death
199
200       movies = {}
201
202       filmorate = nil
203       begin
204         filmorate = @bot.httputil.get(IMDB + sr + "filmorate")
205       rescue Exception
206       end
207
208       if filmorate
209         filmorate.scan(/<div class="filmo">.*?<a href="\/title.*?<\/div>/m) { |str|
210           what = str.match(/<a name="[^"]+">([^<]+)<\/a>/)[1] rescue nil
211           next unless what
212           movies[what] = str.scan(TITLE_MATCH)[0..2].map { |url, tit|
213             fix_article(tit)
214           }
215         }
216       end
217
218       preferred = ['Actor', 'Director']
219       if resp.body.match(/Jump to filmography as:&nbsp;(.*?)<\/div>/)
220         txt = $1
221         preferred = txt.scan(/<a[^>]+>([^<]+)<\/a>/)[0..2].map { |pref|
222           pref.first
223         }
224       end
225
226       unless movies.empty?
227         all_keys = movies.keys.sort
228         debug all_keys.inspect
229         keys = []
230         preferred.each { |key|
231           keys << key if all_keys.include? key
232         }
233         keys = all_keys if keys.empty?
234         ar = []
235         keys.each { |key|
236           ar << key.dup
237           ar.last << ": " + movies[key].join('; ')
238         }
239         info << ar.join('. ')
240       end
241       return info
242
243     end
244     return nil
245   end
246 end
247
248 class ImdbPlugin < Plugin
249   BotConfig.register BotConfigBooleanValue.new('imdb.aka',
250     :default => true,
251     :desc => "Look for IMDB matches also in translated titles and other 'also known as' information")
252   BotConfig.register BotConfigBooleanValue.new('imdb.popular',
253     :default => true,
254     :desc => "Display info on popular IMDB entries matching the request closely")
255   BotConfig.register BotConfigBooleanValue.new('imdb.exact',
256     :default => true,
257     :desc => "Display info on IMDB entries matching the request exactly")
258   BotConfig.register BotConfigBooleanValue.new('imdb.fix_article',
259     :default => false,
260     :desc => "Try to detect an article placed at the end and move it in front of the title")
261
262   def help(plugin, topic="")
263     "imdb <string> => search http://www.imdb.org for <string>"
264   end
265
266   def imdb(m, params)
267     what = params[:what].to_s
268     i = Imdb.new(@bot)
269     info = i.info(what)
270     if !info
271       m.reply "Nothing found for #{what}"
272       return nil
273     end
274     if info.length == 1
275       m.reply Utils.decode_html_entities info.first.join("\n")
276     else
277       m.reply info.map { |i|
278         Utils.decode_html_entities i.join(" | ")
279       }.join("\n")
280     end
281   end
282 end
283
284 plugin = ImdbPlugin.new
285 plugin.map "imdb *what"
286