2
# Copyright (c) 2002-2004, Jeremiah Fincher
3
# Copyright (c) 2008-2009, James Vega
6
# Redistribution and use in source and binary forms, with or without
7
# modification, are permitted provided that the following conditions are met:
9
# * Redistributions of source code must retain the above copyright notice,
10
# this list of conditions, and the following disclaimer.
11
# * Redistributions in binary form must reproduce the above copyright notice,
12
# this list of conditions, and the following disclaimer in the
13
# documentation and/or other materials provided with the distribution.
14
# * Neither the name of the author of this software nor the name of
15
# contributors to this software may be used to endorse or promote products
16
# derived from this software without specific prior written consent.
18
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
# POSSIBILITY OF SUCH DAMAGE.
37
import supybot.conf as conf
38
import supybot.utils as utils
39
import supybot.world as world
40
from supybot.commands import *
41
import supybot.ircmsgs as ircmsgs
42
import supybot.ircutils as ircutils
43
import supybot.callbacks as callbacks
46
simplejson = utils.python.universalImport('json', 'simplejson',
48
# The 3rd party simplejson module was included in Python 2.6 and renamed to
49
# json. Unfortunately, this conflicts with the 3rd party json module.
50
# Luckily, the 3rd party json module has a different interface so we test
51
# to make sure we aren't using it.
52
if hasattr(simplejson, 'read'):
55
raise callbacks.Error, \
56
'You need Python2.6 or the simplejson module installed to use ' \
57
'this plugin. Download the module at ' \
58
'<http://undefined.org/python/#simplejson>.'
60
class Google(callbacks.PluginRegexp):
63
regexps = ['googleSnarfer', 'googleGroups']
66
def _getColorGoogle(self, m):
68
ret = self._colorGoogles.get(s)
71
L[0] = ircutils.mircColor(L[0], 'blue')[:-1]
72
L[1] = ircutils.mircColor(L[1], 'red')[:-1]
73
L[2] = ircutils.mircColor(L[2], 'yellow')[:-1]
74
L[3] = ircutils.mircColor(L[3], 'blue')[:-1]
75
L[4] = ircutils.mircColor(L[4], 'green')[:-1]
76
L[5] = ircutils.mircColor(L[5], 'red')
78
self._colorGoogles[s] = ret
79
return ircutils.bold(ret)
81
_googleRe = re.compile(r'\b(google)\b', re.I)
82
def outFilter(self, irc, msg):
83
if msg.command == 'PRIVMSG' and \
84
self.registryValue('colorfulFilter', msg.args[0]):
86
s = re.sub(self._googleRe, self._getColorGoogle, s)
87
msg = ircmsgs.privmsg(msg.args[0], s, msg=msg)
90
_gsearchUrl = 'http://ajax.googleapis.com/ajax/services/search/web'
91
def search(self, query, channel, options={}):
92
"""Perform a search using Google's AJAX API.
93
search("search phrase", options={})
96
smallsearch - True/False (Default: False)
97
filter - {active,moderate,off} (Default: "moderate")
98
language - Restrict search to documents in the given language
101
ref = self.registryValue('referer')
103
ref = 'http://%s/%s' % (dynamic.irc.server,
105
headers = utils.web.defaultHeaders
106
headers['Referer'] = ref
107
opts = {'q': query, 'v': '1.0'}
108
for (k, v) in options.iteritems():
109
if k == 'smallsearch':
111
opts['rsz'] = 'small'
113
opts['rsz'] = 'large'
116
elif k == 'language':
118
defLang = self.registryValue('defaultLanguage', channel)
119
if 'lr' not in opts and defLang:
121
if 'safe' not in opts:
122
opts['safe'] = self.registryValue('searchFilter', dynamic.channel)
123
if 'rsz' not in opts:
124
opts['rsz'] = 'large'
126
fd = utils.web.getUrlFd('%s?%s' % (self._gsearchUrl,
127
urllib.urlencode(opts)),
129
json = simplejson.load(fd)
131
if json['responseStatus'] != 200:
132
raise callbacks.Error, 'We broke The Google!'
135
def formatData(self, data, bold=True, max=0):
136
if isinstance(data, basestring):
142
title = utils.web.htmlToText(result['titleNoFormatting']\
144
url = result['unescapedUrl'].encode('utf-8')
147
return format('No matches found.')
149
return format(' | '.join(results))
151
def lucky(self, irc, msg, args, text):
154
Does a google search, but only returns the first result.
156
data = self.search(text, msg.args[0], {'smallsearch': True})
157
if data['responseData']['results']:
158
url = data['responseData']['results'][0]['unescapedUrl']
159
irc.reply(url.encode('utf-8'))
161
irc.reply('Google found nothing.')
162
lucky = wrap(lucky, ['text'])
164
def google(self, irc, msg, args, optlist, text):
165
"""<search> [--{filter,language} <value>]
167
Searches google.com for the given string. As many results as can fit
168
are included. --language accepts a language abbreviation; --filter
169
accepts a filtering level ('active', 'moderate', 'off').
171
if 'language' in optlist and optlist['language'].lower() not in \
172
conf.supybot.plugins.Google.safesearch.validStrings:
173
irc.errorInvalid('language')
174
data = self.search(text, msg.args[0], dict(optlist))
175
if data['responseStatus'] != 200:
176
irc.reply('We broke The Google!')
178
bold = self.registryValue('bold', msg.args[0])
179
max = self.registryValue('maximumResults', msg.args[0])
180
irc.reply(self.formatData(data['responseData']['results'],
182
google = wrap(google, [getopts({'language':'something',
186
def cache(self, irc, msg, args, url):
189
Returns a link to the cached version of <url> if it is available.
191
data = self.search(url, msg.args[0], {'smallsearch': True})
192
if data['responseData']['results']:
193
m = data['responseData']['results'][0]
195
url = m['cacheUrl'].encode('utf-8')
198
irc.error('Google seems to have no cache for that site.')
199
cache = wrap(cache, ['url'])
201
def fight(self, irc, msg, args):
202
"""<search string> <search string> [<search string> ...]
204
Returns the results of each search, in order, from greatest number
207
channel = msg.args[0]
210
data = self.search(arg, channel, {'smallsearch': True})
211
count = data['responseData']['cursor']['estimatedResultCount']
212
results.append((int(count), arg))
215
if self.registryValue('bold', msg.args[0]):
219
s = ', '.join([format('%s: %i', bold(s), i) for (i, s) in results])
222
_gtranslateUrl='http://ajax.googleapis.com/ajax/services/language/translate'
223
_transLangs = {'Arabic': 'ar', 'Bulgarian': 'bg',
224
'Chinese_simplified': 'zh-CN',
225
'Chinese_traditional': 'zh-TW', 'Croatian': 'hr',
226
'Czech': 'cs', 'Danish': 'da', 'Dutch': 'nl',
227
'English': 'en', 'Finnish': 'fi', 'French': 'fr',
228
'German': 'de', 'Greek': 'el', 'Hindi': 'hi',
229
'Italian': 'it', 'Japanese': 'ja', 'Korean': 'ko',
230
'Norwegian': 'no', 'Polish': 'pl', 'Portuguese': 'pt',
231
'Romanian': 'ro', 'Russian': 'ru', 'Spanish': 'es',
233
def translate(self, irc, msg, args, fromLang, toLang, text):
234
"""<from-language> [to] <to-language> <text>
236
Returns <text> translated from <from-language> into <to-language>.
237
Beware that translating to or from languages that use multi-byte
238
characters may result in some very odd results.
240
channel = msg.args[0]
241
ref = self.registryValue('referer')
243
ref = 'http://%s/%s' % (dynamic.irc.server,
245
headers = utils.web.defaultHeaders
246
headers['Referer'] = ref
247
opts = {'q': text, 'v': '1.0'}
248
lang = conf.supybot.plugins.Google.defaultLanguage
249
if fromLang.capitalize() in self._transLangs:
250
fromLang = self._transLangs[fromLang.capitalize()]
251
elif lang.normalize('lang_'+fromLang)[5:] \
252
not in self._transLangs.values():
253
irc.errorInvalid('from language', fromLang,
254
format('Valid languages are: %L',
255
self._transLangs.keys()))
257
fromLang = lang.normalize('lang_'+fromLang)[5:]
258
if toLang.capitalize() in self._transLangs:
259
toLang = self._transLangs[toLang.capitalize()]
260
elif lang.normalize('lang_'+toLang)[5:] \
261
not in self._transLangs.values():
262
irc.errorInvalid('to language', toLang,
263
format('Valid languages are: %L',
264
self._transLangs.keys()))
266
toLang = lang.normalize('lang_'+toLang)[5:]
267
opts['langpair'] = '%s|%s' % (fromLang, toLang)
268
fd = utils.web.getUrlFd('%s?%s' % (self._gtranslateUrl,
269
urllib.urlencode(opts)),
271
json = simplejson.load(fd)
273
if json['responseStatus'] != 200:
274
raise callbacks.Error, 'We broke The Google!'
275
irc.reply(json['responseData']['translatedText'].encode('utf-8'))
276
translate = wrap(translate, ['something', 'to', 'something', 'text'])
278
def googleSnarfer(self, irc, msg, match):
280
if not self.registryValue('searchSnarfer', msg.args[0]):
282
searchString = match.group(1)
283
data = self.search(searchString, msg.args[0], {'smallsearch': True})
284
if data['responseData']['results']:
285
url = data['responseData']['results'][0]['unescapedUrl']
286
irc.reply(url.encode('utf-8'), prefixNick=False)
287
googleSnarfer = urlSnarfer(googleSnarfer)
289
_ggThread = re.compile(r'Subject: <b>([^<]+)</b>', re.I)
290
_ggGroup = re.compile(r'<TITLE>Google Groups :\s*([^<]+)</TITLE>', re.I)
291
_ggThreadm = re.compile(r'src="(/group[^"]+)">', re.I)
292
_ggSelm = re.compile(r'selm=[^&]+', re.I)
293
_threadmThread = re.compile(r'TITLE="([^"]+)">', re.I)
294
_threadmGroup = re.compile(r'class=groupname[^>]+>([^<]+)<', re.I)
295
def googleGroups(self, irc, msg, match):
296
r"http://groups.google.[\w.]+/\S+\?(\S+)"
297
if not self.registryValue('groupsSnarfer', msg.args[0]):
299
queries = cgi.parse_qsl(match.group(1))
300
queries = [q for q in queries if q[0] in ('threadm', 'selm')]
303
queries.append(('hl', 'en'))
304
url = 'http://groups.google.com/groups?' + urllib.urlencode(queries)
305
text = utils.web.getUrl(url)
308
if 'threadm=' in url:
309
path = self._ggThreadm.search(text)
311
url = 'http://groups-beta.google.com' + path.group(1)
312
text = utils.web.getUrl(url)
313
mThread = self._threadmThread.search(text)
314
mGroup = self._threadmGroup.search(text)
316
mThread = self._ggThread.search(text)
317
mGroup = self._ggGroup.search(text)
318
if mThread and mGroup:
319
irc.reply(format('Google Groups: %s, %s',
320
mGroup.group(1), mThread.group(1)),
323
self.log.debug('Unable to snarf. %s doesn\'t appear to be a '
324
'proper Google Groups page.', match.group(1))
325
googleGroups = urlSnarfer(googleGroups)
327
def _googleUrl(self, s):
328
s = s.replace('+', '%2B')
329
s = s.replace(' ', '+')
330
url = r'http://google.com/search?q=' + s
333
_calcRe = re.compile(r'<img src=/images/calc_img\.gif.*?<b>(.*?)</b>', re.I)
334
_calcSupRe = re.compile(r'<sup>(.*?)</sup>', re.I)
335
_calcFontRe = re.compile(r'<font size=-2>(.*?)</font>')
336
_calcTimesRe = re.compile(r'&(?:times|#215);')
337
def calc(self, irc, msg, args, expr):
340
Uses Google's calculator to calculate the value of <expression>.
342
url = self._googleUrl(expr)
343
html = utils.web.getUrl(url)
344
match = self._calcRe.search(html)
345
if match is not None:
347
s = self._calcSupRe.sub(r'^(\1)', s)
348
s = self._calcFontRe.sub(r',', s)
349
s = self._calcTimesRe.sub(r'*', s)
352
irc.reply('Google\'s calculator didn\'t come up with anything.')
353
calc = wrap(calc, ['text'])
355
_phoneRe = re.compile(r'Phonebook.*?<font size=-1>(.*?)<a href')
356
def phonebook(self, irc, msg, args, phonenumber):
359
Looks <phone number> up on Google.
361
url = self._googleUrl(phonenumber)
362
html = utils.web.getUrl(url)
363
m = self._phoneRe.search(html)
366
s = s.replace('<b>', '')
367
s = s.replace('</b>', '')
368
s = utils.web.htmlToText(s)
371
irc.reply('Google\'s phonebook didn\'t come up with anything.')
372
phonebook = wrap(phonebook, ['text'])
378
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: