mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-11-14 22:49:23 +01:00
Added Google in the new plugin format.
This commit is contained in:
parent
f6e132dde4
commit
7313ae3b76
85
plugins/Google/GoogleSOAPFacade.py
Normal file
85
plugins/Google/GoogleSOAPFacade.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
"""
|
||||||
|
Facade that hides the differences between the SOAPpy and SOAP.py
|
||||||
|
libraries, so that google.py doesn't have to deal with them.
|
||||||
|
|
||||||
|
@author: Brian Landers <brian@bluecoat93.org>
|
||||||
|
@license: Python
|
||||||
|
@version: 0.5.4
|
||||||
|
"""
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
from distutils.version import LooseVersion
|
||||||
|
|
||||||
|
__author__ = "Brian Landers <brian@bluecoat93.org>"
|
||||||
|
__version__ = "0.6"
|
||||||
|
__license__ = "Python"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Wrapper around the python 'warnings' facility
|
||||||
|
#
|
||||||
|
def warn( message, level=RuntimeWarning ):
|
||||||
|
warnings.warn( message, level, stacklevel=3 )
|
||||||
|
|
||||||
|
# We can't use older version of SOAPpy, due to bugs that break the Google API
|
||||||
|
minSOAPpyVersion = "0.11.3"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Try loading SOAPpy first. If that fails, fall back to the old SOAP.py
|
||||||
|
#
|
||||||
|
SOAPpy = None
|
||||||
|
try:
|
||||||
|
import SOAPpy
|
||||||
|
from SOAPpy import SOAPProxy, Types
|
||||||
|
|
||||||
|
if LooseVersion( minSOAPpyVersion ) > \
|
||||||
|
LooseVersion( SOAPpy.version.__version__ ):
|
||||||
|
|
||||||
|
warn( "Versions of SOAPpy before %s have known bugs that prevent " +
|
||||||
|
"PyGoogle from functioning." % minSOAPpyVersion )
|
||||||
|
raise ImportError
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
warn( "SOAPpy not imported. Trying legacy SOAP.py.",
|
||||||
|
DeprecationWarning )
|
||||||
|
try:
|
||||||
|
import SOAP
|
||||||
|
except ImportError:
|
||||||
|
raise RuntimeError( "Unable to find SOAPpy or SOAP. Can't continue.\n" )
|
||||||
|
|
||||||
|
#
|
||||||
|
# Constants that differ between the modules
|
||||||
|
#
|
||||||
|
if SOAPpy:
|
||||||
|
false = Types.booleanType(0)
|
||||||
|
true = Types.booleanType(1)
|
||||||
|
structType = Types.structType
|
||||||
|
faultType = Types.faultType
|
||||||
|
else:
|
||||||
|
false = SOAP.booleanType(0)
|
||||||
|
true = SOAP.booleanType(1)
|
||||||
|
structType = SOAP.structType
|
||||||
|
faultType = SOAP.faultType
|
||||||
|
|
||||||
|
#
|
||||||
|
# Get a SOAP Proxy object in the correct way for the module we're using
|
||||||
|
#
|
||||||
|
def getProxy( url, namespace, http_proxy ):
|
||||||
|
if SOAPpy:
|
||||||
|
return SOAPProxy( url,
|
||||||
|
namespace = namespace,
|
||||||
|
http_proxy = http_proxy )
|
||||||
|
|
||||||
|
else:
|
||||||
|
return SOAP.SOAPProxy( url,
|
||||||
|
namespace = namespace,
|
||||||
|
http_proxy = http_proxy )
|
||||||
|
|
||||||
|
#
|
||||||
|
# Convert an object to a dictionary in the proper way for the module
|
||||||
|
# we're using for SOAP
|
||||||
|
#
|
||||||
|
def toDict( obj ):
|
||||||
|
if SOAPpy:
|
||||||
|
return obj._asdict()
|
||||||
|
else:
|
||||||
|
return obj._asdict
|
1
plugins/Google/README.txt
Normal file
1
plugins/Google/README.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Insert a description of your plugin here, with any notes, etc. about using it.
|
3977
plugins/Google/SOAP.py
Normal file
3977
plugins/Google/SOAP.py
Normal file
File diff suppressed because it is too large
Load Diff
60
plugins/Google/__init__.py
Normal file
60
plugins/Google/__init__.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
###
|
||||||
|
# Copyright (c) 2005, Jeremiah Fincher
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions, and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions, and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the author of this software nor the name of
|
||||||
|
# contributors to this software may be used to endorse or promote products
|
||||||
|
# derived from this software without specific prior written consent.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
###
|
||||||
|
|
||||||
|
"""
|
||||||
|
Accesses Google for various things.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import supybot
|
||||||
|
import supybot.world as world
|
||||||
|
|
||||||
|
# Use this for the version of this plugin. You may wish to put a CVS keyword
|
||||||
|
# in here if you're keeping the plugin in CVS or some similar system.
|
||||||
|
__version__ = ""
|
||||||
|
|
||||||
|
__author__ = supybot.authors.jemfinch
|
||||||
|
|
||||||
|
# This is a dictionary mapping supybot.Author instances to lists of
|
||||||
|
# contributions.
|
||||||
|
__contributors__ = {}
|
||||||
|
|
||||||
|
import config
|
||||||
|
import plugin
|
||||||
|
reload(plugin) # In case we're being reloaded.
|
||||||
|
# Add more reloads here if you add third-party modules and want them to be
|
||||||
|
# reloaded when this plugin is reloaded. Don't forget to import them as well!
|
||||||
|
|
||||||
|
if world.testing:
|
||||||
|
import test
|
||||||
|
|
||||||
|
Class = plugin.Class
|
||||||
|
configure = config.configure
|
||||||
|
|
||||||
|
|
||||||
|
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
136
plugins/Google/config.py
Normal file
136
plugins/Google/config.py
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
###
|
||||||
|
# Copyright (c) 2005, Jeremiah Fincher
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions, and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions, and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the author of this software nor the name of
|
||||||
|
# contributors to this software may be used to endorse or promote products
|
||||||
|
# derived from this software without specific prior written consent.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
###
|
||||||
|
|
||||||
|
import supybot.conf as conf
|
||||||
|
import supybot.registry as registry
|
||||||
|
|
||||||
|
import google
|
||||||
|
|
||||||
|
def configure(advanced):
|
||||||
|
from supybot.questions import output, expect, anything, something, yn
|
||||||
|
output('To use Google\'t Web Services, you must have a license key.')
|
||||||
|
if yn('Do you have a license key?'):
|
||||||
|
key = something('What is it?')
|
||||||
|
while len(key) != 32:
|
||||||
|
output('That\'s not a valid Google license key.')
|
||||||
|
if yn('Are you sure you have a valid Google license key?'):
|
||||||
|
key = something('What is it?')
|
||||||
|
else:
|
||||||
|
key = ''
|
||||||
|
break
|
||||||
|
if key:
|
||||||
|
conf.registerPlugin('Google', True)
|
||||||
|
conf.supybot.plugins.Google.licenseKey.setValue(key)
|
||||||
|
output("""The Google plugin has the functionality to watch for URLs
|
||||||
|
that match a specific pattern. (We call this a snarfer)
|
||||||
|
When supybot sees such a URL, it will parse the web page
|
||||||
|
for information and reply with the results.
|
||||||
|
|
||||||
|
Google has two available snarfers: Google Groups link
|
||||||
|
snarfing and a google search snarfer.""")
|
||||||
|
if yn('Do you want the Google Groups link snarfer enabled by '
|
||||||
|
'default?'):
|
||||||
|
conf.supybot.plugins.Google.groupsSnarfer.setValue(True)
|
||||||
|
if yn('Do you want the Google search snarfer enabled by default?'):
|
||||||
|
conf.supybot.plugins.Google.searchSnarfer.setValue(True)
|
||||||
|
else:
|
||||||
|
output("""You'll need to get a key before you can use this plugin.
|
||||||
|
You can apply for a key at http://www.google.com/apis/""")
|
||||||
|
|
||||||
|
class LicenseKey(registry.String):
|
||||||
|
def setValue(self, s):
|
||||||
|
if s and len(s) != 32:
|
||||||
|
raise registry.InvalidRegistryValue, 'Invalid Google license key.'
|
||||||
|
try:
|
||||||
|
s = s or ''
|
||||||
|
registry.String.setValue(self, s)
|
||||||
|
if s:
|
||||||
|
google.setLicense(self.value)
|
||||||
|
except AttributeError:
|
||||||
|
if world and not world.dying: # At shutdown world can be None.
|
||||||
|
raise callbacks.Error, \
|
||||||
|
'It appears that the initial import of ' \
|
||||||
|
'our underlying google.py module has ' \
|
||||||
|
'failed. Once the cause of that problem ' \
|
||||||
|
'has been diagnosed and fixed, the bot ' \
|
||||||
|
'will need to be restarted in order to ' \
|
||||||
|
'load this plugin.'
|
||||||
|
|
||||||
|
class Language(registry.OnlySomeStrings):
|
||||||
|
validStrings = ['lang_' + s for s in 'ar zh-CN zh-TW cs da nl en et fi fr '
|
||||||
|
'de el iw hu is it ja ko lv lt no pt '
|
||||||
|
'pl ro ru es sv tr'.split()]
|
||||||
|
validStrings.append('')
|
||||||
|
def normalize(self, s):
|
||||||
|
if not s.startswith('lang_'):
|
||||||
|
s = 'lang_' + s
|
||||||
|
if not s.endswith('CN') or s.endswith('TW'):
|
||||||
|
s = s.lower()
|
||||||
|
else:
|
||||||
|
s = s.lower()[:-2] + s[-2:]
|
||||||
|
return s
|
||||||
|
|
||||||
|
Google = conf.registerPlugin('Google')
|
||||||
|
conf.registerChannelValue(Google, 'groupsSnarfer',
|
||||||
|
registry.Boolean(False, """Determines whether the groups snarfer is
|
||||||
|
enabled. If so, URLs at groups.google.com will be snarfed and their
|
||||||
|
group/title messaged to the channel."""))
|
||||||
|
conf.registerChannelValue(Google, 'searchSnarfer',
|
||||||
|
registry.Boolean(False, """Determines whether the search snarfer is
|
||||||
|
enabled. If so, messages (even unaddressed ones) beginning with the word
|
||||||
|
'google' will result in the first URL Google returns being sent to the
|
||||||
|
channel."""))
|
||||||
|
conf.registerChannelValue(Google, 'colorfulFilter',
|
||||||
|
registry.Boolean(False, """Determines whether the word 'google' in the
|
||||||
|
bot's output will be made colorful (like Google's logo)."""))
|
||||||
|
conf.registerChannelValue(Google, 'bold',
|
||||||
|
registry.Boolean(True, """Determines whether results are bolded."""))
|
||||||
|
conf.registerChannelValue(Google, 'maximumResults',
|
||||||
|
registry.PositiveInteger(10, """Determines the maximum number of results
|
||||||
|
returned from the google command."""))
|
||||||
|
conf.registerChannelValue(Google, 'defaultLanguage',
|
||||||
|
Language('lang_en', """Determines what default language is used in
|
||||||
|
searches. If left empty, no specific language will be requested."""))
|
||||||
|
conf.registerChannelValue(Google, 'safeSearch',
|
||||||
|
registry.Boolean(True, "Determines whether safeSearch is on by default."))
|
||||||
|
conf.registerGlobalValue(Google, 'licenseKey',
|
||||||
|
LicenseKey('', """Sets the Google license key for using Google's Web
|
||||||
|
Services API. This is necessary before you can do any searching with this
|
||||||
|
module.""", private=True))
|
||||||
|
|
||||||
|
conf.registerGroup(Google, 'state')
|
||||||
|
conf.registerGlobalValue(Google.state, 'searches',
|
||||||
|
registry.Integer(0, """Used to keep the total number of searches Google has
|
||||||
|
done for this bot. You shouldn't modify this."""))
|
||||||
|
conf.registerGlobalValue(Google.state, 'time',
|
||||||
|
registry.Float(0.0, """Used to keep the total amount of time Google has
|
||||||
|
spent searching for this bot. You shouldn't modify this."""))
|
||||||
|
|
||||||
|
|
||||||
|
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78
|
638
plugins/Google/google.py
Normal file
638
plugins/Google/google.py
Normal file
@ -0,0 +1,638 @@
|
|||||||
|
"""
|
||||||
|
Python wrapper for Google web APIs
|
||||||
|
|
||||||
|
This module allows you to access Google's web APIs through SOAP,
|
||||||
|
to do things like search Google and get the results programmatically.
|
||||||
|
Described U{here <http://www.google.com/apis/>}
|
||||||
|
|
||||||
|
You need a Google-provided license key to use these services.
|
||||||
|
Follow the link above to get one. These functions will look in
|
||||||
|
several places (in this order) for the license key:
|
||||||
|
|
||||||
|
- the "license_key" argument of each function
|
||||||
|
- the module-level LICENSE_KEY variable (call setLicense once to set it)
|
||||||
|
- an environment variable called GOOGLE_LICENSE_KEY
|
||||||
|
- a file called ".googlekey" in the current directory
|
||||||
|
- a file called "googlekey.txt" in the current directory
|
||||||
|
- a file called ".googlekey" in your home directory
|
||||||
|
- a file called "googlekey.txt" in your home directory
|
||||||
|
- a file called ".googlekey" in the same directory as google.py
|
||||||
|
- a file called "googlekey.txt" in the same directory as google.py
|
||||||
|
|
||||||
|
Sample usage::
|
||||||
|
|
||||||
|
>>> import google
|
||||||
|
>>> google.setLicense('...') # must get your own key!
|
||||||
|
>>> data = google.doGoogleSearch('python')
|
||||||
|
>>> data.meta.searchTime
|
||||||
|
0.043221000000000002
|
||||||
|
|
||||||
|
>>> data.results[0].URL
|
||||||
|
'http://www.python.org/'
|
||||||
|
|
||||||
|
>>> data.results[0].title
|
||||||
|
'<b>Python</b> Language Website'
|
||||||
|
|
||||||
|
@newfield contrib: Contributors
|
||||||
|
@author: Mark Pilgrim <f8dy@diveintomark.org>
|
||||||
|
@author: Brian Landers <brian@bluecoat93.org>
|
||||||
|
@license: Python
|
||||||
|
@version: 0.6
|
||||||
|
@contrib: David Ascher, for the install script
|
||||||
|
@contrib: Erik Max Francis, for the command line interface
|
||||||
|
@contrib: Michael Twomey, for HTTP proxy support
|
||||||
|
@contrib: Mark Recht, for patches to support SOAPpy
|
||||||
|
"""
|
||||||
|
|
||||||
|
__author__ = "Mark Pilgrim (f8dy@diveintomark.org)"
|
||||||
|
__version__ = "0.6"
|
||||||
|
__cvsversion__ = "$Revision: 1.6 $"[11:-2]
|
||||||
|
__date__ = "$Date: 2004/09/30 08:09:09 $"[7:-2]
|
||||||
|
__copyright__ = "Copyright (c) 2002 Mark Pilgrim"
|
||||||
|
__license__ = "Python"
|
||||||
|
__credits__ = """David Ascher, for the install script
|
||||||
|
Erik Max Francis, for the command line interface
|
||||||
|
Michael Twomey, for HTTP proxy support"""
|
||||||
|
|
||||||
|
import os, sys, getopt
|
||||||
|
import GoogleSOAPFacade
|
||||||
|
|
||||||
|
LICENSE_KEY = None
|
||||||
|
HTTP_PROXY = None
|
||||||
|
|
||||||
|
#
|
||||||
|
# Constants
|
||||||
|
#
|
||||||
|
_url = 'http://api.google.com/search/beta2'
|
||||||
|
_namespace = 'urn:GoogleSearch'
|
||||||
|
_googlefile1 = ".googlekey"
|
||||||
|
_googlefile2 = "googlekey.txt"
|
||||||
|
|
||||||
|
_false = GoogleSOAPFacade.false
|
||||||
|
_true = GoogleSOAPFacade.true
|
||||||
|
|
||||||
|
_licenseLocations = (
|
||||||
|
( lambda key: key,
|
||||||
|
'passed to the function in license_key variable' ),
|
||||||
|
( lambda key: LICENSE_KEY,
|
||||||
|
'module-level LICENSE_KEY variable (call setLicense to set it)' ),
|
||||||
|
( lambda key: os.environ.get( 'GOOGLE_LICENSE_KEY', None ),
|
||||||
|
'an environment variable called GOOGLE_LICENSE_KEY' ),
|
||||||
|
( lambda key: _contentsOf( os.getcwd(), _googlefile1 ),
|
||||||
|
'%s in the current directory' % _googlefile1),
|
||||||
|
( lambda key: _contentsOf( os.getcwd(), _googlefile2 ),
|
||||||
|
'%s in the current directory' % _googlefile2),
|
||||||
|
( lambda key: _contentsOf( os.environ.get( 'HOME', '' ), _googlefile1 ),
|
||||||
|
'%s in your home directory' % _googlefile1),
|
||||||
|
( lambda key: _contentsOf( os.environ.get( 'HOME', '' ), _googlefile2 ),
|
||||||
|
'%s in your home directory' % _googlefile2 ),
|
||||||
|
( lambda key: _contentsOf( _getScriptDir(), _googlefile1 ),
|
||||||
|
'%s in the google.py directory' % _googlefile1 ),
|
||||||
|
( lambda key: _contentsOf( _getScriptDir(), _googlefile2 ),
|
||||||
|
'%s in the google.py directory' % _googlefile2 )
|
||||||
|
)
|
||||||
|
|
||||||
|
## ----------------------------------------------------------------------
|
||||||
|
## Exceptions
|
||||||
|
## ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
class NoLicenseKey(Exception):
|
||||||
|
"""
|
||||||
|
Thrown when the API is unable to find a valid license key.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
## ----------------------------------------------------------------------
|
||||||
|
## administrative functions (non-API)
|
||||||
|
## ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _version():
|
||||||
|
"""
|
||||||
|
Display a formatted version string for the module
|
||||||
|
"""
|
||||||
|
print """PyGoogle %(__version__)s
|
||||||
|
%(__copyright__)s
|
||||||
|
released %(__date__)s
|
||||||
|
|
||||||
|
Thanks to:
|
||||||
|
%(__credits__)s""" % globals()
|
||||||
|
|
||||||
|
|
||||||
|
def _usage():
|
||||||
|
"""
|
||||||
|
Display usage information for the command-line interface
|
||||||
|
"""
|
||||||
|
program = os.path.basename(sys.argv[0])
|
||||||
|
print """Usage: %(program)s [options] [querytype] query
|
||||||
|
|
||||||
|
options:
|
||||||
|
-k, --key= <license key> Google license key (see important note below)
|
||||||
|
-1, -l, --lucky show only first hit
|
||||||
|
-m, --meta show meta information
|
||||||
|
-r, --reverse show results in reverse order
|
||||||
|
-x, --proxy= <url> use HTTP proxy
|
||||||
|
-h, --help print this help
|
||||||
|
-v, --version print version and copyright information
|
||||||
|
-t, --test run test queries
|
||||||
|
|
||||||
|
querytype:
|
||||||
|
-s, --search= <query> search (default)
|
||||||
|
-c, --cache= <url> retrieve cached page
|
||||||
|
-p, --spelling= <word> check spelling
|
||||||
|
|
||||||
|
IMPORTANT NOTE: all Google functions require a valid license key;
|
||||||
|
visit http://www.google.com/apis/ to get one. %(program)s will look in
|
||||||
|
these places (in order) and use the first license key it finds:
|
||||||
|
* the key specified on the command line""" % vars()
|
||||||
|
for get, location in _licenseLocations[2:]:
|
||||||
|
print " *", location
|
||||||
|
|
||||||
|
## ----------------------------------------------------------------------
|
||||||
|
## utility functions (API)
|
||||||
|
## ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
def setLicense(license_key):
|
||||||
|
"""
|
||||||
|
Set the U{Google APIs <http://www.google.com/api>} license key
|
||||||
|
|
||||||
|
@param license_key: The new key to use
|
||||||
|
@type license_key: String
|
||||||
|
@todo: validate the key?
|
||||||
|
"""
|
||||||
|
global LICENSE_KEY
|
||||||
|
LICENSE_KEY = license_key
|
||||||
|
|
||||||
|
|
||||||
|
def getLicense(license_key = None):
|
||||||
|
"""
|
||||||
|
Get the U{Google APIs <http://www.google.com/api>} license key
|
||||||
|
|
||||||
|
The key can be read from any number of locations. See the module-leve
|
||||||
|
documentation for the search order.
|
||||||
|
|
||||||
|
@return: the license key
|
||||||
|
@rtype: String
|
||||||
|
@raise NoLicenseKey: if no valid key could be found
|
||||||
|
"""
|
||||||
|
for get, location in _licenseLocations:
|
||||||
|
rc = get(license_key)
|
||||||
|
if rc: return rc
|
||||||
|
_usage()
|
||||||
|
raise NoLicenseKey, 'get a license key at http://www.google.com/apis/'
|
||||||
|
|
||||||
|
|
||||||
|
def setProxy(http_proxy):
|
||||||
|
"""
|
||||||
|
Set the HTTP proxy to be used when accessing Google
|
||||||
|
|
||||||
|
@param http_proxy: the proxy to use
|
||||||
|
@type http_proxy: String
|
||||||
|
@todo: validiate the input?
|
||||||
|
"""
|
||||||
|
global HTTP_PROXY
|
||||||
|
HTTP_PROXY = http_proxy
|
||||||
|
|
||||||
|
|
||||||
|
def getProxy(http_proxy = None):
|
||||||
|
"""
|
||||||
|
Get the HTTP proxy we use for accessing Google
|
||||||
|
|
||||||
|
@return: the proxy
|
||||||
|
@rtype: String
|
||||||
|
"""
|
||||||
|
return http_proxy or HTTP_PROXY
|
||||||
|
|
||||||
|
|
||||||
|
def _contentsOf(dirname, filename):
|
||||||
|
filename = os.path.join(dirname, filename)
|
||||||
|
if not os.path.exists(filename): return None
|
||||||
|
fsock = open(filename)
|
||||||
|
contents = fsock.read()
|
||||||
|
fsock.close()
|
||||||
|
return contents
|
||||||
|
|
||||||
|
|
||||||
|
def _getScriptDir():
|
||||||
|
if __name__ == '__main__':
|
||||||
|
return os.path.abspath(os.path.dirname(sys.argv[0]))
|
||||||
|
else:
|
||||||
|
return os.path.abspath(os.path.dirname(sys.modules[__name__].__file__))
|
||||||
|
|
||||||
|
|
||||||
|
def _marshalBoolean(value):
|
||||||
|
if value:
|
||||||
|
return _true
|
||||||
|
else:
|
||||||
|
return _false
|
||||||
|
|
||||||
|
|
||||||
|
def _getRemoteServer( http_proxy ):
|
||||||
|
return GoogleSOAPFacade.getProxy( _url, _namespace, http_proxy )
|
||||||
|
|
||||||
|
|
||||||
|
## ----------------------------------------------------------------------
|
||||||
|
## search results classes
|
||||||
|
## ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
class _SearchBase:
|
||||||
|
def __init__(self, params):
|
||||||
|
for k, v in params.items():
|
||||||
|
if isinstance(v, GoogleSOAPFacade.structType):
|
||||||
|
v = GoogleSOAPFacade.toDict( v )
|
||||||
|
|
||||||
|
try:
|
||||||
|
if isinstance(v[0], GoogleSOAPFacade.structType):
|
||||||
|
v = [ SOAPProxy.toDict( node ) for node in v ]
|
||||||
|
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.__dict__[str(k)] = v
|
||||||
|
|
||||||
|
## ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
class SearchResultsMetaData(_SearchBase):
|
||||||
|
"""
|
||||||
|
Container class for metadata about a given search query's results.
|
||||||
|
|
||||||
|
@ivar documentFiltering: is duplicate page filtering active?
|
||||||
|
|
||||||
|
@ivar searchComments: human-readable informational message
|
||||||
|
|
||||||
|
example::
|
||||||
|
|
||||||
|
"'the' is a very common word and was not included in your search"
|
||||||
|
|
||||||
|
@ivar estimatedTotalResultsCount: estimated total number of results
|
||||||
|
for this query.
|
||||||
|
|
||||||
|
@ivar estimateIsExact: is estimatedTotalResultsCount an exact value?
|
||||||
|
|
||||||
|
@ivar searchQuery: search string that initiated this search
|
||||||
|
|
||||||
|
@ivar startIndex: index of the first result returned (zero-based)
|
||||||
|
|
||||||
|
@ivar endIndex: index of the last result returned (zero-based)
|
||||||
|
|
||||||
|
@ivar searchTips: human-readable informational message on how to better
|
||||||
|
use Google.
|
||||||
|
|
||||||
|
@ivar directoryCategories: list of categories for the search results
|
||||||
|
|
||||||
|
This field is a list of dictionaries, like so::
|
||||||
|
|
||||||
|
{ 'fullViewableName': 'the Open Directory category',
|
||||||
|
'specialEncoding': 'encoding scheme of this directory category'
|
||||||
|
}
|
||||||
|
|
||||||
|
@ivar searchTime: total search time, in seconds
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
## ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
class SearchResult(_SearchBase):
|
||||||
|
"""
|
||||||
|
Encapsulates the results from a search.
|
||||||
|
|
||||||
|
@ivar URL: URL
|
||||||
|
|
||||||
|
@ivar title: title (HTML)
|
||||||
|
|
||||||
|
@ivar snippet: snippet showing query context (HTML
|
||||||
|
|
||||||
|
@ivar cachedSize: size of cached version of this result, (KB)
|
||||||
|
|
||||||
|
@ivar relatedInformationPresent: is the "related:" keyword supported?
|
||||||
|
|
||||||
|
Flag indicates that the "related:" keyword is supported for this URL
|
||||||
|
|
||||||
|
@ivar hostName: used when filtering occurs
|
||||||
|
|
||||||
|
When filtering occurs, a maximum of two results from any given
|
||||||
|
host is returned. When this occurs, the second resultElement
|
||||||
|
that comes from that host contains the host name in this parameter.
|
||||||
|
|
||||||
|
@ivar directoryCategory: Open Directory category information
|
||||||
|
|
||||||
|
This field is a dictionary with the following values::
|
||||||
|
|
||||||
|
{ 'fullViewableName': 'the Open Directory category',
|
||||||
|
'specialEncoding' : 'encoding scheme of this directory category'
|
||||||
|
}
|
||||||
|
|
||||||
|
@ivar directoryTitle: Open Directory title of this result (or blank)
|
||||||
|
|
||||||
|
@ivar summary: Open Directory summary for this result (or blank)
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
## ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
class SearchReturnValue:
|
||||||
|
"""
|
||||||
|
complete search results for a single query
|
||||||
|
|
||||||
|
@ivar meta: L{SearchResultsMetaData} instance for this query
|
||||||
|
|
||||||
|
@ivar results: list of L{SearchResult} objects for this query
|
||||||
|
"""
|
||||||
|
def __init__( self, metadata, results ):
|
||||||
|
self.meta = metadata
|
||||||
|
self.results = results
|
||||||
|
|
||||||
|
## ----------------------------------------------------------------------
|
||||||
|
## main functions
|
||||||
|
## ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
def doGoogleSearch( q, start = 0, maxResults = 10, filter = 1,
|
||||||
|
restrict='', safeSearch = 0, language = '',
|
||||||
|
inputencoding = '', outputencoding = '',\
|
||||||
|
license_key = None, http_proxy = None ):
|
||||||
|
"""
|
||||||
|
Search Google using the SOAP API and return the results.
|
||||||
|
|
||||||
|
You need a license key to call this function; see the
|
||||||
|
U{Google APIs <http://www.google.com/apis/>} site to get one.
|
||||||
|
Then you can either pass it to this function every time, or
|
||||||
|
set it globally; see the L{google} module-level docs for details.
|
||||||
|
|
||||||
|
See U{http://www.google.com/help/features.html}
|
||||||
|
for examples of advanced features. Anything that works at the
|
||||||
|
Google web site will work as a query string in this method.
|
||||||
|
|
||||||
|
You can use the C{start} and C{maxResults} parameters to page
|
||||||
|
through multiple pages of results. Note that 'maxResults' is
|
||||||
|
currently limited by Google to 10.
|
||||||
|
|
||||||
|
See the API reference for more advanced examples and a full list of
|
||||||
|
country codes and topics for use in the C{restrict} parameter, along
|
||||||
|
with legal values for the C{language}, C{inputencoding}, and
|
||||||
|
C{outputencoding} parameters.
|
||||||
|
|
||||||
|
You can download the API documentation
|
||||||
|
U{http://www.google.com/apis/download.html <here>}.
|
||||||
|
|
||||||
|
@param q: search string.
|
||||||
|
@type q: String
|
||||||
|
|
||||||
|
@param start: (optional) zero-based index of first desired result.
|
||||||
|
@type start: int
|
||||||
|
|
||||||
|
@param maxResults: (optional) maximum number of results to return.
|
||||||
|
@type maxResults: int
|
||||||
|
|
||||||
|
@param filter: (optional) flag to request filtering of similar results
|
||||||
|
@type filter: int
|
||||||
|
|
||||||
|
@param restrict: (optional) restrict results by country or topic.
|
||||||
|
@type restrict: String
|
||||||
|
|
||||||
|
@param safeSearch: (optional)
|
||||||
|
@type safeSearch: int
|
||||||
|
|
||||||
|
@param language: (optional)
|
||||||
|
@type language: String
|
||||||
|
|
||||||
|
@param inputencoding: (optional)
|
||||||
|
@type inputencoding: String
|
||||||
|
|
||||||
|
@param outputencoding: (optional)
|
||||||
|
@type outputencoding: String
|
||||||
|
|
||||||
|
@param license_key: (optional) the Google API license key to use
|
||||||
|
@type license_key: String
|
||||||
|
|
||||||
|
@param http_proxy: (optional) the HTTP proxy to use for talking to Google
|
||||||
|
@type http_proxy: String
|
||||||
|
|
||||||
|
@return: the search results encapsulated in an object
|
||||||
|
@rtype: L{SearchReturnValue}
|
||||||
|
"""
|
||||||
|
license_key = getLicense( license_key )
|
||||||
|
http_proxy = getProxy( http_proxy )
|
||||||
|
remoteserver = _getRemoteServer( http_proxy )
|
||||||
|
|
||||||
|
filter = _marshalBoolean( filter )
|
||||||
|
safeSearch = _marshalBoolean( safeSearch )
|
||||||
|
|
||||||
|
data = remoteserver.doGoogleSearch( license_key, q, start, maxResults,
|
||||||
|
filter, restrict, safeSearch,
|
||||||
|
language, inputencoding,
|
||||||
|
outputencoding )
|
||||||
|
|
||||||
|
metadata = GoogleSOAPFacade.toDict( data )
|
||||||
|
del metadata["resultElements"]
|
||||||
|
|
||||||
|
metadata = SearchResultsMetaData( metadata )
|
||||||
|
|
||||||
|
results = [ SearchResult( GoogleSOAPFacade.toDict( node ) ) \
|
||||||
|
for node in data.resultElements ]
|
||||||
|
|
||||||
|
return SearchReturnValue( metadata, results )
|
||||||
|
|
||||||
|
## ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
def doGetCachedPage( url, license_key = None, http_proxy = None ):
|
||||||
|
"""
|
||||||
|
Retrieve a page from the Google cache.
|
||||||
|
|
||||||
|
You need a license key to call this function; see the
|
||||||
|
U{Google APIs <http://www.google.com/apis/>} site to get one.
|
||||||
|
Then you can either pass it to this function every time, or
|
||||||
|
set it globally; see the L{google} module-level docs for details.
|
||||||
|
|
||||||
|
@param url: full URL to the page to retrieve
|
||||||
|
@type url: String
|
||||||
|
|
||||||
|
@param license_key: (optional) the Google API key to use
|
||||||
|
@type license_key: String
|
||||||
|
|
||||||
|
@param http_proxy: (optional) the HTTP proxy server to use
|
||||||
|
@type http_proxy: String
|
||||||
|
|
||||||
|
@return: full text of the cached page
|
||||||
|
@rtype: String
|
||||||
|
"""
|
||||||
|
license_key = getLicense( license_key )
|
||||||
|
http_proxy = getProxy( http_proxy )
|
||||||
|
remoteserver = _getRemoteServer( http_proxy )
|
||||||
|
|
||||||
|
return remoteserver.doGetCachedPage( license_key, url )
|
||||||
|
|
||||||
|
## ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
def doSpellingSuggestion( phrase, license_key = None, http_proxy = None ):
|
||||||
|
"""
|
||||||
|
Get spelling suggestions from Google
|
||||||
|
|
||||||
|
You need a license key to call this function; see the
|
||||||
|
U{Google APIs <http://www.google.com/apis/>} site to get one.
|
||||||
|
Then you can either pass it to this function every time, or
|
||||||
|
set it globally; see the L{google} module-level docs for details.
|
||||||
|
|
||||||
|
@param phrase: word or phrase to spell-check
|
||||||
|
@type phrase: String
|
||||||
|
|
||||||
|
@param license_key: (optional) the Google API key to use
|
||||||
|
@type license_key: String
|
||||||
|
|
||||||
|
@param http_proxy: (optional) the HTTP proxy to use
|
||||||
|
@type http_proxy: String
|
||||||
|
|
||||||
|
@return: text of any suggested replacement, or None
|
||||||
|
"""
|
||||||
|
license_key = getLicense( license_key )
|
||||||
|
http_proxy = getProxy( http_proxy)
|
||||||
|
remoteserver = _getRemoteServer( http_proxy )
|
||||||
|
|
||||||
|
return remoteserver.doSpellingSuggestion( license_key, phrase )
|
||||||
|
|
||||||
|
## ----------------------------------------------------------------------
|
||||||
|
## functional test suite (see googletest.py for unit test suite)
|
||||||
|
## ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _test():
|
||||||
|
"""
|
||||||
|
Run functional test suite.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
getLicense(None)
|
||||||
|
except NoLicenseKey:
|
||||||
|
return
|
||||||
|
|
||||||
|
print "Searching for Python at google.com..."
|
||||||
|
data = doGoogleSearch( "Python" )
|
||||||
|
_output( data, { "func": "doGoogleSearch"} )
|
||||||
|
|
||||||
|
print "\nSearching for 5 _French_ pages about Python, "
|
||||||
|
print "encoded in ISO-8859-1..."
|
||||||
|
|
||||||
|
data = doGoogleSearch( "Python", language = 'lang_fr',
|
||||||
|
outputencoding = 'ISO-8859-1',
|
||||||
|
maxResults = 5 )
|
||||||
|
|
||||||
|
_output( data, { "func": "doGoogleSearch" } )
|
||||||
|
|
||||||
|
phrase = "Pyhton programming languager"
|
||||||
|
print "\nTesting spelling suggestions for '%s'..." % phrase
|
||||||
|
|
||||||
|
data = doSpellingSuggestion( phrase )
|
||||||
|
|
||||||
|
_output( data, { "func": "doSpellingSuggestion" } )
|
||||||
|
|
||||||
|
## ----------------------------------------------------------------------
|
||||||
|
## Command-line interface
|
||||||
|
## ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
class _OutputFormatter:
|
||||||
|
def boil(self, data):
|
||||||
|
if type(data) == type(u""):
|
||||||
|
return data.encode("ISO-8859-1", "replace")
|
||||||
|
else:
|
||||||
|
return data
|
||||||
|
|
||||||
|
class _TextOutputFormatter(_OutputFormatter):
|
||||||
|
def common(self, data, params):
|
||||||
|
if params.get("showMeta", 0):
|
||||||
|
meta = data.meta
|
||||||
|
for category in meta.directoryCategories:
|
||||||
|
print "directoryCategory: %s" % \
|
||||||
|
self.boil(category["fullViewableName"])
|
||||||
|
for attr in [node for node in dir(meta) if \
|
||||||
|
node <> "directoryCategories" and node[:2] <> '__']:
|
||||||
|
print "%s:" % attr, self.boil(getattr(meta, attr))
|
||||||
|
|
||||||
|
def doGoogleSearch(self, data, params):
|
||||||
|
results = data.results
|
||||||
|
if params.get("feelingLucky", 0):
|
||||||
|
results = results[:1]
|
||||||
|
if params.get("reverseOrder", 0):
|
||||||
|
results.reverse()
|
||||||
|
for result in results:
|
||||||
|
for attr in dir(result):
|
||||||
|
if attr == "directoryCategory":
|
||||||
|
print "directoryCategory:", \
|
||||||
|
self.boil(result.directoryCategory["fullViewableName"])
|
||||||
|
elif attr[:2] <> '__':
|
||||||
|
print "%s:" % attr, self.boil(getattr(result, attr))
|
||||||
|
print
|
||||||
|
self.common(data, params)
|
||||||
|
|
||||||
|
def doGetCachedPage(self, data, params):
|
||||||
|
print data
|
||||||
|
self.common(data, params)
|
||||||
|
|
||||||
|
doSpellingSuggestion = doGetCachedPage
|
||||||
|
|
||||||
|
def _makeFormatter(outputFormat):
|
||||||
|
classname = "_%sOutputFormatter" % outputFormat.capitalize()
|
||||||
|
return globals()[classname]()
|
||||||
|
|
||||||
|
def _output(results, params):
|
||||||
|
formatter = _makeFormatter(params.get("outputFormat", "text"))
|
||||||
|
outputmethod = getattr(formatter, params["func"])
|
||||||
|
outputmethod(results, params)
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
"""
|
||||||
|
Command-line interface.
|
||||||
|
"""
|
||||||
|
if not argv:
|
||||||
|
_usage()
|
||||||
|
return
|
||||||
|
q = None
|
||||||
|
func = None
|
||||||
|
http_proxy = None
|
||||||
|
license_key = None
|
||||||
|
feelingLucky = 0
|
||||||
|
showMeta = 0
|
||||||
|
reverseOrder = 0
|
||||||
|
runTest = 0
|
||||||
|
outputFormat = "text"
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(argv, "s:c:p:k:lmrx:hvt1",
|
||||||
|
["search=", "cache=", "spelling=", "key=", "lucky", "meta",
|
||||||
|
"reverse", "proxy=", "help", "version", "test"])
|
||||||
|
except getopt.GetoptError:
|
||||||
|
_usage()
|
||||||
|
sys.exit(2)
|
||||||
|
for opt, arg in opts:
|
||||||
|
if opt in ("-s", "--search"):
|
||||||
|
q = arg
|
||||||
|
func = "doGoogleSearch"
|
||||||
|
elif opt in ("-c", "--cache"):
|
||||||
|
q = arg
|
||||||
|
func = "doGetCachedPage"
|
||||||
|
elif opt in ("-p", "--spelling"):
|
||||||
|
q = arg
|
||||||
|
func = "doSpellingSuggestion"
|
||||||
|
elif opt in ("-k", "--key"):
|
||||||
|
license_key = arg
|
||||||
|
elif opt in ("-l", "-1", "--lucky"):
|
||||||
|
feelingLucky = 1
|
||||||
|
elif opt in ("-m", "--meta"):
|
||||||
|
showMeta = 1
|
||||||
|
elif opt in ("-r", "--reverse"):
|
||||||
|
reverseOrder = 1
|
||||||
|
elif opt in ("-x", "--proxy"):
|
||||||
|
http_proxy = arg
|
||||||
|
elif opt in ("-h", "--help"):
|
||||||
|
_usage()
|
||||||
|
elif opt in ("-v", "--version"):
|
||||||
|
_version()
|
||||||
|
elif opt in ("-t", "--test"):
|
||||||
|
runTest = 1
|
||||||
|
if runTest:
|
||||||
|
setLicense(license_key)
|
||||||
|
setProxy(http_proxy)
|
||||||
|
_test()
|
||||||
|
if args and not q:
|
||||||
|
q = args[0]
|
||||||
|
func = "doGoogleSearch"
|
||||||
|
if func:
|
||||||
|
results = globals()[func]( q, http_proxy=http_proxy,
|
||||||
|
license_key=license_key )
|
||||||
|
_output(results, locals())
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv[1:])
|
415
plugins/Google/plugin.py
Normal file
415
plugins/Google/plugin.py
Normal file
@ -0,0 +1,415 @@
|
|||||||
|
###
|
||||||
|
# Copyright (c) 2002-2004, Jeremiah Fincher
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions, and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions, and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the author of this software nor the name of
|
||||||
|
# contributors to this software may be used to endorse or promote products
|
||||||
|
# derived from this software without specific prior written consent.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
###
|
||||||
|
|
||||||
|
import re
|
||||||
|
import cgi
|
||||||
|
import time
|
||||||
|
import socket
|
||||||
|
import urllib
|
||||||
|
import xml.sax
|
||||||
|
|
||||||
|
import SOAP
|
||||||
|
import google
|
||||||
|
|
||||||
|
import supybot.conf as conf
|
||||||
|
import supybot.utils as utils
|
||||||
|
import supybot.world as world
|
||||||
|
from supybot.commands import *
|
||||||
|
import supybot.ircmsgs as ircmsgs
|
||||||
|
import supybot.ircutils as ircutils
|
||||||
|
import supybot.callbacks as callbacks
|
||||||
|
import supybot.structures as structures
|
||||||
|
|
||||||
|
def search(log, queries, **kwargs):
|
||||||
|
# We have to keep stats here, rather than in formatData or elsewhere,
|
||||||
|
# because not all searching functions use formatData -- fight, lucky, etc.
|
||||||
|
assert not isinstance(queries, basestring), 'Old code: queries is a list.'
|
||||||
|
try:
|
||||||
|
for (i, query) in enumerate(queries):
|
||||||
|
if len(query.split(None, 1)) > 1:
|
||||||
|
queries[i] = repr(query)
|
||||||
|
proxy = conf.supybot.protocols.http.proxy()
|
||||||
|
if proxy:
|
||||||
|
kwargs['http_proxy'] = proxy
|
||||||
|
query = ' '.join(queries).decode('utf-8')
|
||||||
|
data = google.doGoogleSearch(query, **kwargs)
|
||||||
|
searches = conf.supybot.plugins.Google.state.searches() + 1
|
||||||
|
conf.supybot.plugins.Google.state.searches.setValue(searches)
|
||||||
|
time = conf.supybot.plugins.Google.state.time() + data.meta.searchTime
|
||||||
|
conf.supybot.plugins.Google.state.time.setValue(time)
|
||||||
|
last24hours.enqueue(None)
|
||||||
|
return data
|
||||||
|
except socket.error, e:
|
||||||
|
if e.args[0] == 110:
|
||||||
|
raise callbacks.Error, 'Connection timed out to Google.com.'
|
||||||
|
else:
|
||||||
|
raise callbacks.Error, 'Error connecting to Google.com.'
|
||||||
|
except SOAP.HTTPError, e:
|
||||||
|
log.info('HTTP Error accessing Google: %s', e)
|
||||||
|
raise callbacks.Error, 'Error connecting to Google.com.'
|
||||||
|
except SOAP.faultType, e:
|
||||||
|
if 'Invalid authorization key' in e.faultstring:
|
||||||
|
raise callbacks.Error, 'Invalid Google license key.'
|
||||||
|
elif 'Problem looking up user record' in e.faultstring:
|
||||||
|
raise callbacks.Error, \
|
||||||
|
'Google seems to be having trouble looking up the user for '\
|
||||||
|
'your license key. This probably isn\'t a problem on your '\
|
||||||
|
'side; it\'s probably a bug on Google\'s side. It seems '\
|
||||||
|
'to happen intermittently.'
|
||||||
|
else:
|
||||||
|
log.exception('Unexpected SOAPpy error:')
|
||||||
|
raise callbacks.Error, \
|
||||||
|
'Unexpected error from Google; please report this to the ' \
|
||||||
|
'Supybot developers.'
|
||||||
|
except xml.sax.SAXException, e:
|
||||||
|
log.exception('Uncaught SAX error:')
|
||||||
|
raise callbacks.Error, 'Google returned an unparsable response. ' \
|
||||||
|
'The full traceback has been logged.'
|
||||||
|
except SOAPpy.Error, e:
|
||||||
|
log.exception('Uncaught SOAP exception in Google.search:')
|
||||||
|
raise callbacks.Error, 'Error connecting to Google.com.'
|
||||||
|
|
||||||
|
last24hours = structures.TimeoutQueue(86400)
|
||||||
|
totalTime = conf.supybot.plugins.Google.state.time()
|
||||||
|
searches = conf.supybot.plugins.Google.state.searches()
|
||||||
|
|
||||||
|
class Google(callbacks.PrivmsgCommandAndRegexp):
|
||||||
|
threaded = True
|
||||||
|
callBefore = ['URL']
|
||||||
|
regexps = ['googleSnarfer', 'googleGroups']
|
||||||
|
def __init__(self, irc):
|
||||||
|
self.__parent = super(Google, self)
|
||||||
|
self.__parent.__init__(irc)
|
||||||
|
google.setLicense(self.registryValue('licenseKey'))
|
||||||
|
|
||||||
|
def callCommand(self, name, irc, msg, *L, **kwargs):
|
||||||
|
try:
|
||||||
|
self.__parent.callCommand(name, irc, msg, *L, **kwargs)
|
||||||
|
except xml.sax.SAXReaderNotAvailable, e:
|
||||||
|
irc.error('No XML parser available.')
|
||||||
|
|
||||||
|
_colorGoogles = {}
|
||||||
|
def _getColorGoogle(self, m):
|
||||||
|
s = m.group(1)
|
||||||
|
ret = self._colorGoogles.get(s)
|
||||||
|
if not ret:
|
||||||
|
L = list(s)
|
||||||
|
L[0] = ircutils.mircColor(L[0], 'blue')[:-1]
|
||||||
|
L[1] = ircutils.mircColor(L[1], 'red')[:-1]
|
||||||
|
L[2] = ircutils.mircColor(L[2], 'yellow')[:-1]
|
||||||
|
L[3] = ircutils.mircColor(L[3], 'blue')[:-1]
|
||||||
|
L[4] = ircutils.mircColor(L[4], 'green')[:-1]
|
||||||
|
L[5] = ircutils.mircColor(L[5], 'red')
|
||||||
|
ret = ''.join(L)
|
||||||
|
self._colorGoogles[s] = ret
|
||||||
|
return ircutils.bold(ret)
|
||||||
|
|
||||||
|
_googleRe = re.compile(r'\b(google)\b', re.I)
|
||||||
|
def outFilter(self, irc, msg):
|
||||||
|
if msg.command == 'PRIVMSG' and \
|
||||||
|
self.registryValue('colorfulFilter', msg.args[0]):
|
||||||
|
s = msg.args[1]
|
||||||
|
s = re.sub(self._googleRe, self._getColorGoogle, s)
|
||||||
|
msg = ircmsgs.privmsg(msg.args[0], s, msg=msg)
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def formatData(self, data, bold=True, max=0):
|
||||||
|
if isinstance(data, basestring):
|
||||||
|
return data
|
||||||
|
t = format('Search took %.2f seconds', data.meta.searchTime)
|
||||||
|
results = []
|
||||||
|
if max:
|
||||||
|
data.results = data.results[:max]
|
||||||
|
for result in data.results:
|
||||||
|
title = utils.web.htmlToText(result.title.encode('utf-8'))
|
||||||
|
url = result.URL
|
||||||
|
if title:
|
||||||
|
if bold:
|
||||||
|
title = ircutils.bold(title)
|
||||||
|
results.append(format('%s: %u', title, url))
|
||||||
|
else:
|
||||||
|
results.append(url)
|
||||||
|
if not results:
|
||||||
|
return format('No matches found (%s)', t)
|
||||||
|
else:
|
||||||
|
return format('%s: %s', t, '; '.join(results))
|
||||||
|
|
||||||
|
def lucky(self, irc, msg, args, text):
|
||||||
|
"""<search>
|
||||||
|
|
||||||
|
Does a google search, but only returns the first result.
|
||||||
|
"""
|
||||||
|
data = search(self.log, text)
|
||||||
|
if data.results:
|
||||||
|
url = data.results[0].URL
|
||||||
|
irc.reply(url)
|
||||||
|
else:
|
||||||
|
irc.reply('Google found nothing.')
|
||||||
|
lucky = wrap(lucky, [many('something')])
|
||||||
|
|
||||||
|
def google(self, irc, msg, args, optlist, text):
|
||||||
|
"""<search> [--{language,restrict} <value>] [--{notsafe,similar}]
|
||||||
|
|
||||||
|
Searches google.com for the given string. As many results as can fit
|
||||||
|
are included. --language accepts a language abbreviation; --restrict
|
||||||
|
restricts the results to certain classes of things; --similar tells
|
||||||
|
Google not to filter similar results. --notsafe allows possibly
|
||||||
|
work-unsafe results.
|
||||||
|
"""
|
||||||
|
kwargs = {}
|
||||||
|
if self.registryValue('safeSearch', channel=msg.args[0]):
|
||||||
|
kwargs['safeSearch'] = 1
|
||||||
|
lang = self.registryValue('defaultLanguage', channel=msg.args[0])
|
||||||
|
if lang:
|
||||||
|
kwargs['language'] = lang
|
||||||
|
for (option, argument) in optlist:
|
||||||
|
if option == 'notsafe':
|
||||||
|
kwargs['safeSearch'] = False
|
||||||
|
elif option == 'similar':
|
||||||
|
kwargs['filter'] = False
|
||||||
|
else:
|
||||||
|
kwargs[option] = argument
|
||||||
|
try:
|
||||||
|
data = search(self.log, text, **kwargs)
|
||||||
|
except google.NoLicenseKey, e:
|
||||||
|
irc.error('You must have a free Google web services license key '
|
||||||
|
'in order to use this command. You can get one at '
|
||||||
|
'<http://google.com/apis/>. Once you have one, you can '
|
||||||
|
'set it with the command '
|
||||||
|
'"config supybot.plugins.Google.licenseKey <key>".')
|
||||||
|
return
|
||||||
|
bold = self.registryValue('bold', msg.args[0])
|
||||||
|
max = self.registryValue('maximumResults', msg.args[0])
|
||||||
|
irc.reply(self.formatData(data, bold=bold, max=max))
|
||||||
|
google = wrap(google, [getopts({'language':'something',
|
||||||
|
'restrict':'something',
|
||||||
|
'notsafe':'', 'similar':''}),
|
||||||
|
many('something')])
|
||||||
|
|
||||||
|
def metagoogle(self, irc, msg, args, optlist, text):
|
||||||
|
"""<search> [--(language,restrict)=<value>] [--{similar,notsafe}]
|
||||||
|
|
||||||
|
Searches google and gives all the interesting meta information about
|
||||||
|
the search. See the help for the google command for a detailed
|
||||||
|
description of the parameters.
|
||||||
|
"""
|
||||||
|
kwargs = {'language': 'lang_en', 'safeSearch': 1}
|
||||||
|
for option, argument in optlist:
|
||||||
|
if option == 'notsafe':
|
||||||
|
kwargs['safeSearch'] = False
|
||||||
|
elif option == 'similar':
|
||||||
|
kwargs['filter'] = False
|
||||||
|
else:
|
||||||
|
kwargs[option[2:]] = argument
|
||||||
|
data = search(self.log, text, **kwargs)
|
||||||
|
meta = data.meta
|
||||||
|
categories = [d['fullViewableName'] for d in meta.directoryCategories]
|
||||||
|
categories = [format('%q', s.replace('_', ' ')) for s in categories]
|
||||||
|
s = format('Search for %q returned %s %i results in %.2f seconds.%s',
|
||||||
|
meta.searchQuery,
|
||||||
|
meta.estimateIsExact and 'exactly' or 'approximately',
|
||||||
|
meta.estimatedTotalResultsCount,
|
||||||
|
meta.searchTime,
|
||||||
|
categories and format(' Categories include %L.',categories))
|
||||||
|
irc.reply(s)
|
||||||
|
metagoogle = wrap(metagoogle, [getopts({'language':'something',
|
||||||
|
'restrict':'something',
|
||||||
|
'notsafe':'', 'similar':''}),
|
||||||
|
many('something')])
|
||||||
|
|
||||||
|
_cacheUrlRe = re.compile('<code>([^<]+)</code>')
|
||||||
|
def cache(self, irc, msg, args, url):
|
||||||
|
"""<url>
|
||||||
|
|
||||||
|
Returns a link to the cached version of <url> if it is available.
|
||||||
|
"""
|
||||||
|
html = google.doGetCachedPage(url)
|
||||||
|
m = self._cacheUrlRe.search(html)
|
||||||
|
if m is not None:
|
||||||
|
url = m.group(1)
|
||||||
|
url = utils.web.htmlToText(url)
|
||||||
|
irc.reply(url)
|
||||||
|
else:
|
||||||
|
irc.error('Google seems to have no cache for that site.')
|
||||||
|
cache = wrap(cache, ['url'])
|
||||||
|
|
||||||
|
def fight(self, irc, msg, args):
|
||||||
|
"""<search string> <search string> [<search string> ...]
|
||||||
|
|
||||||
|
Returns the results of each search, in order, from greatest number
|
||||||
|
of results to least.
|
||||||
|
"""
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for arg in args:
|
||||||
|
data = search(self.log, [arg])
|
||||||
|
results.append((data.meta.estimatedTotalResultsCount, arg))
|
||||||
|
results.sort()
|
||||||
|
results.reverse()
|
||||||
|
if self.registryValue('bold', msg.args[0]):
|
||||||
|
bold = ircutils.bold
|
||||||
|
else:
|
||||||
|
bold = repr
|
||||||
|
s = ', '.join([format('%s: %i', bold(s), i) for (i, s) in results])
|
||||||
|
irc.reply(s)
|
||||||
|
|
||||||
|
def spell(self, irc, msg, args, word):
|
||||||
|
"""<word>
|
||||||
|
|
||||||
|
Returns Google's spelling recommendation for <word>.
|
||||||
|
"""
|
||||||
|
result = google.doSpellingSuggestion(word)
|
||||||
|
if result:
|
||||||
|
irc.reply(result)
|
||||||
|
else:
|
||||||
|
irc.reply('No spelling suggestion made. This could mean that '
|
||||||
|
'the word you gave is spelled right; it could also '
|
||||||
|
'mean that its spelling was too whacked out even for '
|
||||||
|
'Google to figure out.')
|
||||||
|
spell = wrap(spell, ['text'])
|
||||||
|
|
||||||
|
def stats(self, irc, msg, args):
|
||||||
|
"""takes no arguments
|
||||||
|
|
||||||
|
Returns interesting information about this Google module. Mostly
|
||||||
|
useful for making sure you don't go over your 1000 requests/day limit.
|
||||||
|
"""
|
||||||
|
recent = len(last24hours)
|
||||||
|
time = self.registryValue('state.time')
|
||||||
|
searches = self.registryValue('state.searches')
|
||||||
|
irc.reply(format('This google module has made %n total; '
|
||||||
|
'%i in the past 24 hours. '
|
||||||
|
'Google has spent %.2f seconds searching for me.',
|
||||||
|
(searches, 'search'), recent, time))
|
||||||
|
stats = wrap(stats)
|
||||||
|
|
||||||
|
def googleSnarfer(self, irc, msg, match):
|
||||||
|
r"^google\s+(.*)$"
|
||||||
|
if not self.registryValue('searchSnarfer', msg.args[0]):
|
||||||
|
return
|
||||||
|
searchString = match.group(1)
|
||||||
|
try:
|
||||||
|
data = search(self.log, [searchString], safeSearch=1)
|
||||||
|
except google.NoLicenseKey:
|
||||||
|
return
|
||||||
|
if data.results:
|
||||||
|
url = data.results[0].URL
|
||||||
|
irc.reply(url, prefixName=False)
|
||||||
|
googleSnarfer = urlSnarfer(googleSnarfer)
|
||||||
|
|
||||||
|
_ggThread = re.compile(r'Subject: <b>([^<]+)</b>', re.I)
|
||||||
|
_ggGroup = re.compile(r'<TITLE>Google Groups :\s*([^<]+)</TITLE>', re.I)
|
||||||
|
_ggThreadm = re.compile(r'src="(/group[^"]+)">', re.I)
|
||||||
|
_ggSelm = re.compile(r'selm=[^&]+', re.I)
|
||||||
|
_threadmThread = re.compile(r'TITLE="([^"]+)">', re.I)
|
||||||
|
_threadmGroup = re.compile(r'class=groupname[^>]+>([^<]+)<', re.I)
|
||||||
|
def googleGroups(self, irc, msg, match):
|
||||||
|
r"http://groups.google.[\w.]+/\S+\?(\S+)"
|
||||||
|
if not self.registryValue('groupsSnarfer', msg.args[0]):
|
||||||
|
return
|
||||||
|
queries = cgi.parse_qsl(match.group(1))
|
||||||
|
queries = [q for q in queries if q[0] in ('threadm', 'selm')]
|
||||||
|
if not queries:
|
||||||
|
return
|
||||||
|
queries.append(('hl', 'en'))
|
||||||
|
url = 'http://groups.google.com/groups?' + urllib.urlencode(queries)
|
||||||
|
text = utils.web.getUrl(url)
|
||||||
|
mThread = None
|
||||||
|
mGroup = None
|
||||||
|
if 'threadm=' in url:
|
||||||
|
path = self._ggThreadm.search(text)
|
||||||
|
if path is not None:
|
||||||
|
url = 'http://groups-beta.google.com' + path.group(1)
|
||||||
|
text = utils.web.getUrl(url)
|
||||||
|
mThread = self._threadmThread.search(text)
|
||||||
|
mGroup = self._threadmGroup.search(text)
|
||||||
|
else:
|
||||||
|
mThread = self._ggThread.search(text)
|
||||||
|
mGroup = self._ggGroup.search(text)
|
||||||
|
if mThread and mGroup:
|
||||||
|
irc.reply(format('Google Groups: %s, %s',
|
||||||
|
mGroup.group(1), mThread.group(1)),
|
||||||
|
prefixName=False)
|
||||||
|
else:
|
||||||
|
self.log.debug('Unable to snarf. %s doesn\'t appear to be a '
|
||||||
|
'proper Google Groups page.', match.group(1))
|
||||||
|
googleGroups = urlSnarfer(googleGroups)
|
||||||
|
|
||||||
|
def _googleUrl(self, s):
|
||||||
|
s = s.replace('+', '%2B')
|
||||||
|
s = s.replace(' ', '+')
|
||||||
|
url = r'http://google.com/search?q=' + s
|
||||||
|
return url
|
||||||
|
|
||||||
|
_calcRe = re.compile(r'<td nowrap><font size=\+1><b>(.*?)</b>', re.I)
|
||||||
|
_calcSupRe = re.compile(r'<sup>(.*?)</sup>', re.I)
|
||||||
|
_calcFontRe = re.compile(r'<font size=-2>(.*?)</font>')
|
||||||
|
_calcTimesRe = re.compile(r'×')
|
||||||
|
def calc(self, irc, msg, args, expr):
|
||||||
|
"""<expression>
|
||||||
|
|
||||||
|
Uses Google's calculator to calculate the value of <expression>.
|
||||||
|
"""
|
||||||
|
url = self._googleUrl(expr)
|
||||||
|
html = utils.web.getUrl(url)
|
||||||
|
match = self._calcRe.search(html)
|
||||||
|
if match is not None:
|
||||||
|
s = match.group(1)
|
||||||
|
s = self._calcSupRe.sub(r'^(\1)', s)
|
||||||
|
s = self._calcFontRe.sub(r',', s)
|
||||||
|
s = self._calcTimesRe.sub(r'*', s)
|
||||||
|
irc.reply(s)
|
||||||
|
else:
|
||||||
|
irc.reply('Google\'s calculator didn\'t come up with anything.')
|
||||||
|
calc = wrap(calc, ['text'])
|
||||||
|
|
||||||
|
_phoneRe = re.compile(r'Phonebook.*?<font size=-1>(.*?)<a href')
|
||||||
|
def phonebook(self, irc, msg, args, phonenumber):
|
||||||
|
"""<phone number>
|
||||||
|
|
||||||
|
Looks <phone number> up on Google.
|
||||||
|
"""
|
||||||
|
url = self._googleUrl(phonenumber)
|
||||||
|
html = utils.web.getUrl(url)
|
||||||
|
m = self._phoneRe.search(html)
|
||||||
|
if m is not None:
|
||||||
|
s = m.group(1)
|
||||||
|
s = s.replace('<b>', '')
|
||||||
|
s = s.replace('</b>', '')
|
||||||
|
s = utils.web.htmlToText(s)
|
||||||
|
irc.reply(s)
|
||||||
|
else:
|
||||||
|
irc.reply('Google return not phonebook results.')
|
||||||
|
phonebook = wrap(phonebook, ['text'])
|
||||||
|
|
||||||
|
|
||||||
|
Class = Google
|
||||||
|
|
||||||
|
|
||||||
|
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
120
plugins/Google/test.py
Normal file
120
plugins/Google/test.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
###
|
||||||
|
# Copyright (c) 2002-2004, Jeremiah Fincher
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions, and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions, and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the author of this software nor the name of
|
||||||
|
# contributors to this software may be used to endorse or promote products
|
||||||
|
# derived from this software without specific prior written consent.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
###
|
||||||
|
|
||||||
|
from supybot.test import *
|
||||||
|
|
||||||
|
class GoogleTestCase(ChannelPluginTestCase):
|
||||||
|
plugins = ('Google',)
|
||||||
|
if network:
|
||||||
|
def testCalc(self):
|
||||||
|
self.assertNotRegexp('google calc e^(i*pi)+1', r'didn\'t')
|
||||||
|
|
||||||
|
def testHtmlHandled(self):
|
||||||
|
self.assertNotRegexp('google calc '
|
||||||
|
'the speed of light '
|
||||||
|
'in microns / fortnight', '<sup>')
|
||||||
|
self.assertNotRegexp('google calc '
|
||||||
|
'the speed of light '
|
||||||
|
'in microns / fortnight', '×')
|
||||||
|
|
||||||
|
def testCalcDoesNotHaveExtraSpaces(self):
|
||||||
|
self.assertNotRegexp('google calc 1000^2', r'\s+,\s+')
|
||||||
|
|
||||||
|
def testNoNoLicenseKeyError(self):
|
||||||
|
conf.supybot.plugins.Google.groupsSnarfer.setValue(True)
|
||||||
|
self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'google blah'))
|
||||||
|
self.assertNoResponse(' ')
|
||||||
|
|
||||||
|
def testGroupsSnarfer(self):
|
||||||
|
orig = conf.supybot.plugins.Google.groupsSnarfer()
|
||||||
|
try:
|
||||||
|
conf.supybot.plugins.Google.groupsSnarfer.setValue(True)
|
||||||
|
# This should work, and does work in practice, but is failing
|
||||||
|
# in the tests.
|
||||||
|
#self.assertSnarfRegexp(
|
||||||
|
# 'http://groups.google.com/groups?dq=&hl=en&lr=lang_en&'
|
||||||
|
# 'ie=UTF-8&oe=UTF-8&selm=698f09f8.0310132012.738e22fc'
|
||||||
|
# '%40posting.google.com',
|
||||||
|
# r'comp\.lang\.python.*question: usage of __slots__')
|
||||||
|
self.assertSnarfRegexp(
|
||||||
|
'http://groups.google.com/groups?selm=ExDm.8bj.23'
|
||||||
|
'%40gated-at.bofh.it&oe=UTF-8&output=gplain',
|
||||||
|
r'linux\.kernel.*NFS client freezes')
|
||||||
|
self.assertSnarfRegexp(
|
||||||
|
'http://groups.google.com/groups?q=kernel+hot-pants&'
|
||||||
|
'hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=1.5.4.32.199703131'
|
||||||
|
'70853.00674d60%40adan.kingston.net&rnum=1',
|
||||||
|
r'Madrid Bluegrass Ramble')
|
||||||
|
self.assertSnarfRegexp(
|
||||||
|
'http://groups.google.com/groups?selm=1.5.4.32.19970'
|
||||||
|
'313170853.00674d60%40adan.kingston.net&oe=UTF-8&'
|
||||||
|
'output=gplain',
|
||||||
|
r'Madrid Bluegrass Ramble')
|
||||||
|
self.assertSnarfRegexp(
|
||||||
|
'http://groups.google.com/groups?dq=&hl=en&lr=&'
|
||||||
|
'ie=UTF-8&threadm=mailman.1010.1069645289.702.'
|
||||||
|
'python-list%40python.org&prev=/groups%3Fhl%3Den'
|
||||||
|
'%26lr%3D%26ie%3DUTF-8%26group%3Dcomp.lang.python',
|
||||||
|
r'comp\.lang\.python.*What exactly are bound')
|
||||||
|
# Test for Bug #1002547
|
||||||
|
self.assertSnarfRegexp(
|
||||||
|
'http://groups.google.com/groups?q=supybot+is+the&'
|
||||||
|
'hl=en&lr=&ie=UTF-8&c2coff=1&selm=1028329672'
|
||||||
|
'%40freshmeat.net&rnum=9',
|
||||||
|
r'fm\.announce.*SupyBot')
|
||||||
|
finally:
|
||||||
|
conf.supybot.plugins.Google.groupsSnarfer.setValue(orig)
|
||||||
|
|
||||||
|
def testConfig(self):
|
||||||
|
orig = conf.supybot.plugins.Google.groupsSnarfer()
|
||||||
|
try:
|
||||||
|
conf.supybot.plugins.Google.groupsSnarfer.setValue(False)
|
||||||
|
self.assertSnarfNoResponse(
|
||||||
|
'http://groups.google.com/groups?dq=&hl=en&lr=lang_en&'
|
||||||
|
'ie=UTF-8&oe=UTF-8&selm=698f09f8.0310132012.738e22fc'
|
||||||
|
'%40posting.google.com')
|
||||||
|
conf.supybot.plugins.Google.groupsSnarfer.setValue(True)
|
||||||
|
self.assertSnarfNotError(
|
||||||
|
'http://groups.google.com/groups?dq=&hl=en&lr=lang_en&'
|
||||||
|
'ie=UTF-8&oe=UTF-8&selm=698f09f8.0310132012.738e22fc'
|
||||||
|
'%40posting.google.com')
|
||||||
|
finally:
|
||||||
|
conf.supybot.plugins.Google.groupsSnarfer.setValue(orig)
|
||||||
|
|
||||||
|
def testInvalidKeyCaught(self):
|
||||||
|
conf.supybot.plugins.Google.licenseKey.set(
|
||||||
|
'abcdefghijklmnopqrstuvwxyz123456')
|
||||||
|
self.assertNotRegexp('google foobar', 'faultType')
|
||||||
|
self.assertNotRegexp('google foobar', 'SOAP')
|
||||||
|
|
||||||
|
def testStats(self):
|
||||||
|
self.assertNotError('google stats')
|
||||||
|
|
||||||
|
|
||||||
|
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
Loading…
Reference in New Issue
Block a user