Lynn Rees avatar Lynn Rees committed 364297f

[svn]

Comments (0)

Files changed (1)

trunk/wsgiakismet.py

+# Based on akisimetVersion 0.1.3
+# 2006/07/18
+
+# Copyright Michael Foord 2005 & 2006
+# Copyright L.C. Rees 2006
+# akismet.py
+# Python interface to the akismet API
+
+# http://www.voidspace.org.uk/python/modules.shtml
+# http://akismet.com
+
+# Released subject to the BSD License
+# Please see http://www.voidspace.org.uk/python/license.shtml
+
+# For information about bugfixes, updates and support, please join the Pythonutils mailing list.
+# http://groups.google.com/group/pythonutils/
+# Comments, suggestions and bug reports welcome.
+# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
+# E-mail fuzzyman@voidspace.org.uk
+
+'''A python interface to the `Akismet <http://akismet.com>`_ 
+{acro;API;Application Programmers Interface}. This is a web service for
+blocking SPAM comments to blogs - or other online services.
+
+You will need a Wordpress API key, from `wordpress.com <http://wordpress.com>`_.
+
+You should pass in the keyword argument 'agent' to the name of your program,
+when you create an Akismet instance. This sets the ``user-agent`` to a useful
+value.
+
+The default is : ::
+
+    Python Interface by Fuzzyman | akismet.py/0.1.3
+
+Whatever you pass in, will replace the *Python Interface by Fuzzyman* part.
+**0.1.2** will change with the version of this interface.
+'''
+
+import cgi
+import urllib2
+import socket
+from urllib import urlencode
+from StringIO import StringIO
+
+__all__ = ('__version__', 'Akismet', 'AkismetError', 'APIKeyError')
+__version__ = '0.1.3'
+__author__ = 'Michael Foord <fuzzyman AT voidspace DOT org DOT uk>'
+__docformat__ = 'restructuredtext en'
+user_agent = '%s | wsgiakismet.py/%s'
+DEFAULTAGENT = 'Python Interface by Fuzzyman/%s'
+
+def _formparse(environ, strict=False):
+    '''Extracts data from form submissions.
+
+    @param environ Environment dictionary
+    @param strict Stops on errors (default: False)
+    '''
+    winput = environ['wsgi.input']
+    if hasattr(winput, 'getvalue'):
+        tinput = winput.getvalue()
+    else:
+        tinput = winput.read()
+        environ['wsgi.input'] = StringIO(tinput)
+    qdict = cgi.parse(StrinIO(tinput), environ, strict, strict)
+    # Remove invididual entries from list and store as naked string
+    for key, value in qdict.iteritems():
+        if len(value) == 1: qdict[key] = value[0]
+    return qdict
+
+def _handler(environ, start_response):
+    '''Replace this default handler.'''
+    start_response('200 OK', [('Content-type', 'text/plain')])
+    return ['Comment was spam.']
+
+def akismet(key, blog_url, **kw):
+    def decorator(application):
+        return WsgiAkismet(application, key, blog_url, **kw)
+    return decorator
+
+
+class WsgiAkismet(object):
+    
+    '''WSGI middleware for working with the akismet API'''
+
+    baseurl = 'rest.akismet.com/1.1/'
+    vals = set(['comment_type', 'comment_author', 'comment_author_email',
+            'comment_author_url', 'permalink'])
+
+    def __init__(self, application, key, blog_url, **kw):
+        self.application = application
+        agent = kw.get('agent', DEFAULTAGENT % __version__)
+        self.user_agent = user_agent % (agent, __version__)
+        self.comment_key = kw.get('comment', 'comment')
+        self.handler = kw.get('handler', _handler)
+        self.key, self.blog_url = key, blog_url
+        if hasattr(socket, 'setdefaulttimeout'):
+            # Set the default timeout on sockets to 5 seconds
+            socket.setdefaulttimeout(kw.get('timeout', 5))
+
+    def __call__(self, environ, start_response):
+        formdata = _formparse(environ)
+        comment = formdata[self.comment_key]
+        data = self._getvalues(formdata)
+        if self.comment_check(environ, comment, data):
+            return self.application(environ, start_response)
+        else:
+            return self.handler(environ, start_response)
+
+    def _getvalues(self, data):        
+        return dict((k, data[k]) for k in data if k in self.vals)
+
+    def _build_data(self, environ, comment, data):
+        '''This function builds the data structure required by ``comment_check``,
+        ``submit_spam``, and ``submit_ham``.
+        
+        It modifies the ``data`` dictionary you give it in place. (and so
+        doesn't return anything)
+        
+        It raises an ``AkismetError`` if the user IP or user-agent can't be
+        worked out.
+        '''
+        data['comment_content'] = comment
+        data['user_ip'] = environ['REMOTE_ADDR']
+        data['user_agent'] = environ['HTTP_USER_AGENT']
+        data['referrer'] = environ.get('HTTP_REFERER', 'unknown')
+        data.setdefault('permalink', '')
+        data.setdefault('comment_type', 'comment')
+        data.setdefault('comment_author', '')
+        data.setdefault('comment_author_email', '')
+        data.setdefault('comment_author_url', '')
+        data['SERVER_ADDR'] = environ.get('SERVER_ADDR', '')
+        data['SERVER_ADMIN'] = environ.get('SERVER_ADMIN', '')
+        data['SERVER_NAME'] = environ.get('SERVER_NAME', '')
+        data['SERVER_PORT'] = environ.get('SERVER_PORT', '')
+        data['SERVER_SIGNATURE'] = environ.get('SERVER_SIGNATURE', '')
+        data['SERVER_SOFTWARE'] = environ.get('SERVER_SOFTWARE', '')
+        data['HTTP_ACCEPT'] = environ.get('HTTP_ACCEPT', '')
+        data['blog'] = self.blog_url
+
+    def comment_check(self, environ, comment, data):
+        '''This is the function that checks comments.
+        
+        It returns ``True`` for spam and ``False`` for ham.
+        It raises ``APIKeyError`` if you have not yet set an API key.
+        
+        If the connection to Akismet fails then the ``HTTPError`` or
+        ``URLError`` will be propogated.
+        
+        As a minimum it requires the body of the comment. This is the
+        ``comment`` argument.
+        
+        Akismet requires some other arguments, and allows some optional ones.
+        The more information you give it, the more likely it is to be able to
+        make an accurate diagnosise.
+        
+        You supply these values using a mapping object (dictionary) as the
+        ``data`` argument.
+        
+        If ``build_data`` is ``True`` (the default), then *akismet.py* will
+        attempt to fill in as much information as possible, using default
+        values where necessary. This is particularly useful for programs
+        running in a {acro;CGI} environment. A lot of useful information
+        can be supplied from evironment variables (``os.environ``). See below.
+        
+        You *only* need supply values for which you don't want defaults filled
+        in for. All values must be strings.
+        
+        There are a few required values. If they are not supplied, and
+        defaults can't be worked out, then an ``AkismetError`` is raised.
+        
+        If you set ``build_data=False`` and a required value is missing an
+        ``AkismetError`` will also be raised.
+        
+        The normal values (and defaults) are as follows : ::
+        
+            'user_ip':          environ['REMOTE_ADDR']       (*)
+            'user_agent':       environ['HTTP_USER_AGENT']   (*)
+            'referrer':         environ.get('HTTP_REFERER', 'unknown') [#]_
+            'permalink':        ''
+            'comment_type':     'comment' [#]_
+            'comment_author':   ''
+            'comment_author_email': ''
+            'comment_author_url': ''
+            'SERVER_ADDR':      environ.get('SERVER_ADDR', '')
+            'SERVER_ADMIN':     environ.get('SERVER_ADMIN', '')
+            'SERVER_NAME':      environ.get('SERVER_NAME', '')
+            'SERVER_PORT':      environ.get('SERVER_PORT', '')
+            'SERVER_SIGNATURE': environ.get('SERVER_SIGNATURE', '')
+            'SERVER_SOFTWARE':  environ.get('SERVER_SOFTWARE', '')
+            'HTTP_ACCEPT':      environ.get('HTTP_ACCEPT', '')
+        
+        (*) Required values
+        
+        You may supply as many additional 'HTTP_*' type values as you wish.
+        These should correspond to the http headers sent with the request.
+        
+        .. [#] Note the spelling 'referrer'. This is a required value by the
+            akismet api - however, referrer information is not always
+            supplied by the browser or server. In fact the HTTP protocol
+            forbids relying on referrer information for functionality in 
+            programs.
+        .. [#] The `API docs <http://akismet.com/development/api/>`_ state that this value
+            can be ' *blank, comment, trackback, pingback, or a made up value*
+            *like 'registration'* '.
+        '''
+        self._build_data(environ, comment, data)
+        url = '%scomment-check' % ('http://%s.%s' % (self.key, self.baseurl))
+        # we *don't* trap the error here
+        # so if akismet is down it will raise an HTTPError or URLError
+        headers = {'User-Agent':self.user_agent}
+        req = urllib2.Request(url, urlencode(data), headers)
+        try:
+            h = urllib2.urlopen(req)
+            resp = h.read().lower()
+            if resp == 'true': return True
+            return False
+        except:
+            return False
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.