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