kamcaptcha 0.0.1 → 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/Gemfile +1 -0
- data/README.md +1 -1
- data/bin/kamcaptcha +7 -5
- data/kamcaptcha.gemspec +1 -1
- data/lib/kamcaptcha/generator.rb +81 -16
- data/test/test_helper.rb +1 -0
- data/test/unit/test_generator.rb +33 -6
- data/test/unit/test_helper.rb +2 -0
- data/test/unit/test_kamcaptcha.rb +2 -0
- data/test/unit/test_validation.rb +2 -0
- metadata +106 -98
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Kamcaptcha
|
1
|
+
# Kamcaptcha [](http://travis-ci.org/morten/kamcaptcha)
|
2
2
|
|
3
3
|
A captcha system that uses less ridiculous images than ReCAPTCHA. Kamcaptcha has two somewhat independent parts to it, a generator for building the word images to present, and then a runtime configuration for checking validity of what gets submitted.
|
4
4
|
|
data/bin/kamcaptcha
CHANGED
@@ -21,7 +21,7 @@ Usage examples:
|
|
21
21
|
kamcaptcha tmp/
|
22
22
|
kamcaptcha --count 100 tmp/
|
23
23
|
kamcaptcha --height 100 --width 300 tmp/
|
24
|
-
kamcaptcha --length 8 --format gif tmp/
|
24
|
+
kamcaptcha --length 8-10 --source random --format gif tmp/
|
25
25
|
|
26
26
|
A default run creates a "./tmp" directory and generates 10 random five character word PNG images, each 240x50 pixels in size.
|
27
27
|
|
@@ -32,7 +32,8 @@ EOS
|
|
32
32
|
opt :count, "How many words to generate", :default => 10
|
33
33
|
opt :height, "Height of the word image in pixels", :default => 50
|
34
34
|
opt :width, "Width of the word image in pixesks", :default => 240
|
35
|
-
opt :
|
35
|
+
opt :source, "How to generate words, [dictionary|random]", :default => "dictionary"
|
36
|
+
opt :length, "Length of the word in number of characters, or as a range e.g. 7-9", :default => "5"
|
36
37
|
opt :format, "Image file format", :default => "png"
|
37
38
|
end
|
38
39
|
|
@@ -43,10 +44,11 @@ end
|
|
43
44
|
|
44
45
|
Kamcaptcha.salt = opts[:salt] || Digest::SHA2.hexdigest((0...64).map { rand(150).chr }.join)
|
45
46
|
|
46
|
-
puts "Generating #{opts[:count]} words into #{ARGV[0]}
|
47
|
+
puts "Generating #{opts[:count]} words into #{ARGV[0]} - hang tight...\n\n"
|
47
48
|
|
48
|
-
|
49
|
-
|
49
|
+
files = Kamcaptcha::Generator.generate(ARGV[0], opts)
|
50
|
+
files.each_with_index do |file, index|
|
51
|
+
puts "\t#{index+1}\t#{file}"
|
50
52
|
end
|
51
53
|
|
52
54
|
puts "\nRemember to set Kamcaptcha.salt = '#{Kamcaptcha.salt}' in your application\n"
|
data/kamcaptcha.gemspec
CHANGED
data/lib/kamcaptcha/generator.rb
CHANGED
@@ -1,39 +1,104 @@
|
|
1
|
-
require "rubygems"
|
2
|
-
|
3
1
|
begin
|
4
|
-
require "
|
2
|
+
require "RMagick"
|
5
3
|
rescue LoadError => e
|
6
4
|
puts "RMagick required to run the generator, gem install rmagick"
|
7
|
-
|
5
|
+
exit
|
8
6
|
end
|
9
7
|
|
10
8
|
module Kamcaptcha
|
11
9
|
class Generator
|
10
|
+
class WordGenerator
|
11
|
+
attr_reader :min, :max
|
12
|
+
|
13
|
+
def initialize(length)
|
14
|
+
if length.to_s =~ /^(\d+)$/
|
15
|
+
@min = @max = $1.to_i
|
16
|
+
elsif length =~ /^(\d+)-(\d+)$/
|
17
|
+
@min = [$1.to_i, $2.to_i].min
|
18
|
+
@max = [$1.to_i, $2.to_i].max
|
19
|
+
else
|
20
|
+
raise ArgumentError.new("Invalid word length specified: #{length}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class RandomWordGenerator < WordGenerator
|
26
|
+
CHARS = "23456789ABCDEFGHJKLMNPQRSTUVXYZ".split("")
|
27
|
+
|
28
|
+
def generate
|
29
|
+
length = min
|
30
|
+
length += Kernel.rand(max - min) unless max == min
|
31
|
+
|
32
|
+
(1..length).map { CHARS[rand(CHARS.size)] }.join(" ")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class DictionaryWordGenerator < WordGenerator
|
37
|
+
class << self
|
38
|
+
attr_accessor :dict # Use this to set a custom dictionary or dictionary generating function
|
39
|
+
end
|
40
|
+
|
41
|
+
def generate
|
42
|
+
words[rand(words.size)].split("").join(" ")
|
43
|
+
end
|
44
|
+
|
45
|
+
def words
|
46
|
+
@words ||= begin
|
47
|
+
if self.class.dict
|
48
|
+
if self.class.dict.respond_to?(:call)
|
49
|
+
w = self.class.dict.call
|
50
|
+
else
|
51
|
+
w = self.class.dict
|
52
|
+
end
|
53
|
+
else
|
54
|
+
w = default_dict
|
55
|
+
end
|
56
|
+
|
57
|
+
w.each_with_index { |word, i| w[i] = word.upcase }
|
58
|
+
w.select { |w| w.size >= min && w.size <= max && w !~ /O/ }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def default_dict
|
63
|
+
return File.read("/usr/share/dict/words").split("\n") if File.exists?("/usr/share/dict/words")
|
64
|
+
return File.read("/usr/dict/words").split("\n") if File.exists?("/usr/dict/words")
|
65
|
+
|
66
|
+
raise ArgumentError.new("Sorry, need a dictionary file")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
12
70
|
DEFAULTS = {
|
13
|
-
:chars => "23456789ABCDEFGHJKLMNPQRSTUVXYZ".split(""),
|
14
71
|
:width => 240,
|
15
72
|
:height => 50,
|
16
73
|
:length => 5,
|
17
|
-
:format => "png"
|
74
|
+
:format => "png",
|
75
|
+
:count => 1
|
18
76
|
}
|
19
77
|
|
20
78
|
def self.generate(path, options = {})
|
21
79
|
options = DEFAULTS.merge(options)
|
22
80
|
|
23
|
-
|
24
|
-
|
81
|
+
if options[:source] != "random"
|
82
|
+
source = DictionaryWordGenerator.new(options[:length])
|
83
|
+
else
|
84
|
+
source = RandomWordGenerator.new(options[:length])
|
85
|
+
end
|
86
|
+
|
87
|
+
files = []
|
25
88
|
|
26
|
-
|
27
|
-
|
89
|
+
options[:count].times do
|
90
|
+
word = source.generate
|
91
|
+
image = build_image(word, options[:width], options[:height])
|
92
|
+
name = word.delete(" ").downcase
|
93
|
+
file = File.join(path, Kamcaptcha.encrypt(name) + ".#{options[:format]}")
|
28
94
|
|
29
|
-
|
30
|
-
|
95
|
+
image.write(file)
|
96
|
+
image.destroy!
|
31
97
|
|
32
|
-
|
33
|
-
|
98
|
+
files << file
|
99
|
+
end
|
34
100
|
|
35
|
-
|
36
|
-
(1..size).map { chars[rand(chars.size)] }.join(" ")
|
101
|
+
files
|
37
102
|
end
|
38
103
|
|
39
104
|
def self.build_image(word, width, height)
|
data/test/test_helper.rb
CHANGED
data/test/unit/test_generator.rb
CHANGED
@@ -1,15 +1,42 @@
|
|
1
|
+
require File.expand_path("test/test_helper")
|
2
|
+
|
1
3
|
describe Kamcaptcha::Generator do
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
|
5
|
+
describe Kamcaptcha::Generator::RandomWordGenerator do
|
6
|
+
subject { Kamcaptcha::Generator::RandomWordGenerator.new("3-10") }
|
7
|
+
|
8
|
+
it "delivers words of the specified size range" do
|
9
|
+
20.times do
|
10
|
+
word = subject.generate.delete(" ")
|
11
|
+
|
12
|
+
assert word.size >= 3, "Size mismatch #{word}"
|
13
|
+
assert word.size <= 10, "Size mismatch #{word}"
|
14
|
+
|
15
|
+
assert_match /[0-9A-Z]+/, word
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe Kamcaptcha::Generator::DictionaryWordGenerator do
|
21
|
+
after do
|
22
|
+
Kamcaptcha::Generator::DictionaryWordGenerator.dict = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
subject { Kamcaptcha::Generator::DictionaryWordGenerator.new("1-5") }
|
26
|
+
|
27
|
+
it "delivers words from the dict" do
|
28
|
+
Kamcaptcha::Generator::DictionaryWordGenerator.dict = [ "foo", "fum" ]
|
29
|
+
|
30
|
+
20.times do
|
31
|
+
word = subject.generate.delete(" ")
|
32
|
+
assert [ "FOO", "FUM" ].member?(word), "Word #{word} not found"
|
33
|
+
end
|
7
34
|
end
|
8
35
|
end
|
9
36
|
|
10
37
|
describe "#generate" do
|
11
38
|
it "generates a file" do
|
12
|
-
file = Kamcaptcha::Generator.generate("/tmp")
|
39
|
+
file = Kamcaptcha::Generator.generate("/tmp", :source => "random").first
|
13
40
|
assert File.exist?(file)
|
14
41
|
File.unlink(file)
|
15
42
|
end
|
data/test/unit/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,119 +1,118 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: kamcaptcha
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
5
|
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
6
11
|
platform: ruby
|
7
|
-
authors:
|
12
|
+
authors:
|
8
13
|
- Morten Primdahl
|
9
14
|
autorequire:
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
17
|
+
|
18
|
+
date: 2012-09-12 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
15
21
|
name: trollop
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
|
-
requirements:
|
19
|
-
- - ! '>='
|
20
|
-
- !ruby/object:Gem::Version
|
21
|
-
version: '2.0'
|
22
|
-
type: :runtime
|
23
22
|
prerelease: false
|
24
|
-
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
24
|
none: false
|
26
|
-
requirements:
|
27
|
-
- -
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
|
30
|
-
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 2
|
31
|
+
- 0
|
32
|
+
version: "2.0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
31
36
|
name: rake
|
32
|
-
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
|
-
requirements:
|
35
|
-
- - ! '>='
|
36
|
-
- !ruby/object:Gem::Version
|
37
|
-
version: '0'
|
38
|
-
type: :development
|
39
37
|
prerelease: false
|
40
|
-
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
39
|
none: false
|
42
|
-
requirements:
|
43
|
-
- -
|
44
|
-
- !ruby/object:Gem::Version
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
none: false
|
50
|
-
requirements:
|
51
|
-
- - ! '>='
|
52
|
-
- !ruby/object:Gem::Version
|
53
|
-
version: '0'
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
54
47
|
type: :development
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: bundler
|
55
51
|
prerelease: false
|
56
|
-
|
57
|
-
none: false
|
58
|
-
requirements:
|
59
|
-
- - ! '>='
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
- !ruby/object:Gem::Dependency
|
63
|
-
name: rmagick
|
64
|
-
requirement: !ruby/object:Gem::Requirement
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
65
53
|
none: false
|
66
|
-
requirements:
|
67
|
-
- -
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
70
61
|
type: :development
|
62
|
+
version_requirements: *id003
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: rmagick
|
71
65
|
prerelease: false
|
72
|
-
|
73
|
-
none: false
|
74
|
-
requirements:
|
75
|
-
- - ! '>='
|
76
|
-
- !ruby/object:Gem::Version
|
77
|
-
version: '0'
|
78
|
-
- !ruby/object:Gem::Dependency
|
79
|
-
name: minitest
|
80
|
-
requirement: !ruby/object:Gem::Requirement
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
81
67
|
none: false
|
82
|
-
requirements:
|
83
|
-
- -
|
84
|
-
- !ruby/object:Gem::Version
|
85
|
-
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
86
75
|
type: :development
|
76
|
+
version_requirements: *id004
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
name: minitest
|
87
79
|
prerelease: false
|
88
|
-
|
80
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
89
81
|
none: false
|
90
|
-
requirements:
|
91
|
-
- -
|
92
|
-
- !ruby/object:Gem::Version
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
none: false
|
98
|
-
requirements:
|
99
|
-
- - ! '>='
|
100
|
-
- !ruby/object:Gem::Version
|
101
|
-
version: 2.1.3
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
102
89
|
type: :development
|
90
|
+
version_requirements: *id005
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: uuidtools
|
103
93
|
prerelease: false
|
104
|
-
|
94
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
105
95
|
none: false
|
106
|
-
requirements:
|
107
|
-
- -
|
108
|
-
- !ruby/object:Gem::Version
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
hash: 13
|
100
|
+
segments:
|
101
|
+
- 2
|
102
|
+
- 1
|
103
|
+
- 3
|
109
104
|
version: 2.1.3
|
105
|
+
type: :development
|
106
|
+
version_requirements: *id006
|
110
107
|
description: Helps humankind with simpler captchas
|
111
108
|
email: primdahl@me.com
|
112
|
-
executables:
|
109
|
+
executables:
|
113
110
|
- kamcaptcha
|
114
111
|
extensions: []
|
112
|
+
|
115
113
|
extra_rdoc_files: []
|
116
|
-
|
114
|
+
|
115
|
+
files:
|
117
116
|
- .gitignore
|
118
117
|
- .travis.yml
|
119
118
|
- Gemfile
|
@@ -132,28 +131,37 @@ files:
|
|
132
131
|
- test/unit/test_kamcaptcha.rb
|
133
132
|
- test/unit/test_validation.rb
|
134
133
|
homepage: http://github.com/morten/kamcaptcha
|
135
|
-
licenses:
|
134
|
+
licenses:
|
136
135
|
- MIT
|
137
136
|
post_install_message:
|
138
137
|
rdoc_options: []
|
139
|
-
|
138
|
+
|
139
|
+
require_paths:
|
140
140
|
- lib
|
141
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
141
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
142
142
|
none: false
|
143
|
-
requirements:
|
144
|
-
- -
|
145
|
-
- !ruby/object:Gem::Version
|
146
|
-
|
147
|
-
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
hash: 3
|
147
|
+
segments:
|
148
|
+
- 0
|
149
|
+
version: "0"
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
148
151
|
none: false
|
149
|
-
requirements:
|
150
|
-
- -
|
151
|
-
- !ruby/object:Gem::Version
|
152
|
-
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
hash: 3
|
156
|
+
segments:
|
157
|
+
- 0
|
158
|
+
version: "0"
|
153
159
|
requirements: []
|
160
|
+
|
154
161
|
rubyforge_project:
|
155
|
-
rubygems_version: 1.8.
|
162
|
+
rubygems_version: 1.8.15
|
156
163
|
signing_key:
|
157
164
|
specification_version: 3
|
158
165
|
summary: A captcha image generator that's a little less retarded
|
159
166
|
test_files: []
|
167
|
+
|