rucaptcha 3.1.2-aarch64-linux

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,14 @@
1
+ [package]
2
+ edition = "2021"
3
+ name = "rucaptcha"
4
+ version = "3.0.0"
5
+
6
+ [lib]
7
+ crate-type = ["cdylib"]
8
+
9
+ [dependencies]
10
+ image = "0.24.4"
11
+ imageproc = "0.23.0"
12
+ magnus = "0.4"
13
+ rand = "0.8.5"
14
+ rusttype = "0.9.2"
@@ -0,0 +1,4 @@
1
+ require "mkmf"
2
+ require "rb_sys/mkmf"
3
+
4
+ create_rust_makefile("rucaptcha/rucaptcha")
@@ -0,0 +1,257 @@
1
+ use image::{ImageBuffer, Rgb};
2
+ use imageproc::drawing::{draw_cubic_bezier_curve_mut, draw_text_mut};
3
+ use imageproc::noise::gaussian_noise_mut;
4
+ use rand::{thread_rng, Rng};
5
+ use rusttype::{Font, Scale};
6
+ use std::io::Cursor;
7
+
8
+ static BASIC_CHAR: [char; 54] = [
9
+ '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M',
10
+ 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
11
+ 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
12
+ ];
13
+
14
+ static FONT_BYTES1: &[u8; 145008] = include_bytes!("../fonts/FuzzyBubbles-Regular.ttf");
15
+ static FONT_BYTES2: &[u8; 37792] = include_bytes!("../fonts/Handlee-Regular.ttf");
16
+
17
+ // https://coolors.co/cc0b8f-7c0abe-5700c8-3c2ea4-3d56a8-3fa67e-45bb30-69d003-a0d003-d8db02
18
+ static COLORS: [(u8, u8, u8); 14] = [
19
+ (197, 166, 3),
20
+ (187, 87, 5),
21
+ (176, 7, 7),
22
+ (186, 9, 56),
23
+ (204, 11, 143),
24
+ (124, 10, 190),
25
+ (87, 0, 200),
26
+ (61, 86, 168),
27
+ (63, 166, 126),
28
+ (69, 187, 48),
29
+ (105, 208, 3),
30
+ (160, 208, 3),
31
+ (216, 219, 2),
32
+ (50, 50, 50),
33
+ ];
34
+
35
+ static SCALE_SM: u32 = 32;
36
+ static SCALE_MD: u32 = 45;
37
+ static SCALE_LG: u32 = 55;
38
+
39
+ fn rand_num(len: usize) -> usize {
40
+ let mut rng = thread_rng();
41
+ rng.gen_range(0..=len)
42
+ }
43
+
44
+ fn get_captcha(len: usize) -> Vec<String> {
45
+ let mut res = vec![];
46
+ for _ in 0..len {
47
+ let rnd = rand_num(53);
48
+ res.push(BASIC_CHAR[rnd].to_string())
49
+ }
50
+ res
51
+ }
52
+
53
+ #[allow(unused)]
54
+ fn get_color() -> Rgb<u8> {
55
+ let rnd = rand_num(COLORS.len() - 1);
56
+ let c = COLORS[rnd];
57
+ Rgb([c.0, c.1, c.2])
58
+ }
59
+
60
+ fn get_colors(num: usize) -> Vec<Rgb<u8>> {
61
+ let rnd = rand_num(COLORS.len());
62
+ let mut out = vec![];
63
+ for i in 0..num {
64
+ let c = COLORS[(rnd + i) % COLORS.len()];
65
+ out.push(Rgb([c.0, c.1, c.2]))
66
+ }
67
+
68
+ out
69
+ }
70
+
71
+ fn get_next(min: f32, max: u32) -> f32 {
72
+ min + rand_num(max as usize - min as usize) as f32
73
+ }
74
+
75
+ fn get_font() -> Font<'static> {
76
+ match rand_num(2) {
77
+ 0 => Font::try_from_bytes(FONT_BYTES1).unwrap(),
78
+ 1 => Font::try_from_bytes(FONT_BYTES2).unwrap(),
79
+ _ => Font::try_from_bytes(FONT_BYTES1).unwrap(),
80
+ }
81
+ }
82
+
83
+ fn get_image(width: usize, height: usize) -> ImageBuffer<Rgb<u8>, Vec<u8>> {
84
+ ImageBuffer::from_fn(width as u32, height as u32, |_, _| {
85
+ image::Rgb([255, 255, 255])
86
+ })
87
+ }
88
+
89
+ fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>) {
90
+ let c = (image.width() - 20) / res.len() as u32;
91
+ let y = image.height() / 3 - 15;
92
+
93
+ let h = image.height() as f32;
94
+
95
+ let scale = match res.len() {
96
+ 1..=3 => SCALE_LG,
97
+ 4..=5 => SCALE_MD,
98
+ _ => SCALE_SM,
99
+ } as f32;
100
+
101
+ let colors = get_colors(res.len());
102
+ let line_colors = get_colors(res.len());
103
+
104
+ let xscale = scale - rand_num((scale * 0.2) as usize) as f32;
105
+ let yscale = h as f32 - rand_num((h * 0.2) as usize) as f32;
106
+
107
+ // Draw line, ellipse first as background
108
+ for (i, _) in res.iter().enumerate() {
109
+ let line_color = line_colors[i];
110
+ draw_interference_line(1, image, line_color);
111
+ draw_interference_ellipse(1, image, line_color);
112
+ }
113
+
114
+ // Draw text
115
+ for (i, _) in res.iter().enumerate() {
116
+ let text = &res[i];
117
+
118
+ let color = colors[i];
119
+ let font = get_font();
120
+
121
+ for j in 0..(rand_num(3) + 1) as i32 {
122
+ // Draw text again with offset
123
+ let offset = j * (rand_num(2) as i32);
124
+ draw_text_mut(
125
+ image,
126
+ color,
127
+ 10 + offset + (i as u32 * c) as i32,
128
+ y as i32 as i32,
129
+ Scale {
130
+ x: xscale + offset as f32,
131
+ y: yscale as f32,
132
+ },
133
+ &font,
134
+ text,
135
+ );
136
+ }
137
+ }
138
+ }
139
+
140
+ fn draw_interference_line(num: usize, image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>, color: Rgb<u8>) {
141
+ for _ in 0..num {
142
+ let width = image.width();
143
+ let height = image.height();
144
+ let x1: f32 = 5.0;
145
+ let y1 = get_next(x1, height / 2);
146
+
147
+ let x2 = (width - 5) as f32;
148
+ let y2 = get_next(5.0, height - 5);
149
+
150
+ let ctrl_x = get_next((width / 6) as f32, width / 4 * 3);
151
+ let ctrl_y = get_next(x1, height - 5);
152
+
153
+ let ctrl_x2 = get_next((width / 12) as f32, width / 12 * 3);
154
+ let ctrl_y2 = get_next(x1, height - 5);
155
+ // Randomly draw bezier curves
156
+ draw_cubic_bezier_curve_mut(
157
+ image,
158
+ (x1, y1),
159
+ (x2, y2),
160
+ (ctrl_x, ctrl_y),
161
+ (ctrl_x2, ctrl_y2),
162
+ color,
163
+ );
164
+ }
165
+ }
166
+
167
+ fn draw_interference_ellipse(
168
+ num: usize,
169
+ image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>,
170
+ color: Rgb<u8>,
171
+ ) {
172
+ for _ in 0..num {
173
+ // max cycle width 20px
174
+ let w = (10 + rand_num(10)) as i32;
175
+ let x = rand_num((image.width() - 25) as usize) as i32;
176
+ let y = rand_num((image.height() - 15) as usize) as i32;
177
+
178
+ imageproc::drawing::draw_filled_ellipse_mut(image, (x, y), w, w, color);
179
+ }
180
+ }
181
+
182
+ pub struct Captcha {
183
+ pub text: String,
184
+ pub image: Vec<u8>,
185
+ }
186
+
187
+ pub struct CaptchaBuilder {
188
+ length: usize,
189
+ width: usize,
190
+ height: usize,
191
+ complexity: usize,
192
+ }
193
+
194
+ impl CaptchaBuilder {
195
+ pub fn new() -> Self {
196
+ CaptchaBuilder {
197
+ length: 4,
198
+ width: 220,
199
+ height: 70,
200
+ complexity: 5,
201
+ }
202
+ }
203
+
204
+ pub fn length(mut self, length: usize) -> Self {
205
+ self.length = length;
206
+ self
207
+ }
208
+
209
+ // pub fn width(mut self, width: usize) -> Self {
210
+ // self.width = width;
211
+ // self
212
+ // }
213
+
214
+ // pub fn height(mut self, height: usize) -> Self {
215
+ // self.height = height;
216
+ // self
217
+ // }
218
+
219
+ pub fn complexity(mut self, complexity: usize) -> Self {
220
+ let mut complexity = complexity;
221
+ if complexity > 10 {
222
+ complexity = 10;
223
+ }
224
+ if complexity < 1 {
225
+ complexity = 1;
226
+ }
227
+ self.complexity = complexity;
228
+ self
229
+ }
230
+
231
+ pub fn build(self) -> Captcha {
232
+ // Generate an array of captcha characters
233
+ let res = get_captcha(self.length);
234
+
235
+ let text = res.join("");
236
+
237
+ // Create a white background image
238
+ let mut image = get_image(self.width, self.height);
239
+
240
+ // Loop to write the verification code string into the background image
241
+ cyclic_write_character(&res, &mut image);
242
+
243
+ gaussian_noise_mut(
244
+ &mut image,
245
+ (self.complexity - 1) as f64,
246
+ ((10 * self.complexity) - 10) as f64,
247
+ ((5 * self.complexity) - 5) as u64,
248
+ );
249
+
250
+ let mut bytes: Vec<u8> = Vec::new();
251
+ image
252
+ .write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Jpeg)
253
+ .unwrap();
254
+
255
+ Captcha { text, image: bytes }
256
+ }
257
+ }
@@ -0,0 +1,18 @@
1
+ use magnus::{define_class, function, Error, Object};
2
+
3
+ mod captcha;
4
+
5
+ pub fn create(len: usize, difficulty: usize) -> (String, Vec<u8>) {
6
+ let c = captcha::CaptchaBuilder::new();
7
+ let out = c.complexity(difficulty).length(len).build();
8
+
9
+ (out.text, out.image)
10
+ }
11
+
12
+ #[magnus::init]
13
+ fn init() -> Result<(), Error> {
14
+ let class = define_class("RuCaptchaCore", Default::default())?;
15
+ class.define_singleton_method("create", function!(create, 2))?;
16
+
17
+ Ok(())
18
+ }
Binary file
Binary file
Binary file
@@ -0,0 +1,12 @@
1
+ require "fileutils"
2
+
3
+ module RuCaptcha
4
+ class << self
5
+ def cache
6
+ return @cache if defined? @cache
7
+
8
+ @cache = ActiveSupport::Cache.lookup_store(RuCaptcha.config.cache_store)
9
+ @cache
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ module RuCaptcha
2
+ class Configuration
3
+ # Store Captcha code where, this config more like Rails config.cache_store
4
+ # default: Rails application config.cache_store
5
+ attr_accessor :cache_store
6
+ # rucaptcha expire time, default 2 minutes
7
+ attr_accessor :expires_in
8
+ # Chars length: default 5, allows: [3..7]
9
+ attr_accessor :length
10
+ # Hard mode, default: 5, allows: [1..10]
11
+ attr_accessor :difficulty
12
+ # skip_cache_store_check, default: false
13
+ attr_accessor :skip_cache_store_check
14
+ end
15
+ end
@@ -0,0 +1,100 @@
1
+ module RuCaptcha
2
+ module ControllerHelpers
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ helper_method :verify_rucaptcha?
7
+ end
8
+
9
+ def rucaptcha_session_id
10
+ cookies[:_rucaptcha_session_id]
11
+ end
12
+
13
+ # session key of rucaptcha
14
+ def rucaptcha_sesion_key_key
15
+ warning_when_session_invalid if rucaptcha_session_id.blank?
16
+
17
+ # With https://github.com/rack/rack/commit/7fecaee81f59926b6e1913511c90650e76673b38
18
+ # to protected session_id into secret
19
+ session_id_digest = Digest::SHA256.hexdigest(rucaptcha_session_id.inspect)
20
+ ["rucaptcha-session", session_id_digest].join(":")
21
+ end
22
+
23
+ # Generate a new Captcha
24
+ def generate_rucaptcha
25
+ generate_rucaptcha_session_id
26
+
27
+ res = RuCaptcha.generate
28
+ session_val = {
29
+ code: res[0],
30
+ time: Time.now.to_i
31
+ }
32
+ RuCaptcha.cache.write(rucaptcha_sesion_key_key, session_val, expires_in: RuCaptcha.config.expires_in)
33
+ res[1]
34
+ end
35
+
36
+ # Verify captcha code
37
+ #
38
+ # params:
39
+ # resource - [optional] a ActiveModel object, if given will add validation error message to object.
40
+ # :keep_session - if true, RuCaptcha will not delete the captcha code session.
41
+ # :captcha - if given, the value of it will be used to verify the captcha,
42
+ # if do not give or blank, the value of params[:_rucaptcha] will be used to verify the captcha
43
+ #
44
+ # exmaples:
45
+ #
46
+ # verify_rucaptcha?
47
+ # verify_rucaptcha?(user, keep_session: true)
48
+ # verify_rucaptcha?(nil, keep_session: true)
49
+ # verify_rucaptcha?(nil, captcha: params[:user][:captcha])
50
+ #
51
+ def verify_rucaptcha?(_resource = nil, opts = {})
52
+ opts ||= {}
53
+
54
+ store_info = RuCaptcha.cache.read(rucaptcha_sesion_key_key)
55
+ # make sure move used key
56
+ RuCaptcha.cache.delete(rucaptcha_sesion_key_key) unless opts[:keep_session]
57
+
58
+ # Make sure session exist
59
+ return add_rucaptcha_validation_error if store_info.blank?
60
+
61
+ # Make sure not expire
62
+ return add_rucaptcha_validation_error if (Time.now.to_i - store_info[:time]) > RuCaptcha.config.expires_in
63
+
64
+ # Make sure parama have captcha
65
+ captcha = (opts[:captcha] || params[:_rucaptcha] || "").downcase.strip
66
+ return add_rucaptcha_validation_error if captcha.blank?
67
+
68
+ return add_rucaptcha_validation_error if captcha != store_info[:code]
69
+
70
+ true
71
+ end
72
+
73
+ private
74
+
75
+ def generate_rucaptcha_session_id
76
+ return if rucaptcha_session_id.present?
77
+
78
+ cookies[:_rucaptcha_session_id] = {
79
+ value: SecureRandom.hex(16),
80
+ expires: 1.day
81
+ }
82
+ end
83
+
84
+ def add_rucaptcha_validation_error
85
+ if defined?(resource) && resource && resource.respond_to?(:errors)
86
+ resource.errors.add(:base, t("rucaptcha.invalid"))
87
+ end
88
+ false
89
+ end
90
+
91
+ def warning_when_session_invalid
92
+ return unless Rails.env.development?
93
+
94
+ Rails.logger.warn "
95
+ WARNING! The session.id is blank, RuCaptcha can't work properly, please keep session available.
96
+ More details about this: https://github.com/huacnlee/rucaptcha/pull/66
97
+ "
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,15 @@
1
+ module RuCaptcha
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace RuCaptcha
4
+
5
+ initializer "rucaptcha.init" do |app|
6
+ # https://github.com/rails/rails/blob/3-2-stable/actionpack/lib/action_dispatch/routing/route_set.rb#L268
7
+ # `app.routes.prepend` start from Rails 3.2 - 5.0
8
+ app.routes.prepend do
9
+ mount RuCaptcha::Engine => "/rucaptcha"
10
+ end
11
+
12
+ RuCaptcha.check_cache_store! unless RuCaptcha.config.skip_cache_store_check
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ module RuCaptcha
2
+ module Errors
3
+ class Configuration < StandardError; end
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module RuCaptcha
2
+ VERSION = "3.1.2"
3
+ end
@@ -0,0 +1,22 @@
1
+ module RuCaptcha
2
+ module ViewHelpers
3
+ def rucaptcha_input_tag(opts = {})
4
+ opts[:name] = "_rucaptcha"
5
+ opts[:type] = "text"
6
+ opts[:autocorrect] = "off"
7
+ opts[:autocapitalize] = "off"
8
+ opts[:pattern] = "[a-zA-Z0-9]*"
9
+ opts[:autocomplete] = "off"
10
+ opts[:maxlength] = RuCaptcha.config.length
11
+ tag(:input, opts)
12
+ end
13
+
14
+ def rucaptcha_image_tag(opts = {})
15
+ @rucaptcha_image_tag__image_path_in_this_request ||= "#{ru_captcha.root_path}?t=#{Time.now.strftime('%s%L')}"
16
+ opts[:class] = opts[:class] || "rucaptcha-image"
17
+ opts[:src] = @rucaptcha_image_tag__image_path_in_this_request
18
+ opts[:onclick] = "this.src = '#{ru_captcha.root_path}?t=' + Date.now();"
19
+ tag(:img, opts)
20
+ end
21
+ end
22
+ end
data/lib/rucaptcha.rb ADDED
@@ -0,0 +1,83 @@
1
+ require "rails"
2
+ require "action_controller"
3
+ require "active_support/all"
4
+
5
+ begin
6
+ # load the precompiled extension file
7
+ ruby_version = /(\d+\.\d+)/.match(::RUBY_VERSION)
8
+ require_relative "rucaptcha/#{ruby_version}/rucaptcha"
9
+ rescue LoadError
10
+ require "rucaptcha/rucaptcha"
11
+ end
12
+
13
+ require "rucaptcha/version"
14
+ require "rucaptcha/configuration"
15
+ require "rucaptcha/controller_helpers"
16
+ require "rucaptcha/view_helpers"
17
+ require "rucaptcha/cache"
18
+ require "rucaptcha/engine"
19
+ require "rucaptcha/errors/configuration"
20
+
21
+ module RuCaptcha
22
+ class << self
23
+ def config
24
+ return @config if defined?(@config)
25
+
26
+ @config = Configuration.new
27
+ @config.length = 5
28
+ @config.difficulty = 3
29
+ @config.expires_in = 2.minutes
30
+ @config.skip_cache_store_check = false
31
+
32
+ @config.cache_store = if Rails.application
33
+ Rails.application.config.cache_store
34
+ else
35
+ :mem_cache_store
36
+ end
37
+ @config.cache_store
38
+ @config
39
+ end
40
+
41
+ def configure(&block)
42
+ config.instance_exec(&block)
43
+ end
44
+
45
+ def generate
46
+ length = config.length
47
+
48
+ raise RuCaptcha::Errors::Configuration, "length config error, value must in 3..7" unless length.in?(3..7)
49
+
50
+ result = RuCaptchaCore.create(length, config.difficulty || 5)
51
+ [result[0].downcase, result[1].pack("c*")]
52
+ end
53
+
54
+ def check_cache_store!
55
+ cache_store = RuCaptcha.config.cache_store
56
+ store_name = cache_store.is_a?(Array) ? cache_store.first : cache_store
57
+ if %i[memory_store null_store file_store].include?(store_name)
58
+ RuCaptcha.config.cache_store = [:file_store, Rails.root.join("tmp/cache/rucaptcha/session")]
59
+
60
+ puts "
61
+
62
+ RuCaptcha's cache_store requirements are stored across processes and machines,
63
+ such as :mem_cache_store, :redis_store, or other distributed storage.
64
+ But your current set is #{cache_store}, it has changed to :file_store for working.
65
+ NOTE: :file_store is still not a good way, it only works with single server case.
66
+
67
+ Please make config file `config/initializers/rucaptcha.rb` to setup `cache_store`.
68
+ More infomation please read GitHub RuCaptcha README file.
69
+ https://github.com/huacnlee/rucaptcha
70
+
71
+ "
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ ActiveSupport.on_load(:action_controller) do
78
+ ActionController::Base.include RuCaptcha::ControllerHelpers
79
+ end
80
+
81
+ ActiveSupport.on_load(:action_view) do
82
+ include RuCaptcha::ViewHelpers
83
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rucaptcha
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.1.2
5
+ platform: aarch64-linux
6
+ authors:
7
+ - Jason Lee
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-01-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: railties
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '3.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rb_sys
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.9.18
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.9.18
41
+ description:
42
+ email: huacnlee@gmail.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - README.md
48
+ - Rakefile
49
+ - app/controllers/ru_captcha/captcha_controller.rb
50
+ - config/locales/rucaptcha.de-DE.yml
51
+ - config/locales/rucaptcha.en.yml
52
+ - config/locales/rucaptcha.pt-BR.yml
53
+ - config/locales/rucaptcha.zh-CN.yml
54
+ - config/locales/rucaptcha.zh-TW.yml
55
+ - config/routes.rb
56
+ - ext/rucaptcha/Cargo.lock
57
+ - ext/rucaptcha/Cargo.toml
58
+ - ext/rucaptcha/extconf.rb
59
+ - ext/rucaptcha/fonts/FuzzyBubbles-Regular.ttf
60
+ - ext/rucaptcha/fonts/Handlee-Regular.ttf
61
+ - ext/rucaptcha/src/captcha.rs
62
+ - ext/rucaptcha/src/lib.rs
63
+ - lib/rucaptcha.rb
64
+ - lib/rucaptcha/2.7/rucaptcha.so
65
+ - lib/rucaptcha/3.0/rucaptcha.so
66
+ - lib/rucaptcha/3.1/rucaptcha.so
67
+ - lib/rucaptcha/cache.rb
68
+ - lib/rucaptcha/configuration.rb
69
+ - lib/rucaptcha/controller_helpers.rb
70
+ - lib/rucaptcha/engine.rb
71
+ - lib/rucaptcha/errors/configuration.rb
72
+ - lib/rucaptcha/version.rb
73
+ - lib/rucaptcha/view_helpers.rb
74
+ homepage: https://github.com/huacnlee/rucaptcha
75
+ licenses:
76
+ - MIT
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '2.7'
87
+ - - "<"
88
+ - !ruby/object:Gem::Version
89
+ version: 3.2.dev
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubygems_version: 3.3.22
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: Captcha Gem for Rails, which generates captcha image by Rust.
100
+ test_files: []