From 760c9d64ddcfaab372381ecf392aa7317cfb8bc4 Mon Sep 17 00:00:00 2001 From: Garret Wassermann Date: Tue, 1 Jan 2019 19:55:08 -0500 Subject: [PATCH] Remove bundled akismet in favor of pypi package dependency, for #687 --- helpdesk/akismet.py | 373 -------------------------------------------- helpdesk/lib.py | 2 +- requirements.txt | 1 + 3 files changed, 2 insertions(+), 374 deletions(-) delete mode 100644 helpdesk/akismet.py diff --git a/helpdesk/akismet.py b/helpdesk/akismet.py deleted file mode 100644 index 31564b92..00000000 --- a/helpdesk/akismet.py +++ /dev/null @@ -1,373 +0,0 @@ -# Version 0.2.0 -# 2009/06/18 - -# Copyright Michael Foord 2005-2009 -# akismet.py -# Python interface to the akismet API -# E-mail fuzzyman@voidspace.org.uk - -# http://www.voidspace.org.uk/python/modules.shtml -# http://akismet.com - -# Released subject to the BSD License -# See http://www.voidspace.org.uk/python/license.shtml - -# Updated by django-helpdesk developers, 2018 -# to be compatible with python 3 - - -""" -A python interface to the `Akismet `_ API. -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 `_. - -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.2.0 - -Whatever you pass in, will replace the *Python Interface by Fuzzyman* part. -**0.2.0** will change with the version of this interface. - -Usage example:: - - from akismet import Akismet - - api = Akismet(agent='Test Script') - # if apikey.txt is in place, - # the key will automatically be set - # or you can call api.setAPIKey() - # - if api.key is None: - print >> sys.stderr, "No 'apikey.txt' file." - elif not api.verify_key(): - print >> sys.stderr, "The API key is invalid." - else: - # data should be a dictionary of values - # They can all be filled in with defaults - # from a CGI environment - if api.comment_check(comment, data): - print >> sys.stderr, 'This comment is spam.' - else: - print >> sys.stderr, 'This comment is ham.' -""" - - -import os -try: - from urllib import urlencode # python2 -except ImportError: - from urllib.parse import urlencode # python3 - -import socket -if hasattr(socket, 'setdefaulttimeout'): - # Set the default timeout on sockets to 5 seconds - socket.setdefaulttimeout(5) - -__version__ = '0.2.0' - -__all__ = ( - '__version__', - 'Akismet', - 'AkismetError', - 'APIKeyError', -) - -__author__ = 'Michael Foord ' - -__docformat__ = "restructuredtext en" - -user_agent = "%s | akismet.py/%s" -DEFAULTAGENT = 'Python Interface by Fuzzyman/%s' - -isfile = os.path.isfile - -urllib2 = None -try: - from google.appengine.api import urlfetch -except ImportError: - import urllib2 - -if urllib2 is None: - def _fetch_url(url, data, headers): - req = urlfetch.fetch(url=url, payload=data, method=urlfetch.POST, headers=headers) - if req.status_code == 200: - return req.content - raise Exception('Could not fetch Akismet URL: %s Response code: %s' % - (url, req.status_code)) -else: - def _fetch_url(url, data, headers): - req = urllib2.Request(url, data, headers) - h = urllib2.urlopen(req) - resp = h.read() - return resp - - -class AkismetError(Exception): - """Base class for all akismet exceptions.""" - pass - - -class APIKeyError(AkismetError): - """Invalid API key.""" - pass - - -class Akismet(object): - """A class for working with the akismet API""" - - baseurl = 'rest.akismet.com/1.1/' - - def __init__(self, key=None, blog_url=None, agent=None): - """Automatically calls ``setAPIKey``.""" - if agent is None: - agent = DEFAULTAGENT % __version__ - self.user_agent = user_agent % (agent, __version__) - self.setAPIKey(key, blog_url) - - def _getURL(self): - """ - Fetch the url to make requests to. - - This comprises of api key plus the baseurl. - """ - return 'http://%s.%s' % (self.key, self.baseurl) - - def _safeRequest(self, url, data, headers): - try: - resp = _fetch_url(url, data, headers) - except Exception as e: - raise AkismetError(str(e)) - return resp - - def setAPIKey(self, key=None, blog_url=None): - """ - Set the wordpress API key for all transactions. - - If you don't specify an explicit API ``key`` and ``blog_url`` it will - attempt to load them from a file called ``apikey.txt`` in the current - directory. - - This method is *usually* called automatically when you create a new - ``Akismet`` instance. - """ - if key is None and isfile('apikey.txt'): - the_file = [l.strip() for l in open('apikey.txt').readlines() - if l.strip() and not l.strip().startswith('#')] - try: - self.key = the_file[0] - self.blog_url = the_file[1] - except IndexError: - raise APIKeyError("Your 'apikey.txt' is invalid.") - else: - self.key = key - self.blog_url = blog_url - - def verify_key(self): - """ - This equates to the ``verify-key`` call against the akismet API. - - It returns ``True`` if the key is valid. - - The docs state that you *ought* to call this at the start of the - transaction. - - It raises ``APIKeyError`` if you have not yet set an API key. - - If the connection to akismet fails, it allows the normal ``HTTPError`` - or ``URLError`` to be raised. - (*akismet.py* uses `urllib2 `_) - """ - if self.key is None: - raise APIKeyError("Your have not set an API key.") - data = {'key': self.key, 'blog': self.blog_url} - # this function *doesn't* use the key as part of the URL - url = 'http://%sverify-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} - resp = self._safeRequest(url, urlencode(data), headers) - if resp.lower() == 'valid': - return True - else: - return False - - def _build_data(self, 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 - if 'user_ip' not in data: - try: - val = os.environ['REMOTE_ADDR'] - except KeyError: - raise AkismetError("No 'user_ip' supplied") - data['user_ip'] = val - if 'user_agent' not in data: - try: - val = os.environ['HTTP_USER_AGENT'] - except KeyError: - raise AkismetError("No 'user_agent' supplied") - data['user_agent'] = val - # - data.setdefault('referrer', os.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.setdefault('SERVER_ADDR', os.environ.get('SERVER_ADDR', '')) - data.setdefault('SERVER_ADMIN', os.environ.get('SERVER_ADMIN', '')) - data.setdefault('SERVER_NAME', os.environ.get('SERVER_NAME', '')) - data.setdefault('SERVER_PORT', os.environ.get('SERVER_PORT', '')) - data.setdefault('SERVER_SIGNATURE', os.environ.get('SERVER_SIGNATURE', '')) - data.setdefault('SERVER_SOFTWARE', os.environ.get('SERVER_SOFTWARE', '')) - data.setdefault('HTTP_ACCEPT', os.environ.get('HTTP_ACCEPT', '')) - data.setdefault('blog', self.blog_url) - - def comment_check(self, comment, data=None, build_data=True, DEBUG=False): - """ - This is the function that checks comments. - - It returns ``True`` for spam and ``False`` for ham. - - If you set ``DEBUG=True`` then it will return the text of the response, - instead of the ``True`` or ``False`` object. - - 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': os.environ['REMOTE_ADDR'] (*) - 'user_agent': os.environ['HTTP_USER_AGENT'] (*) - 'referrer': os.environ.get('HTTP_REFERER', 'unknown') [#]_ - 'permalink': '' - 'comment_type': 'comment' [#]_ - 'comment_author': '' - 'comment_author_email': '' - 'comment_author_url': '' - 'SERVER_ADDR': os.environ.get('SERVER_ADDR', '') - 'SERVER_ADMIN': os.environ.get('SERVER_ADMIN', '') - 'SERVER_NAME': os.environ.get('SERVER_NAME', '') - 'SERVER_PORT': os.environ.get('SERVER_PORT', '') - 'SERVER_SIGNATURE': os.environ.get('SERVER_SIGNATURE', '') - 'SERVER_SOFTWARE': os.environ.get('SERVER_SOFTWARE', '') - 'HTTP_ACCEPT': os.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 `_ state that this value - can be " *blank, comment, trackback, pingback, or a made up value* - *like 'registration'* ". - """ - if self.key is None: - raise APIKeyError("Your have not set an API key.") - if data is None: - data = {} - if build_data: - self._build_data(comment, data) - if 'blog' not in data: - data['blog'] = self.blog_url - url = '%scomment-check' % self._getURL() - # 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} - resp = self._safeRequest(url, urlencode(data), headers) - if DEBUG: - return resp - resp = resp.lower() - if resp == 'true': - return True - elif resp == 'false': - return False - else: - # NOTE: Happens when you get a 'howdy wilbur' response ! - raise AkismetError('missing required argument.') - - def submit_spam(self, comment, data=None, build_data=True): - """ - This function is used to tell akismet that a comment it marked as ham, - is really spam. - - It takes all the same arguments as ``comment_check``, except for - *DEBUG*. - """ - if self.key is None: - raise APIKeyError("Your have not set an API key.") - if data is None: - data = {} - if build_data: - self._build_data(comment, data) - url = '%ssubmit-spam' % self._getURL() - # 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} - self._safeRequest(url, urlencode(data), headers) - - def submit_ham(self, comment, data=None, build_data=True): - """ - This function is used to tell akismet that a comment it marked as spam, - is really ham. - - It takes all the same arguments as ``comment_check``, except for - *DEBUG*. - """ - if self.key is None: - raise APIKeyError("Your have not set an API key.") - if data is None: - data = {} - if build_data: - self._build_data(comment, data) - url = '%ssubmit-ham' % self._getURL() - # 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} - self._safeRequest(url, urlencode(data), headers) diff --git a/helpdesk/lib.py b/helpdesk/lib.py index 6d507a34..54dd1124 100644 --- a/helpdesk/lib.py +++ b/helpdesk/lib.py @@ -167,7 +167,7 @@ def text_is_spam(text, request): from django.contrib.sites.models import Site from django.core.exceptions import ImproperlyConfigured try: - from helpdesk.akismet import Akismet + from akismet import Akismet except ImportError: return False try: diff --git a/requirements.txt b/requirements.txt index 0ce6e45d..72cf63f8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ django-bootstrap4-form celery django-celery-beat email-reply-parser +akismet django-markdown-deux beautifulsoup4 lxml