]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - data/rbot/plugins/search.rb
search: fix gcalc
[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="")?>.*?<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     splits = html.split(/\s*<br\/>\s*/)
187     candidates = splits.select { |section| section.include? ' = ' }
188     debug "candidates: #{candidates.inspect}"
189
190     if candidates.empty?
191       m.reply "couldn't calculate #{what}"
192       return
193     end
194     result = candidates.first
195
196     debug "replying with: #{result.inspect}"
197     m.reply result.ircify_html
198   end
199
200   def gcount(m, params)
201     what = params[:words].to_s
202     searchfor = CGI.escape(what)
203
204     debug "Getting gcount thing: #{searchfor.inspect}"
205     url = GOOGLE_SEARCH + searchfor
206
207     begin
208       html = @bot.httputil.get(url)
209     rescue => e
210       m.reply "error googlecounting #{what}"
211       return
212     end
213
214     debug "#{html.size} bytes of html recieved"
215
216     results = html.scan(GOOGLE_COUNT_RESULT)
217     debug "results: #{results.inspect}"
218
219     if results.length != 1
220       m.reply "couldn't count #{what}"
221       return
222     end
223
224     result = results[0][0].ircify_html
225     debug "replying with: #{result.inspect}"
226     m.reply "total results: #{result}"
227
228   end
229
230   def gdef(m, params)
231     what = params[:words].to_s
232     searchfor = CGI.escape("define " + what)
233
234     debug "Getting gdef thing: #{searchfor.inspect}"
235     url = GOOGLE_WAP_SEARCH + searchfor
236
237     begin
238       html = @bot.httputil.get(url)
239     rescue => e
240       m.reply "error googledefining #{what}"
241       return
242     end
243
244     debug html
245     results = html.scan(GOOGLE_DEF_RESULT)
246     debug "results: #{results.inspect}"
247
248     if results.length != 1
249       m.reply "couldn't find a definition for #{what} on Google"
250       return
251     end
252
253     gdef_link = "http://www.google.com" + CGI.unescapeHTML(results[0][2]) # could be used to extract all defs
254     head = results[0][0].ircify_html
255     text = results[0][1].ircify_html
256     m.reply "#{head} -- #{text}"
257
258     ### gdef_link could be used for something like
259     # html_defs = @bot.httputil.get(gdef_link)
260     # related_index = html_defs.index(/Related phrases:/, 0)
261     # defs_index = html_defs.index(/Definitions of <b>/, related_index)
262
263     # related = html_defs[related_index..defs_index]
264     # defs = html_defs[defs_index..-1]
265
266     # m.reply defs.gsub('  <br/>','<li>').ircify_html
267   end
268
269   def wikipedia(m, params)
270     lang = params[:lang]
271     site = "#{lang.nil? ? '' : lang + '.'}wikipedia.org"
272     debug "Looking up things on #{site}"
273     params[:site] = site
274     params[:filter] = / - Wikipedia.*$/
275     params[:hits] = @bot.config['wikipedia.hits']
276     params[:firstpar] = @bot.config['wikipedia.first_par']
277     return google(m, params)
278   end
279
280   def unpedia(m, params)
281     site = "uncyclopedia.org"
282     debug "Looking up things on #{site}"
283     params[:site] = site
284     params[:filter] = / - Uncyclopedia.*$/
285     params[:hits] = @bot.config['wikipedia.hits']
286     params[:firstpar] = @bot.config['wikipedia.first_par']
287     return google(m, params)
288   end
289
290   def gtime(m, params)
291     where = params[:words].to_s
292     where.sub!(/^\s*in\s*/, '')
293     searchfor = CGI.escape("time in " + where)
294     url = GOOGLE_SEARCH + searchfor
295
296     begin
297       html = @bot.httputil.get(url)
298     rescue => e
299       m.reply "Error googletiming #{where}"
300       return
301     end
302
303     debug html
304     results = html.scan(GOOGLE_TIME_RESULT)
305     debug "results: #{results.inspect}"
306
307     if results.length != 1
308       m.reply "Couldn't find the time for #{where} on Google"
309       return
310     end
311
312     time = results[0][0].ircify_html
313     m.reply "#{time}"
314   end
315 end
316
317 plugin = SearchPlugin.new
318
319 plugin.map "search *words", :action => 'google', :threaded => true
320 plugin.map "google *words", :action => 'google', :threaded => true
321 plugin.map "lucky *words", :action => 'lucky', :threaded => true
322 plugin.map "gcount *words", :action => 'gcount', :threaded => true
323 plugin.map "gcalc *words", :action => 'gcalc', :threaded => true
324 plugin.map "gdef *words", :action => 'gdef', :threaded => true
325 plugin.map "gtime *words", :action => 'gtime', :threaded => true
326 plugin.map "wp :lang *words", :action => 'wikipedia', :requirements => { :lang => /^\w\w\w?$/ }, :threaded => true
327 plugin.map "wp *words", :action => 'wikipedia', :threaded => true
328 plugin.map "unpedia *words", :action => 'unpedia', :threaded => true
329