hOOmanTest: a mootools bot filtering "captcha" class

by D. Christoff - fragged.org - updated 26/06/2009

The spamming bots are annoying. And they can OCR too!

Ever get bored of the old 'type the number|word you see above|below' business when completing an online form? I did. And it probably annoys users on sites where this is installed. But as annoying as it is, it stops bots from automatically submitting spam through forms on pages. The real loser is the end user who needs to take the burden of our own spam protection.

The practice of asking people to 'prove' they are, indeed, human is now known as 'CAPTCHA IMAGE VERIFICATION'. Since the discovery that bots can use optical character recognition - OCR, much like flatbed scanners, people have started to REALLY obtuse the text, making it nearly impossible to make out. Here are a few examples I collected within 5 Min's of my Google search, showing just how difficult to read it can become:

You have got to be kidding me?

Now, even if you could read the first one, you may be able to guess your way around the second one... and you'd still have to carefully type them. And then there's the 3rd one--which aside from being hard to make out--is also a bit longer to type.

I suffer from a slight astigmatism and certain letters, shapes or combinations of letters and rounded shapes, clustered together, present me with a real accessibility problem. People shouldn't have strain themselves and spend minutes filling out a "humanity" test!

The captcha alternative

All this and more is why I wrote hOOmanTest - to provide an alternative way of differentiating bots from humans. Here it is in action below:

How difficult is that in comparison to using the one that's so popular around the web?

*edit*: I really do hope it helps somebody, here is the first site that uses it out there--in a quotation webform on the cleaning 4u site, click the free quote banner to view.

I should also add I saw the idea on another site, accomplished through jQuery and jQuery UI. *edit* It is called Ajax Fancy Captcha and weighs in at a total of 435Kb for the pleasure. I am not sure which is worse, getting your users to download nearly half a meg extra or getting them to strain their eyesight with traditional captcha images.

Nevertheless, very popular within the jQuery community, head over heels for this they were. So here we are a while later, sat with a brand new version that works in mootools 1.2+.

Obviously, we do not need MochaUI to get this working so our version can be as low as 120k total in size (inclusive of the framework files).

Using the hOOmanTest mootools class

Instigating the class is easy, within the domready you do:
var noBots = new hOOmanTest($("demo"), {
    callback: function(state) {
        if (state) {
            // success!
            this.instructions.set("html", "We appreciate it, you can now continue and submit your form.");

            // add a field to the form that can show the processor the test has passed.
            $("someForm").adopt(new Element("input", {
                "type": "hidden",
                "name": "captcha",
                "value": this.mover.name

            // do something else like, enable the submit button.
        else // dropped it elsewhere, tease / lead them.
            this.instructions.set("html", "No, no, no! Drag and drop the "+this.mover.name+" into the BOX!");
The 'callback' function is important, it is where you should fit your ways of making your forms work. For example, you can use the function to set the action property of your form (having had it blank).


Get mootools 1.2+ and hOOmanTest.js
The icons are .png, courtesy of IconFinder

You may want to apply a png transparency fix for IE6 if you still support the browser.

The background images:

There is some CSS as well:
.botMessage {
    width: 180px;
    position: relative;
    float: right;
    top: 10px;
    font-size: 10px;
    font-family: verdana;
.botMessage strong {
    color: #500;
.dropper {
    position: relative;
    width: 49px;
    height: 49px;
    top: 34px;
    margin-right: 11px;
    clear: right;
    float: right;
.captcha {
    float: left;
    padding-right: 10px;
.captcha img {
    background: #fff;
    border: 1px solid #000;
    padding: 1px;
    margin-top: 8px

The hOOmanTest mootools class source itself

// class:           hOOmanTest
// version:         0.9
// dependencies:    mootools 1.2.3 core, 1.2.3 more (Drag.js)
// tested on:       FireFox 3, IE7, Safari 4 (beta), Opera 9.60
// last updated:    26/06/2009
// url:             http://fragged.org/
// author:          dimitar chrostoff 
// authorised use:  modify and use as you deem fit. any link back appreciated :)

var hOOmanTest = new Class({
    Implements: [Events,Options],
    initialize: function(el, options) {
        // set default options...
            messageHtml: "Prove you're human and drag the [what] below into the BOX",
            messageClass: "botMessage", // needed in css
            images: [{              // images to be dragged and their names
                name: "CUBES",
                src: "images/cubes.png"
                name: "PAPER",
                src: "images/news.png"
                name: "PLUS",
                src: "images/plus.png"
                name: "CONE",
                src: "images/cone.png"
            background: {
                url: "images/nobots.gif",
                width: 289,
                height: 118
            callback: $empty // a function to run after drop, args: human(bool), this(bound object instance)
        }, options));

        this.element = $(el); // the target element
    createTest: function() {
        // figure out what to move first
        if (this.container)

        this.human = false;

        if (!this.element)
            return false;

        // need two that show
        this.mover = this.options.images.getRandom();
        this.dummy = this.options.images.erase(this.mover).getRandom();

        this.container = new Element("div", {
            styles: {
                background: "url("+this.options.background.url+") no-repeat",
                width: this.options.background.width,
                height: this.options.background.height

        this.instructions = new Element("div", {
            html: this.options.messageHtml.replace("[what]", this.mover.name)

        this.dropper = new Element("div", {
            "class": "dropper"

        (function() {
            var coords = this.dropper.getCoordinates();

            this.mover.object = new Element("div", {
                styles: {
                    background: "url("+this.mover.src+") no-repeat",
                    width: 48,
                    height: 48,
                    position: "absolute",
                    top: coords.top,
                    left: coords.left - 120,
                    cursor: "move"

            this.dummy.object = new Element("div", {
                styles: {
                    background: "url("+this.dummy.src+") no-repeat",
                    width: 48,
                    height: 48,
                    position: "absolute",
                    top: coords.top,
                    left: coords.left - 65

            if (Browser.Engine.trident)

            var myDrag = new Drag.Move(this.mover.object, {
                droppables: [this.dropper],
                container: this.container,
                onDrop: function(el, droppable, event) {
                    if (!droppable) {
                        this.options.callback.run(false, this);
                        return false;

                        top: 0,
                        left: 0,
                        position: "relative",
                        cursor: "default"

                    this.human = true;
                    this.options.callback.run(true, this);
                onEnter: function(el, droppable) {

        }).delay(1000, this);
    passed: function() {