unity-captcha 1.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 674e45a2d8771ef6be513c4e0699fe7fd02ddae9
4
+ data.tar.gz: 99ef29699c26e68c8478494669be994ba4afb847
5
+ SHA512:
6
+ metadata.gz: 555ee315a14a243b5bceaef420f0005d335565f1d00600581ee291527583997a5fb019abfc9e6ecaba75168945c81bc5370fdd2c9291016cc2ba324859946821
7
+ data.tar.gz: bc433a006182033752483ef1499fb8a0f42258b5beb1909d0418cb17f72b1244f77e67395cc3c1bae0ec69833576c6758fcb870aad18b47df7860f73386c253f
Binary file
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at papayalabs@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in unity-captcha.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Papaya Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,165 @@
1
+ # Unity Captcha
2
+
3
+ Unity Captcha is a gem that use two levels of Captcha. First level is about Left Hemisphere: Logic and Math. Second level is about Right Hemisphere: Intuition and Drawing. This two levels functioning in complete harmony creates unity and this is the base design of this Captcha.
4
+
5
+ The Left Hemisphere Level use a simple question of addition and multiplication. This works in the server side.
6
+
7
+ The Right Hemisphere Level use MotionCAPTCHA (https://github.com/wjcrowcroft/MotionCAPTCHA), that is a jQuery CAPTCHA plugin that requires users to sketch the shape they see in the canvas in order to submit a form. This works in the client side.
8
+
9
+ Dra. Jill Bolte Taylor got a research opportunity to study from inside of her brain how Left and Right Hemisphere works. I recommend to watch her in this video: https://www.ted.com/talks/jill_bolte_taylor_s_powerful_stroke_of_insight#t-1102370
10
+
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'unity-captcha'
18
+ ```
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install unity-captcha
27
+
28
+ ## Usage
29
+
30
+ For the First level - Left Hemisphere: Logic and Math ( Server Side ) we use some code in the controller and the view.
31
+
32
+ 1. In your controller, for example if you have a method inside the controller called 'send_invite':
33
+
34
+ ```ruby
35
+ class InviteController < ApplicationController
36
+ def new
37
+ @captcha = Unity::Captcha::Captcha.new
38
+ end
39
+ def send_invite
40
+ @captcha = Unity::Captcha::Captcha.decrypt(params[:captcha_secret])
41
+ email = params[:friend_email]
42
+ unless @captcha.correct?(params[:captcha])
43
+ @captcha = Unity::Captcha::Captcha.new
44
+ redirect_to invite_url, :alert => "Please make sure you entered correct value for captcha."
45
+ else
46
+ InviteMailer.invite_email(current_user, email).deliver_now
47
+ flash[:notice] = "Thank you for inviting your friend! Please feel free to invite more!"
48
+ redirect_to invite_url
49
+ end
50
+ end
51
+ end
52
+ ```
53
+
54
+ 2. Ask the question in the form, example:
55
+
56
+ ```erb
57
+ <%= form_tag(send_invite_path, :method => :post) do %>
58
+ <%= label_tag :friend_email, "Email" %>
59
+ <%= text_field_tag :friend_email, nil, :size =>50, :class => "placeholder" %>
60
+ <%= label_tag :question, "Question: "+@captcha.question %>
61
+ <%= text_field_tag :captcha %>
62
+ <%= hidden_field_tag :captcha_secret, @captcha.encrypt %>
63
+
64
+ <%= submit_tag "Send Invitation" %>
65
+ <% end %>
66
+ ```
67
+
68
+ For the Second level - Right Hemisphere: Intuition and Drawing ( Cliend Side ) we just need to add some code in the form and make sure to add the plugin scripts: (MotionCAPTCHA is supported down to jQuery 1.4)
69
+
70
+ 1. Adding plugin scripts ( usually added in application.html.erb ):
71
+
72
+ ```erb
73
+ <%= javascript_include_tag "https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"%>
74
+ <%= stylesheet_link_tag "jquery.motionCaptcha.1.0" %>
75
+ <%= javascript_include_tag "jquery.motionCaptcha.1.0" %>
76
+ <%= javascript_include_tag "jquery.placeholder.1.1.1.min" %>
77
+ ```
78
+
79
+ 2. Replace the submit_tag with the CAPTCHA canvas and a hidden_field_tag, like the following code ( remember to add :id => "mc-form" in form):
80
+
81
+ ```erb
82
+ <%= form_tag(send_invite_path, :method => :post, :id => "mc-form") do %>
83
+ <%= label_tag :friend_email, "Email" %>
84
+ <%= text_field_tag :friend_email, nil, :size =>50, :class => "placeholder" %
85
+ <%= label_tag :question, "Question: "+@captcha.question %>
86
+ <%= text_field_tag :captcha %>
87
+ <%= hidden_field_tag :captcha_secret, @captcha.encrypt %>
88
+
89
+ <p>CAPTCHA: Please draw the shape in the box to submit the form: (<a onclick="window.location.reload()" href="#" title="Click for a new shape">new shape</a>)</p>
90
+ <canvas id="mc-canvas"></canvas>
91
+ <%= hidden_field_tag 'mc-action', send_invite_path.to_s %>
92
+ <% end %>
93
+ ```
94
+
95
+ You can also use simple_form:
96
+
97
+ ```erb
98
+ <%= simple_form_for send_invite_path, :html => {:class => 'form-horizontal',:id => "mc-form" } do |f| %>
99
+ <%= f.input :friend_email, label: 'Email',:class => "placeholder", :required => true %>
100
+ <%= f.input :captcha, label: @captcha.question, :required => true %>
101
+ <%= f.input :captcha_secret, :as => :hidden, :input_html => { :value => @captcha.encrypt } %>
102
+
103
+ <p>CAPTCHA: Please draw the shape in the box to submit the form: (<a onclick="window.location.reload()" href="#" title="Click for a new shape">new shape</a>)</p>
104
+ <canvas id="mc-canvas"></canvas>
105
+ <%= f.input :mc_action, :as => :hidden, :input_html => { :value => send_invite_path.to_s } %>
106
+ <% end %>
107
+ ```
108
+
109
+ 3. Initialize the javascrit component in the same form:
110
+
111
+ ```erb
112
+ <script type="text/javaScript">
113
+ jQuery(document).ready(function($) {
114
+ $('#mc-form').motionCaptcha({
115
+ shapes: ['triangle', 'x', 'rectangle', 'circle', 'check', 'zigzag', 'arrow', 'delete', 'pigtail', 'star']
116
+ });
117
+ $("input.placeholder").placeholder();
118
+ });
119
+ </script>
120
+ ```
121
+
122
+ 4. Other options are available to initialize the canvas javascript component:
123
+
124
+ ```erb
125
+ <script type="text/javaScript">
126
+ jQuery(document).ready(function($) {
127
+ $('#mc-form').motioncaptcha({
128
+ // Basics:
129
+ action: '#mc-action', // the ID of the input containing the form action
130
+ divId: '#mc', // if you use an ID other than '#mc' for the placeholder, pass it in here
131
+ cssClass: '.mc-active', // this CSS class is applied to the 'mc' div when the plugin is active
132
+ canvasId: '#mc-canvas', // the ID of the MotionCAPTCHA canvas element
133
+
134
+ // An array of shape names that you want MotionCAPTCHA to use:
135
+ shapes: ['triangle', 'x', 'rectangle', 'circle', 'check', 'caret', 'zigzag', 'arrow', 'leftbracket', 'rightbracket', 'v', 'delete', 'star', 'pigtail'],
136
+
137
+ // These messages are displayed inside the canvas after a user finishes drawing:
138
+ errorMsg: 'Please try again.',
139
+ successMsg: 'Captcha passed!',
140
+
141
+ // This message is displayed if the user's browser doesn't support canvas:
142
+ noCanvasMsg: "Your browser doesn't support <canvas> - try Chrome, FF4, Safari or IE9."
143
+
144
+ // This could be any HTML string (eg. '<label>Draw this shit yo:</label>'):
145
+ label: '<p>Please draw the shape in the box to submit the form:</p>'
146
+ });
147
+ });
148
+ </script>
149
+ ```
150
+
151
+ ## Development
152
+
153
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
154
+
155
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
156
+
157
+ ## Contributing
158
+
159
+ Bug reports and pull requests are welcome on GitHub at https://github.com/papayalabs/unity-captcha. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
160
+
161
+
162
+ ## License
163
+
164
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
165
+
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "unity/captcha"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,53 @@
1
+ require "unity/captcha/version"
2
+ require "unity/captcha/engine"
3
+
4
+ module Unity
5
+ module Captcha
6
+ class Captcha
7
+ attr_accessor :operand1, :operand2, :operator
8
+
9
+ def initialize
10
+ @operand1 = (1..10).to_a.sample
11
+ @operand2 = (1..10).to_a.sample
12
+ @operator = [:+, :*].sample
13
+ end
14
+
15
+ def initialize_from(secret)
16
+ yml = YAML.load(Base64.decode64(secret))
17
+ @operand1, @operand2, @operator = yml[:operand1], yml[:operand2], yml[:operator]
18
+ end
19
+
20
+ def correct?(value)
21
+ result == value.to_i
22
+ end
23
+
24
+ def encrypt
25
+ Base64.encode64 to_yaml
26
+ end
27
+
28
+ def self.decrypt(secret)
29
+ result = new
30
+ result.initialize_from secret
31
+ result
32
+ end
33
+
34
+ def to_yaml
35
+ YAML::dump({
36
+ :operand1 => @operand1,
37
+ :operand2 => @operand2,
38
+ :operator => @operator
39
+ })
40
+ end
41
+
42
+ def question
43
+ "What is #{@operand1} #{@operator.to_s} #{@operand2} = ?"
44
+ end
45
+
46
+ private
47
+
48
+ def result
49
+ @operand1.send @operator, @operand2
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,10 @@
1
+ module Unity
2
+ module Captcha
3
+ class Engine < ::Rails::Engine
4
+ initializer 'static_assets.load_static_assets' do |app|
5
+ app.middleware.use ::ActionDispatch::Static, "#{root}/vendor"
6
+ end
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,5 @@
1
+ module Unity
2
+ module Captcha
3
+ VERSION = "1.0.1"
4
+ end
5
+ end
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'unity/captcha/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "unity-captcha"
8
+ spec.version = Unity::Captcha::VERSION
9
+ spec.authors = ["Manuel Fernandez"]
10
+ spec.email = ["papayalabs@gmail.com"]
11
+
12
+ spec.summary = %q{Unity Captcha is a gem that use two levels of captcha based on the hemispheres of the brain}
13
+ spec.description = %q{Unity Captcha is a gem that use two levels of Captcha. First level is about Left Hemisphere: Logic and Math. Second level is about Right Hemisphere: Intuition and Drawing. This two levels functioning in complete harmony creates unity and this is the base design of this Captcha.}
14
+ spec.homepage = "https://github.com/papayalabs/unity-captcha"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
21
+ else
22
+ raise "RubyGems 2.0 or newer is required to protect against " \
23
+ "public gem pushes."
24
+ end
25
+
26
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
27
+ f.match(%r{^(test|spec|features)/})
28
+ end
29
+ spec.bindir = "exe"
30
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ["lib"]
32
+
33
+ spec.add_development_dependency "bundler", "~> 1.13"
34
+ spec.add_development_dependency "rake", "~> 10.0"
35
+ end
Binary file
Binary file
@@ -0,0 +1,652 @@
1
+ /*!
2
+ * jQuery MotionCAPTCHA v1.0
3
+ *
4
+ * Concept and Development by Joss Crowcroft
5
+ * http://www.josscrowcroft.com
6
+ *
7
+ * Adapted and modified by Manuel Fernandez
8
+ * http://papayalabs.github.io
9
+ *
10
+ * Copyright (c) 2017 Papaya Labs | http://papayalabs.github.io
11
+ *
12
+ * Incoporates other open source projects, attributed below.
13
+ */
14
+ jQuery.fn.motionCaptcha || (function($) {
15
+
16
+ /**
17
+ * Main plugin function definition
18
+ */
19
+ $.fn.motionCaptcha = function(options) {
20
+
21
+ /**
22
+ * Act on matched form element:
23
+ * This could be set up to iterate over multiple elements, but tbh would it ever be useful?
24
+ */
25
+ return this.each(function() {
26
+
27
+ // Build main options before element iteration:
28
+ var opts = $.extend({}, $.fn.motionCaptcha.defaults, options);
29
+
30
+ // Ensure option ID params are valid #selectors:
31
+ opts.actionId = '#' + opts.actionId.replace(/\#/g, '');
32
+ opts.canvasId = '#' + opts.canvasId.replace(/\#/g, '');
33
+ opts.divId = '#' + opts.divId.replace(/\#/g, '');
34
+ opts.submitId = ( opts.submitId ) ? '#' + opts.submitId.replace(/\#/g, '') : false;
35
+
36
+ // Plugin setup:
37
+
38
+ // Set up Harmony vars:
39
+ var brush,
40
+ locked = false;
41
+
42
+ // Set up MotionCAPTCHA form and jQuery elements:
43
+ var $body = $('body'),
44
+ $form = $(this),
45
+ $container = $(opts.divId),
46
+ $canvas = $(opts.canvasId);
47
+
48
+ // Set up MotionCAPTCHA canvas vars:
49
+ var canvasWidth = $canvas.width(),
50
+ canvasHeight = $canvas.height(),
51
+ borderLeftWidth = 1 * $canvas.css('borderLeftWidth').replace('px', ''),
52
+ borderTopWidth = 1 * $canvas.css('borderTopWidth').replace('px', '');
53
+
54
+ // Canvas setup:
55
+
56
+ // Set the canvas DOM element's dimensions to match the display width/height (pretty important):
57
+ $canvas[0].width = canvasWidth;
58
+ $canvas[0].height = canvasHeight;
59
+
60
+ // Get DOM reference to canvas context:
61
+ var ctx = $canvas[0].getContext("2d");
62
+
63
+ // Add canvasWidth and canvasHeight values to context, for Ribbon brush:
64
+ ctx.canvasWidth = canvasWidth;
65
+ ctx.canvasHeight = canvasHeight;
66
+
67
+ // Set canvas context font and fillStyle:
68
+ ctx.font = opts.canvasFont;
69
+ ctx.fillStyle = opts.canvasTextColor;
70
+
71
+ // Set random shape
72
+ $canvas.addClass( opts.shapes[Math.floor(Math.random() * (opts.shapes.length) )] );
73
+
74
+ // Set up Dollar Recognizer and drawing vars:
75
+ var _isDown = false,
76
+ _holdStill = false,
77
+ _points = [],
78
+ _r = new DollarRecognizer();
79
+
80
+ // Create the Harmony Ribbon brush:
81
+ brush = new Ribbon(ctx);
82
+
83
+
84
+
85
+
86
+ // Mousedown event
87
+ // Start Harmony brushstroke and begin recording DR points:
88
+ var touchStartEvent = function(event) {
89
+ if ( locked )
90
+ return false;
91
+
92
+ // Prevent default action:
93
+ event.preventDefault();
94
+
95
+ // Get mouse position inside the canvas:
96
+ var pos = getPos(event),
97
+ x = pos[0],
98
+ y = pos[1];
99
+
100
+ // Internal drawing var
101
+ _isDown = true;
102
+
103
+ // Prevent jumpy-touch bug on android, no effect on other platforms:
104
+ _holdStill = true;
105
+
106
+ // Disable text selection:
107
+ $('body').addClass('mc-noselect');
108
+
109
+ // Clear canvas:
110
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight);
111
+
112
+ // Start brushstroke:
113
+ brush.strokeStart(x, y);
114
+
115
+ // Remove 'mc-invalid' and 'mc-valid' classes from canvas:
116
+ $canvas.removeClass('mc-invalid mc-valid');
117
+
118
+ // Add the first point to the points array:
119
+ _points = [NewPoint(x, y)];
120
+
121
+ return false;
122
+ }; // mousedown/touchstart event
123
+
124
+ // Mousemove event:
125
+ var touchMoveEvent = function(event) {
126
+ if ( _holdStill ) {
127
+ return _holdStill = 0;
128
+ }
129
+ // If mouse is down and canvas not locked:
130
+ if ( !locked && _isDown ) {
131
+
132
+ // Prevent default action:
133
+ event.preventDefault();
134
+
135
+ // Get mouse position inside the canvas:
136
+ var pos = getPos(event),
137
+ x = pos[0],
138
+ y = pos[1];
139
+
140
+ // Append point to points array:
141
+ _points[_points.length] = NewPoint(x, y);
142
+
143
+ // Do brushstroke:
144
+ brush.stroke(x, y);
145
+ }
146
+ return false;
147
+ }; // mousemove/touchmove event
148
+
149
+
150
+ // Mouseup event:
151
+ var touchEndEvent = function(event) {
152
+ // If mouse is down and canvas not locked:
153
+ if ( !locked && _isDown ) {
154
+ _isDown = false;
155
+
156
+ // Allow text-selection again:
157
+ $('body').removeClass('mc-noselect');
158
+
159
+ // Dollar Recognizer result:
160
+ if (_points.length >= 10) {
161
+ var result = _r.Recognize(_points);
162
+ // Check result:
163
+ if ( $canvas.attr('class').match(result.Name) && result.Score > 0.7 ) {
164
+
165
+ // Lock the canvas:
166
+ locked = 1;
167
+
168
+ // Destroy the Harmony brush (give it time to finish drawing)
169
+ setTimeout( brush.destroy, 500 );
170
+
171
+ // Add 'mc-valid' class to canvas:
172
+ $canvas.addClass('mc-valid');
173
+
174
+ // Write success message into canvas:
175
+ ctx.fillText(opts.successMsg, 10, 24);
176
+
177
+ // Call the onSuccess function to handle the rest of the business:
178
+ // Pass in the form, the canvas, the canvas context:
179
+ opts.onSuccess($form, $canvas, ctx);
180
+
181
+ } else {
182
+
183
+ // Add 'mc-invalid' class to canvas:
184
+ $canvas.addClass('mc-invalid');
185
+
186
+ // Write error message into canvas:
187
+ ctx.fillText(opts.errorMsg, 10, 24);
188
+
189
+ // Pass off to the error callback to finish up:
190
+ opts.onError($form, $canvas, ctx);
191
+ }
192
+
193
+ } else { // fewer than 10 points were recorded:
194
+
195
+ // Add 'mc-invalid' class to canvas:
196
+ $canvas.addClass('mc-invalid');
197
+
198
+ // Write error message into canvas:
199
+ ctx.fillText(opts.errorMsg, 10, 24);
200
+
201
+ // Pass off to the error callback to finish up:
202
+ opts.onError($form, $canvas, ctx);
203
+ }
204
+ }
205
+ return false;
206
+ }; // mouseup/touchend event
207
+
208
+ // Bind events to canvas:
209
+ $canvas.bind({
210
+ mousedown: touchStartEvent,
211
+ mousemove: touchMoveEvent,
212
+ mouseup: touchEndEvent,
213
+ });
214
+
215
+ // Mobile touch events:
216
+ $canvas[0].addEventListener('touchstart', touchStartEvent, false);
217
+ $canvas[0].addEventListener('touchmove', touchMoveEvent, false);
218
+ $canvas[0].addEventListener('touchend', touchEndEvent, false);
219
+
220
+ // Add active CSS class to form:
221
+ $form.addClass(opts.cssClass.replace(/\./, ''))
222
+
223
+
224
+ /**
225
+ * Get X/Y mouse position, relative to (/inside) the canvas
226
+ *
227
+ * Handles cross-browser quirks rather nicely, I feel.
228
+ *
229
+ * @todo For 1.0, if no way to obtain coordinates, don't activate MotionCAPTCHA.
230
+ */
231
+ function getPos(event) {
232
+ var x, y;
233
+
234
+ // Check for mobile first to avoid android jumpy-touch bug (iOS / Android):
235
+ if ( event.touches && event.touches.length > 0 ) {
236
+ // iOS/android uses event.touches, relative to entire page:
237
+ x = event.touches[0].pageX - $canvas.offset().left + borderLeftWidth;
238
+ y = event.touches[0].pageY - $canvas.offset().top + borderTopWidth;
239
+ } else if ( event.offsetX ) {
240
+ // Chrome/Safari give the event offset relative to the target event:
241
+ x = event.offsetX - borderLeftWidth;
242
+ y = event.offsetY - borderTopWidth;
243
+ } else {
244
+ // Otherwise, subtract page click from canvas offset (Firefox uses this):
245
+ x = event.pageX - $canvas.offset().left - borderLeftWidth;
246
+ y = event.pageY - $canvas.offset().top - borderTopWidth;
247
+ }
248
+ return [x,y];
249
+ }
250
+
251
+ }); // this.each
252
+
253
+ } // end main plugin function
254
+
255
+
256
+ /**
257
+ * Exposed default plugin settings, which can be overridden in plugin call.
258
+ */
259
+ $.fn.motionCaptcha.defaults = {
260
+ actionId: '#mc-action', // The ID of the input containing the form action
261
+ divId: '#mc', // If you use an ID other than '#mc' for the placeholder, pass it in here
262
+ canvasId: '#mc-canvas', // The ID of the MotionCAPTCHA canvas element
263
+ submitId: false, // If your form has multiple submit buttons, give the ID of the main one here
264
+ cssClass: '.mc-active', // This CSS class is applied to the form, when the plugin is active
265
+
266
+ // An array of shape names that you want MotionCAPTCHA to use:
267
+ shapes: ['triangle', 'x', 'rectangle', 'circle', 'check', 'caret', 'zigzag', 'arrow', 'leftbracket', 'rightbracket', 'v', 'delete', 'star', 'pigtail'],
268
+
269
+ // Canvas vars:
270
+ canvasFont: '15px "Lucida Grande"',
271
+ canvasTextColor: '#111',
272
+
273
+ // These messages are displayed inside the canvas after a user finishes drawing:
274
+ errorMsg: 'Please try again.',
275
+ successMsg: 'Captcha passed!',
276
+
277
+ // This message is displayed if the user's browser doesn't support canvas:
278
+ noCanvasMsg: "Your browser doesn't support <canvas> - try Chrome, FF4, Safari or IE9.",
279
+
280
+ // This could be any HTML string (eg. '<label>Draw this shit yo:</label>'):
281
+ label: '<p>Please draw the shape in the box to submit the form:</p>',
282
+
283
+ // Callback function to execute when a user successfully draws the shape
284
+ // Passed in the form, the canvas and the canvas context
285
+ // Scope (this) is active plugin options object (opts)
286
+ // NB: The default onSuccess callback function enables the submit button, and adds the form action attribute:
287
+ onSuccess: function($form, $canvas, ctx) {
288
+ var opts = this,
289
+ $submit = opts.submitId ? $form.find(opts.submitId) : $form.find('input[type=submit]:disabled');
290
+
291
+ // Set the form action:
292
+ $form.attr( 'action', $(opts.actionId).val() );
293
+
294
+ // Enable the submit button:
295
+ //$submit.prop('disabled', false);
296
+ $form.submit();
297
+ return;
298
+ },
299
+
300
+ // Callback function to execute when a user successfully draws the shape
301
+ // Passed in the form, the canvas and the canvas context
302
+ // Scope (this) is active plugin options object (opts)
303
+ onError: function($form, $canvas, ctx) {
304
+ var opts = this;
305
+ return;
306
+ }
307
+ };
308
+
309
+
310
+
311
+
312
+
313
+ /*!
314
+ * Harmony | mrdoob | Ribbon Brush class
315
+ * http://mrdoob.com/projects/harmony/
316
+ */
317
+
318
+ function Ribbon( ctx ) {
319
+ this.init( ctx );
320
+ }
321
+
322
+ Ribbon.prototype = {
323
+ ctx: null,
324
+ X: null,
325
+ Y: null,
326
+ painters: null,
327
+ interval: null,
328
+ init: function( ctx ) {
329
+ var scope = this,
330
+ userAgent = navigator.userAgent.toLowerCase(),
331
+ brushSize = ( userAgent.search("android") > -1 || userAgent.search("iphone") > -1 ) ? 2 : 1,
332
+ strokeColor = [0, 0, 0];
333
+
334
+ this.ctx = ctx;
335
+ this.ctx.globalCompositeOperation = 'source-over';
336
+
337
+ this.X = this.ctx.canvasWidth / 2;
338
+ this.Y = this.ctx.canvasHeight / 2;
339
+
340
+ this.painters = [];
341
+
342
+ // Draw each of the lines:
343
+ for ( var i = 0; i < 38; i++ ) {
344
+ this.painters.push({
345
+ dx: this.ctx.canvasWidth / 2,
346
+ dy: this.ctx.canvasHeight / 2,
347
+ ax: 0,
348
+ ay: 0,
349
+ div: 0.1,
350
+ ease: Math.random() * 0.18 + 0.60
351
+ });
352
+ }
353
+
354
+ // Set the ticker:
355
+ this.interval = setInterval( update, 1000/60 );
356
+
357
+ function update() {
358
+ var i;
359
+
360
+ scope.ctx.lineWidth = brushSize;
361
+ scope.ctx.strokeStyle = "rgba(" + strokeColor[0] + ", " + strokeColor[1] + ", " + strokeColor[2] + ", " + 0.06 + ")";
362
+
363
+ for ( i = 0; i < scope.painters.length; i++ ) {
364
+ scope.ctx.beginPath();
365
+ scope.ctx.moveTo(scope.painters[i].dx, scope.painters[i].dy);
366
+
367
+ scope.painters[i].dx -= scope.painters[i].ax = (scope.painters[i].ax + (scope.painters[i].dx - scope.X) * scope.painters[i].div) * scope.painters[i].ease;
368
+ scope.painters[i].dy -= scope.painters[i].ay = (scope.painters[i].ay + (scope.painters[i].dy - scope.Y) * scope.painters[i].div) * scope.painters[i].ease;
369
+ scope.ctx.lineTo(scope.painters[i].dx, scope.painters[i].dy);
370
+ scope.ctx.stroke();
371
+ }
372
+ }
373
+ },
374
+ destroy: function() {
375
+ clearInterval(this.interval);
376
+ },
377
+ strokeStart: function( X, Y ) {
378
+ this.X = X;
379
+ this.Y = Y
380
+
381
+ for (var i = 0; i < this.painters.length; i++) {
382
+ this.painters[i].dx = X;
383
+ this.painters[i].dy = Y;
384
+ }
385
+
386
+ this.shouldDraw = true;
387
+ },
388
+ stroke: function( X, Y ) {
389
+ this.X = X;
390
+ this.Y = Y;
391
+ }
392
+ };
393
+
394
+
395
+
396
+ /*!
397
+ * The $1 Unistroke Recognizer
398
+ * http://depts.washington.edu/aimgroup/proj/dollar/
399
+ *
400
+ * Jacob O. Wobbrock, Ph.D. | wobbrock@u.washington.edu
401
+ * Andrew D. Wilson, Ph.D. | awilson@microsoft.com
402
+ * Yang Li, Ph.D. | yangli@cs.washington.edu
403
+ *
404
+ * Modified to include the Protractor gesture recognizing algorithm
405
+ * http://www.yangl.org/pdf/protractor-chi2010.pdf
406
+ *
407
+ * Adapted and modified for purpose by Joss Crowcroft
408
+ * http://www.josscrowcroft.com
409
+ *
410
+ * The original software is distributed under the "New BSD License" agreement
411
+ *
412
+ * Copyright (c) 2007-2011, Jacob O. Wobbrock, Andrew D. Wilson and Yang Li. All rights reserved.
413
+ **/
414
+
415
+ // Point class
416
+ function Point(x, y) {
417
+ this.X = x;
418
+ this.Y = y;
419
+ }
420
+
421
+ // Wrapper for Point class (saves mega kb when compressing the template definitions):
422
+ function NewPoint(x, y) {
423
+ return new Point(x, y)
424
+ }
425
+
426
+ // Rectangle class
427
+ function Rectangle(x, y, width, height) {
428
+ this.X = x;
429
+ this.Y = y;
430
+ this.Width = width;
431
+ this.Height = height;
432
+ }
433
+
434
+ // Template class: a unistroke template
435
+ function Template(name, points) {
436
+ this.Name = name;
437
+ this.Points = Resample(points, NumPoints);
438
+ var radians = IndicativeAngle(this.Points);
439
+ this.Points = RotateBy(this.Points, -radians);
440
+ this.Points = ScaleTo(this.Points, SquareSize);
441
+ this.Points = TranslateTo(this.Points, Origin);
442
+ this.Vector = Vectorize(this.Points); // for Protractor
443
+ }
444
+
445
+ // Result class
446
+ function Result(name, score) {
447
+ this.Name = name;
448
+ this.Score = score;
449
+ }
450
+
451
+ // DollarRecognizer class constants
452
+ var NumTemplates = 16,
453
+ NumPoints = 64,
454
+ SquareSize = 250.0,
455
+ Origin = NewPoint(0,0);
456
+
457
+ // DollarRecognizer class
458
+ function DollarRecognizer() {
459
+
460
+ // Predefined templates for each gesture type:
461
+ this.Templates = [];
462
+
463
+ this.Templates.push( new Template("triangle", [NewPoint(137,139),NewPoint(135,141),NewPoint(133,144),NewPoint(132,146),NewPoint(130,149),NewPoint(128,151),NewPoint(126,155),NewPoint(123,160),NewPoint(120,166),NewPoint(116,171),NewPoint(112,177),NewPoint(107,183),NewPoint(102,188),NewPoint(100,191),NewPoint(95,195),NewPoint(90,199),NewPoint(86,203),NewPoint(82,206),NewPoint(80,209),NewPoint(75,213),NewPoint(73,213),NewPoint(70,216),NewPoint(67,219),NewPoint(64,221),NewPoint(61,223),NewPoint(60,225),NewPoint(62,226),NewPoint(65,225),NewPoint(67,226),NewPoint(74,226),NewPoint(77,227),NewPoint(85,229),NewPoint(91,230),NewPoint(99,231),NewPoint(108,232),NewPoint(116,233),NewPoint(125,233),NewPoint(134,234),NewPoint(145,233),NewPoint(153,232),NewPoint(160,233),NewPoint(170,234),NewPoint(177,235),NewPoint(179,236),NewPoint(186,237),NewPoint(193,238),NewPoint(198,239),NewPoint(200,237),NewPoint(202,239),NewPoint(204,238),NewPoint(206,234),NewPoint(205,230),NewPoint(202,222),NewPoint(197,216),NewPoint(192,207),NewPoint(186,198),NewPoint(179,189),NewPoint(174,183),NewPoint(170,178),NewPoint(164,171),NewPoint(161,168),NewPoint(154,160),NewPoint(148,155),NewPoint(143,150),NewPoint(138,148),NewPoint(136,148)]) );
464
+
465
+ this.Templates.push( new Template("x", [NewPoint(87,142),NewPoint(89,145),NewPoint(91,148),NewPoint(93,151),NewPoint(96,155),NewPoint(98,157),NewPoint(100,160),NewPoint(102,162),NewPoint(106,167),NewPoint(108,169),NewPoint(110,171),NewPoint(115,177),NewPoint(119,183),NewPoint(123,189),NewPoint(127,193),NewPoint(129,196),NewPoint(133,200),NewPoint(137,206),NewPoint(140,209),NewPoint(143,212),NewPoint(146,215),NewPoint(151,220),NewPoint(153,222),NewPoint(155,223),NewPoint(157,225),NewPoint(158,223),NewPoint(157,218),NewPoint(155,211),NewPoint(154,208),NewPoint(152,200),NewPoint(150,189),NewPoint(148,179),NewPoint(147,170),NewPoint(147,158),NewPoint(147,148),NewPoint(147,141),NewPoint(147,136),NewPoint(144,135),NewPoint(142,137),NewPoint(140,139),NewPoint(135,145),NewPoint(131,152),NewPoint(124,163),NewPoint(116,177),NewPoint(108,191),NewPoint(100,206),NewPoint(94,217),NewPoint(91,222),NewPoint(89,225),NewPoint(87,226),NewPoint(87,224)]) );
466
+
467
+ this.Templates.push( new Template("rectangle", [NewPoint(78,149),NewPoint(78,153),NewPoint(78,157),NewPoint(78,160),NewPoint(79,162),NewPoint(79,164),NewPoint(79,167),NewPoint(79,169),NewPoint(79,173),NewPoint(79,178),NewPoint(79,183),NewPoint(80,189),NewPoint(80,193),NewPoint(80,198),NewPoint(80,202),NewPoint(81,208),NewPoint(81,210),NewPoint(81,216),NewPoint(82,222),NewPoint(82,224),NewPoint(82,227),NewPoint(83,229),NewPoint(83,231),NewPoint(85,230),NewPoint(88,232),NewPoint(90,233),NewPoint(92,232),NewPoint(94,233),NewPoint(99,232),NewPoint(102,233),NewPoint(106,233),NewPoint(109,234),NewPoint(117,235),NewPoint(123,236),NewPoint(126,236),NewPoint(135,237),NewPoint(142,238),NewPoint(145,238),NewPoint(152,238),NewPoint(154,239),NewPoint(165,238),NewPoint(174,237),NewPoint(179,236),NewPoint(186,235),NewPoint(191,235),NewPoint(195,233),NewPoint(197,233),NewPoint(200,233),NewPoint(201,235),NewPoint(201,233),NewPoint(199,231),NewPoint(198,226),NewPoint(198,220),NewPoint(196,207),NewPoint(195,195),NewPoint(195,181),NewPoint(195,173),NewPoint(195,163),NewPoint(194,155),NewPoint(192,145),NewPoint(192,143),NewPoint(192,138),NewPoint(191,135),NewPoint(191,133),NewPoint(191,130),NewPoint(190,128),NewPoint(188,129),NewPoint(186,129),NewPoint(181,132),NewPoint(173,131),NewPoint(162,131),NewPoint(151,132),NewPoint(149,132),NewPoint(138,132),NewPoint(136,132),NewPoint(122,131),NewPoint(120,131),NewPoint(109,130),NewPoint(107,130),NewPoint(90,132),NewPoint(81,133),NewPoint(76,133)]) );
468
+
469
+ this.Templates.push( new Template("circle", [NewPoint(127,141),NewPoint(124,140),NewPoint(120,139),NewPoint(118,139),NewPoint(116,139),NewPoint(111,140),NewPoint(109,141),NewPoint(104,144),NewPoint(100,147),NewPoint(96,152),NewPoint(93,157),NewPoint(90,163),NewPoint(87,169),NewPoint(85,175),NewPoint(83,181),NewPoint(82,190),NewPoint(82,195),NewPoint(83,200),NewPoint(84,205),NewPoint(88,213),NewPoint(91,216),NewPoint(96,219),NewPoint(103,222),NewPoint(108,224),NewPoint(111,224),NewPoint(120,224),NewPoint(133,223),NewPoint(142,222),NewPoint(152,218),NewPoint(160,214),NewPoint(167,210),NewPoint(173,204),NewPoint(178,198),NewPoint(179,196),NewPoint(182,188),NewPoint(182,177),NewPoint(178,167),NewPoint(170,150),NewPoint(163,138),NewPoint(152,130),NewPoint(143,129),NewPoint(140,131),NewPoint(129,136),NewPoint(126,139)]) );
470
+
471
+ this.Templates.push( new Template("check", [NewPoint(91,185),NewPoint(93,185),NewPoint(95,185),NewPoint(97,185),NewPoint(100,188),NewPoint(102,189),NewPoint(104,190),NewPoint(106,193),NewPoint(108,195),NewPoint(110,198),NewPoint(112,201),NewPoint(114,204),NewPoint(115,207),NewPoint(117,210),NewPoint(118,212),NewPoint(120,214),NewPoint(121,217),NewPoint(122,219),NewPoint(123,222),NewPoint(124,224),NewPoint(126,226),NewPoint(127,229),NewPoint(129,231),NewPoint(130,233),NewPoint(129,231),NewPoint(129,228),NewPoint(129,226),NewPoint(129,224),NewPoint(129,221),NewPoint(129,218),NewPoint(129,212),NewPoint(129,208),NewPoint(130,198),NewPoint(132,189),NewPoint(134,182),NewPoint(137,173),NewPoint(143,164),NewPoint(147,157),NewPoint(151,151),NewPoint(155,144),NewPoint(161,137),NewPoint(165,131),NewPoint(171,122),NewPoint(174,118),NewPoint(176,114),NewPoint(177,112),NewPoint(177,114),NewPoint(175,116),NewPoint(173,118)]) );
472
+
473
+ this.Templates.push( new Template("caret", [NewPoint(79,245),NewPoint(79,242),NewPoint(79,239),NewPoint(80,237),NewPoint(80,234),NewPoint(81,232),NewPoint(82,230),NewPoint(84,224),NewPoint(86,220),NewPoint(86,218),NewPoint(87,216),NewPoint(88,213),NewPoint(90,207),NewPoint(91,202),NewPoint(92,200),NewPoint(93,194),NewPoint(94,192),NewPoint(96,189),NewPoint(97,186),NewPoint(100,179),NewPoint(102,173),NewPoint(105,165),NewPoint(107,160),NewPoint(109,158),NewPoint(112,151),NewPoint(115,144),NewPoint(117,139),NewPoint(119,136),NewPoint(119,134),NewPoint(120,132),NewPoint(121,129),NewPoint(122,127),NewPoint(124,125),NewPoint(126,124),NewPoint(129,125),NewPoint(131,127),NewPoint(132,130),NewPoint(136,139),NewPoint(141,154),NewPoint(145,166),NewPoint(151,182),NewPoint(156,193),NewPoint(157,196),NewPoint(161,209),NewPoint(162,211),NewPoint(167,223),NewPoint(169,229),NewPoint(170,231),NewPoint(173,237),NewPoint(176,242),NewPoint(177,244),NewPoint(179,250),NewPoint(181,255),NewPoint(182,257)]) );
474
+
475
+ this.Templates.push( new Template("zigzag", [NewPoint(307,216),NewPoint(333,186),NewPoint(356,215),NewPoint(375,186),NewPoint(399,216),NewPoint(418,186)]) );
476
+
477
+ this.Templates.push( new Template("arrow", [NewPoint(68,222),NewPoint(70,220),NewPoint(73,218),NewPoint(75,217),NewPoint(77,215),NewPoint(80,213),NewPoint(82,212),NewPoint(84,210),NewPoint(87,209),NewPoint(89,208),NewPoint(92,206),NewPoint(95,204),NewPoint(101,201),NewPoint(106,198),NewPoint(112,194),NewPoint(118,191),NewPoint(124,187),NewPoint(127,186),NewPoint(132,183),NewPoint(138,181),NewPoint(141,180),NewPoint(146,178),NewPoint(154,173),NewPoint(159,171),NewPoint(161,170),NewPoint(166,167),NewPoint(168,167),NewPoint(171,166),NewPoint(174,164),NewPoint(177,162),NewPoint(180,160),NewPoint(182,158),NewPoint(183,156),NewPoint(181,154),NewPoint(178,153),NewPoint(171,153),NewPoint(164,153),NewPoint(160,153),NewPoint(150,154),NewPoint(147,155),NewPoint(141,157),NewPoint(137,158),NewPoint(135,158),NewPoint(137,158),NewPoint(140,157),NewPoint(143,156),NewPoint(151,154),NewPoint(160,152),NewPoint(170,149),NewPoint(179,147),NewPoint(185,145),NewPoint(192,144),NewPoint(196,144),NewPoint(198,144),NewPoint(200,144),NewPoint(201,147),NewPoint(199,149),NewPoint(194,157),NewPoint(191,160),NewPoint(186,167),NewPoint(180,176),NewPoint(177,179),NewPoint(171,187),NewPoint(169,189),NewPoint(165,194),NewPoint(164,196)]) );
478
+
479
+ this.Templates.push( new Template("leftbracket", [NewPoint(140,124),NewPoint(138,123),NewPoint(135,122),NewPoint(133,123),NewPoint(130,123),NewPoint(128,124),NewPoint(125,125),NewPoint(122,124),NewPoint(120,124),NewPoint(118,124),NewPoint(116,125),NewPoint(113,125),NewPoint(111,125),NewPoint(108,124),NewPoint(106,125),NewPoint(104,125),NewPoint(102,124),NewPoint(100,123),NewPoint(98,123),NewPoint(95,124),NewPoint(93,123),NewPoint(90,124),NewPoint(88,124),NewPoint(85,125),NewPoint(83,126),NewPoint(81,127),NewPoint(81,129),NewPoint(82,131),NewPoint(82,134),NewPoint(83,138),NewPoint(84,141),NewPoint(84,144),NewPoint(85,148),NewPoint(85,151),NewPoint(86,156),NewPoint(86,160),NewPoint(86,164),NewPoint(86,168),NewPoint(87,171),NewPoint(87,175),NewPoint(87,179),NewPoint(87,182),NewPoint(87,186),NewPoint(88,188),NewPoint(88,195),NewPoint(88,198),NewPoint(88,201),NewPoint(88,207),NewPoint(89,211),NewPoint(89,213),NewPoint(89,217),NewPoint(89,222),NewPoint(88,225),NewPoint(88,229),NewPoint(88,231),NewPoint(88,233),NewPoint(88,235),NewPoint(89,237),NewPoint(89,240),NewPoint(89,242),NewPoint(91,241),NewPoint(94,241),NewPoint(96,240),NewPoint(98,239),NewPoint(105,240),NewPoint(109,240),NewPoint(113,239),NewPoint(116,240),NewPoint(121,239),NewPoint(130,240),NewPoint(136,237),NewPoint(139,237),NewPoint(144,238),NewPoint(151,237),NewPoint(157,236),NewPoint(159,237)]) );
480
+
481
+ this.Templates.push( new Template("rightbracket", [NewPoint(112,138),NewPoint(112,136),NewPoint(115,136),NewPoint(118,137),NewPoint(120,136),NewPoint(123,136),NewPoint(125,136),NewPoint(128,136),NewPoint(131,136),NewPoint(134,135),NewPoint(137,135),NewPoint(140,134),NewPoint(143,133),NewPoint(145,132),NewPoint(147,132),NewPoint(149,132),NewPoint(152,132),NewPoint(153,134),NewPoint(154,137),NewPoint(155,141),NewPoint(156,144),NewPoint(157,152),NewPoint(158,161),NewPoint(160,170),NewPoint(162,182),NewPoint(164,192),NewPoint(166,200),NewPoint(167,209),NewPoint(168,214),NewPoint(168,216),NewPoint(169,221),NewPoint(169,223),NewPoint(169,228),NewPoint(169,231),NewPoint(166,233),NewPoint(164,234),NewPoint(161,235),NewPoint(155,236),NewPoint(147,235),NewPoint(140,233),NewPoint(131,233),NewPoint(124,233),NewPoint(117,235),NewPoint(114,238),NewPoint(112,238)]) );
482
+
483
+ this.Templates.push( new Template("v", [NewPoint(89,164),NewPoint(90,162),NewPoint(92,162),NewPoint(94,164),NewPoint(95,166),NewPoint(96,169),NewPoint(97,171),NewPoint(99,175),NewPoint(101,178),NewPoint(103,182),NewPoint(106,189),NewPoint(108,194),NewPoint(111,199),NewPoint(114,204),NewPoint(117,209),NewPoint(119,214),NewPoint(122,218),NewPoint(124,222),NewPoint(126,225),NewPoint(128,228),NewPoint(130,229),NewPoint(133,233),NewPoint(134,236),NewPoint(136,239),NewPoint(138,240),NewPoint(139,242),NewPoint(140,244),NewPoint(142,242),NewPoint(142,240),NewPoint(142,237),NewPoint(143,235),NewPoint(143,233),NewPoint(145,229),NewPoint(146,226),NewPoint(148,217),NewPoint(149,208),NewPoint(149,205),NewPoint(151,196),NewPoint(151,193),NewPoint(153,182),NewPoint(155,172),NewPoint(157,165),NewPoint(159,160),NewPoint(162,155),NewPoint(164,150),NewPoint(165,148),NewPoint(166,146)]) );
484
+
485
+ this.Templates.push( new Template("delete", [NewPoint(123,129),NewPoint(123,131),NewPoint(124,133),NewPoint(125,136),NewPoint(127,140),NewPoint(129,142),NewPoint(133,148),NewPoint(137,154),NewPoint(143,158),NewPoint(145,161),NewPoint(148,164),NewPoint(153,170),NewPoint(158,176),NewPoint(160,178),NewPoint(164,183),NewPoint(168,188),NewPoint(171,191),NewPoint(175,196),NewPoint(178,200),NewPoint(180,202),NewPoint(181,205),NewPoint(184,208),NewPoint(186,210),NewPoint(187,213),NewPoint(188,215),NewPoint(186,212),NewPoint(183,211),NewPoint(177,208),NewPoint(169,206),NewPoint(162,205),NewPoint(154,207),NewPoint(145,209),NewPoint(137,210),NewPoint(129,214),NewPoint(122,217),NewPoint(118,218),NewPoint(111,221),NewPoint(109,222),NewPoint(110,219),NewPoint(112,217),NewPoint(118,209),NewPoint(120,207),NewPoint(128,196),NewPoint(135,187),NewPoint(138,183),NewPoint(148,167),NewPoint(157,153),NewPoint(163,145),NewPoint(165,142),NewPoint(172,133),NewPoint(177,127),NewPoint(179,127),NewPoint(180,125)]) );
486
+
487
+ this.Templates.push( new Template("star", [NewPoint(75,250),NewPoint(75,247),NewPoint(77,244),NewPoint(78,242),NewPoint(79,239),NewPoint(80,237),NewPoint(82,234),NewPoint(82,232),NewPoint(84,229),NewPoint(85,225),NewPoint(87,222),NewPoint(88,219),NewPoint(89,216),NewPoint(91,212),NewPoint(92,208),NewPoint(94,204),NewPoint(95,201),NewPoint(96,196),NewPoint(97,194),NewPoint(98,191),NewPoint(100,185),NewPoint(102,178),NewPoint(104,173),NewPoint(104,171),NewPoint(105,164),NewPoint(106,158),NewPoint(107,156),NewPoint(107,152),NewPoint(108,145),NewPoint(109,141),NewPoint(110,139),NewPoint(112,133),NewPoint(113,131),NewPoint(116,127),NewPoint(117,125),NewPoint(119,122),NewPoint(121,121),NewPoint(123,120),NewPoint(125,122),NewPoint(125,125),NewPoint(127,130),NewPoint(128,133),NewPoint(131,143),NewPoint(136,153),NewPoint(140,163),NewPoint(144,172),NewPoint(145,175),NewPoint(151,189),NewPoint(156,201),NewPoint(161,213),NewPoint(166,225),NewPoint(169,233),NewPoint(171,236),NewPoint(174,243),NewPoint(177,247),NewPoint(178,249),NewPoint(179,251),NewPoint(180,253),NewPoint(180,255),NewPoint(179,257),NewPoint(177,257),NewPoint(174,255),NewPoint(169,250),NewPoint(164,247),NewPoint(160,245),NewPoint(149,238),NewPoint(138,230),NewPoint(127,221),NewPoint(124,220),NewPoint(112,212),NewPoint(110,210),NewPoint(96,201),NewPoint(84,195),NewPoint(74,190),NewPoint(64,182),NewPoint(55,175),NewPoint(51,172),NewPoint(49,170),NewPoint(51,169),NewPoint(56,169),NewPoint(66,169),NewPoint(78,168),NewPoint(92,166),NewPoint(107,164),NewPoint(123,161),NewPoint(140,162),NewPoint(156,162),NewPoint(171,160),NewPoint(173,160),NewPoint(186,160),NewPoint(195,160),NewPoint(198,161),NewPoint(203,163),NewPoint(208,163),NewPoint(206,164),NewPoint(200,167),NewPoint(187,172),NewPoint(174,179),NewPoint(172,181),NewPoint(153,192),NewPoint(137,201),NewPoint(123,211),NewPoint(112,220),NewPoint(99,229),NewPoint(90,237),NewPoint(80,244),NewPoint(73,250),NewPoint(69,254),NewPoint(69,252)]) );
488
+
489
+ this.Templates.push( new Template("pigtail", [NewPoint(81,219),NewPoint(84,218),NewPoint(86,220),NewPoint(88,220),NewPoint(90,220),NewPoint(92,219),NewPoint(95,220),NewPoint(97,219),NewPoint(99,220),NewPoint(102,218),NewPoint(105,217),NewPoint(107,216),NewPoint(110,216),NewPoint(113,214),NewPoint(116,212),NewPoint(118,210),NewPoint(121,208),NewPoint(124,205),NewPoint(126,202),NewPoint(129,199),NewPoint(132,196),NewPoint(136,191),NewPoint(139,187),NewPoint(142,182),NewPoint(144,179),NewPoint(146,174),NewPoint(148,170),NewPoint(149,168),NewPoint(151,162),NewPoint(152,160),NewPoint(152,157),NewPoint(152,155),NewPoint(152,151),NewPoint(152,149),NewPoint(152,146),NewPoint(149,142),NewPoint(148,139),NewPoint(145,137),NewPoint(141,135),NewPoint(139,135),NewPoint(134,136),NewPoint(130,140),NewPoint(128,142),NewPoint(126,145),NewPoint(122,150),NewPoint(119,158),NewPoint(117,163),NewPoint(115,170),NewPoint(114,175),NewPoint(117,184),NewPoint(120,190),NewPoint(125,199),NewPoint(129,203),NewPoint(133,208),NewPoint(138,213),NewPoint(145,215),NewPoint(155,218),NewPoint(164,219),NewPoint(166,219),NewPoint(177,219),NewPoint(182,218),NewPoint(192,216),NewPoint(196,213),NewPoint(199,212),NewPoint(201,211)]) );
490
+
491
+
492
+ // $1 Gesture Recognizer API (now using Protractor instead)
493
+ this.Recognize = function(points) {
494
+ var b = +Infinity,
495
+ t = 0,
496
+ radians,
497
+ i,
498
+ score,
499
+ vector;
500
+
501
+ points = Resample(points, NumPoints);
502
+ radians = IndicativeAngle(points);
503
+ points = RotateBy(points, -radians);
504
+ vector = Vectorize(points); // for Protractor
505
+
506
+ for (i = 0; i < this.Templates.length; i++) {
507
+ var d = OptimalCosineDistance(this.Templates[i].Vector, vector);
508
+ if (d < b) {
509
+ b = d; // best (least) distance
510
+ t = i; // unistroke template
511
+ }
512
+ }
513
+ return new Result(this.Templates[t].Name, 1 / b);
514
+ };
515
+
516
+ }
517
+
518
+ // Helper functions:
519
+ function Resample(points, n) {
520
+ var I = PathLength(points) / (n - 1), // interval length
521
+ D = 0.0,
522
+ newpoints = new Array(points[0]),
523
+ i;
524
+ for (i = 1; i < points.length; i++) {
525
+ var d = Distance(points[i - 1], points[i]);
526
+ if ((D + d) >= I) {
527
+ var qx = points[i - 1].X + ((I - D) / d) * (points[i].X - points[i - 1].X),
528
+ qy = points[i - 1].Y + ((I - D) / d) * (points[i].Y - points[i - 1].Y),
529
+ q = NewPoint(qx, qy);
530
+ newpoints[newpoints.length] = q; // append new point 'q'
531
+ points.splice(i, 0, q); // insert 'q' at position i in points s.t. 'q' will be the next i
532
+ D = 0.0;
533
+ }
534
+ else D += d;
535
+ }
536
+ // somtimes we fall a rounding-error short of adding the last point, so add it if so
537
+ if (newpoints.length == n - 1) {
538
+ newpoints[newpoints.length] = NewPoint(points[points.length - 1].X, points[points.length - 1].Y);
539
+ }
540
+ return newpoints;
541
+ }
542
+ function IndicativeAngle(points) {
543
+ var c = Centroid(points);
544
+ return Math.atan2(c.Y - points[0].Y, c.X - points[0].X);
545
+ }
546
+ function RotateBy(points, radians) {
547
+ var c = Centroid(points),
548
+ cos = Math.cos(radians),
549
+ sin = Math.sin(radians),
550
+ newpoints = [],
551
+ i;
552
+ for (i = 0; i < points.length; i++) {
553
+ var qx = (points[i].X - c.X) * cos - (points[i].Y - c.Y) * sin + c.X,
554
+ qy = (points[i].X - c.X) * sin + (points[i].Y - c.Y) * cos + c.Y;
555
+ newpoints[newpoints.length] = NewPoint(qx, qy);
556
+ }
557
+ return newpoints;
558
+ }
559
+ function ScaleTo(points, size) {
560
+ var B = BoundingBox(points),
561
+ newpoints = [],
562
+ i;
563
+ for (i = 0; i < points.length; i++) {
564
+ var qx = points[i].X * (size / B.Width),
565
+ qy = points[i].Y * (size / B.Height);
566
+ newpoints[newpoints.length] = NewPoint(qx, qy);
567
+ }
568
+ return newpoints;
569
+ }
570
+ function TranslateTo(points, pt) {
571
+ var c = Centroid(points),
572
+ newpoints = [],
573
+ i;
574
+ for (i = 0; i < points.length; i++) {
575
+ var qx = points[i].X + pt.X - c.X,
576
+ qy = points[i].Y + pt.Y - c.Y;
577
+ newpoints[newpoints.length] = NewPoint(qx, qy);
578
+ }
579
+ return newpoints;
580
+ }
581
+ function Vectorize(points) { // for Protractor
582
+ var sum = 0.0,
583
+ vector = [],
584
+ i,
585
+ magnitude;
586
+ for ( i = 0; i < points.length; i++) {
587
+ vector[vector.length] = points[i].X;
588
+ vector[vector.length] = points[i].Y;
589
+ sum += points[i].X * points[i].X + points[i].Y * points[i].Y;
590
+ }
591
+ magnitude = Math.sqrt(sum);
592
+ for ( i = 0; i < vector.length; i++ )
593
+ vector[i] /= magnitude;
594
+ return vector;
595
+ }
596
+ function OptimalCosineDistance(v1, v2) { // for Protractor
597
+ var a = 0.0,
598
+ b = 0.0,
599
+ i,
600
+ angle;
601
+ for (i = 0; i < v1.length; i += 2) {
602
+ a += v1[i] * v2[i] + v1[i + 1] * v2[i + 1];
603
+ b += v1[i] * v2[i + 1] - v1[i + 1] * v2[i];
604
+ }
605
+ angle = Math.atan(b / a);
606
+ return Math.acos(a * Math.cos(angle) + b * Math.sin(angle));
607
+ }
608
+ function Centroid(points) {
609
+ var x = 0.0,
610
+ y = 0.0,
611
+ i;
612
+ for (i = 0; i < points.length; i++) {
613
+ x += points[i].X;
614
+ y += points[i].Y;
615
+ }
616
+ x /= points.length;
617
+ y /= points.length;
618
+ return NewPoint(x, y);
619
+ }
620
+ function BoundingBox(points) {
621
+ var minX = +Infinity,
622
+ maxX = -Infinity,
623
+ minY = +Infinity,
624
+ maxY = -Infinity,
625
+ i;
626
+ for (i = 0; i < points.length; i++) {
627
+ if (points[i].X < minX)
628
+ minX = points[i].X;
629
+ if (points[i].X > maxX)
630
+ maxX = points[i].X;
631
+ if (points[i].Y < minY)
632
+ minY = points[i].Y;
633
+ if (points[i].Y > maxY)
634
+ maxY = points[i].Y;
635
+ }
636
+ return new Rectangle(minX, minY, maxX - minX, maxY - minY);
637
+ }
638
+ function PathLength(points) {
639
+ var d = 0.0,
640
+ i;
641
+ for (i = 1; i < points.length; i++) {
642
+ d += Distance(points[i - 1], points[i]);
643
+ }
644
+ return d;
645
+ }
646
+ function Distance(p1, p2) {
647
+ var dx = p2.X - p1.X,
648
+ dy = p2.Y - p1.Y;
649
+ return Math.sqrt(dx * dx + dy * dy);
650
+ }
651
+
652
+ })(jQuery);
@@ -0,0 +1,10 @@
1
+ /*!
2
+ * jQuery Placeholder 1.1.1
3
+ *
4
+ * Copyright (c) 2010 Michael J. Ryan (http://tracker1.info/)
5
+ *
6
+ * Dual licensed under the MIT and GPL licenses:
7
+ * http://www.opensource.org/licenses/mit-license.php
8
+ * http://www.gnu.org/licenses/gpl.html
9
+ */
10
+ (function(e){var g="PLACEHOLDER-INPUT";var f="PLACEHOLDER-LABEL";var a=false;var d={labelClass:"placeholder"};var i=document.createElement("input");if("placeholder" in i){e.fn.placeholder=e.fn.unplaceholder=function(){};delete i;return}delete i;e.fn.placeholder=function(j){c();var k=e.extend(d,j);this.each(function(){var n=Math.random().toString(32).replace(/\./,""),l=e(this),m=e('<label style="position:absolute;display:none;top:0;left:0;"></label>');if(!l.attr("placeholder")||l.data(g)===g){return}if(!l.attr("id")){l.attr("id")="input_"+n}m.attr("id",l.attr("id")+"_placeholder").data(g,"#"+l.attr("id")).attr("for",l.attr("id")).addClass(k.labelClass).addClass(k.labelClass+"-for-"+this.tagName.toLowerCase()).addClass(f).text(l.attr("placeholder"));l.data(f,"#"+m.attr("id")).data(g,g).addClass(g).after(m);b.call(this);h.call(this)})};e.fn.unplaceholder=function(){this.each(function(){var j=e(this),k=e(j.data(f));if(j.data(g)!==g){return}k.remove();j.removeData(g).removeData(f).removeClass(g)})};function c(){if(a){return}e("."+g).live("click",b).live("focusin",b).live("focusout",h);bound=true;a=true}function b(){var j=e(this),k=e(j.data(f));k.css("display","none")}function h(){var j=this;setTimeout(function(){var k=e(j);e(k.data(f)).css("top",k.position().top+"px").css("left",k.position().left+"px").css("display",!!k.val()?"none":"block")},200)}}(jQuery));
@@ -0,0 +1,56 @@
1
+ /*!
2
+ * jQuery MotionCAPTCHA Plugin CSS v1.0
3
+ *
4
+ * Concept and Development by Joss Crowcroft
5
+ * http://www.josscrowcroft.com
6
+ *
7
+ * Adapted and modified by Manuel Fernandez
8
+ * http://papayalabs.github.io
9
+ *
10
+ * Copyright (c) 2017 Papaya Labs | http://papayalabs.github.io
11
+ *
12
+ * Incoporates other open source projects, attributed below.
13
+ *
14
+ * Only these rules are absolutely necessary for the plugin.
15
+ * NB. The width & height of the canvas can be flexible to fit your layout but there's a recommended min/max
16
+ * - if you need it much bigger/smaller, you could tweak the background-positions of the shape classes.
17
+ */
18
+
19
+ /* The canvas: */
20
+ #mc-canvas {
21
+ width:220px; /* For best results, set width to between 210px and 240px */
22
+ height:154px; /* For best results, set height to between 140px and 170px */
23
+ background:#fff -9999px -9999px no-repeat;
24
+ background-image: url('motionCaptcha-shapes.jpg') !important;
25
+ padding:1px;
26
+ display: block;
27
+ border: 4px solid #433e45;
28
+ -webkit-border-radius: 4px;
29
+ -moz-border-radius: 4px;
30
+ border-radius: 4px;
31
+ }
32
+
33
+ #mc-canvas.mc-invalid {
34
+ border: 4px solid #aa4444;
35
+ }
36
+ /* Green border when valid */
37
+ #mc-canvas.mc-valid {
38
+ border: 4px solid #44aa44;
39
+ }
40
+ /* The shapes: */
41
+ #mc-canvas.triangle { background-position: 10px 10px }
42
+ #mc-canvas.x { background-position:-200px 10px }
43
+ #mc-canvas.rectangle { background-position:-400px 10px }
44
+ #mc-canvas.circle { background-position:-600px 10px }
45
+ #mc-canvas.check { background-position: 10px -150px }
46
+ #mc-canvas.caret { background-position:-200px -150px }
47
+ #mc-canvas.zigzag { background-position:-400px -150px }
48
+ #mc-canvas.arrow { background-position:-600px -150px }
49
+ #mc-canvas.leftbracket { background-position: 10px -300px }
50
+ #mc-canvas.rightbracket { background-position:-200px -300px }
51
+ #mc-canvas.v { background-position:-400px -300px }
52
+ #mc-canvas.delete { background-position:-600px -300px }
53
+ #mc-canvas.leftbrace { background-position: 10px -450px }
54
+ #mc-canvas.rightbrace { background-position:-200px -450px }
55
+ #mc-canvas.star { background-position:-400px -450px }
56
+ #mc-canvas.pigtail { background-position:-600px -450px }
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: unity-captcha
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Manuel Fernandez
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-07-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.13'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ description: 'Unity Captcha is a gem that use two levels of Captcha. First level is
42
+ about Left Hemisphere: Logic and Math. Second level is about Right Hemisphere: Intuition
43
+ and Drawing. This two levels functioning in complete harmony creates unity and this
44
+ is the base design of this Captcha.'
45
+ email:
46
+ - papayalabs@gmail.com
47
+ executables: []
48
+ extensions: []
49
+ extra_rdoc_files: []
50
+ files:
51
+ - ".DS_Store"
52
+ - ".gitignore"
53
+ - CODE_OF_CONDUCT.md
54
+ - Gemfile
55
+ - LICENSE.txt
56
+ - README.md
57
+ - Rakefile
58
+ - bin/console
59
+ - bin/setup
60
+ - lib/unity/captcha.rb
61
+ - lib/unity/captcha/engine.rb
62
+ - lib/unity/captcha/version.rb
63
+ - unity-captcha.gemspec
64
+ - vendor/.DS_Store
65
+ - vendor/assets/.DS_Store
66
+ - vendor/assets/javascripts/jquery.motionCaptcha.1.0.js
67
+ - vendor/assets/javascripts/jquery.placeholder.1.1.1.min.js
68
+ - vendor/assets/stylesheets/jquery.motionCaptcha.1.0.css
69
+ - vendor/assets/stylesheets/motionCaptcha-shapes.jpg
70
+ homepage: https://github.com/papayalabs/unity-captcha
71
+ licenses:
72
+ - MIT
73
+ metadata:
74
+ allowed_push_host: https://rubygems.org
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubyforge_project:
91
+ rubygems_version: 2.4.8
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: Unity Captcha is a gem that use two levels of captcha based on the hemispheres
95
+ of the brain
96
+ test_files: []