]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - data/rbot/plugins/search.rb
search: show long definitions with 'google define:stuff'
[user/henk/code/ruby/rbot.git] / 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="")?>.*?<h2 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{<a href="([^"]*)"[^>]*>(Web definitions for .*?)<br/>(.*?)<br/>(.*?)\s-\s+<a href}
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.scan(GOOGLE_WAP_LINK)
94     if results.length == 0
95       m.reply "no results found for #{what}"
96       return
97     end
98     single ||= (results.length==1)
99     urls = Array.new
100     n = 0
101     results = results[0...hits].map { |res|
102       n += 1
103       t = Utils.decode_html_entities res[2].gsub(filter, '').strip
104       u = URI.unescape(res[0] || res[1])
105       urls.push(u)
106       "%{n}%{b}%{t}%{b}%{sep}%{u}" % {
107         :n => (single ? "" : "#{n}. "),
108         :sep => (single ? " -- " : ": "),
109         :b => Bold, :t => t, :u => u
110       }
111     }.join(" | ")
112
113     if params[:lucky]
114       m.reply results.first
115       return
116     end
117
118     # If we return a single, full result, change the output to a more compact representation
119     if single
120       m.reply "Result for %s: %s -- %s" % [what, results, Utils.get_first_pars(urls, first_pars)], :overlong => :truncate
121       return
122     end
123
124     m.reply "Results for #{what}: #{results}", :split_at => /\s+\|\s+/
125
126     return unless first_pars > 0
127
128     Utils.get_first_pars urls, first_pars, :message => m
129
130   end
131
132   def google_define(m, what, params)
133     begin
134       wml = @bot.httputil.get(GOOGLE_SEARCH + CGI.escape(what))
135       raise unless wml
136     rescue => e
137       m.reply "error googling for #{what}"
138       return
139     end
140
141     begin
142       related_index = wml.index(/Related phrases:/, 0)
143       raise unless related_index
144       defs_index = wml.index(/Definitions of <b>/, related_index)
145       raise unless defs_index
146       defs_end = wml.index(/<input/, defs_index)
147       raise unless defs_end
148     rescue => e
149       m.reply "no results found for #{what}"
150       return
151     end
152
153     related = wml[related_index...defs_index]
154     defs = wml[defs_index...defs_end]
155
156     m.reply defs.ircify_html(:a_href => Underline), :split_at => (Underline + ' ')
157
158   end
159
160   def lucky(m, params)
161     params.merge!(:lucky => true)
162     google(m, params)
163   end
164
165   def gcalc(m, params)
166     what = params[:words].to_s
167     searchfor = CGI.escape(what)
168
169     debug "Getting gcalc thing: #{searchfor.inspect}"
170     url = GOOGLE_SEARCH + searchfor
171
172     begin
173       html = @bot.httputil.get(url)
174     rescue => e
175       m.reply "error googlecalcing #{what}"
176       return
177     end
178
179     debug "#{html.size} bytes of html recieved"
180
181     results = html.scan(GOOGLE_CALC_RESULT)
182     debug "results: #{results.inspect}"
183
184     if results.length != 1
185       m.reply "couldn't calculate #{what}"
186       return
187     end
188
189     result = results[0][0].ircify_html
190     debug "replying with: #{result.inspect}"
191     m.reply "#{result}"
192   end
193
194   def gcount(m, params)
195     what = params[:words].to_s
196     searchfor = CGI.escape(what)
197
198     debug "Getting gcount thing: #{searchfor.inspect}"
199     url = GOOGLE_SEARCH + searchfor
200
201     begin
202       html = @bot.httputil.get(url)
203     rescue => e
204       m.reply "error googlecounting #{what}"
205       return
206     end
207
208     debug "#{html.size} bytes of html recieved"
209
210     results = html.scan(GOOGLE_COUNT_RESULT)
211     debug "results: #{results.inspect}"
212
213     if results.length != 1
214       m.reply "couldn't count #{what}"
215       return
216     end
217
218     result = results[0][0].ircify_html
219     debug "replying with: #{result.inspect}"
220     m.reply "total results: #{result}"
221
222   end
223
224   def gdef(m, params)
225     what = params[:words].to_s
226     searchfor = CGI.escape("define " + what)
227
228     debug "Getting gdef thing: #{searchfor.inspect}"
229     url = GOOGLE_WAP_SEARCH + searchfor
230
231     begin
232       html = @bot.httputil.get(url)
233     rescue => e
234       m.reply "error googledefining #{what}"
235       return
236     end
237
238     debug html
239     results = html.scan(GOOGLE_DEF_RESULT)
240     debug "results: #{results.inspect}"
241
242     if results.length != 1
243       m.reply "couldn't find a definition for #{what} on Google"
244       return
245     end
246
247     gdef_link = "http://www.google.com" + CGI.unescapeHTML(results[0][0]) # could be used to extract all defs
248     head = results[0][1].ircify_html
249     text = results[0][2].ircify_html
250     link = results[0][3]
251     m.reply "#{head} -- #{link}\n#{text}"
252
253     ### gdef_link could be used for something like
254     # html_defs = @bot.httputil.get(gdef_link)
255     # related_index = html_defs.index(/Related phrases:/, 0)
256     # defs_index = html_defs.index(/Definitions of <b>/, related_index)
257
258     # related = html_defs[related_index..defs_index]
259     # defs = html_defs[defs_index..-1]
260
261     # m.reply defs.gsub('  <br/>','<li>').ircify_html
262   end
263
264   def wikipedia(m, params)
265     lang = params[:lang]
266     site = "#{lang.nil? ? '' : lang + '.'}wikipedia.org"
267     debug "Looking up things on #{site}"
268     params[:site] = site
269     params[:filter] = / - Wikipedia.*$/
270     params[:hits] = @bot.config['wikipedia.hits']
271     params[:firstpar] = @bot.config['wikipedia.first_par']
272     return google(m, params)
273   end
274
275   def unpedia(m, params)
276     site = "uncyclopedia.org"
277     debug "Looking up things on #{site}"
278     params[:site] = site
279     params[:filter] = / - Uncyclopedia.*$/
280     params[:hits] = @bot.config['wikipedia.hits']
281     params[:firstpar] = @bot.config['wikipedia.first_par']
282     return google(m, params)
283   end
284
285   def gtime(m, params)
286     where = params[:words].to_s
287     where.sub!(/^\s*in\s*/, '')
288     searchfor = CGI.escape("time in " + where)
289     url = GOOGLE_SEARCH + searchfor
290
291     begin
292       html = @bot.httputil.get(url)
293     rescue => e
294       m.reply "Error googletiming #{where}"
295       return
296     end
297
298     debug html
299     results = html.scan(GOOGLE_TIME_RESULT)
300     debug "results: #{results.inspect}"
301
302     if results.length != 1
303       m.reply "Couldn't find the time for #{where} on Google"
304       return
305     end
306
307     time = results[0][0].ircify_html
308     m.reply "#{time}"
309   end
310 end
311
312 plugin = SearchPlugin.new
313
314 plugin.map "search *words", :action => 'google', :threaded => true
315 plugin.map "google *words", :action => 'google', :threaded => true
316 plugin.map "lucky *words", :action => 'lucky', :threaded => true
317 plugin.map "gcount *words", :action => 'gcount', :threaded => true
318 plugin.map "gcalc *words", :action => 'gcalc', :threaded => true
319 plugin.map "gdef *words", :action => 'gdef', :threaded => true
320 plugin.map "gtime *words", :action => 'gtime', :threaded => true
321 plugin.map "wp :lang *words", :action => 'wikipedia', :requirements => { :lang => /^\w\w\w?$/ }, :threaded => true
322 plugin.map "wp *words", :action => 'wikipedia', :threaded => true
323 plugin.map "unpedia *words", :action => 'unpedia', :threaded => true
324