search: update gdef to changes in web result
[rbot] / data / rbot / plugins / search.rb
1 #-- vim:sw=2:et
2 #++
3 #
4 # :title: Google and Wikipedia search plugin for rbot
5 #
6 # Author:: Tom Gilbert (giblet) <tom@linuxbrit.co.uk>
7 # Author:: Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>
8 #
9 # Copyright:: (C) 2002-2005 Tom Gilbert
10 # Copyright:: (C) 2006 Tom Gilbert, Giuseppe Bilotta
11 # Copyright:: (C) 2006-2007 Giuseppe Bilotta
12
13 # TODO:: use lr=lang_<code> or whatever is most appropriate to let google know
14 #        it shouldn't use the bot's location to find the preferred language
15 # TODO:: support localized uncyclopedias -- not easy because they have different names
16 #        for most languages
17
18 GOOGLE_SEARCH = "http://www.google.com/search?oe=UTF-8&q="
19 GOOGLE_WAP_SEARCH = "http://www.google.com/m/search?hl=en&q="
20 # GOOGLE_WAP_LINK = /<a accesskey="(\d)" href=".*?u=(.*?)">(.*?)<\/a>/im
21 GOOGLE_WAP_LINK = /<a href="(?:.*?u=(.*?)|(http:\/\/.*?))">(.*?)<\/a>/im
22 GOOGLE_CALC_RESULT = %r{<img src=/images/calc_img\.gif(?: width=40 height=30 alt="")?>.*?<h[1-6] class=r[^>]*><b>(.+?)</b>}
23 GOOGLE_COUNT_RESULT = %r{<font size=-1>Results <b>1<\/b> - <b>10<\/b> of about <b>(.*)<\/b> for}
24 GOOGLE_DEF_RESULT = %r{<br/>\s*(.*?)\s*<br/>\s*(.*?)<a href="(/dictionary\?[^"]*)"[^>]*>(More ยป)\s*</a>\s*<br/>}
25 GOOGLE_TIME_RESULT = %r{alt="Clock"></td><td valign=[^>]+>(.+?)<(br|/td)>}
26
27 class SearchPlugin < Plugin
28   Config.register Config::IntegerValue.new('google.hits',
29     :default => 3,
30     :desc => "Number of hits to return from Google searches")
31   Config.register Config::IntegerValue.new('google.first_par',
32     :default => 0,
33     :desc => "When set to n > 0, the bot will return the first paragraph from the first n search hits")
34   Config.register Config::IntegerValue.new('wikipedia.hits',
35     :default => 3,
36     :desc => "Number of hits to return from Wikipedia searches")
37   Config.register Config::IntegerValue.new('wikipedia.first_par',
38     :default => 1,
39     :desc => "When set to n > 0, the bot will return the first paragraph from the first n wikipedia search hits")
40
41   def help(plugin, topic="")
42     case topic
43     when "search", "google"
44       "#{topic} <string> => search google for <string>"
45     when "gcalc"
46       "gcalc <equation> => use the google calculator to find the answer to <equation>"
47     when "gdef"
48       "gdef <term(s)> => use the google define mechanism to find a definition of <term(s)>"
49     when "gtime"
50       "gtime <location> => use the google clock to find the current time at <location>"
51     when "wp"
52       "wp [<code>] <string> => search for <string> on Wikipedia. You can select a national <code> to only search the national Wikipedia"
53     when "unpedia"
54       "unpedia <string> => search for <string> on Uncyclopedia"
55     else
56       "search <string> (or: google <string>) => search google for <string> | wp <string> => search for <string> on Wikipedia | unpedia <string> => search for <string> on Uncyclopedia"
57     end
58   end
59
60   def google(m, params)
61     what = params[:words].to_s
62     if what.match(/^define:/)
63       return google_define(m, what, params)
64     end
65
66     searchfor = CGI.escape what
67     # This method is also called by other methods to restrict searching to some sites
68     if params[:site]
69       site = "site:#{params[:site]}+"
70     else
71       site = ""
72     end
73     # It is also possible to choose a filter to remove constant parts from the titles
74     # e.g.: "Wikipedia, the free encyclopedia" when doing Wikipedia searches
75     filter = params[:filter] || ""
76
77     url = GOOGLE_WAP_SEARCH + site + searchfor
78
79     hits = params[:hits] || @bot.config['google.hits']
80     hits = 1 if params[:lucky]
81
82     first_pars = params[:firstpar] || @bot.config['google.first_par']
83
84     single = params[:lucky] || (hits == 1 and first_pars == 1)
85
86     begin
87       wml = @bot.httputil.get(url)
88       raise unless wml
89     rescue => e
90       m.reply "error googling for #{what}"
91       return
92     end
93     results = wml.match('<p align="center">').pre_match.scan(GOOGLE_WAP_LINK)
94
95     if results.length == 0
96       m.reply "no results found for #{what}"
97       return
98     end
99
100     single ||= (results.length==1)
101
102     urls = Array.new
103     n = 0
104     results = results[0...hits].map { |res|
105       n += 1
106       t = res[2].ircify_html(:img => "[%{src} %{alt} %{dimensions}]").strip
107       u = URI.unescape(res[0] || res[1])
108       urls.push(u)
109       "%{n}%{b}%{t}%{b}%{sep}%{u}" % {
110         :n => (single ? "" : "#{n}. "),
111         :sep => (single ? " -- " : ": "),
112         :b => Bold, :t => t, :u => u
113       }
114     }
115
116     if params[:lucky]
117       m.reply results.first
118       return
119     end
120
121     result_string = results.join(" | ")
122
123     # If we return a single, full result, change the output to a more compact representation
124     if single
125       m.reply "Result for %s: %s -- %s" % [what, result_string, Utils.get_first_pars(urls, first_pars)], :overlong => :truncate
126       return
127     end
128
129     m.reply "Results for #{what}: #{result_string}", :split_at => /\s+\|\s+/
130
131     return unless first_pars > 0
132
133     Utils.get_first_pars urls, first_pars, :message => m
134
135   end
136
137   def google_define(m, what, params)
138     begin
139       wml = @bot.httputil.get(GOOGLE_SEARCH + CGI.escape(what))
140       raise unless wml
141     rescue => e
142       m.reply "error googling for #{what}"
143       return
144     end
145
146     begin
147       related_index = wml.index(/Related phrases:/, 0)
148       raise unless related_index
149       defs_index = wml.index(/Definitions of <b>/, related_index)
150       raise unless defs_index
151       defs_end = wml.index(/<input/, defs_index)
152       raise unless defs_end
153     rescue => e
154       m.reply "no results found for #{what}"
155       return
156     end
157
158     related = wml[related_index...defs_index]
159     defs = wml[defs_index...defs_end]
160
161     m.reply defs.ircify_html(:a_href => Underline), :split_at => (Underline + ' ')
162
163   end
164
165   def lucky(m, params)
166     params.merge!(:lucky => true)
167     google(m, params)
168   end
169
170   def gcalc(m, params)
171     what = params[:words].to_s
172     searchfor = CGI.escape(what)
173
174     debug "Getting gcalc thing: #{searchfor.inspect}"
175     url = GOOGLE_WAP_SEARCH + searchfor
176
177     begin
178       html = @bot.httputil.get(url)
179     rescue => e
180       m.reply "error googlecalcing #{what}"
181       return
182     end
183
184     debug "#{html.size} bytes of html recieved"
185
186     intro, result, junk = html.split(/\s*<br\/>\s*/, 3)
187     debug "result: #{result.inspect}"
188
189     unless result.include? '='
190       m.reply "couldn't calculate #{what}"
191       return
192     end
193
194     debug "replying with: #{result.inspect}"
195     m.reply result.ircify_html
196   end
197
198   def gcount(m, params)
199     what = params[:words].to_s
200     searchfor = CGI.escape(what)
201
202     debug "Getting gcount thing: #{searchfor.inspect}"
203     url = GOOGLE_SEARCH + searchfor
204
205     begin
206       html = @bot.httputil.get(url)
207     rescue => e
208       m.reply "error googlecounting #{what}"
209       return
210     end
211
212     debug "#{html.size} bytes of html recieved"
213
214     results = html.scan(GOOGLE_COUNT_RESULT)
215     debug "results: #{results.inspect}"
216
217     if results.length != 1
218       m.reply "couldn't count #{what}"
219       return
220     end
221
222     result = results[0][0].ircify_html
223     debug "replying with: #{result.inspect}"
224     m.reply "total results: #{result}"
225
226   end
227
228   def gdef(m, params)
229     what = params[:words].to_s
230     searchfor = CGI.escape("define " + what)
231
232     debug "Getting gdef thing: #{searchfor.inspect}"
233     url = GOOGLE_WAP_SEARCH + searchfor
234
235     begin
236       html = @bot.httputil.get(url)
237     rescue => e
238       m.reply "error googledefining #{what}"
239       return
240     end
241
242     debug html
243     results = html.scan(GOOGLE_DEF_RESULT)
244     debug "results: #{results.inspect}"
245
246     if results.length != 1
247       m.reply "couldn't find a definition for #{what} on Google"
248       return
249     end
250
251     gdef_link = "http://www.google.com" + CGI.unescapeHTML(results[0][2]) # could be used to extract all defs
252     head = results[0][0].ircify_html
253     text = results[0][1].ircify_html
254     m.reply "#{head} -- #{text}"
255
256     ### gdef_link could be used for something like
257     # html_defs = @bot.httputil.get(gdef_link)
258     # related_index = html_defs.index(/Related phrases:/, 0)
259     # defs_index = html_defs.index(/Definitions of <b>/, related_index)
260
261     # related = html_defs[related_index..defs_index]
262     # defs = html_defs[defs_index..-1]
263
264     # m.reply defs.gsub('  <br/>','<li>').ircify_html
265   end
266
267   def wikipedia(m, params)
268     lang = params[:lang]
269     site = "#{lang.nil? ? '' : lang + '.'}wikipedia.org"
270     debug "Looking up things on #{site}"
271     params[:site] = site
272     params[:filter] = / - Wikipedia.*$/
273     params[:hits] = @bot.config['wikipedia.hits']
274     params[:firstpar] = @bot.config['wikipedia.first_par']
275     return google(m, params)
276   end
277
278   def unpedia(m, params)
279     site = "uncyclopedia.org"
280     debug "Looking up things on #{site}"
281     params[:site] = site
282     params[:filter] = / - Uncyclopedia.*$/
283     params[:hits] = @bot.config['wikipedia.hits']
284     params[:firstpar] = @bot.config['wikipedia.first_par']
285     return google(m, params)
286   end
287
288   def gtime(m, params)
289     where = params[:words].to_s
290     where.sub!(/^\s*in\s*/, '')
291     searchfor = CGI.escape("time in " + where)
292     url = GOOGLE_SEARCH + searchfor
293
294     begin
295       html = @bot.httputil.get(url)
296     rescue => e
297       m.reply "Error googletiming #{where}"
298       return
299     end
300
301     debug html
302     results = html.scan(GOOGLE_TIME_RESULT)
303     debug "results: #{results.inspect}"
304
305     if results.length != 1
306       m.reply "Couldn't find the time for #{where} on Google"
307       return
308     end
309
310     time = results[0][0].ircify_html
311     m.reply "#{time}"
312   end
313 end
314
315 plugin = SearchPlugin.new
316
317 plugin.map "search *words", :action => 'google', :threaded => true
318 plugin.map "google *words", :action => 'google', :threaded => true
319 plugin.map "lucky *words", :action => 'lucky', :threaded => true
320 plugin.map "gcount *words", :action => 'gcount', :threaded => true
321 plugin.map "gcalc *words", :action => 'gcalc', :threaded => true
322 plugin.map "gdef *words", :action => 'gdef', :threaded => true
323 plugin.map "gtime *words", :action => 'gtime', :threaded => true
324 plugin.map "wp :lang *words", :action => 'wikipedia', :requirements => { :lang => /^\w\w\w?$/ }, :threaded => true
325 plugin.map "wp *words", :action => 'wikipedia', :threaded => true
326 plugin.map "unpedia *words", :action => 'unpedia', :threaded => true
327