~ubuntu-ru-irc/+junk/irckit

« back to all changes in this revision

Viewing changes to ubuntuhelp/ubuntubot/plugins/Google/plugin.py

  • Committer: rmPIC30 at gmail
  • Date: 2010-07-13 09:08:58 UTC
  • Revision ID: rmpic30@gmail.com-20100713090858-w5kkmk093hx38cen
Добавляю бота

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
###
 
2
# Copyright (c) 2002-2004, Jeremiah Fincher
 
3
# Copyright (c) 2008-2009, James Vega
 
4
# All rights reserved.
 
5
#
 
6
# Redistribution and use in source and binary forms, with or without
 
7
# modification, are permitted provided that the following conditions are met:
 
8
#
 
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.
 
17
#
 
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.
 
29
###
 
30
 
 
31
import re
 
32
import cgi
 
33
import time
 
34
import socket
 
35
import urllib
 
36
 
 
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
 
44
 
 
45
try:
 
46
    simplejson = utils.python.universalImport('json', 'simplejson',
 
47
                                              'local.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'):
 
53
        raise ImportError
 
54
except ImportError:
 
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>.'
 
59
 
 
60
class Google(callbacks.PluginRegexp):
 
61
    threaded = True
 
62
    callBefore = ['Web']
 
63
    regexps = ['googleSnarfer', 'googleGroups']
 
64
 
 
65
    _colorGoogles = {}
 
66
    def _getColorGoogle(self, m):
 
67
        s = m.group(1)
 
68
        ret = self._colorGoogles.get(s)
 
69
        if not ret:
 
70
            L = list(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')
 
77
            ret = ''.join(L)
 
78
            self._colorGoogles[s] = ret
 
79
        return ircutils.bold(ret)
 
80
 
 
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]):
 
85
            s = msg.args[1]
 
86
            s = re.sub(self._googleRe, self._getColorGoogle, s)
 
87
            msg = ircmsgs.privmsg(msg.args[0], s, msg=msg)
 
88
        return msg
 
89
 
 
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={})
 
94
 
 
95
        Valid options are:
 
96
            smallsearch - True/False (Default: False)
 
97
            filter - {active,moderate,off} (Default: "moderate")
 
98
            language - Restrict search to documents in the given language
 
99
                       (Default: "lang_en")
 
100
        """
 
101
        ref = self.registryValue('referer')
 
102
        if not ref:
 
103
            ref = 'http://%s/%s' % (dynamic.irc.server,
 
104
                                    dynamic.irc.nick)
 
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':
 
110
                if v:
 
111
                    opts['rsz'] = 'small'
 
112
                else:
 
113
                    opts['rsz'] = 'large'
 
114
            elif k == 'filter':
 
115
                opts['safe'] = v
 
116
            elif k == 'language':
 
117
                opts['lr'] = v
 
118
        defLang = self.registryValue('defaultLanguage', channel)
 
119
        if 'lr' not in opts and defLang:
 
120
            opts['lr'] = 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'
 
125
        
 
126
        fd = utils.web.getUrlFd('%s?%s' % (self._gsearchUrl,
 
127
                                           urllib.urlencode(opts)),
 
128
                                headers)
 
129
        json = simplejson.load(fd)
 
130
        fd.close()
 
131
        if json['responseStatus'] != 200:
 
132
            raise callbacks.Error, 'We broke The Google!'
 
133
        return json
 
134
 
 
135
    def formatData(self, data, bold=True, max=0):
 
136
        if isinstance(data, basestring):
 
137
            return data
 
138
        results = []
 
139
        if max:
 
140
            data = data[:max]
 
141
        for result in data:
 
142
            title = utils.web.htmlToText(result['titleNoFormatting']\
 
143
                                         .encode('utf-8'))
 
144
            url = result['unescapedUrl'].encode('utf-8')
 
145
            results.append(url)
 
146
        if not results:
 
147
            return format('No matches found.')
 
148
        else:
 
149
            return format(' | '.join(results))
 
150
 
 
151
    def lucky(self, irc, msg, args, text):
 
152
        """<search>
 
153
 
 
154
        Does a google search, but only returns the first result.
 
155
        """
 
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'))
 
160
        else:
 
161
            irc.reply('Google found nothing.')
 
162
    lucky = wrap(lucky, ['text'])
 
163
 
 
164
    def google(self, irc, msg, args, optlist, text):
 
165
        """<search> [--{filter,language} <value>]
 
166
 
 
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').
 
170
        """
 
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!')
 
177
            return
 
178
        bold = self.registryValue('bold', msg.args[0])
 
179
        max = self.registryValue('maximumResults', msg.args[0])
 
180
        irc.reply(self.formatData(data['responseData']['results'],
 
181
                                  bold=bold, max=max))
 
182
    google = wrap(google, [getopts({'language':'something',
 
183
                                    'filter':''}),
 
184
                           'text'])
 
185
 
 
186
    def cache(self, irc, msg, args, url):
 
187
        """<url>
 
