--- nospam.py.orig	2006-04-14 14:12:17.000000000 -0400
+++ nospam.py	2006-09-23 13:01:05.691442716 -0400
@@ -3,8 +3,8 @@
 Based on a idea and ref impl of Jesus Roncero Franco <jesus at roncero.org>.
 Implemented as a pyblosxom plugin by Steven Armstrong <sa at c-area.ch>.
 
-Creates a random number, generates an image of it, and stores the number
-in the session. Then compares the number the user entered in the comment
+Creates a random string, generates an image of it, and stores the string 
+in the session. Then compares the string the user entered in the comment
 form with the one stored in the session. Rejects the comment if they 
 don't match.
 
@@ -38,8 +38,8 @@
 
 Add something like this to your comment-form.html template:
 <label for="nospam">Secret Number:</label>
-<img src="$base_url/nospam.png" alt="Secret Number Image" title="Type this number into the field on the right" />
-<input name="nospam" id="nospam" type="text" value="" maxlength="5" style="width:5em" />
+<img src="$base_url/nospam.png" alt="Secret Number Image" title="Type this code into the field on the right" />
+<input name="nospam" id="nospam" type="text" value="" maxlength="10" style="width:10em" />
 
 
 Dependecies:
@@ -61,6 +61,9 @@
 import sys
 import os
 import random
+import string
+import math
+
 from Pyblosxom import tools
 
 # PIL imports http://www.pythonware.com/products/pil/
@@ -68,6 +71,7 @@
     import Image
     import ImageDraw
     import ImageOps
+    import ImageEnhance
     try:
         import ImageFont
     except ImportError:
@@ -82,7 +86,7 @@
 _bgColor = (255,255,255) # White
 _gridInk = (200,200,200)
 _fontInk = (130,130,130)
-_fontSize = 14
+_fontSize = 32 
 # set in cb_start callback
 _fontPath = None
 
@@ -146,10 +150,63 @@
     
     return retval
 
+# this function is (c) Benjamin Mako Hill <mako@atdot.cc>
+# generate the unqiue string
+def _generateString():
+    import string
+    import random
+
+    chars = string.ascii_lowercase + string.digits
+    secret_string = ""
+
+    # generate a string that is 5-7 characters long 
+    string_len = random.randint(5,7)
+
+    while (len(secret_string) < string_len ):
+        char = chars[random.randint(0,35)]
+	
+        # skip a number of potentially confusable characters
+        if char in ['l', '1', 'i', 'j', 'o', '0', 'u', 'v', 'd', '5', 's', 'f', 't' ]:
+            continue
+        secret_string += char
+    return secret_string
+
+# This function is (c) Neil Harris
+# Taken from Mediawiki's ConfirmEdit extension's captcha.py
+# Modified and incorporated by Benjamin Mako Hill <mako@atdot.cc>
+#
+# Does X-axis wobbly copy, sandwiched between two rotates
+def _wobbly_copy(src, wob, col, scale, ang):
+    x, y = src.size
+    f = random.uniform(4*scale, 5*scale)
+    p = random.uniform(0, math.pi*2)
+    rr = ang+random.uniform(-30, 30) # vary, but not too much
+    int_d = Image.new('RGB', src.size, 0) # a black rectangle
+    rot = src.rotate(rr, Image.BILINEAR)
+    # Do a cheap bounding-box op here to try to limit work below
+    bbx = rot.getbbox()
+    if bbx == None:
+        print "whoops"
+        return src
+    else:
+        l, t, r, b= bbx
 
-# This function is (c) Jesus Roncero Franco <jesus at roncero.org>.
-# Modified to support old and new PIL versions by Steven Armstrong.
-def _generateImage(number):
+    # and only do lines with content on
+    for i in range(t, b+1):
+        # Drop a scan line in
+        xoff = int(math.sin(p+(i*f/y))*wob)
+        xoff += int(random.uniform(-wob*0.5, wob*0.5))
+        int_d.paste(rot.crop((0, i, x, i+1)), (xoff, i))
+
+    # try to stop blurring from building up
+    int_d = int_d.rotate(-rr, Image.BILINEAR)
+    enh = ImageEnhance.Sharpness(int_d)
+    return enh.enhance(2)
+
+# This function is (c) Neil Harris
+# Taken from Mediawiki's ConfirmEdit extension's captcha.py
+# Modified and incorporated by Benjamin Mako Hill <mako@atdot.cc>
+def _generateImage(text):
     try:
         # recent PIL version with support for truetype fonts
         font = ImageFont.truetype(_fontPath, _fontSize)
