nice_text_captcha 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +73 -0
- data/lib/nice_text_captcha.rb +21 -0
- data/lib/nice_text_captcha/action_controller_extensions.rb +11 -0
- data/lib/nice_text_captcha/active_record_extensions.rb +31 -0
- data/lib/nice_text_captcha/captcha.rb +30 -0
- data/lib/nice_text_captcha/form_builder_extensions.rb +13 -0
- data/lib/nice_text_captcha/types/default_question.rb +42 -0
- data/lib/nice_text_captcha/types/letter_position_question.rb +32 -0
- data/lib/nice_text_captcha/types/maths_question.rb +36 -0
- data/lib/nice_text_captcha/types/word_length_question.rb +20 -0
- data/lib/nice_text_captcha/types/words_in_list_question.rb +38 -0
- metadata +78 -0
data/README
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
Nice Text Captcha
|
2
|
+
by Neil Smith
|
3
|
+
|
4
|
+
About
|
5
|
+
-----
|
6
|
+
|
7
|
+
This generates text-based CATPCHAs that are "nice", by my own personal
|
8
|
+
subjective definition!
|
9
|
+
|
10
|
+
Questions are purposely simple, and examples would include:
|
11
|
+
|
12
|
+
* How many letters are there in the word 'White'?
|
13
|
+
* In the word "tiger", what is the letter in the 5th position?
|
14
|
+
* How many animals are in the words: "cat", "orange", and "dolphin"?
|
15
|
+
* What is 'four' + "nine"?
|
16
|
+
|
17
|
+
Implementation
|
18
|
+
--------------
|
19
|
+
|
20
|
+
You call a method in your view which both generates a random question, and places
|
21
|
+
the possible answers into a flash[] object, stored in a SHA2 hash.
|
22
|
+
|
23
|
+
(It's stored in a flash, as Rails handles ageing the data after a request - if we
|
24
|
+
used the session, it would hang around for the duration of the session.)
|
25
|
+
|
26
|
+
Then, in your controller action, you call a method which extracts the answers from
|
27
|
+
the flash, and injects them into your model.
|
28
|
+
|
29
|
+
A validation method on the model finally checks the answer against the hashed
|
30
|
+
possible answers, and pushes an error onto the base of your model if the answer
|
31
|
+
doesn't match.
|
32
|
+
|
33
|
+
For numerical answers ("What is 'two' plus 'three'?"), both numbers ("3") and
|
34
|
+
words ("three") are accepted.
|
35
|
+
|
36
|
+
Requirements
|
37
|
+
------------
|
38
|
+
|
39
|
+
Tested against Rails 2.3 - currently untested against Rails 3.
|
40
|
+
'linguistics' gem for turning numbers into words and ordinals.
|
41
|
+
'Digest/SHA2' for hashing the possible answers.
|
42
|
+
|
43
|
+
Instructions
|
44
|
+
------------
|
45
|
+
|
46
|
+
In your model, you need to add some validation:
|
47
|
+
|
48
|
+
class Thing < ActiveRecord::Base
|
49
|
+
validate :check_nice_text_captcha
|
50
|
+
end
|
51
|
+
|
52
|
+
In your controller, you need to add a call to populate the answers from the
|
53
|
+
data in the session:
|
54
|
+
|
55
|
+
class ThingsController < ActiveRecord::Base
|
56
|
+
def create
|
57
|
+
@thing = Thing.new(params[:thing]) # as normal
|
58
|
+
validate_nice_text_captcha_for(@thing)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
Finally, in your view, you need to render the question by calling nice_text_captcha,
|
63
|
+
and give the user a text box for their answer:
|
64
|
+
|
65
|
+
<% form_for(@thing) do |f| %>
|
66
|
+
<!-- normal form elements here -->
|
67
|
+
|
68
|
+
<%= f.nice_text_captcha %><br />
|
69
|
+
<%= f.text_field :nice_text_captcha %><br />
|
70
|
+
|
71
|
+
<!-- probably a submit button here -->
|
72
|
+
|
73
|
+
<% end %>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#config.gem "linguistics"
|
2
|
+
require 'linguistics'
|
3
|
+
Linguistics::use(:en)
|
4
|
+
|
5
|
+
require 'nice_text_captcha/captcha'
|
6
|
+
|
7
|
+
require 'nice_text_captcha/active_record_extensions'
|
8
|
+
require 'nice_text_captcha/action_controller_extensions'
|
9
|
+
require 'nice_text_captcha/form_builder_extensions'
|
10
|
+
|
11
|
+
require 'nice_text_captcha/types/default_question'
|
12
|
+
require 'nice_text_captcha/types/letter_position_question'
|
13
|
+
require 'nice_text_captcha/types/maths_question'
|
14
|
+
require 'nice_text_captcha/types/word_length_question'
|
15
|
+
require 'nice_text_captcha/types/words_in_list_question'
|
16
|
+
|
17
|
+
ActionView::Helpers::FormBuilder.send :include, NiceTextCaptcha::FormBuilderExtensions
|
18
|
+
|
19
|
+
ActiveRecord::Base.send :include, NiceTextCaptcha::ActiveRecordExtensions
|
20
|
+
|
21
|
+
ActionController::Base.send :include, NiceTextCaptcha::ActionControllerExtensions
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module NiceTextCaptcha
|
2
|
+
|
3
|
+
module ActiveRecordExtensions
|
4
|
+
|
5
|
+
attr_accessor :nice_text_captcha
|
6
|
+
attr_accessor :nice_text_captcha_responses
|
7
|
+
|
8
|
+
def check_nice_text_captcha
|
9
|
+
|
10
|
+
captcha_successful = @nice_text_captcha_responses.nil?
|
11
|
+
@nice_text_captcha_responses.each do |response|
|
12
|
+
captcha_successful = true if Captcha.hash(self.class.to_s.underscore, @nice_text_captcha.strip.downcase) == response
|
13
|
+
end if @nice_text_captcha_responses
|
14
|
+
|
15
|
+
if !captcha_successful
|
16
|
+
errors.add_to_base(nice_text_captcha_failure_message)
|
17
|
+
end
|
18
|
+
|
19
|
+
@nice_text_captcha = nil
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def nice_text_captcha_failure_message
|
26
|
+
"Please ensure you answer the security question (CAPTCHA) correctly"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module NiceTextCaptcha
|
2
|
+
|
3
|
+
class Captcha
|
4
|
+
|
5
|
+
attr_reader :question
|
6
|
+
attr_reader :answers
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
captcha = [
|
10
|
+
Types::MathsQuestion,
|
11
|
+
Types::LetterPositionQuestion,
|
12
|
+
Types::WordsInListQuestion,
|
13
|
+
Types::WordLengthQuestion,
|
14
|
+
].rand.new
|
15
|
+
@question = captcha.question
|
16
|
+
@answers = captcha.answers
|
17
|
+
end
|
18
|
+
|
19
|
+
def encrypted_answers_for(object_name)
|
20
|
+
answers.collect { |answer| Captcha.hash(object_name, answer) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.hash(obj, answer)
|
24
|
+
str = obj.to_s + answer.to_s + ActionController::Base.session_options[:secret].to_s
|
25
|
+
Digest::SHA2.hexdigest(str)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module NiceTextCaptcha
|
2
|
+
|
3
|
+
module FormBuilderExtensions
|
4
|
+
|
5
|
+
def nice_text_captcha
|
6
|
+
captcha = NiceTextCaptcha::Captcha.new
|
7
|
+
@template.flash[:nice_text_captcha_responses] = captcha.encrypted_answers_for(@object_name)
|
8
|
+
captcha.question
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module NiceTextCaptcha::Types
|
2
|
+
class DefaultQuestion
|
3
|
+
|
4
|
+
ANIMALS = ["dog", "cat", "dolphin", "mouse", "horse", "penguin", "panda", "tiger", "lion", "zebra"]
|
5
|
+
COLOURS = ["black", "white", "red", "green", "blue", "pink", "purple", "orange", "yellow", "brown"]
|
6
|
+
|
7
|
+
attr_reader :question
|
8
|
+
attr_reader :answers
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def fifty_fifty?
|
13
|
+
rand(2) == 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def number_to_word(number)
|
17
|
+
number.to_i.en.numwords
|
18
|
+
end
|
19
|
+
|
20
|
+
def number_to_ordinal(number)
|
21
|
+
number.to_i.en.ordinal
|
22
|
+
end
|
23
|
+
|
24
|
+
def randomly_quote(word)
|
25
|
+
q = ["'", "\""].rand
|
26
|
+
q + word + q
|
27
|
+
end
|
28
|
+
|
29
|
+
def randomly_capitalise(word)
|
30
|
+
fifty_fifty? ? word : word.capitalize
|
31
|
+
end
|
32
|
+
|
33
|
+
def randomly_quote_and_capitalise(word)
|
34
|
+
randomly_quote(randomly_capitalise(word))
|
35
|
+
end
|
36
|
+
|
37
|
+
def randomly_sort(list)
|
38
|
+
list.sort { |x,y| rand(100) <=> rand(100) }
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class NiceTextCaptcha::Types::LetterPositionQuestion < NiceTextCaptcha::Types::DefaultQuestion
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
word = [ANIMALS + COLOURS].flatten.rand
|
5
|
+
position = rand(word.length) + 1
|
6
|
+
answer = word[position - 1, 1]
|
7
|
+
|
8
|
+
position_string = number_to_ordinal(position)
|
9
|
+
|
10
|
+
position_string = "first" if position == 1 and fifty_fifty?
|
11
|
+
position_string = "second" if position == 2 and fifty_fifty?
|
12
|
+
position_string = (fifty_fifty? ? "final" : "last") if position == word.length and fifty_fifty?
|
13
|
+
position_string = "second to last" if position == word.length - 1 and fifty_fifty?
|
14
|
+
|
15
|
+
@question = format_question(randomly_quote_and_capitalise(word), position_string)
|
16
|
+
@answers = [answer]
|
17
|
+
end
|
18
|
+
|
19
|
+
def format_question(word, position)
|
20
|
+
[
|
21
|
+
"What is the %s letter in the word %s?" % [position, word],
|
22
|
+
"What's the %s letter in the word %s?" % [position, word],
|
23
|
+
"The %s letter in %s is what?" % [position, word],
|
24
|
+
"The %s letter in the word %s is?" % [position, word],
|
25
|
+
"In the word %s, what is the %s letter?" % [word, position],
|
26
|
+
"In the word %s, what is the letter in the %s position?" % [word, position],
|
27
|
+
].rand
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class NiceTextCaptcha::Types::MathsQuestion < NiceTextCaptcha::Types::DefaultQuestion
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
number_1 = one_to_nine
|
5
|
+
number_2 = one_to_nine
|
6
|
+
answer = number_1 + number_2
|
7
|
+
|
8
|
+
@question = format_question(number_or_word(number_1), number_or_word(number_2))
|
9
|
+
@answers = [answer, number_to_word(answer)]
|
10
|
+
end
|
11
|
+
|
12
|
+
def format_question(one, two)
|
13
|
+
[
|
14
|
+
"What is %s plus %s?" % [one, two],
|
15
|
+
"What is %s + %s?" % [one, two],
|
16
|
+
"What is %s added to %s?" % [one, two],
|
17
|
+
"What do you get if you add the numbers %s and %s?" % [one, two],
|
18
|
+
"What is the result of adding %s and %s?" % [one, two],
|
19
|
+
].rand
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def one_to_nine
|
25
|
+
rand(9) + 1
|
26
|
+
end
|
27
|
+
|
28
|
+
def number_or_word(number)
|
29
|
+
if fifty_fifty?
|
30
|
+
number
|
31
|
+
else
|
32
|
+
randomly_quote_and_capitalise(number_to_word(number))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class NiceTextCaptcha::Types::WordLengthQuestion < NiceTextCaptcha::Types::DefaultQuestion
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
word = [ANIMALS + COLOURS].flatten.rand
|
5
|
+
answer = word.length
|
6
|
+
|
7
|
+
@question = format_question(randomly_quote_and_capitalise(word))
|
8
|
+
@answers = [answer, number_to_word(answer)]
|
9
|
+
end
|
10
|
+
|
11
|
+
def format_question(word)
|
12
|
+
[
|
13
|
+
"How many letters are in the word %s?" % word,
|
14
|
+
"How many letters are there in the word %s?" % word,
|
15
|
+
"How many letters are there in %s?" % word,
|
16
|
+
"In the word %s, how many letters are there?" % word,
|
17
|
+
].rand
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class NiceTextCaptcha::Types::WordsInListQuestion < NiceTextCaptcha::Types::DefaultQuestion
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
animals = randomly_sort(ANIMALS)
|
5
|
+
colours = randomly_sort(COLOURS)
|
6
|
+
|
7
|
+
answer = rand(3) + 1
|
8
|
+
|
9
|
+
if fifty_fifty?
|
10
|
+
words = animals[0 .. answer-1]
|
11
|
+
words << colours[0 .. rand(3)]
|
12
|
+
noun = "animals"
|
13
|
+
else
|
14
|
+
words = colours[0 .. answer-1]
|
15
|
+
words << animals[0 .. rand(3)]
|
16
|
+
noun = "colours"
|
17
|
+
end
|
18
|
+
|
19
|
+
words.flatten!
|
20
|
+
words.collect! { |w| randomly_quote_and_capitalise(w) }
|
21
|
+
|
22
|
+
@question = format_question(noun, randomly_sort(words))
|
23
|
+
|
24
|
+
@answers = [answer, number_to_word(answer)]
|
25
|
+
end
|
26
|
+
|
27
|
+
def format_question(noun, word_list)
|
28
|
+
collective_noun = ["list", "words", "list of words", "word list"].rand
|
29
|
+
collective_noun = "following " + collective_noun if fifty_fifty?
|
30
|
+
|
31
|
+
[
|
32
|
+
"How many %s are in the %s: %s?" % [noun, collective_noun, word_list.to_sentence],
|
33
|
+
"How many %s are there in the %s: %s?" % [noun, collective_noun, word_list.to_sentence],
|
34
|
+
"In the %s: %s - how many %s are there?" % [collective_noun, word_list.to_sentence, noun],
|
35
|
+
].rand
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nice_text_captcha
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Neil Smith
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-06 00:00:00 +01:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description:
|
23
|
+
email: neil.jx.smith@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- README
|
30
|
+
files:
|
31
|
+
- README
|
32
|
+
- lib/nice_text_captcha/action_controller_extensions.rb
|
33
|
+
- lib/nice_text_captcha/active_record_extensions.rb
|
34
|
+
- lib/nice_text_captcha/captcha.rb
|
35
|
+
- lib/nice_text_captcha/form_builder_extensions.rb
|
36
|
+
- lib/nice_text_captcha/types/default_question.rb
|
37
|
+
- lib/nice_text_captcha/types/letter_position_question.rb
|
38
|
+
- lib/nice_text_captcha/types/maths_question.rb
|
39
|
+
- lib/nice_text_captcha/types/word_length_question.rb
|
40
|
+
- lib/nice_text_captcha/types/words_in_list_question.rb
|
41
|
+
- lib/nice_text_captcha.rb
|
42
|
+
has_rdoc: true
|
43
|
+
homepage:
|
44
|
+
licenses: []
|
45
|
+
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options:
|
48
|
+
- --main
|
49
|
+
- README
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
hash: 3
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
requirements: []
|
71
|
+
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 1.3.7
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: Generates Text Catpchas, that are 'nice' by my own personal definition!
|
77
|
+
test_files: []
|
78
|
+
|