31 from optparse
import OptionParser
38 from PIL
import ImageFont
39 from PIL
import ImageDraw
40 from PIL
import ImageEnhance
41 from PIL
import ImageOps
42 from PIL
import ImageMath
44 sys.exit(
"This script requires the Python Imaging Library - http://www.pythonware.com/products/pil/")
46 nonalpha = re.compile(
'[^a-z]')
51 f = random.uniform(4*scale, 5*scale)
52 p = random.uniform(0, math.pi*2)
53 rr = ang+random.uniform(-10, 10)
54 int_d = Image.new(
'RGB', src.size, 0)
55 rot = src.rotate(rr, Image.BILINEAR)
63 for i
in range(t, b+1):
65 xoff = int(math.sin(p+(i*f/y))*wob)
66 xoff += int(random.uniform(-wob*0.5, wob*0.5))
67 int_d.paste(rot.crop((0, i, x, i+1)), (xoff, i))
69 int_d = int_d.rotate(-rr, Image.BILINEAR)
70 enh = ImageEnhance.Sharpness(int_d)
75 """Generate a captcha image"""
80 font = ImageFont.truetype(fontname,fontsize)
82 dim = font.getsize(text)
84 edge = max(dim[0], dim[1]) + 2*min(dim[0], dim[1])
85 im = Image.new(
'RGB', (edge, edge), bgcolor)
86 d = ImageDraw.Draw(im)
89 d.text((x/2-dim[0]/2, y/2-dim[1]/2), text, font=font, fill=fgcolor)
104 bord = min(dim[0], dim[1])/4
105 im = im.crop((bbox[0]-bord, bbox[1]-bord, bbox[2]+bord, bbox[3]+bord))
109 nsize = (im.size[0] / nblock, im.size[1] / nblock)
110 noise = Image.new(
'L', nsize, bgcolor)
112 for x
in range(nsize[0]):
113 for y
in range(nsize[1]):
114 r = random.randint(0, 65)
115 gradient = 70 * x / nsize[0]
116 data[x, y] = r + gradient
118 noise = noise.resize(im.size, Image.BILINEAR)
120 im = ImageMath.eval(
'convert(convert(a, "L") / 3 + b, "RGB")', a=im, b=noise)
123 im = ImageOps.invert(im)
129 """Generate a subdirectory path out of the first _levels_
130 characters of _hash_, and ensure the directories exist
133 for i
in range(0, levels):
136 subdir = os.path.join(subdir, char)
139 fulldir = os.path.join(basedir, subdir)
140 if not os.path.exists(fulldir):
144 def try_pick_word(words, blacklist, verbose, nwords, min_length, max_length):
145 if words
is not None:
146 word = words[random.randint(0,len(words)-1)]
148 word2 = words[random.randint(0,len(words)-1)]
153 max_length = max_length
if max_length > 0
else 10
154 for i
in range(0, random.randint(min_length, max_length)):
155 word = word + chr(97 + random.randint(0,25))
158 print(
"word is %s" % word)
160 if len(word) < min_length:
162 print(
"skipping word pair '%s' because it has fewer than %d characters" % (word, min_length))
165 if max_length > 0
and len(word) > max_length:
167 print(
"skipping word pair '%s' because it has more than %d characters" % (word, max_length))
170 if nonalpha.search(word):
172 print(
"skipping word pair '%s' because it contains non-alphabetic characters" % word)
175 for naughty
in blacklist:
178 print(
"skipping word pair '%s' because it contains blacklisted word '%s'" % (word, naughty))
182 def pick_word(words, blacklist, verbose, nwords, min_length, max_length):
183 for x
in range(1000):
184 word =
try_pick_word(words, blacklist, verbose, nwords, min_length, max_length)
187 sys.exit(
"Unable to find valid word combinations")
191 words = [x.strip().lower()
for x
in f.readlines()]
195 if __name__ ==
'__main__':
196 """This grabs random words from the dictionary 'words' (one
197 word per line) and generates a captcha image for each one,
198 with a keyed salted hash of the correct answer in the filename.
200 To check a reply, hash it in the same way with the same salt and
201 secret key, then compare with the hash value given.
203 script_dir = os.path.dirname(os.path.realpath(__file__))
204 parser = OptionParser()
205 parser.add_option(
"--wordlist", help=
"A list of words (required)", metavar=
"WORDS.txt")
206 parser.add_option(
"--random", help=
"Use random charcters instead of a wordlist", action=
"store_true")
207 parser.add_option(
"--key", help=
"The passphrase set as $wgCaptchaSecret (required)", metavar=
"KEY")
208 parser.add_option(
"--output", help=
"The directory to put the images in - $wgCaptchaDirectory (required)", metavar=
"DIR")
209 parser.add_option(
"--font", help=
"The font to use (required)", metavar=
"FONT.ttf")
210 parser.add_option(
"--font-size", help=
"The font size (default 40)", metavar=
"N", type=
'int', default=40)
211 parser.add_option(
"--count", help=
"The maximum number of images to make (default 20)", metavar=
"N", type=
'int', default=20)
212 parser.add_option(
"--blacklist", help=
"A blacklist of words that should not be used", metavar=
"FILE", default=os.path.join(script_dir,
"blacklist"))
213 parser.add_option(
"--fill", help=
"Fill the output directory to contain N files, overrides count, cannot be used with --dirs", metavar=
"N", type=
'int')
214 parser.add_option(
"--dirs", help=
"Put the images into subdirectories N levels deep - $wgCaptchaDirectoryLevels", metavar=
"N", type=
'int')
215 parser.add_option(
"--verbose",
"-v", help=
"Show debugging information", action=
'store_true')
216 parser.add_option(
"--number-words", help=
"Number of words from the wordlist which make a captcha challenge (default 2)", type=
'int', default=2)
217 parser.add_option(
"--min-length", help=
"Minimum length for a captcha challenge", type=
'int', default=1)
218 parser.add_option(
"--max-length", help=
"Maximum length for a captcha challenge", type=
'int', default=-1)
220 opts, args = parser.parse_args()
223 wordlist = opts.wordlist
227 sys.exit(
"Need to specify a wordlist")
231 sys.exit(
"Need to specify a key")
235 sys.exit(
"Need to specify an output directory")
236 if opts.font
and os.path.exists(opts.font):
239 sys.exit(
"Need to specify the location of a font")
245 verbose = opts.verbose
246 fontsize = opts.font_size
249 count = max(0, fill - len(os.listdir(output)))
254 words = [x
for x
in words
255 if len(x)
in (4,5)
and x[0] !=
"f"
256 and x[0] != x[1]
and x[-1] != x[-2]]
258 for i
in range(count):
259 word =
pick_word(words, blacklist, verbose, opts.number_words, opts.min_length, opts.max_length)
260 salt =
"%08x" % random.randrange(2**32)
262 md5hash = hashlib.md5((key+salt+word+key+salt).encode(
'utf-8')).hexdigest()[:16]
263 filename =
"image_%s_%s.png" % (salt, md5hash)
266 filename = os.path.join(subdir, filename)
269 gen_captcha(word, font, fontsize, os.path.join(output, filename))