nice_text_captcha 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|