188
 
 
189
        Returns a link to the cached version of <url> if it is available.
 
190
        """
 
191
        data = self.search(url, msg.args[0], {'smallsearch': True})
 
192
        if data['responseData']['results']:
 
193
            m = data['responseData']['results'][0]
 
194
            if m['cacheUrl']:
 
195
                url = m['cacheUrl'].encode('utf-8')
 
196
                irc.reply(url)
 
197
                return
 
198
        irc.error('Google seems to have no cache for that site.')
 
199
    cache = wrap(cache, ['url'])
 
200
 
 
201
    def fight(self, irc, msg, args):
 
202
        """<search string> <search string> [<search string> ...]
 
203
 
 
204
        Returns the results of each search, in order, from greatest number
 
205
        of results to least.
 
206
        """
 
207
        channel = msg.args[0]
 
208
        results = []
 
209
        for arg in args:
 
210
            data = self.search(arg, channel, {'smallsearch': True})
 
211
            count = data['responseData']['cursor']['estimatedResultCount']
 
212
            results.append((int(count), arg))
 
213
        results.sort()
 
214
        results.reverse()
 
215
        if self.registryValue('bold', msg.args[0]):
 
216
            bold = ircutils.bold
 
217
        else:
 
218
            bold = repr
 
219
        s = ', '.join([format('%s: %i', bold(s), i) for (i, s) in results])
 
220
        irc.reply(s)
 
221
 
 
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',
 
232
                   'Swedish': 'sv'}
 
233
    def translate(self, irc, msg, args, fromLang, toLang, text):
 
234
        """<from-language> [to] <to-language> <text>
 
235
 
 
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.
 
239
        """
 
240
        channel = msg.args[0]
 
241
        ref = self.registryValue('referer')
 
242
        if not ref:
 
243
            ref = 'http://%s/%s' % (dynamic.irc.server,
 
244
                                    dynamic.irc.nick)
 
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()))
 
256
        else:
 
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()))
 
265
        else:
 
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)),
 
270
                                headers)
 
271
        json = simplejson.load(fd)
 
272
        fd.close()
 
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'])
 
277
 
 
278
    def googleSnarfer(self, irc, msg, match):
 
279
        r"^google\s+(.*)$"
 
280
        if not self.registryValue('searchSnarfer', msg.args[0]):
 
281
            return
 
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)
 
288
 
 
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]):
 
298
            return
 
299
        queries = cgi.parse_qsl(match.group(1))
 
300
        queries = [q for q in queries if q[0] in ('threadm', 'selm')]
 
301
        if not queries:
 
302
            return
 
303
        queries.append(('hl', 'en'))
 
304
        url = 'http://groups.google.com/groups?' + urllib.urlencode(queries)
 
305
        text = utils.web.getUrl(url)
 
306
        mThread = None
 
307
        mGroup = None
 
308
        if 'threadm=' in url:
 
309
            path = self._ggThreadm.search(text)
 
310
            if path is not None:
 
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)
 
315
        else:
 
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)),
 
321
                      prefixNick=False)
 
322
        else:
 
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)
 
326
 
 
327
    def _googleUrl(self, s):
 
328
        s = s.replace('+', '%2B')
 
329
        s = s.replace(' ', '+')
 
330
        url = r'http://google.com/search?q=' + s
 
331
        return url
 
332
 
 
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):
 
338
        """<expression>
 
339
 
 
340
        Uses Google's calculator to calculate the value of <expression>.
 
341
        """
 
342
        url = self._googleUrl(expr)
 
343
        html = utils.web.getUrl(url)
 
344
        match = self._calcRe.search(html)
 
345
        if match is not None:
 
346
            s = match.group(1)
 
347
            s = self._calcSupRe.sub(r'^(\1)', s)
 
348
            s = self._calcFontRe.sub(r',', s)
 
349
            s = self._calcTimesRe.sub(r'*', s)
 
350
            irc.reply(s)
 
351
        else:
 
352
            irc.reply('Google\'s calculator didn\'t come up with anything.')
 
353
    calc = wrap(calc, ['text'])
 
354
 
 
355
    _phoneRe = re.compile(r'Phonebook.*?<font size=-1>(.*?)<a href')
 
356
    def phonebook(self, irc, msg, args, phonenumber):
 
357
        """<phone number>
 
358
 
 
359
        Looks <phone number> up on Google.
 
360
        """
 
361
        url = self._googleUrl(phonenumber)
 
362
        html = utils.web.getUrl(url)
 
363
        m = self._phoneRe.search(html)
 
364
        if m is not None:
 
365
            s = m.group(1)
 
366
            s = s.replace('<b>', '')
 
367
            s = s.replace('</b>', '')
 
368
            s = utils.web.htmlToText(s)
 
369
            irc.reply(s)
 
370
        else:
 
371
            irc.reply('Google\'s phonebook didn\'t come up with anything.')
 
372
    phonebook = wrap(phonebook, ['text'])
 
373
 
 
374
 
 
375
Class = Google
 
376
 
 
377
 
 
378
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: