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