@@ -157,48 +214,52 @@
         # old PIL version, fallback to pil fonts
         font = ImageFont.load(_fontPath)
 
-    img = Image.new("RGB", _imageSize, _bgColor)
-    draw = ImageDraw.Draw(img)
-    
-    xsize, ysize = img.size
-
-    # Do we want the grid start at 0,0 or want some offset?
-    x, y = 0,0
-    
-    while x <= xsize:
-        try:
-            # recent PIL version
-            draw.line(((x, 0), (x, ysize)), fill=_gridInk)
-        except TypeError:
-            # old PIL version
-            draw.setink(_gridInk)
-            draw.line(((x, 0), (x, ysize)))
-        x = x + _xstep 
-    while y <= ysize:
-        try:
-            draw.line(((0, y), (xsize, y)), fill=_gridInk)
-        except TypeError:
-            draw.setink(_gridInk)
-            draw.line(((0, y), (xsize, y)))
-        y = y + _ystep 
-    
-    try:
-        draw.text((10, 2), number, font=font, fill=_fontInk)
-    except TypeError:
-        draw.setink(_fontInk)
-        draw.text((10, 2), number, font=font)
+    """Generate a captcha image"""
+    # white text on a black background
+    bgcolor = 0x0
+    fgcolor = 0xffffff
+
+    # determine dimensions of the text
+    dim = font.getsize(text)
+
+    # create a new image significantly larger that the text
+    edge = max(dim[0], dim[1]) + 2*min(dim[0], dim[1])
+
+    im = Image.new('RGB', (edge, edge), bgcolor)
+    d = ImageDraw.Draw(im)
+    x, y = im.size
+    # add the text to the image
+    d.text((x/2-dim[0]/2, y/2-dim[1]/2), text, font=font, fill=fgcolor)
+    k = 3
+    wob = 0.20*dim[1]/k
+    rot = 45
+    # Apply lots of small stirring operations, rather than a few large ones
+    # in order to get some uniformity of treatment, whilst
+    # maintaining randomness
+    for i in range(k):
+        im = _wobbly_copy(im, wob, bgcolor, i*2+3, rot+0)
+        im = _wobbly_copy(im, wob, bgcolor, i*2+1, rot+45)
+        im = _wobbly_copy(im, wob, bgcolor, i*2+2, rot+90)
+        rot += 30
+
+    # now get the bounding box of the nonzero parts of the image
+    bbox = im.getbbox()
+    bord = min(dim[0], dim[1])/4 # a bit of a border
+    im = im.crop((bbox[0]-bord, bbox[1]-bord, bbox[2]+bord, bbox[3]+bord))
+    # and turn into black on white
+    im = ImageOps.invert(im)
 
-    return img
+    return(im)
 
 
 def _writeImage(request):
-    number = str(random.randrange(1,99999,1))
+    secret_string = _generateString()
 
     session = request.getSession()
-    session["nospam"] = number
+    session["nospam"] = secret_string
     session.save()
 
-    image = _generateImage(number)
+    image = _generateImage(secret_string)
 
     response = request.getResponse()
     response.addHeader('Content-Type', 'image/png')
@@ -259,7 +320,7 @@
 
 def cb_comment_reject(args):
     """
-    Checks if the the nospam number of the incomming request 
+    Checks if the the nospam code of the incoming request 
     matches the one stored in the session.
     Creates a template variable $cmt_nospam_error with a 
     error message if it didn't.
@@ -285,11 +346,11 @@
     allow_trackback = config.get('nospam_allow_trackback', 0)
     
     try:
-        nospam = int(form["nospam"].value)
-        sess_nospam = int(session["nospam"])
+        nospam = form["nospam"].value
+        sess_nospam = session["nospam"]
     except:
-        nospam = 0
-        sess_nospam = 1
+        nospam = "0"
+        sess_nospam = "1"
 
     if allow_trackback:
         comment = args['comment']
@@ -300,7 +361,7 @@
         
     if nospam != sess_nospam:
         _remember_comment(request)
-        data["cmt_nospam_error"] = "Secret number did not match."
+        data["cmt_nospam_error"] = "Secret code did not match."
         return True
     else:
         _forget_comment(request)
