rucaptcha 3.1.4.pre-aarch64-linux → 3.2.2-aarch64-linux
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +16 -0
- data/Rakefile +10 -5
- data/ext/rucaptcha/src/captcha.rs +132 -48
- data/ext/rucaptcha/src/lib.rs +15 -3
- data/lib/rucaptcha/3.1/rucaptcha.so +0 -0
- data/lib/rucaptcha/3.2/rucaptcha.so +0 -0
- data/lib/rucaptcha/configuration.rb +8 -0
- data/lib/rucaptcha/engine.rb +1 -1
- data/lib/rucaptcha/version.rb +1 -1
- data/lib/rucaptcha.rb +5 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 29ed42582e9a236c464d5e4704997b7cbe651af4f3d775ab84778fbba8590e84
|
4
|
+
data.tar.gz: 1595d58857150b8ab6c6df16c877434af6074ff8b10985f7437903f1b48d3a21
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8be780aa01aff1b9dac6d2d1f676f4f3933a78d76a77f4831981278a2604d1b9beef9c445edb6e616fd48c3aa005bb5d446e1530881aca6c3abd72061040eaf
|
7
|
+
data.tar.gz: dc1b62f56cd021532882d7196a9d925e818533a5f73573cf935ad4dd632f0677da6b3aebb722b456943bb0db30441bdce9beb49e35bc181e7c817e27aef7d95b
|
data/README.md
CHANGED
@@ -37,6 +37,7 @@ Create `config/initializers/rucaptcha.rb`
|
|
37
37
|
RuCaptcha.configure do
|
38
38
|
# Custom captcha code expire time if you need, default: 2 minutes
|
39
39
|
# self.expires_in = 120
|
40
|
+
|
40
41
|
# [Requirement / 重要]
|
41
42
|
# Store Captcha code where, this config more like Rails config.cache_store
|
42
43
|
# default: Read config info from `Rails.application.config.cache_store`
|
@@ -44,10 +45,25 @@ RuCaptcha.configure do
|
|
44
45
|
# 默认:会从 Rails 配置的 cache_store 里面读取相同的配置信息,并尝试用可以运行的方式,用于存储验证码字符
|
45
46
|
# 但如果是 [:null_store, :memory_store, :file_store] 之类的,你可以通过下面的配置项单独给 RuCaptcha 配置 cache_store
|
46
47
|
self.cache_store = :mem_cache_store
|
48
|
+
|
49
|
+
# If you wants disable `cache_store` check warning, you can do it, default: false
|
47
50
|
# 如果想要 disable cache_store 的 warning,就设置为 true,default false
|
48
51
|
# self.skip_cache_store_check = true
|
52
|
+
|
49
53
|
# Chars length, default: 5, allows: [3 - 7]
|
50
54
|
# self.length = 5
|
55
|
+
|
56
|
+
# Enable or disable Strikethrough, default: true
|
57
|
+
# self.line = true
|
58
|
+
|
59
|
+
# Enable or disable noise, default: false
|
60
|
+
# self.noise = false
|
61
|
+
|
62
|
+
# Set the image format, default: png, allows: [jpeg, png, webp]
|
63
|
+
# self.format = 'png'
|
64
|
+
|
65
|
+
# Custom mount path, default: '/rucaptcha'
|
66
|
+
# self.mount_path = '/rucaptcha'
|
51
67
|
end
|
52
68
|
```
|
53
69
|
|
data/Rakefile
CHANGED
@@ -27,9 +27,14 @@ end
|
|
27
27
|
RSpec::Core::RakeTask.new(:spec)
|
28
28
|
task default: :spec
|
29
29
|
|
30
|
+
def create_captcha(length = 5, difficulty = 5)
|
31
|
+
require "rucaptcha"
|
32
|
+
RuCaptchaCore.create(length, difficulty, false, false, "png")
|
33
|
+
end
|
34
|
+
|
30
35
|
task :preview do
|
31
36
|
require "rucaptcha"
|
32
|
-
res =
|
37
|
+
res = create_captcha()
|
33
38
|
warn "-------------------------\n#{res[0]}"
|
34
39
|
puts res[1].pack("c*")
|
35
40
|
end
|
@@ -38,11 +43,11 @@ task :memory do
|
|
38
43
|
require "rucaptcha"
|
39
44
|
require "memory_profiler"
|
40
45
|
|
41
|
-
|
46
|
+
create_captcha()
|
42
47
|
|
43
48
|
report = MemoryProfiler.report do
|
44
49
|
1000.times do
|
45
|
-
|
50
|
+
create_captcha()
|
46
51
|
end
|
47
52
|
end
|
48
53
|
|
@@ -57,10 +62,10 @@ task :benchmark do
|
|
57
62
|
require "rucaptcha"
|
58
63
|
require "benchmark/ips"
|
59
64
|
|
60
|
-
RuCaptchaCore.create(5, 5)
|
65
|
+
RuCaptchaCore.create(5, 5, true, true, "png")
|
61
66
|
|
62
67
|
Benchmark.ips do |x|
|
63
|
-
x.report("Generate image") { RuCaptchaCore.create(5, 5) }
|
68
|
+
x.report("Generate image") { RuCaptchaCore.create(5, 5, true, true, "png") }
|
64
69
|
x.compare!
|
65
70
|
end
|
66
71
|
end
|
@@ -1,6 +1,4 @@
|
|
1
|
-
use image::{ImageBuffer,
|
2
|
-
use imageproc::drawing::{draw_cubic_bezier_curve_mut, draw_text_mut};
|
3
|
-
use imageproc::noise::gaussian_noise_mut;
|
1
|
+
use image::{ImageBuffer, Rgba};
|
4
2
|
use rand::{thread_rng, Rng};
|
5
3
|
use rusttype::{Font, Scale};
|
6
4
|
use std::io::Cursor;
|
@@ -15,21 +13,21 @@ static FONT_BYTES1: &[u8; 145008] = include_bytes!("../fonts/FuzzyBubbles-Regula
|
|
15
13
|
static FONT_BYTES2: &[u8; 37792] = include_bytes!("../fonts/Handlee-Regular.ttf");
|
16
14
|
|
17
15
|
// 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),
|
16
|
+
static COLORS: [(u8, u8, u8, u8); 14] = [
|
17
|
+
(197, 166, 3, 255),
|
18
|
+
(187, 87, 5, 255),
|
19
|
+
(176, 7, 7, 255),
|
20
|
+
(186, 9, 56, 255),
|
21
|
+
(204, 11, 143, 255),
|
22
|
+
(124, 10, 190, 255),
|
23
|
+
(87, 0, 200, 255),
|
24
|
+
(61, 86, 168, 255),
|
25
|
+
(63, 166, 126, 255),
|
26
|
+
(69, 187, 48, 255),
|
27
|
+
(105, 208, 3, 255),
|
28
|
+
(160, 208, 3, 255),
|
29
|
+
(216, 219, 2, 255),
|
30
|
+
(50, 50, 50, 255),
|
33
31
|
];
|
34
32
|
|
35
33
|
static SCALE_SM: u32 = 32;
|
@@ -51,18 +49,18 @@ fn get_captcha(len: usize) -> Vec<String> {
|
|
51
49
|
}
|
52
50
|
|
53
51
|
#[allow(unused)]
|
54
|
-
fn get_color() ->
|
52
|
+
fn get_color() -> Rgba<u8> {
|
55
53
|
let rnd = rand_num(COLORS.len() - 1);
|
56
54
|
let c = COLORS[rnd];
|
57
|
-
|
55
|
+
Rgba([c.0, c.1, c.2, c.3])
|
58
56
|
}
|
59
57
|
|
60
|
-
fn get_colors(num: usize) -> Vec<
|
58
|
+
fn get_colors(num: usize) -> Vec<Rgba<u8>> {
|
61
59
|
let rnd = rand_num(COLORS.len());
|
62
60
|
let mut out = vec![];
|
63
61
|
for i in 0..num {
|
64
62
|
let c = COLORS[(rnd + i) % COLORS.len()];
|
65
|
-
out.push(
|
63
|
+
out.push(Rgba([c.0, c.1, c.2, c.3]))
|
66
64
|
}
|
67
65
|
|
68
66
|
out
|
@@ -80,13 +78,13 @@ fn get_font() -> Font<'static> {
|
|
80
78
|
}
|
81
79
|
}
|
82
80
|
|
83
|
-
fn get_image(width: usize, height: usize) -> ImageBuffer<
|
81
|
+
fn get_image(width: usize, height: usize) -> ImageBuffer<Rgba<u8>, Vec<u8>> {
|
84
82
|
ImageBuffer::from_fn(width as u32, height as u32, |_, _| {
|
85
|
-
image::
|
83
|
+
image::Rgba([255, 255, 255, 255])
|
86
84
|
})
|
87
85
|
}
|
88
86
|
|
89
|
-
fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<
|
87
|
+
fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>, lines: bool) {
|
90
88
|
let c = (image.width() - 20) / res.len() as u32;
|
91
89
|
let y = image.height() / 3 - 15;
|
92
90
|
|
@@ -107,7 +105,10 @@ fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<Rgb<u8>, Vec<u
|
|
107
105
|
// Draw line, ellipse first as background
|
108
106
|
for (i, _) in res.iter().enumerate() {
|
109
107
|
let line_color = line_colors[i];
|
110
|
-
|
108
|
+
|
109
|
+
if lines {
|
110
|
+
draw_interference_line(1, image, line_color);
|
111
|
+
}
|
111
112
|
draw_interference_ellipse(1, image, line_color);
|
112
113
|
}
|
113
114
|
|
@@ -121,7 +122,7 @@ fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<Rgb<u8>, Vec<u
|
|
121
122
|
for j in 0..(rand_num(3) + 1) as i32 {
|
122
123
|
// Draw text again with offset
|
123
124
|
let offset = j * (rand_num(2) as i32);
|
124
|
-
draw_text_mut(
|
125
|
+
imageproc::drawing::draw_text_mut(
|
125
126
|
image,
|
126
127
|
color,
|
127
128
|
10 + offset + (i as u32 * c) as i32,
|
@@ -137,7 +138,7 @@ fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<Rgb<u8>, Vec<u
|
|
137
138
|
}
|
138
139
|
}
|
139
140
|
|
140
|
-
fn draw_interference_line(num: usize, image: &mut ImageBuffer<
|
141
|
+
fn draw_interference_line(num: usize, image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>, color: Rgba<u8>) {
|
141
142
|
for _ in 0..num {
|
142
143
|
let width = image.width();
|
143
144
|
let height = image.height();
|
@@ -153,7 +154,7 @@ fn draw_interference_line(num: usize, image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>,
|
|
153
154
|
let ctrl_x2 = get_next((width / 12) as f32, width / 12 * 3);
|
154
155
|
let ctrl_y2 = get_next(x1, height - 5);
|
155
156
|
// Randomly draw bezier curves
|
156
|
-
draw_cubic_bezier_curve_mut(
|
157
|
+
imageproc::drawing::draw_cubic_bezier_curve_mut(
|
157
158
|
image,
|
158
159
|
(x1, y1),
|
159
160
|
(x2, y2),
|
@@ -166,8 +167,8 @@ fn draw_interference_line(num: usize, image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>,
|
|
166
167
|
|
167
168
|
fn draw_interference_ellipse(
|
168
169
|
num: usize,
|
169
|
-
image: &mut ImageBuffer<
|
170
|
-
color:
|
170
|
+
image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
|
171
|
+
color: Rgba<u8>,
|
171
172
|
) {
|
172
173
|
for _ in 0..num {
|
173
174
|
// max cycle width 20px
|
@@ -189,6 +190,9 @@ pub struct CaptchaBuilder {
|
|
189
190
|
width: usize,
|
190
191
|
height: usize,
|
191
192
|
complexity: usize,
|
193
|
+
line: bool,
|
194
|
+
noise: bool,
|
195
|
+
format: image::ImageFormat,
|
192
196
|
}
|
193
197
|
|
194
198
|
impl CaptchaBuilder {
|
@@ -198,6 +202,9 @@ impl CaptchaBuilder {
|
|
198
202
|
width: 220,
|
199
203
|
height: 70,
|
200
204
|
complexity: 5,
|
205
|
+
line: true,
|
206
|
+
noise: false,
|
207
|
+
format: image::ImageFormat::Png,
|
201
208
|
}
|
202
209
|
}
|
203
210
|
|
@@ -206,15 +213,26 @@ impl CaptchaBuilder {
|
|
206
213
|
self
|
207
214
|
}
|
208
215
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
216
|
+
pub fn line(mut self, line: bool) -> Self {
|
217
|
+
self.line = line;
|
218
|
+
self
|
219
|
+
}
|
220
|
+
|
221
|
+
pub fn noise(mut self, noise: bool) -> Self {
|
222
|
+
self.noise = noise;
|
223
|
+
self
|
224
|
+
}
|
225
|
+
|
226
|
+
pub fn format(mut self, format: &str) -> Self {
|
227
|
+
self.format = match format {
|
228
|
+
"png" => image::ImageFormat::Png,
|
229
|
+
"jpg" | "jpeg" => image::ImageFormat::Jpeg,
|
230
|
+
"webp" => image::ImageFormat::WebP,
|
231
|
+
_ => image::ImageFormat::Png,
|
232
|
+
};
|
213
233
|
|
214
|
-
|
215
|
-
|
216
|
-
// self
|
217
|
-
// }
|
234
|
+
self
|
235
|
+
}
|
218
236
|
|
219
237
|
pub fn complexity(mut self, complexity: usize) -> Self {
|
220
238
|
let mut complexity = complexity;
|
@@ -238,20 +256,86 @@ impl CaptchaBuilder {
|
|
238
256
|
let mut image = get_image(self.width, self.height);
|
239
257
|
|
240
258
|
// Loop to write the verification code string into the background image
|
241
|
-
cyclic_write_character(&res, &mut image);
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
259
|
+
cyclic_write_character(&res, &mut image, self.line);
|
260
|
+
|
261
|
+
if self.noise {
|
262
|
+
imageproc::noise::gaussian_noise_mut(
|
263
|
+
&mut image,
|
264
|
+
(self.complexity - 1) as f64,
|
265
|
+
((10 * self.complexity) - 10) as f64,
|
266
|
+
((5 * self.complexity) - 5) as u64,
|
267
|
+
);
|
268
|
+
}
|
249
269
|
|
250
270
|
let mut bytes: Vec<u8> = Vec::new();
|
251
271
|
image
|
252
|
-
.write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::
|
272
|
+
.write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Png)
|
253
273
|
.unwrap();
|
254
274
|
|
255
275
|
Captcha { text, image: bytes }
|
256
276
|
}
|
257
277
|
}
|
278
|
+
|
279
|
+
#[cfg(test)]
|
280
|
+
mod tests {
|
281
|
+
use super::*;
|
282
|
+
|
283
|
+
#[test]
|
284
|
+
fn test_format() {
|
285
|
+
let mut builder = CaptchaBuilder::new();
|
286
|
+
assert_eq!(builder.format, image::ImageFormat::Png);
|
287
|
+
|
288
|
+
builder = builder.format("jpg");
|
289
|
+
assert_eq!(builder.format, image::ImageFormat::Jpeg);
|
290
|
+
builder = builder.format("jpeg");
|
291
|
+
assert_eq!(builder.format, image::ImageFormat::Jpeg);
|
292
|
+
builder = builder.format("webp");
|
293
|
+
assert_eq!(builder.format, image::ImageFormat::WebP);
|
294
|
+
builder = builder.format("png");
|
295
|
+
assert_eq!(builder.format, image::ImageFormat::Png);
|
296
|
+
builder = builder.format("gif");
|
297
|
+
assert_eq!(builder.format, image::ImageFormat::Png);
|
298
|
+
}
|
299
|
+
|
300
|
+
#[test]
|
301
|
+
fn test_line() {
|
302
|
+
let mut builder = CaptchaBuilder::new();
|
303
|
+
assert!(builder.line);
|
304
|
+
|
305
|
+
builder = builder.line(false);
|
306
|
+
assert!(!builder.line);
|
307
|
+
}
|
308
|
+
|
309
|
+
#[test]
|
310
|
+
fn test_noise() {
|
311
|
+
let mut builder = CaptchaBuilder::new();
|
312
|
+
assert!(!builder.noise);
|
313
|
+
|
314
|
+
builder = builder.noise(true);
|
315
|
+
assert!(builder.noise);
|
316
|
+
}
|
317
|
+
|
318
|
+
#[test]
|
319
|
+
fn test_difficulty() {
|
320
|
+
let mut builder = CaptchaBuilder::new();
|
321
|
+
assert_eq!(builder.complexity, 5);
|
322
|
+
|
323
|
+
builder = builder.complexity(10);
|
324
|
+
assert_eq!(builder.complexity, 10);
|
325
|
+
|
326
|
+
builder = builder.complexity(11);
|
327
|
+
assert_eq!(builder.complexity, 10);
|
328
|
+
|
329
|
+
builder = builder.complexity(0);
|
330
|
+
assert_eq!(builder.complexity, 1);
|
331
|
+
}
|
332
|
+
|
333
|
+
#[test]
|
334
|
+
fn test_length() {
|
335
|
+
let mut builder = CaptchaBuilder::new();
|
336
|
+
assert_eq!(builder.length, 4);
|
337
|
+
|
338
|
+
builder = builder.length(10);
|
339
|
+
assert_eq!(builder.length, 10);
|
340
|
+
}
|
341
|
+
}
|
data/ext/rucaptcha/src/lib.rs
CHANGED
@@ -2,9 +2,21 @@ use magnus::{define_class, function, Error, Object};
|
|
2
2
|
|
3
3
|
mod captcha;
|
4
4
|
|
5
|
-
pub fn create(
|
5
|
+
pub fn create(
|
6
|
+
len: usize,
|
7
|
+
difficulty: usize,
|
8
|
+
line: bool,
|
9
|
+
noise: bool,
|
10
|
+
format: String,
|
11
|
+
) -> (String, Vec<u8>) {
|
6
12
|
let c = captcha::CaptchaBuilder::new();
|
7
|
-
let out = c
|
13
|
+
let out = c
|
14
|
+
.complexity(difficulty)
|
15
|
+
.length(len)
|
16
|
+
.line(line)
|
17
|
+
.noise(noise)
|
18
|
+
.format(&format)
|
19
|
+
.build();
|
8
20
|
|
9
21
|
(out.text, out.image)
|
10
22
|
}
|
@@ -12,7 +24,7 @@ pub fn create(len: usize, difficulty: usize) -> (String, Vec<u8>) {
|
|
12
24
|
#[magnus::init]
|
13
25
|
fn init() -> Result<(), Error> {
|
14
26
|
let class = define_class("RuCaptchaCore", Default::default())?;
|
15
|
-
class.define_singleton_method("create", function!(create,
|
27
|
+
class.define_singleton_method("create", function!(create, 5))?;
|
16
28
|
|
17
29
|
Ok(())
|
18
30
|
}
|
Binary file
|
Binary file
|
@@ -9,7 +9,15 @@ module RuCaptcha
|
|
9
9
|
attr_accessor :length
|
10
10
|
# Hard mode, default: 5, allows: [1..10]
|
11
11
|
attr_accessor :difficulty
|
12
|
+
# Enable or disable strikethrough lines on captcha image, default: true
|
13
|
+
attr_accessor :line
|
14
|
+
# Enable or disable noise on captcha image, default: false
|
15
|
+
attr_accessor :noise
|
16
|
+
# Image format allow: ['jpeg', 'png', 'webp'], default: 'png'
|
17
|
+
attr_accessor :format
|
12
18
|
# skip_cache_store_check, default: false
|
13
19
|
attr_accessor :skip_cache_store_check
|
20
|
+
# custom rucaptcha mount path, default: '/rucaptcha'
|
21
|
+
attr_accessor :mount_path
|
14
22
|
end
|
15
23
|
end
|
data/lib/rucaptcha/engine.rb
CHANGED
@@ -6,7 +6,7 @@ module RuCaptcha
|
|
6
6
|
# https://github.com/rails/rails/blob/3-2-stable/actionpack/lib/action_dispatch/routing/route_set.rb#L268
|
7
7
|
# `app.routes.prepend` start from Rails 3.2 - 5.0
|
8
8
|
app.routes.prepend do
|
9
|
-
mount RuCaptcha::Engine =>
|
9
|
+
mount RuCaptcha::Engine => RuCaptcha.config.mount_path
|
10
10
|
end
|
11
11
|
|
12
12
|
RuCaptcha.check_cache_store! unless RuCaptcha.config.skip_cache_store_check
|
data/lib/rucaptcha/version.rb
CHANGED
data/lib/rucaptcha.rb
CHANGED
@@ -28,6 +28,10 @@ module RuCaptcha
|
|
28
28
|
@config.difficulty = 3
|
29
29
|
@config.expires_in = 2.minutes
|
30
30
|
@config.skip_cache_store_check = false
|
31
|
+
@config.line = true
|
32
|
+
@config.noise = true
|
33
|
+
@config.format = "png"
|
34
|
+
@config.mount_path = "/rucaptcha"
|
31
35
|
|
32
36
|
@config.cache_store = if Rails.application
|
33
37
|
Rails.application.config.cache_store
|
@@ -47,7 +51,7 @@ module RuCaptcha
|
|
47
51
|
|
48
52
|
raise RuCaptcha::Errors::Configuration, "length config error, value must in 3..7" unless length.in?(3..7)
|
49
53
|
|
50
|
-
result = RuCaptchaCore.create(length, config.difficulty || 5)
|
54
|
+
result = RuCaptchaCore.create(length, config.difficulty || 5, config.line, config.noise, config.format)
|
51
55
|
[result[0].downcase, result[1].pack("c*")]
|
52
56
|
end
|
53
57
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rucaptcha
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.2.2
|
5
5
|
platform: aarch64-linux
|
6
6
|
authors:
|
7
7
|
- Jason Lee
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -89,11 +89,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
89
89
|
version: 3.3.dev
|
90
90
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
91
|
requirements:
|
92
|
-
- - "
|
92
|
+
- - ">="
|
93
93
|
- !ruby/object:Gem::Version
|
94
|
-
version:
|
94
|
+
version: '0'
|
95
95
|
requirements: []
|
96
|
-
rubygems_version: 3.4.
|
96
|
+
rubygems_version: 3.4.4
|
97
97
|
signing_key:
|
98
98
|
specification_version: 4
|
99
99
|
summary: Captcha Gem for Rails, which generates captcha image by Rust.
|